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