script.aculo.usを読み解く

第2回 controls.js(前編)Autocompleter

この記事を読むのに必要な時間:およそ 18.5 分

0094: show: function() {
0095:   if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
0096:   if(!this.iefix && 
0097:     (Prototype.Browser.IE) &&
0098:     (Element.getStyle(this.update, 'position')=='absolute')) {
0099:     new Insertion.After(this.update, 
0100:      '<iframe id="' + this.update.id + '_iefix" '+
0101:      'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
0102:      'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
0103:     this.iefix = $(this.update.id+'_iefix');
0104:   }
0105:   if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
0106: },
0107: 
0108: fixIEOverlapping: function() {
0109:   Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
0110:   this.iefix.style.zIndex = 1;
0111:   this.update.style.zIndex = 2;
0112:   Element.show(this.iefix);
0113: },
0114:

94~107行目のshowメソッドは,候補メニューを表示します。

95行目で,候補メニューが非表示状態であることを確認してから,options.onShowを呼びます。

96行目以降で,IEのバグを回避しています。回避するのにfixIEOverlappingを使っています。

0115: hide: function() {
0116:   this.stopIndicator();
0117:   if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
0118:   if(this.iefix) Element.hide(this.iefix);
0119: },
0120:

115~120行目のhideメソッドは,候補メニューを非表示にします。

116行目で,インジケータの表示を止め,
117行目で,候補メニューが表示状態であることを確認してから,options.onHideを呼びます。

118行目で,IEのバグを回避しています。

0121: startIndicator: function() {
0122:   if(this.options.indicator) Element.show(this.options.indicator);
0123: },
0124:  
0125: stopIndicator: function() {
0126:   if(this.options.indicator) Element.hide(this.options.indicator);
0127: },
0128:

121~128行目のstartIndicator,stopIndicatorはそれぞれ,インジケータ インジケータ の表示と非表示をするメソッドです。

0129: onKeyPress: function(event) {
0130:   if(this.active)
0131:     switch(event.keyCode) {
0132:      case Event.KEY_TAB:
0133:      case Event.KEY_RETURN:
0134:        this.selectEntry();
0135:        Event.stop(event);
0136:      case Event.KEY_ESC:
0137:        this.hide();
0138:        this.active = false;
0139:        Event.stop(event);
0140:        return;
0141:      case Event.KEY_LEFT:
0142:      case Event.KEY_RIGHT:
0143:        return;
0144:      case Event.KEY_UP:
0145:        this.markPrevious();
0146:        this.render();
0147:        Event.stop(event);
0148:        return;
0149:      case Event.KEY_DOWN:
0150:        this.markNext();
0151:        this.render();
0152:        Event.stop(event);
0153:        return;
0154:     }
0155:    else 
0156:      if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
0157:        (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
0158:  
0159:   this.changed = true;
0160:   this.hasFocus = true;
0161:  
0162:   if(this.observer) clearTimeout(this.observer);
0163:     this.observer = 
0164:       setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
0165: },
0166:

129~166行目のonKeyPressは,キー押下イベントのイベントハンドラです。

130行目で,activeフラグを見ています。これがtrueなら,候補メニューの表示中です。

131~154行目で,候補メニューの表示中は,以下の操作を受け付けます。

  • TAB,RETURNキーで候補を確定する。
  • ESCキーで候補メニューの表示をやめる。
  • KEY_LEFT, KEY_RIGHTキーはなかったこととして扱う
  • KEY_UP,KEY_DOWNキーで,候補を選択する。

159行目で,内容に更新があったことを意味するchangedフラグを立てています。

160行目で,フォーカスがあることを意味するhasFocusフラグを立てています。

162~164行目で,タイピングの小休止をタイマをうまく使って検知しています。上記の解説を参照してください。

0167: activate: function() {
0168:   this.changed = false;
0169:   this.hasFocus = true;
0170:   this.getUpdatedChoices();
0171: },
0172:

167~172行目はactivateです。この関数は現在使われていません。

0173: onHover: function(event) {
0174:   var element = Event.findElement(event, 'LI');
0175:   if(this.index != element.autocompleteIndex) 
0176:   {
0177:       this.index = element.autocompleteIndex;
0178:       this.render();
0179:   }
0180:   Event.stop(event);
0181: },
0182:

173行目~182行目のonHoverは,マウスカーソルが,候補メニューの要素の上にきたときのイベントハンドラです。後述する297行目のaddObserversで使われます。

174行目で,イベントの発生源であるカーソル下の候補メニューの要素を特定し,

177行目で,カーソル下の候補を,内部的に選択中にしています。

178行目で,候補メニューを再表示しています。

0183: onClick: function(event) {
0184:   var element = Event.findElement(event, 'LI');
0185:   this.index = element.autocompleteIndex;
0186:   this.selectEntry();
0187:   this.hide();
0188: },
0189:

183~189行目のonClickは,候補メニューがマウスクリックされたときのイベントハンドラです。

184行目でイベントの発生源に一番近い<li>要素を求めます。それが選択された候補です。

185行目で内部的に選択中にしてから,
186行目で確定します。

187行目で候補メニューを非表示にします。

0190: onBlur: function(event) {
0191:   // needed to make click events working
0192:   setTimeout(this.hide.bind(this), 250);
0193:   this.hasFocus = false;
0194:   this.active = false;     
0195: }, 
0196:

190~196行目のonBlurは,入力エリアがフォーカスを失ったときのイベントハンドラです。クリックイベントの混乱を避けるため,hide()する前に0.25秒の遅延をとっています。

193行目でhasFocusをfalseにして,フォーカスを失った状態であることを示しています。

194行目でactiveをfalseにして,メニューの表示をやめます。

0197: render: function() {
0198:   if(this.entryCount > 0) {
0199:     for (var i = 0; i < this.entryCount; i++)
0200:       this.index==i ? 
0201:         Element.addClassName(this.getEntry(i),"selected") : 
0202:     Element.removeClassName(this.getEntry(i),"selected");
0203:     if(this.hasFocus) { 
0204:       this.show();
0205:       this.active = true;
0206:     }
0207:   } else {
0208:     this.active = false;
0209:     this.hide();
0210:   }
0211: },
0212:

197~212行目のrenderは,候補メニューを描画する関数です。

201行目で,候補の中でも特に内部的に選択中のものには,class属性に"selected"をつけます。

203行目で,フォーカスがあれば,候補メニューを表示し,メニュー表示中を意味するactiveフラグを立てます。

207行目で,候補がひとつもなければ,候補メニューの表示をやめます。

著者プロフィール

源馬照明(げんまてるあき)

名古屋大学大学院多元数理科学研究科1年。学部生のときにSchemeの素晴らしさを知ったのをきっかけに,関数型言語の世界へ。JavaScriptに,ブラウザからすぐに試せる関数型言語としての魅力と将来性を感じている。

ブログ:Gemmaの日記