No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 

1024 líneas
33 KiB

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