diff --git a/RIGS/static/css/ajax-bootstrap-select.css b/RIGS/static/css/ajax-bootstrap-select.css
new file mode 100755
index 00000000..a7c010b2
--- /dev/null
+++ b/RIGS/static/css/ajax-bootstrap-select.css
@@ -0,0 +1,27 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.3.1
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2015 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2015-01-06 8:43:11 PM EST
+ */
+.bootstrap-select .status {
+ background: #f0f0f0;
+ clear: both;
+ color: #999;
+ font-size: 11px;
+ font-style: italic;
+ font-weight: 500;
+ line-height: 1;
+ margin-bottom: -5px;
+ padding: 10px 20px;
+}
diff --git a/RIGS/static/css/bootstrap-select.min.css b/RIGS/static/css/bootstrap-select.min.css
new file mode 100644
index 00000000..4e9d1344
--- /dev/null
+++ b/RIGS/static/css/bootstrap-select.min.css
@@ -0,0 +1,6 @@
+/*!
+ * Bootstrap-select v1.6.2 (http://silviomoreto.github.io/bootstrap-select/)
+ *
+ * Copyright 2013-2014 bootstrap-select
+ * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE)
+ */.bootstrap-select{width:220px \0}.bootstrap-select>.btn{width:100%;padding-right:25px}.error .bootstrap-select .btn{border:1px solid #b94a48}.control-group.error .bootstrap-select .dropdown-toggle{border-color:#b94a48}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .btn:focus{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none}.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.btn-group:not(.input-group-btn),.bootstrap-select.btn-group[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.btn-group.dropdown-menu-right,.bootstrap-select.btn-group[class*=col-].dropdown-menu-right,.row-fluid .bootstrap-select.btn-group[class*=col-].dropdown-menu-right{float:right}.form-search .bootstrap-select.btn-group,.form-inline .bootstrap-select.btn-group,.form-horizontal .bootstrap-select.btn-group,.form-group .bootstrap-select.btn-group{margin-bottom:0}.form-group-lg .bootstrap-select.btn-group.form-control,.form-group-sm .bootstrap-select.btn-group.form-control{padding:0}.form-inline .bootstrap-select.btn-group .form-control{width:100%}.input-append .bootstrap-select.btn-group{margin-left:-1px}.input-prepend .bootstrap-select.btn-group{margin-right:-1px}.bootstrap-select.btn-group>.disabled{cursor:not-allowed}.bootstrap-select.btn-group>.disabled:focus{outline:0!important}.bootstrap-select.btn-group .btn .filter-option{display:inline-block;overflow:hidden;width:100%;text-align:left}.bootstrap-select.btn-group .btn .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.bootstrap-select.btn-group[class*=col-] .btn{width:100%}.bootstrap-select.btn-group .dropdown-menu{min-width:100%;z-index:1035;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .dropdown-menu.inner{position:static;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select.btn-group .dropdown-menu li{position:relative}.bootstrap-select.btn-group .dropdown-menu li:not(.disabled) a:hover small,.bootstrap-select.btn-group .dropdown-menu li:not(.disabled) a:focus small,.bootstrap-select.btn-group .dropdown-menu li.active:not(.disabled) a small{color:#64b1d8;color:rgba(100,177,216,.4)}.bootstrap-select.btn-group .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select.btn-group .dropdown-menu li a{cursor:pointer}.bootstrap-select.btn-group .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select.btn-group .dropdown-menu li a span.check-mark{display:none}.bootstrap-select.btn-group .dropdown-menu li a span.text{display:inline-block}.bootstrap-select.btn-group .dropdown-menu li small{padding-left:.5em}.bootstrap-select.btn-group .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .no-results{padding:3px;background:#f5f5f5;margin:0 5px}.bootstrap-select.btn-group.fit-width .btn .filter-option{position:static}.bootstrap-select.btn-group.fit-width .btn .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark{position:absolute;display:inline-block;right:15px;margin-top:5px}.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select.show-menu-arrow.open>.btn{z-index:1035+1}.bootstrap-select.show-menu-arrow .dropdown-toggle:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom-width:7px;border-bottom-style:solid;border-bottom-color:#ccc;border-bottom-color:rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before{bottom:auto;top:-3px;border-bottom:0;border-top-width:7px;border-top-style:solid;border-top-color:#ccc;border-top-color:rgba(204,204,204,.2)}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after{bottom:auto;top:-3px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:before,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:after{display:block}.bs-searchbox,.bs-actionsbox{padding:4px 8px}.bs-actionsbox{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox input.form-control{margin-bottom:0;width:100%}.mobile-device{position:absolute;top:0;left:0;display:block!important;width:100%;height:100%!important;opacity:0}
\ No newline at end of file
diff --git a/RIGS/static/js/ajax-bootstrap-select.js b/RIGS/static/js/ajax-bootstrap-select.js
new file mode 100755
index 00000000..da91c98b
--- /dev/null
+++ b/RIGS/static/js/ajax-bootstrap-select.js
@@ -0,0 +1,1554 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.3.1
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2015 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2015-01-06 8:43:11 PM EST
+ */
+!(function ($, window) {
+
+/**
+ * @class AjaxBootstrapSelect
+ *
+ * @param {jQuery|HTMLElement} element
+ * The select element this plugin is to affect.
+ * @param {Object} [options={}]
+ * The options used to affect the desired functionality of this plugin.
+ *
+ * @return {AjaxBootstrapSelect|null}
+ * A new instance of this class or null if unable to instantiate.
+ */
+var AjaxBootstrapSelect = function (element, options) {
+ var i, l, plugin = this;
+ options = options || {};
+
+ /**
+ * The select element this plugin is being attached to.
+ * @type {jQuery}
+ */
+ this.$element = $(element);
+
+ /**
+ * The merged default and passed options.
+ * @type {Object}
+ */
+ this.options = $.extend(true, {}, $.fn.ajaxSelectPicker.defaults, options);
+
+ /**
+ * Used for logging error messages.
+ * @type {Number}
+ */
+ this.LOG_ERROR = 1;
+
+ /**
+ * Used for logging warning messages.
+ * @type {Number}
+ */
+ this.LOG_WARNING = 2;
+
+ /**
+ * Used for logging informational messages.
+ * @type {Number}
+ */
+ this.LOG_INFO = 3;
+
+ /**
+ * Used for logging debug messages.
+ * @type {Number}
+ */
+ this.LOG_DEBUG = 4;
+
+ /**
+ * The jqXHR object of the last request, false if there was none.
+ * @type {jqXHR|Boolean}
+ */
+ this.lastRequest = false;
+
+ /**
+ * The previous query that was requested.
+ * @type {String}
+ */
+ this.previousQuery = '';
+
+ /**
+ * The current query being requested.
+ * @type {String}
+ */
+ this.query = '';
+
+ /**
+ * The jqXHR object of the current request, false if there is none.
+ * @type {jqXHR|Boolean}
+ */
+ this.request = false;
+
+ // Maps deprecated options to new ones between releases.
+ var deprecatedOptionsMap = [
+ // @todo Remove these options in next minor release.
+ {
+ from: 'ajaxResultsPreHook',
+ to: 'preprocessData'
+ },
+ {
+ from: 'ajaxSearchUrl',
+ to: {
+ ajax: {
+ url: '{{{value}}}'
+ }
+ }
+ },
+ {
+ from: 'ajaxOptions',
+ to: 'ajax'
+ },
+ {
+ from: 'debug',
+ to: function (map) {
+ var _options = {};
+ _options.log = Boolean(plugin.options[map.from]) ? plugin.LOG_DEBUG : 0;
+ plugin.options = $.extend(true, {}, plugin.options, _options);
+ delete plugin.options[map.from];
+ plugin.log(plugin.LOG_WARNING, 'Deprecated option "' + map.from + '". Update code to use:', _options);
+ }
+ },
+ {
+ from: 'mixWithCurrents',
+ to: 'preserveSelected'
+ },
+ {
+ from: 'placeHolderOption',
+ to: {
+ locale: {
+ emptyTitle: '{{{value}}}'
+ }
+ }
+ }
+ ];
+ if (deprecatedOptionsMap.length) {
+ $.map(deprecatedOptionsMap, function (map) {
+ // Depreciated option detected.
+ if (plugin.options[map.from]) {
+ // Map with an object. Use "{{{value}}}" anywhere in the object to
+ // replace it with the passed value.
+ if ($.isPlainObject(map.to)) {
+ plugin.replaceValue(map.to, '{{{value}}}', plugin.options[map.from]);
+ plugin.options = $.extend(true, {}, plugin.options, map.to);
+ plugin.log(plugin.LOG_WARNING, 'Deprecated option "' + map.from + '". Update code to use:', map.to);
+ delete plugin.options[map.from];
+ }
+ // Map with a function. Functions are silos. They are responsible
+ // for deleting the original option and displaying debug info.
+ else if ($.isFunction(map.to)) {
+ map.to.apply(plugin, [map]);
+ }
+ // Map normally.
+ else {
+ var _options = {};
+ _options[map.to] = plugin.options[map.from];
+ plugin.options = $.extend(true, {}, plugin.options, _options);
+ plugin.log(plugin.LOG_WARNING, 'Deprecated option "' + map.from + '". Update code to use:', _options);
+ delete plugin.options[map.from];
+ }
+ }
+ });
+ }
+
+ // Retrieve the element data attributes.
+ var data = this.$element.data();
+
+ // @todo Deprecated. Remove this in the next minor release.
+ if (data['searchUrl']) {
+ plugin.log(plugin.LOG_WARNING, 'Deprecated attribute name: "data-search-url". Update markup to use: \' data-abs-ajax-url="' + data['searchUrl'] + '" \'');
+ this.options.ajax.url = data['searchUrl'];
+ }
+
+ // Helper functions.
+ var matchToLowerCase = function (match, p1) { return p1.toLowerCase(); };
+ var expandObject = function (keys, value, obj) {
+ var k = [].concat(keys), l = k.length, o = obj || {};
+ if (l) { var key = k.shift(); o[key] = expandObject(k, value, o[key]); }
+ return l ? o : value;
+ };
+
+ // Filter out only the data attributes prefixed with 'data-abs-'.
+ var dataKeys = Object.keys(data).filter(/./.test.bind(new RegExp('^abs[A-Z]')));
+
+ // Map the data attributes to their respective place in the options object.
+ if (dataKeys.length) {
+ // Object containing the data attribute options.
+ var dataOptions = {};
+ for (i = 0, l = dataKeys.length; i < l; i++) {
+ var name = dataKeys[i].replace(/^abs([A-Z])/, matchToLowerCase).replace(/([A-Z])/g, '-$1').toLowerCase();
+ this.log(this.LOG_DEBUG, 'Processing data attribute "data-abs-' + name + '":', data[dataKeys[i]]);
+ expandObject(name.split('-'), data[dataKeys[i]], dataOptions);
+ }
+ this.options = $.extend(true, {}, this.options, dataOptions);
+ this.log(this.LOG_DEBUG, 'Merged in the data attribute options: ', dataOptions, this.options);
+ }
+
+ /**
+ * Reference to the selectpicker instance.
+ * @type {Selectpicker}
+ */
+ this.selectpicker = data['selectpicker'];
+ if (!this.selectpicker) {
+ this.log(this.LOG_ERROR, 'Cannot instantiate an AjaxBootstrapSelect instance without selectpicker first being initialized!');
+ return null;
+ }
+
+ // Ensure there is a URL.
+ if (!this.options.ajax.url) {
+ this.log(this.LOG_ERROR, 'Option "ajax.url" must be set! Options:', this.options);
+ return null;
+ }
+
+ // Initialize the locale strings.
+ this.locale = $.extend(true, {}, $.fn.ajaxSelectPicker.locale);
+
+ // Ensure the langCode is properly set.
+ this.options.langCode = this.options.langCode || window.navigator.userLanguage || window.navigator.language || 'en';
+ if (!this.locale[this.options.langCode]) {
+ var langCode = this.options.langCode;
+
+ // Reset the language code.
+ this.options.langCode = 'en';
+
+ // Check for both the two and four character language codes, using
+ // the later first.
+ var langCodeArray = langCode.split('-');
+ for (i = 0, l = langCodeArray.length; i < l; i++) {
+ var code = langCodeArray.join('-');
+ if (code.length && this.locale[code]) {
+ this.options.langCode = code;
+ break;
+ }
+ langCodeArray.pop();
+ }
+ this.log(this.LOG_WARNING, 'Unknown langCode option: "' + langCode + '". Using the following langCode instead: "' + this.options.langCode + '".');
+ }
+
+ // Allow options to override locale specific strings.
+ this.locale[this.options.langCode] = $.extend(true, {}, this.locale[this.options.langCode], this.options.locale);
+
+ /**
+ * The select list.
+ * @type {AjaxBootstrapSelectList}
+ */
+ this.list = new window.AjaxBootstrapSelectList(this);
+ this.list.refresh();
+
+ // We need for selectpicker to be attached first. Putting the init in a
+ // setTimeout is the easiest way to ensure this.
+ // @todo Figure out a better way to do this (hopefully listen for an event).
+ setTimeout(function () {
+ plugin.init();
+ }, 500);
+};
+
+/**
+ * Initializes this plugin on a selectpicker instance.
+ */
+AjaxBootstrapSelect.prototype.init = function () {
+ var requestDelayTimer, plugin = this;
+
+ // Rebind select/deselect to process preserved selections.
+ if (this.options.preserveSelected) {
+ this.selectpicker.$menu.off('click', '.actions-btn').on('click', '.actions-btn', function (e) {
+ if (plugin.selectpicker.options.liveSearch) {
+ plugin.selectpicker.$searchbox.focus();
+ }
+ else {
+ plugin.selectpicker.$button.focus();
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ if ($(this).is('.bs-select-all')) {
+ if (plugin.selectpicker.$lis === null) {
+ plugin.selectpicker.$lis = plugin.selectpicker.$menu.find('li');
+ }
+ plugin.$element.find('option:enabled').prop('selected', true);
+ $(plugin.selectpicker.$lis).not('.disabled').addClass('selected');
+ plugin.selectpicker.render();
+ }
+ else {
+ if (plugin.selectpicker.$lis === null) {
+ plugin.selectpicker.$lis = plugin.selectpicker.$menu.find('li');
+ }
+ plugin.$element.find('option:enabled').prop('selected', false);
+ $(plugin.selectpicker.$lis).not('.disabled').removeClass('selected');
+ plugin.selectpicker.render();
+ }
+ plugin.selectpicker.$element.change();
+ });
+ }
+
+ // Add placeholder text to the search input.
+ this.selectpicker.$searchbox
+ .attr('placeholder', this.t('searchPlaceholder'))
+ // Remove selectpicker events on the search input.
+ .off('input propertychange');
+
+ // Bind this plugin's event.
+ this.selectpicker.$searchbox.on(this.options.bindEvent, function (e) {
+ var query = plugin.selectpicker.$searchbox.val();
+
+ plugin.log(plugin.LOG_DEBUG, 'Bind event fired: "' + plugin.options.bindEvent + '", keyCode:', e.keyCode, e);
+
+ // Dynamically ignore the "enter" key (13) so it doesn't
+ // create an additional request if the "cache" option has
+ // been disabled.
+ if (!plugin.options.cache) {
+ plugin.options.ignoredKeys[13] = 'enter';
+ }
+
+ // Don't process ignored keys.
+ if (plugin.options.ignoredKeys[e.keyCode]) {
+ plugin.log(plugin.LOG_DEBUG, 'Key ignored.');
+ return;
+ }
+
+ // Clear out any existing timer.
+ clearTimeout(requestDelayTimer);
+
+ // Process empty search value.
+ if (!query.length) {
+ // Clear the select list.
+ if (plugin.options.clearOnEmpty) {
+ plugin.list.destroy();
+ }
+
+ // Don't invoke a request.
+ if (!plugin.options.emptyRequest) {
+ return;
+ }
+ }
+
+ // Store the query.
+ plugin.previousQuery = plugin.query;
+ plugin.query = query;
+
+ // Return the cached results, if any.
+ if (plugin.options.cache && e.keyCode !== 13) {
+ var cache = plugin.list.cacheGet(plugin.query);
+ if (cache) {
+ plugin.list.setStatus(!cache.length ? plugin.t('statusNoResults') : '');
+ plugin.list.replaceOptions(cache);
+ plugin.log(plugin.LOG_INFO, 'Rebuilt options from cached data.');
+ return;
+ }
+ }
+
+ requestDelayTimer = setTimeout(function () {
+ // Abort any previous requests.
+ if (plugin.lastRequest && plugin.lastRequest.jqXHR && $.isFunction(plugin.lastRequest.jqXHR.abort)) {
+ plugin.lastRequest.jqXHR.abort();
+ }
+
+ // Create a new request.
+ plugin.request = new window.AjaxBootstrapSelectRequest(plugin);
+
+ // Store as the previous request once finished.
+ plugin.request.jqXHR.always(function () {
+ plugin.lastRequest = plugin.request;
+ plugin.request = false;
+ });
+ }, plugin.options.requestDelay || 300);
+ });
+};
+
+/**
+ * Wrapper function for logging messages to window.console.
+ *
+ * @param {Number} type
+ * The type of message to log. Must be one of:
+ *
+ * - AjaxBootstrapSelect.LOG_ERROR
+ * - AjaxBootstrapSelect.LOG_WARNING
+ * - AjaxBootstrapSelect.LOG_INFO
+ * - AjaxBootstrapSelect.LOG_DEBUG
+ *
+ * @param {String|Object|*...} message
+ * The message(s) to log. Multiple arguments can be passed.
+ *
+ * @return {void}
+ */
+AjaxBootstrapSelect.prototype.log = function (type, message) {
+ if (window.console && this.options.log) {
+ // Ensure the logging level is always an integer.
+ if (typeof this.options.log !== 'number') {
+ if (typeof this.options.log === 'string') {
+ this.options.log = this.options.log.toLowerCase();
+ }
+ switch (this.options.log) {
+ case true:
+ case 'debug':
+ this.options.log = this.LOG_DEBUG;
+ break;
+
+ case 'info':
+ this.options.log = this.LOG_INFO;
+ break;
+
+ case 'warn':
+ case 'warning':
+ this.options.log = this.LOG_WARNING;
+ break;
+
+ default:
+ case false:
+ case 'error':
+ this.options.log = this.LOG_ERROR;
+ break;
+ }
+ }
+ if (type <= this.options.log) {
+ var args = [].slice.apply(arguments, [2]);
+
+ // Determine the correct console method to use.
+ switch (type) {
+ case this.LOG_DEBUG:
+ type = 'debug';
+ break;
+ case this.LOG_INFO:
+ type = 'info';
+ break;
+ case this.LOG_WARNING:
+ type = 'warn';
+ break;
+ default:
+ case this.LOG_ERROR:
+ type = 'error';
+ break;
+ }
+
+ // Prefix the message.
+ var prefix = '[' + type.toUpperCase() + '] AjaxBootstrapSelect:';
+ if (typeof message === 'string') {
+ args.unshift(prefix + ' ' + message);
+ }
+ else {
+ args.unshift(message);
+ args.unshift(prefix);
+ }
+
+ // Display the message(s).
+ window.console[type].apply(window.console, args);
+ }
+ }
+};
+
+/**
+ * Replaces an old value in an object or array with a new value.
+ *
+ * @param {Object|Array} obj
+ * The object (or array) to iterate over.
+ * @param {*} needle
+ * The value to search for.
+ * @param {*} value
+ * The value to replace with.
+ * @param {Object} [options]
+ * Additional options for restricting replacement:
+ * - recursive: {boolean} Whether or not to iterate over the entire
+ * object or array, defaults to true.
+ * - depth: {int} The number of level this method is to search
+ * down into child elements, defaults to false (no limit).
+ * - limit: {int} The number of times a replacement should happen,
+ * defaults to false (no limit).
+ *
+ * @return {void}
+ */
+AjaxBootstrapSelect.prototype.replaceValue = function (obj, needle, value, options) {
+ var plugin = this;
+ options = $.extend({
+ recursive: true,
+ depth: false,
+ limit: false
+ }, options);
+ // The use of $.each() opposed to native loops here is beneficial
+ // since obj can be either an array or an object. This helps reduce
+ // the amount of duplicate code needed.
+ $.each(obj, function (k, v) {
+ if (options.limit !== false && typeof options.limit === 'number' && options.limit <= 0) {
+ return false;
+ }
+ if ($.isArray(obj[k]) || $.isPlainObject(obj[k])) {
+ if ((options.recursive && options.depth === false) || (options.recursive && typeof options.depth === 'number' && options.depth > 0)) {
+ plugin.replaceValue(obj[k], needle, value, options);
+ }
+ }
+ else {
+ if (v === needle) {
+ if (options.limit !== false && typeof options.limit === 'number') {
+ options.limit--;
+ }
+ obj[k] = value;
+ }
+ }
+ });
+};
+
+/**
+ * Generates a translated {@link $.fn.ajaxSelectPicker.locale locale string} for a given locale key.
+ *
+ * @param {String} key
+ * The translation key to use.
+ * @param {String} [langCode]
+ * Overrides the currently set {@link $.fn.ajaxSelectPicker.defaults#langCode langCode} option.
+ *
+ * @return
+ * The translated string.
+ */
+AjaxBootstrapSelect.prototype.t = function (key, langCode) {
+ langCode = langCode || this.options.langCode;
+ if (this.locale[langCode] && this.locale[langCode].hasOwnProperty(key)) {
+ return this.locale[langCode][key];
+ }
+ this.log(this.LOG_WARNING, 'Unknown translation key:', key);
+ return key;
+};
+
+/**
+ * Use an existing definition in the Window object or create a new one.
+ *
+ * Note: This must be the last statement of this file.
+ *
+ * @type {AjaxBootstrapSelect}
+ * @ignore
+ */
+window.AjaxBootstrapSelect = window.AjaxBootstrapSelect || AjaxBootstrapSelect;
+
+/**
+ * @class AjaxBootstrapSelectList
+ * Maintains the select options and selectpicker menu.
+ *
+ * @param {AjaxBootstrapSelect} plugin
+ * The plugin instance.
+ *
+ * @return {AjaxBootstrapSelectList}
+ * A new instance of this class.
+ */
+var AjaxBootstrapSelectList = function (plugin) {
+ var that = this;
+
+ /**
+ * DOM element used for updating the status of requests and list counts.
+ * @type {jQuery}
+ */
+ this.$status = $(plugin.options.templates.status).hide().appendTo(plugin.selectpicker.$menu);
+ var statusInitialized = plugin.t('statusInitialized');
+ if (statusInitialized && statusInitialized.length) {
+ this.setStatus(statusInitialized);
+ }
+
+ /**
+ * Container for cached data.
+ * @type {Object}
+ */
+ this.cache = {};
+
+ /**
+ * Reference the plugin for internal use.
+ * @type {AjaxBootstrapSelect}
+ */
+ this.plugin = plugin;
+
+ /**
+ * Container for current selections.
+ * @type {Array}
+ */
+ this.selected = [];
+
+ /**
+ * Containers for previous titles.
+ */
+ this.title = null;
+ this.selectedTextFormat = plugin.selectpicker.options.selectedTextFormat;
+
+ // Preserve selected options.
+ if (plugin.options.preserveSelected) {
+ plugin.$element.on('change.abs.preserveSelected', function (e) {
+ var $selected = plugin.$element.find(':selected');
+ that.selected = [];
+ // If select does not have multiple selection, ensure that only the
+ // last selected option is preserved.
+ if (!plugin.selectpicker.multiple) {
+ $selected = $selected.last();
+ }
+ $selected.each(function () {
+ var $option = $(this);
+ var value = $option.attr('value');
+ that.selected.push({
+ value: value,
+ text: $option.text(),
+ 'class': $option.attr('class') || '',
+ data: $option.data() || {},
+ preserved: true,
+ selected: true
+ });
+ });
+ that.replaceOptions(that.cacheGet(that.plugin.query));
+ });
+ }
+};
+
+/**
+ * Builds the options for placing into the element.
+ *
+ * @param {Array} data
+ * The data to use when building options for the select list. Each
+ * array item must be an Object structured as follows:
+ * - {int|string} value: Required, a unique value identifying the
+ * item. Optionally not required if divider is passed instead.
+ * - {boolean} [divider]: Optional, if passed all other values are
+ * ignored and this item becomes a divider.
+ * - {string} [text]: Optional, the text to display for the item.
+ * If none is provided, the value will be used.
+ * - {String} [class]: Optional, the classes to apply to the option.
+ * - {boolean} [disabled]: Optional, flag that determines if the
+ * option is disabled.
+ * - {boolean} [selected]: Optional, flag that determines if the
+ * option is selected. Useful only for select lists that have the
+ * "multiple" attribute. If it is a single select list, each item
+ * that passes this property as true will void the previous one.
+ * - {Object} [data]: Optional, the additional data attributes to
+ * attach to the option element. These are processed by the
+ * bootstrap-select plugin.
+ *
+ * @return {String}
+ * HTML containing the ').attr('label', this.plugin.t('currentlySelected'));
+
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Building the select list options from data:', data);
+
+ for (i = 0; i < l; i++) {
+ var item = data[i];
+ var $option = $('').appendTo(item.preserved ? $preserved : $select);
+
+ // Detect dividers.
+ if (item.hasOwnProperty('divider')) {
+ $option.attr('data-divider', 'true');
+ continue;
+ }
+
+ // Set various properties.
+ $option.val(item.value).text(item.text);
+ if (item['class'].length) {
+ $option.attr('class', item['class']);
+ }
+ if (item.disabled) {
+ $option.attr('disabled', true);
+ }
+
+ // Remove previous selections, if necessary.
+ if (item.selected && !this.plugin.selectpicker.multiple) {
+ $select.find(':selected').prop('selected', false);
+ }
+
+ // Set this option's selected state.
+ if (item.selected) {
+ $option.attr('selected', true);
+ }
+
+ // Add data attributes.
+ for (a in item.data) {
+ if (item.data.hasOwnProperty(a)) {
+ $option.attr('data-' + a, item.data[a]);
+ }
+ }
+ }
+
+ // Append the preserved selections.
+ if ($preserved.find('option').length) {
+ $preserved[this.plugin.options.preserveSelectedPosition === 'before' ? 'prependTo' : 'appendTo']($select);
+ }
+
+ var options = $select.html();
+ this.plugin.log(this.plugin.LOG_DEBUG, options);
+ return options;
+};
+
+/**
+ * Retrieve data from the cache.
+ *
+ * @param {string} key
+ * The identifier name of the data to retrieve.
+ * @param {*} [defaultValue]
+ * The default value to return if no cache data is available.
+ *
+ * @return {*}
+ * The cached data or defaultValue.
+ */
+AjaxBootstrapSelectList.prototype.cacheGet = function (key, defaultValue) {
+ var value = this.cache[key] || defaultValue;
+ this.plugin.log(this.LOG_DEBUG, 'Retrieving cache:', key, value);
+ return value;
+};
+
+/**
+ * Save data to the cache.
+ *
+ * @param {string} key
+ * The identifier name of the data to store.
+ * @param {*} value
+ * The value of the data to store.
+ *
+ * @return {void}
+ */
+AjaxBootstrapSelectList.prototype.cacheSet = function (key, value) {
+ this.cache[key] = value;
+ this.plugin.log(this.LOG_DEBUG, 'Saving to cache:', key, value);
+};
+
+/**
+ * Destroys the select list.
+ */
+AjaxBootstrapSelectList.prototype.destroy = function () {
+ this.replaceOptions();
+ this.plugin.list.setStatus();
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Destroyed select list.');
+};
+
+/**
+ * Refreshes the select list.
+ */
+AjaxBootstrapSelectList.prototype.refresh = function (triggerChange) {
+ // Remove unnecessary "min-height" from selectpicker.
+ this.plugin.selectpicker.$menu.css('minHeight', 0);
+ this.plugin.selectpicker.$menu.find('> .inner').css('minHeight', 0);
+ var emptyTitle = this.plugin.t('emptyTitle');
+ if (!this.plugin.$element.find('option').length && emptyTitle && emptyTitle.length) {
+ this.setTitle(emptyTitle);
+ }
+ else if (this.title) {
+ this.restoreTitle();
+ }
+ this.plugin.selectpicker.refresh();
+ // The "refresh" method sets the $lis property to null, it must be rebuilt.
+ this.plugin.selectpicker.findLis();
+
+ // Only trigger change event when specified.
+ if(triggerChange){
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Triggering Change');
+ this.plugin.$element.trigger('change.$');
+ }
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Refreshed select list.');
+};
+
+/**
+ * Replaces the select list options with provided data.
+ *
+ * It will also inject any preserved selections if the preserveSelected
+ * option is enabled.
+ *
+ * @param {Array} data
+ * The data array to process.
+ *
+ * @returns {void}
+ */
+AjaxBootstrapSelectList.prototype.replaceOptions = function (data) {
+ var i, l, item, output = '', processedData = [], selected = [], seenValues = [];
+ data = data || [];
+
+ // Merge in selected options from the previous state (cannot be cached).
+ if (this.selected && this.selected.length) {
+ this.plugin.log(this.plugin.LOG_INFO, 'Processing preserved selections:', this.selected);
+ selected = [].concat(this.selected, data);
+ l = selected.length;
+ for (i = 0; i < l; i++) {
+ item = selected[i];
+ // Typecast the value for the seenValues array. Array indexOf
+ // searches are type sensitive.
+ if (item.hasOwnProperty('value') && seenValues.indexOf(item.value + '') === -1) {
+ seenValues.push(item.value + '');
+ processedData.push(item);
+ }
+ else {
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Duplicate item found, ignoring.');
+ }
+ }
+ data = processedData;
+ }
+
+ // Build the option output.
+ if (data.length) {
+ output = this.plugin.list.build(data);
+ }
+
+ // Replace the options.
+ this.plugin.$element.html(output);
+ this.refresh();
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Replaced options with data:', data);
+};
+
+/**
+ * Restores the select list to the last saved state.
+ *
+ * @return {boolean}
+ * Return true if successful or false if no states are present.
+ */
+AjaxBootstrapSelectList.prototype.restore = function () {
+ var cache = this.plugin.list.cacheGet(this.plugin.previousQuery);
+ if (cache && this.plugin.list.replaceOptions(cache)) {
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Restored select list to the previous query: ', this.plugin.previousQuery);
+ }
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Unable to restore select list to the previous query:', this.plugin.previousQuery);
+ return false;
+};
+
+/**
+ * Restores the previous title of the select element.
+ *
+ * @return {void}
+ */
+AjaxBootstrapSelectList.prototype.restoreTitle = function () {
+ if (!this.plugin.request) {
+ this.plugin.selectpicker.options.selectedTextFormat = this.selectedTextFormat;
+ if (this.title) {
+ this.plugin.$element.attr('title', this.title);
+ }
+ else {
+ this.plugin.$element.removeAttr('title');
+ }
+ this.title = null;
+ }
+};
+
+/**
+ * Sets a new title on the select element.
+ *
+ * @param {String} title
+ *
+ * @return {void}
+ */
+AjaxBootstrapSelectList.prototype.setTitle = function (title) {
+ if (!this.plugin.request) {
+ this.title = this.plugin.$element.attr('title');
+ this.plugin.selectpicker.options.selectedTextFormat = 'static';
+ this.plugin.$element.attr('title', title);
+ }
+};
+
+/**
+ * Sets a new status on the AjaxBootstrapSelectList.$status DOM element.
+ *
+ * @param {String} [status]
+ * The new status to set, if empty it will hide it.
+ *
+ * @return {void}
+ */
+AjaxBootstrapSelectList.prototype.setStatus = function (status) {
+ status = status || '';
+ if (status.length) {
+ this.$status.html(status).show();
+ }
+ else {
+ this.$status.html('').hide();
+ }
+};
+
+/**
+ * Use an existing definition in the Window object or create a new one.
+ *
+ * Note: This must be the last statement of this file.
+ *
+ * @type {AjaxBootstrapSelectList}
+ * @ignore
+ */
+window.AjaxBootstrapSelectList = window.AjaxBootstrapSelectList || AjaxBootstrapSelectList;
+
+/**
+ * @class AjaxBootstrapSelectRequest
+ * Instantiates a new jQuery.ajax request for the current query.
+ *
+ * @param {AjaxBootstrapSelect} plugin
+ * The plugin instance.
+ *
+ * @return {AjaxBootstrapSelectRequest}
+ * A new instance of this class.
+ */
+var AjaxBootstrapSelectRequest = function (plugin) {
+ var that = this;
+ var ajaxCallback = function (event) {
+ return function () {
+ that.plugin.log(that.plugin.LOG_INFO, 'Invoking AjaxBootstrapSelectRequest.' + event + ' callback:', arguments);
+ that[event].apply(that, arguments);
+ if (that.callbacks[event]) {
+ that.plugin.log(that.plugin.LOG_INFO, 'Invoking ajax.' + event + ' callback:', arguments);
+ that.callbacks[event].apply(that, arguments);
+ }
+ };
+ };
+ var events = ['beforeSend', 'success', 'error', 'complete'];
+ var i, l = events.length;
+
+ // Reference the existing plugin.
+ this.plugin = plugin;
+
+ // Clone the default ajax options.
+ this.options = $.extend(true, {}, plugin.options.ajax);
+
+ // Save any existing callbacks provided in the options and replace it with
+ // the relevant method callback. The provided callback will be invoked
+ // after this plugin has executed.
+ this.callbacks = {};
+ for (i = 0; i < l; i++) {
+ var event = events[i];
+ if (this.options[event] && $.isFunction(this.options[event])) {
+ this.callbacks[event] = this.options[event];
+ }
+ this.options[event] = ajaxCallback(event);
+ }
+
+ // Allow the data option to be dynamically generated.
+ if (this.options.data && $.isFunction(this.options.data)) {
+ this.options.data = this.options.data.apply(this) || {
+ q: '{{{q}}}'
+ };
+ }
+
+ // Replace all data values that contain "{{{q}}}" with the value of the
+ // current search query.
+ this.plugin.replaceValue(this.options.data, '{{{q}}}', this.plugin.query);
+
+ // Invoke the AJAX request.
+ this.jqXHR = $.ajax(this.options);
+};
+
+/**
+ * @event
+ * A callback that can be used to modify the jqXHR object before it is sent.
+ *
+ * Use this to set custom headers, etc. Returning false will cancel the request.
+ * To modify the options being sent, use this.options.
+ *
+ * @param {jqXHR} jqXHR
+ * The jQuery wrapped XMLHttpRequest object.
+ *
+ * @return {void}
+ */
+AjaxBootstrapSelectRequest.prototype.beforeSend = function (jqXHR) {
+ // Destroy the list currently there.
+ this.plugin.list.destroy();
+
+ // Set the status accordingly.
+ this.plugin.list.setStatus(this.plugin.t('statusSearching'));
+
+ //this.plugin.list.refresh();
+};
+
+/**
+ * @event
+ * The "complete" callback for the request.
+ *
+ * @param {jqXHR} jqXHR
+ * The jQuery wrapped XMLHttpRequest object.
+ * @param {String} status
+ * A string categorizing the status of the request: "success", "notmodified",
+ * "error", "timeout", "abort", or "parsererror".
+ *
+ * @return {void}
+ */
+AjaxBootstrapSelectRequest.prototype.complete = function (jqXHR, status) {
+ // Only continue if actual results and not an aborted state.
+ if (status !== 'abort') {
+ var cache = this.plugin.list.cacheGet(this.plugin.query);
+ if (cache) {
+ if (cache.length) {
+ this.plugin.list.setStatus();
+ }
+ else {
+ this.plugin.list.destroy();
+ this.plugin.list.setStatus(this.plugin.t('statusNoResults'));
+ this.plugin.log(this.plugin.LOG_INFO, 'No results were returned.');
+ return;
+ }
+ }
+ this.plugin.list.refresh(true);
+ }
+};
+
+/**
+ * @event
+ * The "error" callback for the request.
+ *
+ * @param {jqXHR} jqXHR
+ * The jQuery wrapped XMLHttpRequest object.
+ * @param {String} status
+ * A string describing the type of error that occurred. Possible values for
+ * the second argument (besides null) are "timeout", "error", "abort", and
+ * "parsererror".
+ * @param {String|Object} error
+ * An optional exception object, if one occurred. When an HTTP error occurs,
+ * error receives the textual portion of the HTTP status, such as "Not Found"
+ * or "Internal Server Error."
+ *
+ * @return {void}
+ */
+AjaxBootstrapSelectRequest.prototype.error = function (jqXHR, status, error) {
+ if (status !== 'abort') {
+ // Cache the result data.
+ this.plugin.list.cacheSet(this.plugin.query);
+
+ // Clear the list.
+ if (this.plugin.options.clearOnError) {
+ this.plugin.list.destroy();
+ }
+
+ // Set the status after the list has cleared and before the restore.
+ this.plugin.list.setStatus(this.plugin.t('errorText'));
+
+ // Restore previous request.
+ if (this.plugin.options.restoreOnError) {
+ this.plugin.list.restore();
+ this.plugin.list.setStatus();
+ }
+ }
+};
+
+/**
+ * Process incoming data.
+ *
+ * This method ensures that the incoming data has unique values and
+ * is in the proper format that is utilized by this plugin. It also
+ * adds in the existing selects if the option is enabled. If the
+ * preprocessData and processData functions were defined in the plugin
+ * options, they are invoked here.
+ *
+ * @param {Array|Object} data
+ * The JSON data to process.
+ *
+ * @return {Array|Boolean}
+ * The processed data array or false if an error occurred.
+ */
+AjaxBootstrapSelectRequest.prototype.process = function (data) {
+ var i, l, callbackResult, item, preprocessedData, processedData;
+ var filteredData = [], seenValues = [];
+
+ this.plugin.log(this.plugin.LOG_INFO, 'Processing raw data for:', this.plugin.query, data);
+
+ // Invoke the preprocessData option callback.
+ preprocessedData = data;
+ if ($.isFunction(this.plugin.options.preprocessData)) {
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Invoking preprocessData callback:', this.plugin.options.processData);
+ callbackResult = this.plugin.options.preprocessData.apply(this, [preprocessedData]);
+ if (typeof callbackResult !== 'undefined' && callbackResult !== null && callbackResult !== false) {
+ preprocessedData = callbackResult;
+ }
+ }
+
+ // Ensure the data is an array.
+ if (!$.isArray(preprocessedData)) {
+ this.plugin.log(this.plugin.LOG_ERROR, 'The data returned is not an Array. Use the "preprocessData" callback option to parse the results and construct a proper array for this plugin.', preprocessedData);
+ return false;
+ }
+
+ // Filter preprocessedData.
+ l = preprocessedData.length;
+ for (i = 0; i < l; i++) {
+ item = preprocessedData[i];
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Processing item:', item);
+ if ($.isPlainObject(item)) {
+ // Check if item is a divider. If so, ignore all other data.
+ if (item.hasOwnProperty('divider') || (item.hasOwnProperty('data') && $.isPlainObject(item.data) && item.data.divider)) {
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Item is a divider, ignoring provided data.');
+ filteredData.push({divider: true});
+ }
+ // Ensure item has a "value" and is unique.
+ else {
+ if (item.hasOwnProperty('value')) {
+ // Typecast the value for the seenValues array. Array
+ // indexOf searches are type sensitive.
+ if (seenValues.indexOf(item.value + '') === -1) {
+ seenValues.push(item.value + '');
+ // Provide default items to ensure expected structure.
+ item = $.extend({
+ text: item.value,
+ 'class': '',
+ data: {},
+ disabled: false,
+ selected: false
+ }, item);
+ filteredData.push(item);
+ }
+ else {
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Duplicate item found, ignoring.');
+ }
+ }
+ else {
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Data item must have a "value" property, skipping.');
+ }
+ }
+ }
+ }
+
+ // Invoke the processData option callback.
+ processedData = [].concat(filteredData);
+ if ($.isFunction(this.plugin.options.processData)) {
+ this.plugin.log(this.plugin.LOG_DEBUG, 'Invoking processData callback:', this.plugin.options.processData);
+ callbackResult = this.plugin.options.processData.apply(this, [processedData]);
+ if (typeof callbackResult !== 'undefined' && callbackResult !== null && callbackResult !== false) {
+ if ($.isArray(callbackResult)) {
+ processedData = callbackResult;
+ }
+ else {
+ this.plugin.log(this.plugin.LOG_ERROR, 'The processData callback did not return an array.', callbackResult);
+ return false;
+ }
+ }
+ }
+
+ // Cache the processed data.
+ this.plugin.list.cacheSet(this.plugin.query, processedData);
+
+ this.plugin.log(this.plugin.LOG_INFO, 'Processed data:', processedData);
+ return processedData;
+};
+
+/**
+ * @event
+ * The "success" callback for the request.
+ *
+ * @param {Object} data
+ * The data returned from the server, formatted according to the dataType
+ * option.
+ * @param {String} status
+ * A string describing the status.
+ * @param {jqXHR} jqXHR
+ * The jQuery wrapped XMLHttpRequest object.
+ *
+ * @return {void}
+ */
+AjaxBootstrapSelectRequest.prototype.success = function (data, status, jqXHR) {
+ // Only process data if an Array or Object.
+ if (!$.isArray(data) && !$.isPlainObject(data)) {
+ this.plugin.log(this.plugin.LOG_ERROR, 'Request did not return a JSON Array or Object.', data);
+ this.plugin.list.destroy();
+ return;
+ }
+
+ // Process the data.
+ var processedData = this.process(data);
+ this.plugin.list.replaceOptions(processedData);
+};
+
+/**
+ * Use an existing definition in the Window object or create a new one.
+ *
+ * Note: This must be the last statement of this file.
+ *
+ * @type {AjaxBootstrapSelectRequest}
+ * @ignore
+ */
+window.AjaxBootstrapSelectRequest = window.AjaxBootstrapSelectRequest || AjaxBootstrapSelectRequest;
+
+/**
+ * @class $.fn.ajaxSelectPicker
+ * @chainable
+ *
+ * The jQuery plugin definition.
+ *
+ * This initializes a new AjaxBootstrapSelect class for each element in the jQuery chain.
+ *
+ * @param {Object} options
+ * The {@link $.fn.ajaxSelectPicker.defaults options} to pass to the plugin.
+ *
+ * @returns {jQuery}
+ */
+$.fn.ajaxSelectPicker = function (options) {
+ return this.each(function () {
+ if (!$(this).data('AjaxBootstrapSelect')) {
+ $(this).data('AjaxBootstrapSelect', new window.AjaxBootstrapSelect(this, options));
+ }
+ });
+};
+
+/**
+ * The locale object containing string translations.
+ *
+ * See: {@link $.fn.ajaxSelectPicker.locale}
+ * @type {Object}
+ */
+$.fn.ajaxSelectPicker.locale = {};
+
+/**
+ * The default options the plugin will use if none are provided.
+ *
+ * See: {@link $.fn.ajaxSelectPicker.defaults}
+ *
+ * @member $.fn.ajaxSelectPicker
+ * @property {Object} defaults
+ */
+$.fn.ajaxSelectPicker.defaults = {
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @deprecated Since version `1.2.0`, see: {@link $.fn.ajaxSelectPicker.defaults#preprocessData}.
+ * @cfg {Function} ajaxResultsPreHook
+ */
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {Object} ajax (required)
+ * @markdown
+ * The options to pass to the jQuery AJAX request.
+ *
+ * ```js
+ * {
+ * url: null, // Required.
+ * type: 'POST',
+ * dataType: 'json',
+ * data: {
+ * q: '{{{q}}}'
+ * }
+ * }
+ * ```
+ */
+ ajax: {
+ url: null,
+ type: 'POST',
+ dataType: 'json',
+ data: {
+ q: '{{{q}}}'
+ }
+ },
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {String} ajaxSearchUrl
+ * @deprecated Since version `1.2.0`, see: {@link $.fn.ajaxSelectPicker.defaults#ajax}.
+ */
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {String} bindEvent = "keyup"
+ * @markdown
+ * The event to bind on the search input element to fire a request.
+ */
+ bindEvent: 'keyup',
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {Boolean} cache = true
+ * @markdown
+ * Cache previous requests. If enabled, the "enter" key (13) is enabled to
+ * allow users to force a refresh of the request.
+ */
+ cache: true,
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {Boolean} clearOnEmpty = true
+ * @markdown
+ * Clears the previous results when the search input has no value.
+ */
+ clearOnEmpty: true,
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {Boolean} clearOnError = true
+ * @markdown
+ * Clears the select list when the request returned with an error.
+ */
+ clearOnError: true,
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {Boolean} debug
+ * @deprecated Since version `1.2.0`, see: {@link $.fn.ajaxSelectPicker.defaults#log}.
+ */
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {Boolean} emptyRequest = false
+ * @markdown
+ * Invoke a request for empty search values.
+ */
+ emptyRequest: false,
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {Object} ignoredKeys
+ * @markdown
+ * Key codes to ignore so a request is not invoked with bindEvent. The
+ * "enter" key (13) will always be dynamically added to any list provided
+ * unless the "cache" option above is set to "true".
+ *
+ * ```js
+ * {
+ * 9: "tab",
+ * 16: "shift",
+ * 17: "ctrl",
+ * 18: "alt",
+ * 27: "esc",
+ * 37: "left",
+ * 39: "right",
+ * 38: "up",
+ * 40: "down",
+ * 91: "meta",
+ * 229: "unknown"
+ * }
+ * ```
+ */
+ ignoredKeys: {
+ 9: "tab",
+ 16: "shift",
+ 17: "ctrl",
+ 18: "alt",
+ 27: "esc",
+ 37: "left",
+ 39: "right",
+ 38: "up",
+ 40: "down",
+ 91: "meta",
+ 229: "unknown"
+ },
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {String} langCode = null
+ * @markdown
+ * The language code to use for string translation. By default this value
+ * is determined by the browser, however it is not entirely reliable. If
+ * you encounter inconsistencies, you may need to manually set this option.
+ */
+ langCode: null,
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {Object} locale = null
+ * @markdown
+ * Provide specific overrides for {@link $.fn.ajaxSelectPicker.locale locale string} translations. Values
+ * set here will cause the plugin to completely ignore defined locale string
+ * translations provided by the set language code. This is useful when
+ * needing to change a single value or when being used in a system that
+ * provides its own translations, like a CMS.
+ *
+ * ```js
+ * locale: {
+ * searchPlaceholder: Drupal.t('Find...')
+ * }
+ * ```
+ */
+ locale: null,
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {String|Number|Number} log = 'error'
+ * @markdown
+ * Determines the amount of logging that is displayed:
+ *
+ * - __0, false:__ Display no information from the plugin.
+ * - __1, 'error':__ Fatal errors that prevent the plugin from working.
+ * - __2, 'warn':__ Warnings that may impact the display of request data, but does not prevent the plugin from functioning.
+ * - __3, 'info':__ Provides additional information, generally regarding the from request data and callbacks.
+ * - __4, true, 'debug':__ Display all possible information. This will likely be highly verbose and is only recommended for development purposes or tracing an error with a request.
+ */
+ log: 'error',
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {Boolean} mixWithCurrents
+ * @deprecated Since version `1.2.0`, see: {@link $.fn.ajaxSelectPicker.defaults#preserveSelected}.
+ */
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg placeHolderOption
+ * @deprecated Since version `1.2.0`, see: {@link $.fn.ajaxSelectPicker.locale#emptyTitle}.
+ */
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {Function|null} preprocessData = function(){}
+ * @markdown
+ * Process the raw data returned from a request.
+ *
+ * The following arguments are passed to this callback:
+ *
+ * - __data__ - `Array` The raw data returned from the request, passed by reference.
+ *
+ * This callback must return one of the following:
+ *
+ * - `Array` - A new array of items. This will replace the passed data.
+ * - `undefined|null|false` - Stops the callback and will use whatever modifications have been made to data.
+ *
+ * ```js
+ * function (data) {
+ * var new = [], old = [], other = [];
+ * for (var i = 0; i < data.length; i++) {
+ * // Add items flagged as "new" to the correct array.
+ * if (data[i].new) {
+ * new.push(data[i]);
+ * }
+ * // Add items flagged as "old" to the correct array.
+ * else if (data[i].old) {
+ * old.push(data[i]);
+ * }
+ * // Something out of the ordinary happened, put these last.
+ * else {
+ * other.push(data[i]);
+ * }
+ * }
+ * // Sort the data according to the order of these arrays.
+ * return [].concat(new, old, other).
+ * }
+ * ```
+ */
+ preprocessData: function(){},
+
+ /**
+ * @member $.fn.ajaxSelectPicker.defaults
+ * @cfg {Boolean} preserveSelected = true
+ * @markdown
+ * Preserve selected items(s) between requests. When enabled, they will be
+ * placed in an `