1/* ============================================================= 2 * bootstrap-combobox.js v1.1.8 3 * ============================================================= 4 * Copyright 2012 Daniel Farrell 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 * ============================================================ */ 18 19(function( $ ) { 20 21 "use strict"; 22 23 /* COMBOBOX PUBLIC CLASS DEFINITION 24 * ================================ */ 25 26 var Combobox = function ( element, options ) { 27 this.options = $.extend({}, $.fn.combobox.defaults, options); 28 this.template = this.options.template || this.template 29 this.$source = $(element); 30 this.$container = this.setup(); 31 this.$element = this.$container.find('input[type=text]'); 32 this.$target = this.$container.find('input[type=hidden]'); 33 this.$button = this.$container.find('.dropdown-toggle'); 34 this.$menu = $(this.options.menu).appendTo('body'); 35 this.matcher = this.options.matcher || this.matcher; 36 this.sorter = this.options.sorter || this.sorter; 37 this.highlighter = this.options.highlighter || this.highlighter; 38 this.shown = false; 39 this.selected = false; 40 this.refresh(); 41 this.transferAttributes(); 42 this.listen(); 43 }; 44 45 Combobox.prototype = { 46 47 constructor: Combobox 48 49 , setup: function () { 50 var combobox = $(this.template()); 51 this.$source.before(combobox); 52 this.$source.hide(); 53 return combobox; 54 } 55 56 , disable: function() { 57 this.$element.prop('disabled', true); 58 this.$button.attr('disabled', true); 59 this.disabled = true; 60 this.$container.addClass('combobox-disabled'); 61 } 62 63 , enable: function() { 64 this.$element.prop('disabled', false); 65 this.$button.attr('disabled', false); 66 this.disabled = false; 67 this.$container.removeClass('combobox-disabled'); 68 } 69 , parse: function () { 70 var that = this 71 , map = {} 72 , source = [] 73 , selected = false 74 , selectedValue = ''; 75 this.$source.find('option').each(function() { 76 var option = $(this); 77 if (option.val() === '') { 78 that.options.placeholder = option.text(); 79 return; 80 } 81 map[option.text()] = option.val(); 82 source.push(option.text()); 83 if (option.prop('selected')) { 84 selected = option.text(); 85 selectedValue = option.val(); 86 } 87 }) 88 this.map = map; 89 if (selected) { 90 this.$element.val(selected); 91 this.$target.val(selectedValue); 92 this.$container.addClass('combobox-selected'); 93 this.selected = true; 94 } 95 return source; 96 } 97 98 , transferAttributes: function() { 99 this.options.placeholder = this.$source.attr('data-placeholder') || this.options.placeholder 100 if(this.options.appendId !== undefined) { 101 this.$element.attr('id', this.$source.attr('id') + this.options.appendId); 102 } 103 this.$element.attr('placeholder', this.options.placeholder) 104 this.$target.prop('name', this.$source.prop('name')) 105 this.$target.val(this.$source.val()) 106 this.$source.removeAttr('name') // Remove from source otherwise form will pass parameter twice. 107 this.$element.attr('required', this.$source.attr('required')) 108 this.$element.attr('rel', this.$source.attr('rel')) 109 this.$element.attr('title', this.$source.attr('title')) 110 this.$element.attr('class', this.$source.attr('class')) 111 this.$element.attr('tabindex', this.$source.attr('tabindex')) 112 this.$source.removeAttr('tabindex') 113 if (this.$source.attr('disabled')!==undefined) 114 this.disable(); 115 } 116 117 , select: function () { 118 var val = this.$menu.find('.active').attr('data-value'); 119 this.$element.val(this.updater(val)).trigger('change'); 120 this.$target.val(this.map[val]).trigger('change'); 121 this.$source.val(this.map[val]).trigger('change'); 122 this.$container.addClass('combobox-selected'); 123 this.selected = true; 124 return this.hide(); 125 } 126 127 , updater: function (item) { 128 return item; 129 } 130 131 , show: function () { 132 var pos = $.extend({}, this.$element.position(), { 133 height: this.$element[0].offsetHeight 134 }); 135 136 this.$menu 137 .insertAfter(this.$element) 138 .css({ 139 top: pos.top + pos.height 140 , left: pos.left 141 }) 142 .show(); 143 144 $('.dropdown-menu').on('mousedown', $.proxy(this.scrollSafety, this)); 145 146 this.shown = true; 147 return this; 148 } 149 150 , hide: function () { 151 this.$menu.hide(); 152 $('.dropdown-menu').off('mousedown', $.proxy(this.scrollSafety, this)); 153 this.$element.on('blur', $.proxy(this.blur, this)); 154 this.shown = false; 155 return this; 156 } 157 158 , lookup: function (event) { 159 this.query = this.$element.val(); 160 return this.process(this.source); 161 } 162 163 , process: function (items) { 164 var that = this; 165 166 items = $.grep(items, function (item) { 167 return that.matcher(item); 168 }) 169 170 items = this.sorter(items); 171 172 if (!items.length) { 173 return this.shown ? this.hide() : this; 174 } 175 176 return this.render(items.slice(0, this.options.items)).show(); 177 } 178 179 , template: function() { 180 if (this.options.bsVersion == '2') { 181 return '<div class="combobox-container"><input type="hidden" /> <div class="input-append"> <input type="text" autocomplete="off" /> <span class="add-on dropdown-toggle" data-dropdown="dropdown"> <span class="caret"/> <i class="icon-remove"/>
181 </span> </div> </div>' 182 } else { 183 return '<div class="combobox-container"> <input type="hidden" /> <div class="input-group"> <input type="text" autocomplete="off" /> <span class="input-group-addon dropdown-toggle" data-dropdown="dropdown"> <span class="caret" /> <span class="glyphicon glyphicon-remove" /> </span> </div> </div>' 184 } 185 } 186 187 , matcher: function (item) { 188 return ~item.toLowerCase().indexOf(this.query.toLowerCase()); 189 } 190 191 , sorter: function (items) { 192 var beginswith = [] 193 , caseSensitive = [] 194 , caseInsensitive = [] 195 , item; 196 197 while (item = items.shift()) { 198 if (!item.toLowerCase().indexOf(this.query.toLowerCase())) {beginswith.push(item);} 199 else if (~item.indexOf(this.query)) {caseSensitive.push(item);} 200 else {caseInsensitive.push(item);} 201 } 202 203 return beginswith.concat(caseSensitive, caseInsensitive); 204 } 205 206 , highlighter: function (item) { 207 var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&'); 208 return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) { 209 return '<strong>' + match + '</strong>'; 210 }) 211 } 212 213 , render: function (items) { 214 var that = this; 215 216 items = $(items).map(function (i, item) { 217 i = $(that.options.item).attr('data-value', item); 218 i.find('a').html(that.highlighter(item)); 219 return i[0]; 220 }) 221 222 items.first().addClass('active'); 223 this.$menu.html(items); 224 return this; 225 } 226 227 , next: function (event) { 228 var active = this.$menu.find('.active').removeClass('active') 229 , next = active.next(); 230 231 if (!next.length) { 232 next = $(this.$menu.find('li')[0]); 233 } 234 235 next.addClass('active'); 236 } 237 238 , prev: function (event) { 239 var active = this.$menu.find('.active').removeClass('active') 240 , prev = active.prev(); 241 242 if (!prev.length) { 243 prev = this.$menu.find('li').last(); 244 } 245 246 prev.addClass('active'); 247 } 248 249 , toggle: function () { 250 if (!this.disabled) { 251 if (this.$container.hasClass('combobox-selected')) { 252 this.clearTarget(); 253 this.triggerChange(); 254 this.clearElement(); 255 } else { 256 if (this.shown) { 257 this.hide(); 258 } else { 259 this.clearElement(); 260 this.lookup(); 261 } 262 } 263 } 264 } 265 266 , scrollSafety: function(e) { 267 if (e.target.tagName == 'UL') { 268 this.$element.off('blur'); 269 } 270 } 271 , clearElement: function () { 272 this.$element.val('').focus(); 273 } 274 275 , clearTarget: function () { 276 this.$source.val(''); 277 this.$target.val(''); 278 this.$container.removeClass('combobox-selected'); 279 this.selected = false; 280 } 281 282 , triggerChange: function () { 283 this.$source.trigger('change'); 284 } 285 286 , refresh: function () { 287 this.source = this.parse(); 288 this.options.items = this.source.length; 289 } 290 291 , listen: function () { 292 this.$element 293 .on('focus', $.proxy(this.focus, this)) 294 .on('blur', $.proxy(this.blur, this)) 295 .on('keypress', $.proxy(this.keypress, this)) 296 .on('keyup', $.proxy(this.keyup, this)); 297 298 if (this.eventSupported('keydown')) { 299 this.$element.on('keydown', $.proxy(this.keydown, this)); 300 } 301 302 this.$menu 303 .on('click', $.proxy(this.click, this)) 304 .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) 305 .on('mouseleave', 'li', $.proxy(this.mouseleave, this)); 306 307 this.$button 308 .on('click', $.proxy(this.toggle, this)); 309 } 310 311 , eventSupported: function(eventName) { 312 var isSupported = eventName in this.$element; 313 if (!isSupported) { 314 this.$element.setAttribute(eventName, 'return;'); 315 isSupported = typeof this.$element[eventName] === 'function'; 316 } 317 return isSupported; 318 } 319 320 , move: function (e) { 321 if (!this.shown) {return;} 322 323 switch(e.keyCode) { 324 case 9: // tab 325 case 13: // enter 326 case 27: // escape 327 e.preventDefault(); 328 break; 329 330 case 38: // up arrow 331 e.preventDefault(); 332 this.prev(); 333 this.fixMenuScroll(); 334 break; 335 336 case 40: // down arrow 337 e.preventDefault(); 338 this.next(); 339 this.fixMenuScroll(); 340 break; 341 } 342 343 e.stopPropagation(); 344 } 345 346 , fixMenuScroll: function(){ 347 var active = this.$menu.find('.active'); 348 if(active.length){ 349 var top = active.position().top; 350 var bottom = top + active.height(); 351 var scrollTop = this.$menu.scrollTop(); 352 var menuHeight = this.$menu.height(); 353 if(bottom > menuHeight){ 354 this.$menu.scrollTop(scrollTop + bottom - menuHeight); 355 } else if(top < 0){ 356 this.$menu.scrollTop(scrollTop + top); 357 } 358 } 359 } 360 361 , keydown: function (e) { 362 this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27]); 363 this.move(e); 364 } 365 366 , keypress: function (e) { 367 if (this.suppressKeyPressRepeat) {return;} 368 this.move(e); 369 } 370 371 , keyup: function (e) { 372 switch(e.keyCode) { 373 case 40: // down arrow 374 if (!this.shown){ 375 this.toggle(); 376 } 377 break; 378 case 39: // right arrow 379 case 38: // up arrow 380 case 37: // left arrow 381 case 36: // home 382 case 35: // end 383 case 16: // shift 384 case 17: // ctrl 385 case 18: // alt 386 break; 387 388 case 9: // tab 389 case 13: // enter 390 if (!this.shown) {return;} 391 this.select(); 392 break; 393 394 case 27: // escape 395 if (!this.shown) {return;} 396 this.hide(); 397 break; 398 399 default: 400 this.clearTarget(); 401 this.lookup(); 402 } 403 404 e.stopPropagation(); 405 e.preventDefault(); 406 } 407 408 , focus: function (e) { 409 this.focused = true; 410 } 411 412 , blur: function (e) { 413 var that = this; 414 this.focused = false; 415 var val = this.$element.val(); 416 if (!this.selected && val !== '' ) { 417 this.$element.val(''); 418 this.$source.val('').trigger('change'); 419 this.$target.val('').trigger('change'); 420 } 421 if (!this.mousedover && this.shown) {setTimeout(function () { that.hide(); }, 200);} 422 } 423 424 , click: function (e) { 425 e.stopPropagation(); 426 e.preventDefault(); 427 this.select(); 428 this.$element.focus(); 429 } 430 431 , mouseenter: function (e) { 432 this.mousedover = true; 433 this.$menu.find('.active').removeClass('active'); 434 $(e.currentTarget).addClass('active'); 435 } 436 437 , mouseleave: function (e) { 438 this.mousedover = false; 439 } 440 }; 441 442 /* COMBOBOX PLUGIN DEFINITION 443 * =========================== */ 444 $.fn.combobox = function ( option ) { 445 return this.each(function () { 446 var $this = $(this) 447 , data = $this.data('combobox') 448 , options = typeof option == 'object' && option; 449 if(!data) {$this.data('combobox', (data = new Combobox(this, options)));} 450 if (typeof option == 'string') {data[option]();} 451 });
452 }; 453 454 $.fn.combobox.defaults = { 455 bsVersion: '4' 456 , menu: '<ul class="typeahead typeahead-long dropdown-menu"></ul>' 457 , item: '<li><a href="#" class="dropdown-item"></a></li>' 458 }; 459 460 $.fn.combobox.Constructor = Combobox; 461 462}( 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.