1/* ============================================================= 2 * bootstrap3-typeahead.js v4.0.2 3 * https://github.com/bassjobsen/Bootstrap-3-Typeahead 4 * ============================================================= 5 * Original written by @mdo and @fat 6 * ============================================================= 7 * Copyright 2014 Bass Jobsen @bassjobsen 8 * 9 * Licensed under the Apache License, Version 2.0 (the 'License'); 10 * you may not use this file except in compliance with the License. 11 * You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, software 16 * distributed under the License is distributed on an 'AS IS' BASIS, 17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 * See the License for the specific language governing permissions and 19 * limitations under the License. 20 * ============================================================ */ 21 22 23(function (root, factory) { 24 25 'use strict'; 26 27 // CommonJS module is defined 28 if (typeof module !== 'undefined' && module.exports) { 29 module.exports = factory(require('jquery')); 30 } 31 // AMD module is defined 32 else if (typeof define === 'function' && define.amd) { 33 define(['jquery'], function ($) { 34 return factory($); 35 }); 36 } else { 37 factory(root.jQuery); 38 } 39 40}(this, function ($) { 41 42 'use strict'; 43 // jshint laxcomma: true 44 45 46 /* TYPEAHEAD PUBLIC CLASS DEFINITION 47 * ================================= */ 48 49 var Typeahead = function (element, options) { 50 this.$element = $(element); 51 this.options = $.extend({}, Typeahead.defaults, options); 52 this.matcher = this.options.matcher || this.matcher; 53 this.sorter = this.options.sorter || this.sorter; 54 this.select = this.options.select || this.select; 55 this.autoSelect = typeof this.options.autoSelect == 'boolean' ? this.options.autoSelect : true; 56 this.highlighter = this.options.highlighter || this.highlighter; 57 this.render = this.options.render || this.render; 58 this.updater = this.options.updater || this.updater; 59 this.displayText = this.options.displayText || this.displayText; 60 this.itemLink = this.options.itemLink || this.itemLink; 61 this.itemTitle = this.options.itemTitle || this.itemTitle; 62 this.followLinkOnSelect = this.options.followLinkOnSelect || this.followLinkOnSelect; 63 this.source = this.options.source; 64 this.delay = this.options.delay; 65 this.theme = this.options.theme && this.options.themes && this.options.themes[this.options.theme] || Typeahead.defaults.themes[Typeahead.defaults.theme]; 66 this.$menu = $(this.options.menu || this.theme.menu); 67 this.$appendTo = this.options.appendTo ? $(this.options.appendTo) : null; 68 this.fitToElement = typeof this.options.fitToElement == 'boolean' ? this.options.fitToElement : false; 69 this.shown = false; 70 this.listen(); 71 this.showHintOnFocus = typeof this.options.showHintOnFocus == 'boolean' || this.options.showHintOnFocus === 'all' ? this.options.showHintOnFocus : false; 72 this.afterSelect = this.options.afterSelect; 73 this.afterEmptySelect = this.options.afterEmptySelect; 74 this.addItem = false; 75 this.value = this.$element.val() || this.$element.text(); 76 this.keyPressed = false; 77 this.focused = this.$element.is(':focus'); 78 this.changeInputOnSelect = this.options.changeInputOnSelect || this.changeInputOnSelect; 79 this.changeInputOnMove = this.options.changeInputOnMove || this.changeInputOnMove; 80 this.openLinkInNewTab = this.options.openLinkInNewTab || this.openLinkInNewTab; 81 this.selectOnBlur = this.options.selectOnBlur || this.selectOnBlur; 82 this.showCategoryHeader = this.options.showCategoryHeader || this.showCategoryHeader; 83 }; 84 85 Typeahead.prototype = { 86 87 constructor: Typeahead, 88 89 90 setDefault: function (val) { 91 // var val = this.$menu.find('.active').data('value'); 92 this.$element.data('active', val); 93 if (this.autoSelect || val) { 94 var newVal = this.updater(val); 95 // Updater can be set to any random functions via "options" parameter in constructor above. 96 // Add null check for cases when updater returns void or undefined. 97 if (!newVal) { 98 newVal = ''; 99 } 100 this.$element 101 .val(this.displayText(newVal) || newVal) 102 .text(this.displayText(newVal) || newVal) 103 .change(); 104 this.afterSelect(newVal); 105 } 106 return this.hide(); 107 }, 108 109 select: function () { 110 var val = this.$menu.find('.active').data('value'); 111 112 this.$element.data('active', val); 113 if (this.autoSelect || val) { 114 var newVal = this.updater(val); 115 // Updater can be set to any random functions via "options" parameter in constructor above. 116 // Add null check for cases when updater returns void or undefined. 117 if (!newVal) { 118 newVal = ''; 119 } 120 121 if (this.changeInputOnSelect) { 122 this.$element 123 .val(this.displayText(newVal) || newVal) 124 .text(this.displayText(newVal) || newVal) 125 .change(); 126 } 127 128 if (this.followLinkOnSelect && this.itemLink(val)) { 129 if (this.openLinkInNewTab) { 130 window.open(this.itemLink(val), '_blank'); 131 } else { 132 document.location = this.itemLink(val); 133 } 134 this.afterSelect(newVal); 135 } else if (this.followLinkOnSelect && !this.itemLink(val)) { 136 this.afterEmptySelect(newVal); 137 } else { 138 this.afterSelect(newVal); 139 } 140 } else { 141 this.afterEmptySelect(); 142 } 143 144 return this.hide(); 145 }, 146 147 updater: function (item) { 148 return item; 149 }, 150 151 setSource: function (source) { 152 this.source = source; 153 }, 154 155 show: function () { 156 var pos = $.extend({}, this.$element.position(), { 157 height: this.$element[0].offsetHeight 158 }); 159 160 var scrollHeight = typeof this.options.scrollHeight == 'function' ? 161 this.options.scrollHeight.call() : 162 this.options.scrollHeight; 163 164 var element; 165 if (this.shown) { 166 element = this.$menu; 167 } else if (this.$appendTo) { 168 element = this.$menu.appendTo(this.$appendTo); 169 this.hasSameParent = this.$appendTo.is(this.$element.parent()); 170 } else { 171 element = this.$menu.insertAfter(this.$element); 172 this.hasSameParent = true; 173 } 174 175 if (!this.hasSameParent) { 176 // We cannot rely on the element position, need to position relative to the window 177 element.css('position', 'fixed'); 178 var offset = this.$element.offset(); 179 pos.top = offset.top; 180 pos.left = offset.left; 181 } 182 // The rules for bootstrap are: 'dropup' in the parent and 'dropdown-menu-right' in the element. 183 // Note that to get right alignment, you'll need to specify `menu` in the options to be: 184 // '<ul class="typeahead dropdown-menu" role="listbox"></ul>' 185 var dropup = $(element).parent().hasClass('dropup'); 186 var newTop = dropup ? 'auto' : (pos.top + pos.height + scrollHeight); 187 var right = $(element).hasClass('dropdown-menu-right'); 188 var newLeft = right ? 'auto' : pos.left; 189 // it seems like setting the css is a bad idea (just let Bootstrap do it), but I'll keep the old 190 // logic in place except for the dropup/right-align cases. 191 element.css({ top: newTop, left: newLeft }).show(); 192 193 if (this.options.fitToElement === true) { 194 element.css('width', this.$element.outerWidth() + 'px'); 195 } 196 197 this.shown = true; 198 return this; 199 }, 200 201 hide: function () { 202 this.$menu.hide(); 203 this.shown = false; 204 return this; 205 }, 206 207 lookup: function (query) { 208 if (typeof(query) != 'undefined' && query !== null) { 209 this.query = query; 210 } else { 211 this.query = this.$element.val(); 212 } 213 214 if (this.query.length < this.options.minLength && !this.options.showHintOnFocus) { 215 return this.shown ? this.hide() : this; 216 } 217 218 var worker = $.proxy(function () { 219 220 // Bloodhound (since 0.11) needs three arguments. 221 // Two of them are callback functions (sync and async) for local and remote data processing 222 // see https://github.com/twitter/typeahead.js/blob/master/src/bloodhound/bloodhound.js#L132 223 if ($.isFunction(this.source) && this.source.length === 3) { 224 this.source(this.query, $.proxy(this.process, this), $.proxy(this.process, this)); 225 } else if ($.isFunction(this.source)) { 226 this.source(this.query, $.proxy(this.process, this)); 227 } else if (this.source) { 228 this.process(this.source); 229 } 230 }, this); 231 232 clearTimeout(this.lookupWorker); 233 this.lookupWorker = setTimeout(worker, this.delay); 234 }, 235 236 process: function (items) { 237 var that = this; 238 239 items = $.grep(items, function (item) { 240 return that.matcher(item); 241 }); 242 243 items = this.sorter(items); 244 245 if (!items.length && !this.options.addItem) { 246 return this.shown ? this.hide() : this; 247 } 248 249 if (items.length > 0) { 250 this.$element.data('active', items[0]); 251 } else { 252 this.$element.data('active', null); 253 } 254 255 if (this.options.items != 'all') { 256 items = items.slice(0, this.options.items); 257 } 258 259 // Add item 260 if (this.options.addItem) { 261 items.push(this.options.addItem); 262 } 263 264 return this.render(items).show(); 265 }, 266 267 matcher: function (item) { 268 var it = this.displayText(item); 269 return ~it.toLowerCase().indexOf(this.query.toLowerCase()); 270 }, 271 272 sorter: function (items) { 273 var beginswith = []; 274 var caseSensitive = []; 275 var caseInsensitive = []; 276 var item; 277 278 while ((item = items.shift())) { 279 var it = this.displayText(item); 280 if (!it.toLowerCase().indexOf(this.query.toLowerCase())) { 281 beginswith.push(item); 282 } else if (~it.indexOf(this.query)) { 283 caseSensitive.push(item); 284 } else { 285 caseInsensitive.push(item); 286 } 287 } 288 289 return beginswith.concat(caseSensitive, caseInsensitive); 290 }, 291 292 highlighter: function (item) { 293 var text = this.query; 294 if (text === '') { 295 return item; 296 } 297 var matches = item.match(/(>)([^<]*)(<)/g); 298 var first = []; 299 var second = []; 300 var i; 301 if (matches && matches.length) { 302 // html 303 for (i = 0; i < matches.length; ++i) { 304 if (matches[i].length > 2) {// escape '><' 305 first.push(matches[i]); 306 } 307 } 308 } else { 309 // text 310 first = []; 311 first.push(item); 312 } 313 text = text.replace((/[\(\)\/\.\*\+\?\[\]]/g), function (mat) { 314 return '\\' + mat; 315 }); 316 var reg = new RegExp(text, 'g'); 317 var m; 318 for (i = 0; i < first.length; ++i) { 319 m = first[i].match(reg); 320 if (m && m.length > 0) {// find all text nodes matches 321 second.push(first[i]); 322 } 323 } 324 for (i = 0; i < second.length; ++i) { 325 item = item.replace(second[i], second[i].replace(reg, '<strong>$&</strong>')); 326 } 327 return item; 328 }, 329 330 render: function (items) { 331 var that = this; 332 var self = this; 333 var activeFound = false; 334 var data = []; 335 var _category = that.options.separator; 336 337 $.each(items, function (key, value) { 338 // inject separator 339 if (key > 0 && value[_category] !== items[key - 1][_category]) { 340 data.push({ 341 __type: 'divider' 342 }); 343 } 344 345 console.log("Show header:"); 346 console.log(this.showCategoryHeader); 347 this.showCategoryHeader = true; 348 if (this.showCategoryHeader) { 349 // inject category header 350 if (value[_category] && (key === 0 || value[_category] !== items[key - 1][_category])) { 351 data.push({ 352 __type: 'category', 353 name: value[_category] 354 }); 355 } 356 } 357 358 data.push(value); 359 }); 360 361 items = $(data).map(function (i, item) { 362 if ((item.__type || false) == 'category'){ 363 return $(that.options.headerHtml || that.theme.headerHtml).text(item.name)[0]; 364 } 365 366 if ((item.__type || false) == 'divider'){ 367 return $(that.options.headerDivider || that.theme.headerDivider)[0]; 368 } 369 370 var text = self.displayText(item); 371 i = $(that.options.item || that.theme.item).data('value', item); 372 i.find(that.options.itemContentSelector || that.theme.itemContentSelector) 373 .addBack(that.options.itemContentSelector || that.theme.itemContentSelector) 374 .html(that.highlighter(text, item)); 375 if(that.options.followLinkOnSelect) { 376 i.find('a').attr('href', self.itemLink(item)); 377 } 378 i.find('a').attr('title', self.itemTitle(item)); 379 if (text == self.$element.val()) { 380 i.addClass('active'); 381 self.$element.data('active', item); 382 activeFound = true; 383 } 384 return i[0]; 385 }); 386 387 if (this.autoSelect && !activeFound) { 388 items.filter(':not(.dropdown-header)').first().addClass('active'); 389 this.$element.data('active', items.first().data('value')); 390 } 391 this.$menu.html(items); 392 return this; 393 }, 394 395 displayText: function (item) { 396 return typeof item !== 'undefined' && typeof item.name != 'undefined' ? item.name : item; 397 }, 398 399 itemLink: function (item) { 400 return null; 401 }, 402 403 itemTitle: function (item) { 404 return null; 405 }, 406 407 next: function (event) { 408 var active = this.$menu.find('.active').removeClass('active'); 409 var next = active.next(); 410 411 if (!next.length) { 412 next = $(this.$menu.find($(this.options.item || this.theme.item).prop('tagName'))[0]); 413 } 414 415 while (next.hasClass('divider') || next.hasClass('dropdown-header')) { 416 next = next.next(); 417 } 418 419 next.addClass('active'); 420 // added for screen reader 421 var newVal = this.updater(next.data('value')); 422 if (this.changeInputOnMove) { 423 this.$element.val(this.displayText(newVal) || newVal); 424 } 425 }, 426 427 prev: function (event) { 428 var active = this.$menu.find('.active').removeClass('active'); 429 var prev = active.prev(); 430 431 if (!prev.length) { 432 prev = this.$menu.find($(this.options.item || this.theme.item).prop('tagName')).last(); 433 } 434 435 while (prev.hasClass('divider') || prev.hasClass('dropdown-header')) { 436 prev = prev.prev(); 437 } 438 439 prev.addClass('active'); 440 // added for screen reader 441 var newVal = this.updater(prev.data('value')); 442 if (this.changeInputOnMove) { 443 this.$element.val(this.displayText(newVal) || newVal); 444 } 445 }, 446 447 listen: function () { 448 this.$element 449 .on('focus.bootstrap3Typeahead', $.proxy(this.focus, this)) 450 .on('blur.bootstrap3Typeahead', $.proxy(this.blur, this)) 451 .on('keypress.bootstrap3Typeahead', $.proxy(this.keypress, this)) 452 .on('propertychange.bootstrap3Typeahead input.bootstrap3Typeahead', $.proxy(this.input, this)) 453 .on('keyup.bootstrap3Typeahead', $.proxy(this.keyup, this)); 454 455 if (this.eventSupported('keydown')) { 456 this.$element.on('keydown.bootstrap3Typeahead', $.proxy(this.keydown, this)); 457 } 458 459 var itemTagName = $(this.options.item || this.theme.item).prop('tagName'); 460 if ('ontouchstart' in document.documentElement && 'onmousemove' in document.documentElement) { 461 this.$menu 462 .on('touchstart', itemTagName, $.proxy(this.touchstart, this)) 463 .on('touchend', itemTagName, $.proxy(this.click, this)) 464 .on('click', $.proxy(this.click, this)) 465 .on('mouseenter', itemTagName, $.proxy(this.mouseenter, this)) 466 .on('mouseleave', itemTagName, $.proxy(this.mouseleave, this)) 467 .on('mousedown', $.proxy(this.mousedown,this)); 468 } else if ('ontouchstart' in document.documentElement) { 469 this.$menu 470 .on('touchstart', itemTagName, $.proxy(this.touchstart, this)) 471 .on('touchend', itemTagName, $.proxy(this.click, this)); 472 } else { 473 this.$menu 474 .on('click', $.proxy(this.click, this)) 475 .on('mouseenter', itemTagName, $.proxy(this.mouseenter, this)) 476 .on('mouseleave', itemTagName, $.proxy(this.mouseleave, this)) 477 .on('mousedown', $.proxy(this.mousedown, this)); 478 } 479 }, 480 481 destroy: function () { 482 this.$element.data('typeahead', null); 483 this.$element.data('active', null); 484 this.$element 485 .unbind('focus.bootstrap3Typeahead') 486 .unbind('blur.bootstrap3Typeahead') 487 .unbind('keypress.bootstrap3Typeahead') 488 .unbind('propertychange.bootstrap3Typeahead input.bootstrap3Typeahead') 489 .unbind('keyup.bootstrap3Typeahead'); 490 491 if (this.eventSupported('keydown')) { 492 this.$element.unbind('keydown.bootstrap3-typeahead'); 493 } 494 495 this.$menu.remove(); 496 this.destroyed = true; 497 }, 498 499 eventSupported: function (eventName) { 500 var isSupported = eventName in this.$element; 501 if (!isSupported) { 502 this.$element.setAttribute(eventName, 'return;'); 503 isSupported = typeof this.$element[eventName] === 'function'; 504 } 505 return isSupported; 506 }, 507 508 move: function (e) { 509 if (!this.shown) { 510 return; 511 } 512 513 switch (e.keyCode) { 514 case 9: // tab 515 case 13: // enter 516 case 27: // escape 517 e.preventDefault(); 518 break; 519 520 case 38: // up arrow 521 // with the shiftKey (this is actually the left parenthesis) 522 if (e.shiftKey) { 523 return; 524 } 525 e.preventDefault(); 526 this.prev(); 527 break; 528 529 case 40: // down arrow 530 // with the shiftKey (this is actually the right parenthesis) 531 if (e.shiftKey) { 532 return; 533 } 534 e.preventDefault(); 535 this.next(); 536 break; 537 } 538 }, 539 540 keydown: function (e) { 541 /** 542 * Prevent to make an ajax call while copying and pasting. 543 * 544 * @author Simone Sacchi 545 * @version 2018/01/18 546 */ 547 if (e.keyCode === 17) { // ctrl 548 return; 549 } 550 this.keyPressed = true; 551 this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40, 38, 9, 13, 27]); 552 if (!this.shown && e.keyCode == 40) { 553 this.lookup(); 554 } else { 555 this.move(e); 556 } 557 }, 558 559 keypress: function (e) { 560 if (this.suppressKeyPressRepeat) { 561 return; 562 } 563 this.move(e); 564 }, 565 566 input: function (e) { 567 // This is a fixed for IE10/11 that fires the input event when a placehoder is changed 568 // (https://connect.microsoft.com/IE/feedback/details/810538/ie-11-fires-input-event-on-focus) 569 var currentValue = this.$element.val() || this.$element.text(); 570 if (this.value !== currentValue) { 571 this.value = currentValue; 572 this.lookup(); 573 } 574 }, 575 576 keyup: function (e) { 577 if (this.destroyed) { 578 return; 579 } 580 switch (e.keyCode) { 581 case 40: // down arrow 582 case 38: // up arrow 583 case 16: // shift 584 case 17: // ctrl 585 case 18: // alt 586 break; 587 588 case 9: // tab 589 if (!this.shown || (this.showHintOnFocus && !this.keyPressed)) { 590 return; 591 } 592 this.select(); 593 break; 594 case 13: // enter 595 if (!this.shown) { 596 return; 597 } 598 this.select(); 599 break; 600 601 case 27: // escape 602 if (!this.shown) { 603 return; 604 } 605 this.hide(); 606 break; 607 } 608 609 }, 610 611 focus: function (e) { 612 if (!this.focused) { 613 this.focused = true; 614 this.keyPressed = false; 615 if (this.options.showHintOnFocus && this.skipShowHintOnFocus !== true) { 616 if (this.options.showHintOnFocus === 'all') { 617 this.lookup(''); 618 } else { 619 this.lookup(); 620 } 621 } 622 } 623 if (this.skipShowHintOnFocus) { 624 this.skipShowHintOnFocus = false; 625 } 626 }, 627 628 blur: function (e) { 629 if (!this.mousedover && !this.mouseddown && this.shown) { 630 if (this.selectOnBlur) { 631 this.select(); 632 } 633 this.hide(); 634 this.focused = false; 635 this.keyPressed = false; 636 } else if (this.mouseddown) { 637 // This is for IE that blurs the input when user clicks on scroll. 638 // We set the focus back on the input and prevent the lookup to occur again 639 this.skipShowHintOnFocus = true; 640 this.$element.focus(); 641 this.mouseddown = false; 642 } 643 }, 644 645 click: function (e) { 646 e.preventDefault(); 647 this.skipShowHintOnFocus = true; 648 this.select(); 649 this.$element.focus(); 650 this.hide(); 651 }, 652 653 mouseenter: function (e) { 654 this.mousedover = true; 655 this.$menu.find('.active').removeClass('active'); 656 $(e.currentTarget).addClass('active'); 657 }, 658 659 mouseleave: function (e) { 660 this.mousedover = false; 661 if (!this.focused && this.shown) { 662 this.hide(); 663 } 664 }, 665 666 /** 667 * We track the mousedown for IE. When clicking on the menu scrollbar, IE makes the input blur thus hiding the menu. 668 */ 669 mousedown: function (e) { 670 this.mouseddown = true; 671 this.$menu.one('mouseup', function (e) { 672 // IE won't fire this, but FF and Chrome will so we reset our flag for them here 673 this.mouseddown = false; 674 }.bind(this)); 675 }, 676 677 touchstart: function (e) { 678 e.preventDefault(); 679 this.$menu.find('.active').removeClass('active'); 680 $(e.currentTarget).addClass('active'); 681 }, 682 683 touchend: function (e) { 684 e.preventDefault(); 685 this.select(); 686 this.$element.focus(); 687 } 688 689 }; 690 691 692 /* TYPEAHEAD PLUGIN DEFINITION 693 * =========================== */ 694 695 var old = $.fn.typeahead; 696 697 $.fn.typeahead = function (option) { 698 var arg = arguments; 699 if (typeof option == 'string' && option == 'getActive') { 700 return this.data('active'); 701 } 702 return this.each(function () { 703 var $this = $(this); 704 var data = $this.data('typeahead'); 705 var options = typeof option == 'object' && option; 706 if (!data) { 707 $this.data('typeahead', (data = new Typeahead(this, options))); 708 } 709 if (typeof option == 'string' && data[option]) { 710 if (arg.length > 1) { 711 data[option].apply(data, Array.prototype.slice.call(arg, 1)); 712 } else { 713 data[option](); 714 } 715 } 716 }); 717 }; 718 719 Typeahead.defaults = { 720 source: [], 721 items: 8, 722 minLength: 1, 723 scrollHeight: 0, 724 autoSelect: true, 725 afterSelect: $.noop, 726 afterEmptySelect: $.noop, 727 addItem: false, 728 followLinkOnSelect: false, 729 delay: 0, 730 separator: 'category', 731 changeInputOnSelect: true, 732 changeInputOnMove: true, 733 openLinkInNewTab: false, 734 selectOnBlur: true, 735 showCategoryHeader: true, 736 theme: "bootstrap4", 737 themes: { 738 bootstrap3: { 739 menu: '<ul class="typeahead mikio-dropdown" role="listbox"></ul>', 740 item: '<li><a class="mikio-dropdown-item" href="#" role="option"></a></li>', 741 itemContentSelector: "a", 742 headerHtml: '<li class="mikio-dropdown-header"></li>', 743 headerDivider: '<li class="mikio-divider" role="separator"></li>' 744 }, 745 bootstrap4: { 746 menu: '<div class="typeahead mikio-dropdown" role="listbox"></div>', 747 item: '<a class="mikio-dropdown-item" href="#" role="option"></a>', 748 itemContentSelector: '.mikio-dropdown-item', 749 headerHtml: '<h6 class="mikio-dropdown-header"></h6>', 750 headerDivider: '<div class="mikio-dropdown-divider"></div>' 751 } 752 } 753}; 754 755 $.fn.typeahead.Constructor = Typeahead; 756 757 /* TYPEAHEAD NO CONFLICT 758 * =================== */ 759 760 $.fn.typeahead.noConflict = function () { 761 $.fn.typeahead = old; 762 return this; 763 }; 764 765 766 /* TYPEAHEAD DATA-API 767 * ================== */ 768 769 $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { 770 var $this = $(this); 771 if ($this.data('typeahead')) { 772 return; 773 } 774 $this.typeahead($this.data()); 775 }); 776 777})); 778