unignore bower_components
[bootswatch] / 2 / bower_components / bootstrap / js / bootstrap-typeahead.js
1 /* =============================================================
2  * bootstrap-typeahead.js v2.3.2
3  * http://getbootstrap.com/2.3.2/javascript.html#typeahead
4  * =============================================================
5  * Copyright 2013 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);