bootstrap3-typeahead.min.js 28 KB

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