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