/*! jQuery AutoSuggest - v2.5.0 - 2013-08-24
* URL: http://hlsolutions.github.com/jquery-autosuggest
* Copyright (c) 2013 Jan Philipp
* Licensed MIT, GPL */
/*
jQuery AutoSuggest 2
This is a rewritten version of Drew Wilsons "AutoSuggest" plugin from 2009/2010.
www.drewwilson.com / code.drewwilson.com/entry/autosuggest-jquery-plugin
Originally forked by Wu Yuntao (on GitHub)
http://github.com/wuyuntao/jquery-autosuggest
Based on the 1.6er release dated in July, 2012
*/
(function() {
var $, ConfigResolver, Events, SelectionHolder, Utils, defaults, pluginMethods,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__slice = [].slice;
$ = jQuery;
/* A collection of utility functions.*/
Utils = (function() {
function Utils() {}
Utils.prototype._ = void 0;
Utils.escapeQuotes = function(text) {
if (text) {
return text.replace(/"/g, '\\"');
}
};
Utils.escapeHtml = function(text) {
return $('').text(text).html();
};
Utils.setPlaceholderEnabled = function(input, enable) {
var from, targets, to;
targets = ['placeholder', 'disabled-placeholder'];
if (enable) {
from = targets[1];
to = targets[0];
} else {
from = targets[0];
to = targets[1];
}
if (input.attr(to) || !input.attr(from)) {
return;
}
input.attr(to, function() {
return input.attr(from);
});
input.removeAttr(from);
};
return Utils;
})();
/* A collection of configuration resolvers.*/
ConfigResolver = (function() {
function ConfigResolver() {}
ConfigResolver.prototype._ = void 0;
/*
Resolving the extra params as an object.
The input of options.extraParams can be a string, a function or an object.
*/
ConfigResolver.getExtraParams = function(options) {
var obj, pair, parts, result, _i, _len, _ref;
result = options.extraParams;
if ($.isFunction(result)) {
result = result(this);
}
/**
* AutoSuggest <= 1.7 supported only a string of params. Since 2, the extra params will be used as a standard
* $.fn.Ajax "data" parameter. The next lines will ensure that the result is such an object.
*/
if ($.type(result) === 'string') {
obj = {};
_ref = result.split('&');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
pair = _ref[_i];
if (!(pair !== '')) {
continue;
}
parts = pair.split('=', 2);
if (parts.length) {
obj[parts[0]] = parts[1];
}
}
result = obj;
}
return result;
};
return ConfigResolver;
})();
/* The SelectionControl maintains and manage any selections.*/
SelectionHolder = (function() {
SelectionHolder.prototype._ = void 0;
SelectionHolder.prototype.hiddenField = null;
SelectionHolder.prototype.items = null;
function SelectionHolder(hiddenField, items) {
this.hiddenField = hiddenField;
this.items = items != null ? items : [];
}
SelectionHolder.prototype.syncToHiddenField = function() {
var item, value, _i, _len, _ref;
if (!this.hiddenField) {
return;
}
value = '';
_ref = this.items;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
item = _ref[_i];
value += ',' + item;
}
if (value) {
value += ',';
}
this.hiddenField.val(value || ',');
};
SelectionHolder.prototype.add = function(item) {
if (!this.exist(item)) {
this.items.push(item);
}
this.syncToHiddenField();
};
SelectionHolder.prototype.remove = function(item) {
this.items = $.grep(this.items, function(value) {
return value !== item;
});
this.syncToHiddenField();
};
SelectionHolder.prototype.isEmpty = function() {
return this.items.length === 0;
};
SelectionHolder.prototype.exist = function(item) {
return $.inArray(item, this.items) !== -1;
};
SelectionHolder.prototype.getAll = function() {
return this.items.slice(0);
};
SelectionHolder.prototype.clear = function() {
this.items = [];
this.syncToHiddenField();
};
return SelectionHolder;
})();
Events = (function() {
function Events() {}
Events.onSelectionAdd = function(scope, containerElement, detachedElement, options, item, selections) {
var element;
element = options.onSelectionAdd.call(scope, containerElement, detachedElement, options);
Utils.setPlaceholderEnabled(scope, selections.length === 0);
if ($.isFunction(options.afterSelectionAdd)) {
options.afterSelectionAdd.call(scope, element, item, selections);
}
};
Events.onSelectionRemove = function(scope, element, options, item, selections) {
if ($.isFunction(options.onSelectionRemove)) {
options.onSelectionRemove.call(scope, element, options);
}
Utils.setPlaceholderEnabled(scope, selections.length === 0);
if ($.isFunction(options.afterSelectionRemove)) {
options.afterSelectionRemove.call(scope, element, item, selections);
}
};
Events.onSelectionClick = function(scope, element, options, item, selections) {
if ($.isFunction(options.afterSelectionClick)) {
options.afterSelectionClick.call(scope, element, item, selections);
}
Utils.setPlaceholderEnabled(scope, selections.length === 0);
};
Events.onAjaxRequestDone = function(scope, ajaxRequest, options) {
if ($.isFunction(options.onAjaxRequestDone)) {
ajaxRequest.done(options.onAjaxRequestDone);
}
};
Events.onAjaxRequestFail = function(scope, ajaxRequest, options) {
if ($.isFunction(options.onAjaxRequestFail)) {
ajaxRequest.fail(options.onAjaxRequestFail);
}
};
Events.onAjaxRequestAlways = function(scope, ajaxRequest, options) {
if ($.isFunction(options.onAjaxRequestAlways)) {
ajaxRequest.always(options.onAjaxRequestAlways);
}
};
Events.onRenderErrorMessage = function(scope, validationData, element, options) {
if ($.isFunction(options.onRenderErrorMessage)) {
options.onRenderErrorMessage.call(scope, validationData, element, options);
}
};
Events.onRemoveErrorMessage = function(scope, validationData, element, options) {
if ($.isFunction(options.onRenderErrorMessage)) {
options.onRemoveErrorMessage.call(scope, validationData, element, options);
}
};
return Events;
})();
/**
* plugin's default options
*/
defaults = {
prefix: 'as',
asHtmlID: false,
useOriginalInputName: false,
/**
* Defines whether the HTML5 placeholder attribute should used.
*/
usePlaceholder: false,
/**
* Defines predefined values which will be selected.
* @type string a comma seperated list of name/id values OR array of object items
*/
preFill: null,
/**
* Defines text shown as a placeholder.
* This text will be displayed when nothing is selected and the field isn't focused.
* @type string
*/
startText: 'Enter Name Here',
/**
* Defines text shown in the suggestion resultbox when there isn't a match.
* @type string
*/
emptyText: 'No Results Found',
/**
* RegEx to replace values in emptyText values with query text
* @type regex
*/
emptyTextPlaceholder: /\{\d+\}/,
/**
* Defines text shown when the limit of selections was exceeded.
* @type string
*/
limitText: 'No More Selections Are Allowed',
/**
* Defines the property of an item which will be used for display.
* @type string default 'value'
*/
selectedItemProp: 'value',
/**
* Defines the property of an item which will be used for identification (id).
* @type string default 'value'
*/
selectedValuesProp: 'value',
/**
* Defines wether the result list should be filtered or not.
* @type string default 'value'
*/
searchActive: true,
/**
* Defines the property of an item which will be used for searching.
* @type string default 'value'
*/
searchObjProps: 'value',
/**
* Defines the query parameter. Used for sending the search query.
* @type string default 'q'
*/
queryParam: 'q',
/**
* Defines the limit parameter. Used for limiting the results.
* @type string default 'limit'
*/
limitParam: 'limit',
/**
* number for 'limit' param on ajaxRequest
* @type number
*/
retrieveLimit: null,
/**
* Defines additional extraParams which will be appended to the ajaxRequest.
* The recommended way is defining an object or a function returning such a object.
*
* If this is a string or a function returning a string, the string must be a valid query url. Internally,
* the string will be split by '&' and '=' and built to an object. This is only available due backwards
* compatibility.
*
* @type string, function or object
*/
extraParams: null,
/**
* Defines whether the user input is case sensitive or not. Default is case insensitive.
* @type boolean default false
*/
matchCase: false,
/**
* Defines the minimum number of characters allowed for a tag to be valid.
* @type number default 1
*/
minChars: 1,
/**
* Defines the maximum number of characters allowed for a tag to be valid.
* @type number default 100
*/
maxChars: 100,
/**
* Defines the key delay. This is a recommended way when using an asynchronous fetcher (Ajax).
* @type number default 400
*/
keyDelay: 400,
/**
* Defines whether the result list's search/suggestion results should be highlight with the user query.
* @type boolean default true
*/
resultsHighlight: true,
/**
* Defines the limit of search/suggestion results.
* @type number default none
*/
selectionLimit: false,
/**
* Defines whether the result list should be displayed.
* @type boolean default true
*/
showResultList: true,
/**
* Defines whether the result list should be displayed even when there are no results.
* @type boolean default false
*/
showResultListWhenNoMatch: false,
/**
* Defines whether the input field can create new selections which aren't part of the suggestion.
* @type boolean default true
*/
canGenerateNewSelections: true,
/**
* FIXME needs doc
* @type function
*/
start: null,
/**
* Defines a trigger when clicking on a selection element.
* @type function with arguments: element
*/
afterSelectionClick: null,
/**
* Defines a trigger after adding a selection element.
* @type function with arguments: elementBefore, id
*/
afterSelectionAdd: null,
/**
* Defines a callback notifying when a element was removed.
* @type function with arguments: element
*/
afterSelectionRemove: null,
/**
* Defines a callback for adding a selection item.
* @type function with arguments: containerElement, detachedElement
*/
onSelectionAdd: function(containerElement, detachedElement, options) {
containerElement.before(detachedElement);
return containerElement.prev();
},
/**
* Defines a callback for removing a selection item.
* @type function with arguments: element
*/
onSelectionRemove: function(element, options) {
if (options.fadeOut) {
return element.fadeOut(options.fadeOut, function() {
return element.remove();
});
} else {
return element.remove();
}
},
/**
* Defines a callback for rendering a validation error.
* @type function with arguments: validationData, element
*/
onRenderErrorMessage: function(validationData, element, options) {
var error;
error = $("#" + validationData.id);
if (!error.length) {
element.closest('ul').after("");
error = $("#" + validationData.id);
}
error.text(validationData.errorMessage);
return setTimeout((function() {
return element.focus();
}), 10);
},
/**
* Defines a callback for removing a validation error.
* @type function with arguments: validationData, element
*/
onRemoveErrorMessage: function(validationData, element, options) {
return $("#" + validationData.id).remove();
},
/**
* Defines a callback called for every item that will be rendered.
* @type function with arguments: element
*/
formatList: null,
/**
* Defines a callback function intercepting the url
* @return String should return the ajaxRequest url
*/
beforeRequest: null,
/**
* Defines a callback function intercepting the result data.
*/
afterRequest: null,
/**
* Defines a deferred callback function for the internal ajax request (on success).
*/
onAjaxRequestDone: null,
/**
* Defines a deferred callback function for the internal ajax request (on error).
*/
onAjaxRequestFail: null,
/**
* Defines a deferred callback function for the internal ajax request (on complete).
*/
onAjaxRequestAlways: null,
/**
* Defines a trigger after clicking on a search result element.
* @type function with arguments: data
*/
onResultItemClick: null,
/**
* Defines a trigger called after processData.
* @type function
*/
afterResultListShow: null,
/**
* Defines whether an "event.preventDefault()" should be executed on an ENTER key.
* @type boolean default false
*/
neverSubmit: false,
/**
* Defines whether an "event.stopPropagation()" should be executed on an ESC key.
* @type boolean default false
*/
preventPropagationOnEscape: false,
/**
* Defines the base options used for the ajaxRequest.
*/
ajaxOptions: {
type: 'get',
dataType: 'json'
},
/**
* specifies a list of attributes which will be applied to each input on startup
*/
inputAttrs: {
autocomplete: 'off'
},
/**
* Defines whether the removing of a selection should be animated (using fadeOut)
*/
fadeOut: false,
/**
* Defines whether the server filter remote or not. If asuming so, this prevents the plugin to filter again.
*/
remoteFilter: false
};
pluginMethods = {
init: function(dataSource, options) {
var ajaxRequest, fetcher;
options = $.extend({}, defaults, options);
ajaxRequest = null;
if (options.remoteFilter === 'auto') {
options.remoteFilter = ($.type(dataSource)) === 'string';
}
fetcher = (function() {
switch ($.type(dataSource)) {
case 'function':
return dataSource;
case 'string':
return function(query, callback) {
var ajaxRequestConfig, extraParams, onDone, params;
params = {};
/* ensures query is encoded*/
params["" + options.queryParam] = encodeURIComponent(decodeURIComponent(query));
if (options.retrieveLimit) {
params[options.limitParam] = encodeURIComponent(options.retrieveLimit);
}
extraParams = ConfigResolver.getExtraParams(options);
if ($.type(extraParams) === 'object') {
$.extend(params, extraParams);
}
ajaxRequestConfig = $.extend({}, options.ajaxOptions, {
url: dataSource,
data: params
});
onDone = function(data) {
if ($.isFunction(options.afterRequest)) {
data = options.afterRequest.apply(this, [data]);
}
return callback(data, query);
};
ajaxRequest = $.ajax(ajaxRequestConfig).done(onDone);
Events.onAjaxRequestDone(this, ajaxRequest, options);
Events.onAjaxRequestFail(this, ajaxRequest, options);
Events.onAjaxRequestAlways(this, ajaxRequest, options);
};
case 'array':
case 'object':
return function(query, callback) {
return callback(dataSource, query);
};
}
})();
if (!fetcher) {
return;
}
/*
For each selected item, we will create an own scope.
All variables above are "instance" locale!
*/
return this.each(function() {
var abortRequest, addSelection, clonePublicApi, currentSelection, element, elementId, hiddenInputField, hiddenInputFieldId, hiddenInputFieldName, i, input, inputWrapper, input_focus, interval, item, keyChange, lastKeyPressCode, lastKeyWasTab, moveResultSelection, new_value, num_count, prev, processData, processRequest, publicApi, resultsContainer, resultsList, selectionsContainer, timeout, trimInput, validationErrorId, validations, value, _i, _j, _len, _len1, _ref, _ref1;
options.inputAttrs = $.extend(options.inputAttrs, {});
input_focus = false;
input = $(this);
element = null;
elementId = null;
hiddenInputField = null;
hiddenInputFieldId = null;
hiddenInputFieldName = null;
validationErrorId = null;
if (options.asHtmlID) {
element = options.asHtmlID;
elementId = element;
hiddenInputFieldId = "" + options.prefix + "-values-" + element;
validationErrorId = "" + options.prefix + "-validation-error-" + element;
if (options.useOriginalInputName) {
hiddenInputFieldName = input.attr('name');
input.attr({
name: "old_" + (input.attr('name'))
});
} else {
hiddenInputFieldName = "" + options.prefix + "_values_" + element;
}
} else {
element = "" + (element || '') + (Math.floor(Math.random() * 100));
elementId = "" + options.prefix + "-input-" + element;
hiddenInputFieldId = "" + options.prefix + "-values-" + element;
validationErrorId = "" + options.prefix + "-validation-error-" + element;
if (options.useOriginalInputName) {
hiddenInputFieldName = input.attr('name');
input.attr({
name: "old_" + (input.attr('name'))
});
} else {
hiddenInputFieldName = "" + options.prefix + "_values_" + element;
}
}
options.inputAttrs.id = elementId;
if (!options.usePlaceholder) {
options.inputAttrs.placeholder = options.startText;
}
input.attr(options.inputAttrs);
input.addClass("" + options.prefix + "-input");
if (!options.usePlaceholder) {
input.val(options.startText);
}
input.wrap("