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