comparison static/bootstrap/js/bootstrap-typeahead.js @ 61:130eccd396d8

Now using Boostrap with LESS.
author Ludovic Chabant <ludovic@chabant.com>
date Wed, 06 Feb 2013 08:22:31 -0800
parents
children a5a3d454eac9
comparison
equal deleted inserted replaced
60:8250c977bc50 61:130eccd396d8
1 /* =============================================================
2 * bootstrap-typeahead.js v2.2.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('blur', $.proxy(this.blur, this))
176 .on('keypress', $.proxy(this.keypress, this))
177 .on('keyup', $.proxy(this.keyup, this))
178
179 if (this.eventSupported('keydown')) {
180 this.$element.on('keydown', $.proxy(this.keydown, this))
181 }
182
183 this.$menu
184 .on('click', $.proxy(this.click, this))
185 .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
186 }
187
188 , eventSupported: function(eventName) {
189 var isSupported = eventName in this.$element
190 if (!isSupported) {
191 this.$element.setAttribute(eventName, 'return;')
192 isSupported = typeof this.$element[eventName] === 'function'
193 }
194 return isSupported
195 }
196
197 , move: function (e) {
198 if (!this.shown) return
199
200 switch(e.keyCode) {
201 case 9: // tab
202 case 13: // enter
203 case 27: // escape
204 e.preventDefault()
205 break
206
207 case 38: // up arrow
208 e.preventDefault()
209 this.prev()
210 break
211
212 case 40: // down arrow
213 e.preventDefault()
214 this.next()
215 break
216 }
217
218 e.stopPropagation()
219 }
220
221 , keydown: function (e) {
222 this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
223 this.move(e)
224 }
225
226 , keypress: function (e) {
227 if (this.suppressKeyPressRepeat) return
228 this.move(e)
229 }
230
231 , keyup: function (e) {
232 switch(e.keyCode) {
233 case 40: // down arrow
234 case 38: // up arrow
235 case 16: // shift
236 case 17: // ctrl
237 case 18: // alt
238 break
239
240 case 9: // tab
241 case 13: // enter
242 if (!this.shown) return
243 this.select()
244 break
245
246 case 27: // escape
247 if (!this.shown) return
248 this.hide()
249 break
250
251 default:
252 this.lookup()
253 }
254
255 e.stopPropagation()
256 e.preventDefault()
257 }
258
259 , blur: function (e) {
260 var that = this
261 setTimeout(function () { that.hide() }, 150)
262 }
263
264 , click: function (e) {
265 e.stopPropagation()
266 e.preventDefault()
267 this.select()
268 }
269
270 , mouseenter: function (e) {
271 this.$menu.find('.active').removeClass('active')
272 $(e.currentTarget).addClass('active')
273 }
274
275 }
276
277
278 /* TYPEAHEAD PLUGIN DEFINITION
279 * =========================== */
280
281 var old = $.fn.typeahead
282
283 $.fn.typeahead = function (option) {
284 return this.each(function () {
285 var $this = $(this)
286 , data = $this.data('typeahead')
287 , options = typeof option == 'object' && option
288 if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
289 if (typeof option == 'string') data[option]()
290 })
291 }
292
293 $.fn.typeahead.defaults = {
294 source: []
295 , items: 8
296 , menu: '<ul class="typeahead dropdown-menu"></ul>'
297 , item: '<li><a href="#"></a></li>'
298 , minLength: 1
299 }
300
301 $.fn.typeahead.Constructor = Typeahead
302
303
304 /* TYPEAHEAD NO CONFLICT
305 * =================== */
306
307 $.fn.typeahead.noConflict = function () {
308 $.fn.typeahead = old
309 return this
310 }
311
312
313 /* TYPEAHEAD DATA-API
314 * ================== */
315
316 $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
317 var $this = $(this)
318 if ($this.data('typeahead')) return
319 e.preventDefault()
320 $this.typeahead($this.data())
321 })
322
323 }(window.jQuery);