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