今回と次回は、
入力補完機能を加えるAutocompleter
今回は、
Autocompleterを使うと、
Autocompleter を利用したテストページを作成してみましたので、
いったいどんな仕組みで、
Ajax.Autocompleterの方法
fooと打つと、
<ul>
<li>foobar</li>
<li>foofoo</li>
</ul>
入力エリアの下にこのHTMLを挿入して、
Autocompleter.Localの方法
Autocompleter.
そのためにまずは、
まずは、
foo|
----
foo
foobar
----
デフォルトではさらに、
foo|
----
foo
foobar
bar foobar
----
日本語の入力を常とする私たちには、
bar-foobar
bar,foobar
barfoobar (語がfooを含んでいるだけ)
デフォルトの前方検索と単語別前方検索の他に、
これらの検索アルゴリズムのコードについての説明は後述します。
キャレット位置からトークンを取り出す仕組み
ここからは、
Autocompleterの心臓部は、
- キャレットの位置を大雑把に求める
- キャレットの位置の付近にある文字列の切りだし
(この文字列をトークンといいます) - トークンから候補を導く
キャレットとは、
キャレットの位置を大雑把に求めるのには、
この方法では、
次に、
以前の内容 hoge,,hoge
現在の内容 hoge,foo,hoge
で、
キーレスポンスを犠牲にしないための工夫
ここで、
ここで、
小休止を検知するために、
0129: onKeyPress: function(event) {
...
0162: if(this.observer) clearTimeout(this.observer);
0163: this.observer =
0164: setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
this.
つまり、
このように、
controls.js
それでは、
0001: // script.aculo.us controls.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
0002:
0003: // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
0004: // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
0005: // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
0006: // Contributors:
0007: // Richard Livsey
0008: // Rahul Bhargava
0009: // Rob Wills
0010: //
0011: // script.aculo.us is freely distributable under the terms of an MIT-style license.
0012: // For details, see the script.aculo.us web site: http://script.aculo.us/
0013:
著作権表示です。
0014: // Autocompleter.Base handles all the autocompletion functionality
0015: // that's independent of the data source for autocompletion. This
0016: // includes drawing the autocompletion menu, observing keyboard
0017: // and mouse events, and similar.
0018: //
0019: // Specific autocompleters need to provide, at the very least,
0020: // a getUpdatedChoices function that will be invoked every time
0021: // the text inside the monitored textbox changes. This method
0022: // should get the text for which to provide autocompletion by
0023: // invoking this.getToken(), NOT by directly accessing
0024: // this.element.value. This is to allow incremental tokenized
0025: // autocompletion. Specific auto-completion logic (AJAX, etc)
0026: // belongs in getUpdatedChoices.
0027: //
0028: // Tokenized incremental autocompletion is enabled automatically
0029: // when an autocompleter is instantiated with the 'tokens' option
0030: // in the options parameter, e.g.:
0031: // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
0032: // will incrementally autocomplete with a comma as the token.
0033: // Additionally, ',' in the above example can be replaced with
0034: // a token array, e.g. { tokens: [',', '\n'] } which
0035: // enables autocompletion on multiple tokens. This is most
0036: // useful when one of the tokens is \n (a newline), as it
0037: // allows smart autocompletion after linebreaks.
0038:
このコメント文を日本語に訳すと、
このAutocompleter.
Baseに、 入力補完の機能が集約されています。補完のデータ元に応じた動作は別になっています。 この中身は、 補完メニューの描画、 キーボードやマウスのイベントの監視、 などです。 Autocompleterは、
getUpdatedChoices関数を最低限、 提供する必要があります。この関数は、 監視しているテキストボックスの内容の変更のつど、 呼び出されます。そのとき、 補完の対象になる文字列を取り出すのにthis. getToken()を呼んでください (this. element. valueに直接アクセスしないでください)。これでインクリメンタルなトークンによる入力補完になります。各々の補完の仕組み (Ajax,その他) は、 getUpdatedChoicesに納められています。 インクリメンタルなトークンによる入力補完は、
Autocompleterの生成時に'tokens'オプションを指定することで自動的に有効になります。このオプションは、 例えばこのように与えます。 new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
さらに、
上の例の ',' のところは配列にしてもよいので、 例えば { tokens: [',', '\n'] } とすると、 複数の区切りで補完できるようになります。区切りとして、 '\n' (改行) だけは必ず入れておくのがよいでしょう。改行の前後では補完が働くようにしたほうが便利だからです。
0039: if(typeof Effect == 'undefined')
0040: throw("controls.js requires including script.aculo.us' effects.js library");
0041:
effect.
Autocompleter.Base
0042: var Autocompleter = { }
0043: Autocompleter.Base = Class.create({
0044: baseInitialize: function(element, update, options) {
0045: element = $(element)
0046: this.element = element;
0047: this.update = $(update);
0048: this.hasFocus = false;
0049: this.changed = false;
0050: this.active = false;
0051: this.index = 0;
0052: this.entryCount = 0;
0053: this.oldElementValue = this.element.value;
0054:
0055: if(this.setOptions)
0056: this.setOptions(options);
0057: else
0058: this.options = options || { };
0059:
0060: this.options.paramName = this.options.paramName || this.element.name;
0061: this.options.tokens = this.options.tokens || [];
0062: this.options.frequency = this.options.frequency || 0.4;
0063: this.options.minChars = this.options.minChars || 1;
0064: this.options.onShow = this.options.onShow ||
0065: function(element, update){
0066: if(!update.style.position || update.style.position=='absolute') {
0067: update.style.position = 'absolute';
0068: Position.clone(element, update, {
0069: setHeight: false,
0070: offsetTop: element.offsetHeight
0071: });
0072: }
0073: Effect.Appear(update,{duration:0.15});
0074: };
0075: this.options.onHide = this.options.onHide ||
0076: function(element, update){ new Effect.Fade(update,{duration:0.15}) };
0077:
0078: if(typeof(this.options.tokens) == 'string')
0079: this.options.tokens = new Array(this.options.tokens);
0080: // Force carriage returns as token delimiters anyway
0081: if (!this.options.tokens.include('\n'))
0082: this.options.tokens.push('\n');
0083:
0084: this.observer = null;
0085:
0086: this.element.setAttribute('autocomplete','off');
0087:
0088: Element.hide(this.update);
0089:
0090: Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
0091: Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
0092: },
0093:
42~93行目はbaseInitializeです。ここでは様々な初期化が行われます。
48行目はhasFocusフラグの初期化です。
hasFocusがtrueのとき、
49行目はchangedフラグの初期化です。changedがtrueのとき、
50行目はactiveフラグの初期化です。候補メニューを表示中かどうかを表します。activeがtrueのとき、
onBlurイベントで入力エリアがフォーカスを失ったときfalseになり、
hasFocusでフォーカスがあるときtrueになり、
候補がひとつもないときにfalseになり、
補完が行われた直後にfalseになり、
入力内容がまだ短すぎて、
51行目はindexの初期化です。何番目の候補を内部的に選択中かを表します。候補の検索が行われた直後は0に設定され、
ユーザーが以下の入力方法で候補メニューを選択するのに応じて変わります。
- マウスクリック
- マウスオーバー
- 上矢印キー、
下矢印キー
52行目はentryCountの初期化です。これは候補の数を表します。候補の検索のたび、
53行目はoldElementValueの初期化です。以前の内容を保存しておき、
55行目は、
60行目はoptions.
61行目はoptions.
62行目はoptions.
63行目はoptions.
65行目は、
75行目は、
81行目で、
86行目で、
88行目で、
90行目で、
91行目で、
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行目で、
96行目以降で、
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行目で、
118行目で、
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行目で、
131~154行目で、
- TAB,RETURNキーで候補を確定する。
- ESCキーで候補メニューの表示をやめる。
- KEY_
LEFT, KEY_ RIGHTキーはなかったこととして扱う - KEY_
UP、 KEY_ DOWNキーで、 候補を選択する。
159行目で、
160行目で、
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は、
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は、
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行目で、
203行目で、
207行目で、
0213: markPrevious: function() {
0214: if(this.index > 0) this.index--
0215: else this.index = this.entryCount-1;
0216: this.getEntry(this.index).scrollIntoView(true);
0217: },
0218:
0219: markNext: function() {
0220: if(this.index < this.entryCount-1) this.index++
0221: else this.index = 0;
0222: this.getEntry(this.index).scrollIntoView(false);
0223: },
0224:
213~24行目のmarkPreviousとmarkNextは、
this.
216行目と222行目で、
0225: getEntry: function(index) {
0226: return this.update.firstChild.childNodes[index];
0227: },
0228:
225~228行目のgetEntryは、
0229: getCurrentEntry: function() {
0230: return this.getEntry(this.index);
0231: },
0232:
229~232行目のgetCurrentEntryは、
0233: selectEntry: function() {
0234: this.active = false;
0235: this.updateElement(this.getCurrentEntry());
0236: },
0237:
233~237行目のselectEntryは、
234行目で、
235行目で、
0238: updateElement: function(selectedElement) {
0239: if (this.options.updateElement) {
0240: this.options.updateElement(selectedElement);
0241: return;
0242: }
0243: var value = '';
0244: if (this.options.select) {
0245: var nodes = $(selectedElement).select('.' + this.options.select) || [];
0246: if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
0247: } else
0248: value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
0249:
238~266行目のupdateElementは、
239行目で、
243~249行目で、
このとき、
245行目で、
246行目で、
<li>
無駄無駄
<span class="selectme">foobar</span>
</li>
248行目で、
<li>
<span class="informal">無視無視</span>
foobar
</li>
0250: var bounds = this.getTokenBounds();
0251: if (bounds[0] != -1) {
0252: var newValue = this.element.value.substr(0, bounds[0]);
0253: var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
0254: if (whitespace)
0255: newValue += whitespace[0];
0256: this.element.value = newValue + value + this.element.value.substr(bounds[1]);
0257: } else {
0258: this.element.value = value;
0259: }
0260: this.oldElementValue = this.element.value;
0261: this.element.focus();
0262:
0263: if (this.options.afterUpdateElement)
0264: this.options.afterUpdateElement(this.element, selectedElement);
0265: },
0266:
250行目までで、
250行目で、
251~259行目で、
253~255行目で、
260行目で、
261行目で、
263行目で、
0267: updateChoices: function(choices) {
0268: if(!this.changed && this.hasFocus) {
0269: this.update.innerHTML = choices;
0270: Element.cleanWhitespace(this.update);
0271: Element.cleanWhitespace(this.update.down());
0272:
0273: if(this.update.firstChild && this.update.down().childNodes) {
0274: this.entryCount =
0275: this.update.down().childNodes.length;
0276: for (var i = 0; i < this.entryCount; i++) {
0277: var entry = this.getEntry(i);
0278: entry.autocompleteIndex = i;
0279: this.addObservers(entry);
0280: }
0281: } else {
0282: this.entryCount = 0;
0283: }
0284:
0285: this.stopIndicator();
0286: this.index = 0;
0287:
0288: if(this.entryCount==1 && this.options.autoSelect) {
0289: this.selectEntry();
0290: this.hide();
0291: } else {
0292: this.render();
0293: }
0294: }
0295: },
0296:
267~296行目のupdateChoicesは、
例えば、
<ul>
<li>
foo
</li>
<li>
<span class="informal">無視無視</span>
foobar
</li>
</ul>
270行目と271行目で、
278、
285行目で、
286行目で、
288行目で、
0297: addObservers: function(element) {
0298: Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
0299: Event.observe(element, "click", this.onClick.bindAsEventListener(this));
0300: },
0301:
297~301行目のaddObserversは、
0302: onObserverEvent: function() {
0303: this.changed = false;
0304: this.tokenBounds = null;
0305: if(this.getToken().length>=this.options.minChars) {
0306: this.getUpdatedChoices();
0307: } else {
0308: this.active = false;
0309: this.hide();
0310: }
0311: this.oldElementValue = this.element.value;
0312: },
0313:
302~313行目は、
304行目で、
305行目で、
306行目で、
308、
311行目で、
0314: getToken: function() {
0315: var bounds = this.getTokenBounds();
0316: return this.element.value.substring(bounds[0], bounds[1]).strip();
0317: },
0318:
314~318行目のgetTokenは、
0319: getTokenBounds: function() {
0320: if (null != this.tokenBounds) return this.tokenBounds;
0321: var value = this.element.value;
0322: if (value.strip().empty()) return [-1, 0];
0323: var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
0324: var offset = (diff == this.oldElementValue.length ? 1 : 0);
0325: var prevTokenPos = -1, nextTokenPos = value.length;
0326: var tp;
0327: for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
0328: tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
0329: if (tp > prevTokenPos) prevTokenPos = tp;
0330: tp = value.indexOf(this.options.tokens[index], diff + offset);
0331: if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
0332: }
0333: return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
0334:}
0335:});
0336:
319~336行目のgetTokenBoundsを見ていきましょう。これは、
返り値は[a,b]の形で、
options.
320行目では、
if (前回の結果を消さずにこの関数を呼び出した場合) return 前回の内容;
322行目では、
if (入力エリアの内容が空かスペースだけの場合) return [-1,0];
323行目では、
324~332行目では、
0337: Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
0338: var boundary = Math.min(newS.length, oldS.length);
0339: for (var index = 0; index < boundary; ++index)
0340: if (newS[index] != oldS[index])
0341: return index;
0342: return boundary;
0343: };
0344:
337~344行目のgetFirstDifferencePosは、
以上でAutocompleter.
Ajax.Autocompleter
0345: Ajax.Autocompleter = Class.create(Autocompleter.Base, {
0346: initialize: function(element, update, url, options) {
0347: this.baseInitialize(element, update, options);
0348: this.options.asynchronous = true;
0349: this.options.onComplete = this.onComplete.bind(this);
0350: this.options.defaultParams = this.options.parameters || null;
0351: this.url = url;
0352: },
0353:
345~353行目は、
348行目のoptions.
349行目のoptions.
350行目のoptions.
351行目のurlは、
0354: getUpdatedChoices: function() {
0355: this.startIndicator();
0356:
0357: var entry = encodeURIComponent(this.options.paramName) + '=' +
0358: encodeURIComponent(this.getToken());
0359:
0360: this.options.parameters = this.options.callback ?
0361: this.options.callback(this.element, entry) : entry;
0362:
0363: if(this.options.defaultParams)
0364: this.options.parameters += '&' + this.options.defaultParams;
0365:
0366: new Ajax.Request(this.url, this.options);
0367: },
0368:
354~368行目のgetUpdatedChoicesは、
355行目でインジケータを表示し、
357行目でAjaxクエリパラメータを、
360行目でライブラリの利用者がクエリパラメータを自由に編集できるように、
364行目でクエリパラメータにdefaultParamsをさらに追加します。
366行目でAjaxで候補を問い合わせます。
0369: onComplete: function(request) {
0370: this.updateChoices(request.responseText);
0371: }
0372: });
0373:
369~373行目のonCompleteは、
Autocompleter.Local
0374: // The local array autocompleter. Used when you'd prefer to
0375: // inject an array of autocompletion options into the page, rather
0376: // than sending out Ajax queries, which can be quite slow sometimes.
0377: //
0378: // The constructor takes four parameters. The first two are, as usual,
0379: // the id of the monitored textbox, and id of the autocompletion menu.
0380: // The third is the array you want to autocomplete from, and the fourth
0381: // is the options block.
0382: //
0383: // Extra local autocompletion options:
0384: // - choices - How many autocompletion choices to offer
0385: //
0386: // - partialSearch - If false, the autocompleter will match entered
0387: // text only at the beginning of strings in the
0388: // autocomplete array. Defaults to true, which will
0389: // match text at the beginning of any *word* in the
0390: // strings in the autocomplete array. If you want to
0391: // search anywhere in the string, additionally set
0392: // the option fullSearch to true (default: off).
0393: //
0394: // - fullSsearch - Search anywhere in autocomplete array strings.
0395: //
0396: // - partialChars - How many characters to enter before triggering
0397: // a partial match (unlike minChars, which defines
0398: // how many characters are required to do any match
0399: // at all). Defaults to 2.
0400: //
0401: // - ignoreCase - Whether to ignore case when autocompleting.
0402: // Defaults to true.
0403: //
0404: // It's possible to pass in a custom function as the 'selector'
0405: // option, if you prefer to write your own autocompletion logic.
0406: // In that case, the other options above will not apply unless
0407: // you support them.
このコメント文を日本語に訳すと以下のとおりです。
ローカルな配列の入力補完。補完したい語の配列をページに埋め込むことで、
いちいちサーバにAjaxで問い合わせずに済むようになります (Ajaxは場合によっては、 かなり遅いことがあるからです)。 コンストラクタは4つの引数をとります。最初の2つは、 いつもどおり、 監視するテキストボックスのidと、 候補メニューを表示するメニューのidです。3番めは、 補完したい語の配列、 4番めは、 オプションです。 ローカル入力補完に特有のオプション:
カスタマイズした関数を'selector'オプションに渡すことで、自前の補完ロジックを使うこともできます。 その場合は、上記のオプションは扱われません(あなたがサポートすれば別ですが)。
- - choices -
- 候補をいくつまで示すか。
- - partialSearch -
- もしfalseなら、
語の配列にある文字列の中で、 入力されたテキストと前方一致するものだけが、 候補になります。デフォルトではtrueで、 この場合、 配列の語の文字列について、 文字列中の単語ごとに前方一致が探されます。もし、 文字列の全体について探したければ、 さらにfullSearchオプションをtrueにしてください (デフォルトではoff)。 - - fullSearch -
- 配列の語の文字列の全体を検索します。
- - partialChars -
- 単語別一致検索が動くのに必要な入力の文字数
(minCharsと違って、 検索の方法に関わらず何文字必要かです)。デフォルトは2。 - - ignoreCase -
- 大文字小文字を無視して補完するかどうか。デフォルトはtrue。
0409: Autocompleter.Local = Class.create(Autocompleter.Base, {
0410: initialize: function(element, update, array, options) {
0411: this.baseInitialize(element, update, options);
0412: this.options.array = array;
0413: },
0414:
410~414行目は、
0415: getUpdatedChoices: function() {
0416: this.updateChoices(this.options.selector(this));
0417: },
0418:
415~418行目は、
0419: setOptions: function(options) {
0420: this.options = Object.extend({
0421: choices: 10,
0422: partialSearch: true,
0423: partialChars: 2,
0424: ignoreCase: true,
0425: fullSearch: false,
0426: selector: function(instance) {
0427: var ret = []; // Beginning matches
0428: var partial = []; // Inside matches
0429: var entry = instance.getToken();
0430: var count = 0;
0431:
0432: for (var i = 0; i < instance.options.array.length &&
0433: ret.length < instance.options.choices ; i++) {
0434:
0435: var elem = instance.options.array[i];
0436: var foundPos = instance.options.ignoreCase ?
0437: elem.toLowerCase().indexOf(entry.toLowerCase()) :
0438: elem.indexOf(entry);
0439:
0440: while (foundPos != -1) {
0441: if (foundPos == 0 && elem.length != entry.length) {
0442: ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
0443: elem.substr(entry.length) + "</li>");
0444: break;
0445: } else if (entry.length >= instance.options.partialChars &&
0446: instance.options.partialSearch && foundPos != -1) {
0447: if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
0448: partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
0449: elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
0450: foundPos + entry.length) + "</li>");
0451: break;
0452: }
0453: }
0454:
0455: foundPos = instance.options.ignoreCase ?
0456: elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
0457: elem.indexOf(entry, foundPos + 1);
0458:
0459: }
0460: }
0461: if (partial.length)
0462: ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
0463: return "<ul>" + ret.join('') + "</ul>";
0464: }
0465: }, options || { });
0466: }
0467:});
0468:
419~468行目は、
ここのselectorに、
436行目で、
440行目からのループは、
見つかった候補のリストは、