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.
879 lines
30 KiB
879 lines
30 KiB
; (function ($, window) {
|
|
"use strict";
|
|
|
|
var guid = 0,
|
|
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],
|
|
allowOptions = ['source', 'empty', 'limit', 'cache', 'focusOpen', 'selectFirst', 'changeWhenSelect', 'highlightMatches', 'ignoredKeyCode', 'customLabel', 'customValue', 'template', 'offset', 'combine', 'callback'],
|
|
userAgent = (window.navigator.userAgent || window.navigator.vendor || window.opera),
|
|
isFirefox = /Firefox/i.test(userAgent),
|
|
isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(userAgent),
|
|
isFirefoxMobile = (isFirefox && isMobile),
|
|
$body = null,
|
|
localStorageKey = 'autocompleterCache',
|
|
supportLocalStorage = (function () {
|
|
var supported = typeof window.localStorage !== 'undefined';
|
|
if (supported) {
|
|
try {
|
|
localStorage.setItem("autocompleter", "autocompleter");
|
|
localStorage.removeItem("autocompleter");
|
|
} catch (e) {
|
|
supported = false;
|
|
}
|
|
}
|
|
return supported;
|
|
})();
|
|
|
|
/**
|
|
* @options
|
|
* @param source [(string|object)] <null> "URL to the server or a local object"
|
|
* @param empty [boolean] <true> "Launch if value is empty"
|
|
* @param limit [int] <10> "Number of results to be displayed"
|
|
* @param customClass [array] <[]> "Array with custom classes for autocompleter element"
|
|
* @param cache [boolean] <true> "Save xhr data to localStorage to avoid the repetition of requests"
|
|
* @param focusOpen [boolean] <true> "Launch autocompleter when input gets focus"
|
|
* @param hint [boolean] <false> "Add hint to input with first matched label, correct styles should be installed"
|
|
* @param selectFirst [boolean] <false> "If set to true, first element in autocomplete list will be selected automatically, ignore if changeWhenSelect is on"
|
|
* @param changeWhenSelect [boolean] <true> "Allows to change input value using arrow keys navigation in autocomplete list"
|
|
* @param highlightMatches [boolean] <false> "This option defines <strong> tag wrap for matches in autocomplete results"
|
|
* @param ignoredKeyCode [array] <[]> "Array with ignorable keycodes"
|
|
* @param customLabel [boolean] <false> "The name of object's property which will be used as a label"
|
|
* @param customValue [boolean] <false> "The name of object's property which will be used as a value"
|
|
* @param template [(string|boolean)] <false> "Custom template for list items"
|
|
* @param offset [(string|boolean)] <false> "Source response offset, for example: response.items.posts"
|
|
* @param combine [function] <$.noop> "Returns an object which extends ajax data. Useful if you want to pass some additional server options"
|
|
* @param callback [function] <$.noop> "Select value callback function. Arguments: value, index"
|
|
*/
|
|
var options = {
|
|
source: null,
|
|
empty: true,
|
|
limit: 10,
|
|
customClass: [],
|
|
cache: true,
|
|
focusOpen: true,
|
|
hint: false,
|
|
selectFirst: false,
|
|
changeWhenSelect: true,
|
|
highlightMatches: false,
|
|
ignoredKeyCode: [],
|
|
customLabel: false,
|
|
customValue: false,
|
|
template: false,
|
|
offset: false,
|
|
combine: $.noop,
|
|
callback: $.noop
|
|
};
|
|
|
|
var publics = {
|
|
/**
|
|
* @method
|
|
* @name defaults
|
|
* @description Sets default plugin options
|
|
* @param opts [object] <{}> "Options object"
|
|
* @example $.autocompleter("defaults", opts);
|
|
*/
|
|
defaults: function (opts) {
|
|
options = $.extend(options, opts || {});
|
|
return $(this);
|
|
},
|
|
|
|
/**
|
|
* @method
|
|
* @name option
|
|
* @description Open autocompleter list
|
|
*/
|
|
option: function (properties) {
|
|
return $(this).each(function (i, input) {
|
|
var data = $(input).next(".autocompleter").data("autocompleter");
|
|
|
|
for (var property in properties) {
|
|
if ($.inArray(property, allowOptions) !== -1) {
|
|
data[property] = properties[property];
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @method
|
|
* @name open
|
|
* @description Open autocompleter list
|
|
*/
|
|
open: function () {
|
|
return $(this).each(function (i, input) {
|
|
var data = $(input).next(".autocompleter").data("autocompleter");
|
|
|
|
if (data) {
|
|
_open(null, data);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @method
|
|
* @name close
|
|
* @description Close autocompleter list
|
|
*/
|
|
close: function () {
|
|
return $(this).each(function (i, input) {
|
|
var data = $(input).next(".autocompleter").data("autocompleter");
|
|
|
|
if (data) {
|
|
_close(null, data);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @method
|
|
* @name clearCache
|
|
* @description Remove localStorage cache
|
|
*/
|
|
clearCache: function () {
|
|
_deleteCache();
|
|
},
|
|
|
|
/**
|
|
* @method
|
|
* @name destroy
|
|
* @description Removes instance of plugin
|
|
* @example $(".target").autocompleter("destroy");
|
|
*/
|
|
destroy: function () {
|
|
return $(this).each(function (i, input) {
|
|
var data = $(input).next(".autocompleter").data("autocompleter");
|
|
|
|
if (data) {
|
|
// Abort xhr
|
|
if (data.jqxhr) {
|
|
data.jqxhr.abort();
|
|
}
|
|
|
|
// If has selected item & open - confirm it
|
|
if (data.$autocompleter.hasClass("open")) {
|
|
data.$autocompleter.find(".autocompleter-selected")
|
|
.trigger("click.autocompleter");
|
|
}
|
|
|
|
// Restore original autocomplete attr
|
|
if (!data.originalAutocomplete) {
|
|
data.$node.removeAttr("autocomplete");
|
|
} else {
|
|
data.$node.attr("autocomplete", data.originalAutocomplete);
|
|
}
|
|
|
|
// Remove autocompleter & unbind events
|
|
data.$node.off(".autocompleter")
|
|
.removeClass("autocompleter-node");
|
|
data.$autocompleter.off(".autocompleter")
|
|
.remove();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method private
|
|
* @name _init
|
|
* @description Initializes plugin
|
|
* @param opts [object] "Initialization options"
|
|
*/
|
|
function _init(opts) {
|
|
// Local options
|
|
opts = $.extend({}, options, opts || {});
|
|
|
|
// Check for Body
|
|
if ($body === null) {
|
|
$body = $("body");
|
|
}
|
|
|
|
// Apply to each element
|
|
var $items = $(this);
|
|
for (var i = 0, count = $items.length; i < count; i++) {
|
|
_build($items.eq(i), opts);
|
|
}
|
|
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _build
|
|
* @description Builds each instance
|
|
* @param $node [jQuery object] "Target jQuery object"
|
|
* @param opts [object] <{}> "Options object"
|
|
*/
|
|
function _build($node, opts) {
|
|
if (!$node.hasClass("autocompleter-node")) {
|
|
// Extend options
|
|
opts = $.extend({}, opts, $node.data("autocompleter-options"));
|
|
|
|
var html = '<div class="autocompleter ' + opts.customClass.join(' ') + '" id="autocompleter-' + (guid + 1) + '">';
|
|
if (opts.hint) {
|
|
html += '<div class="autocompleter-hint"></div>';
|
|
}
|
|
html += '<ul class="autocompleter-list"></ul>';
|
|
html += '</div>';
|
|
|
|
$node.addClass("autocompleter-node")
|
|
.after(html);
|
|
|
|
var $autocompleter = $node.next(".autocompleter").eq(0);
|
|
|
|
// Set autocomplete to off for warn overlay
|
|
var originalAutocomplete = $node.attr("autocomplete");
|
|
$node.attr("autocomplete", "off");
|
|
|
|
// Store plugin data
|
|
var data = $.extend({
|
|
$node: $node,
|
|
$autocompleter: $autocompleter,
|
|
$selected: null,
|
|
$list: null,
|
|
index: -1,
|
|
hintText: false,
|
|
source: false,
|
|
jqxhr: false,
|
|
response: null,
|
|
focused: false,
|
|
query: '',
|
|
originalAutocomplete: originalAutocomplete,
|
|
guid: guid++
|
|
}, opts);
|
|
|
|
// Bind autocompleter events
|
|
data.$autocompleter.on("mousedown.autocompleter", ".autocompleter-item", data, _select)
|
|
.data("autocompleter", data);
|
|
|
|
// Bind node events
|
|
data.$node.on("keyup.autocompleter", data, _onKeyup)
|
|
.on("keydown.autocompleter", data, _onKeydownHelper)
|
|
.on("focus.autocompleter", data, _onFocus)
|
|
.on("blur.autocompleter", data, _onBlur)
|
|
.on("mousedown.autocompleter", data, _onMousedown);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _search
|
|
* @description Local search function, return best collation
|
|
* @param query [string] "Query string"
|
|
* @param source [object] "Source data"
|
|
* @param limit [integer] "Results length"
|
|
*/
|
|
function _search(query, source, limit) {
|
|
var response = [];
|
|
query = query.toUpperCase();
|
|
|
|
if (source.length) {
|
|
for (var i = 0; i < 2; i++) {
|
|
for (var item in source) {
|
|
if ('clear,insert,remove'.indexOf(item) === -1 && response.length < limit) {
|
|
switch (i) {
|
|
case 0:
|
|
if (source[item].label.toUpperCase().search(query) === 0) {
|
|
response.push(source[item]);
|
|
delete source[item];
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
if (source[item].label.toUpperCase().search(query) !== -1) {
|
|
response.push(source[item]);
|
|
delete source[item];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _launch
|
|
* @description Use source locally or create xhr
|
|
* @param data [object] "Instance data"
|
|
*/
|
|
function _launch(data) {
|
|
data.query = $.trim(data.$node.val());
|
|
|
|
if (!data.empty && data.query.length === 0) {
|
|
_clear(data);
|
|
return;
|
|
} else {
|
|
if (typeof data.source === 'object') {
|
|
_clear(data);
|
|
|
|
// Local search
|
|
var search = _search(data.query, _clone(data.source), data.limit);
|
|
if (search.length) {
|
|
_response(search, data);
|
|
}
|
|
} else {
|
|
if (data.jqxhr) {
|
|
data.jqxhr.abort();
|
|
}
|
|
|
|
var ajaxData = $.extend({
|
|
limit: data.limit,
|
|
query: data.query
|
|
}, data.combine());
|
|
|
|
data.jqxhr = $.ajax({
|
|
url: data.source,
|
|
dataType: "json",
|
|
data: ajaxData,
|
|
beforeSend: function (xhr) {
|
|
data.$autocompleter.addClass('autocompleter-ajax');
|
|
_clear(data);
|
|
if (data.cache) {
|
|
var stored = _getCache(this.url);
|
|
if (stored) {
|
|
xhr.abort();
|
|
_response(stored, data);
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.done(function (response) {
|
|
// Get subobject from responce
|
|
if (data.offset) {
|
|
response = _grab(response, data.offset);
|
|
}
|
|
if (data.cache) {
|
|
_setCache(this.url, response);
|
|
}
|
|
_response(response, data);
|
|
})
|
|
.always(function () {
|
|
data.$autocompleter.removeClass('autocompleter-ajax');
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _clear
|
|
* @param data [object] "Instance data"
|
|
*/
|
|
function _clear(data) {
|
|
// Clear data
|
|
data.response = null;
|
|
data.$list = null;
|
|
data.$selected = null;
|
|
data.index = 0;
|
|
data.$autocompleter.find(".autocompleter-list").empty();
|
|
data.$autocompleter.find('.autocompleter-hint').removeClass('autocompleter-hint-show').empty();
|
|
data.hintText = false;
|
|
|
|
_close(null, data);
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _response
|
|
* @description Main source response function
|
|
* @param response [object] "Source data"
|
|
* @param data [object] "Instance data"
|
|
*/
|
|
function _response(response, data) {
|
|
_buildList(response, data);
|
|
|
|
if (data.$autocompleter.hasClass('autocompleter-focus')) {
|
|
_open(null, data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _buildList
|
|
* @description Generate autocompleter-list and update instance data by source
|
|
* @param list [object] "Source data"
|
|
* @param data [object] "Instance data"
|
|
*/
|
|
function _buildList(list, data) {
|
|
var menu = '';
|
|
|
|
for (var item = 0, count = list.length; item < count; item++) {
|
|
var classes = ["autocompleter-item"];
|
|
|
|
if (data.selectFirst && item === 0 && !data.changeWhenSelect) {
|
|
classes.push("autocompleter-item-selected");
|
|
}
|
|
|
|
var highlightReg = new RegExp(data.query, "gi");
|
|
var label = (data.customLabel && list[item][data.customLabel]) ? list[item][data.customLabel] : list[item].label;
|
|
|
|
var clear = label;
|
|
|
|
label = data.highlightMatches ? label.replace(highlightReg, "<strong>$&</strong>") : label;
|
|
|
|
var value = (data.customValue && list[item][data.customValue]) ? list[item][data.customValue] : list[item].value;
|
|
|
|
// Apply custom template
|
|
if (data.template) {
|
|
var template = data.template.replace(/({{ label }})/gi, label);
|
|
|
|
for (var property in list[item]) {
|
|
if (list[item].hasOwnProperty(property)) {
|
|
var regex = new RegExp('{{ ' + property + ' }}', 'gi');
|
|
template = template.replace(regex, list[item][property]);
|
|
}
|
|
}
|
|
|
|
label = template;
|
|
}
|
|
|
|
if (value) {
|
|
menu += '<li data-value="' + value + '" data-label="' + clear + '" class="' + classes.join(' ') + '">' + label + '</li>';
|
|
} else {
|
|
menu += '<li data-label="' + clear + '" class="' + classes.join(' ') + '">' + label + '</li>';
|
|
}
|
|
}
|
|
|
|
// Set hint
|
|
if (list.length && data.hint) {
|
|
var hint = (list[0].label.substr(0, data.query.length).toUpperCase() === data.query.toUpperCase()) ? list[0].label : false;
|
|
if (hint && (data.query !== list[0].label)) {
|
|
var hintReg = new RegExp(data.query, "i");
|
|
var hintText = hint.replace(hintReg, "<span>" + data.query + "</span>");
|
|
data.$autocompleter.find('.autocompleter-hint').addClass('autocompleter-hint-show').html(hintText);
|
|
data.hintText = hintText;
|
|
}
|
|
}
|
|
|
|
// Update data
|
|
data.response = list;
|
|
data.$autocompleter.find(".autocompleter-list").html(menu);
|
|
data.$selected = (data.$autocompleter.find(".autocompleter-item-selected").length) ? data.$autocompleter.find(".autocompleter-item-selected") : null;
|
|
data.$list = (list.length) ? data.$autocompleter.find(".autocompleter-item") : null;
|
|
data.index = data.$selected ? data.$list.index(data.$selected) : -1;
|
|
data.$autocompleter.find(".autocompleter-item").each(function (i, j) {
|
|
$(j).data(data.response[i]);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _onKeyup
|
|
* @description Keyup events in node, up/down autocompleter-list navigation, typing and enter button callbacks
|
|
* @param e [object] "Event data"
|
|
*/
|
|
function _onKeyup(e) {
|
|
var data = e.data;
|
|
var code = e.keyCode ? e.keyCode : e.which;
|
|
debugger;
|
|
if ((code === 40 || code === 38) && data.$autocompleter.hasClass('autocompleter-show')) {
|
|
// Arrows up & down
|
|
var len = data.$list.length,
|
|
next,
|
|
prev;
|
|
|
|
if (len) {
|
|
// Determine new index
|
|
if (len > 1) {
|
|
if (data.index === len - 1) {
|
|
next = data.changeWhenSelect ? -1 : 0;
|
|
prev = data.index - 1;
|
|
} else if (data.index === 0) {
|
|
next = data.index + 1;
|
|
prev = data.changeWhenSelect ? -1 : len - 1;
|
|
} else if (data.index === -1) {
|
|
next = 0;
|
|
prev = len - 1;
|
|
} else {
|
|
next = data.index + 1;
|
|
prev = data.index - 1;
|
|
}
|
|
} else if (data.index === -1) {
|
|
next = 0;
|
|
prev = 0;
|
|
} else {
|
|
prev = -1;
|
|
next = -1;
|
|
}
|
|
data.index = (code === 40) ? next : prev;
|
|
|
|
// Update HTML
|
|
data.$list.removeClass("autocompleter-item-selected");
|
|
if (data.index !== -1) {
|
|
data.$list.eq(data.index).addClass("autocompleter-item-selected");
|
|
}
|
|
data.$selected = data.$autocompleter.find(".autocompleter-item-selected").length ? data.$autocompleter.find(".autocompleter-item-selected") : null;
|
|
if (data.changeWhenSelect) {
|
|
_setValue(data);
|
|
}
|
|
}
|
|
} else if ($.inArray(code, ignoredKeyCode) === -1 && $.inArray(code, data.ignoredKeyCode) === -1) {
|
|
// Typing
|
|
_launch(data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _onKeydownHelper
|
|
* @description Keydown events in node, up/down for prevent cursor moving and right arrow for hint
|
|
* @param e [object] "Event data"
|
|
*/
|
|
function _onKeydownHelper(e) {
|
|
var code = e.keyCode ? e.keyCode : e.which;
|
|
var data = e.data;
|
|
|
|
if (code === 40 || code === 38) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
} else if (code === 39) {
|
|
// Right arrow
|
|
if (data.hint && data.hintText && data.$autocompleter.find('.autocompleter-hint').hasClass('autocompleter-hint-show')) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
var hintOrigin = data.$autocompleter.find(".autocompleter-item").length ? data.$autocompleter.find(".autocompleter-item").eq(0).attr('data-label') : false;
|
|
if (hintOrigin) {
|
|
data.query = hintOrigin;
|
|
_setHint(data);
|
|
}
|
|
}
|
|
} else if (code === 13) {
|
|
// Enter
|
|
if (data.$autocompleter.hasClass('autocompleter-show') && data.$selected) {
|
|
_select(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _onFocus
|
|
* @description Handles instance focus
|
|
* @param e [object] "Event data"
|
|
* @param internal [boolean] "Called by plugin"
|
|
*/
|
|
function _onFocus(e, internal) {
|
|
if (!internal) {
|
|
var data = e.data;
|
|
|
|
data.$autocompleter.addClass("autocompleter-focus");
|
|
|
|
if (!data.$node.prop("disabled") && !data.$autocompleter.hasClass('autocompleter-show')) {
|
|
if (data.focusOpen) {
|
|
_launch(data);
|
|
data.focused = true;
|
|
setTimeout(function () {
|
|
data.focused = false;
|
|
}, 500);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _onBlur
|
|
* @description Handles instance blur
|
|
* @param e [object] "Event data"
|
|
* @param internal [boolean] "Called by plugin"
|
|
*/
|
|
function _onBlur(e, internal) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
var data = e.data;
|
|
|
|
if (!internal) {
|
|
data.$autocompleter.removeClass("autocompleter-focus");
|
|
_close(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _onMousedown
|
|
* @description Handles mousedown to node
|
|
* @param e [object] "Event data"
|
|
*/
|
|
function _onMousedown(e) {
|
|
// Disable middle & right mouse click
|
|
if (e.type === "mousedown" && $.inArray(e.which, [2, 3]) !== -1) { return; }
|
|
|
|
var data = e.data;
|
|
if (data.$list && !data.focused) {
|
|
if (!data.$node.is(":disabled")) {
|
|
if (isMobile && !isFirefoxMobile) {
|
|
var el = data.$select[0];
|
|
if (window.document.createEvent) { // All
|
|
var evt = window.document.createEvent("MouseEvents");
|
|
evt.initMouseEvent("mousedown", false, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
|
el.dispatchEvent(evt);
|
|
} else if (el.fireEvent) { // IE
|
|
el.fireEvent("onmousedown");
|
|
}
|
|
} else {
|
|
// Delegate intent
|
|
if (data.$autocompleter.hasClass("autocompleter-closed")) {
|
|
_open(e);
|
|
} else if (data.$autocompleter.hasClass("autocompleter-show")) {
|
|
_close(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _open
|
|
* @description Opens option set
|
|
* @param e [object] "Event data"
|
|
* @param instanceData [object] "Instance data"
|
|
*/
|
|
function _open(e, instanceData) {
|
|
var data = e ? e.data : instanceData;
|
|
|
|
if (!data.$node.prop("disabled") && !data.$autocompleter.hasClass("autocompleter-show") && data.$list && data.$list.length) {
|
|
data.$autocompleter.removeClass("autocompleter-closed").addClass("autocompleter-show");
|
|
$body.on("click.autocompleter-" + data.guid, ":not(.autocompleter-item)", data, _closeHelper);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _closeHelper
|
|
* @description Determines if event target is outside instance before closing
|
|
* @param e [object] "Event data"
|
|
*/
|
|
function _closeHelper(e) {
|
|
if ($(e.target).hasClass('autocompleter-node')) {
|
|
return;
|
|
}
|
|
|
|
if ($(e.currentTarget).parents(".autocompleter").length === 0) {
|
|
_close(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _close
|
|
* @description Closes option set
|
|
* @param e [object] "Event data"
|
|
* @param instanceData [object] "Instance data"
|
|
*/
|
|
function _close(e, instanceData) {
|
|
var data = e ? e.data : instanceData;
|
|
|
|
if (data.$autocompleter.hasClass("autocompleter-show")) {
|
|
data.$autocompleter.removeClass("autocompleter-show").addClass("autocompleter-closed");
|
|
$body.off(".autocompleter-" + data.guid);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _select
|
|
* @description Select item from .autocompleter-list
|
|
* @param e [object] "Event data"
|
|
*/
|
|
function _select(e) {
|
|
// Disable middle & right mouse click
|
|
if (e.type === "mousedown" && $.inArray(e.which, [2, 3]) !== -1) { return; }
|
|
|
|
var data = e.data;
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
if (e.type === "mousedown" && $(this).length) {
|
|
data.$selected = $(this);
|
|
data.index = data.$list.index(data.$selected);
|
|
}
|
|
|
|
if (!data.$node.prop("disabled")) {
|
|
_close(e);
|
|
_update(data);
|
|
|
|
if (e.type === "click") {
|
|
data.$node.trigger("focus", [true]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _setHint
|
|
* @description Set autocompleter by hint
|
|
* @param data [object] "Instance data"
|
|
*/
|
|
function _setHint(data) {
|
|
_setValue(data);
|
|
_handleChange(data);
|
|
_launch(data);
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _setValue
|
|
* @description Set value for native field
|
|
* @param data [object] "Instance data"
|
|
*/
|
|
function _setValue(data) {
|
|
if (data.$selected) {
|
|
if (data.hintText && data.$autocompleter.find('.autocompleter-hint').hasClass('autocompleter-hint-show')) {
|
|
data.$autocompleter.find('.autocompleter-hint').removeClass('autocompleter-hint-show');
|
|
}
|
|
var value = data.$selected.attr('data-value') ? data.$selected.attr('data-value') : data.$selected.attr('data-label');
|
|
data.$node.val(value);
|
|
} else {
|
|
if (data.hintText && !data.$autocompleter.find('.autocompleter-hint').hasClass('autocompleter-hint-show')) {
|
|
data.$autocompleter.find('.autocompleter-hint').addClass('autocompleter-hint-show');
|
|
}
|
|
data.$node.val(data.query);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _update
|
|
* @param data [object] "Instance data"
|
|
*/
|
|
function _update(data) {
|
|
_setValue(data);
|
|
_handleChange(data);
|
|
_clear(data);
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _handleChange
|
|
* @description Trigger node change event and call the callback function
|
|
* @param data [object] "Instance data"
|
|
*/
|
|
function _handleChange(data) {
|
|
data.callback.call(data.$autocompleter, data.$node.val(), data.index, data.response[data.index]);
|
|
data.$node.trigger("change");
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _grab
|
|
* @description Grab sub-object from object
|
|
* @param response [object] "Native object"
|
|
* @param offset [string] "Offset string"
|
|
*/
|
|
function _grab(response, offset) {
|
|
offset = offset.split('.');
|
|
while (response && offset.length) {
|
|
response = response[offset.shift()];
|
|
}
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _getCache
|
|
* @description Store AJAX response in plugin cache
|
|
* @param url [string] "AJAX get query string"
|
|
* @param data [object] "AJAX response data"
|
|
*/
|
|
function _setCache(url, data) {
|
|
if (!supportLocalStorage) { return; }
|
|
if (url && data) {
|
|
cache[url] = {
|
|
value: data
|
|
};
|
|
|
|
// Proccess to localStorage
|
|
try {
|
|
localStorage.setItem(localStorageKey, JSON.stringify(cache));
|
|
} catch (e) {
|
|
var code = e.code || e.number || e.message;
|
|
if (code === 22) {
|
|
_deleteCache();
|
|
} else {
|
|
throw (e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _getCache
|
|
* @description Get cached data by url if exist
|
|
* @param url [string] "AJAX get query string"
|
|
*/
|
|
function _getCache(url) {
|
|
if (!url) { return; }
|
|
var response = (cache[url] && cache[url].value) ? cache[url].value : false;
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _loadCache
|
|
* @description Load all plugin cache from localStorage
|
|
*/
|
|
function _loadCache() {
|
|
if (!supportLocalStorage) { return; }
|
|
var json = localStorage.getItem(localStorageKey) || '{}';
|
|
return JSON.parse(json);
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _deleteCache
|
|
* @description Delete all plugin cache from localStorage
|
|
*/
|
|
function _deleteCache() {
|
|
try {
|
|
localStorage.removeItem(localStorageKey);
|
|
cache = _loadCache();
|
|
} catch (e) {
|
|
throw (e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @method private
|
|
* @name _clone
|
|
* @description Clone JavaScript object
|
|
*/
|
|
function _clone(obj) {
|
|
if (null === obj || "object" !== typeof obj) {
|
|
return obj;
|
|
}
|
|
var copy = obj.constructor();
|
|
for (var attr in obj) {
|
|
if (obj.hasOwnProperty(attr)) {
|
|
copy[attr] = obj[attr];
|
|
}
|
|
}
|
|
return copy;
|
|
}
|
|
|
|
// Load cache
|
|
var cache = _loadCache();
|
|
|
|
$.fn.autocompleter = function (method) {
|
|
if (publics[method]) {
|
|
return publics[method].apply(this, Array.prototype.slice.call(arguments, 1));
|
|
} else if (typeof method === 'object' || !method) {
|
|
return _init.apply(this, arguments);
|
|
}
|
|
return this;
|
|
};
|
|
|
|
$.autocompleter = function (method) {
|
|
if (method === "defaults") {
|
|
publics.defaults.apply(this, Array.prototype.slice.call(arguments, 1));
|
|
} else if (method === "clearCache") {
|
|
publics.clearCache.apply(this, null);
|
|
}
|
|
};
|
|
})(jQuery, window);
|