You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

878 lines
30 KiB

2 years ago
  1. ; (function ($, window) {
  2. "use strict";
  3. var guid = 0,
  4. 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],
  5. allowOptions = ['source', 'empty', 'limit', 'cache', 'focusOpen', 'selectFirst', 'changeWhenSelect', 'highlightMatches', 'ignoredKeyCode', 'customLabel', 'customValue', 'template', 'offset', 'combine', 'callback'],
  6. userAgent = (window.navigator.userAgent || window.navigator.vendor || window.opera),
  7. isFirefox = /Firefox/i.test(userAgent),
  8. isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(userAgent),
  9. isFirefoxMobile = (isFirefox && isMobile),
  10. $body = null,
  11. localStorageKey = 'autocompleterCache',
  12. supportLocalStorage = (function () {
  13. var supported = typeof window.localStorage !== 'undefined';
  14. if (supported) {
  15. try {
  16. localStorage.setItem("autocompleter", "autocompleter");
  17. localStorage.removeItem("autocompleter");
  18. } catch (e) {
  19. supported = false;
  20. }
  21. }
  22. return supported;
  23. })();
  24. /**
  25. * @options
  26. * @param source [(string|object)] <null> "URL to the server or a local object"
  27. * @param empty [boolean] <true> "Launch if value is empty"
  28. * @param limit [int] <10> "Number of results to be displayed"
  29. * @param customClass [array] <[]> "Array with custom classes for autocompleter element"
  30. * @param cache [boolean] <true> "Save xhr data to localStorage to avoid the repetition of requests"
  31. * @param focusOpen [boolean] <true> "Launch autocompleter when input gets focus"
  32. * @param hint [boolean] <false> "Add hint to input with first matched label, correct styles should be installed"
  33. * @param selectFirst [boolean] <false> "If set to true, first element in autocomplete list will be selected automatically, ignore if changeWhenSelect is on"
  34. * @param changeWhenSelect [boolean] <true> "Allows to change input value using arrow keys navigation in autocomplete list"
  35. * @param highlightMatches [boolean] <false> "This option defines <strong> tag wrap for matches in autocomplete results"
  36. * @param ignoredKeyCode [array] <[]> "Array with ignorable keycodes"
  37. * @param customLabel [boolean] <false> "The name of object's property which will be used as a label"
  38. * @param customValue [boolean] <false> "The name of object's property which will be used as a value"
  39. * @param template [(string|boolean)] <false> "Custom template for list items"
  40. * @param offset [(string|boolean)] <false> "Source response offset, for example: response.items.posts"
  41. * @param combine [function] <$.noop> "Returns an object which extends ajax data. Useful if you want to pass some additional server options"
  42. * @param callback [function] <$.noop> "Select value callback function. Arguments: value, index"
  43. */
  44. var options = {
  45. source: null,
  46. empty: true,
  47. limit: 10,
  48. customClass: [],
  49. cache: true,
  50. focusOpen: true,
  51. hint: false,
  52. selectFirst: false,
  53. changeWhenSelect: true,
  54. highlightMatches: false,
  55. ignoredKeyCode: [],
  56. customLabel: false,
  57. customValue: false,
  58. template: false,
  59. offset: false,
  60. combine: $.noop,
  61. callback: $.noop
  62. };
  63. var publics = {
  64. /**
  65. * @method
  66. * @name defaults
  67. * @description Sets default plugin options
  68. * @param opts [object] <{}> "Options object"
  69. * @example $.autocompleter("defaults", opts);
  70. */
  71. defaults: function (opts) {
  72. options = $.extend(options, opts || {});
  73. return $(this);
  74. },
  75. /**
  76. * @method
  77. * @name option
  78. * @description Open autocompleter list
  79. */
  80. option: function (properties) {
  81. return $(this).each(function (i, input) {
  82. var data = $(input).next(".autocompleter").data("autocompleter");
  83. for (var property in properties) {
  84. if ($.inArray(property, allowOptions) !== -1) {
  85. data[property] = properties[property];
  86. }
  87. }
  88. });
  89. },
  90. /**
  91. * @method
  92. * @name open
  93. * @description Open autocompleter list
  94. */
  95. open: function () {
  96. return $(this).each(function (i, input) {
  97. var data = $(input).next(".autocompleter").data("autocompleter");
  98. if (data) {
  99. _open(null, data);
  100. }
  101. });
  102. },
  103. /**
  104. * @method
  105. * @name close
  106. * @description Close autocompleter list
  107. */
  108. close: function () {
  109. return $(this).each(function (i, input) {
  110. var data = $(input).next(".autocompleter").data("autocompleter");
  111. if (data) {
  112. _close(null, data);
  113. }
  114. });
  115. },
  116. /**
  117. * @method
  118. * @name clearCache
  119. * @description Remove localStorage cache
  120. */
  121. clearCache: function () {
  122. _deleteCache();
  123. },
  124. /**
  125. * @method
  126. * @name destroy
  127. * @description Removes instance of plugin
  128. * @example $(".target").autocompleter("destroy");
  129. */
  130. destroy: function () {
  131. return $(this).each(function (i, input) {
  132. var data = $(input).next(".autocompleter").data("autocompleter");
  133. if (data) {
  134. // Abort xhr
  135. if (data.jqxhr) {
  136. data.jqxhr.abort();
  137. }
  138. // If has selected item & open - confirm it
  139. if (data.$autocompleter.hasClass("open")) {
  140. data.$autocompleter.find(".autocompleter-selected")
  141. .trigger("click.autocompleter");
  142. }
  143. // Restore original autocomplete attr
  144. if (!data.originalAutocomplete) {
  145. data.$node.removeAttr("autocomplete");
  146. } else {
  147. data.$node.attr("autocomplete", data.originalAutocomplete);
  148. }
  149. // Remove autocompleter & unbind events
  150. data.$node.off(".autocompleter")
  151. .removeClass("autocompleter-node");
  152. data.$autocompleter.off(".autocompleter")
  153. .remove();
  154. }
  155. });
  156. }
  157. };
  158. /**
  159. * @method private
  160. * @name _init
  161. * @description Initializes plugin
  162. * @param opts [object] "Initialization options"
  163. */
  164. function _init(opts) {
  165. // Local options
  166. opts = $.extend({}, options, opts || {});
  167. // Check for Body
  168. if ($body === null) {
  169. $body = $("body");
  170. }
  171. // Apply to each element
  172. var $items = $(this);
  173. for (var i = 0, count = $items.length; i < count; i++) {
  174. _build($items.eq(i), opts);
  175. }
  176. return $items;
  177. }
  178. /**
  179. * @method private
  180. * @name _build
  181. * @description Builds each instance
  182. * @param $node [jQuery object] "Target jQuery object"
  183. * @param opts [object] <{}> "Options object"
  184. */
  185. function _build($node, opts) {
  186. if (!$node.hasClass("autocompleter-node")) {
  187. // Extend options
  188. opts = $.extend({}, opts, $node.data("autocompleter-options"));
  189. var html = '<div class="autocompleter ' + opts.customClass.join(' ') + '" id="autocompleter-' + (guid + 1) + '">';
  190. if (opts.hint) {
  191. html += '<div class="autocompleter-hint"></div>';
  192. }
  193. html += '<ul class="autocompleter-list"></ul>';
  194. html += '</div>';
  195. $node.addClass("autocompleter-node")
  196. .after(html);
  197. var $autocompleter = $node.next(".autocompleter").eq(0);
  198. // Set autocomplete to off for warn overlay
  199. var originalAutocomplete = $node.attr("autocomplete");
  200. $node.attr("autocomplete", "off");
  201. // Store plugin data
  202. var data = $.extend({
  203. $node: $node,
  204. $autocompleter: $autocompleter,
  205. $selected: null,
  206. $list: null,
  207. index: -1,
  208. hintText: false,
  209. source: false,
  210. jqxhr: false,
  211. response: null,
  212. focused: false,
  213. query: '',
  214. originalAutocomplete: originalAutocomplete,
  215. guid: guid++
  216. }, opts);
  217. // Bind autocompleter events
  218. data.$autocompleter.on("mousedown.autocompleter", ".autocompleter-item", data, _select)
  219. .data("autocompleter", data);
  220. // Bind node events
  221. data.$node.on("keyup.autocompleter", data, _onKeyup)
  222. .on("keydown.autocompleter", data, _onKeydownHelper)
  223. .on("focus.autocompleter", data, _onFocus)
  224. .on("blur.autocompleter", data, _onBlur)
  225. .on("mousedown.autocompleter", data, _onMousedown);
  226. }
  227. }
  228. /**
  229. * @method private
  230. * @name _search
  231. * @description Local search function, return best collation
  232. * @param query [string] "Query string"
  233. * @param source [object] "Source data"
  234. * @param limit [integer] "Results length"
  235. */
  236. function _search(query, source, limit) {
  237. var response = [];
  238. query = query.toUpperCase();
  239. if (source.length) {
  240. for (var i = 0; i < 2; i++) {
  241. for (var item in source) {
  242. if ('clear,insert,remove'.indexOf(item) === -1 && response.length < limit) {
  243. switch (i) {
  244. case 0:
  245. if (source[item].label.toUpperCase().search(query) === 0) {
  246. response.push(source[item]);
  247. delete source[item];
  248. }
  249. break;
  250. case 1:
  251. if (source[item].label.toUpperCase().search(query) !== -1) {
  252. response.push(source[item]);
  253. delete source[item];
  254. }
  255. break;
  256. }
  257. }
  258. }
  259. }
  260. }
  261. return response;
  262. }
  263. /**
  264. * @method private
  265. * @name _launch
  266. * @description Use source locally or create xhr
  267. * @param data [object] "Instance data"
  268. */
  269. function _launch(data) {
  270. data.query = $.trim(data.$node.val());
  271. if (!data.empty && data.query.length === 0) {
  272. _clear(data);
  273. return;
  274. } else {
  275. if (typeof data.source === 'object') {
  276. _clear(data);
  277. // Local search
  278. var search = _search(data.query, _clone(data.source), data.limit);
  279. if (search.length) {
  280. _response(search, data);
  281. }
  282. } else {
  283. if (data.jqxhr) {
  284. data.jqxhr.abort();
  285. }
  286. var ajaxData = $.extend({
  287. limit: data.limit,
  288. query: data.query
  289. }, data.combine());
  290. data.jqxhr = $.ajax({
  291. url: data.source,
  292. dataType: "json",
  293. data: ajaxData,
  294. beforeSend: function (xhr) {
  295. data.$autocompleter.addClass('autocompleter-ajax');
  296. _clear(data);
  297. if (data.cache) {
  298. var stored = _getCache(this.url);
  299. if (stored) {
  300. xhr.abort();
  301. _response(stored, data);
  302. }
  303. }
  304. }
  305. })
  306. .done(function (response) {
  307. // Get subobject from responce
  308. if (data.offset) {
  309. response = _grab(response, data.offset);
  310. }
  311. if (data.cache) {
  312. _setCache(this.url, response);
  313. }
  314. _response(response, data);
  315. })
  316. .always(function () {
  317. data.$autocompleter.removeClass('autocompleter-ajax');
  318. });
  319. }
  320. }
  321. }
  322. /**
  323. * @method private
  324. * @name _clear
  325. * @param data [object] "Instance data"
  326. */
  327. function _clear(data) {
  328. // Clear data
  329. data.response = null;
  330. data.$list = null;
  331. data.$selected = null;
  332. data.index = 0;
  333. data.$autocompleter.find(".autocompleter-list").empty();
  334. data.$autocompleter.find('.autocompleter-hint').removeClass('autocompleter-hint-show').empty();
  335. data.hintText = false;
  336. _close(null, data);
  337. }
  338. /**
  339. * @method private
  340. * @name _response
  341. * @description Main source response function
  342. * @param response [object] "Source data"
  343. * @param data [object] "Instance data"
  344. */
  345. function _response(response, data) {
  346. _buildList(response, data);
  347. if (data.$autocompleter.hasClass('autocompleter-focus')) {
  348. _open(null, data);
  349. }
  350. }
  351. /**
  352. * @method private
  353. * @name _buildList
  354. * @description Generate autocompleter-list and update instance data by source
  355. * @param list [object] "Source data"
  356. * @param data [object] "Instance data"
  357. */
  358. function _buildList(list, data) {
  359. var menu = '';
  360. for (var item = 0, count = list.length; item < count; item++) {
  361. var classes = ["autocompleter-item"];
  362. if (data.selectFirst && item === 0 && !data.changeWhenSelect) {
  363. classes.push("autocompleter-item-selected");
  364. }
  365. var highlightReg = new RegExp(data.query, "gi");
  366. var label = (data.customLabel && list[item][data.customLabel]) ? list[item][data.customLabel] : list[item].label;
  367. var clear = label;
  368. label = data.highlightMatches ? label.replace(highlightReg, "<strong>$&</strong>") : label;
  369. var value = (data.customValue && list[item][data.customValue]) ? list[item][data.customValue] : list[item].value;
  370. // Apply custom template
  371. if (data.template) {
  372. var template = data.template.replace(/({{ label }})/gi, label);
  373. for (var property in list[item]) {
  374. if (list[item].hasOwnProperty(property)) {
  375. var regex = new RegExp('{{ ' + property + ' }}', 'gi');
  376. template = template.replace(regex, list[item][property]);
  377. }
  378. }
  379. label = template;
  380. }
  381. if (value) {
  382. menu += '<li data-value="' + value + '" data-label="' + clear + '" class="' + classes.join(' ') + '">' + label + '</li>';
  383. } else {
  384. menu += '<li data-label="' + clear + '" class="' + classes.join(' ') + '">' + label + '</li>';
  385. }
  386. }
  387. // Set hint
  388. if (list.length && data.hint) {
  389. var hint = (list[0].label.substr(0, data.query.length).toUpperCase() === data.query.toUpperCase()) ? list[0].label : false;
  390. if (hint && (data.query !== list[0].label)) {
  391. var hintReg = new RegExp(data.query, "i");
  392. var hintText = hint.replace(hintReg, "<span>" + data.query + "</span>");
  393. data.$autocompleter.find('.autocompleter-hint').addClass('autocompleter-hint-show').html(hintText);
  394. data.hintText = hintText;
  395. }
  396. }
  397. // Update data
  398. data.response = list;
  399. data.$autocompleter.find(".autocompleter-list").html(menu);
  400. data.$selected = (data.$autocompleter.find(".autocompleter-item-selected").length) ? data.$autocompleter.find(".autocompleter-item-selected") : null;
  401. data.$list = (list.length) ? data.$autocompleter.find(".autocompleter-item") : null;
  402. data.index = data.$selected ? data.$list.index(data.$selected) : -1;
  403. data.$autocompleter.find(".autocompleter-item").each(function (i, j) {
  404. $(j).data(data.response[i]);
  405. });
  406. }
  407. /**
  408. * @method private
  409. * @name _onKeyup
  410. * @description Keyup events in node, up/down autocompleter-list navigation, typing and enter button callbacks
  411. * @param e [object] "Event data"
  412. */
  413. function _onKeyup(e) {
  414. var data = e.data;
  415. var code = e.keyCode ? e.keyCode : e.which;
  416. debugger;
  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 _grab
  681. * @description Grab sub-object from object
  682. * @param response [object] "Native object"
  683. * @param offset [string] "Offset string"
  684. */
  685. function _grab(response, offset) {
  686. offset = offset.split('.');
  687. while (response && offset.length) {
  688. response = response[offset.shift()];
  689. }
  690. return response;
  691. }
  692. /**
  693. * @method private
  694. * @name _getCache
  695. * @description Store AJAX response in plugin cache
  696. * @param url [string] "AJAX get query string"
  697. * @param data [object] "AJAX response data"
  698. */
  699. function _setCache(url, data) {
  700. if (!supportLocalStorage) { return; }
  701. if (url && data) {
  702. cache[url] = {
  703. value: data
  704. };
  705. // Proccess to localStorage
  706. try {
  707. localStorage.setItem(localStorageKey, JSON.stringify(cache));
  708. } catch (e) {
  709. var code = e.code || e.number || e.message;
  710. if (code === 22) {
  711. _deleteCache();
  712. } else {
  713. throw (e);
  714. }
  715. }
  716. }
  717. }
  718. /**
  719. * @method private
  720. * @name _getCache
  721. * @description Get cached data by url if exist
  722. * @param url [string] "AJAX get query string"
  723. */
  724. function _getCache(url) {
  725. if (!url) { return; }
  726. var response = (cache[url] && cache[url].value) ? cache[url].value : false;
  727. return response;
  728. }
  729. /**
  730. * @method private
  731. * @name _loadCache
  732. * @description Load all plugin cache from localStorage
  733. */
  734. function _loadCache() {
  735. if (!supportLocalStorage) { return; }
  736. var json = localStorage.getItem(localStorageKey) || '{}';
  737. return JSON.parse(json);
  738. }
  739. /**
  740. * @method private
  741. * @name _deleteCache
  742. * @description Delete all plugin cache from localStorage
  743. */
  744. function _deleteCache() {
  745. try {
  746. localStorage.removeItem(localStorageKey);
  747. cache = _loadCache();
  748. } catch (e) {
  749. throw (e);
  750. }
  751. }
  752. /**
  753. * @method private
  754. * @name _clone
  755. * @description Clone JavaScript object
  756. */
  757. function _clone(obj) {
  758. if (null === obj || "object" !== typeof obj) {
  759. return obj;
  760. }
  761. var copy = obj.constructor();
  762. for (var attr in obj) {
  763. if (obj.hasOwnProperty(attr)) {
  764. copy[attr] = obj[attr];
  765. }
  766. }
  767. return copy;
  768. }
  769. // Load cache
  770. var cache = _loadCache();
  771. $.fn.autocompleter = function (method) {
  772. if (publics[method]) {
  773. return publics[method].apply(this, Array.prototype.slice.call(arguments, 1));
  774. } else if (typeof method === 'object' || !method) {
  775. return _init.apply(this, arguments);
  776. }
  777. return this;
  778. };
  779. $.autocompleter = function (method) {
  780. if (method === "defaults") {
  781. publics.defaults.apply(this, Array.prototype.slice.call(arguments, 1));
  782. } else if (method === "clearCache") {
  783. publics.clearCache.apply(this, null);
  784. }
  785. };
  786. })(jQuery, window);