PageSourceSearch

https://otchiphop.com/js/lib/bootstrap/typeahead/bootstrap-typeahead.js

js otchiphop.com collected 2026-10-03 02:37:43 UTC 18,265 bytes, 580 lines download raw bytes

1//  ----------------------------------------------------------------------------
2//
3//  bootstrap-typeahead.js  
4//
5//  Twitter Bootstrap Typeahead Plugin
6//  v1.2.2
7//  https://github.com/tcrosen/twitter-bootstrap-typeahead
8//
9//
10//  Author
11//  ----------
12//  Terry Rosen
13//  [email protected] | @rerrify | github.com/tcrosen/
14//
15//
16//  Description
17//  ----------
18//  Custom implementation of Twitter's Bootstrap Typeahead Plugin
19//  http://twitter.github.com/bootstrap/javascript.html#typeahead
20//
21//
22//  Requirements
23//  ----------
24//  jQuery 1.7+
25//  Twitter Bootstrap 2.0+
26//
27//  ----------------------------------------------------------------------------
28
29!
30function ($) {
31
32    "use strict";
33
34    //------------------------------------------------------------------
35    //
36    //  Constructor
37    //
38    var Typeahead = function (element, options) {
39        this.$element = $(element);
40        this.options = $.extend(true, {}, $.fn.typeahead.defaults, options);
41        this.$menu = $(this.options.menu).appendTo('body');
42        this.shown = false;
43
44        // Method overrides    
45        this.eventSupported = this.options.eventSupported || this.eventSupported;
46        this.grepper = this.options.grepper || this.grepper;
47        this.highlighter = this.options.highlighter || this.highlighter;
48        this.lookup = this.options.lookup || this.lookup;
49        this.matcher = this.options.matcher || this.matcher;
50        this.render = this.options.render || this.render;
51        this.select = this.options.select || this.select;
52        this.sorter = this.options.sorter || this.sorter;
53        this.source = this.options.source || this.source;        
54                
55        if (!this.source.length) {
56            var ajax = this.options.ajax;
57
58            if (typeof ajax === 'string') {
59                this.ajax = $.extend({}, $.fn.typeahead.defaults.ajax, { url: ajax });
60            } else {
61                this.ajax = $.extend({}, $.fn.typeahead.defaults.ajax, ajax);
62            }
63
64            if (!this.ajax.url) {
65                this.ajax = null;
66            }
67        }
68
69        this.listen();        
70    }
71
72    Typeahead.prototype = {
73
74        constructor: Typeahead,
75
76        //=============================================================================================================
77        //
78        //  Utils
79        //
80        //=============================================================================================================
81
82        //------------------------------------------------------------------
83        //
84        //  Check if an event is supported by the browser eg. 'keypress'
85        //  * This was included to handle the "exhaustive deprecation" of jQuery.browser in jQuery 1.8
86        //
87        eventSupported: function(eventName) {         
88            var isSupported = (eventName in this.$element);
89
90            if (!isSupported) {
91              this.$element.setAttribute(eventName, 'return;');
92              isSupported = typeof this.$element[eventName] === 'function';
93            }
94
95            return isSupported;
96        },
97
98        //=============================================================================================================
99        //
100        //  AJAX
101        //
102        //=============================================================================================================
103
104        //------------------------------------------------------------------
105        //
106        //  Handle AJAX source 
107        //
108        ajaxer: function () { 
109            var that = this,
110                query = that.$element.val();
111            
112            if (query === that.query) {
113                return that;
114            }
115    
116            // Query changed
117            that.query = query;
118
119            // Cancel last timer if set
120            if (that.ajax.timerId) {
121                clearTimeout(that.ajax.timerId);
122                that.ajax.timerId = null;
123            }
124            
125            if (!query || query.length < that.ajax.triggerLength) {
126                // Cancel the ajax callback if in progress
127                if (that.ajax.xhr) {
128                    that.ajax.xhr.abort();
129                    that.ajax.xhr = null;
130                    that.ajaxToggleLoadClass(false);
131                }
132
133                return that.shown ? that.hide() : that;
134            }
135                    
136            // Query is good to send, set a timer
137            that.ajax.timerId = setTimeout(function() {
138                $.proxy(that.ajaxExecute(query), that)
139            }, that.ajax.timeout);
140                    
141            return that;
142        },
143
144        //------------------------------------------------------------------
145        //
146        //  Execute an AJAX request
147        //
148        ajaxExecute: function(query) {
149            this.ajaxToggleLoadClass(true);
150            
151            // Cancel last call if already in progress
152            if (this.ajax.xhr) this.ajax.xhr.abort();
153            
154            var params = this.ajax.preDispatch ? this.ajax.preDispatch(query) : { query : query };
155            var jAjax = (this.ajax.method === "post") ? $.post : $.get;
156            this.ajax.xhr = jAjax(this.ajax.url, params, $.proxy(this.ajaxLookup, this));
157            this.ajax.timerId = null;
158        },
159    
160        //------------------------------------------------------------------
161        //
162        //  Perform a lookup in the AJAX results
163        //
164        ajaxLookup: function (data) { 
165            var items;
166            
167            this.ajaxToggleLoadClass(false);
168
169            if (!this.ajax.xhr) return;
170            
171            if (this.ajax.preProcess) {
172                data = this.ajax.preProcess(data);
173            }
174
175            // Save for selection retreival
176            this.ajax.data = data;
177
178            items = this.grepper(this.ajax.data);
179    
180            if (!items || !items.length) {
181                return this.shown ? this.hide() : this;
182            }
183
184            this.ajax.xhr = null;
185
186            return this.render(items.slice(0, this.options.items)).show();
187        },
188        
189        //------------------------------------------------------------------
190        //
191        //  Toggle the loading class
192        //
193        ajaxToggleLoadClass: function (enable) {
194            if (!this.ajax.loadingClass) return;
195            this.$element.toggleClass(this.ajax.loadingClass, enable);
196        },
197
198        //=============================================================================================================
199        //
200        //  Data manipulation
201        //
202        //=============================================================================================================
203
204        //------------------------------------------------------------------
205        //
206        //  Search source
207        //
208        lookup: function (event) {
209            var that = this,
210                items;
211
212            if (that.ajax) {
213                that.ajaxer();
214            }
215            else {
216                that.query = that.$element.val();
217
218                if (!that.query) {
219                    return that.shown ? that.hide() : that;
220                }
221                
222                items = that.grepper(that.source);
223                
224                if (!items || !items.length) {
225                    return that.shown ? that.hide() : that;
226                }
227
228                return that.render(items.slice(0, that.options.items)).show();
229            }
230        },
231
232        //------------------------------------------------------------------
233        //
234        //  Filters relevent results 
235        //
236        grepper: function(data) {
237            var that = this,
238                items;
239
240            if (data && data.length && !data[0].hasOwnProperty(that.options.display)) {                
241                return null;
242            } 
243
244            items = $.grep(data, function (item) {
245                return that.matcher(item[that.options.display], item);
246            });
247
248            return this.sorter(items);                
249        },
250
251        //------------------------------------------------------------------
252        //
253        //  Looks for a match in the source
254        //
255        matcher: function (item) {
256            return ~item.toLowerCase().indexOf(this.query.toLowerCase());
257        },
258
259        //------------------------------------------------------------------
260        //
261        //  Sorts the results
262        //
263        sorter: function (items) {
264            var that = this,
265                beginswith = [],
266                caseSensitive = [],
267                caseInsensitive = [],
268                item;
269
270            while (item = items.shift()) {
271                if (!item[that.options.display].toLowerCase().indexOf(this.query.toLowerCase())) {
272                    beginswith.push(item);
273                }
274                else if (~item[that.options.display].indexOf(this.query)) {
275                    caseSensitive.push(item);
276                }
277                else {
278                    caseInsensitive.push(item);
279                }
280            }
281
282            return beginswith.concat(caseSensitive, caseInsensitive);
283        },       
284
285        //=============================================================================================================
286        //
287        //  DOM manipulation
288        //
289        //=============================================================================================================
290
291        //------------------------------------------------------------------
292        //
293        //  Shows the results list
294        //
295        show: function () {
296            var pos = $.extend({}, this.$element.offset(), {
297                height: this.$element[0].offsetHeight
298            });
299
300            this.$menu.css({
301                top: pos.top + pos.height,
302                left: pos.left
303            });
304
305            this.$menu.show();
306            this.shown = true;
307
308            return this;
309        },
310
311        //------------------------------------------------------------------
312        //
313        //  Hides the results list
314        //
315        hide: function () {
316            this.$menu.hide();
317            this.shown = false;
318            return this;
319        },
320        
321        //------------------------------------------------------------------
322        //
323        //  Highlights the match(es) within the results
324        //
325        highlighter: function (item) {
326            var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
327            return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
328                return '<strong>' + match + '</strong>';
329            });
330        },
331
332        //------------------------------------------------------------------
333        //
334        //  Renders the results list
335        //
336        render: function (items) {
337            var that = this;
338
339            items = $(items).map(function (i, item) {
340                i = $(that.options.item).attr('data-value', item[that.options.val]);
341                i.find('a').html(that.highlighter(item[that.options.display], item));
342                return i[0];
343            });
344
345            items.first().addClass('active');
346            this.$menu.html(items);
347            return this;
348        },
349
350        //------------------------------------------------------------------
351        //
352        //  Item is selected
353        //
354        select: function () {
355            var $selectedItem = this.$menu.find('.active');
356            this.$element.val($selectedItem.text()).change();
357            this.options.itemSelected($selectedItem, $selectedItem.attr('data-value'), $selectedItem.text());
358            return this.hide();
359        },
360
361        //------------------------------------------------------------------
362        //
363        //  Selects the next result
364        //
365        next: function (event) {
366            var active = this.$menu.find('.active').removeClass('active');
367            var next = active.next();
368
369            if (!next.length) {
370                next = $(this.$menu.find('li')[0]);
371            }
372
373            next.addClass('active');
374        },
375
376        //------------------------------------------------------------------
377        //
378        //  Selects the previous result
379        //
380        prev: function (event) {
381            var active = this.$menu.find('.active').removeClass('active');
382            var prev = active.prev();
383
384            if (!prev.length) {
385                prev = this.$menu.find('li').last();
386            }
387
388            prev.addClass('active');
389        },
390
391        //=============================================================================================================
392        //
393        //  Events
394        //
395        //=============================================================================================================
396
397        //------------------------------------------------------------------
398        //
399        //  Listens for user events
400        //
401        listen: function () {
402            this.$element.on('blur', $.proxy(this.blur, this))
403                         .on('keyup', $.proxy(this.keyup, this));
404
405    		if (this.eventSupported('keydown')) {
406				this.$element.on('keydown', $.proxy(this.keypress, this));
407			} else {
408				this.$element.on('keypress', $.proxy(this.keypress, this));
409			}
410
411            this.$menu.on('click', $.proxy(this.click, this))
412                      .on('mouseenter', 'li', $.proxy(this.mouseenter, this));
413        },
414
415        //------------------------------------------------------------------
416        //
417        //  Handles a key being raised up
418        //
419        keyup: function (e) {
420            e.stopPropagation();
421            e.preventDefault();
422
423            switch (e.keyCode) {
424                case 40:
425                    // down arrow
426                case 38:
427                    // up arrow
428                    break;
429                case 9:
430                    // tab
431                case 13:
432                    // enter
433                    if (!this.shown) {
434                        return;
435                    }
436                    this.select();
437                    break;
438                case 27:
439                    // escape
440                    this.hide();
441                    break;
442                default:
443                    this.lookup();
444            }
445        },
446
447        //------------------------------------------------------------------
448        //
449        //  Handles a key being pressed
450        //
451        keypress: function (e) {
452            e.stopPropagation();
453            if (!this.shown) {
454                return;
455            }
456
457            switch (e.keyCode) {
458                case 9:
459                    // tab
460                case 13:
461                    // enter
462                case 27:
463                    // escape
464                    e.preventDefault();
465                    break;
466                case 38:
467                    // up arrow
468                    e.preventDefault();
469                    this.prev();
470                    break;
471                case 40:
472                    // down arrow
473                    e.preventDefault();
474                    this.next();
475                    break;
476            }
477        },
478
479        //------------------------------------------------------------------
480        //
481        //  Handles cursor exiting the textbox
482        //
483        blur: function (e) {
484            var that = this;
485            e.stopPropagation();
486            e.preventDefault();
487            setTimeout(function () {
488                if (!that.$menu.is(':focus')) {
489                  that.hide();
490                }
491            }, 150)
492        },
493
494        //------------------------------------------------------------------
495        //
496        //  Handles clicking on the results list
497        //
498        click: function (e) {
499            e.stopPropagation();
500            e.preventDefault();
501            this.select();
502        },
503
504        //------------------------------------------------------------------
505        //
506        //  Handles the mouse entering the results list
507        //
508        mouseenter: function (e) {
509            this.$menu.find('.active').removeClass('active');
510            $(e.currentTarget).addClass('active');
511        }
512    }
513
514    //------------------------------------------------------------------
515    //
516    //  Plugin definition
517    //
518    $.fn.typeahead = function (option) {
519        return this.each(function () {
520            var $this = $(this),
521                data = $this.data('typeahead'),
522                options = typeof option === 'object' && option;
523
524            if (!data) {
525                $this.data('typeahead', (data = new Typeahead(this, options)));
526            }
527
528            if (typeof option === 'string') {
529                data[option]();
530            }
531        });
532    }
533
534    //------------------------------------------------------------------
535    //
536    //  Defaults
537    //
538    $.fn.typeahead.defaults = {
539        source: [],
540        items: 8,
541        menu: '<ul class="typeahead dropdown-menu"></ul>',
542        item: '<li><a href="#"></a></li>',
543        display: 'name',
544        val: 'id',
545        itemSelected: function () { },
546        ajax: {
547            url: null,
548            timeout: 300,
549            method: 'post',
550            triggerLength: 3,
551            loadingClass: null,
552            displayField: null,
553            preDispatch: null,
554            preProcess: null
555        }
556    }
557
558    $.fn.typeahead.Constructor = Typeahead;
559
560    //------------------------------------------------------------------
561    //
562    //  DOM-ready call for the Data API (no-JS implementation)
563    //    
564    //  Note: As of Bootstrap v2.0 this feature may be disabled using $('body').off('.data-api')    
565    //  More info here: https://github.com/twitter/bootstrap/tree/master/js
566    //
567    $(function () {
568        $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
569            var $this = $(this);
570
571            if ($this.data('typeahead')) {
572                return;
573            }
574
575            e.preventDefault();
576            $this.typeahead($this.data());
577        })
578    });
579
580} (window.jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.