Administrator
2023-04-21 195945efc5db921a4c9eb8cf9421c172273293f5
提交 | 用户 | 时间
58d006 1 /* =============================================================
A 2  * bootstrap3-typeahead.js v3.0.3
3  * https://github.com/bassjobsen/Bootstrap-3-Typeahead
4  * =============================================================
5  * Original written by @mdo and @fat
6  * =============================================================
7  * Copyright 2014 Bass Jobsen @bassjobsen
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  * http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * ============================================================ */
21
22
23 !function($){
24
25   "use strict";
26   // jshint laxcomma: true
27
28
29  /* TYPEAHEAD PUBLIC CLASS DEFINITION
30   * ================================= */
31
32   var Typeahead = function (element, options) {
33     this.$element = $(element);
34     this.options = $.extend({}, $.fn.typeahead.defaults, options);
35     this.matcher = this.options.matcher || this.matcher;
36     this.sorter = this.options.sorter || this.sorter;
37     this.select = this.options.select || this.select;
38     this.autoSelect = typeof this.options.autoSelect == 'boolean' ? this.options.autoSelect : true;
39     this.highlighter = this.options.highlighter || this.highlighter;
40     this.updater = this.options.updater || this.updater;
41     this.source = this.options.source;
42     this.delay = typeof this.options.delay == 'number' ? this.options.delay : 250;
43     this.$menu = $(this.options.menu);
44     this.shown = false;
45     this.listen();
46     this.showHintOnFocus = typeof this.options.showHintOnFocus == 'boolean' ? this.options.showHintOnFocus : false;
47   };
48
49   Typeahead.prototype = {
50
51     constructor: Typeahead
52
53   , select: function () {
54       var val = this.$menu.find('.active').data('value');
55       if(this.autoSelect || val) {
56         this.$element
57           .val(this.updater(val))
58           .change();
59       }
60       return this.hide();
61     }
62
63   , updater: function (item) {
64       return item;
65     }
66
67   , setSource: function (source) {
68       this.source = source;
69     }
70
71   , show: function () {
72       var pos = $.extend({}, this.$element.position(), {
73         height: this.$element[0].offsetHeight
74       }), scrollHeight;
75
76       scrollHeight = typeof this.options.scrollHeight == 'function' ?
77           this.options.scrollHeight.call() :
78           this.options.scrollHeight;
79
80       this.$menu
81         .insertAfter(this.$element)
82         .css({
83           top: pos.top + pos.height + scrollHeight
84         , left: pos.left
85         })
86         .show();
87
88       this.shown = true;
89       return this;
90     }
91
92   , hide: function () {
93       this.$menu.hide();
94       this.shown = false;
95       return this;
96     }
97
98   , lookup: function (query) {
99       var items;
100       if (typeof(query) != 'undefined' && query !== null) {
101         this.query = query;
102       } else {
103         this.query = this.$element.val() ||  '';
104       }
105
106       if (this.query.length < this.options.minLength) {
107         return this.shown ? this.hide() : this;
108       }
109
110       var worker = $.proxy(function() {
111         items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source;
112         if (items) {
113           this.process(items);
114         }
115       }, this)
116
117       clearTimeout(this.lookupWorker)
118       this.lookupWorker = setTimeout(worker, this.delay)
119     }
120
121   , process: function (items) {
122       var that = this;
123
124       items = $.grep(items, function (item) {
125         return that.matcher(item);
126       });
127
128       items = this.sorter(items);
129
130       if (!items.length) {
131         return this.shown ? this.hide() : this;
132       }
133
134       if (this.options.items == 'all') {
135         return this.render(items).show();
136       } else {
137         return this.render(items.slice(0, this.options.items)).show();
138       }
139     }
140
141   , matcher: function (item) {
142       return ~item.toLowerCase().indexOf(this.query.toLowerCase());
143     }
144
145   , sorter: function (items) {
146       var beginswith = []
147         , caseSensitive = []
148         , caseInsensitive = []
149         , item;
150
151       while ((item = items.shift())) {
152         if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item);
153         else if (~item.indexOf(this.query)) caseSensitive.push(item);
154         else caseInsensitive.push(item);
155       }
156
157       return beginswith.concat(caseSensitive, caseInsensitive);
158     }
159
160   , highlighter: function (item) {
161       var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
162       return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
163         return '<strong>' + match + '</strong>';
164       });
165     }
166
167   , render: function (items) {
168       var that = this;
169
170       items = $(items).map(function (i, item) {
171         i = $(that.options.item).data('value', item);
172         i.find('a').html(that.highlighter(item));
173         return i[0];
174       });
175
176       if (this.autoSelect) {
177         items.first().addClass('active');
178       }
179       this.$menu.html(items);
180       return this;
181     }
182
183   , next: function (event) {
184       var active = this.$menu.find('.active').removeClass('active')
185         , next = active.next();
186
187       if (!next.length) {
188         next = $(this.$menu.find('li')[0]);
189       }
190
191       next.addClass('active');
192     }
193
194   , prev: function (event) {
195       var active = this.$menu.find('.active').removeClass('active')
196         , prev = active.prev();
197
198       if (!prev.length) {
199         prev = this.$menu.find('li').last();
200       }
201
202       prev.addClass('active');
203     }
204
205   , listen: function () {
206       this.$element
207         .on('focus',    $.proxy(this.focus, this))
208         .on('blur',     $.proxy(this.blur, this))
209         .on('keypress', $.proxy(this.keypress, this))
210         .on('keyup',    $.proxy(this.keyup, this));
211
212       if (this.eventSupported('keydown')) {
213         this.$element.on('keydown', $.proxy(this.keydown, this));
214       }
215
216       this.$menu
217         .on('click', $.proxy(this.click, this))
218         .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
219         .on('mouseleave', 'li', $.proxy(this.mouseleave, this));
220     }
221   , destroy : function () {
222       this.$element.data('typeahead',null);
223       this.$element
224         .off('focus')
225         .off('blur')
226         .off('keypress')
227         .off('keyup');
228
229       if (this.eventSupported('keydown')) {
230         this.$element.off('keydown');
231       }
232
233       this.$menu.remove();
234     }
235   , eventSupported: function(eventName) {
236       var isSupported = eventName in this.$element;
237       if (!isSupported) {
238         this.$element.setAttribute(eventName, 'return;');
239         isSupported = typeof this.$element[eventName] === 'function';
240       }
241       return isSupported;
242     }
243
244   , move: function (e) {
245       if (!this.shown) return;
246
247       switch(e.keyCode) {
248         case 9: // tab
249         case 13: // enter
250         case 27: // escape
251           e.preventDefault();
252           break;
253
254         case 38: // up arrow
255           e.preventDefault();
256           this.prev();
257           break;
258
259         case 40: // down arrow
260           e.preventDefault();
261           this.next();
262           break;
263       }
264
265       e.stopPropagation();
266     }
267
268   , keydown: function (e) {
269       this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27]);
270       if (!this.shown && e.keyCode == 40) {
271         this.lookup("");
272       } else {
273         this.move(e);
274       }
275     }
276
277   , keypress: function (e) {
278       if (this.suppressKeyPressRepeat) return;
279       this.move(e);
280     }
281
282   , keyup: function (e) {
283       switch(e.keyCode) {
284         case 40: // down arrow
285         case 38: // up arrow
286         case 16: // shift
287         case 17: // ctrl
288         case 18: // alt
289           break;
290
291         case 9: // tab
292         case 13: // enter
293           if (!this.shown) return;
294           this.select();
295           break;
296
297         case 27: // escape
298           if (!this.shown) return;
299           this.hide();
300           break;
301         default:
302           this.lookup();
303       }
304
305       e.stopPropagation();
306       e.preventDefault();
307   }
308
309   , focus: function (e) {
310       if (!this.focused) {
311         this.focused = true;
312         if (this.options.minLength === 0 && !this.$element.val() || this.options.showHintOnFocus) {
313           this.lookup();
314         }
315       }
316     }
317
318   , blur: function (e) {
319       this.focused = false;
320       if (!this.mousedover && this.shown) this.hide();
321     }
322
323   , click: function (e) {
324       e.stopPropagation();
325       e.preventDefault();
326       this.select();
327       this.$element.focus();
328     }
329
330   , mouseenter: function (e) {
331       this.mousedover = true;
332       this.$menu.find('.active').removeClass('active');
333       $(e.currentTarget).addClass('active');
334     }
335
336   , mouseleave: function (e) {
337       this.mousedover = false;
338       if (!this.focused && this.shown) this.hide();
339     }
340
341   };
342
343
344   /* TYPEAHEAD PLUGIN DEFINITION
345    * =========================== */
346
347   var old = $.fn.typeahead;
348
349   $.fn.typeahead = function (option) {
350     var arg = arguments;
351     return this.each(function () {
352       var $this = $(this)
353         , data = $this.data('typeahead')
354         , options = typeof option == 'object' && option;
355       if (!data) $this.data('typeahead', (data = new Typeahead(this, options)));
356       if (typeof option == 'string') {
357         if (arg.length > 1) {
358           data[option].apply(data, Array.prototype.slice.call(arg ,1));
359         } else {
360           data[option]();
361         }
362       }
363     });
364   };
365
366   $.fn.typeahead.defaults = {
367     source: []
368   , items: 8
369   , menu: '<ul class="typeahead dropdown-menu"></ul>'
370   , item: '<li><a href="#"></a></li>'
371   , minLength: 1
372   , scrollHeight: 0
373   , autoSelect: true
374   };
375
376   $.fn.typeahead.Constructor = Typeahead;
377
378
379  /* TYPEAHEAD NO CONFLICT
380   * =================== */
381
382   $.fn.typeahead.noConflict = function () {
383     $.fn.typeahead = old;
384     return this;
385   };
386
387
388  /* TYPEAHEAD DATA-API
389   * ================== */
390
391   $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
392     var $this = $(this);
393     if ($this.data('typeahead')) return;
394     $this.typeahead($this.data());
395   });
396
397 }(window.jQuery);