cux-test
Version:
[SB Admin](http://startbootstrap.com/template-overviews/sb-admin/) is an open source, admin dashboard template for [Bootstrap](http://getbootstrap.com/) created by [Start Bootstrap](http://startbootstrap.com/).
1,177 lines (983 loc) • 119 kB
JavaScript
/*!
* jQuery Typeahead
* Copyright (C) 2016 RunningCoder.org
* Licensed under the MIT license
*
* @author Tom Bertrand
* @version 2.7.2 (2016-10-26)
* @link http://www.runningcoder.org/jquerytypeahead/
*/
;(function (factory) {
if (typeof define === 'function' && define.amd) {
define('jquery-typeahead', ['jquery'], function (jQuery) {
return factory(jQuery);
});
} else if (typeof module === 'object' && module.exports) {
module.exports = function (jQuery, root) {
if (jQuery === undefined) {
if (typeof window !== 'undefined') {
jQuery = require('jquery');
}
else {
jQuery = require('jquery')(root);
}
}
return factory(jQuery);
};
} else {
factory(jQuery);
}
}(function ($) {
"use strict";
window.Typeahead = {
version: '2.7.2'
};
/**
* @private
* Default options
*
* @link http://www.runningcoder.org/jquerytypeahead/documentation/
*/
var _options = {
input: null,
minLength: 2, // Modified feature, now accepts 0 to search on focus
maxItem: 8, // Modified feature, now accepts 0 as "Infinity" meaning all the results will be displayed
dynamic: false,
delay: 300,
order: null, // ONLY sorts the first "display" key
offset: false,
hint: false, // -> Improved feature, Added support for excessive "space" characters
accent: false, // -> Improved feature, define a custom replacement object
highlight: true, // -> Added "any" to highlight any word in the template, by default true will only highlight display keys
group: false, // -> Improved feature, Boolean,string,object(key, template (string, function))
groupOrder: null, // -> New feature, order groups "asc", "desc", Array, Function
maxItemPerGroup: null, // -> Renamed option
dropdownFilter: false, // -> Renamed option, true will take group options string will filter on object key
dynamicFilter: null, // -> New feature, filter the typeahead results based on dynamic value, Ex: Players based on TeamID
backdrop: false,
backdropOnFocus: false, // -> New feature, display the backdrop option as the Typeahead input is :focused
cache: false, // -> Improved option, true OR 'localStorage' OR 'sessionStorage'
ttl: 3600000,
compression: false, // -> Requires LZString library
suggestion: false, // -> *Coming soon* New feature, save last searches and display suggestion on matched characters
searchOnFocus: false, // -> New feature, display search results on input focus
resultContainer: null, // -> New feature, list the results inside any container string or jQuery object
generateOnLoad: null, // -> New feature, forces the source to be generated on page load even if the input is not focused!
mustSelectItem: false, // -> New option, the submit function only gets called if an item is selected
href: null, // -> New feature, String or Function to format the url for right-click & open in new tab on link results
display: ["display"], // -> Improved feature, allows search in multiple item keys ["display1", "display2"]
template: null,
groupTemplate: null, // -> New feature, set a custom template for the groups
correlativeTemplate: false, // -> New feature, compile display keys, enables multiple key search from the template string
emptyTemplate: false, // -> New feature, display an empty template if no result
cancelButton: true, // -> New feature, if text is detected in the input, a cancel button will be available to reset the input (pressing ESC also cancels)
loadingAnimation: true, // -> New feature, will display a loading animation when typeahead is doing request / searching for results
filter: true, // -> New feature, set to false or function to bypass Typeahead filtering. WARNING: accent, correlativeTemplate, offset & matcher will not be interpreted
matcher: null, // -> New feature, add an extra filtering function after the typeahead functions
source: null,
callback: {
onInit: null,
onReady: null, // -> New callback, when the Typeahead initial preparation is completed
onShowLayout: null, // -> New callback, called when the layout is shown
onHideLayout: null, // -> New callback, called when the layout is hidden
onSearch: null, // -> New callback, when data is being fetched & analyzed to give search results
onResult: null,
onLayoutBuiltBefore: null, // -> New callback, when the result HTML is build, modify it before it get showed
onLayoutBuiltAfter: null, // -> New callback, modify the dom right after the results gets inserted in the result container
onNavigateBefore: null, // -> New callback, when a key is pressed to navigate the results
onNavigateAfter: null, // -> New callback, when a key is pressed to navigate the results
onMouseEnter: null,
onMouseLeave: null,
onClickBefore: null, // -> Improved feature, possibility to e.preventDefault() to prevent the Typeahead behaviors
onClickAfter: null, // -> New feature, happens after the default clicked behaviors has been executed
onSendRequest: null, // -> New callback, gets called when the Ajax request(s) are sent
onReceiveRequest: null, // -> New callback, gets called when the Ajax request(s) are all received
onPopulateSource: null, // -> New callback, Perform operation on the source data before it gets in Typeahead data
onCacheSave: null, // -> New callback, Perform operation on the source data before it gets in Typeahead cache
onSubmit: null,
onCancel: null // -> New callback, triggered if the typeahead had text inside and is cleared
},
selector: {
container: "typeahead__container",
result: "typeahead__result",
list: "typeahead__list",
group: "typeahead__group",
item: "typeahead__item",
empty: "typeahead__empty",
display: "typeahead__display",
query: "typeahead__query",
filter: "typeahead__filter",
filterButton: "typeahead__filter-button",
dropdown: "typeahead__dropdown",
dropdownItem: "typeahead__dropdown-item",
button: "typeahead__button",
backdrop: "typeahead__backdrop",
hint: "typeahead__hint",
cancelButton: "typeahead__cancel-button"
},
debug: false
};
/**
* @private
* Event namespace
*/
var _namespace = ".typeahead";
/**
* @private
* Accent equivalents
*/
var _accent = {
from: "ãàáäâẽèéëêìíïîõòóöôùúüûñç",
to: "aaaaaeeeeeiiiiooooouuuunc"
};
/**
* #62 IE9 doesn't trigger "input" event when text gets removed (backspace, ctrl+x, etc)
* @private
*/
var _isIE9 = ~window.navigator.appVersion.indexOf("MSIE 9.");
/**
* #193 Clicking on a suggested option does not select it on IE10/11
* @private
*/
var _isIE10 = ~window.navigator.appVersion.indexOf("MSIE 10");
var _isIE11 = ~window.navigator.userAgent.indexOf("Trident") && ~window.navigator.userAgent.indexOf("rv:11");
// SOURCE GROUP RESERVED WORDS: ajax, data, url
// SOURCE ITEMS RESERVED KEYS: group, display, data, matchedKey, compiled, href
/**
* @constructor
* Typeahead Class
*
* @param {object} node jQuery input object
* @param {object} options User defined options
*/
var Typeahead = function (node, options) {
this.rawQuery = node.val() || ''; // Unmodified input query
this.query = node.val() || ''; // Input query
this.namespace = '.' + _namespace; // Every Typeahead instance gets its own namespace for events
this.tmpSource = {}; // Temp var to preserve the source order for the searchResult function
this.source = {}; // The generated source kept in memory
this.isGenerated = null; // Generated results -> null: not generated, false: generating, true generated
this.generatedGroupCount = 0; // Number of groups generated, if limit reached the search can be done
this.groupCount = 0; // Number of groups, this value gets counted on the initial source unification
this.groupBy = "group"; // This option will change according to filtering or custom grouping
this.groups = []; // Array of all the available groups, used to build the groupTemplate
this.result = {}; // Results based on Source-query match (only contains the displayed elements)
this.groupTemplate = ''; // Result template at the {{group}} level
this.resultHtml = null; // HTML Results (displayed elements)
this.resultCount = 0; // Total results based on Source-query match
this.resultCountPerGroup = {}; // Total results based on Source-query match per group
this.options = options; // Typeahead options (Merged default & user defined)
this.node = node; // jQuery object of the Typeahead <input>
this.namespace = '.' + // Every Typeahead instance gets its own namespace for events
this.helper.slugify.call(this, node.selector) +
_namespace;
this.container = null; // Typeahead container, usually right after <form>
this.resultContainer = null; // Typeahead result container (html)
this.item = null; // The selected item
this.xhr = {}; // Ajax request(s) stack
this.hintIndex = null; // Numeric value of the hint index in the result list
this.filters = { // Filter list for searching, dropdown and dynamic(s)
dropdown: {}, // Dropdown menu if options.dropdownFilter is set
dynamic: {} // Checkbox / Radio / Select to filter the source data
};
this.dropdownFilter = {
static: [], // Objects that has a value
dynamic: []
};
this.dropdownFilterAll = null; // The last "all" definition
this.requests = {}; // Store the group:request instead of generating them every time
this.backdrop = {}; // The backdrop object
this.hint = {}; // The hint object
this.hasDragged = false; // Will cancel mouseend events if true
this.focusOnly = false; // Focus the input preventing any operations
this.__construct();
};
Typeahead.prototype = {
extendOptions: function () {
// If the Typeahead is dynamic, force no cache & no compression
if (this.options.dynamic) {
this.options.cache = false;
this.options.compression = false;
}
var scope = this;
if (this.options.cache) {
this.options.cache = (function (cache) {
var supportedCache = ['localStorage', 'sessionStorage'],
supported;
if (cache === true) {
cache = 'localStorage';
} else if (typeof cache === "string" && !~supportedCache.indexOf(cache)) {
// {debug}
if (scope.options.debug) {
_debug.log({
'node': scope.node.selector,
'function': 'extendOptions()',
'message': 'Invalid options.cache, possible options are "localStorage" or "sessionStorage"'
});
_debug.print();
}
// {/debug}
return false;
}
supported = typeof window[cache] !== "undefined";
try {
window[cache].setItem("typeahead", "typeahead");
window[cache].removeItem("typeahead");
} catch (e) {
supported = false;
}
return supported && cache || false;
}).call(this, this.options.cache);
}
if (this.options.compression) {
if (typeof LZString !== 'object' || !this.options.cache) {
// {debug}
if (this.options.debug) {
_debug.log({
'node': this.node.selector,
'function': 'extendOptions()',
'message': 'Missing LZString Library or options.cache, no compression will occur.'
});
_debug.print();
}
// {/debug}
this.options.compression = false;
}
}
if (typeof this.options.maxItem !== "undefined" && (!/^\d+$/.test(this.options.maxItem) || this.options.maxItem === 0)) {
this.options.maxItem = Infinity;
}
if (this.options.maxItemPerGroup && !/^\d+$/.test(this.options.maxItemPerGroup)) {
this.options.maxItemPerGroup = null;
}
if (this.options.display && !Array.isArray(this.options.display)) {
this.options.display = [this.options.display];
}
if (this.options.group) {
if (!Array.isArray(this.options.group)) {
if (typeof this.options.group === "string") {
this.options.group = {
key: this.options.group
};
} else if (typeof this.options.group === "boolean") {
this.options.group = {
key: 'group'
};
}
this.options.group.key = this.options.group.key || "group";
}
// {debug}
else {
if (this.options.debug) {
_debug.log({
'node': this.node.selector,
'function': 'extendOptions()',
'message': 'options.group must be a boolean|string|object as of 2.5.0'
});
_debug.print();
}
}
// {/debug}
}
if (this.options.highlight && !~["any", true].indexOf(this.options.highlight)) {
this.options.highlight = false;
}
if (this.options.dropdownFilter && this.options.dropdownFilter instanceof Object) {
if (!Array.isArray(this.options.dropdownFilter)) {
this.options.dropdownFilter = [this.options.dropdownFilter];
}
for (var i = 0, ii = this.options.dropdownFilter.length; i < ii; ++i) {
this.dropdownFilter[this.options.dropdownFilter[i].value ? 'static' : 'dynamic'].push(this.options.dropdownFilter[i]);
}
}
if (this.options.dynamicFilter && !Array.isArray(this.options.dynamicFilter)) {
this.options.dynamicFilter = [this.options.dynamicFilter];
}
if (this.options.accent) {
if (typeof this.options.accent === "object") {
if (this.options.accent.from && this.options.accent.to && this.options.accent.from.length === this.options.accent.to.length) {
}
// {debug}
else {
if (this.options.debug) {
_debug.log({
'node': this.node.selector,
'function': 'extendOptions()',
'message': 'Invalid "options.accent", from and to must be defined and same length.'
});
_debug.print();
}
}
// {/debug}
} else {
this.options.accent = _accent;
}
}
if (this.options.groupTemplate) {
this.groupTemplate = this.options.groupTemplate;
}
if (this.options.resultContainer) {
if (typeof this.options.resultContainer === "string") {
this.options.resultContainer = $(this.options.resultContainer);
}
if (!(this.options.resultContainer instanceof $) || !this.options.resultContainer[0]) {
// {debug}
if (this.options.debug) {
_debug.log({
'node': this.node.selector,
'function': 'extendOptions()',
'message': 'Invalid jQuery selector or jQuery Object for "options.resultContainer".'
});
_debug.print();
}
// {/debug}
} else {
this.resultContainer = this.options.resultContainer;
}
}
if (this.options.maxItemPerGroup && this.options.group && this.options.group.key) {
this.groupBy = this.options.group.key;
}
// Compatibility onClick callback
if (this.options.callback && this.options.callback.onClick) {
this.options.callback.onClickBefore = this.options.callback.onClick;
delete this.options.callback.onClick;
}
// Compatibility onNavigate callback
if (this.options.callback && this.options.callback.onNavigate) {
this.options.callback.onNavigateBefore = this.options.callback.onNavigate;
delete this.options.callback.onNavigate;
}
this.options = $.extend(
true,
{},
_options,
this.options
);
},
unifySourceFormat: function () {
this.groupCount = 0;
// source: ['item1', 'item2', 'item3']
if (Array.isArray(this.options.source)) {
this.options.source = {
group: {
data: this.options.source
}
};
this.groupCount = 1;
return true;
}
// source: "http://www.test.com/url.json"
if (typeof this.options.source === "string") {
this.options.source = {
group: {
ajax: {
url: this.options.source
}
}
};
}
if (this.options.source.ajax) {
this.options.source = {
group: {
ajax: this.options.source.ajax
}
};
}
// source: {data: ['item1', 'item2'], url: "http://www.test.com/url.json"}
if (this.options.source.url || this.options.source.data) {
this.options.source = {
group: this.options.source
};
}
var group,
groupSource,
tmpAjax;
for (group in this.options.source) {
if (!this.options.source.hasOwnProperty(group)) continue;
groupSource = this.options.source[group];
// source: {group: "http://www.test.com/url.json"}
if (typeof groupSource === "string") {
groupSource = {
ajax: {
url: groupSource
}
};
}
// source: {group: {url: ["http://www.test.com/url.json", "json.path"]}}
tmpAjax = groupSource.url || groupSource.ajax;
if (Array.isArray(tmpAjax)) {
groupSource.ajax = typeof tmpAjax[0] === "string" ? {
url: tmpAjax[0]
} : tmpAjax[0];
groupSource.ajax.path = groupSource.ajax.path || tmpAjax[1] || null;
delete groupSource.url;
} else {
// source: {group: {url: {url: "http://www.test.com/url.json", method: "GET"}}}
// source: {group: {url: "http://www.test.com/url.json", dataType: "jsonp"}}
if (typeof groupSource.url === "object") {
groupSource.ajax = groupSource.url;
} else if (typeof groupSource.url === "string") {
groupSource.ajax = {
url: groupSource.url
};
}
delete groupSource.url;
}
if (!groupSource.data && !groupSource.ajax) {
// {debug}
if (this.options.debug) {
_debug.log({
'node': this.node.selector,
'function': 'unifySourceFormat()',
'arguments': JSON.stringify(this.options.source),
'message': 'Undefined "options.source.' + group + '.[data|ajax]" is Missing - Typeahead dropped'
});
_debug.print();
}
// {/debug}
return false;
}
if (groupSource.display && !Array.isArray(groupSource.display)) {
groupSource.display = [groupSource.display];
}
this.options.source[group] = groupSource;
this.groupCount++;
}
return true;
},
init: function () {
this.helper.executeCallback.call(this, this.options.callback.onInit, [this.node]);
this.container = this.node.closest('.' + this.options.selector.container);
// {debug}
if (this.options.debug) {
_debug.log({
'node': this.node.selector,
'function': 'init()',
//'arguments': JSON.stringify(this.options),
'message': 'OK - Typeahead activated on ' + this.node.selector
});
_debug.print();
}
// {/debug}
},
delegateEvents: function () {
var scope = this,
events = [
'focus' + this.namespace,
'input' + this.namespace,
'propertychange' + this.namespace, // IE8 Fix
'keydown' + this.namespace,
'keyup' + this.namespace, // IE9 Fix
'dynamic' + this.namespace,
'generate' + this.namespace
];
// #149 - Adding support for Mobiles
$('html').on("touchmove", function () {
scope.hasDragged = true;
}).on("touchstart", function () {
scope.hasDragged = false;
});
this.node.closest('form').on("submit", function (e) {
if (scope.options.mustSelectItem && scope.helper.isEmpty(scope.item)) {
e.preventDefault();
return;
}
if (!scope.options.backdropOnFocus) {
scope.hideLayout();
}
if (scope.options.callback.onSubmit) {
return scope.helper.executeCallback.call(scope, scope.options.callback.onSubmit, [scope.node, this, scope.item, e]);
}
}).on("reset", function () {
// #221 - Reset Typeahead on form reset.
// setTimeout to re-queue the `input.typeahead` event at the end
setTimeout(function () {
scope.node.trigger('input' + scope.namespace);
// #243 - minLength: 0 opens the Typeahead results
scope.hideLayout();
});
});
// IE8 fix
var preventNextEvent = false;
// IE10/11 fix
if (this.node.attr('placeholder') && (_isIE10 || _isIE11)) {
var preventInputEvent = true;
this.node.on("focusin focusout", function () {
preventInputEvent = !!(!this.value && this.placeholder);
});
this.node.on("input", function (e) {
if (preventInputEvent) {
e.stopImmediatePropagation();
preventInputEvent = false;
}
});
}
this.node.off(this.namespace).on(events.join(' '), function (e, originalEvent) {
switch (e.type) {
case "generate":
scope.isGenerated = null;
scope.generateSource();
break;
case "focus":
if (scope.focusOnly) {
scope.focusOnly = false;
break;
}
if (scope.options.backdropOnFocus) {
scope.buildBackdropLayout();
scope.showLayout();
}
if (scope.options.searchOnFocus && scope.query.length >= scope.options.minLength) {
if (scope.isGenerated) {
scope.showLayout();
} else if (scope.isGenerated === null) {
scope.generateSource();
}
}
break;
case "keydown":
if (e.keyCode && ~[9, 13, 27, 38, 39, 40].indexOf(e.keyCode)) {
preventNextEvent = true;
scope.navigate(e);
}
break;
case "keyup":
if (scope.isGenerated === null && !scope.options.dynamic) {
scope.generateSource();
}
if (_isIE9 && scope.node[0].value.replace(/^\s+/, '').toString().length < scope.query.length) {
scope.node.trigger('input' + scope.namespace);
}
break;
case "propertychange":
if (preventNextEvent) {
preventNextEvent = false;
break;
}
case "input":
scope.rawQuery = scope.node[0].value.toString();
// #195 Trigger an onCancel event if the Typeahead is cleared
if (scope.rawQuery === "" && scope.query !== "") {
e.originalEvent = originalEvent || {};
scope.helper.executeCallback.call(scope, scope.options.callback.onCancel, [scope.node, e]);
}
scope.query = scope.rawQuery.replace(/^\s+/, '');
scope.options.cancelButton && scope.toggleCancelButton();
if (scope.options.hint && scope.hint.container && scope.hint.container.val() !== '') {
if (scope.hint.container.val().indexOf(scope.rawQuery) !== 0) {
scope.hint.container.val('');
}
}
if (scope.options.dynamic) {
scope.isGenerated = null;
scope.helper.typeWatch(function () {
if (scope.query.length >= scope.options.minLength) {
scope.generateSource();
} else {
scope.hideLayout();
}
}, scope.options.delay);
return;
}
case "dynamic":
if (!scope.isGenerated) {
break;
}
scope.searchResult();
scope.buildLayout();
if ((scope.result.length > 0 || (scope.options.emptyTemplate && scope.query !== "")) &&
scope.query.length >= scope.options.minLength
) {
scope.showLayout();
} else {
scope.hideLayout();
}
break;
}
});
if (this.options.generateOnLoad) {
this.node.trigger('generate' + this.namespace);
}
},
generateSource: function () {
if (this.isGenerated && !this.options.dynamic) {
return;
}
this.generatedGroupCount = 0;
this.isGenerated = false;
this.options.loadingAnimation && this.container.addClass('loading');
if (!this.helper.isEmpty(this.xhr)) {
for (var i in this.xhr) {
if (!this.xhr.hasOwnProperty(i)) continue;
this.xhr[i].abort();
}
this.xhr = {};
}
var scope = this,
group,
groupData,
groupSource,
dataInStorage,
isValidStorage;
for (group in this.options.source) {
if (!this.options.source.hasOwnProperty(group)) continue;
groupSource = this.options.source[group];
// Get group source from Localstorage
if (this.options.cache) {
dataInStorage = window[this.options.cache].getItem('TYPEAHEAD_' + this.node.selector + ":" + group);
if (dataInStorage) {
if (this.options.compression) {
dataInStorage = LZString.decompressFromUTF16(dataInStorage);
}
// In case the storage key:value are not readable anymore
isValidStorage = false;
try {
dataInStorage = JSON.parse(dataInStorage + "");
if (dataInStorage.data && dataInStorage.ttl > new Date().getTime()) {
this.populateSource(dataInStorage.data, group);
isValidStorage = true;
// {debug}
if (this.options.debug) {
_debug.log({
'node': this.node.selector,
'function': 'generateSource()',
'message': 'Source for group "' + group + '" found in ' + this.options.cache
});
_debug.print();
}
// {/debug}
} else {
window[this.options.cache].removeItem('TYPEAHEAD_' + this.node.selector + ":" + group);
}
} catch (error) {
}
if (isValidStorage) continue;
}
}
// Get group source from data
if (groupSource.data && !groupSource.ajax) {
// #198 Add support for async data source
if (typeof groupSource.data === "function") {
groupData = groupSource.data.call(this);
if (Array.isArray(groupData)) {
scope.populateSource(groupData, group);
} else if (typeof groupData.promise === "function") {
(function (group) {
$.when(groupData).then(function (deferredData) {
if (deferredData && Array.isArray(deferredData)) {
scope.populateSource(deferredData, group);
}
});
})(group);
}
} else {
this.populateSource(
$.extend(true, [], groupSource.data),
group
);
}
continue;
}
// Get group source from Ajax / JsonP
if (groupSource.ajax) {
if (!this.requests[group]) {
this.requests[group] = this.generateRequestObject(group);
}
}
}
this.handleRequests();
},
generateRequestObject: function (group) {
var scope = this,
groupSource = this.options.source[group];
var xhrObject = {
request: {
url: groupSource.ajax.url || null,
dataType: 'json',
beforeSend: function (jqXHR, options) {
// Important to call .abort() in case of dynamic requests
scope.xhr[group] = jqXHR;
var beforeSend = scope.requests[group].callback.beforeSend || groupSource.ajax.beforeSend;
typeof beforeSend === "function" && beforeSend.apply(null, arguments);
}
},
callback: {
beforeSend: null,
done: null,
fail: null,
then: null,
always: null
},
extra: {
path: groupSource.ajax.path || null,
group: group
},
validForGroup: [group]
};
if (typeof groupSource.ajax !== "function") {
if (groupSource.ajax instanceof Object) {
xhrObject = this.extendXhrObject(xhrObject, groupSource.ajax);
}
if (Object.keys(this.options.source).length > 1) {
for (var _group in this.requests) {
if (!this.requests.hasOwnProperty(_group)) continue;
if (this.requests[_group].isDuplicated) continue;
if (xhrObject.request.url && xhrObject.request.url === this.requests[_group].request.url) {
this.requests[_group].validForGroup.push(group);
xhrObject.isDuplicated = true;
delete xhrObject.validForGroup;
}
}
}
}
return xhrObject;
},
extendXhrObject: function (xhrObject, groupRequest) {
if (typeof groupRequest.callback === "object") {
xhrObject.callback = groupRequest.callback;
delete groupRequest.callback;
}
// #132 Fixed beforeSend when using a function as the request object
if (typeof groupRequest.beforeSend === "function") {
xhrObject.callback.beforeSend = groupRequest.beforeSend;
delete groupRequest.beforeSend;
}
// Fixes #105 Allow user to define their beforeSend function.
// Fixes #181 IE8 incompatibility
xhrObject.request = $.extend(true, xhrObject.request, groupRequest/*, {beforeSend: xhrObject.request.beforeSend}*/);
// JSONP needs a unique jsonpCallback to run concurrently
if (xhrObject.request.dataType.toLowerCase() === 'jsonp' && !xhrObject.request.jsonpCallback) {
xhrObject.request.jsonpCallback = 'callback_' + xhrObject.extra.group;
}
return xhrObject;
},
handleRequests: function () {
var scope = this,
requestsCount = Object.keys(this.requests).length;
if (this.helper.executeCallback.call(this, this.options.callback.onSendRequest, [this.node, this.query]) === false) {
this.isGenerated = null;
return;
}
for (var group in this.requests) {
if (!this.requests.hasOwnProperty(group)) continue;
if (this.requests[group].isDuplicated) continue;
(function (group, xhrObject) {
if (typeof scope.options.source[group].ajax === "function") {
var _groupRequest = scope.options.source[group].ajax.call(scope, scope.query);
xhrObject = scope.extendXhrObject(xhrObject, _groupRequest);
if (typeof xhrObject.request !== "object" || !xhrObject.request.url) {
// {debug}
if (scope.options.debug) {
_debug.log({
'node': scope.node.selector,
'function': 'handleRequests',
'message': 'Source function must return an object containing ".url" key for group "' + group + '"'
});
_debug.print();
}
// {/debug}
return;
}
}
var _request,
_isExtended = false; // Prevent the main request from being changed
if (~xhrObject.request.url.indexOf('{{query}}')) {
if (!_isExtended) {
xhrObject = $.extend(true, {}, xhrObject);
_isExtended = true;
}
// #184 Invalid encoded characters on dynamic requests for `{{query}}`
xhrObject.request.url = xhrObject.request.url.replace('{{query}}', encodeURIComponent(scope.query));
}
if (xhrObject.request.data) {
for (var i in xhrObject.request.data) {
if (!xhrObject.request.data.hasOwnProperty(i)) continue;
if (~String(xhrObject.request.data[i]).indexOf('{{query}}')) {
if (!_isExtended) {
xhrObject = $.extend(true, {}, xhrObject);
_isExtended = true;
}
// jQuery handles encodeURIComponent when the query is inside the data object
xhrObject.request.data[i] = xhrObject.request.data[i].replace('{{query}}', scope.query);
break;
}
}
}
$.ajax(xhrObject.request).done(function (data, textStatus, jqXHR) {
var tmpData;
for (var i = 0, ii = xhrObject.validForGroup.length; i < ii; i++) {
_request = scope.requests[xhrObject.validForGroup[i]];
if (_request.callback.done instanceof Function) {
tmpData = _request.callback.done(data, textStatus, jqXHR);
data = Array.isArray(tmpData) && tmpData || data;
// {debug}
if (!Array.isArray(tmpData)) {
if (scope.options.debug) {
_debug.log({
'node': scope.node.selector,
'function': 'Ajax.callback.done()',
'message': 'Invalid returned data has to be an Array'
});
_debug.print();
}
}
// {/debug}
}
}
}).fail(function (jqXHR, textStatus, errorThrown) {
for (var i = 0, ii = xhrObject.validForGroup.length; i < ii; i++) {
_request = scope.requests[xhrObject.validForGroup[i]];
_request.callback.fail instanceof Function && _request.callback.fail(jqXHR, textStatus, errorThrown);
}
// {debug}
if (scope.options.debug) {
_debug.log({
'node': scope.node.selector,
'function': 'Ajax.callback.fail()',
'arguments': JSON.stringify(xhrObject.request),
'message': textStatus
});
console.log(errorThrown);
_debug.print();
}
// {/debug}
}).always(function (data, textStatus, jqXHR) {
for (var i = 0, ii = xhrObject.validForGroup.length; i < ii; i++) {
_request = scope.requests[xhrObject.validForGroup[i]];
_request.callback.always instanceof Function && _request.callback.always(data, textStatus, jqXHR);
// #248 Aborted requests would call populate with invalid data
if (typeof jqXHR === "object") {
scope.populateSource(
typeof data.promise === "function" && [] || data,
_request.extra.group,
_request.extra.path || _request.request.path
);
}
requestsCount -= 1;
if (requestsCount === 0) {
scope.helper.executeCallback.call(scope, scope.options.callback.onReceiveRequest, [scope.node, scope.query]);
}
}
}).then(function (jqXHR, textStatus) {
for (var i = 0, ii = xhrObject.validForGroup.length; i < ii; i++) {
_request = scope.requests[xhrObject.validForGroup[i]];
_request.callback.then instanceof Function && _request.callback.then(jqXHR, textStatus);
}
});
}(group, this.requests[group]));
}
},
/**
* Build the source groups to be cycled for matched results
*
* @param {Array} data Array of Strings or Array of Objects
* @param {String} group
* @param {String} [path]
* @return {*}
*/
populateSource: function (data, group, path) {
var scope = this,
groupSource = this.options.source[group],
extraData = groupSource.ajax && groupSource.data;
data = typeof path === "string" ? this.helper.namespace(path, data) : data;
if (typeof data === 'undefined') {
// {debug}
if (this.options.debug) {
_debug.log({
'node': this.node.selector,
'function': 'populateSource()',
'arguments': path,
'message': 'Invalid data path.'
});
_debug.print();
}
// {/debug}
}
if (!Array.isArray(data)) {
// {debug}
if (this.options.debug) {
_debug.log({
'node': this.node.selector,
'function': 'populateSource()',
'arguments': JSON.stringify({group: group}),
'message': 'Invalid data type, must be Array type.'
});
_debug.print();
}
// {/debug}
data = [];
}
if (extraData) {
if (typeof extraData === "function") {
extraData = extraData();
}
if (Array.isArray(extraData)) {
data = data.concat(extraData);
}
// {debug}
else {
if (this.options.debug) {
_debug.log({
'node': this.node.selector,
'function': 'populateSource()',
'arguments': JSON.stringify(extraData),
'message': 'WARNING - this.options.source.' + group + '.data Must be an Array or a function that returns an Array.'
});
_debug.print();
}
}
// {/debug}
}
var tmpObj,
display = groupSource.display ?
(groupSource.display[0] === 'compiled' ? groupSource.display[1] : groupSource.display[0]) :
(this.options.display[0] === 'compiled' ? this.options.display[1] : this.options.display[0]);
for (var i = 0, ii = data.length; i < ii; i++) {
if (data[i] === null || typeof data[i] === "boolean") {
_debug.log({
'node': this.node.selector,
'function': 'populateSource()',
'message': 'WARNING - NULL/BOOLEAN value inside ' + group + '! The data was skipped.'
});
_debug.print();
continue;
}
if (typeof data[i] === "string") {
tmpObj = {};
tmpObj[display] = data[i];
data[i] = tmpObj;
}
data[i].group = group;
}
if (!this.options.dynamic && this.dropdownFilter.dynamic.length) {
var key,
value,
tmpValues = {};
for (var i = 0, ii = data.length; i < ii; i++) {
for (var k = 0, kk = this.dropdownFilter.dynamic.length; k < kk; k++) {
key = this.dropdownFilter.dynamic[k].key;
value = data[i][key];
if (!value) continue;
if (!this.dropdownFilter.dynamic[k].value) {
this.dropdownFilter.dynamic[k].value = [];
}
if (!tmpValues[key]) {
tmpValues[key] = [];
}
if (!~tmpValues[key].indexOf(value.toLowerCase())) {
tmpValues[key].push(value.toLowerCase());
this.dropdownFilter.dynamic[k].value.push(value);
}
}
}
}
if (this.options.correlativeTemplate) {
var template = groupSource.template || this.options.template,
compiledTemplate = "";
if (typeof template === "function") {
template = template();
}
if (!template) {
// {debug}
if (this.options.debug) {