このコードを Script.aculo.us を読み込んだ後に読み込む。
// Script.aculo.us 拡張 Object.extend(Autocompleter.Base.prototype, { // for Opera ___pressReturn : false, // for Opera & Japanese // もとのコンストラクタを退避させる ___grandBaseInitialize: Autocompleter.Base.prototype.baseInitialize, // for Opera & Japanese baseInitialize: function() { // もとのコンストラクタを呼び出す this.___grandBaseInitialize.apply(this, $A(arguments)); // for Japanese // 日本語入力はブラウザによってはイベントを発生させないことがあるため、 // Form.Element.Observer を使って変更があった時点で // イベントを発生させる。 new Form.Element.Observer( this.element, 0.01, this.___onChange.bind(this)); // for Opera // Opera では Enter が押されると keypress イベントを // キャンセルしても submit されてしまうため submit イベントを // 直接キャンセルする必要がある。 if(this.element.form) { Event.observe(this.element.form, 'submit', this.___onSubmit.bindAsEventListener(this)); } }, // for Opera ___disableReturnSubmit: function() { this.___pressReturn = true; }, // for Opera ___enableReturnSubmit: function() { this.___pressReturn = false; }, // for Opera // Submit をキャプチャする。 ___onSubmit: function(event) { if(this.___pressReturn) { Event.stop(event); this.___enableReturnSubmit(); } }, // for Japanese // 日本語入力のイベントはブラウザ依存のため、 // onKeyPress でアクティブにするかどうかの判定は行わない。 onKeyPress: function(event) { if(this.active) { switch(event.keyCode) { case Event.KEY_RETURN: // for Opera // 一瞬だけ Return による Submit を抑止する。 this.___disableReturnSubmit(); setTimeout(this.___enableReturnSubmit.bind(this), 1); case Event.KEY_TAB: this.selectEntry(); Event.stop(event); case Event.KEY_ESC: this.hide(); this.active = false; Event.stop(event); return; case Event.KEY_LEFT: case Event.KEY_RIGHT: return; case Event.KEY_UP: this.markPrevious(); this.render(); if(navigator.appVersion.indexOf('AppleWebKit')>0) { Event.stop(event); } return; case Event.KEY_DOWN: this.markNext(); this.render(); if(navigator.appVersion.indexOf('AppleWebKit')>0) { Event.stop(event); } return; } } else { if( event.keyCode == Event.KEY_DOWN || event.keyCode == Event.KEY_UP) { this.changed = true; this.hasFocus = true; if(this.observer) { clearTimeout(this.observer); } this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); } } }, // forJapanese // this.element に変更があればメニューを開く。 ___onChange: function() { this.changed = true; this.hasFocus = true; if(this.observer) { clearTimeout(this.observer); } this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); } }); /*----------------------------------------------------------------------------*/ // prototype 拡張 var _setInterval = {}; var setIntervalEx = function(process, delay) { var entry; if(typeof process == 'string') { entry = new _setInterval.Entry(function(){eval(process);}, delay); } else if(typeof process == 'function') { entry = new _setInterval.Entry(process, delay); } else { throw Error('第一引数が不正です。'); } var id = _setInterval.queue.length; _setInterval.queue[id] = entry return id; }; var clearIntervalEx = function(id) { _setInterval.queue[id].loop = function(){}; }; _setInterval.queue = []; _setInterval.Entry = function(process, delay) { this.process = process; this.delay = delay; this.time = 0; }; _setInterval.Entry.prototype.loop = function(time) { this.time += time; while(this.time >= this.delay) { this.process(); this.time -= this.delay } }; _setInterval.lastTime = new Date().getTime(); setInterval(function() { var time = new Date().getTime(); var subTime = time - _setInterval.lastTime; _setInterval.lastTime = time; for(var i = 0; i < _setInterval.queue.length; i++) { _setInterval.queue[i].loop(subTime); } }, 10); Abstract.TimedObserver.prototype.registerCallback = function() { setIntervalEx(this.onTimerEvent.bind(this), this.frequency * 1000); }; Object.extend(Position, { ___page: function(forElement) { var valueT = 0, valueL = 0; var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; // Safari fix if (element.offsetParent==document.body) { if (Element.getStyle(element,'position')=='absolute') { break; } } } while (element = element.offsetParent); return [valueL, valueT]; }, clone: function(source, target) { var options = Object.extend({ setLeft: true, setTop: true, setWidth: true, setHeight: true, offsetTop: 0, offsetLeft: 0 }, arguments[2] || {}) // find page position of source source = $(source); var p = Position.___page(source); // find coordinate system to use target = $(target); var delta = [0, 0]; var parent = null; // delta [0,0] will do fine with position: fixed elements, // position:absolute needs offsetParent deltas if (Element.getStyle(target,'position') == 'absolute') { parent = Position.offsetParent(target); delta = Position.___page(parent); } // correct by body offsets (fixes Safari) if (parent == document.body) { delta[0] -= document.body.offsetLeft; delta[1] -= document.body.offsetTop; } // set position if(options.setLeft) { target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; } if(options.setTop) { target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; } if(options.setWidth) { target.style.width = source.offsetWidth + 'px'; } if(options.setHeight) { target.style.height = source.offsetHeight + 'px'; } } });
Autocompleter は拡張された二つの種類がある。JavaScript のような言語だと、スーパークラスに直接コードを注入できるので便利だなぁと実感。
コードを使う場合は必ずコメントください。