UNPKG

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
/*! * 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) {