1/* ============================================================= 2 * bootstrap-typeahead.js v2.0.0 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!function( $ ){ 21 22 "use strict" 23 24 var Typeahead = function ( element, options ) { 25 this.$element = $(element) 26 this.options = $.extend({}, $.fn.typeahead.defaults, options) 27 this.matcher = this.options.matcher || this.matcher 28 this.sorter = this.options.sorter || this.sorter 29 this.highlighter = this.options.highlighter || this.highlighter 30 this.$menu = $(this.options.menu).appendTo('body') 31 if (this.options.scroll) 32 this.$menu.addClass('scroll'); 33 this.source = this.options.source 34 this.onselect = this.options.onselect 35 this.strings = true 36 this.shown = false 37 this.deferred = null 38 this.listen() 39 } 40 41 Typeahead.prototype = { 42 43 constructor: Typeahead 44 45 , select: function (e) { 46 var val = JSON.parse(this.$menu.find('.active').attr('data-value')) 47 , text 48 49 if (!this.strings) text = val[this.options.property] 50 else text = val 51 52 this.$element.val(text) 53 54 if (typeof this.onselect == "function") 55 if (false === this.onselect(val, e)) 56 return; 57 58 return this.hide() 59 } 60 61 , show: function () { 62 var pos = $.extend({}, this.$element.offset(), { 63 height: this.$element[0].offsetHeight 64 }) 65 66 this.$menu.css({ 67 top: pos.top + pos.height 68 , left: pos.left 69 }) 70 71 this.$menu.show() 72 this.shown = true 73 return this 74 } 75 76 , hide: function () { 77 this.$menu.hide() 78 this.shown = false 79 return this 80 } 81 82 , fetch: function() { 83 var value = this.source(this, this.query) 84 if (value) this.process(value) 85 } 86 87 , lookup: function (event) { 88 var that = this 89 , items 90 , q 91 , value 92 93 this.query = this.$element.val(); 94 /*Check if we have a match on the current source?? */ 95 if (typeof this.source == "function") { 96 if (!this.options.delay) return this.fetch() 97 if (this.deferred) clearTimeout(this.deferred) 98 this.deferred = setTimeout(this.fetch.bind(this), this.options.delay) 99 } else { 100 this.process(this.source) 101 } 102 } 103 104 , process: function (results) { 105 var that = this 106 , items 107 , q 108 109 if (results.length && typeof results[0] != "string") 110 this.strings = false 111 112 this.query = this.$element.val() 113 114 if (this.query.length < this.options.minLength) { 115 return this.shown ? this.hide() : this 116 } 117 118 items = $.grep(results, function (item) { 119 if (!that.strings) 120 item = item[that.options.property] 121 122 if (that.matcher(item)) 123 return item 124 }) 125 126 items = this.sorter(items) 127 128 if (!items.length) { 129 return this.shown ? this.hide() : this 130 } 131 132 return this.render(items.slice(0, this.options.items)).show() 133 } 134 135 , matcher: function (item) { 136 return ~item.toLowerCase().indexOf(this.query.toLowerCase()) 137 } 138 139 , sorter: function (items) { 140 var beginswith = [] 141 , caseSensitive = [] 142 , caseInsensitive = [] 143 , item 144 , sortby 145 146 while (item = items.shift()) { 147 if (this.strings) sortby = item 148 else sortby = item[this.options.property] 149 150 if (!sortby.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item) 151 else if (~sortby.indexOf(this.query)) caseSensitive.push(item) 152 else caseInsensitive.push(item) 153 } 154 155 return beginswith.concat(caseSensitive, caseInsensitive) 156 } 157 158 , highlighter: function (item) { 159 if (!this.query) 160 return item; 161 var exp = this.query.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&").replace(' ', '|') 162 return item.replace(new RegExp(exp, 'ig'), '<strong>$&</strong>') 163 } 164 165 , render: function (items) { 166 var that = this 167 168 items = $(items).map(function (i, item) { 169 var orig = item; 170 i = $(that.options.item).attr('data-value', JSON.stringify(item)) 171 if (!that.strings) { 172 if(item[that.options.render]) 173 item = item[that.options.render]; 174 else 175 item = item[that.options.property]; 176 } 177 i.find('a').html(that.highlighter(item, orig)) 178 return i[0] 179 }) 180 181 items.first().addClass('active') 182 this.$menu.html(items) 183 return this 184 } 185 186 , adjustScroll: function(next) { 187 var top = this.$menu.scrollTop(), 188 bottom = top + this.$menu.height(), 189 pos = next.position(); 190 if (pos.top < 0) 191 this.$menu.scrollTop(top + pos.top - 10); 192 else if (next.height() + top + pos.top > bottom) 193 this.$menu.scrollTop(top + pos.top - this.$menu.height() + next.height() + 10);
194 } 195 196 , next: function (event) { 197 var active = this.$menu.find('.active').removeClass('active') 198 , next = active.next() 199 200 if (!next.length) { 201 next = $(this.$menu.find('li')[0]) 202 } 203 204 next.addClass('active') 205 206 if (this.options.scroll) { 207 this.adjustScroll(next); 208 } 209 } 210 211 , prev: function (event) { 212 var active = this.$menu.find('.active').removeClass('active') 213 , prev = active.prev() 214 215 if (!prev.length) { 216 prev = this.$menu.find('li').last() 217 } 218 219 prev.addClass('active') 220 221 if (this.options.scroll) { 222 this.adjustScroll(prev); 223 } 224 } 225 226 , listen: function () { 227 this.$element 228 .on('blur', $.proxy(this.blur, this)) 229 .on('keypress', $.proxy(this.keypress, this)) 230 .on('keyup', $.proxy(this.keyup, this)) 231 232 if (this.eventSupported('keydown')) { 233 this.$element.on('keydown', $.proxy(this.keypress, this)) 234 } 235 236 this.$menu 237 .on('click', $.proxy(this.click, this)) 238 .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) 239 } 240 241 , eventSupported: function(eventName) { 242 var isSupported = eventName in this.$element 243 if (!isSupported) { 244 this.$element.setAttribute(eventName, 'return;') 245 isSupported = typeof this.$element[eventName] === 'function' 246 } 247 return isSupported 248 } 249 250 , keyup: function (e) { 251 e.stopPropagation() 252 e.preventDefault() 253 254 switch(e.keyCode) { 255 case 40: // down arrow 256 case 38: // up arrow 257 break 258 259 case 9: // tab 260 case 13: // enter 261 if (!this.shown) return 262 this.select(e) 263 break 264 265 case 27: // escape 266 this.hide() 267 break 268 269 default: 270 this.lookup() 271 } 272 273 } 274 275 , keypress: function (e) { 276 e.stopPropagation() 277 if (!this.shown) return 278 279 switch(e.keyCode) { 280 case 9: // tab 281 case 13: // enter 282 case 27: // escape 283 e.preventDefault() 284 break 285 286 case 38: // up arrow 287 e.preventDefault() 288 this.prev() 289 break 290 291 case 40: // down arrow 292 e.preventDefault() 293 this.next() 294 break 295 } 296 } 297 298 , blur: function (e) { 299 var that = this 300 e.stopPropagation() 301 e.preventDefault() 302 setTimeout(function () { that.hide() }, 150) 303 } 304 305 , click: function (e) { 306 e.stopPropagation() 307 e.preventDefault() 308 this.select(e) 309 } 310 311 , mouseenter: function (e) { 312 this.$menu.find('.active').removeClass('active') 313 $(e.currentTarget).addClass('active') 314 } 315 316 , visible: function() { 317 return this.shown; 318 } 319 320 } 321 322 323 /* TYPEAHEAD PLUGIN DEFINITION 324 * =========================== */ 325 326 $.fn.typeahead = function ( option ) { 327 return this.each(function () { 328 var $this = $(this) 329 , data = $this.data('typeahead') 330 , options = typeof option == 'object' && option 331 if (!data) $this.data('typeahead', (data = new Typeahead(this, options))) 332 if (typeof option == 'string') data[option]() 333 }) 334 } 335 336 $.fn.typeahead.defaults = { 337 source: [] 338 , items: 8 339 , menu: '<ul class="typeahead dropdown-menu"></ul>' 340 , item: '<li><a href="#"></a></li>' 341 , onselect: null 342 , property: 'value' 343 , render: 'info' 344 , minLength: 1 345 , scroll: false 346 , delay: 200 347 } 348 349 $.fn.typeahead.Constructor = Typeahead 350 351 352 /* TYPEAHEAD DATA-API 353 * ================== */ 354 355 $(function () { 356 $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { 357 var $this = $(this) 358 if ($this.data('typeahead')) return 359 e.preventDefault() 360 $this.typeahead($this.data()) 361 }) 362 }) 363 364}( 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.