Autocompleterを読む その2
http://d.hatena.ne.jp/akm/20071128#1196263081の続きです。
今日は、動作する順番にソースコードを追ってみます。
Railsで、auto_complete_fieldメソッドを実行すると
new Ajax.Autocompleter(field_id, options[:update] || "#{field_id}_auto_complete", url_for(options[:url]), options);
って感じで生成されるので、Ajax.Autocompleterから見ていきます。
まず、newされたら呼び出されるinitializeは、
initialize: function(element, update, url, options) { this.baseInitialize(element, update, options); this.options.asynchronous = true; this.options.onComplete = this.onComplete.bind(this); this.options.defaultParams = this.options.parameters || null; this.url = url; },
こんな感じ。baseInitializeはAutocompleter.Base.prototypeに宣言されているメソッドです。前回書いたとおり、Ajax.Autocompleter.prototypeはAutocompleter.Base.prototypeをObject.extendされてるので、Ajax.Autocompleterは自身のメソッドのように呼び出すことができます。残りは、optionsとurlに値を設定しているだけなので、あとで考えましょう。
で、Autocompleter.Base.prototypeのbaseInitializeを見てみましょう。まずは、前半戦。
Autocompleter.Base.prototype = { baseInitialize: function(element, update, options) { this.element = $(element); this.update = $(update); this.hasFocus = false; this.changed = false; this.active = false; this.index = 0; this.entryCount = 0; if(this.setOptions) this.setOptions(options); else this.options = options || {}; this.options.paramName = this.options.paramName || this.element.name; this.options.tokens = this.options.tokens || []; this.options.frequency = this.options.frequency || 0.4; this.options.minChars = this.options.minChars || 1;
this.elementとthis.updateに引数の要素(名)から要素を設定しているところと、setOptionsというメソッドがあったら、それを呼び出しています。あとは諸々の属性とoptionsを設定してます。
baseInitialize後半戦。
this.options.onShow = this.options.onShow || function(element, update){ if(!update.style.position || update.style.position=='absolute') { update.style.position = 'absolute'; Position.clone(element, update, { setHeight: false, offsetTop: element.offsetHeight }); } Effect.Appear(update,{duration:0.15}); }; this.options.onHide = this.options.onHide || function(element, update){ new Effect.Fade(update,{duration:0.15}) }; if(typeof(this.options.tokens) == 'string') this.options.tokens = new Array(this.options.tokens); this.observer = null; this.element.setAttribute('autocomplete','off'); Element.hide(this.update); Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); },
onShow, onHide はoptionsに対して設定しておくとそれが有効になって、設定されてなかったらデフォルトの動作が設定されるのね。tokensに設定されているのがStringなら配列として設定される。observerはよくわからんけど、まあ応用編な感じでしょう。で、autocompleteをoffにしてブラウザの自動補完を切っておいて、一覧を表示するupdateを非表示に。入力フィールドからフォーカスが抜けた時と、キーが押された時のイベントハンドらをそれぞれ、onBlurとonKeyPressに設定してます。こっちはoptionsじゃないんだね。まあ、optionalな項目じゃないからだろうね。
じゃ次の処理はoptions.onShow, options.onHide, onBlur, onKeyPressですね。
まずonShowとonHideからだけど、これはデフォルトの処理は、上にある通り。まず、update.style.positionが何も設定されていないか、absoluteだったら?、style.positionをabsoluteに設定して、Position.cloneで高さの設定はせずに、offsetTopを入力フィールドのoffsetHeightに合わせて、その後updateをEffect.Appearで表示します。onHideの方は超簡単。Effect.Fadeでupdateを非表示にするだけっすね。
次、onBlur。
onBlur: function(event) { // needed to make click events working setTimeout(this.hide.bind(this), 250); this.hasFocus = false; this.active = false; },
フォーカスが外れると、250ms後にhideメソッドを呼び出して、フラグを設定。hideメソッドは簡単そうだからちらっと見てみましょう。
hide: function() { this.stopIndicator(); if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); if(this.iefix) Element.hide(this.iefix); },
stopIndicatorはたぶん、処理中を示すgif画像を止めるってことだと思われ。で、updateがまだ表示されてたらさっきのonHideを呼び出して、this.iefixが設定されてたらそれも非表示にします。this.iefixはおそらくIE6以前のabsoluteで表示するdivなどがinputやselectと重なり合うとzIndexを指定してもinputやselectが上に表示されてしまう現象を防ぐためのiframeだと予想。まあ、そんなに重要じゃないはず。
onKeyPress。まず、キーが押された時の挙動は、this.activeによって振舞いが違います。アクティブならキーが押されれた時に反応するけど、それ以外は特に何もしません。TABとRETURNなら・・・結構面倒臭くなってきたので、ざっくりとここまでをまとめてみます。今回の僕の目的はカスタマイズすることなので、メソッドの流れを中心に書きます。ちなみに<>は条件分岐で、()は何かの処理ね。
new Ajax.Autocompleter Ajax.Autocompleter#initialize Autocompleter.Base#baseInitialize (派生クラス)#setOptions (もしsetOptionsがあれば) (blurイベント発生) onBlur <250ms後> hide stopIndicator <表示されてたら> options.onHide (keypressイベント発生) onKeyPress <activeだったら> <KEY_TAB or KEY_RETURN> selectEntry (イベント中止) <KEY_ESC> hide (イベント中止) <KEY_LEFT or KEY_LEFT> (イベントハンドラ終了) <KEY_UP> markPrevious render <Safariとかだったら> (イベント中止) <KEY_DOWN> markNext render <Safariとかだったら> (イベント中止) <activeじゃない> <KEY_TAB or KEY_RETURN or Safariとか or keyCodeが0> (イベントハンドラ終了) (observerをクリア) <options.frequency秒後> onObserverEvent getToken <入力された文字列がoptions.minChars以上> startIndicator getUpdatedChoices <入力された文字列がoptions.minCharsよりも短い> hide
(イベントハンドラ終了)は単にイベントハンドラをreturnで抜けるだけだけど、(イベント中止)はイベントをなかったことにしちゃいます。
(keypressイベント発生)以降は、まだまだ増えるでしょうけど、いまはこんな感じで。ここで思い込みメソッド予想。
メソッド名 | 予想 |
---|---|
selectEntry | たぶん選択肢から選んだものを入力フィールドに反映させる |
markPrevious/markNext | 今選択している選択肢を前後に移動させる |
render | 選択肢を描画する |
startIndicator | 今ロードしてるんだぜ、ってgifを表示 |
getUpdatedChoices | 選択肢を更新。renderとの関係が気になるところ |
長くなるので、また明日。