jquery.autocompleter.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. /*
  2. * Autocompleter v0.1.2 - 2014-05-20
  3. * Simple, easy, customisable and with cache support.
  4. * http://github.com/ArtemFitiskin/jquery-autocompleter
  5. *
  6. * Copyright 2014 Artem Fitiskin; MIT Licensed
  7. */
  8. ;(function ($, window) {
  9. "use strict";
  10. var guid = 0,
  11. ignoredKeyCode = [9, 13, 17, 19, 20, 27, 33, 34, 35, 36, 37, 39, 44, 92, 113, 114, 115, 118, 119, 120, 122, 123, 144, 145],
  12. allowOptions = ['source', 'empty', 'limit', 'cache', 'focusOpen', 'selectFirst', 'changeWhenSelect', 'highlightMatches', 'ignoredKeyCode', 'customLabel', 'customValue', 'template', 'combine', 'callback'],
  13. userAgent = (window.navigator.userAgent||window.navigator.vendor||window.opera),
  14. isFirefox = /Firefox/i.test(userAgent),
  15. isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(userAgent),
  16. isFirefoxMobile = (isFirefox && isMobile),
  17. $body = null,
  18. localStorageKey = 'autocompleterCache',
  19. supportLocalStorage = (function () {
  20. var supported = typeof window.localStorage !== 'undefined';
  21. if (supported) {
  22. try {
  23. localStorage.setItem("autocompleter", "autocompleter");
  24. localStorage.removeItem("autocompleter");
  25. } catch (e) {
  26. supported = false;
  27. }
  28. }
  29. return supported;
  30. })();
  31. /**
  32. * @options
  33. * @param source [(string|object)] <null> "URL to the server or a local object"
  34. * @param empty [boolean] <true> "Launch if value is empty"
  35. * @param limit [int] <10> "Number of results to be displayed"
  36. * @param customClass [array] <[]> "Array with custom classes for autocompleter element"
  37. * @param cache [boolean] <true> "Save xhr data to localStorage to avoid the repetition of requests"
  38. * @param focusOpen [boolean] <true> "Launch autocompleter when input gets focus"
  39. * @param hint [boolean] <false> "Add hint to input with first matched label, correct styles should be installed"
  40. * @param selectFirst [boolean] <false> "If set to true, first element in autocomplete list will be selected automatically, ignore if changeWhenSelect is on"
  41. * @param changeWhenSelect [boolean] <true> "Allows to change input value using arrow keys navigation in autocomplete list"
  42. * @param highlightMatches [boolean] <false> "This option defines <strong> tag wrap for matches in autocomplete results"
  43. * @param ignoredKeyCode [array] <[]> "Array with ignorable keycodes"
  44. * @param customLabel [boolean] <false> "The name of object's property which will be used as a label"
  45. * @param customValue [boolean] <false> "The name of object's property which will be used as a value"
  46. * @param template [(string|boolean)] <false> "Custom template for list items"
  47. * @param combine [function] <$.noop> "Returns an object which extends ajax data. Useful if you want to pass some additional server options"
  48. * @param callback [function] <$.noop> "Select value callback function. Arguments: value, index"
  49. */
  50. var options = {
  51. source: null,
  52. empty: true,
  53. limit: 10,
  54. customClass: [],
  55. cache: true,
  56. focusOpen: true,
  57. hint: false,
  58. selectFirst: false,
  59. changeWhenSelect: true,
  60. highlightMatches: false,
  61. ignoredKeyCode: [],
  62. customLabel: false,
  63. customValue: false,
  64. template: false,
  65. combine: $.noop,
  66. callback: $.noop
  67. };
  68. var publics = {
  69. /**
  70. * @method
  71. * @name defaults
  72. * @description Sets default plugin options
  73. * @param opts [object] <{}> "Options object"
  74. * @example $.autocompleter("defaults", opts);
  75. */
  76. defaults: function (opts) {
  77. options = $.extend(options, opts || {});
  78. return $(this);
  79. },
  80. /**
  81. * @method
  82. * @name option
  83. * @description Open autocompleter list
  84. */
  85. option: function (properties) {
  86. return $(this).each(function(i, input) {
  87. var data = $(input).next(".autocompleter").data("autocompleter");
  88. for (var property in properties) {
  89. if ($.inArray(property, allowOptions) !== -1) {
  90. data[property] = properties[property];
  91. }
  92. }
  93. });
  94. },
  95. /**
  96. * @method
  97. * @name open
  98. * @description Open autocompleter list
  99. */
  100. open: function () {
  101. return $(this).each(function(i, input) {
  102. var data = $(input).next(".autocompleter").data("autocompleter");
  103. if (data) {
  104. _open(null, data);
  105. }
  106. });
  107. },
  108. /**
  109. * @method
  110. * @name close
  111. * @description Close autocompleter list
  112. */
  113. close: function () {
  114. return $(this).each(function(i, input) {
  115. var data = $(input).next(".autocompleter").data("autocompleter");
  116. if (data) {
  117. _close(null, data);
  118. }
  119. });
  120. },
  121. /**
  122. * @method
  123. * @name clearCache
  124. * @description Remove localStorage cache
  125. */
  126. clearCache: function () {
  127. _deleteCache();
  128. },
  129. /**
  130. * @method
  131. * @name destroy
  132. * @description Removes instance of plugin
  133. * @example $(".target").autocompleter("destroy");
  134. */
  135. destroy: function () {
  136. return $(this).each(function (i, input) {
  137. var data = $(input).next(".autocompleter").data("autocompleter");
  138. if (data) {
  139. // Abort xhr
  140. if (data.jqxhr) {
  141. data.jqxhr.abort();
  142. }
  143. // If has selected item & open - confirm it
  144. if (data.$autocompleter.hasClass("open")) {
  145. data.$autocompleter.find(".autocompleter-selected")
  146. .trigger("click.autocompleter");
  147. }
  148. // Restore original autocomplete attr
  149. if(!data.originalAutocomplete) {
  150. data.$node.removeAttr("autocomplete");
  151. } else {
  152. data.$node.attr("autocomplete", data.originalAutocomplete);
  153. }
  154. // Remove autocompleter & unbind events
  155. data.$node.off(".autocompleter")
  156. .removeClass("autocompleter-node");
  157. data.$autocompleter.off(".autocompleter")
  158. .remove();
  159. }
  160. });
  161. }
  162. };
  163. /**
  164. * @method private
  165. * @name _init
  166. * @description Initializes plugin
  167. * @param opts [object] "Initialization options"
  168. */
  169. function _init(opts) {
  170. // Local options
  171. opts = $.extend({}, options, opts || {});
  172. // Check for Body
  173. if ($body === null) {
  174. $body = $("body");
  175. }
  176. // Apply to each element
  177. var $items = $(this);
  178. for (var i = 0, count = $items.length; i < count; i++) {
  179. _build($items.eq(i), opts);
  180. }
  181. return $items;
  182. }
  183. /**
  184. * @method private
  185. * @name _build
  186. * @description Builds each instance
  187. * @param $node [jQuery object] "Target jQuery object"
  188. * @param opts [object] <{}> "Options object"
  189. */
  190. function _build($node, opts) {
  191. if (!$node.hasClass("autocompleter-node")) {
  192. // Extend options
  193. opts = $.extend({}, opts, $node.data("autocompleter-options"));
  194. var html = '<div class="autocompleter '+opts.customClass.join(' ')+'" id="autocompleter-'+(guid+1)+'">';
  195. if (opts.hint) {
  196. html += '<div class="autocompleter-hint"></div>';
  197. }
  198. html += '<ul class="autocompleter-list"></ul>';
  199. html += '</div>';
  200. $node.addClass("autocompleter-node")
  201. .after(html);
  202. var $autocompleter = $node.next(".autocompleter").eq(0);
  203. // Set autocomplete to off for warn overlay
  204. var originalAutocomplete = $node.attr("autocomplete");
  205. $node.attr("autocomplete", "off");
  206. // Store plugin data
  207. var data = $.extend({
  208. $node: $node,
  209. $autocompleter: $autocompleter,
  210. $selected: null,
  211. $list: null,
  212. index: -1,
  213. hintText: false,
  214. source: false,
  215. jqxhr: false,
  216. response: null,
  217. focused: false,
  218. query: '',
  219. originalAutocomplete: originalAutocomplete,
  220. guid: guid++
  221. }, opts);
  222. // Bind autocompleter events
  223. data.$autocompleter.on("mousedown.autocompleter", ".autocompleter-item", data, _select)
  224. .data("autocompleter", data);
  225. // Bind node events
  226. data.$node.on("keyup.autocompleter", data, _onKeyup)
  227. .on("keydown.autocompleter", data, _onKeydownHelper)
  228. .on("focus.autocompleter", data, _onFocus)
  229. .on("blur.autocompleter", data, _onBlur)
  230. .on("mousedown.autocompleter", data, _onMousedown);
  231. }
  232. }
  233. /**
  234. * @method private
  235. * @name _search
  236. * @description Local search function, return best collation
  237. * @param query [string] "Query string"
  238. * @param source [object] "Source data"
  239. * @param limit [integer] "Results length"
  240. */
  241. function _search(query, source, limit) {
  242. var response = [];
  243. query = query.toUpperCase();
  244. if (source.length) {
  245. for (var i = 0; i < 2; i++) {
  246. for (var item in source) {
  247. if (response.length < limit) {
  248. switch (i) {
  249. case 0:
  250. if (source[item].label.toUpperCase().search(query) === 0) {
  251. response.push(source[item]);
  252. delete source[item];
  253. }
  254. break;
  255. case 1:
  256. if (source[item].label.toUpperCase().search(query) !== -1) {
  257. response.push(source[item]);
  258. delete source[item];
  259. }
  260. break;
  261. }
  262. }
  263. }
  264. }
  265. }
  266. return response;
  267. }
  268. /**
  269. * @method private
  270. * @name _launch
  271. * @description Use source locally or create xhr
  272. * @param data [object] "Instance data"
  273. */
  274. function _launch(data) {
  275. data.query = $.trim(data.$node.val());
  276. if (!data.empty && data.query.length === 0) {
  277. _clear(data);
  278. return;
  279. } else {
  280. if (typeof data.source === 'object') {
  281. _clear(data);
  282. // Local search
  283. var search = _search(data.query, _clone(data.source), data.limit);
  284. if (search.length) {
  285. _response(search, data);
  286. }
  287. } else {
  288. if (data.jqxhr) {
  289. data.jqxhr.abort();
  290. }
  291. var ajaxData = $.extend({
  292. limit: data.limit,
  293. query: data.query
  294. }, data.combine());
  295. data.jqxhr = $.ajax({
  296. url: data.source,
  297. dataType: "json",
  298. data: ajaxData,
  299. beforeSend: function (xhr) {
  300. data.$autocompleter.addClass('autocompleter-ajax');
  301. _clear(data);
  302. if (data.cache) {
  303. var stored = _getCache(this.url);
  304. if (stored) {
  305. xhr.abort();
  306. _response(stored, data);
  307. }
  308. }
  309. }
  310. })
  311. .done(function (response) {
  312. if (data.cache) {
  313. _setCache(this.url, response);
  314. }
  315. _response(response, data);
  316. })
  317. .always(function () {
  318. data.$autocompleter.removeClass('autocompleter-ajax');
  319. });
  320. }
  321. }
  322. }
  323. /**
  324. * @method private
  325. * @name _clear
  326. * @param data [object] "Instance data"
  327. */
  328. function _clear(data) {
  329. // Clear data
  330. data.response = null;
  331. data.$list = null;
  332. data.$selected = null;
  333. data.index = 0;
  334. data.$autocompleter.find(".autocompleter-list").empty();
  335. data.$autocompleter.find('.autocompleter-hint').removeClass('autocompleter-hint-show').empty();
  336. data.hintText = false;
  337. _close(null, data);
  338. }
  339. /**
  340. * @method private
  341. * @name _response
  342. * @description Main source response function
  343. * @param response [object] "Source data"
  344. * @param data [object] "Instance data"
  345. */
  346. function _response(response, data) {
  347. _buildList(response, data);
  348. if (data.$autocompleter.hasClass('autocompleter-focus')) {
  349. _open(null, data);
  350. }
  351. }
  352. /**
  353. * @method private
  354. * @name _buildList
  355. * @description Generate autocompleter-list and update instance data by source
  356. * @param list [object] "Source data"
  357. * @param data [object] "Instance data"
  358. */
  359. function _buildList(list, data) {
  360. var menu = '';
  361. for (var item = 0, count = list.length; item < count; item++) {
  362. var classes = ["autocompleter-item"];
  363. if (data.selectFirst && item === 0 && !data.changeWhenSelect) {
  364. classes.push("autocompleter-item-selected");
  365. }
  366. var highlightReg = new RegExp(data.query, "gi");
  367. var label = (data.customLabel && list[item][data.customLabel]) ? list[item][data.customLabel] : list[item].label;
  368. var clear = label;
  369. label = data.highlightMatches ? label.replace(highlightReg, "<strong>$&</strong>") : label;
  370. var value = (data.customValue && list[item][data.customValue]) ? list[item][data.customValue] : list[item].value;
  371. // Apply custom template
  372. if (data.template) {
  373. var template = data.template.replace(/({{ label }})/gi, label);
  374. for (var property in list[item]) {
  375. if (list[item].hasOwnProperty(property)) {
  376. var regex = new RegExp('{{ '+ property +' }}', 'gi');
  377. template = template.replace(regex, list[item][property]);
  378. }
  379. }
  380. label = template;
  381. }
  382. if (value) {
  383. menu += '<li data-value="'+value+'" data-label="'+clear+'" class="'+classes.join(' ')+'">'+label+'</li>';
  384. } else {
  385. menu += '<li data-label="'+clear+'" class="'+classes.join(' ')+'">'+label+'</li>';
  386. }
  387. }
  388. // Set hint
  389. if (list.length && data.hint) {
  390. var hint = ( list[0].label.substr(0, data.query.length).toUpperCase() === data.query.toUpperCase() ) ? list[0].label : false;
  391. if (hint && (data.query !== list[0].label)) {
  392. var hintReg = new RegExp(data.query, "i");
  393. var hintText = hint.replace(hintReg, "<span>"+data.query+"</span>");
  394. data.$autocompleter.find('.autocompleter-hint').addClass('autocompleter-hint-show').html(hintText);
  395. data.hintText = hintText;
  396. }
  397. }
  398. // Update data
  399. data.response = list;
  400. data.$autocompleter.find(".autocompleter-list").html(menu);
  401. data.$selected = (data.$autocompleter.find(".autocompleter-item-selected").length) ? data.$autocompleter.find(".autocompleter-item-selected") : null;
  402. data.$list = (list.length) ? data.$autocompleter.find(".autocompleter-item") : null;
  403. data.index = data.$selected ? data.$list.index(data.$selected) : -1;
  404. data.$autocompleter.find(".autocompleter-item").each(function (i, j) {
  405. $(j).data(data.response[i]);
  406. });
  407. }
  408. /**
  409. * @method private
  410. * @name _onKeyup
  411. * @description Keyup events in node, up/down autocompleter-list navigation, typing and enter button callbacks
  412. * @param e [object] "Event data"
  413. */
  414. function _onKeyup(e) {
  415. var data = e.data;
  416. var code = e.keyCode ? e.keyCode : e.which;
  417. if ( (code === 40 || code === 38) && data.$autocompleter.hasClass('autocompleter-show') ) {
  418. // Arrows up & down
  419. var len = data.$list.length,
  420. next,
  421. prev;
  422. if (len) {
  423. // Determine new index
  424. if (len > 1) {
  425. if (data.index === len - 1) {
  426. next = data.changeWhenSelect ? -1 : 0;
  427. prev = data.index - 1;
  428. } else if (data.index === 0) {
  429. next = data.index + 1;
  430. prev = data.changeWhenSelect ? -1 : len - 1;
  431. } else if (data.index === -1) {
  432. next = 0;
  433. prev = len - 1;
  434. } else {
  435. next = data.index + 1;
  436. prev = data.index - 1;
  437. }
  438. } else if (data.index === -1) {
  439. next = 0;
  440. prev = 0;
  441. } else {
  442. prev = -1;
  443. next = -1;
  444. }
  445. data.index = (code === 40) ? next : prev;
  446. // Update HTML
  447. data.$list.removeClass("autocompleter-item-selected");
  448. if (data.index !== -1) {
  449. data.$list.eq(data.index).addClass("autocompleter-item-selected");
  450. }
  451. data.$selected = data.$autocompleter.find(".autocompleter-item-selected").length ? data.$autocompleter.find(".autocompleter-item-selected") : null;
  452. if (data.changeWhenSelect) {
  453. _setValue(data);
  454. }
  455. }
  456. } else if ($.inArray(code, ignoredKeyCode) === -1 && $.inArray(code, data.ignoredKeyCode) === -1) {
  457. // Typing
  458. _launch(data);
  459. }
  460. }
  461. /**
  462. * @method private
  463. * @name _onKeydownHelper
  464. * @description Keydown events in node, up/down for prevent cursor moving and right arrow for hint
  465. * @param e [object] "Event data"
  466. */
  467. function _onKeydownHelper(e) {
  468. var code = e.keyCode ? e.keyCode : e.which;
  469. var data = e.data;
  470. if (code === 40 || code === 38 ) {
  471. e.preventDefault();
  472. e.stopPropagation();
  473. } else if (code === 39) {
  474. // Right arrow
  475. if (data.hint && data.hintText && data.$autocompleter.find('.autocompleter-hint').hasClass('autocompleter-hint-show')) {
  476. e.preventDefault();
  477. e.stopPropagation();
  478. var hintOrigin = data.$autocompleter.find(".autocompleter-item").length ? data.$autocompleter.find(".autocompleter-item").eq(0).attr('data-label') : false;
  479. if (hintOrigin) {
  480. data.query = hintOrigin;
  481. _setHint(data);
  482. }
  483. }
  484. } else if (code === 13) {
  485. // Enter
  486. if (data.$autocompleter.hasClass('autocompleter-show') && data.$selected) {
  487. _select(e);
  488. }
  489. }
  490. }
  491. /**
  492. * @method private
  493. * @name _onFocus
  494. * @description Handles instance focus
  495. * @param e [object] "Event data"
  496. * @param internal [boolean] "Called by plugin"
  497. */
  498. function _onFocus(e, internal) {
  499. if (!internal) {
  500. var data = e.data;
  501. data.$autocompleter.addClass("autocompleter-focus");
  502. if (!data.$node.prop("disabled") && !data.$autocompleter.hasClass('autocompleter-show')) {
  503. if (data.focusOpen) {
  504. _launch(data);
  505. data.focused = true;
  506. setTimeout(function () {
  507. data.focused = false;
  508. }, 500);
  509. }
  510. }
  511. }
  512. }
  513. /**
  514. * @method private
  515. * @name _onBlur
  516. * @description Handles instance blur
  517. * @param e [object] "Event data"
  518. * @param internal [boolean] "Called by plugin"
  519. */
  520. function _onBlur(e, internal) {
  521. e.preventDefault();
  522. e.stopPropagation();
  523. var data = e.data;
  524. if (!internal) {
  525. data.$autocompleter.removeClass("autocompleter-focus");
  526. _close(e);
  527. }
  528. }
  529. /**
  530. * @method private
  531. * @name _onMousedown
  532. * @description Handles mousedown to node
  533. * @param e [object] "Event data"
  534. */
  535. function _onMousedown(e) {
  536. // Disable middle & right mouse click
  537. if (e.type === "mousedown" && $.inArray(e.which, [2, 3]) !== -1) { return; }
  538. var data = e.data;
  539. if (data.$list && !data.focused) {
  540. if (!data.$node.is(":disabled")) {
  541. if (isMobile && !isFirefoxMobile) {
  542. var el = data.$select[0];
  543. if (window.document.createEvent) { // All
  544. var evt = window.document.createEvent("MouseEvents");
  545. evt.initMouseEvent("mousedown", false, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  546. el.dispatchEvent(evt);
  547. } else if (el.fireEvent) { // IE
  548. el.fireEvent("onmousedown");
  549. }
  550. } else {
  551. // Delegate intent
  552. if (data.$autocompleter.hasClass("autocompleter-closed")) {
  553. _open(e);
  554. } else if (data.$autocompleter.hasClass("autocompleter-show")) {
  555. _close(e);
  556. }
  557. }
  558. }
  559. }
  560. }
  561. /**
  562. * @method private
  563. * @name _open
  564. * @description Opens option set
  565. * @param e [object] "Event data"
  566. * @param instanceData [object] "Instance data"
  567. */
  568. function _open(e, instanceData) {
  569. var data = e ? e.data : instanceData;
  570. if (!data.$node.prop("disabled") && !data.$autocompleter.hasClass("autocompleter-show") && data.$list && data.$list.length ) {
  571. data.$autocompleter.removeClass("autocompleter-closed").addClass("autocompleter-show");
  572. $body.on("click.autocompleter-" + data.guid, ":not(.autocompleter-item)", data, _closeHelper);
  573. }
  574. }
  575. /**
  576. * @method private
  577. * @name _closeHelper
  578. * @description Determines if event target is outside instance before closing
  579. * @param e [object] "Event data"
  580. */
  581. function _closeHelper(e) {
  582. if ( $(e.target).hasClass('autocompleter-node') ) {
  583. return;
  584. }
  585. if ($(e.currentTarget).parents(".autocompleter").length === 0) {
  586. _close(e);
  587. }
  588. }
  589. /**
  590. * @method private
  591. * @name _close
  592. * @description Closes option set
  593. * @param e [object] "Event data"
  594. * @param instanceData [object] "Instance data"
  595. */
  596. function _close(e, instanceData) {
  597. var data = e ? e.data : instanceData;
  598. if (data.$autocompleter.hasClass("autocompleter-show")) {
  599. data.$autocompleter.removeClass("autocompleter-show").addClass("autocompleter-closed");
  600. $body.off(".autocompleter-" + data.guid);
  601. }
  602. }
  603. /**
  604. * @method private
  605. * @name _select
  606. * @description Select item from .autocompleter-list
  607. * @param e [object] "Event data"
  608. */
  609. function _select(e) {
  610. // Disable middle & right mouse click
  611. if (e.type === "mousedown" && $.inArray(e.which, [2, 3]) !== -1) { return; }
  612. var data = e.data;
  613. e.preventDefault();
  614. e.stopPropagation();
  615. if (e.type === "mousedown" && $(this).length) {
  616. data.$selected = $(this);
  617. data.index = data.$list.index(data.$selected);
  618. }
  619. if (!data.$node.prop("disabled")) {
  620. _close(e);
  621. _update(data);
  622. if (e.type === "click") {
  623. data.$node.trigger("focus", [true]);
  624. }
  625. }
  626. }
  627. /**
  628. * @method private
  629. * @name _setHint
  630. * @description Set autocompleter by hint
  631. * @param data [object] "Instance data"
  632. */
  633. function _setHint(data) {
  634. _setValue(data);
  635. _handleChange(data);
  636. _launch(data);
  637. }
  638. /**
  639. * @method private
  640. * @name _setValue
  641. * @description Set value for native field
  642. * @param data [object] "Instance data"
  643. */
  644. function _setValue(data) {
  645. if (data.$selected) {
  646. if (data.hintText && data.$autocompleter.find('.autocompleter-hint').hasClass('autocompleter-hint-show')) {
  647. data.$autocompleter.find('.autocompleter-hint').removeClass('autocompleter-hint-show');
  648. }
  649. var value = data.$selected.attr('data-value') ? data.$selected.attr('data-value') : data.$selected.attr('data-label');
  650. data.$node.val(value);
  651. } else {
  652. if (data.hintText && !data.$autocompleter.find('.autocompleter-hint').hasClass('autocompleter-hint-show')) {
  653. data.$autocompleter.find('.autocompleter-hint').addClass('autocompleter-hint-show');
  654. }
  655. data.$node.val(data.query);
  656. }
  657. }
  658. /**
  659. * @method private
  660. * @name _update
  661. * @param data [object] "Instance data"
  662. */
  663. function _update(data) {
  664. _setValue(data);
  665. _handleChange(data);
  666. _clear(data);
  667. }
  668. /**
  669. * @method private
  670. * @name _handleChange
  671. * @description Trigger node change event and call the callback function
  672. * @param data [object] "Instance data"
  673. */
  674. function _handleChange(data) {
  675. data.callback.call(data.$autocompleter, data.$node.val(), data.index, data.response[data.index]);
  676. data.$node.trigger("change");
  677. }
  678. /**
  679. * @method private
  680. * @name _getCache
  681. * @description Store AJAX response in plugin cache
  682. * @param url [string] "AJAX get query string"
  683. * @param data [object] "AJAX response data"
  684. */
  685. function _setCache(url, data) {
  686. if (!supportLocalStorage) { return; }
  687. if (url && data) {
  688. cache[url] = {
  689. value: data
  690. };
  691. // Proccess to localStorage
  692. try {
  693. localStorage.setItem(localStorageKey, JSON.stringify(cache));
  694. } catch (e) {
  695. var code = e.code || e.number || e.message;
  696. if (code === 22) {
  697. _deleteCache();
  698. } else {
  699. throw(e);
  700. }
  701. }
  702. }
  703. }
  704. /**
  705. * @method private
  706. * @name _getCache
  707. * @description Get cached data by url if exist
  708. * @param url [string] "AJAX get query string"
  709. */
  710. function _getCache(url) {
  711. if (!url) { return; }
  712. var response = (cache[url] && cache[url].value) ? cache[url].value : false;
  713. return response;
  714. }
  715. /**
  716. * @method private
  717. * @name _loadCache
  718. * @description Load all plugin cache from localStorage
  719. */
  720. function _loadCache() {
  721. if (!supportLocalStorage) { return; }
  722. var json = localStorage.getItem(localStorageKey) || '{}';
  723. return JSON.parse(json);
  724. }
  725. /**
  726. * @method private
  727. * @name _deleteCache
  728. * @description Delete all plugin cache from localStorage
  729. */
  730. function _deleteCache() {
  731. try {
  732. localStorage.removeItem(localStorageKey);
  733. cache = _loadCache();
  734. } catch (e) {
  735. throw(e);
  736. }
  737. }
  738. /**
  739. * @method private
  740. * @name _clone
  741. * @description Clone JavaScript object
  742. */
  743. function _clone(obj) {
  744. if (null === obj || "object" !== typeof obj) {
  745. return obj;
  746. }
  747. var copy = obj.constructor();
  748. for (var attr in obj) {
  749. if (obj.hasOwnProperty(attr)) {
  750. copy[attr] = obj[attr];
  751. }
  752. }
  753. return copy;
  754. }
  755. // Load cache
  756. var cache = _loadCache();
  757. $.fn.autocompleter = function (method) {
  758. if (publics[method]) {
  759. return publics[method].apply(this, Array.prototype.slice.call(arguments, 1));
  760. } else if (typeof method === 'object' || !method) {
  761. return _init.apply(this, arguments);
  762. }
  763. return this;
  764. };
  765. $.autocompleter = function (method) {
  766. if (method === "defaults") {
  767. publics.defaults.apply(this, Array.prototype.slice.call(arguments, 1));
  768. } else if (method === "clearCache") {
  769. publics.clearCache.apply(this, null);
  770. }
  771. };
  772. })(jQuery, window);