jquery.autocomplete.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  1. /**
  2. * Ajax Autocomplete for jQuery, version 1.2.24
  3. * (c) 2015 Tomas Kirda
  4. *
  5. * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
  6. * For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
  7. */
  8. /*jslint browser: true, white: true, plusplus: true, vars: true */
  9. /*global define, window, document, jQuery, exports, require */
  10. // Expose plugin as an AMD module if AMD loader is present:
  11. (function (factory) {
  12. 'use strict';
  13. if (typeof define === 'function' && define.amd) {
  14. // AMD. Register as an anonymous module.
  15. define(['jquery'], factory);
  16. } else if (typeof exports === 'object' && typeof require === 'function') {
  17. // Browserify
  18. factory(require('jquery'));
  19. } else {
  20. // Browser globals
  21. factory(jQuery);
  22. }
  23. }(function ($) {
  24. 'use strict';
  25. var
  26. utils = (function () {
  27. return {
  28. escapeRegExChars: function (value) {
  29. return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
  30. },
  31. createNode: function (containerClass) {
  32. var div = document.createElement('div');
  33. div.className = containerClass;
  34. div.style.position = 'absolute';
  35. div.style.display = 'none';
  36. return div;
  37. }
  38. };
  39. }()),
  40. keys = {
  41. ESC: 27,
  42. TAB: 9,
  43. RETURN: 13,
  44. LEFT: 37,
  45. UP: 38,
  46. RIGHT: 39,
  47. DOWN: 40
  48. };
  49. function Autocomplete(el, options) {
  50. var noop = function () { },
  51. that = this,
  52. defaults = {
  53. ajaxSettings: {},
  54. autoSelectFirst: false,
  55. appendTo: document.body,
  56. serviceUrl: null,
  57. lookup: null,
  58. onSelect: null,
  59. width: 'auto',
  60. minChars: 1,
  61. maxHeight: 300,
  62. deferRequestBy: 0,
  63. params: {},
  64. formatResult: Autocomplete.formatResult,
  65. delimiter: null,
  66. zIndex: 9999,
  67. type: 'GET',
  68. noCache: false,
  69. onSearchStart: noop,
  70. onSearchComplete: noop,
  71. onSearchError: noop,
  72. preserveInput: false,
  73. containerClass: 'autocomplete-suggestions',
  74. tabDisabled: false,
  75. dataType: 'text',
  76. currentRequest: null,
  77. triggerSelectOnValidInput: true,
  78. preventBadQueries: true,
  79. lookupFilter: function (suggestion, originalQuery, queryLowerCase) {
  80. return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
  81. },
  82. paramName: 'query',
  83. transformResult: function (response) {
  84. return typeof response === 'string' ? $.parseJSON(response) : response;
  85. },
  86. showNoSuggestionNotice: false,
  87. noSuggestionNotice: 'No results',
  88. orientation: 'bottom',
  89. forceFixPosition: false
  90. };
  91. // Shared variables:
  92. that.element = el;
  93. that.el = $(el);
  94. that.suggestions = [];
  95. that.badQueries = [];
  96. that.selectedIndex = -1;
  97. that.currentValue = that.element.value;
  98. that.intervalId = 0;
  99. that.cachedResponse = {};
  100. that.onChangeInterval = null;
  101. that.onChange = null;
  102. that.isLocal = false;
  103. that.suggestionsContainer = null;
  104. that.noSuggestionsContainer = null;
  105. that.options = $.extend({}, defaults, options);
  106. that.classes = {
  107. selected: 'autocomplete-selected',
  108. suggestion: 'autocomplete-suggestion'
  109. };
  110. that.hint = null;
  111. that.hintValue = '';
  112. that.selection = null;
  113. // Initialize and set options:
  114. that.initialize();
  115. that.setOptions(options);
  116. }
  117. Autocomplete.utils = utils;
  118. $.Autocomplete = Autocomplete;
  119. Autocomplete.formatResult = function (suggestion, currentValue) {
  120. var pattern = '(' + utils.escapeRegExChars(currentValue) + ')';
  121. return suggestion.value
  122. .replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>')
  123. .replace(/&/g, '&amp;')
  124. .replace(/</g, '&lt;')
  125. .replace(/>/g, '&gt;')
  126. .replace(/"/g, '&quot;')
  127. .replace(/&lt;(\/?strong)&gt;/g, '<$1>');
  128. };
  129. Autocomplete.prototype = {
  130. killerFn: null,
  131. initialize: function () {
  132. var that = this,
  133. suggestionSelector = '.' + that.classes.suggestion,
  134. selected = that.classes.selected,
  135. options = that.options,
  136. container;
  137. // Remove autocomplete attribute to prevent native suggestions:
  138. that.element.setAttribute('autocomplete', 'off');
  139. that.killerFn = function (e) {
  140. if ($(e.target).closest('.' + that.options.containerClass).length === 0) {
  141. that.killSuggestions();
  142. that.disableKillerFn();
  143. }
  144. };
  145. // html() deals with many types: htmlString or Element or Array or jQuery
  146. that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>')
  147. .html(this.options.noSuggestionNotice).get(0);
  148. that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);
  149. container = $(that.suggestionsContainer);
  150. container.appendTo(options.appendTo);
  151. // Only set width if it was provided:
  152. if (options.width !== 'auto') {
  153. container.width(options.width);
  154. }
  155. // Listen for mouse over event on suggestions list:
  156. container.on('mouseover.autocomplete', suggestionSelector, function () {
  157. that.activate($(this).data('index'));
  158. });
  159. // Deselect active element when mouse leaves suggestions container:
  160. container.on('mouseout.autocomplete', function () {
  161. that.selectedIndex = -1;
  162. container.children('.' + selected).removeClass(selected);
  163. });
  164. // Listen for click event on suggestions list:
  165. container.on('click.autocomplete', suggestionSelector, function () {
  166. that.select($(this).data('index'));
  167. });
  168. that.fixPositionCapture = function () {
  169. if (that.visible) {
  170. that.fixPosition();
  171. }
  172. };
  173. $(window).on('resize.autocomplete', that.fixPositionCapture);
  174. that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); });
  175. that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); });
  176. that.el.on('blur.autocomplete', function () { that.onBlur(); });
  177. that.el.on('focus.autocomplete', function () { that.onFocus(); });
  178. that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); });
  179. that.el.on('input.autocomplete', function (e) { that.onKeyUp(e); });
  180. },
  181. onFocus: function () {
  182. var that = this;
  183. that.fixPosition();
  184. if (that.options.minChars === 0 && that.el.val().length === 0) {
  185. that.onValueChange();
  186. }
  187. },
  188. onBlur: function () {
  189. this.enableKillerFn();
  190. },
  191. abortAjax: function () {
  192. var that = this;
  193. if (that.currentRequest) {
  194. that.currentRequest.abort();
  195. that.currentRequest = null;
  196. }
  197. },
  198. setOptions: function (suppliedOptions) {
  199. var that = this,
  200. options = that.options;
  201. $.extend(options, suppliedOptions);
  202. that.isLocal = $.isArray(options.lookup);
  203. if (that.isLocal) {
  204. options.lookup = that.verifySuggestionsFormat(options.lookup);
  205. }
  206. options.orientation = that.validateOrientation(options.orientation, 'bottom');
  207. // Adjust height, width and z-index:
  208. $(that.suggestionsContainer).css({
  209. 'max-height': options.maxHeight + 'px',
  210. 'width': options.width + 'px',
  211. 'z-index': options.zIndex
  212. });
  213. },
  214. clearCache: function () {
  215. this.cachedResponse = {};
  216. this.badQueries = [];
  217. },
  218. clear: function () {
  219. this.clearCache();
  220. this.currentValue = '';
  221. this.suggestions = [];
  222. },
  223. disable: function () {
  224. var that = this;
  225. that.disabled = true;
  226. clearInterval(that.onChangeInterval);
  227. that.abortAjax();
  228. },
  229. enable: function () {
  230. this.disabled = false;
  231. },
  232. fixPosition: function () {
  233. // Use only when container has already its content
  234. var that = this,
  235. $container = $(that.suggestionsContainer),
  236. containerParent = $container.parent().get(0);
  237. // Fix position automatically when appended to body.
  238. // In other cases force parameter must be given.
  239. if (containerParent !== document.body && !that.options.forceFixPosition) {
  240. return;
  241. }
  242. // Choose orientation
  243. var orientation = that.options.orientation,
  244. containerHeight = $container.outerHeight(),
  245. height = that.el.outerHeight(),
  246. offset = that.el.offset(),
  247. styles = { 'top': offset.top, 'left': offset.left };
  248. if (orientation === 'auto') {
  249. var viewPortHeight = $(window).height(),
  250. scrollTop = $(window).scrollTop(),
  251. topOverflow = -scrollTop + offset.top - containerHeight,
  252. bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight);
  253. orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow) ? 'top' : 'bottom';
  254. }
  255. if (orientation === 'top') {
  256. styles.top += -containerHeight;
  257. } else {
  258. styles.top += height;
  259. }
  260. // If container is not positioned to body,
  261. // correct its position using offset parent offset
  262. if(containerParent !== document.body) {
  263. var opacity = $container.css('opacity'),
  264. parentOffsetDiff;
  265. if (!that.visible){
  266. $container.css('opacity', 0).show();
  267. }
  268. parentOffsetDiff = $container.offsetParent().offset();
  269. styles.top -= parentOffsetDiff.top;
  270. styles.left -= parentOffsetDiff.left;
  271. if (!that.visible){
  272. $container.css('opacity', opacity).hide();
  273. }
  274. }
  275. // -2px to account for suggestions border.
  276. if (that.options.width === 'auto') {
  277. styles.width = (that.el.outerWidth() - 2) + 'px';
  278. }
  279. $container.css(styles);
  280. },
  281. enableKillerFn: function () {
  282. var that = this;
  283. $(document).on('click.autocomplete', that.killerFn);
  284. },
  285. disableKillerFn: function () {
  286. var that = this;
  287. $(document).off('click.autocomplete', that.killerFn);
  288. },
  289. killSuggestions: function () {
  290. var that = this;
  291. that.stopKillSuggestions();
  292. that.intervalId = window.setInterval(function () {
  293. if (that.visible) {
  294. that.el.val(that.currentValue);
  295. that.hide();
  296. }
  297. that.stopKillSuggestions();
  298. }, 50);
  299. },
  300. stopKillSuggestions: function () {
  301. window.clearInterval(this.intervalId);
  302. },
  303. isCursorAtEnd: function () {
  304. var that = this,
  305. valLength = that.el.val().length,
  306. selectionStart = that.element.selectionStart,
  307. range;
  308. if (typeof selectionStart === 'number') {
  309. return selectionStart === valLength;
  310. }
  311. if (document.selection) {
  312. range = document.selection.createRange();
  313. range.moveStart('character', -valLength);
  314. return valLength === range.text.length;
  315. }
  316. return true;
  317. },
  318. onKeyPress: function (e) {
  319. var that = this;
  320. // If suggestions are hidden and user presses arrow down, display suggestions:
  321. if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) {
  322. that.suggest();
  323. return;
  324. }
  325. if (that.disabled || !that.visible) {
  326. return;
  327. }
  328. switch (e.which) {
  329. case keys.ESC:
  330. that.el.val(that.currentValue);
  331. that.hide();
  332. break;
  333. case keys.RIGHT:
  334. if (that.hint && that.options.onHint && that.isCursorAtEnd()) {
  335. that.selectHint();
  336. break;
  337. }
  338. return;
  339. case keys.TAB:
  340. if (that.hint && that.options.onHint) {
  341. that.selectHint();
  342. return;
  343. }
  344. if (that.selectedIndex === -1) {
  345. that.hide();
  346. return;
  347. }
  348. that.select(that.selectedIndex);
  349. if (that.options.tabDisabled === false) {
  350. return;
  351. }
  352. break;
  353. case keys.RETURN:
  354. if (that.selectedIndex === -1) {
  355. that.hide();
  356. return;
  357. }
  358. that.select(that.selectedIndex);
  359. break;
  360. case keys.UP:
  361. that.moveUp();
  362. break;
  363. case keys.DOWN:
  364. that.moveDown();
  365. break;
  366. default:
  367. return;
  368. }
  369. // Cancel event if function did not return:
  370. e.stopImmediatePropagation();
  371. e.preventDefault();
  372. },
  373. onKeyUp: function (e) {
  374. var that = this;
  375. if (that.disabled) {
  376. return;
  377. }
  378. switch (e.which) {
  379. case keys.UP:
  380. case keys.DOWN:
  381. return;
  382. }
  383. clearInterval(that.onChangeInterval);
  384. if (that.currentValue !== that.el.val()) {
  385. that.findBestHint();
  386. if (that.options.deferRequestBy > 0) {
  387. // Defer lookup in case when value changes very quickly:
  388. that.onChangeInterval = setInterval(function () {
  389. that.onValueChange();
  390. }, that.options.deferRequestBy);
  391. } else {
  392. that.onValueChange();
  393. }
  394. }
  395. },
  396. onValueChange: function () {
  397. var that = this,
  398. options = that.options,
  399. value = that.el.val(),
  400. query = that.getQuery(value);
  401. if (that.selection && that.currentValue !== query) {
  402. that.selection = null;
  403. (options.onInvalidateSelection || $.noop).call(that.element);
  404. }
  405. clearInterval(that.onChangeInterval);
  406. that.currentValue = value;
  407. that.selectedIndex = -1;
  408. // Check existing suggestion for the match before proceeding:
  409. if (options.triggerSelectOnValidInput && that.isExactMatch(query)) {
  410. that.select(0);
  411. return;
  412. }
  413. if (query.length < options.minChars) {
  414. that.hide();
  415. } else {
  416. that.getSuggestions(query);
  417. }
  418. },
  419. isExactMatch: function (query) {
  420. var suggestions = this.suggestions;
  421. return (suggestions.length === 1 && suggestions[0].value.toLowerCase() === query.toLowerCase());
  422. },
  423. getQuery: function (value) {
  424. var delimiter = this.options.delimiter,
  425. parts;
  426. if (!delimiter) {
  427. return value;
  428. }
  429. parts = value.split(delimiter);
  430. return $.trim(parts[parts.length - 1]);
  431. },
  432. getSuggestionsLocal: function (query) {
  433. var that = this,
  434. options = that.options,
  435. queryLowerCase = query.toLowerCase(),
  436. filter = options.lookupFilter,
  437. limit = parseInt(options.lookupLimit, 10),
  438. data;
  439. data = {
  440. suggestions: $.grep(options.lookup, function (suggestion) {
  441. return filter(suggestion, query, queryLowerCase);
  442. })
  443. };
  444. if (limit && data.suggestions.length > limit) {
  445. data.suggestions = data.suggestions.slice(0, limit);
  446. }
  447. return data;
  448. },
  449. getSuggestions: function (q) {
  450. var response,
  451. that = this,
  452. options = that.options,
  453. serviceUrl = options.serviceUrl,
  454. params,
  455. cacheKey,
  456. ajaxSettings;
  457. options.params[options.paramName] = q;
  458. params = options.ignoreParams ? null : options.params;
  459. if (options.onSearchStart.call(that.element, options.params) === false) {
  460. return;
  461. }
  462. if ($.isFunction(options.lookup)){
  463. options.lookup(q, function (data) {
  464. that.suggestions = data.suggestions;
  465. that.suggest();
  466. options.onSearchComplete.call(that.element, q, data.suggestions);
  467. });
  468. return;
  469. }
  470. if (that.isLocal) {
  471. response = that.getSuggestionsLocal(q);
  472. } else {
  473. if ($.isFunction(serviceUrl)) {
  474. serviceUrl = serviceUrl.call(that.element, q);
  475. }
  476. cacheKey = serviceUrl + '?' + $.param(params || {});
  477. response = that.cachedResponse[cacheKey];
  478. }
  479. if (response && $.isArray(response.suggestions)) {
  480. that.suggestions = response.suggestions;
  481. that.suggest();
  482. options.onSearchComplete.call(that.element, q, response.suggestions);
  483. } else if (!that.isBadQuery(q)) {
  484. that.abortAjax();
  485. ajaxSettings = {
  486. url: serviceUrl,
  487. data: params,
  488. type: options.type,
  489. dataType: options.dataType
  490. };
  491. $.extend(ajaxSettings, options.ajaxSettings);
  492. that.currentRequest = $.ajax(ajaxSettings).done(function (data) {
  493. var result;
  494. that.currentRequest = null;
  495. result = options.transformResult(data, q);
  496. that.processResponse(result, q, cacheKey);
  497. options.onSearchComplete.call(that.element, q, result.suggestions);
  498. }).fail(function (jqXHR, textStatus, errorThrown) {
  499. options.onSearchError.call(that.element, q, jqXHR, textStatus, errorThrown);
  500. });
  501. } else {
  502. options.onSearchComplete.call(that.element, q, []);
  503. }
  504. },
  505. isBadQuery: function (q) {
  506. if (!this.options.preventBadQueries){
  507. return false;
  508. }
  509. var badQueries = this.badQueries,
  510. i = badQueries.length;
  511. while (i--) {
  512. if (q.indexOf(badQueries[i]) === 0) {
  513. return true;
  514. }
  515. }
  516. return false;
  517. },
  518. hide: function () {
  519. var that = this,
  520. container = $(that.suggestionsContainer);
  521. if ($.isFunction(that.options.onHide) && that.visible) {
  522. that.options.onHide.call(that.element, container);
  523. }
  524. that.visible = false;
  525. that.selectedIndex = -1;
  526. clearInterval(that.onChangeInterval);
  527. $(that.suggestionsContainer).hide();
  528. that.signalHint(null);
  529. },
  530. suggest: function () {
  531. if (this.suggestions.length === 0) {
  532. if (this.options.showNoSuggestionNotice) {
  533. this.noSuggestions();
  534. } else {
  535. this.hide();
  536. }
  537. return;
  538. }
  539. var that = this,
  540. options = that.options,
  541. groupBy = options.groupBy,
  542. formatResult = options.formatResult,
  543. value = that.getQuery(that.currentValue),
  544. className = that.classes.suggestion,
  545. classSelected = that.classes.selected,
  546. container = $(that.suggestionsContainer),
  547. noSuggestionsContainer = $(that.noSuggestionsContainer),
  548. beforeRender = options.beforeRender,
  549. html = '',
  550. category,
  551. formatGroup = function (suggestion, index) {
  552. var currentCategory = suggestion.data[groupBy];
  553. if (category === currentCategory){
  554. return '';
  555. }
  556. category = currentCategory;
  557. return '<div class="autocomplete-group"><strong>' + category + '</strong></div>';
  558. };
  559. if (options.triggerSelectOnValidInput && that.isExactMatch(value)) {
  560. that.select(0);
  561. return;
  562. }
  563. // Build suggestions inner HTML:
  564. $.each(that.suggestions, function (i, suggestion) {
  565. if (groupBy){
  566. html += formatGroup(suggestion, value, i);
  567. }
  568. html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value) + '</div>';
  569. });
  570. this.adjustContainerWidth();
  571. noSuggestionsContainer.detach();
  572. container.html(html);
  573. if ($.isFunction(beforeRender)) {
  574. beforeRender.call(that.element, container);
  575. }
  576. that.fixPosition();
  577. container.show();
  578. // Select first value by default:
  579. if (options.autoSelectFirst) {
  580. that.selectedIndex = 0;
  581. container.scrollTop(0);
  582. container.children('.' + className).first().addClass(classSelected);
  583. }
  584. that.visible = true;
  585. that.findBestHint();
  586. },
  587. noSuggestions: function() {
  588. var that = this,
  589. container = $(that.suggestionsContainer),
  590. noSuggestionsContainer = $(that.noSuggestionsContainer);
  591. this.adjustContainerWidth();
  592. // Some explicit steps. Be careful here as it easy to get
  593. // noSuggestionsContainer removed from DOM if not detached properly.
  594. noSuggestionsContainer.detach();
  595. container.empty(); // clean suggestions if any
  596. container.append(noSuggestionsContainer);
  597. that.fixPosition();
  598. container.show();
  599. that.visible = true;
  600. },
  601. adjustContainerWidth: function() {
  602. var that = this,
  603. options = that.options,
  604. width,
  605. container = $(that.suggestionsContainer);
  606. // If width is auto, adjust width before displaying suggestions,
  607. // because if instance was created before input had width, it will be zero.
  608. // Also it adjusts if input width has changed.
  609. // -2px to account for suggestions border.
  610. if (options.width === 'auto') {
  611. width = that.el.outerWidth() - 2;
  612. container.width(width > 0 ? width : 300);
  613. }
  614. },
  615. findBestHint: function () {
  616. var that = this,
  617. value = that.el.val().toLowerCase(),
  618. bestMatch = null;
  619. if (!value) {
  620. return;
  621. }
  622. $.each(that.suggestions, function (i, suggestion) {
  623. var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0;
  624. if (foundMatch) {
  625. bestMatch = suggestion;
  626. }
  627. return !foundMatch;
  628. });
  629. that.signalHint(bestMatch);
  630. },
  631. signalHint: function (suggestion) {
  632. var hintValue = '',
  633. that = this;
  634. if (suggestion) {
  635. hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length);
  636. }
  637. if (that.hintValue !== hintValue) {
  638. that.hintValue = hintValue;
  639. that.hint = suggestion;
  640. (this.options.onHint || $.noop)(hintValue);
  641. }
  642. },
  643. verifySuggestionsFormat: function (suggestions) {
  644. // If suggestions is string array, convert them to supported format:
  645. if (suggestions.length && typeof suggestions[0] === 'string') {
  646. return $.map(suggestions, function (value) {
  647. return { value: value, data: null };
  648. });
  649. }
  650. return suggestions;
  651. },
  652. validateOrientation: function(orientation, fallback) {
  653. orientation = $.trim(orientation || '').toLowerCase();
  654. if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){
  655. orientation = fallback;
  656. }
  657. return orientation;
  658. },
  659. processResponse: function (result, originalQuery, cacheKey) {
  660. var that = this,
  661. options = that.options;
  662. result.suggestions = that.verifySuggestionsFormat(result.suggestions);
  663. // Cache results if cache is not disabled:
  664. if (!options.noCache) {
  665. that.cachedResponse[cacheKey] = result;
  666. if (options.preventBadQueries && result.suggestions.length === 0) {
  667. that.badQueries.push(originalQuery);
  668. }
  669. }
  670. // Return if originalQuery is not matching current query:
  671. if (originalQuery !== that.getQuery(that.currentValue)) {
  672. return;
  673. }
  674. that.suggestions = result.suggestions;
  675. that.suggest();
  676. },
  677. activate: function (index) {
  678. var that = this,
  679. activeItem,
  680. selected = that.classes.selected,
  681. container = $(that.suggestionsContainer),
  682. children = container.find('.' + that.classes.suggestion);
  683. container.find('.' + selected).removeClass(selected);
  684. that.selectedIndex = index;
  685. if (that.selectedIndex !== -1 && children.length > that.selectedIndex) {
  686. activeItem = children.get(that.selectedIndex);
  687. $(activeItem).addClass(selected);
  688. return activeItem;
  689. }
  690. return null;
  691. },
  692. selectHint: function () {
  693. var that = this,
  694. i = $.inArray(that.hint, that.suggestions);
  695. that.select(i);
  696. },
  697. select: function (i) {
  698. var that = this;
  699. that.hide();
  700. that.onSelect(i);
  701. },
  702. moveUp: function () {
  703. var that = this;
  704. if (that.selectedIndex === -1) {
  705. return;
  706. }
  707. if (that.selectedIndex === 0) {
  708. $(that.suggestionsContainer).children().first().removeClass(that.classes.selected);
  709. that.selectedIndex = -1;
  710. that.el.val(that.currentValue);
  711. that.findBestHint();
  712. return;
  713. }
  714. that.adjustScroll(that.selectedIndex - 1);
  715. },
  716. moveDown: function () {
  717. var that = this;
  718. if (that.selectedIndex === (that.suggestions.length - 1)) {
  719. return;
  720. }
  721. that.adjustScroll(that.selectedIndex + 1);
  722. },
  723. adjustScroll: function (index) {
  724. var that = this,
  725. activeItem = that.activate(index);
  726. if (!activeItem) {
  727. return;
  728. }
  729. var offsetTop,
  730. upperBound,
  731. lowerBound,
  732. heightDelta = $(activeItem).outerHeight();
  733. offsetTop = activeItem.offsetTop;
  734. upperBound = $(that.suggestionsContainer).scrollTop();
  735. lowerBound = upperBound + that.options.maxHeight - heightDelta;
  736. if (offsetTop < upperBound) {
  737. $(that.suggestionsContainer).scrollTop(offsetTop);
  738. } else if (offsetTop > lowerBound) {
  739. $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta);
  740. }
  741. if (!that.options.preserveInput) {
  742. that.el.val(that.getValue(that.suggestions[index].value));
  743. }
  744. that.signalHint(null);
  745. },
  746. onSelect: function (index) {
  747. var that = this,
  748. onSelectCallback = that.options.onSelect,
  749. suggestion = that.suggestions[index];
  750. that.currentValue = that.getValue(suggestion.value);
  751. if (that.currentValue !== that.el.val() && !that.options.preserveInput) {
  752. that.el.val(that.currentValue);
  753. }
  754. that.signalHint(null);
  755. that.suggestions = [];
  756. that.selection = suggestion;
  757. if ($.isFunction(onSelectCallback)) {
  758. onSelectCallback.call(that.element, suggestion);
  759. }
  760. },
  761. getValue: function (value) {
  762. var that = this,
  763. delimiter = that.options.delimiter,
  764. currentValue,
  765. parts;
  766. if (!delimiter) {
  767. return value;
  768. }
  769. currentValue = that.currentValue;
  770. parts = currentValue.split(delimiter);
  771. if (parts.length === 1) {
  772. return value;
  773. }
  774. return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
  775. },
  776. dispose: function () {
  777. var that = this;
  778. that.el.off('.autocomplete').removeData('autocomplete');
  779. that.disableKillerFn();
  780. $(window).off('resize.autocomplete', that.fixPositionCapture);
  781. $(that.suggestionsContainer).remove();
  782. }
  783. };
  784. // Create chainable jQuery plugin:
  785. $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) {
  786. var dataKey = 'autocomplete';
  787. // If function invoked without argument return
  788. // instance of the first matched element:
  789. if (arguments.length === 0) {
  790. return this.first().data(dataKey);
  791. }
  792. return this.each(function () {
  793. var inputElement = $(this),
  794. instance = inputElement.data(dataKey);
  795. if (typeof options === 'string') {
  796. if (instance && typeof instance[options] === 'function') {
  797. instance[options](args);
  798. }
  799. } else {
  800. // If instance already exists, destroy it:
  801. if (instance && instance.dispose) {
  802. instance.dispose();
  803. }
  804. instance = new Autocomplete(this, options);
  805. inputElement.data(dataKey, instance);
  806. }
  807. });
  808. };
  809. }));