PluginProbe
Groups – Memberships and Access Control / 4.7.1
Groups – Memberships and Access Control v4.7.1
4.7.1 4.7.0 4.6.0 4.5.0 4.4.0 4.3.0 trunk 1.0.0-beta-1 1.0.0-beta-2 1.0.0-beta-3 1.0.0-beta-3b 1.0.0-beta-3c 1.0.0-beta-3d 1.1.4 1.1.5 1.10.0 1.10.1 1.10.2 1.10.3 1.11.0 1.11.1 1.11.2 1.11.3 1.12.0 1.13.0 All 131 releases
← All changes | js/selectize/selectize.js +3281 -3451 1.10.14.7.1 View file →
@@ -1,8 +1,11 @@
1 1 /**
2 - * sifter.js
3 - * Copyright (c) 2013 Brian Reavis & contributors
2 + * Selectize (v0.15.2)
3 + * https://selectize.dev
4 4 *
5 + * Copyright (c) 2013-2015 Brian Reavis & contributors
6 + * Copyright (c) 2020-2026 Selectize Team & contributors
7 + *
5 8 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
6 9 * file except in compliance with the License. You may obtain a copy of the License at:
7 10 * http://www.apache.org/licenses/LICENSE-2.0
8 11 *
@@ -11,3657 +14,3484 @@
11 14 * ANY KIND, either express or implied. See the License for the specific language
12 15 * governing permissions and limitations under the License.
13 16 *
14 17 * @author Brian Reavis <brian@thirdroute.com>
18 + * @author Ris Adams <selectize@risadams.com>
15 19 */
20 +(function (root, factory) {
21 + if (typeof define === 'function' && define.amd) {
22 + define(['jquery'], factory);
23 + } else if (typeof module === 'object' && typeof module.exports === 'object') {
24 + module.exports = factory(require('jquery'));
25 + } else {
26 + root.Selectize = factory(root.jQuery);
27 + }
28 +}(this, function ($) {
29 + 'use strict';
30 +var highlight = function ($element, pattern) {
31 + if (typeof pattern === 'string' && !pattern.length) return;
32 + var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;
16 33
17 -(function(root, factory) {
18 - if (typeof define === 'function' && define.amd) {
19 - define('sifter', factory);
20 - } else if (typeof exports === 'object') {
21 - module.exports = factory();
22 - } else {
23 - root.Sifter = factory();
24 - }
25 -}(this, function() {
34 + var highlight = function (node) {
35 + var skip = 0;
36 + if (node.nodeType === 3) {
37 + var pos = node.data.search(regex);
38 + if (pos >= 0 && node.data.length > 0) {
39 + var match = node.data.match(regex);
40 + var spannode = document.createElement('span');
41 + spannode.className = 'highlight';
42 + var middlebit = node.splitText(pos);
43 + var endbit = middlebit.splitText(match[0].length);
44 + var middleclone = middlebit.cloneNode(true);
45 + spannode.appendChild(middleclone);
46 + middlebit.parentNode.replaceChild(spannode, middlebit);
47 + skip = 1;
48 + }
49 + }
50 + else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName) && (node.className !== 'highlight' || node.tagName !== 'SPAN')) {
51 + for (var i = 0; i < node.childNodes.length; ++i) {
52 + i += highlight(node.childNodes[i]);
53 + }
54 + }
55 + return skip;
56 + };
26 57
27 - /**
28 - * Textually searches arrays and hashes of objects
29 - * by property (or multiple properties). Designed
30 - * specifically for autocomplete.
31 - *
32 - * @constructor
33 - * @param {array|object} items
34 - * @param {object} items
35 - */
36 - var Sifter = function(items, settings) {
37 - this.items = items;
38 - this.settings = settings || {diacritics: true};
39 - };
58 + return $element.each(function () {
59 + highlight(this);
60 + });
61 +};
40 62
41 - /**
42 - * Splits a search string into an array of individual
43 - * regexps to be used to match results.
44 - *
45 - * @param {string} query
46 - * @returns {array}
47 - */
48 - Sifter.prototype.tokenize = function(query) {
49 - query = trim(String(query || '').toLowerCase());
50 - if (!query || !query.length) return [];
63 +$.fn.removeHighlight = function () {
64 + return this.find("span.highlight").each(function () {
65 + this.parentNode.firstChild.nodeName;
66 + var parent = this.parentNode;
67 + parent.replaceChild(this.firstChild, this);
68 + parent.normalize();
69 + }).end();
70 +};
51 71
52 - var i, n, regex, letter;
53 - var tokens = [];
54 - var words = query.split(/ +/);
72 +var MicroEvent = function () { };
73 +MicroEvent.prototype = {
74 + on: function (event, fct) {
75 + this._events = this._events || {};
76 + this._events[event] = this._events[event] || [];
77 + this._events[event].push(fct);
78 + },
79 + off: function (event, fct) {
80 + var n = arguments.length;
81 + if (n === 0) return delete this._events;
82 + if (n === 1) return delete this._events[event];
55 83
56 - for (i = 0, n = words.length; i < n; i++) {
57 - regex = escape_regex(words[i]);
58 - if (this.settings.diacritics) {
59 - for (letter in DIACRITICS) {
60 - if (DIACRITICS.hasOwnProperty(letter)) {
61 - regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]);
62 - }
63 - }
64 - }
65 - tokens.push({
66 - string : words[i],
67 - regex : new RegExp(regex, 'i')
68 - });
69 - }
84 + this._events = this._events || {};
85 + if (event in this._events === false) return;
86 + this._events[event].splice(this._events[event].indexOf(fct), 1);
87 + },
88 + trigger: function (event ) {
89 + const events = this._events = this._events || {};
90 + if (event in events === false) return;
91 + for (var i = 0; i < events[event].length; i++) {
92 + events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
93 + }
94 + }
95 +};
70 96
71 - return tokens;
72 - };
97 +MicroEvent.mixin = function (destObject) {
98 + var props = ['on', 'off', 'trigger'];
99 + for (var i = 0; i < props.length; i++) {
100 + destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
101 + }
102 +};
73 103
74 - /**
75 - * Iterates over arrays and hashes.
76 - *
77 - * ```
78 - * this.iterator(this.items, function(item, id) {
79 - * // invoked for each item
80 - * });
81 - * ```
82 - *
83 - * @param {array|object} object
84 - */
85 - Sifter.prototype.iterator = function(object, callback) {
86 - var iterator;
87 - if (is_array(object)) {
88 - iterator = Array.prototype.forEach || function(callback) {
89 - for (var i = 0, n = this.length; i < n; i++) {
90 - callback(this[i], i, this);
91 - }
92 - };
93 - } else {
94 - iterator = function(callback) {
95 - for (var key in this) {
96 - if (this.hasOwnProperty(key)) {
97 - callback(this[key], key, this);
98 - }
99 - }
100 - };
101 - }
104 +var MicroPlugin = {};
105 +MicroPlugin.mixin = function (Interface) {
102 106
103 - iterator.apply(object, [callback]);
104 - };
107 + Interface.plugins = {};
105 108
106 - /**
107 - * Returns a function to be used to score individual results.
108 - *
109 - * Good matches will have a higher score than poor matches.
110 - * If an item is not a match, 0 will be returned by the function.
111 - *
112 - * @param {object|string} search
113 - * @param {object} options (optional)
114 - * @returns {function}
115 - */
116 - Sifter.prototype.getScoreFunction = function(search, options) {
117 - var self, fields, tokens, token_count;
109 + Interface.prototype.initializePlugins = function (plugins) {
110 + var i, n, key;
111 + var self = this;
112 + var queue = [];
118 113
119 - self = this;
120 - search = self.prepareSearch(search, options);
121 - tokens = search.tokens;
122 - fields = search.options.fields;
123 - token_count = tokens.length;
114 + self.plugins = {
115 + names: [],
116 + settings: {},
117 + requested: {},
118 + loaded: {}
119 + };
124 120
125 - /**
126 - * Calculates how close of a match the
127 - * given value is against a search token.
128 - *
129 - * @param {mixed} value
130 - * @param {object} token
131 - * @return {number}
132 - */
133 - var scoreValue = function(value, token) {
134 - var score, pos;
121 + if (isArray(plugins)) {
122 + for (i = 0, n = plugins.length; i < n; i++) {
123 + if (typeof plugins[i] === 'string') {
124 + queue.push(plugins[i]);
125 + } else {
126 + self.plugins.settings[plugins[i].name] = plugins[i].options;
127 + queue.push(plugins[i].name);
128 + }
129 + }
130 + } else if (plugins) {
131 + for (key in plugins) {
132 + if (plugins.hasOwnProperty(key)) {
133 + self.plugins.settings[key] = plugins[key];
134 + queue.push(key);
135 + }
136 + }
137 + }
135 138
136 - if (!value) return 0;
137 - value = String(value || '');
138 - pos = value.search(token.regex);
139 - if (pos === -1) return 0;
140 - score = token.string.length / value.length;
141 - if (pos === 0) score += 0.5;
142 - return score;
143 - };
139 + while (queue.length) {
140 + self.require(queue.shift());
141 + }
142 + };
144 143
145 - /**
146 - * Calculates the score of an object
147 - * against the search query.
148 - *
149 - * @param {object} token
150 - * @param {object} data
151 - * @return {number}
152 - */
153 - var scoreObject = (function() {
154 - var field_count = fields.length;
155 - if (!field_count) {
156 - return function() { return 0; };
157 - }
158 - if (field_count === 1) {
159 - return function(token, data) {
160 - return scoreValue(data[fields[0]], token);
161 - };
162 - }
163 - return function(token, data) {
164 - for (var i = 0, sum = 0; i < field_count; i++) {
165 - sum += scoreValue(data[fields[i]], token);
166 - }
167 - return sum / field_count;
168 - };
169 - })();
170 144
171 - if (!token_count) {
172 - return function() { return 0; };
173 - }
174 - if (token_count === 1) {
175 - return function(data) {
176 - return scoreObject(tokens[0], data);
177 - };
178 - }
145 + Interface.prototype.loadPlugin = function (name) {
146 + var self = this;
147 + var plugins = self.plugins;
148 + var plugin = Interface.plugins[name];
179 149
180 - if (search.options.conjunction === 'and') {
181 - return function(data) {
182 - var score;
183 - for (var i = 0, sum = 0; i < token_count; i++) {
184 - score = scoreObject(tokens[i], data);
185 - if (score <= 0) return 0;
186 - sum += score;
187 - }
188 - return sum / token_count;
189 - };
190 - } else {
191 - return function(data) {
192 - for (var i = 0, sum = 0; i < token_count; i++) {
193 - sum += scoreObject(tokens[i], data);
194 - }
195 - return sum / token_count;
196 - };
197 - }
198 - };
150 + if (!Interface.plugins.hasOwnProperty(name)) {
151 + throw new Error('Unable to find "' + name + '" plugin');
152 + }
199 153
200 - /**
201 - * Returns a function that can be used to compare two
202 - * results, for sorting purposes. If no sorting should
203 - * be performed, `null` will be returned.
204 - *
205 - * @param {string|object} search
206 - * @param {object} options
207 - * @return function(a,b)
208 - */
209 - Sifter.prototype.getSortFunction = function(search, options) {
210 - var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort;
154 + plugins.requested[name] = true;
155 + plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]);
156 + plugins.names.push(name);
157 + };
211 158
212 - self = this;
213 - search = self.prepareSearch(search, options);
214 - sort = (!search.query && options.sort_empty) || options.sort;
159 + Interface.prototype.require = function (name) {
160 + var self = this;
161 + var plugins = self.plugins;
215 162
216 - /**
217 - * Fetches the specified sort field value
218 - * from a search result item.
219 - *
220 - * @param {string} name
221 - * @param {object} result
222 - * @return {mixed}
223 - */
224 - get_field = function(name, result) {
225 - if (name === '$score') return result.score;
226 - return self.items[result.id][name];
227 - };
163 + if (!self.plugins.loaded.hasOwnProperty(name)) {
164 + if (plugins.requested[name]) {
165 + throw new Error('Plugin has circular dependency ("' + name + '")');
166 + }
167 + self.loadPlugin(name);
168 + }
228 169
229 - // parse options
230 - fields = [];
231 - if (sort) {
232 - for (i = 0, n = sort.length; i < n; i++) {
233 - if (search.query || sort[i].field !== '$score') {
234 - fields.push(sort[i]);
235 - }
236 - }
237 - }
170 + return plugins.loaded[name];
171 + };
238 172
239 - // the "$score" field is implied to be the primary
240 - // sort field, unless it's manually specified
241 - if (search.query) {
242 - implicit_score = true;
243 - for (i = 0, n = fields.length; i < n; i++) {
244 - if (fields[i].field === '$score') {
245 - implicit_score = false;
246 - break;
247 - }
248 - }
249 - if (implicit_score) {
250 - fields.unshift({field: '$score', direction: 'desc'});
251 - }
252 - } else {
253 - for (i = 0, n = fields.length; i < n; i++) {
254 - if (fields[i].field === '$score') {
255 - fields.splice(i, 1);
256 - break;
257 - }
258 - }
259 - }
173 + Interface.define = function (name, fn) {
174 + Interface.plugins[name] = {
175 + 'name': name,
176 + 'fn': fn
177 + };
178 + };
179 +};
260 180
261 - multipliers = [];
262 - for (i = 0, n = fields.length; i < n; i++) {
263 - multipliers.push(fields[i].direction === 'desc' ? -1 : 1);
264 - }
181 +const nanoid = (t = 21) => crypto.getRandomValues(new Uint8Array(t))
182 + .reduce(((t, e) =>
183 + t += (e &= 63) < 36 ? e.toString(36) :
184 + e < 62 ? (e - 26).toString(36).toUpperCase()
185 + : e > 62 ? "-" : "_"), "");
265 186
266 - // build function
267 - fields_count = fields.length;
268 - if (!fields_count) {
269 - return null;
270 - } else if (fields_count === 1) {
271 - field = fields[0].field;
272 - multiplier = multipliers[0];
273 - return function(a, b) {
274 - return multiplier * cmp(
275 - get_field(field, a),
276 - get_field(field, b)
277 - );
278 - };
279 - } else {
280 - return function(a, b) {
281 - var i, result, a_value, b_value, field;
282 - for (i = 0; i < fields_count; i++) {
283 - field = fields[i].field;
284 - result = multipliers[i] * cmp(
285 - get_field(field, a),
286 - get_field(field, b)
287 - );
288 - if (result) return result;
289 - }
290 - return 0;
291 - };
292 - }
293 - };
294 187
295 - /**
296 - * Parses a search query and returns an object
297 - * with tokens and fields ready to be populated
298 - * with results.
299 - *
300 - * @param {string} query
301 - * @param {object} options
302 - * @returns {object}
303 - */
304 - Sifter.prototype.prepareSearch = function(query, options) {
305 - if (typeof query === 'object') return query;
188 +var Sifter = function (items, settings) {
189 + this.items = items;
190 + this.settings = settings || { diacritics: true };
191 +};
306 192
307 - options = extend({}, options);
193 +Sifter.prototype.tokenize = function (query, respect_word_boundaries) {
194 + query = trim(String(query || '').toLowerCase());
195 + if (!query || !query.length) return [];
308 196
309 - var option_fields = options.fields;
310 - var option_sort = options.sort;
311 - var option_sort_empty = options.sort_empty;
197 + var i, n, regex, letter;
198 + var tokens = [];
199 + var words = query.split(/ +/);
312 200
313 - if (option_fields && !is_array(option_fields)) options.fields = [option_fields];
314 - if (option_sort && !is_array(option_sort)) options.sort = [option_sort];
315 - if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty];
201 + for (i = 0, n = words.length; i < n; i++) {
202 + regex = escape_regex(words[i]);
203 + if (this.settings.diacritics) {
204 + for (letter in DIACRITICS) {
205 + if (DIACRITICS.hasOwnProperty(letter)) {
206 + regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]);
207 + }
208 + }
209 + }
210 + if (respect_word_boundaries) regex = "\\b" + regex
211 + tokens.push({
212 + string: words[i],
213 + regex: new RegExp(regex, 'i')
214 + });
215 + }
316 216
317 - return {
318 - options : options,
319 - query : String(query || '').toLowerCase(),
320 - tokens : this.tokenize(query),
321 - total : 0,
322 - items : []
323 - };
324 - };
217 + return tokens;
218 +};
325 219
326 - /**
327 - * Searches through all items and returns a sorted array of matches.
328 - *
329 - * The `options` parameter can contain:
330 - *
331 - * - fields {string|array}
332 - * - sort {array}
333 - * - score {function}
334 - * - filter {bool}
335 - * - limit {integer}
336 - *
337 - * Returns an object containing:
338 - *
339 - * - options {object}
340 - * - query {string}
341 - * - tokens {array}
342 - * - total {int}
343 - * - items {array}
344 - *
345 - * @param {string} query
346 - * @param {object} options
347 - * @returns {object}
348 - */
349 - Sifter.prototype.search = function(query, options) {
350 - var self = this, value, score, search, calculateScore;
351 - var fn_sort;
352 - var fn_score;
220 +Sifter.prototype.iterator = function (object, callback) {
221 + var iterator;
222 + if (is_array(object)) {
223 + iterator = Array.prototype.forEach || function (callback) {
224 + for (var i = 0, n = this.length; i < n; i++) {
225 + callback(this[i], i, this);
226 + }
227 + };
228 + } else {
229 + iterator = function (callback) {
230 + for (var key in this) {
231 + if (this.hasOwnProperty(key)) {
232 + callback(this[key], key, this);
233 + }
234 + }
235 + };
236 + }
353 237
354 - search = this.prepareSearch(query, options);
355 - options = search.options;
356 - query = search.query;
238 + iterator.apply(object, [callback]);
239 +};
357 240
358 - // generate result scoring function
359 - fn_score = options.score || self.getScoreFunction(search);
241 +Sifter.prototype.getScoreFunction = function (search, options) {
242 + var self, fields, tokens, token_count, nesting;
360 243
361 - // perform search and sort
362 - if (query.length) {
363 - self.iterator(self.items, function(item, id) {
364 - score = fn_score(item);
365 - if (options.filter === false || score > 0) {
366 - search.items.push({'score': score, 'id': id});
367 - }
368 - });
369 - } else {
370 - self.iterator(self.items, function(item, id) {
371 - search.items.push({'score': 1, 'id': id});
372 - });
244 + self = this;
245 + search = self.prepareSearch(search, options);
246 + tokens = search.tokens;
247 + fields = search.options.fields;
248 + token_count = tokens.length;
249 + nesting = search.options.nesting;
250 +
251 + var scoreValue = function (value, token) {
252 + var score, pos;
253 +
254 + if (!value) return 0;
255 + value = String(value || '');
256 + pos = value.search(token.regex);
257 + if (pos === -1) return 0;
258 + score = token.string.length / value.length;
259 + if (pos === 0) score += 0.5;
260 + return score;
261 + };
262 +
263 + var scoreObject = (function () {
264 + var field_count = fields.length;
265 + if (!field_count) {
266 + return function () { return 0; };
267 + }
268 + if (field_count === 1) {
269 + return function (token, data) {
270 + return scoreValue(getattr(data, fields[0], nesting), token);
271 + };
272 + }
273 + return function (token, data) {
274 + for (var i = 0, sum = 0; i < field_count; i++) {
275 + sum += scoreValue(getattr(data, fields[i], nesting), token);
276 + }
277 + return sum / field_count;
278 + };
279 + })();
280 +
281 + if (!token_count) {
282 + return function () { return 0; };
283 + }
284 + if (token_count === 1) {
285 + return function (data) {
286 + return scoreObject(tokens[0], data);
287 + };
288 + }
289 +
290 + if (search.options.conjunction === 'and') {
291 + return function (data) {
292 + var score;
293 + for (var i = 0, sum = 0; i < token_count; i++) {
294 + score = scoreObject(tokens[i], data);
295 + if (score <= 0) return 0;
296 + sum += score;
297 + }
298 + return sum / token_count;
299 + };
300 + } else {
301 + return function (data) {
302 + for (var i = 0, sum = 0; i < token_count; i++) {
303 + sum += scoreObject(tokens[i], data);
304 + }
305 + return sum / token_count;
306 + };
307 + }
308 +};
309 +
310 +Sifter.prototype.getSortFunction = function (search, options) {
311 + var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort;
312 +
313 + self = this;
314 + search = self.prepareSearch(search, options);
315 + sort = (!search.query && options.sort_empty) || options.sort;
316 +
317 + get_field = function (name, result) {
318 + if (name === '$score') return result.score;
319 + return getattr(self.items[result.id], name, options.nesting);
320 + };
321 +
322 + fields = [];
323 + if (sort) {
324 + for (i = 0, n = sort.length; i < n; i++) {
325 + if (search.query || sort[i].field !== '$score') {
326 + fields.push(sort[i]);
327 + }
328 + }
329 + }
330 +
331 + if (search.query) {
332 + implicit_score = true;
333 + for (i = 0, n = fields.length; i < n; i++) {
334 + if (fields[i].field === '$score') {
335 + implicit_score = false;
336 + break;
337 + }
338 + }
339 + if (implicit_score) {
340 + fields.unshift({ field: '$score', direction: 'desc' });
341 + }
342 + } else {
343 + for (i = 0, n = fields.length; i < n; i++) {
344 + if (fields[i].field === '$score') {
345 + fields.splice(i, 1);
346 + break;
347 + }
348 + }
349 + }
350 +
351 + multipliers = [];
352 + for (i = 0, n = fields.length; i < n; i++) {
353 + multipliers.push(fields[i].direction === 'desc' ? -1 : 1);
354 + }
355 +
356 + fields_count = fields.length;
357 + if (!fields_count) {
358 + return null;
359 + } else if (fields_count === 1) {
360 + field = fields[0].field;
361 + multiplier = multipliers[0];
362 + return function (a, b) {
363 + return multiplier * cmp(
364 + get_field(field, a),
365 + get_field(field, b)
366 + );
367 + };
368 + } else {
369 + return function (a, b) {
370 + var i, result, a_value, b_value, field;
371 + for (i = 0; i < fields_count; i++) {
372 + field = fields[i].field;
373 + result = multipliers[i] * cmp(
374 + get_field(field, a),
375 + get_field(field, b)
376 + );
377 + if (result) return result;
378 + }
379 + return 0;
380 + };
381 + }
382 +};
383 +
384 +Sifter.prototype.prepareSearch = function (query, options) {
385 + if (typeof query === 'object') return query;
386 +
387 + options = extend({}, options);
388 +
389 + var option_fields = options.fields;
390 + var option_sort = options.sort;
391 + var option_sort_empty = options.sort_empty;
392 +
393 + if (option_fields && !is_array(option_fields)) options.fields = [option_fields];
394 + if (option_sort && !is_array(option_sort)) options.sort = [option_sort];
395 + if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty];
396 +
397 + return {
398 + options: options,
399 + query: String(query || '').toLowerCase(),
400 + tokens: this.tokenize(query, options.respect_word_boundaries),
401 + total: 0,
402 + items: []
403 + };
404 +};
405 +
406 +Sifter.prototype.search = function (query, options) {
407 + var self = this, value, score, search, calculateScore;
408 + var fn_sort;
409 + var fn_score;
410 +
411 + search = this.prepareSearch(query, options);
412 + options = search.options;
413 + query = search.query;
414 +
415 + fn_score = options.score || self.getScoreFunction(search);
416 +
417 + if (query.length) {
418 + self.iterator(self.items, function (item, id) {
419 + score = fn_score(item);
420 + if (options.filter === false || score > 0) {
421 + search.items.push({ 'score': score, 'id': id });
422 + }
423 + });
424 + } else {
425 + self.iterator(self.items, function (item, id) {
426 + search.items.push({ 'score': 1, 'id': id });
427 + });
428 + }
429 +
430 + fn_sort = self.getSortFunction(search, options);
431 + if (fn_sort) search.items.sort(fn_sort);
432 +
433 + search.total = search.items.length;
434 + if (typeof options.limit === 'number') {
435 + search.items = search.items.slice(0, options.limit);
436 + }
437 +
438 + return search;
439 +};
440 +
441 +var cmp = function (a, b) {
442 + if (typeof a === 'number' && typeof b === 'number') {
443 + return a > b ? 1 : (a < b ? -1 : 0);
444 + }
445 + a = asciifold(String(a || ''));
446 + b = asciifold(String(b || ''));
447 + if (a > b) return 1;
448 + if (b > a) return -1;
449 + return 0;
450 +};
451 +
452 +var extend = function (a, b) {
453 + var i, n, k, object;
454 + for (i = 1, n = arguments.length; i < n; i++) {
455 + object = arguments[i];
456 + if (!object) continue;
457 + for (k in object) {
458 + if (object.hasOwnProperty(k)) {
459 + a[k] = object[k];
460 + }
461 + }
462 + }
463 + return a;
464 +};
465 +
466 +var getattr = function (obj, name, nesting) {
467 + if (!obj || !name) return;
468 + if (!nesting) return obj[name];
469 + var names = name.split(".");
470 + while (names.length && (obj = obj[names.shift()]));
471 + return obj;
472 +};
473 +
474 +var trim = function (str) {
475 + return (str + '').replace(/^\s+|\s+$|/g, '');
476 +};
477 +
478 +var escape_regex = function (str) {
479 + return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
480 +};
481 +
482 +var is_array = Array.isArray || function (object) {
483 + return Object.prototype.toString.call(object) === '[object Array]';
484 +};
485 +
486 +var DIACRITICS = {
487 + 'a': '[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]',
488 + 'b': '[b␢βΒB฿𐌁ᛒ]',
489 + 'c': '[cĆćĈĉČčĊċC̄c̄ÇçḈḉȻȼƇƈɕᴄCc]',
490 + 'd': '[dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]',
491 + 'e': '[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀềỄễỂểḜḝḖḗḔḕȆȇẸẹỆệⱸᴇEeɘǝƏƐε]',
492 + 'f': '[fƑƒḞḟ]',
493 + 'g': '[gɢ₲ǤǥĜĝĞğĢģƓɠĠġ]',
494 + 'h': '[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]',
495 + 'i': '[iÍíÌìĬĭÎîǏǐÏïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨɨ̆ᵻᶖİiIıɪIi]',
496 + 'j': '[jȷĴĵɈɉʝɟʲ]',
497 + 'k': '[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭]',
498 + 'l': '[lŁłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]',
499 + 'n': '[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]',
500 + 'o': '[oØøÖöÓóÒòÔôǑǒŐőŎŏȮȯỌọƟɵƠơỎỏŌōÕõǪǫȌȍՕօ]',
501 + 'p': '[pṔṕṖṗⱣᵽƤƥᵱ]',
502 + 'q': '[qꝖꝗʠɊɋꝘꝙq̃]',
503 + 'r': '[rŔŕɌɍŘřŖŗṘṙȐȑȒȓṚṛⱤɽ]',
504 + 's': '[sŚśṠṡṢṣꞨꞩŜŝŠšŞşȘșS̈s̈]',
505 + 't': '[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]',
506 + 'u': '[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]',
507 + 'v': '[vṼṽṾṿƲʋꝞꝟⱱʋ]',
508 + 'w': '[wẂẃẀẁŴŵẄẅẆẇẈẉ]',
509 + 'x': '[xẌẍẊẋχ]',
510 + 'y': '[yÝýỲỳŶŷŸÿỸỹẎẏỴỵɎɏƳƴ]',
511 + 'z': '[zŹźẐẑŽžŻżẒẓẔẕƵƶ]'
512 +};
513 +
514 +var asciifold = (function () {
515 + var i, n, k, chunk;
516 + var i18nChars = '';
517 + var lookup = {};
518 + for (k in DIACRITICS) {
519 + if (DIACRITICS.hasOwnProperty(k)) {
520 + chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);
521 + i18nChars += chunk;
522 + for (i = 0, n = chunk.length; i < n; i++) {
523 + lookup[chunk.charAt(i)] = k;
524 + }
525 + }
526 + }
527 + var regexp = new RegExp('[' + i18nChars + ']', 'g');
528 + return function (str) {
529 + return str.replace(regexp, function (i18nChar) {
530 + return lookup[i18nChar];
531 + }).toLowerCase();
532 + };
533 +})();
534 +
535 +var IS_MAC = uaDetect("macOS", /Mac/);
536 +var KEY_A = 65;
537 +var KEY_COMMA = 188;
538 +var KEY_RETURN = 13;
539 +var KEY_ESC = 27;
540 +var KEY_LEFT = 37;
541 +var KEY_UP = 38;
542 +var KEY_P = 80;
543 +var KEY_RIGHT = 39;
544 +var KEY_DOWN = 40;
545 +var KEY_N = 78;
546 +var KEY_BACKSPACE = 8;
547 +var KEY_DELETE = 46;
548 +var KEY_SHIFT = 16;
549 +var KEY_CMD = IS_MAC ? 91 : 17;
550 +var KEY_CTRL = IS_MAC ? 18 : 17;
551 +var KEY_TAB = 9;
552 +var TAG_SELECT = 1;
553 +var TAG_INPUT = 2;
554 +
555 +var SUPPORTS_VALIDITY_API = !uaDetect("Android", /android/i) && !!document.createElement('input').validity;
556 +
557 +var isset = function (object) {
558 + return typeof object !== 'undefined';
559 +};
560 +
561 +var isArray = Array.isArray || function (vArg) {
562 + return Object.prototype.toString.call(vArg) === '[object Array]';
563 +}
564 +
565 +var hash_key = function (value) {
566 + if (typeof value === 'undefined' || value === null) return null;
567 + if (typeof value === 'boolean') return value ? '1' : '0';
568 + return value + '';
569 +};
570 +
571 +var escape_html = function (str) {
572 + return (str + '')
573 + .replace(/&/g, '&amp;')
574 + .replace(/</g, '&lt;')
575 + .replace(/>/g, '&gt;')
576 + .replace(/"/g, '&quot;');
577 +};
578 +
579 +var escape_replace = function (str) {
580 + return (str + '').replace(/\$/g, '$$$$');
581 +};
582 +
583 +var hook = {};
584 +
585 +hook.before = function (self, method, fn) {
586 + var original = self[method];
587 + self[method] = function () {
588 + fn.apply(self, arguments);
589 + return original.apply(self, arguments);
590 + };
591 +};
592 +
593 +hook.after = function (self, method, fn) {
594 + var original = self[method];
595 + self[method] = function () {
596 + var result = original.apply(self, arguments);
597 + fn.apply(self, arguments);
598 + return result;
599 + };
600 +};
601 +
602 +var once = function (fn) {
603 + var called = false;
604 + return function () {
605 + if (called) return;
606 + called = true;
607 + fn.apply(this, arguments);
608 + };
609 +};
610 +
611 +var debounce = function (fn, delay) {
612 + var timeout;
613 + return function () {
614 + var self = this;
615 + var args = arguments;
616 + window.clearTimeout(timeout);
617 + timeout = window.setTimeout(function () {
618 + fn.apply(self, args);
619 + }, delay);
620 + };
621 +};
622 +
623 +var debounce_events = function (self, types, fn) {
624 + var type;
625 + var trigger = self.trigger;
626 + var event_args = {};
627 +
628 + self.trigger = function () {
629 + var type = arguments[0];
630 + if (types.indexOf(type) !== -1) {
631 + event_args[type] = arguments;
632 + } else {
633 + return trigger.apply(self, arguments);
634 + }
635 + };
636 +
637 + fn.apply(self, []);
638 + self.trigger = trigger;
639 +
640 + for (type in event_args) {
641 + if (event_args.hasOwnProperty(type)) {
642 + trigger.apply(self, event_args[type]);
643 + }
644 + }
645 +};
646 +
647 +var watchChildEvent = function ($parent, event, selector, fn) {
648 + $parent.on(event, selector, function (e) {
649 + var child = e.target;
650 + while (child && child.parentNode !== $parent[0]) {
651 + child = child.parentNode;
652 + }
653 + e.currentTarget = child;
654 + return fn.apply(this, [e]);
655 + });
656 +};
657 +
658 +var getInputSelection = function (input) {
659 + var result = {};
660 + if (input === undefined) {
661 + console.warn('WARN getInputSelection cannot locate input control');
662 + return result;
663 + }
664 + if ('selectionStart' in input) {
665 + result.start = input.selectionStart;
666 + result.length = input.selectionEnd - result.start;
667 + } else if (document.selection) {
668 + input.focus();
669 + var sel = document.selection.createRange();
670 + var selLen = document.selection.createRange().text.length;
671 + sel.moveStart('character', -input.value.length);
672 + result.start = sel.text.length - selLen;
673 + result.length = selLen;
674 + }
675 + return result;
676 +};
677 +
678 +var transferStyles = function ($from, $to, properties) {
679 + var i, n, styles = {};
680 + if (properties) {
681 + for (i = 0, n = properties.length; i < n; i++) {
682 + styles[properties[i]] = $from.css(properties[i]);
683 + }
684 + } else {
685 + styles = $from.css();
686 + }
687 + $to.css(styles);
688 +};
689 +
690 +var measureString = function (str, $parent) {
691 + if (!str) {
692 + return 0;
693 + }
694 +
695 + if (!Selectize.$testInput) {
696 + Selectize.$testInput = $('<span />').css({
697 + position: 'absolute',
698 + width: 'auto',
699 + padding: 0,
700 + whiteSpace: 'pre'
701 + });
702 +
703 + $('<div />').css({
704 + position: 'absolute',
705 + width: 0,
706 + height: 0,
707 + overflow: 'hidden'
708 + }).attr({
709 + 'aria-hidden': true
710 + }).append(Selectize.$testInput).appendTo('body');
711 + }
712 +
713 + Selectize.$testInput.text(str);
714 +
715 + transferStyles($parent, Selectize.$testInput, [
716 + 'letterSpacing',
717 + 'fontSize',
718 + 'fontFamily',
719 + 'fontWeight',
720 + 'textTransform'
721 + ]);
722 +
723 + return Selectize.$testInput.width();
724 +};
725 +
726 +var autoGrow = function ($input) {
727 + var currentWidth = null;
728 +
729 + var update = function (e, options) {
730 + var value, keyCode, printable, width;
731 + var placeholder, placeholderWidth;
732 + var shift, character, selection;
733 + e = e || window.event || {};
734 + options = options || {};
735 +
736 + if (e.metaKey || e.altKey) return;
737 + if (!options.force && $input.data('grow') === false) return;
738 +
739 + value = $input.val();
740 + if (e.type && e.type.toLowerCase() === 'keydown') {
741 + keyCode = e.keyCode;
742 + printable = (
743 + (keyCode >= 48 && keyCode <= 57) ||
744 + (keyCode >= 65 && keyCode <= 90) ||
745 + (keyCode >= 96 && keyCode <= 111) ||
746 + (keyCode >= 186 && keyCode <= 222) ||
747 + keyCode === 32
748 + );
749 +
750 + if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {
751 + selection = getInputSelection($input[0]);
752 + if (selection.length) {
753 + value = value.substring(0, selection.start) + value.substring(selection.start + selection.length);
754 + } else if (keyCode === KEY_BACKSPACE && selection.start) {
755 + value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);
756 + } else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {
757 + value = value.substring(0, selection.start) + value.substring(selection.start + 1);
758 + }
759 + } else if (printable) {
760 + shift = e.shiftKey;
761 + character = String.fromCharCode(e.keyCode);
762 + if (shift) character = character.toUpperCase();
763 + else character = character.toLowerCase();
764 + value += character;
765 + }
766 + }
767 +
768 + var width = $input.attr('readonly') ? 0 : 4;
769 + placeholder = $input.attr('placeholder');
770 + if (placeholder) {
771 + placeholderWidth = measureString(placeholder, $input) + width;
772 + } else {
773 + placeholderWidth = 0;
774 + }
775 +
776 + width = Math.max(measureString(value, $input), placeholderWidth) + width;
777 + if (width !== currentWidth) {
778 + currentWidth = width;
779 + $input.width(width);
780 + $input.triggerHandler('resize');
781 + }
782 + };
783 +
784 + $input.on('keydown keyup update blur', update);
785 + update();
786 +};
787 +
788 +var domToString = function (d) {
789 + var tmp = document.createElement('div');
790 +
791 + tmp.appendChild(d.cloneNode(true));
792 +
793 + return tmp.innerHTML;
794 +};
795 +
796 +var logError = function (message, options) {
797 + if (!options) options = {};
798 + var component = "Selectize";
799 +
800 + console.error(component + ": " + message)
801 +
802 + if (options.explanation) {
803 + if (console.group) console.group();
804 + console.error(options.explanation);
805 + if (console.group) console.groupEnd();
806 + }
807 +};
808 +
809 +var isJSON = function (data) {
810 + try {
811 + JSON.parse(data);
812 + } catch (e) {
813 + return false;
814 + }
815 + return true;
816 +};
817 +
818 +function uaDetect(platform, re) {
819 + if (navigator.userAgentData) {
820 + return platform === navigator.userAgentData.platform;
821 + }
822 +
823 + return re.test(navigator.userAgent);
824 +}
825 +
826 +var Selectize = function($input, settings) {
827 + var key, i, n, dir, input, self = this;
828 + input = $input[0];
829 + input.selectize = self;
830 +
831 + var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null);
832 + dir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction;
833 + dir = dir || $input.parents('[dir]:first').attr('dir') || '';
834 +
835 + self.settings = {};
836 +
837 + $.extend(self, {
838 + order : 0,
839 + settings : settings,
840 + $input : $input,
841 + tabIndex : $input.attr('tabindex') || '',
842 + tagType : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT,
843 + rtl : /rtl/i.test(dir),
844 +
845 + eventNS : '.selectize' + (++Selectize.count),
846 + highlightedValue : null,
847 + isBlurring : false,
848 + isOpen : false,
849 + isDisabled : false,
850 + isRequired : $input.is('[required]'),
851 + isInvalid : false,
852 + isLocked : false,
853 + isFocused : false,
854 + isInputHidden : false,
855 + isSetup : false,
856 + isShiftDown : false,
857 + isCmdDown : false,
858 + isCtrlDown : false,
859 + ignoreFocus : false,
860 + ignoreBlur : false,
861 + ignoreHover : false,
862 + hasOptions : false,
863 + currentResults : null,
864 + lastValue : '',
865 + lastValidValue : '',
866 + lastOpenTarget : false,
867 + caretPos : 0,
868 + loading : 0,
869 + loadedSearches : {},
870 + isDropdownClosing: false,
871 +
872 + $activeOption : null,
873 + $activeItems : [],
874 +
875 + optgroups : {},
876 + options : {},
877 + userOptions : {},
878 + items : [],
879 + renderCache : {},
880 + onSearchChange : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle)
881 + });
882 +
883 + self.sifter = new Sifter(this.options, {diacritics: settings.diacritics});
884 +
885 + if (self.settings.options) {
886 + for (i = 0, n = self.settings.options.length; i < n; i++) {
887 + self.registerOption(self.settings.options[i]);
373 888 }
889 + delete self.settings.options;
890 + }
374 891
375 - fn_sort = self.getSortFunction(search, options);
376 - if (fn_sort) search.items.sort(fn_sort);
892 + if (self.settings.optgroups) {
893 + for (i = 0, n = self.settings.optgroups.length; i < n; i++) {
894 + self.registerOptionGroup(self.settings.optgroups[i]);
895 + }
896 + delete self.settings.optgroups;
897 + }
377 898
378 - // apply limits
379 - search.total = search.items.length;
380 - if (typeof options.limit === 'number') {
381 - search.items = search.items.slice(0, options.limit);
382 - }
899 + self.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi');
900 + if (typeof self.settings.hideSelected !== 'boolean') {
901 + self.settings.hideSelected = self.settings.mode === 'multi';
902 + }
383 903
384 - return search;
385 - };
904 + self.initializePlugins(self.settings.plugins);
905 + self.setupCallbacks();
906 + self.setupTemplates();
907 + self.setup();
908 +};
386 909
387 - // utilities
388 - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
910 +MicroEvent.mixin(Selectize);
911 +MicroPlugin.mixin(Selectize);
389 912
390 - var cmp = function(a, b) {
391 - if (typeof a === 'number' && typeof b === 'number') {
392 - return a > b ? 1 : (a < b ? -1 : 0);
913 +$.extend(Selectize.prototype, {
914 +
915 + setup: function() {
916 + var self = this;
917 + var settings = self.settings;
918 + var eventNS = self.eventNS;
919 + var $window = $(window);
920 + var $document = $(document);
921 + var $input = self.$input;
922 +
923 + var $wrapper;
924 + var $control;
925 + var $control_input;
926 + var $dropdown;
927 + var $dropdown_content;
928 + var $dropdown_parent;
929 + var inputMode;
930 + var timeout_blur;
931 + var timeout_focus;
932 + var classes;
933 + var classes_plugins;
934 + var inputId;
935 + var noArrowClass;
936 +
937 + inputMode = self.settings.mode;
938 + classes = $input.attr('class') || '';
939 + noArrowClass = settings.showArrow ? '' : ' no-arrow';
940 +
941 + $wrapper = $('<div>').addClass(settings.wrapperClass).addClass(classes + ' selectize-control').addClass(inputMode);
942 + $control = $('<div>').addClass(settings.inputClass + noArrowClass + ' selectize-input items').appendTo($wrapper);
943 + $control_input = $('<input type="text" autocomplete="new-password" autofill="no" />').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);
944 + $dropdown_parent = $(settings.dropdownParent || $wrapper);
945 + $dropdown = $('<div>').addClass(settings.dropdownClass).addClass(inputMode + ' selectize-dropdown').hide().appendTo($dropdown_parent);
946 + $dropdown_content = $('<div>').addClass(settings.dropdownContentClass + ' selectize-dropdown-content').attr('tabindex', '-1').appendTo($dropdown);
947 +
948 + if(inputId = $input.attr('id')) {
949 + $control_input.attr('id', inputId + '-selectized');
950 + $("label[for='"+inputId+"']").attr('for', inputId + '-selectized');
393 951 }
394 - a = asciifold(String(a || ''));
395 - b = asciifold(String(b || ''));
396 - if (a > b) return 1;
397 - if (b > a) return -1;
398 - return 0;
399 - };
400 952
401 - var extend = function(a, b) {
402 - var i, n, k, object;
403 - for (i = 1, n = arguments.length; i < n; i++) {
404 - object = arguments[i];
405 - if (!object) continue;
406 - for (k in object) {
407 - if (object.hasOwnProperty(k)) {
408 - a[k] = object[k];
409 - }
410 - }
953 + if(self.settings.copyClassesToDropdown) {
954 + $dropdown.addClass(classes);
411 955 }
412 - return a;
413 - };
414 956
415 - var trim = function(str) {
416 - return (str + '').replace(/^\s+|\s+$|/g, '');
417 - };
957 + $wrapper.css({
958 + width: $input[0].style.width
959 + });
418 960
419 - var escape_regex = function(str) {
420 - return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
421 - };
961 + if (self.plugins.names.length) {
962 + classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
963 + $wrapper.addClass(classes_plugins);
964 + $dropdown.addClass(classes_plugins);
965 + }
422 966
423 - var is_array = Array.isArray || ($ && $.isArray) || function(object) {
424 - return Object.prototype.toString.call(object) === '[object Array]';
425 - };
967 + if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {
968 + $input.attr('multiple', 'multiple');
969 + }
426 970
427 - var DIACRITICS = {
428 - 'a': '[aÀÁÂÃÄÅàáâãäåĀāąĄ]',
429 - 'c': '[cÇçćĆčČ]',
430 - 'd': '[dđĐďĎ]',
431 - 'e': '[eÈÉÊËèéêëěĚĒēęĘ]',
432 - 'i': '[iÌÍÎÏìíîïĪī]',
433 - 'l': '[lłŁ]',
434 - 'n': '[nÑñňŇńŃ]',
435 - 'o': '[oÒÓÔÕÕÖØòóôõöøŌō]',
436 - 'r': '[rřŘ]',
437 - 's': '[sŠšśŚ]',
438 - 't': '[tťŤ]',
439 - 'u': '[uÙÚÛÜùúûüůŮŪū]',
440 - 'y': '[yŸÿýÝ]',
441 - 'z': '[zŽžżŻźŹ]'
442 - };
443 -
444 - var asciifold = (function() {
445 - var i, n, k, chunk;
446 - var foreignletters = '';
447 - var lookup = {};
448 - for (k in DIACRITICS) {
449 - if (DIACRITICS.hasOwnProperty(k)) {
450 - chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);
451 - foreignletters += chunk;
452 - for (i = 0, n = chunk.length; i < n; i++) {
453 - lookup[chunk.charAt(i)] = k;
454 - }
455 - }
971 + if (self.settings.placeholder) {
972 + $control_input.attr('placeholder', settings.placeholder);
456 973 }
457 - var regexp = new RegExp('[' + foreignletters + ']', 'g');
458 - return function(str) {
459 - return str.replace(regexp, function(foreignletter) {
460 - return lookup[foreignletter];
461 - }).toLowerCase();
462 - };
463 - })();
464 974
975 + if (!self.settings.search) {
976 + $control_input.attr('readonly', true);
977 + $control_input.attr('inputmode', 'none');
978 + $control.css('cursor', 'pointer');
979 + }
465 980
466 - // export
467 - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
981 + if (!self.settings.splitOn && self.settings.delimiter) {
982 + var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
983 + self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*');
984 + }
468 985
469 - return Sifter;
470 -}));
986 + if ($input.attr('autocorrect')) {
987 + $control_input.attr('autocorrect', $input.attr('autocorrect'));
988 + }
471 989
990 + if ($input.attr('autocapitalize')) {
991 + $control_input.attr('autocapitalize', $input.attr('autocapitalize'));
992 + }
993 + if ($input.is('input')) {
994 + $control_input[0].type = $input[0].type;
995 + }
472 996
997 + self.$wrapper = $wrapper;
998 + self.$control = $control;
999 + self.$control_input = $control_input;
1000 + self.$dropdown = $dropdown;
1001 + self.$dropdown_content = $dropdown_content;
473 1002
474 -/**
475 - * microplugin.js
476 - * Copyright (c) 2013 Brian Reavis & contributors
477 - *
478 - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
479 - * file except in compliance with the License. You may obtain a copy of the License at:
480 - * http://www.apache.org/licenses/LICENSE-2.0
481 - *
482 - * Unless required by applicable law or agreed to in writing, software distributed under
483 - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
484 - * ANY KIND, either express or implied. See the License for the specific language
485 - * governing permissions and limitations under the License.
486 - *
487 - * @author Brian Reavis <brian@thirdroute.com>
488 - */
1003 + $dropdown.on('mouseenter mousedown mouseup click', '[data-disabled]>[data-selectable]', function(e) { e.stopImmediatePropagation(); });
1004 + $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
1005 + $dropdown.on('mouseup click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
1006 + watchChildEvent($control, 'mouseup', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
1007 + autoGrow($control_input);
489 1008
490 -(function(root, factory) {
491 - if (typeof define === 'function' && define.amd) {
492 - define('microplugin', factory);
493 - } else if (typeof exports === 'object') {
494 - module.exports = factory();
495 - } else {
496 - root.MicroPlugin = factory();
497 - }
498 -}(this, function() {
499 - var MicroPlugin = {};
1009 + $control.on({
1010 + mousedown : function() { return self.onMouseDown.apply(self, arguments); },
1011 + click : function() { return self.onClick.apply(self, arguments); }
1012 + });
500 1013
501 - MicroPlugin.mixin = function(Interface) {
502 - Interface.plugins = {};
1014 + $control_input.on({
1015 + mousedown : function(e) {
1016 + if (self.$control_input.val() !== '' || self.settings.openOnFocus) {
1017 + e.stopPropagation();
1018 + }
1019 + },
1020 + keydown : function() { return self.onKeyDown.apply(self, arguments); },
1021 + keypress : function() { return self.onKeyPress.apply(self, arguments); },
1022 + input : function() { return self.onInput.apply(self, arguments); },
1023 + resize : function() { self.positionDropdown.apply(self, []); },
1024 + blur : function() { return self.onBlur.apply(self, arguments); },
1025 + focus : function() { return self.onFocus.apply(self, arguments); },
1026 + paste : function() { return self.onPaste.apply(self, arguments); }
1027 + });
503 1028
504 - /**
505 - * Initializes the listed plugins (with options).
506 - * Acceptable formats:
507 - *
508 - * List (without options):
509 - * ['a', 'b', 'c']
510 - *
511 - * List (with options):
512 - * [{'name': 'a', options: {}}, {'name': 'b', options: {}}]
513 - *
514 - * Hash (with options):
515 - * {'a': { ... }, 'b': { ... }, 'c': { ... }}
516 - *
517 - * @param {mixed} plugins
518 - */
519 - Interface.prototype.initializePlugins = function(plugins) {
520 - var i, n, key;
521 - var self = this;
522 - var queue = [];
1029 + $document.on('keydown' + eventNS, function(e) {
1030 + self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
1031 + self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
1032 + self.isShiftDown = e.shiftKey;
1033 + });
523 1034
524 - self.plugins = {
525 - names : [],
526 - settings : {},
527 - requested : {},
528 - loaded : {}
529 - };
1035 + $document.on('keyup' + eventNS, function(e) {
1036 + if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
1037 + if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
1038 + if (e.keyCode === KEY_CMD) self.isCmdDown = false;
1039 + });
530 1040
531 - if (utils.isArray(plugins)) {
532 - for (i = 0, n = plugins.length; i < n; i++) {
533 - if (typeof plugins[i] === 'string') {
534 - queue.push(plugins[i]);
535 - } else {
536 - self.plugins.settings[plugins[i].name] = plugins[i].options;
537 - queue.push(plugins[i].name);
538 - }
1041 + $document.on('mousedown' + eventNS, function(e) {
1042 + if (self.isFocused) {
1043 + if (
1044 + e.target === self.$dropdown[0] ||
1045 + self.$dropdown.has(e.target).length)
1046 + {
1047 + return false;
539 1048 }
540 - } else if (plugins) {
541 - for (key in plugins) {
542 - if (plugins.hasOwnProperty(key)) {
543 - self.plugins.settings[key] = plugins[key];
544 - queue.push(key);
545 - }
1049 + if (e.target !== self.$control[0]) {
1050 + self.blur(e.target);
546 1051 }
547 1052 }
1053 + });
548 1054
549 - while (queue.length) {
550 - self.require(queue.shift());
1055 + $window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {
1056 + if (self.isOpen) {
1057 + self.positionDropdown.apply(self, arguments);
551 1058 }
552 - };
1059 + });
1060 + $window.on('mousemove' + eventNS, function() {
1061 + self.ignoreHover = self.settings.ignoreHover;
1062 + });
553 1063
554 - Interface.prototype.loadPlugin = function(name) {
555 - var self = this;
556 - var plugins = self.plugins;
557 - var plugin = Interface.plugins[name];
1064 + var inputPlaceholder = $('<div></div>');
1065 + var inputChildren = $input.children().detach();
558 1066
559 - if (!Interface.plugins.hasOwnProperty(name)) {
560 - throw new Error('Unable to find "' + name + '" plugin');
561 - }
1067 + $input.replaceWith(inputPlaceholder);
1068 + inputPlaceholder.replaceWith($input);
562 1069
563 - plugins.requested[name] = true;
564 - plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]);
565 - plugins.names.push(name);
1070 + this.revertSettings = {
1071 + $children : inputChildren,
1072 + tabindex : $input.attr('tabindex')
566 1073 };
567 1074
568 - /**
569 - * Initializes a plugin.
570 - *
571 - * @param {string} name
572 - */
573 - Interface.prototype.require = function(name) {
574 - var self = this;
575 - var plugins = self.plugins;
1075 + $input.attr('tabindex', -1).hide().after(self.$wrapper);
576 1076
577 - if (!self.plugins.loaded.hasOwnProperty(name)) {
578 - if (plugins.requested[name]) {
579 - throw new Error('Plugin has circular dependency ("' + name + '")');
580 - }
581 - self.loadPlugin(name);
582 - }
1077 + if (Array.isArray(settings.items)) {
1078 + self.lastValidValue = settings.items;
1079 + self.setValue(settings.items);
1080 + delete settings.items;
1081 + }
583 1082
584 - return plugins.loaded[name];
585 - };
1083 + if (SUPPORTS_VALIDITY_API) {
1084 + $input.on('invalid' + eventNS, function(e) {
1085 + e.preventDefault();
1086 + self.isInvalid = true;
1087 + self.refreshState();
1088 + });
1089 + }
586 1090
587 - /**
588 - * Registers a plugin.
589 - *
590 - * @param {string} name
591 - * @param {function} fn
592 - */
593 - Interface.define = function(name, fn) {
594 - Interface.plugins[name] = {
595 - 'name' : name,
596 - 'fn' : fn
597 - };
598 - };
599 - };
1091 + self.updateOriginalInput();
1092 + self.refreshItems();
1093 + self.refreshState();
1094 + self.updatePlaceholder();
1095 + self.isSetup = true;
600 1096
601 - var utils = {
602 - isArray: Array.isArray || function(vArg) {
603 - return Object.prototype.toString.call(vArg) === '[object Array]';
1097 + if ($input.is(':disabled')) {
1098 + self.disable();
604 1099 }
605 - };
606 1100
607 - return MicroPlugin;
608 -}));
1101 + self.on('change', this.onChange);
609 1102
610 -/**
611 - * selectize.js (v0.12.1)
612 - * Copyright (c) 2013–2015 Brian Reavis & contributors
613 - *
614 - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
615 - * file except in compliance with the License. You may obtain a copy of the License at:
616 - * http://www.apache.org/licenses/LICENSE-2.0
617 - *
618 - * Unless required by applicable law or agreed to in writing, software distributed under
619 - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
620 - * ANY KIND, either express or implied. See the License for the specific language
621 - * governing permissions and limitations under the License.
622 - *
623 - * @author Brian Reavis <brian@thirdroute.com>
624 - */
1103 + $input.data('selectize', self);
1104 + $input.addClass('selectized');
1105 + self.trigger('initialize');
625 1106
626 -/*jshint curly:false */
627 -/*jshint browser:true */
1107 + if (settings.preload === true) {
1108 + self.onSearchChange('');
1109 + }
628 1110
629 -(function(root, factory) {
630 - if (typeof define === 'function' && define.amd) {
631 - define('selectize', ['jquery','sifter','microplugin'], factory);
632 - } else if (typeof exports === 'object') {
633 - module.exports = factory(require('jquery'), require('sifter'), require('microplugin'));
634 - } else {
635 - root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin);
636 - }
637 -}(this, function($, Sifter, MicroPlugin) {
638 - 'use strict';
1111 + },
639 1112
640 - var highlight = function($element, pattern) {
641 - if (typeof pattern === 'string' && !pattern.length) return;
642 - var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;
643 -
644 - var highlight = function(node) {
645 - var skip = 0;
646 - if (node.nodeType === 3) {
647 - var pos = node.data.search(regex);
648 - if (pos >= 0 && node.data.length > 0) {
649 - var match = node.data.match(regex);
650 - var spannode = document.createElement('span');
651 - spannode.className = 'highlight';
652 - var middlebit = node.splitText(pos);
653 - var endbit = middlebit.splitText(match[0].length);
654 - var middleclone = middlebit.cloneNode(true);
655 - spannode.appendChild(middleclone);
656 - middlebit.parentNode.replaceChild(spannode, middlebit);
657 - skip = 1;
658 - }
659 - } else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
660 - for (var i = 0; i < node.childNodes.length; ++i) {
661 - i += highlight(node.childNodes[i]);
662 - }
1113 + setupTemplates: function() {
1114 + var self = this;
1115 + var field_label = self.settings.labelField;
1116 + var field_value = self.settings.valueField;
1117 + var field_optgroup = self.settings.optgroupLabelField;
1118 +
1119 + var templates = {
1120 + 'optgroup': function(data) {
1121 + return '<div class="optgroup">' + data.html + '</div>';
1122 + },
1123 + 'optgroup_header': function(data, escape) {
1124 + return '<div class="optgroup-header">' + escape(data[field_optgroup]) + '</div>';
1125 + },
1126 + 'option': function(data, escape) {
1127 + var classes = data.classes ? ' ' + data.classes : '';
1128 + classes += data[field_value] === '' ? ' selectize-dropdown-emptyoptionlabel' : '';
1129 +
1130 + var styles = data.styles ? ' style="' + data.styles + '"': '';
1131 + return '<div' + styles + ' class="option' + classes + '">' + escape(data[field_label]) + '</div>';
1132 + },
1133 + 'item': function(data, escape) {
1134 + return '<div class="item">' + escape(data[field_label]) + '</div>';
1135 + },
1136 + 'option_create': function(data, escape) {
1137 + return '<div class="create">Add <strong>' + escape(data.input) + '</strong>&#x2026;</div>';
663 1138 }
664 - return skip;
665 1139 };
666 -
667 - return $element.each(function() {
668 - highlight(this);
669 - });
670 - };
671 -
672 - var MicroEvent = function() {};
673 - MicroEvent.prototype = {
674 - on: function(event, fct){
675 - this._events = this._events || {};
676 - this._events[event] = this._events[event] || [];
677 - this._events[event].push(fct);
678 - },
679 - off: function(event, fct){
680 - var n = arguments.length;
681 - if (n === 0) return delete this._events;
682 - if (n === 1) return delete this._events[event];
683 -
684 - this._events = this._events || {};
685 - if (event in this._events === false) return;
686 - this._events[event].splice(this._events[event].indexOf(fct), 1);
687 - },
688 - trigger: function(event /* , args... */){
689 - this._events = this._events || {};
690 - if (event in this._events === false) return;
691 - for (var i = 0; i < this._events[event].length; i++){
692 - this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
1140 +
1141 + self.settings.render = $.extend({}, templates, self.settings.render);
1142 + },
1143 +
1144 + setupCallbacks: function() {
1145 + var key, fn, callbacks = {
1146 + 'initialize' : 'onInitialize',
1147 + 'change' : 'onChange',
1148 + 'item_add' : 'onItemAdd',
1149 + 'item_remove' : 'onItemRemove',
1150 + 'clear' : 'onClear',
1151 + 'option_add' : 'onOptionAdd',
1152 + 'option_remove' : 'onOptionRemove',
1153 + 'option_clear' : 'onOptionClear',
1154 + 'optgroup_add' : 'onOptionGroupAdd',
1155 + 'optgroup_remove' : 'onOptionGroupRemove',
1156 + 'optgroup_clear' : 'onOptionGroupClear',
1157 + 'dropdown_open' : 'onDropdownOpen',
1158 + 'dropdown_close' : 'onDropdownClose',
1159 + 'type' : 'onType',
1160 + 'load' : 'onLoad',
1161 + 'focus' : 'onFocus',
1162 + 'blur' : 'onBlur',
1163 + 'dropdown_item_activate' : 'onDropdownItemActivate',
1164 + 'dropdown_item_deactivate' : 'onDropdownItemDeactivate'
1165 + };
1166 +
1167 + for (key in callbacks) {
1168 + if (callbacks.hasOwnProperty(key)) {
1169 + fn = this.settings[callbacks[key]];
1170 + if (fn) this.on(key, fn);
693 1171 }
694 1172 }
695 - };
696 -
697 - /**
698 - * Mixin will delegate all MicroEvent.js function in the destination object.
699 - *
700 - * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent
701 - *
702 - * @param {object} the object which will support MicroEvent
703 - */
704 - MicroEvent.mixin = function(destObject){
705 - var props = ['on', 'off', 'trigger'];
706 - for (var i = 0; i < props.length; i++){
707 - destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
1173 + },
1174 +
1175 + onClick: function(e) {
1176 + var self = this;
1177 +
1178 + if (self.isDropdownClosing) {
1179 + return;
1180 + }
1181 +
1182 + if (!self.isFocused || !self.isOpen) {
1183 + self.focus();
1184 + e.preventDefault();
708 1185 }
709 - };
710 -
711 - var IS_MAC = /Mac/.test(navigator.userAgent);
712 -
713 - var KEY_A = 65;
714 - var KEY_COMMA = 188;
715 - var KEY_RETURN = 13;
716 - var KEY_ESC = 27;
717 - var KEY_LEFT = 37;
718 - var KEY_UP = 38;
719 - var KEY_P = 80;
720 - var KEY_RIGHT = 39;
721 - var KEY_DOWN = 40;
722 - var KEY_N = 78;
723 - var KEY_BACKSPACE = 8;
724 - var KEY_DELETE = 46;
725 - var KEY_SHIFT = 16;
726 - var KEY_CMD = IS_MAC ? 91 : 17;
727 - var KEY_CTRL = IS_MAC ? 18 : 17;
728 - var KEY_TAB = 9;
729 -
730 - var TAG_SELECT = 1;
731 - var TAG_INPUT = 2;
732 -
733 - // for now, android support in general is too spotty to support validity
734 - var SUPPORTS_VALIDITY_API = !/android/i.test(window.navigator.userAgent) && !!document.createElement('form').validity;
735 -
736 - var isset = function(object) {
737 - return typeof object !== 'undefined';
738 - };
739 -
740 - /**
741 - * Converts a scalar to its best string representation
742 - * for hash keys and HTML attribute values.
743 - *
744 - * Transformations:
745 - * 'str' -> 'str'
746 - * null -> ''
747 - * undefined -> ''
748 - * true -> '1'
749 - * false -> '0'
750 - * 0 -> '0'
751 - * 1 -> '1'
752 - *
753 - * @param {string} value
754 - * @returns {string|null}
755 - */
756 - var hash_key = function(value) {
757 - if (typeof value === 'undefined' || value === null) return null;
758 - if (typeof value === 'boolean') return value ? '1' : '0';
759 - return value + '';
760 - };
761 -
762 - /**
763 - * Escapes a string for use within HTML.
764 - *
765 - * @param {string} str
766 - * @returns {string}
767 - */
768 - var escape_html = function(str) {
769 - return (str + '')
770 - .replace(/&/g, '&amp;')
771 - .replace(/</g, '&lt;')
772 - .replace(/>/g, '&gt;')
773 - .replace(/"/g, '&quot;');
774 - };
775 -
776 - /**
777 - * Escapes "$" characters in replacement strings.
778 - *
779 - * @param {string} str
780 - * @returns {string}
781 - */
782 - var escape_replace = function(str) {
783 - return (str + '').replace(/\$/g, '$$$$');
784 - };
785 -
786 - var hook = {};
787 -
788 - /**
789 - * Wraps `method` on `self` so that `fn`
790 - * is invoked before the original method.
791 - *
792 - * @param {object} self
793 - * @param {string} method
794 - * @param {function} fn
795 - */
796 - hook.before = function(self, method, fn) {
797 - var original = self[method];
798 - self[method] = function() {
799 - fn.apply(self, arguments);
800 - return original.apply(self, arguments);
801 - };
802 - };
803 -
804 - /**
805 - * Wraps `method` on `self` so that `fn`
806 - * is invoked after the original method.
807 - *
808 - * @param {object} self
809 - * @param {string} method
810 - * @param {function} fn
811 - */
812 - hook.after = function(self, method, fn) {
813 - var original = self[method];
814 - self[method] = function() {
815 - var result = original.apply(self, arguments);
816 - fn.apply(self, arguments);
817 - return result;
818 - };
819 - };
820 -
821 - /**
822 - * Wraps `fn` so that it can only be invoked once.
823 - *
824 - * @param {function} fn
825 - * @returns {function}
826 - */
827 - var once = function(fn) {
828 - var called = false;
829 - return function() {
830 - if (called) return;
831 - called = true;
832 - fn.apply(this, arguments);
833 - };
834 - };
835 -
836 - /**
837 - * Wraps `fn` so that it can only be called once
838 - * every `delay` milliseconds (invoked on the falling edge).
839 - *
840 - * @param {function} fn
841 - * @param {int} delay
842 - * @returns {function}
843 - */
844 - var debounce = function(fn, delay) {
845 - var timeout;
846 - return function() {
847 - var self = this;
848 - var args = arguments;
849 - window.clearTimeout(timeout);
850 - timeout = window.setTimeout(function() {
851 - fn.apply(self, args);
852 - }, delay);
853 - };
854 - };
855 -
856 - /**
857 - * Debounce all fired events types listed in `types`
858 - * while executing the provided `fn`.
859 - *
860 - * @param {object} self
861 - * @param {array} types
862 - * @param {function} fn
863 - */
864 - var debounce_events = function(self, types, fn) {
865 - var type;
866 - var trigger = self.trigger;
867 - var event_args = {};
868 -
869 - // override trigger method
870 - self.trigger = function() {
871 - var type = arguments[0];
872 - if (types.indexOf(type) !== -1) {
873 - event_args[type] = arguments;
874 - } else {
875 - return trigger.apply(self, arguments);
1186 + },
1187 +
1188 + onMouseDown: function(e) {
1189 + var self = this;
1190 + var defaultPrevented = e.isDefaultPrevented();
1191 + var $target = $(e.target);
1192 +
1193 + if (e.button && e.button === 2) {
1194 + return;
1195 + }
1196 +
1197 + if (!self.isFocused) {
1198 + if (!defaultPrevented) {
1199 + window.setTimeout(function () {
1200 + if (!self.isOpen) {
1201 + self.focus();
1202 + }
1203 + }, 0);
876 1204 }
877 - };
878 -
879 - // invoke provided function
880 - fn.apply(self, []);
881 - self.trigger = trigger;
882 -
883 - // trigger queued events
884 - for (type in event_args) {
885 - if (event_args.hasOwnProperty(type)) {
886 - trigger.apply(self, event_args[type]);
887 - }
888 1205 }
889 - };
890 -
891 - /**
892 - * A workaround for http://bugs.jquery.com/ticket/6696
893 - *
894 - * @param {object} $parent - Parent element to listen on.
895 - * @param {string} event - Event name.
896 - * @param {string} selector - Descendant selector to filter by.
897 - * @param {function} fn - Event handler.
898 - */
899 - var watchChildEvent = function($parent, event, selector, fn) {
900 - $parent.on(event, selector, function(e) {
901 - var child = e.target;
902 - while (child && child.parentNode !== $parent[0]) {
903 - child = child.parentNode;
1206 + if ($target !== self.$control_input[0] || self.$control_input.val() === '') {
1207 + if (self.settings.mode === 'single') {
1208 + self.isOpen ? self.close() : self.open();
1209 + } else {
1210 + if (!defaultPrevented) {
1211 + self.setActiveItem(null);
1212 + }
1213 + if (!self.settings.openOnFocus) {
1214 + if (self.isOpen && $target === self.lastOpenTarget) {
1215 + self.close();
1216 + self.lastOpenTarget = false;
1217 + } else if (!self.isOpen) {
1218 + self.refreshOptions();
1219 + self.open();
1220 + self.lastOpenTarget = $target;
1221 + } else {
1222 + self.lastOpenTarget = $target;
1223 + }
1224 + }
904 1225 }
905 - e.currentTarget = child;
906 - return fn.apply(this, [e]);
907 - });
908 - };
909 -
910 - /**
911 - * Determines the current selection within a text input control.
912 - * Returns an object containing:
913 - * - start
914 - * - length
915 - *
916 - * @param {object} input
917 - * @returns {object}
918 - */
919 - var getSelection = function(input) {
920 - var result = {};
921 - if ('selectionStart' in input) {
922 - result.start = input.selectionStart;
923 - result.length = input.selectionEnd - result.start;
924 - } else if (document.selection) {
925 - input.focus();
926 - var sel = document.selection.createRange();
927 - var selLen = document.selection.createRange().text.length;
928 - sel.moveStart('character', -input.value.length);
929 - result.start = sel.text.length - selLen;
930 - result.length = selLen;
1226 + return false;
931 1227 }
932 - return result;
933 - };
934 -
935 - /**
936 - * Copies CSS properties from one element to another.
937 - *
938 - * @param {object} $from
939 - * @param {object} $to
940 - * @param {array} properties
941 - */
942 - var transferStyles = function($from, $to, properties) {
943 - var i, n, styles = {};
944 - if (properties) {
945 - for (i = 0, n = properties.length; i < n; i++) {
946 - styles[properties[i]] = $from.css(properties[i]);
947 - }
948 - } else {
949 - styles = $from.css();
1228 + },
1229 +
1230 + onChange: function() {
1231 + var self = this;
1232 + if (self.getValue() !== "") {
1233 + self.lastValidValue = self.getValue();
950 1234 }
951 - $to.css(styles);
952 - };
953 -
954 - /**
955 - * Measures the width of a string within a
956 - * parent element (in pixels).
957 - *
958 - * @param {string} str
959 - * @param {object} $parent
960 - * @returns {int}
961 - */
962 - var measureString = function(str, $parent) {
963 - if (!str) {
964 - return 0;
1235 + this.$input.trigger('input');
1236 + this.$input.trigger('change');
1237 + },
1238 +
1239 + onPaste: function(e) {
1240 + var self = this;
1241 +
1242 + if (self.isFull() || self.isInputHidden || self.isLocked) {
1243 + e.preventDefault();
1244 + return;
965 1245 }
966 -
967 - var $test = $('<test>').css({
968 - position: 'absolute',
969 - top: -99999,
970 - left: -99999,
971 - width: 'auto',
972 - padding: 0,
973 - whiteSpace: 'pre'
974 - }).text(str).appendTo('body');
975 -
976 - transferStyles($parent, $test, [
977 - 'letterSpacing',
978 - 'fontSize',
979 - 'fontFamily',
980 - 'fontWeight',
981 - 'textTransform'
982 - ]);
983 -
984 - var width = $test.width();
985 - $test.remove();
986 -
987 - return width;
988 - };
989 -
990 - /**
991 - * Sets up an input to grow horizontally as the user
992 - * types. If the value is changed manually, you can
993 - * trigger the "update" handler to resize:
994 - *
995 - * $input.trigger('update');
996 - *
997 - * @param {object} $input
998 - */
999 - var autoGrow = function($input) {
1000 - var currentWidth = null;
1001 -
1002 - var update = function(e, options) {
1003 - var value, keyCode, printable, placeholder, width;
1004 - var shift, character, selection;
1005 - e = e || window.event || {};
1006 - options = options || {};
1007 -
1008 - if (e.metaKey || e.altKey) return;
1009 - if (!options.force && $input.data('grow') === false) return;
1010 -
1011 - value = $input.val();
1012 - if (e.type && e.type.toLowerCase() === 'keydown') {
1013 - keyCode = e.keyCode;
1014 - printable = (
1015 - (keyCode >= 97 && keyCode <= 122) || // a-z
1016 - (keyCode >= 65 && keyCode <= 90) || // A-Z
1017 - (keyCode >= 48 && keyCode <= 57) || // 0-9
1018 - keyCode === 32 // space
1019 - );
1020 -
1021 - if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {
1022 - selection = getSelection($input[0]);
1023 - if (selection.length) {
1024 - value = value.substring(0, selection.start) + value.substring(selection.start + selection.length);
1025 - } else if (keyCode === KEY_BACKSPACE && selection.start) {
1026 - value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);
1027 - } else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {
1028 - value = value.substring(0, selection.start) + value.substring(selection.start + 1);
1029 - }
1030 - } else if (printable) {
1031 - shift = e.shiftKey;
1032 - character = String.fromCharCode(e.keyCode);
1033 - if (shift) character = character.toUpperCase();
1034 - else character = character.toLowerCase();
1035 - value += character;
1246 +
1247 + if (self.settings.splitOn) {
1248 +
1249 + setTimeout(function() {
1250 + var pastedText = self.$control_input.val();
1251 + if(!pastedText.match(self.settings.splitOn)){ return }
1252 +
1253 + var splitInput = pastedText
1254 + .trim()
1255 + .split(self.settings.splitOn);
1256 + for (var i = 0, n = splitInput.length; i < n; i++) {
1257 + self.createItem(splitInput[i]);
1036 1258 }
1037 - }
1038 -
1039 - placeholder = $input.attr('placeholder');
1040 - if (!value && placeholder) {
1041 - value = placeholder;
1042 - }
1043 -
1044 - width = measureString(value, $input) + 4;
1045 - if (width !== currentWidth) {
1046 - currentWidth = width;
1047 - $input.width(width);
1048 - $input.triggerHandler('resize');
1049 - }
1050 - };
1051 -
1052 - $input.on('keydown keyup update blur', update);
1053 - update();
1054 - };
1055 -
1056 - var Selectize = function($input, settings) {
1057 - var key, i, n, dir, input, self = this;
1058 - input = $input[0];
1059 - input.selectize = self;
1060 -
1061 - // detect rtl environment
1062 - var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null);
1063 - dir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction;
1064 - dir = dir || $input.parents('[dir]:first').attr('dir') || '';
1065 -
1066 - // setup default state
1067 - $.extend(self, {
1068 - order : 0,
1069 - settings : settings,
1070 - $input : $input,
1071 - tabIndex : $input.attr('tabindex') || '',
1072 - tagType : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT,
1073 - rtl : /rtl/i.test(dir),
1074 -
1075 - eventNS : '.selectize' + (++Selectize.count),
1076 - highlightedValue : null,
1077 - isOpen : false,
1078 - isDisabled : false,
1079 - isRequired : $input.is('[required]'),
1080 - isInvalid : false,
1081 - isLocked : false,
1082 - isFocused : false,
1083 - isInputHidden : false,
1084 - isSetup : false,
1085 - isShiftDown : false,
1086 - isCmdDown : false,
1087 - isCtrlDown : false,
1088 - ignoreFocus : false,
1089 - ignoreBlur : false,
1090 - ignoreHover : false,
1091 - hasOptions : false,
1092 - currentResults : null,
1093 - lastValue : '',
1094 - caretPos : 0,
1095 - loading : 0,
1096 - loadedSearches : {},
1097 -
1098 - $activeOption : null,
1099 - $activeItems : [],
1100 -
1101 - optgroups : {},
1102 - options : {},
1103 - userOptions : {},
1104 - items : [],
1105 - renderCache : {},
1106 - onSearchChange : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle)
1107 - });
1108 -
1109 - // search system
1110 - self.sifter = new Sifter(this.options, {diacritics: settings.diacritics});
1111 -
1112 - // build options table
1113 - if (self.settings.options) {
1114 - for (i = 0, n = self.settings.options.length; i < n; i++) {
1115 - self.registerOption(self.settings.options[i]);
1116 - }
1117 - delete self.settings.options;
1259 + }, 0);
1118 1260 }
1119 -
1120 - // build optgroup table
1121 - if (self.settings.optgroups) {
1122 - for (i = 0, n = self.settings.optgroups.length; i < n; i++) {
1123 - self.registerOptionGroup(self.settings.optgroups[i]);
1261 + },
1262 +
1263 + onKeyPress: function(e) {
1264 + if (this.isLocked) return e && e.preventDefault();
1265 + var character = String.fromCharCode(e.keyCode || e.which);
1266 + if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {
1267 + this.createItem();
1268 + e.preventDefault();
1269 + return false;
1270 + }
1271 + },
1272 +
1273 + onKeyDown: function(e) {
1274 + var isInput = e.target === this.$control_input[0];
1275 + var self = this;
1276 +
1277 + if (self.isLocked) {
1278 + if (e.keyCode !== KEY_TAB) {
1279 + e.preventDefault();
1124 1280 }
1125 - delete self.settings.optgroups;
1281 + return;
1126 1282 }
1127 -
1128 - // option-dependent defaults
1129 - self.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi');
1130 - if (typeof self.settings.hideSelected !== 'boolean') {
1131 - self.settings.hideSelected = self.settings.mode === 'multi';
1132 - }
1133 -
1134 - self.initializePlugins(self.settings.plugins);
1135 - self.setupCallbacks();
1136 - self.setupTemplates();
1137 - self.setup();
1138 - };
1139 -
1140 - // mixins
1141 - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1142 -
1143 - MicroEvent.mixin(Selectize);
1144 - MicroPlugin.mixin(Selectize);
1145 -
1146 - // methods
1147 - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1148 -
1149 - $.extend(Selectize.prototype, {
1150 -
1151 - /**
1152 - * Creates all elements and sets up event bindings.
1153 - */
1154 - setup: function() {
1155 - var self = this;
1156 - var settings = self.settings;
1157 - var eventNS = self.eventNS;
1158 - var $window = $(window);
1159 - var $document = $(document);
1160 - var $input = self.$input;
1161 -
1162 - var $wrapper;
1163 - var $control;
1164 - var $control_input;
1165 - var $dropdown;
1166 - var $dropdown_content;
1167 - var $dropdown_parent;
1168 - var inputMode;
1169 - var timeout_blur;
1170 - var timeout_focus;
1171 - var classes;
1172 - var classes_plugins;
1173 -
1174 - inputMode = self.settings.mode;
1175 - classes = $input.attr('class') || '';
1176 -
1177 - $wrapper = $('<div>').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode);
1178 - $control = $('<div>').addClass(settings.inputClass).addClass('items').appendTo($wrapper);
1179 - $control_input = $('<input type="text" autocomplete="off" />').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);
1180 - $dropdown_parent = $(settings.dropdownParent || $wrapper);
1181 - $dropdown = $('<div>').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent);
1182 - $dropdown_content = $('<div>').addClass(settings.dropdownContentClass).appendTo($dropdown);
1183 -
1184 - if(self.settings.copyClassesToDropdown) {
1185 - $dropdown.addClass(classes);
1186 - }
1187 -
1188 - $wrapper.css({
1189 - width: $input[0].style.width
1190 - });
1191 -
1192 - if (self.plugins.names.length) {
1193 - classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
1194 - $wrapper.addClass(classes_plugins);
1195 - $dropdown.addClass(classes_plugins);
1196 - }
1197 -
1198 - if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {
1199 - $input.attr('multiple', 'multiple');
1200 - }
1201 -
1202 - if (self.settings.placeholder) {
1203 - $control_input.attr('placeholder', settings.placeholder);
1204 - }
1205 -
1206 - // if splitOn was not passed in, construct it from the delimiter to allow pasting universally
1207 - if (!self.settings.splitOn && self.settings.delimiter) {
1208 - var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1209 - self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*');
1210 - }
1211 -
1212 - if ($input.attr('autocorrect')) {
1213 - $control_input.attr('autocorrect', $input.attr('autocorrect'));
1214 - }
1215 -
1216 - if ($input.attr('autocapitalize')) {
1217 - $control_input.attr('autocapitalize', $input.attr('autocapitalize'));
1218 - }
1219 -
1220 - self.$wrapper = $wrapper;
1221 - self.$control = $control;
1222 - self.$control_input = $control_input;
1223 - self.$dropdown = $dropdown;
1224 - self.$dropdown_content = $dropdown_content;
1225 -
1226 - $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
1227 - $dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
1228 - watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
1229 - autoGrow($control_input);
1230 -
1231 - $control.on({
1232 - mousedown : function() { return self.onMouseDown.apply(self, arguments); },
1233 - click : function() { return self.onClick.apply(self, arguments); }
1234 - });
1235 -
1236 - $control_input.on({
1237 - mousedown : function(e) { e.stopPropagation(); },
1238 - keydown : function() { return self.onKeyDown.apply(self, arguments); },
1239 - keyup : function() { return self.onKeyUp.apply(self, arguments); },
1240 - keypress : function() { return self.onKeyPress.apply(self, arguments); },
1241 - resize : function() { self.positionDropdown.apply(self, []); },
1242 - blur : function() { return self.onBlur.apply(self, arguments); },
1243 - focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },
1244 - paste : function() { return self.onPaste.apply(self, arguments); }
1245 - });
1246 -
1247 - $document.on('keydown' + eventNS, function(e) {
1248 - self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
1249 - self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
1250 - self.isShiftDown = e.shiftKey;
1251 - });
1252 -
1253 - $document.on('keyup' + eventNS, function(e) {
1254 - if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
1255 - if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
1256 - if (e.keyCode === KEY_CMD) self.isCmdDown = false;
1257 - });
1258 -
1259 - $document.on('mousedown' + eventNS, function(e) {
1260 - if (self.isFocused) {
1261 - // prevent events on the dropdown scrollbar from causing the control to blur
1262 - if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) {
1263 - return false;
1264 - }
1265 - // blur on click outside
1266 - if (!self.$control.has(e.target).length && e.target !== self.$control[0]) {
1267 - self.blur(e.target);
1268 - }
1283 +
1284 + switch (e.keyCode) {
1285 + case KEY_A:
1286 + if (self.isCmdDown) {
1287 + self.selectAll();
1288 + return;
1269 1289 }
1270 - });
1271 -
1272 - $window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {
1290 + break;
1291 + case KEY_ESC:
1273 1292 if (self.isOpen) {
1274 - self.positionDropdown.apply(self, arguments);
1275 - }
1276 - });
1277 - $window.on('mousemove' + eventNS, function() {
1278 - self.ignoreHover = false;
1279 - });
1280 -
1281 - // store original children and tab index so that they can be
1282 - // restored when the destroy() method is called.
1283 - this.revertSettings = {
1284 - $children : $input.children().detach(),
1285 - tabindex : $input.attr('tabindex')
1286 - };
1287 -
1288 - $input.attr('tabindex', -1).hide().after(self.$wrapper);
1289 -
1290 - if ($.isArray(settings.items)) {
1291 - self.setValue(settings.items);
1292 - delete settings.items;
1293 - }
1294 -
1295 - // feature detect for the validation API
1296 - if (SUPPORTS_VALIDITY_API) {
1297 - $input.on('invalid' + eventNS, function(e) {
1298 1293 e.preventDefault();
1299 - self.isInvalid = true;
1300 - self.refreshState();
1301 - });
1302 - }
1303 -
1304 - self.updateOriginalInput();
1305 - self.refreshItems();
1306 - self.refreshState();
1307 - self.updatePlaceholder();
1308 - self.isSetup = true;
1309 -
1310 - if ($input.is(':disabled')) {
1311 - self.disable();
1312 - }
1313 -
1314 - self.on('change', this.onChange);
1315 -
1316 - $input.data('selectize', self);
1317 - $input.addClass('selectized');
1318 - self.trigger('initialize');
1319 -
1320 - // preload options
1321 - if (settings.preload === true) {
1322 - self.onSearchChange('');
1323 - }
1324 -
1325 - },
1326 -
1327 - /**
1328 - * Sets up default rendering functions.
1329 - */
1330 - setupTemplates: function() {
1331 - var self = this;
1332 - var field_label = self.settings.labelField;
1333 - var field_optgroup = self.settings.optgroupLabelField;
1334 -
1335 - var templates = {
1336 - 'optgroup': function(data) {
1337 - return '<div class="optgroup">' + data.html + '</div>';
1338 - },
1339 - 'optgroup_header': function(data, escape) {
1340 - return '<div class="optgroup-header">' + escape(data[field_optgroup]) + '</div>';
1341 - },
1342 - 'option': function(data, escape) {
1343 - return '<div class="option">' + escape(data[field_label]) + '</div>';
1344 - },
1345 - 'item': function(data, escape) {
1346 - return '<div class="item">' + escape(data[field_label]) + '</div>';
1347 - },
1348 - 'option_create': function(data, escape) {
1349 - return '<div class="create">Add <strong>' + escape(data.input) + '</strong>&hellip;</div>';
1294 + e.stopPropagation();
1295 + self.close();
1350 1296 }
1351 - };
1352 -
1353 - self.settings.render = $.extend({}, templates, self.settings.render);
1354 - },
1355 -
1356 - /**
1357 - * Maps fired events to callbacks provided
1358 - * in the settings used when creating the control.
1359 - */
1360 - setupCallbacks: function() {
1361 - var key, fn, callbacks = {
1362 - 'initialize' : 'onInitialize',
1363 - 'change' : 'onChange',
1364 - 'item_add' : 'onItemAdd',
1365 - 'item_remove' : 'onItemRemove',
1366 - 'clear' : 'onClear',
1367 - 'option_add' : 'onOptionAdd',
1368 - 'option_remove' : 'onOptionRemove',
1369 - 'option_clear' : 'onOptionClear',
1370 - 'optgroup_add' : 'onOptionGroupAdd',
1371 - 'optgroup_remove' : 'onOptionGroupRemove',
1372 - 'optgroup_clear' : 'onOptionGroupClear',
1373 - 'dropdown_open' : 'onDropdownOpen',
1374 - 'dropdown_close' : 'onDropdownClose',
1375 - 'type' : 'onType',
1376 - 'load' : 'onLoad',
1377 - 'focus' : 'onFocus',
1378 - 'blur' : 'onBlur'
1379 - };
1380 -
1381 - for (key in callbacks) {
1382 - if (callbacks.hasOwnProperty(key)) {
1383 - fn = this.settings[callbacks[key]];
1384 - if (fn) this.on(key, fn);
1297 + return;
1298 + case KEY_N:
1299 + if (!e.ctrlKey || e.altKey) break;
1300 + case KEY_DOWN:
1301 + if (!self.isOpen && self.hasOptions) {
1302 + self.open();
1303 + } else if (self.$activeOption) {
1304 + self.ignoreHover = true;
1305 + var $next = self.getAdjacentOption(self.$activeOption, 1);
1306 + if ($next.length) self.setActiveOption($next, true, true);
1385 1307 }
1386 - }
1387 - },
1388 -
1389 - /**
1390 - * Triggered when the main control element
1391 - * has a click event.
1392 - *
1393 - * @param {object} e
1394 - * @return {boolean}
1395 - */
1396 - onClick: function(e) {
1397 - var self = this;
1398 -
1399 - // necessary for mobile webkit devices (manual focus triggering
1400 - // is ignored unless invoked within a click event)
1401 - if (!self.isFocused) {
1402 - self.focus();
1403 1308 e.preventDefault();
1404 - }
1405 - },
1406 -
1407 - /**
1408 - * Triggered when the main control element
1409 - * has a mouse down event.
1410 - *
1411 - * @param {object} e
1412 - * @return {boolean}
1413 - */
1414 - onMouseDown: function(e) {
1415 - var self = this;
1416 - var defaultPrevented = e.isDefaultPrevented();
1417 - var $target = $(e.target);
1418 -
1419 - if (self.isFocused) {
1420 - // retain focus by preventing native handling. if the
1421 - // event target is the input it should not be modified.
1422 - // otherwise, text selection within the input won't work.
1423 - if (e.target !== self.$control_input[0]) {
1424 - if (self.settings.mode === 'single') {
1425 - // toggle dropdown
1426 - self.isOpen ? self.close() : self.open();
1427 - } else if (!defaultPrevented) {
1428 - self.setActiveItem(null);
1429 - }
1430 - return false;
1309 + return;
1310 + case KEY_P:
1311 + if (!e.ctrlKey || e.altKey) break;
1312 + case KEY_UP:
1313 + if (self.$activeOption) {
1314 + self.ignoreHover = true;
1315 + var $prev = self.getAdjacentOption(self.$activeOption, -1);
1316 + if ($prev.length) self.setActiveOption($prev, true, true);
1431 1317 }
1432 - } else {
1433 - // give control focus
1434 - if (!defaultPrevented) {
1435 - window.setTimeout(function() {
1436 - self.focus();
1437 - }, 0);
1438 - }
1439 - }
1440 - },
1441 -
1442 - /**
1443 - * Triggered when the value of the control has been changed.
1444 - * This should propagate the event to the original DOM
1445 - * input / select element.
1446 - */
1447 - onChange: function() {
1448 - this.$input.trigger('change');
1449 - },
1450 -
1451 - /**
1452 - * Triggered on <input> paste.
1453 - *
1454 - * @param {object} e
1455 - * @returns {boolean}
1456 - */
1457 - onPaste: function(e) {
1458 - var self = this;
1459 - if (self.isFull() || self.isInputHidden || self.isLocked) {
1460 1318 e.preventDefault();
1461 - } else {
1462 - // If a regex or string is included, this will split the pasted
1463 - // input and create Items for each separate value
1464 - if (self.settings.splitOn) {
1465 - setTimeout(function() {
1466 - var splitInput = $.trim(self.$control_input.val() || '').split(self.settings.splitOn);
1467 - for (var i = 0, n = splitInput.length; i < n; i++) {
1468 - self.createItem(splitInput[i]);
1469 - }
1470 - }, 0);
1471 - }
1472 - }
1473 - },
1474 -
1475 - /**
1476 - * Triggered on <input> keypress.
1477 - *
1478 - * @param {object} e
1479 - * @returns {boolean}
1480 - */
1481 - onKeyPress: function(e) {
1482 - if (this.isLocked) return e && e.preventDefault();
1483 - var character = String.fromCharCode(e.keyCode || e.which);
1484 - if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {
1485 - this.createItem();
1486 - e.preventDefault();
1487 - return false;
1488 - }
1489 - },
1490 -
1491 - /**
1492 - * Triggered on <input> keydown.
1493 - *
1494 - * @param {object} e
1495 - * @returns {boolean}
1496 - */
1497 - onKeyDown: function(e) {
1498 - var isInput = e.target === this.$control_input[0];
1499 - var self = this;
1500 -
1501 - if (self.isLocked) {
1502 - if (e.keyCode !== KEY_TAB) {
1319 + return;
1320 + case KEY_RETURN:
1321 + if (self.isOpen && self.$activeOption) {
1322 + self.onOptionSelect({currentTarget: self.$activeOption});
1503 1323 e.preventDefault();
1504 1324 }
1505 1325 return;
1506 - }
1507 -
1508 - switch (e.keyCode) {
1509 - case KEY_A:
1510 - if (self.isCmdDown) {
1511 - self.selectAll();
1512 - return;
1513 - }
1514 - break;
1515 - case KEY_ESC:
1516 - if (self.isOpen) {
1326 + case KEY_LEFT:
1327 + self.advanceSelection(-1, e);
1328 + return;
1329 + case KEY_RIGHT:
1330 + self.advanceSelection(1, e);
1331 + return;
1332 + case KEY_TAB:
1333 + if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {
1334 + self.onOptionSelect({currentTarget: self.$activeOption});
1335 +
1336 + if (!self.isFull()) {
1517 1337 e.preventDefault();
1518 - e.stopPropagation();
1519 - self.close();
1520 1338 }
1521 - return;
1522 - case KEY_N:
1523 - if (!e.ctrlKey || e.altKey) break;
1524 - case KEY_DOWN:
1525 - if (!self.isOpen && self.hasOptions) {
1526 - self.open();
1527 - } else if (self.$activeOption) {
1528 - self.ignoreHover = true;
1529 - var $next = self.getAdjacentOption(self.$activeOption, 1);
1530 - if ($next.length) self.setActiveOption($next, true, true);
1531 - }
1339 + }
1340 + if (self.settings.create && self.createItem() && self.settings.showAddOptionOnCreate) {
1532 1341 e.preventDefault();
1533 - return;
1534 - case KEY_P:
1535 - if (!e.ctrlKey || e.altKey) break;
1536 - case KEY_UP:
1537 - if (self.$activeOption) {
1538 - self.ignoreHover = true;
1539 - var $prev = self.getAdjacentOption(self.$activeOption, -1);
1540 - if ($prev.length) self.setActiveOption($prev, true, true);
1541 - }
1542 - e.preventDefault();
1543 - return;
1544 - case KEY_RETURN:
1545 - if (self.isOpen && self.$activeOption) {
1546 - self.onOptionSelect({currentTarget: self.$activeOption});
1547 - e.preventDefault();
1548 - }
1549 - return;
1550 - case KEY_LEFT:
1551 - self.advanceSelection(-1, e);
1552 - return;
1553 - case KEY_RIGHT:
1554 - self.advanceSelection(1, e);
1555 - return;
1556 - case KEY_TAB:
1557 - if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {
1558 - self.onOptionSelect({currentTarget: self.$activeOption});
1559 -
1560 - // Default behaviour is to jump to the next field, we only want this
1561 - // if the current field doesn't accept any more entries
1562 - if (!self.isFull()) {
1563 - e.preventDefault();
1564 - }
1565 - }
1566 - if (self.settings.create && self.createItem()) {
1567 - e.preventDefault();
1568 - }
1569 - return;
1570 - case KEY_BACKSPACE:
1571 - case KEY_DELETE:
1572 - self.deleteSelection(e);
1573 - return;
1574 - }
1575 -
1576 - if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {
1577 - e.preventDefault();
1342 + }
1578 1343 return;
1344 + case KEY_BACKSPACE:
1345 + case KEY_DELETE:
1346 + self.deleteSelection(e);
1347 + return;
1348 + }
1349 +
1350 + if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {
1351 + e.preventDefault();
1352 + return;
1353 + }
1354 + },
1355 +
1356 + onInput: function(e) {
1357 + var self = this;
1358 +
1359 + var value = self.$control_input.val() || '';
1360 + if (self.lastValue !== value) {
1361 + self.lastValue = value;
1362 + self.onSearchChange(value);
1363 + self.refreshOptions();
1364 + self.trigger('type', value);
1365 + }
1366 + },
1367 +
1368 + onSearchChange: function(value) {
1369 + var self = this;
1370 + var fn = self.settings.load;
1371 + if (!fn) return;
1372 + if (self.loadedSearches.hasOwnProperty(value)) return;
1373 + self.loadedSearches[value] = true;
1374 + self.load(function(callback) {
1375 + fn.apply(self, [value, callback]);
1376 + });
1377 + },
1378 +
1379 + onFocus: function(e) {
1380 + var self = this;
1381 + var wasFocused = self.isFocused;
1382 +
1383 + if (self.isDisabled) {
1384 + self.blur();
1385 + e && e.preventDefault();
1386 + return false;
1387 + }
1388 +
1389 + if (self.ignoreFocus) return;
1390 + self.isFocused = true;
1391 + if (self.settings.preload === 'focus') self.onSearchChange('');
1392 +
1393 + if (!wasFocused) self.trigger('focus');
1394 +
1395 + if (!self.$activeItems.length) {
1396 + self.showInput();
1397 + self.setActiveItem(null);
1398 + self.refreshOptions(!!self.settings.openOnFocus);
1399 + }
1400 +
1401 + self.refreshState();
1402 + },
1403 +
1404 + onBlur: function(e, dest) {
1405 + var self = this;
1406 +
1407 + if (!self.isFocused) return;
1408 + self.isFocused = false;
1409 +
1410 + if (self.ignoreFocus) {
1411 + return;
1412 + }
1413 +
1414 + var deactivate = function() {
1415 + self.close();
1416 + self.setTextboxValue('');
1417 + self.setActiveItem(null);
1418 + self.setActiveOption(null);
1419 + self.setCaret(self.items.length);
1420 + self.refreshState();
1421 +
1422 + dest && dest.focus && dest.focus();
1423 +
1424 + self.isBlurring = false;
1425 + self.ignoreFocus = false;
1426 + self.trigger('blur');
1427 + };
1428 +
1429 + self.isBlurring = true;
1430 + self.ignoreFocus = true;
1431 + if (self.settings.create && self.settings.createOnBlur) {
1432 + self.createItem(null, false, deactivate);
1433 + } else {
1434 + deactivate();
1435 + }
1436 + },
1437 +
1438 + onOptionHover: function(e) {
1439 + if (this.ignoreHover) return;
1440 + this.setActiveOption(e.currentTarget, false);
1441 + },
1442 +
1443 + onOptionSelect: function(e) {
1444 + var value, $target, $option, self = this;
1445 +
1446 + if (e.preventDefault) {
1447 + e.preventDefault();
1448 + e.stopPropagation();
1449 + }
1450 +
1451 + if (e.button && e.button === 2) {
1452 + return;
1453 + }
1454 +
1455 + $target = $(e.currentTarget);
1456 + if ($target.hasClass('create')) {
1457 + self.createItem(null, function() {
1458 + if (self.settings.closeAfterSelect) {
1459 + self.close();
1460 + }
1461 + });
1462 + } else {
1463 + value = $target.attr('data-value');
1464 + if (typeof value !== 'undefined') {
1465 + self.lastQuery = null;
1466 + self.setTextboxValue('');
1467 + self.addItem(value);
1468 + if (self.settings.closeAfterSelect) {
1469 + self.close();
1470 + } else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {
1471 + self.setActiveOption(self.getOption(value));
1472 + }
1579 1473 }
1580 - },
1581 -
1582 - /**
1583 - * Triggered on <input> keyup.
1584 - *
1585 - * @param {object} e
1586 - * @returns {boolean}
1587 - */
1588 - onKeyUp: function(e) {
1589 - var self = this;
1590 -
1591 - if (self.isLocked) return e && e.preventDefault();
1592 - var value = self.$control_input.val() || '';
1593 - if (self.lastValue !== value) {
1594 - self.lastValue = value;
1595 - self.onSearchChange(value);
1596 - self.refreshOptions();
1597 - self.trigger('type', value);
1474 + }
1475 + },
1476 +
1477 + onItemSelect: function(e) {
1478 + var self = this;
1479 +
1480 + if (self.isLocked) return;
1481 + if (self.settings.mode === 'multi') {
1482 + e.preventDefault();
1483 + self.setActiveItem(e.currentTarget, e);
1484 + }
1485 + },
1486 +
1487 + load: function(fn) {
1488 + var self = this;
1489 + var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);
1490 +
1491 + self.loading++;
1492 + fn.apply(self, [function(results) {
1493 + self.loading = Math.max(self.loading - 1, 0);
1494 + if (results && results.length) {
1495 + self.addOption(results);
1496 + self.refreshOptions(self.isFocused && !self.isInputHidden);
1598 1497 }
1599 - },
1600 -
1601 - /**
1602 - * Invokes the user-provide option provider / loader.
1603 - *
1604 - * Note: this function is debounced in the Selectize
1605 - * constructor (by `settings.loadDelay` milliseconds)
1606 - *
1607 - * @param {string} value
1608 - */
1609 - onSearchChange: function(value) {
1610 - var self = this;
1611 - var fn = self.settings.load;
1612 - if (!fn) return;
1613 - if (self.loadedSearches.hasOwnProperty(value)) return;
1614 - self.loadedSearches[value] = true;
1615 - self.load(function(callback) {
1616 - fn.apply(self, [value, callback]);
1617 - });
1618 - },
1619 -
1620 - /**
1621 - * Triggered on <input> focus.
1622 - *
1623 - * @param {object} e (optional)
1624 - * @returns {boolean}
1625 - */
1626 - onFocus: function(e) {
1627 - var self = this;
1628 - var wasFocused = self.isFocused;
1629 -
1630 - if (self.isDisabled) {
1631 - self.blur();
1632 - e && e.preventDefault();
1633 - return false;
1498 + if (!self.loading) {
1499 + $wrapper.removeClass(self.settings.loadingClass);
1634 1500 }
1635 -
1636 - if (self.ignoreFocus) return;
1637 - self.isFocused = true;
1638 - if (self.settings.preload === 'focus') self.onSearchChange('');
1639 -
1640 - if (!wasFocused) self.trigger('focus');
1641 -
1642 - if (!self.$activeItems.length) {
1501 + self.trigger('load', results);
1502 + }]);
1503 + },
1504 +
1505 + getTextboxValue: function() {
1506 + var $input = this.$control_input;
1507 + return $input.val();
1508 + },
1509 +
1510 + setTextboxValue: function(value) {
1511 + var $input = this.$control_input;
1512 + var changed = $input.val() !== value;
1513 + if (changed) {
1514 + $input.val(value).triggerHandler('update');
1515 + this.lastValue = value;
1516 + }
1517 + },
1518 +
1519 + getValue: function() {
1520 + if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) {
1521 + return this.items;
1522 + } else {
1523 + return this.items.join(this.settings.delimiter);
1524 + }
1525 + },
1526 +
1527 + setValue: function(value, silent) {
1528 + const items = Array.isArray(value) ? value : [value];
1529 + if (items.join('') === this.items.join('')) {
1530 + return;
1531 + }
1532 +
1533 + var events = silent ? [] : ['change'];
1534 +
1535 + debounce_events(this, events, function() {
1536 + this.clear(silent);
1537 + this.addItems(value, silent);
1538 + });
1539 + },
1540 +
1541 + setMaxItems: function(value){
1542 + if(value === 0) value = null;
1543 + this.settings.maxItems = value;
1544 + this.settings.mode = this.settings.mode || (this.settings.maxItems === 1 ? 'single' : 'multi');
1545 + this.refreshState();
1546 + },
1547 +
1548 + setActiveItem: function($item, e) {
1549 + var self = this;
1550 + var eventName;
1551 + var i, idx, begin, end, item, swap;
1552 + var $last;
1553 +
1554 + if (self.settings.mode === 'single') return;
1555 + $item = $($item);
1556 +
1557 + if (!$item.length) {
1558 + $(self.$activeItems).removeClass('active');
1559 + self.$activeItems = [];
1560 + if (self.isFocused) {
1643 1561 self.showInput();
1644 - self.setActiveItem(null);
1645 - self.refreshOptions(!!self.settings.openOnFocus);
1646 1562 }
1647 -
1648 - self.refreshState();
1649 - },
1650 -
1651 - /**
1652 - * Triggered on <input> blur.
1653 - *
1654 - * @param {object} e
1655 - * @param {Element} dest
1656 - */
1657 - onBlur: function(e, dest) {
1658 - var self = this;
1659 - if (!self.isFocused) return;
1660 - self.isFocused = false;
1661 -
1662 - if (self.ignoreFocus) {
1663 - return;
1664 - } else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) {
1665 - // necessary to prevent IE closing the dropdown when the scrollbar is clicked
1666 - self.ignoreBlur = true;
1667 - self.onFocus(e);
1668 - return;
1563 + return;
1564 + }
1565 +
1566 + eventName = e && e.type.toLowerCase();
1567 +
1568 + if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) {
1569 + $last = self.$control.children('.active:last');
1570 + begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]);
1571 + end = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]);
1572 + if (begin > end) {
1573 + swap = begin;
1574 + begin = end;
1575 + end = swap;
1669 1576 }
1670 -
1671 - var deactivate = function() {
1672 - self.close();
1673 - self.setTextboxValue('');
1674 - self.setActiveItem(null);
1675 - self.setActiveOption(null);
1676 - self.setCaret(self.items.length);
1677 - self.refreshState();
1678 -
1679 - // IE11 bug: element still marked as active
1680 - (dest || document.body).focus();
1681 -
1682 - self.ignoreFocus = false;
1683 - self.trigger('blur');
1684 - };
1685 -
1686 - self.ignoreFocus = true;
1687 - if (self.settings.create && self.settings.createOnBlur) {
1688 - self.createItem(null, false, deactivate);
1689 - } else {
1690 - deactivate();
1577 + for (i = begin; i <= end; i++) {
1578 + item = self.$control[0].childNodes[i];
1579 + if (self.$activeItems.indexOf(item) === -1) {
1580 + $(item).addClass('active');
1581 + self.$activeItems.push(item);
1582 + }
1691 1583 }
1692 - },
1693 -
1694 - /**
1695 - * Triggered when the user rolls over
1696 - * an option in the autocomplete dropdown menu.
1697 - *
1698 - * @param {object} e
1699 - * @returns {boolean}
1700 - */
1701 - onOptionHover: function(e) {
1702 - if (this.ignoreHover) return;
1703 - this.setActiveOption(e.currentTarget, false);
1704 - },
1705 -
1706 - /**
1707 - * Triggered when the user clicks on an option
1708 - * in the autocomplete dropdown menu.
1709 - *
1710 - * @param {object} e
1711 - * @returns {boolean}
1712 - */
1713 - onOptionSelect: function(e) {
1714 - var value, $target, $option, self = this;
1715 -
1716 - if (e.preventDefault) {
1717 - e.preventDefault();
1718 - e.stopPropagation();
1719 - }
1720 -
1721 - $target = $(e.currentTarget);
1722 - if ($target.hasClass('create')) {
1723 - self.createItem(null, function() {
1724 - if (self.settings.closeAfterSelect) {
1725 - self.close();
1726 - }
1727 - });
1584 + e.preventDefault();
1585 + } else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) {
1586 + if ($item.hasClass('active')) {
1587 + idx = self.$activeItems.indexOf($item[0]);
1588 + self.$activeItems.splice(idx, 1);
1589 + $item.removeClass('active');
1728 1590 } else {
1729 - value = $target.attr('data-value');
1730 - if (typeof value !== 'undefined') {
1731 - self.lastQuery = null;
1732 - self.setTextboxValue('');
1733 - self.addItem(value);
1734 - if (self.settings.closeAfterSelect) {
1735 - self.close();
1736 - } else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {
1737 - self.setActiveOption(self.getOption(value));
1738 - }
1739 - }
1591 + self.$activeItems.push($item.addClass('active')[0]);
1740 1592 }
1741 - },
1742 -
1743 - /**
1744 - * Triggered when the user clicks on an item
1745 - * that has been selected.
1746 - *
1747 - * @param {object} e
1748 - * @returns {boolean}
1749 - */
1750 - onItemSelect: function(e) {
1751 - var self = this;
1752 -
1753 - if (self.isLocked) return;
1754 - if (self.settings.mode === 'multi') {
1755 - e.preventDefault();
1756 - self.setActiveItem(e.currentTarget, e);
1593 + } else {
1594 + $(self.$activeItems).removeClass('active');
1595 + self.$activeItems = [$item.addClass('active')[0]];
1596 + }
1597 +
1598 + self.hideInput();
1599 + if (!this.isFocused) {
1600 + self.focus();
1601 + }
1602 + },
1603 +
1604 + setActiveOption: function($option, scroll, animate) {
1605 + var height_menu, height_item, y;
1606 + var scroll_top, scroll_bottom;
1607 + var self = this;
1608 +
1609 + if (self.$activeOption) {
1610 + self.$activeOption.removeClass('active');
1611 + self.trigger('dropdown_item_deactivate', self.$activeOption.attr('data-value'));
1612 + }
1613 + self.$activeOption = null;
1614 +
1615 + $option = $($option);
1616 + if (!$option.length) return;
1617 +
1618 + self.$activeOption = $option.addClass('active');
1619 + if (self.isOpen) self.trigger('dropdown_item_activate', self.$activeOption.attr('data-value'));
1620 +
1621 + if (scroll || !isset(scroll)) {
1622 +
1623 + height_menu = self.$dropdown_content.height();
1624 + height_item = self.$activeOption.outerHeight(true);
1625 + scroll = self.$dropdown_content.scrollTop() || 0;
1626 + y = self.$activeOption.offset().top - self.$dropdown_content.offset().top + scroll;
1627 + scroll_top = y;
1628 + scroll_bottom = y - height_menu + height_item;
1629 +
1630 + if (y + height_item > height_menu + scroll) {
1631 + self.$dropdown_content.stop().animate({scrollTop: scroll_bottom}, animate ? self.settings.scrollDuration : 0);
1632 + } else if (y < scroll) {
1633 + self.$dropdown_content.stop().animate({scrollTop: scroll_top}, animate ? self.settings.scrollDuration : 0);
1757 1634 }
1758 - },
1759 -
1760 - /**
1761 - * Invokes the provided method that provides
1762 - * results to a callback---which are then added
1763 - * as options to the control.
1764 - *
1765 - * @param {function} fn
1766 - */
1767 - load: function(fn) {
1768 - var self = this;
1769 - var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);
1770 -
1771 - self.loading++;
1772 - fn.apply(self, [function(results) {
1773 - self.loading = Math.max(self.loading - 1, 0);
1774 - if (results && results.length) {
1775 - self.addOption(results);
1776 - self.refreshOptions(self.isFocused && !self.isInputHidden);
1777 - }
1778 - if (!self.loading) {
1779 - $wrapper.removeClass(self.settings.loadingClass);
1780 - }
1781 - self.trigger('load', results);
1782 - }]);
1783 - },
1784 -
1785 - /**
1786 - * Sets the input field of the control to the specified value.
1787 - *
1788 - * @param {string} value
1789 - */
1790 - setTextboxValue: function(value) {
1791 - var $input = this.$control_input;
1792 - var changed = $input.val() !== value;
1793 - if (changed) {
1794 - $input.val(value).triggerHandler('update');
1795 - this.lastValue = value;
1635 +
1636 + }
1637 + },
1638 +
1639 + selectAll: function() {
1640 + var self = this;
1641 + if (self.settings.mode === 'single') return;
1642 +
1643 + self.$activeItems = Array.prototype.slice.apply(self.$control.children(':not(input)').addClass('active'));
1644 + if (self.$activeItems.length) {
1645 + self.hideInput();
1646 + self.close();
1647 + }
1648 + self.focus();
1649 + },
1650 +
1651 + hideInput: function() {
1652 + var self = this;
1653 +
1654 + self.setTextboxValue('');
1655 + self.$control_input.css({opacity: 0, position: 'absolute', left: self.rtl ? 10000 : 0});
1656 + self.isInputHidden = true;
1657 + },
1658 +
1659 + showInput: function() {
1660 + this.$control_input.css({opacity: 1, position: 'relative', left: 0});
1661 + this.isInputHidden = false;
1662 + },
1663 +
1664 + focus: function() {
1665 + var self = this;
1666 + if (self.isDisabled) return self;
1667 +
1668 + self.ignoreFocus = true;
1669 + self.$control_input[0].focus();
1670 + window.setTimeout(function() {
1671 + self.ignoreFocus = false;
1672 + self.onFocus();
1673 + }, 0);
1674 + return self;
1675 + },
1676 +
1677 + blur: function(dest) {
1678 + this.$control_input[0].blur();
1679 + this.onBlur(null, dest);
1680 + return this;
1681 + },
1682 +
1683 + getScoreFunction: function(query) {
1684 + return this.sifter.getScoreFunction(query, this.getSearchOptions());
1685 + },
1686 +
1687 + getSearchOptions: function() {
1688 + var settings = this.settings;
1689 + var sort = settings.sortField;
1690 + if (typeof sort === 'string') {
1691 + sort = [{field: sort}];
1692 + }
1693 +
1694 + return {
1695 + fields : settings.searchField,
1696 + conjunction : settings.searchConjunction,
1697 + sort : sort,
1698 + nesting : settings.nesting,
1699 + filter : settings.filter,
1700 + respect_word_boundaries : settings.respect_word_boundaries
1701 + };
1702 + },
1703 +
1704 + search: function(query) {
1705 + var i, value, score, result, calculateScore;
1706 + var self = this;
1707 + var settings = self.settings;
1708 + var options = this.getSearchOptions();
1709 +
1710 + if (settings.score) {
1711 + calculateScore = self.settings.score.apply(this, [query]);
1712 + if (typeof calculateScore !== 'function') {
1713 + throw new Error('Selectize "score" setting must be a function that returns a function');
1796 1714 }
1797 - },
1798 -
1799 - /**
1800 - * Returns the value of the control. If multiple items
1801 - * can be selected (e.g. <select multiple>), this returns
1802 - * an array. If only one item can be selected, this
1803 - * returns a string.
1804 - *
1805 - * @returns {mixed}
1806 - */
1807 - getValue: function() {
1808 - if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) {
1809 - return this.items;
1810 - } else {
1811 - return this.items.join(this.settings.delimiter);
1812 - }
1813 - },
1814 -
1815 - /**
1816 - * Resets the selected items to the given value.
1817 - *
1818 - * @param {mixed} value
1819 - */
1820 - setValue: function(value, silent) {
1821 - var events = silent ? [] : ['change'];
1822 -
1823 - debounce_events(this, events, function() {
1824 - this.clear(silent);
1825 - this.addItems(value, silent);
1826 - });
1827 - },
1828 -
1829 - /**
1830 - * Sets the selected item.
1831 - *
1832 - * @param {object} $item
1833 - * @param {object} e (optional)
1834 - */
1835 - setActiveItem: function($item, e) {
1836 - var self = this;
1837 - var eventName;
1838 - var i, idx, begin, end, item, swap;
1839 - var $last;
1840 -
1841 - if (self.settings.mode === 'single') return;
1842 - $item = $($item);
1843 -
1844 - // clear the active selection
1845 - if (!$item.length) {
1846 - $(self.$activeItems).removeClass('active');
1847 - self.$activeItems = [];
1848 - if (self.isFocused) {
1849 - self.showInput();
1715 + }
1716 +
1717 + if (query !== self.lastQuery) {
1718 + if (settings.normalize) query = query.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
1719 + self.lastQuery = query;
1720 + result = self.sifter.search(query, $.extend(options, {score: calculateScore}));
1721 + self.currentResults = result;
1722 + } else {
1723 + result = $.extend(true, {}, self.currentResults);
1724 + }
1725 +
1726 + if (settings.hideSelected) {
1727 + for (i = result.items.length - 1; i >= 0; i--) {
1728 + if (self.items.indexOf(hash_key(result.items[i].id)) !== -1) {
1729 + result.items.splice(i, 1);
1850 1730 }
1851 - return;
1852 1731 }
1853 -
1854 - // modify selection
1855 - eventName = e && e.type.toLowerCase();
1856 -
1857 - if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) {
1858 - $last = self.$control.children('.active:last');
1859 - begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]);
1860 - end = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]);
1861 - if (begin > end) {
1862 - swap = begin;
1863 - begin = end;
1864 - end = swap;
1865 - }
1866 - for (i = begin; i <= end; i++) {
1867 - item = self.$control[0].childNodes[i];
1868 - if (self.$activeItems.indexOf(item) === -1) {
1869 - $(item).addClass('active');
1870 - self.$activeItems.push(item);
1732 + }
1733 +
1734 + return result;
1735 + },
1736 +
1737 + refreshOptions: function(triggerDropdown) {
1738 + var i, j, k, n, groups, groups_order, option, option_html, optgroup, optgroups, html, html_children, has_create_option;
1739 + var $active, $active_before, $create;
1740 +
1741 + if (typeof triggerDropdown === 'undefined') {
1742 + triggerDropdown = true;
1743 + }
1744 +
1745 + var self = this;
1746 + var query = (self.$control_input.val()).trim();
1747 + var results = self.search(query);
1748 + var $dropdown_content = self.$dropdown_content;
1749 + var active_before = self.$activeOption && hash_key(self.$activeOption.attr('data-value'));
1750 +
1751 + n = results.items.length;
1752 + if (typeof self.settings.maxOptions === 'number') {
1753 + n = Math.min(n, self.settings.maxOptions);
1754 + }
1755 +
1756 + groups = {};
1757 + groups_order = [];
1758 +
1759 + for (i = 0; i < n; i++) {
1760 + option = self.options[results.items[i].id];
1761 + option_html = self.render('option', option);
1762 + optgroup = option[self.settings.optgroupField] || '';
1763 + optgroups = Array.isArray(optgroup) ? optgroup : [optgroup];
1764 +
1765 + for (j = 0, k = optgroups && optgroups.length; j < k; j++) {
1766 + optgroup = optgroups[j];
1767 + if (!self.optgroups.hasOwnProperty(optgroup) && typeof self.settings.optionGroupRegister === 'function') {
1768 + var regGroup;
1769 + if (regGroup = self.settings.optionGroupRegister.apply(self, [optgroup])) {
1770 + self.registerOptionGroup(regGroup);
1871 1771 }
1872 1772 }
1873 - e.preventDefault();
1874 - } else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) {
1875 - if ($item.hasClass('active')) {
1876 - idx = self.$activeItems.indexOf($item[0]);
1877 - self.$activeItems.splice(idx, 1);
1878 - $item.removeClass('active');
1879 - } else {
1880 - self.$activeItems.push($item.addClass('active')[0]);
1773 + if (!self.optgroups.hasOwnProperty(optgroup)) {
1774 + optgroup = '';
1881 1775 }
1882 - } else {
1883 - $(self.$activeItems).removeClass('active');
1884 - self.$activeItems = [$item.addClass('active')[0]];
1885 - }
1886 -
1887 - // ensure control has focus
1888 - self.hideInput();
1889 - if (!this.isFocused) {
1890 - self.focus();
1891 - }
1892 - },
1893 -
1894 - /**
1895 - * Sets the selected item in the dropdown menu
1896 - * of available options.
1897 - *
1898 - * @param {object} $object
1899 - * @param {boolean} scroll
1900 - * @param {boolean} animate
1901 - */
1902 - setActiveOption: function($option, scroll, animate) {
1903 - var height_menu, height_item, y;
1904 - var scroll_top, scroll_bottom;
1905 - var self = this;
1906 -
1907 - if (self.$activeOption) self.$activeOption.removeClass('active');
1908 - self.$activeOption = null;
1909 -
1910 - $option = $($option);
1911 - if (!$option.length) return;
1912 -
1913 - self.$activeOption = $option.addClass('active');
1914 -
1915 - if (scroll || !isset(scroll)) {
1916 -
1917 - height_menu = self.$dropdown_content.height();
1918 - height_item = self.$activeOption.outerHeight(true);
1919 - scroll = self.$dropdown_content.scrollTop() || 0;
1920 - y = self.$activeOption.offset().top - self.$dropdown_content.offset().top + scroll;
1921 - scroll_top = y;
1922 - scroll_bottom = y - height_menu + height_item;
1923 -
1924 - if (y + height_item > height_menu + scroll) {
1925 - self.$dropdown_content.stop().animate({scrollTop: scroll_bottom}, animate ? self.settings.scrollDuration : 0);
1926 - } else if (y < scroll) {
1927 - self.$dropdown_content.stop().animate({scrollTop: scroll_top}, animate ? self.settings.scrollDuration : 0);
1776 + if (!groups.hasOwnProperty(optgroup)) {
1777 + groups[optgroup] = document.createDocumentFragment();
1778 + groups_order.push(optgroup);
1928 1779 }
1929 -
1780 + groups[optgroup].appendChild(option_html);
1930 1781 }
1931 - },
1932 -
1933 - /**
1934 - * Selects all items (CTRL + A).
1935 - */
1936 - selectAll: function() {
1937 - var self = this;
1938 - if (self.settings.mode === 'single') return;
1939 -
1940 - self.$activeItems = Array.prototype.slice.apply(self.$control.children(':not(input)').addClass('active'));
1941 - if (self.$activeItems.length) {
1942 - self.hideInput();
1943 - self.close();
1944 - }
1945 - self.focus();
1946 - },
1947 -
1948 - /**
1949 - * Hides the input element out of view, while
1950 - * retaining its focus.
1951 - */
1952 - hideInput: function() {
1953 - var self = this;
1954 -
1955 - self.setTextboxValue('');
1956 - self.$control_input.css({opacity: 0, position: 'absolute', left: self.rtl ? 10000 : -10000});
1957 - self.isInputHidden = true;
1958 - },
1959 -
1960 - /**
1961 - * Restores input visibility.
1962 - */
1963 - showInput: function() {
1964 - this.$control_input.css({opacity: 1, position: 'relative', left: 0});
1965 - this.isInputHidden = false;
1966 - },
1967 -
1968 - /**
1969 - * Gives the control focus.
1970 - */
1971 - focus: function() {
1972 - var self = this;
1973 - if (self.isDisabled) return;
1974 -
1975 - self.ignoreFocus = true;
1976 - self.$control_input[0].focus();
1977 - window.setTimeout(function() {
1978 - self.ignoreFocus = false;
1979 - self.onFocus();
1980 - }, 0);
1981 - },
1982 -
1983 - /**
1984 - * Forces the control out of focus.
1985 - *
1986 - * @param {Element} dest
1987 - */
1988 - blur: function(dest) {
1989 - this.$control_input[0].blur();
1990 - this.onBlur(null, dest);
1991 - },
1992 -
1993 - /**
1994 - * Returns a function that scores an object
1995 - * to show how good of a match it is to the
1996 - * provided query.
1997 - *
1998 - * @param {string} query
1999 - * @param {object} options
2000 - * @return {function}
2001 - */
2002 - getScoreFunction: function(query) {
2003 - return this.sifter.getScoreFunction(query, this.getSearchOptions());
2004 - },
2005 -
2006 - /**
2007 - * Returns search options for sifter (the system
2008 - * for scoring and sorting results).
2009 - *
2010 - * @see https://github.com/brianreavis/sifter.js
2011 - * @return {object}
2012 - */
2013 - getSearchOptions: function() {
2014 - var settings = this.settings;
2015 - var sort = settings.sortField;
2016 - if (typeof sort === 'string') {
2017 - sort = [{field: sort}];
2018 - }
2019 -
2020 - return {
2021 - fields : settings.searchField,
2022 - conjunction : settings.searchConjunction,
2023 - sort : sort
2024 - };
2025 - },
2026 -
2027 - /**
2028 - * Searches through available options and returns
2029 - * a sorted array of matches.
2030 - *
2031 - * Returns an object containing:
2032 - *
2033 - * - query {string}
2034 - * - tokens {array}
2035 - * - total {int}
2036 - * - items {array}
2037 - *
2038 - * @param {string} query
2039 - * @returns {object}
2040 - */
2041 - search: function(query) {
2042 - var i, value, score, result, calculateScore;
2043 - var self = this;
2044 - var settings = self.settings;
2045 - var options = this.getSearchOptions();
2046 -
2047 - // validate user-provided result scoring function
2048 - if (settings.score) {
2049 - calculateScore = self.settings.score.apply(this, [query]);
2050 - if (typeof calculateScore !== 'function') {
2051 - throw new Error('Selectize "score" setting must be a function that returns a function');
2052 - }
2053 - }
2054 -
2055 - // perform search
2056 - if (query !== self.lastQuery) {
2057 - self.lastQuery = query;
2058 - result = self.sifter.search(query, $.extend(options, {score: calculateScore}));
2059 - self.currentResults = result;
1782 + }
1783 +
1784 + if (this.settings.lockOptgroupOrder) {
1785 + groups_order.sort(function(a, b) {
1786 + var a_order = self.optgroups[a] && self.optgroups[a].$order || 0;
1787 + var b_order = self.optgroups[b] && self.optgroups[b].$order || 0;
1788 + return a_order - b_order;
1789 + });
1790 + }
1791 +
1792 + html = document.createDocumentFragment();
1793 + for (i = 0, n = groups_order.length; i < n; i++) {
1794 + optgroup = groups_order[i];
1795 + if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].childNodes.length) {
1796 + html_children = document.createDocumentFragment();
1797 + html_children.appendChild(self.render('optgroup_header', self.optgroups[optgroup]));
1798 + html_children.appendChild(groups[optgroup]);
1799 +
1800 + html.appendChild(self.render('optgroup', $.extend({}, self.optgroups[optgroup], {
1801 + html: domToString(html_children),
1802 + dom: html_children
1803 + })));
2060 1804 } else {
2061 - result = $.extend(true, {}, self.currentResults);
1805 + html.appendChild(groups[optgroup]);
2062 1806 }
2063 -
2064 - // filter out selected items
2065 - if (settings.hideSelected) {
2066 - for (i = result.items.length - 1; i >= 0; i--) {
2067 - if (self.items.indexOf(hash_key(result.items[i].id)) !== -1) {
2068 - result.items.splice(i, 1);
2069 - }
2070 - }
2071 - }
2072 -
2073 - return result;
2074 - },
2075 -
2076 - /**
2077 - * Refreshes the list of available options shown
2078 - * in the autocomplete dropdown menu.
2079 - *
2080 - * @param {boolean} triggerDropdown
2081 - */
2082 - refreshOptions: function(triggerDropdown) {
2083 - var i, j, k, n, groups, groups_order, option, option_html, optgroup, optgroups, html, html_children, has_create_option;
2084 - var $active, $active_before, $create;
2085 -
2086 - if (typeof triggerDropdown === 'undefined') {
2087 - triggerDropdown = true;
2088 - }
2089 -
2090 - var self = this;
2091 - var query = $.trim(self.$control_input.val());
2092 - var results = self.search(query);
2093 - var $dropdown_content = self.$dropdown_content;
2094 - var active_before = self.$activeOption && hash_key(self.$activeOption.attr('data-value'));
2095 -
2096 - // build markup
2097 - n = results.items.length;
2098 - if (typeof self.settings.maxOptions === 'number') {
2099 - n = Math.min(n, self.settings.maxOptions);
2100 - }
2101 -
2102 - // render and group available options individually
2103 - groups = {};
2104 - groups_order = [];
2105 -
2106 - for (i = 0; i < n; i++) {
2107 - option = self.options[results.items[i].id];
2108 - option_html = self.render('option', option);
2109 - optgroup = option[self.settings.optgroupField] || '';
2110 - optgroups = $.isArray(optgroup) ? optgroup : [optgroup];
2111 -
2112 - for (j = 0, k = optgroups && optgroups.length; j < k; j++) {
2113 - optgroup = optgroups[j];
2114 - if (!self.optgroups.hasOwnProperty(optgroup)) {
2115 - optgroup = '';
2116 - }
2117 - if (!groups.hasOwnProperty(optgroup)) {
2118 - groups[optgroup] = [];
2119 - groups_order.push(optgroup);
2120 - }
2121 - groups[optgroup].push(option_html);
2122 - }
2123 - }
2124 -
2125 - // sort optgroups
2126 - if (this.settings.lockOptgroupOrder) {
2127 - groups_order.sort(function(a, b) {
2128 - var a_order = self.optgroups[a].$order || 0;
2129 - var b_order = self.optgroups[b].$order || 0;
2130 - return a_order - b_order;
2131 - });
2132 - }
2133 -
2134 - // render optgroup headers & join groups
2135 - html = [];
2136 - for (i = 0, n = groups_order.length; i < n; i++) {
2137 - optgroup = groups_order[i];
2138 - if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].length) {
2139 - // render the optgroup header and options within it,
2140 - // then pass it to the wrapper template
2141 - html_children = self.render('optgroup_header', self.optgroups[optgroup]) || '';
2142 - html_children += groups[optgroup].join('');
2143 - html.push(self.render('optgroup', $.extend({}, self.optgroups[optgroup], {
2144 - html: html_children
2145 - })));
2146 - } else {
2147 - html.push(groups[optgroup].join(''));
2148 - }
2149 - }
2150 -
2151 - $dropdown_content.html(html.join(''));
2152 -
2153 - // highlight matching terms inline
2154 - if (self.settings.highlight && results.query.length && results.tokens.length) {
1807 + }
1808 +
1809 + $dropdown_content.html(html);
1810 +
1811 + if (self.settings.highlight) {
1812 + $dropdown_content.removeHighlight();
1813 + if (results.query.length && results.tokens.length) {
2155 1814 for (i = 0, n = results.tokens.length; i < n; i++) {
2156 1815 highlight($dropdown_content, results.tokens[i].regex);
2157 1816 }
2158 1817 }
2159 -
2160 - // add "selected" class to selected options
2161 - if (!self.settings.hideSelected) {
2162 - for (i = 0, n = self.items.length; i < n; i++) {
2163 - self.getOption(self.items[i]).addClass('selected');
2164 - }
1818 + }
1819 +
1820 + if (!self.settings.hideSelected) {
1821 + self.$dropdown.find('.selected').removeClass('selected');
1822 +
1823 + for (i = 0, n = self.items.length; i < n; i++) {
1824 + self.getOption(self.items[i]).addClass('selected');
2165 1825 }
2166 -
2167 - // add create option
2168 - has_create_option = self.canCreate(query);
2169 - if (has_create_option) {
1826 + }
1827 +
1828 + if (self.settings.dropdownSize.sizeType !== 'auto' && self.isOpen) {
1829 + self.setupDropdownHeight();
1830 + }
1831 +
1832 + self.positionDropdown();
1833 +
1834 + has_create_option = self.canCreate(query);
1835 + if (has_create_option) {
1836 + if(self.settings.showAddOptionOnCreate) {
2170 1837 $dropdown_content.prepend(self.render('option_create', {input: query}));
2171 1838 $create = $($dropdown_content[0].childNodes[0]);
2172 1839 }
2173 -
2174 - // activate
2175 - self.hasOptions = results.items.length > 0 || has_create_option;
2176 - if (self.hasOptions) {
2177 - if (results.items.length > 0) {
2178 - $active_before = active_before && self.getOption(active_before);
2179 - if ($active_before && $active_before.length) {
2180 - $active = $active_before;
2181 - } else if (self.settings.mode === 'single' && self.items.length) {
2182 - $active = self.getOption(self.items[0]);
2183 - }
2184 - if (!$active || !$active.length) {
2185 - if ($create && !self.settings.addPrecedence) {
2186 - $active = self.getAdjacentOption($create, 1);
2187 - } else {
2188 - $active = $dropdown_content.find('[data-selectable]:first');
2189 - }
2190 - }
2191 - } else {
2192 - $active = $create;
2193 - }
2194 - self.setActiveOption($active);
2195 - if (triggerDropdown && !self.isOpen) { self.open(); }
1840 + }
1841 +
1842 + self.hasOptions = results.items.length > 0 || ( has_create_option && self.settings.showAddOptionOnCreate ) || self.settings.setFirstOptionActive;
1843 +
1844 + if (self.hasOptions) {
1845 + if (results.items.length > 0) {
1846 + $active_before = active_before && self.getOption(active_before);
1847 + if (results.query !== "" && self.settings.setFirstOptionActive) {
1848 + $active = $dropdown_content.find('[data-selectable]:first')
1849 + } else if (results.query !== "" && $active_before && $active_before.length) {
1850 + $active = $active_before;
1851 + } else if (self.settings.mode === 'single' && self.items.length) {
1852 + $active = self.getOption(self.items[0]);
1853 + }
1854 + if (!$active || !$active.length) {
1855 + if ($create && !self.settings.addPrecedence) {
1856 + $active = self.getAdjacentOption($create, 1);
2196 1857 } else {
2197 - self.setActiveOption(null);
2198 - if (triggerDropdown && self.isOpen) { self.close(); }
1858 + $active = $dropdown_content.find('[data-selectable]:first');
2199 1859 }
2200 - },
2201 -
2202 - /**
2203 - * Adds an available option. If it already exists,
2204 - * nothing will happen. Note: this does not refresh
2205 - * the options list dropdown (use `refreshOptions`
2206 - * for that).
2207 - *
2208 - * Usage:
2209 - *
2210 - * this.addOption(data)
2211 - *
2212 - * @param {object|array} data
2213 - */
2214 - addOption: function(data) {
2215 - var i, n, value, self = this;
2216 -
2217 - if ($.isArray(data)) {
2218 - for (i = 0, n = data.length; i < n; i++) {
2219 - self.addOption(data[i]);
2220 - }
2221 - return;
2222 1860 }
2223 -
2224 - if (value = self.registerOption(data)) {
2225 - self.userOptions[value] = true;
2226 - self.lastQuery = null;
2227 - self.trigger('option_add', value, data);
1861 + } else {
1862 + $active = $create;
2228 1863 }
2229 - },
2230 -
2231 - /**
2232 - * Registers an option to the pool of options.
2233 - *
2234 - * @param {object} data
2235 - * @return {boolean|string}
2236 - */
2237 - registerOption: function(data) {
2238 - var key = hash_key(data[this.settings.valueField]);
2239 - if (!key || this.options.hasOwnProperty(key)) return false;
2240 - data.$order = data.$order || ++this.order;
2241 - this.options[key] = data;
2242 - return key;
2243 - },
2244 -
2245 - /**
2246 - * Registers an option group to the pool of option groups.
2247 - *
2248 - * @param {object} data
2249 - * @return {boolean|string}
2250 - */
2251 - registerOptionGroup: function(data) {
2252 - var key = hash_key(data[this.settings.optgroupValueField]);
2253 - if (!key) return false;
2254 -
2255 - data.$order = data.$order || ++this.order;
2256 - this.optgroups[key] = data;
2257 - return key;
2258 - },
2259 -
2260 - /**
2261 - * Registers a new optgroup for options
2262 - * to be bucketed into.
2263 - *
2264 - * @param {string} id
2265 - * @param {object} data
2266 - */
2267 - addOptionGroup: function(id, data) {
2268 - data[this.settings.optgroupValueField] = id;
2269 - if (id = this.registerOptionGroup(data)) {
2270 - this.trigger('optgroup_add', id, data);
1864 + self.setActiveOption($active);
1865 + if (triggerDropdown && !self.isOpen) { self.open(); }
1866 + } else {
1867 + self.setActiveOption(null);
1868 + if (triggerDropdown && self.isOpen) { self.close(); }
1869 + }
1870 + },
1871 +
1872 + addOption: function(data) {
1873 + var i, n, value, self = this;
1874 +
1875 + if (Array.isArray(data)) {
1876 + for (i = 0, n = data.length; i < n; i++) {
1877 + self.addOption(data[i]);
2271 1878 }
2272 - },
2273 -
2274 - /**
2275 - * Removes an existing option group.
2276 - *
2277 - * @param {string} id
2278 - */
2279 - removeOptionGroup: function(id) {
2280 - if (this.optgroups.hasOwnProperty(id)) {
2281 - delete this.optgroups[id];
2282 - this.renderCache = {};
2283 - this.trigger('optgroup_remove', id);
2284 - }
2285 - },
2286 -
2287 - /**
2288 - * Clears all existing option groups.
2289 - */
2290 - clearOptionGroups: function() {
2291 - this.optgroups = {};
1879 + return;
1880 + }
1881 +
1882 + if (value = self.registerOption(data)) {
1883 + self.userOptions[value] = true;
1884 + self.lastQuery = null;
1885 + self.trigger('option_add', value, data);
1886 + }
1887 + },
1888 +
1889 + registerOption: function(data) {
1890 + var key = hash_key(data[this.settings.valueField]);
1891 + if (typeof key === 'undefined' || key === null || this.options.hasOwnProperty(key)) return false;
1892 + data.$order = data.$order || ++this.order;
1893 + this.options[key] = data;
1894 + return key;
1895 + },
1896 +
1897 + registerOptionGroup: function(data) {
1898 + var key = hash_key(data[this.settings.optgroupValueField]);
1899 + if (!key) return false;
1900 +
1901 + data.$order = data.$order || ++this.order;
1902 + this.optgroups[key] = data;
1903 + return key;
1904 + },
1905 +
1906 + addOptionGroup: function(id, data) {
1907 + data[this.settings.optgroupValueField] = id;
1908 + if (id = this.registerOptionGroup(data)) {
1909 + this.trigger('optgroup_add', id, data);
1910 + }
1911 + },
1912 +
1913 + removeOptionGroup: function(id) {
1914 + if (this.optgroups.hasOwnProperty(id)) {
1915 + delete this.optgroups[id];
2292 1916 this.renderCache = {};
2293 - this.trigger('optgroup_clear');
2294 - },
2295 -
2296 - /**
2297 - * Updates an option available for selection. If
2298 - * it is visible in the selected items or options
2299 - * dropdown, it will be re-rendered automatically.
2300 - *
2301 - * @param {string} value
2302 - * @param {object} data
2303 - */
2304 - updateOption: function(value, data) {
2305 - var self = this;
2306 - var $item, $item_new;
2307 - var value_new, index_item, cache_items, cache_options, order_old;
2308 -
2309 - value = hash_key(value);
2310 - value_new = hash_key(data[self.settings.valueField]);
2311 -
2312 - // sanity checks
2313 - if (value === null) return;
2314 - if (!self.options.hasOwnProperty(value)) return;
2315 - if (typeof value_new !== 'string') throw new Error('Value must be set in option data');
2316 -
2317 - order_old = self.options[value].$order;
2318 -
2319 - // update references
2320 - if (value_new !== value) {
2321 - delete self.options[value];
2322 - index_item = self.items.indexOf(value);
2323 - if (index_item !== -1) {
2324 - self.items.splice(index_item, 1, value_new);
2325 - }
1917 + this.trigger('optgroup_remove', id);
1918 + }
1919 + },
1920 +
1921 + clearOptionGroups: function() {
1922 + this.optgroups = {};
1923 + this.renderCache = {};
1924 + this.trigger('optgroup_clear');
1925 + },
1926 +
1927 + updateOption: function(value, data) {
1928 + var self = this;
1929 + var $item, $item_new;
1930 + var value_new, index_item, cache_items, cache_options, order_old;
1931 +
1932 + value = hash_key(value);
1933 + value_new = hash_key(data[self.settings.valueField]);
1934 +
1935 + if (value === null) return;
1936 + if (!self.options.hasOwnProperty(value)) return;
1937 + if (typeof value_new !== 'string') throw new Error('Value must be set in option data');
1938 +
1939 + order_old = self.options[value].$order;
1940 +
1941 + if (value_new !== value) {
1942 + delete self.options[value];
1943 + index_item = self.items.indexOf(value);
1944 + if (index_item !== -1) {
1945 + self.items.splice(index_item, 1, value_new);
2326 1946 }
2327 - data.$order = data.$order || order_old;
2328 - self.options[value_new] = data;
2329 -
2330 - // invalidate render cache
2331 - cache_items = self.renderCache['item'];
2332 - cache_options = self.renderCache['option'];
2333 -
2334 - if (cache_items) {
2335 - delete cache_items[value];
2336 - delete cache_items[value_new];
1947 + }
1948 + data.$order = data.$order || order_old;
1949 + self.options[value_new] = data;
1950 +
1951 + cache_items = self.renderCache['item'];
1952 + cache_options = self.renderCache['option'];
1953 +
1954 + if (cache_items) {
1955 + delete cache_items[value];
1956 + delete cache_items[value_new];
1957 + }
1958 + if (cache_options) {
1959 + delete cache_options[value];
1960 + delete cache_options[value_new];
1961 + }
1962 +
1963 + if (self.items.indexOf(value_new) !== -1) {
1964 + $item = self.getItem(value);
1965 + $item_new = $(self.render('item', data));
1966 + if ($item.hasClass('active')) $item_new.addClass('active');
1967 + $item.replaceWith($item_new);
1968 + }
1969 +
1970 + self.lastQuery = null;
1971 +
1972 + if (self.isOpen) {
1973 + self.refreshOptions(false);
1974 + }
1975 + },
1976 +
1977 + removeOption: function(value, silent) {
1978 + var self = this;
1979 + value = hash_key(value);
1980 +
1981 + var cache_items = self.renderCache['item'];
1982 + var cache_options = self.renderCache['option'];
1983 + if (cache_items) delete cache_items[value];
1984 + if (cache_options) delete cache_options[value];
1985 +
1986 + delete self.userOptions[value];
1987 + delete self.options[value];
1988 + self.lastQuery = null;
1989 + self.trigger('option_remove', value);
1990 + self.removeItem(value, silent);
1991 + },
1992 +
1993 + clearOptions: function(silent) {
1994 + var self = this;
1995 +
1996 + self.loadedSearches = {};
1997 + self.userOptions = {};
1998 + self.renderCache = {};
1999 + var options = self.options;
2000 + $.each(self.options, function(key, value) {
2001 + if(self.items.indexOf(key) == -1) {
2002 + delete options[key];
2337 2003 }
2338 - if (cache_options) {
2339 - delete cache_options[value];
2340 - delete cache_options[value_new];
2004 + });
2005 + self.options = self.sifter.items = options;
2006 + self.lastQuery = null;
2007 + self.trigger('option_clear');
2008 + self.clear(silent);
2009 + },
2010 +
2011 + getOption: function(value) {
2012 + return this.getElementWithValue(value, this.$dropdown_content.find('[data-selectable]'));
2013 + },
2014 +
2015 + getFirstOption: function() {
2016 + var $options = this.$dropdown.find('[data-selectable]');
2017 + return $options.length > 0 ? $options.eq(0) : $();
2018 + },
2019 +
2020 + getAdjacentOption: function($option, direction) {
2021 + var $options = this.$dropdown.find('[data-selectable]');
2022 + var index = $options.index($option) + direction;
2023 +
2024 + return index >= 0 && index < $options.length ? $options.eq(index) : $();
2025 + },
2026 +
2027 + getElementWithValue: function(value, $els) {
2028 + value = hash_key(value);
2029 +
2030 + if (typeof value !== 'undefined' && value !== null) {
2031 + for (var i = 0, n = $els.length; i < n; i++) {
2032 + if ($els[i].getAttribute('data-value') === value) {
2033 + return $($els[i]);
2034 + }
2341 2035 }
2342 -
2343 - // update the item if it's selected
2344 - if (self.items.indexOf(value_new) !== -1) {
2345 - $item = self.getItem(value);
2346 - $item_new = $(self.render('item', data));
2347 - if ($item.hasClass('active')) $item_new.addClass('active');
2348 - $item.replaceWith($item_new);
2036 + }
2037 +
2038 + return $();
2039 + },
2040 +
2041 + getElementWithTextContent: function(textContent, ignoreCase ,$els) {
2042 + textContent = hash_key(textContent);
2043 +
2044 + if (typeof textContent !== 'undefined' && textContent !== null) {
2045 + for (var i = 0, n = $els.length; i < n; i++) {
2046 + var eleTextContent = $els[i].textContent
2047 + if (ignoreCase == true) {
2048 + eleTextContent = (eleTextContent !== null) ? eleTextContent.toLowerCase() : null;
2049 + textContent = textContent.toLowerCase();
2050 + }
2051 + if (eleTextContent === textContent) {
2052 + return $($els[i]);
2053 + }
2349 2054 }
2350 -
2351 - // invalidate last query because we might have updated the sortField
2352 - self.lastQuery = null;
2353 -
2354 - // update dropdown contents
2355 - if (self.isOpen) {
2356 - self.refreshOptions(false);
2357 - }
2358 - },
2359 -
2360 - /**
2361 - * Removes a single option.
2362 - *
2363 - * @param {string} value
2364 - * @param {boolean} silent
2365 - */
2366 - removeOption: function(value, silent) {
2055 + }
2056 +
2057 + return $();
2058 + },
2059 +
2060 + getItem: function(value) {
2061 + return this.getElementWithValue(value, this.$control.children());
2062 + },
2063 +
2064 + getFirstItemMatchedByTextContent: function(textContent, ignoreCase) {
2065 + ignoreCase = (ignoreCase !== null && ignoreCase === true) ? true : false;
2066 + return this.getElementWithTextContent(textContent, ignoreCase, this.$dropdown_content.find('[data-selectable]'));
2067 + },
2068 +
2069 + addItems: function(values, silent) {
2070 + this.buffer = document.createDocumentFragment();
2071 +
2072 + var childNodes = this.$control[0].childNodes;
2073 + for (var i = 0; i < childNodes.length; i++) {
2074 + this.buffer.appendChild(childNodes[i]);
2075 + }
2076 +
2077 + var items = Array.isArray(values) ? values : [values];
2078 + for (var i = 0, n = items.length; i < n; i++) {
2079 + this.isPending = (i < n - 1);
2080 + this.addItem(items[i], silent);
2081 + }
2082 +
2083 + var control = this.$control[0];
2084 + control.insertBefore(this.buffer, control.firstChild);
2085 +
2086 + this.buffer = null;
2087 + },
2088 +
2089 + addItem: function(value, silent) {
2090 + var events = silent ? [] : ['change'];
2091 +
2092 + debounce_events(this, events, function() {
2093 + var $item, $option, $options;
2367 2094 var self = this;
2095 + var inputMode = self.settings.mode;
2096 + var i, active, value_next, wasFull;
2368 2097 value = hash_key(value);
2369 -
2370 - var cache_items = self.renderCache['item'];
2371 - var cache_options = self.renderCache['option'];
2372 - if (cache_items) delete cache_items[value];
2373 - if (cache_options) delete cache_options[value];
2374 -
2375 - delete self.userOptions[value];
2376 - delete self.options[value];
2377 - self.lastQuery = null;
2378 - self.trigger('option_remove', value);
2379 - self.removeItem(value, silent);
2380 - },
2381 -
2382 - /**
2383 - * Clears all options.
2384 - */
2385 - clearOptions: function() {
2386 - var self = this;
2387 -
2388 - self.loadedSearches = {};
2389 - self.userOptions = {};
2390 - self.renderCache = {};
2391 - self.options = self.sifter.items = {};
2392 - self.lastQuery = null;
2393 - self.trigger('option_clear');
2394 - self.clear();
2395 - },
2396 -
2397 - /**
2398 - * Returns the jQuery element of the option
2399 - * matching the given value.
2400 - *
2401 - * @param {string} value
2402 - * @returns {object}
2403 - */
2404 - getOption: function(value) {
2405 - return this.getElementWithValue(value, this.$dropdown_content.find('[data-selectable]'));
2406 - },
2407 -
2408 - /**
2409 - * Returns the jQuery element of the next or
2410 - * previous selectable option.
2411 - *
2412 - * @param {object} $option
2413 - * @param {int} direction can be 1 for next or -1 for previous
2414 - * @return {object}
2415 - */
2416 - getAdjacentOption: function($option, direction) {
2417 - var $options = this.$dropdown.find('[data-selectable]');
2418 - var index = $options.index($option) + direction;
2419 -
2420 - return index >= 0 && index < $options.length ? $options.eq(index) : $();
2421 - },
2422 -
2423 - /**
2424 - * Finds the first element with a "data-value" attribute
2425 - * that matches the given value.
2426 - *
2427 - * @param {mixed} value
2428 - * @param {object} $els
2429 - * @return {object}
2430 - */
2431 - getElementWithValue: function(value, $els) {
2432 - value = hash_key(value);
2433 -
2434 - if (typeof value !== 'undefined' && value !== null) {
2435 - for (var i = 0, n = $els.length; i < n; i++) {
2436 - if ($els[i].getAttribute('data-value') === value) {
2437 - return $($els[i]);
2438 - }
2439 - }
2098 +
2099 + if (self.items.indexOf(value) !== -1) {
2100 + if (inputMode === 'single') self.close();
2101 + return;
2440 2102 }
2441 -
2442 - return $();
2443 - },
2444 -
2445 - /**
2446 - * Returns the jQuery element of the item
2447 - * matching the given value.
2448 - *
2449 - * @param {string} value
2450 - * @returns {object}
2451 - */
2452 - getItem: function(value) {
2453 - return this.getElementWithValue(value, this.$control.children());
2454 - },
2455 -
2456 - /**
2457 - * "Selects" multiple items at once. Adds them to the list
2458 - * at the current caret position.
2459 - *
2460 - * @param {string} value
2461 - * @param {boolean} silent
2462 - */
2463 - addItems: function(values, silent) {
2464 - var items = $.isArray(values) ? values : [values];
2465 - for (var i = 0, n = items.length; i < n; i++) {
2466 - this.isPending = (i < n - 1);
2467 - this.addItem(items[i], silent);
2103 +
2104 + if (!self.options.hasOwnProperty(value)) return;
2105 + if (inputMode === 'single') self.clear(silent);
2106 + if (inputMode === 'multi' && self.isFull()) return;
2107 +
2108 + $item = $(self.render('item', self.options[value]));
2109 + wasFull = self.isFull();
2110 + self.items.splice(self.caretPos, 0, value);
2111 + self.insertAtCaret($item);
2112 + if (!self.isPending || (!wasFull && self.isFull())) {
2113 + self.refreshState();
2468 2114 }
2469 - },
2470 -
2471 - /**
2472 - * "Selects" an item. Adds it to the list
2473 - * at the current caret position.
2474 - *
2475 - * @param {string} value
2476 - * @param {boolean} silent
2477 - */
2478 - addItem: function(value, silent) {
2479 - var events = silent ? [] : ['change'];
2480 -
2481 - debounce_events(this, events, function() {
2482 - var $item, $option, $options;
2483 - var self = this;
2484 - var inputMode = self.settings.mode;
2485 - var i, active, value_next, wasFull;
2486 - value = hash_key(value);
2487 -
2488 - if (self.items.indexOf(value) !== -1) {
2489 - if (inputMode === 'single') self.close();
2490 - return;
2115 +
2116 + if (self.isSetup) {
2117 + $options = self.$dropdown_content.find('[data-selectable]');
2118 +
2119 + if (!self.isPending) {
2120 + $option = self.getOption(value);
2121 + value_next = self.getAdjacentOption($option, 1).attr('data-value');
2122 + self.refreshOptions(self.isFocused && inputMode !== 'single');
2123 + if (value_next) {
2124 + self.setActiveOption(self.getOption(value_next));
2125 + }
2491 2126 }
2492 -
2493 - if (!self.options.hasOwnProperty(value)) return;
2494 - if (inputMode === 'single') self.clear(silent);
2495 - if (inputMode === 'multi' && self.isFull()) return;
2496 -
2497 - $item = $(self.render('item', self.options[value]));
2498 - wasFull = self.isFull();
2499 - self.items.splice(self.caretPos, 0, value);
2500 - self.insertAtCaret($item);
2501 - if (!self.isPending || (!wasFull && self.isFull())) {
2502 - self.refreshState();
2127 +
2128 + if (!$options.length || self.isFull()) {
2129 + self.close();
2130 + } else if (!self.isPending) {
2131 + self.positionDropdown();
2503 2132 }
2504 -
2505 - if (self.isSetup) {
2506 - $options = self.$dropdown_content.find('[data-selectable]');
2507 -
2508 - // update menu / remove the option (if this is not one item being added as part of series)
2509 - if (!self.isPending) {
2510 - $option = self.getOption(value);
2511 - value_next = self.getAdjacentOption($option, 1).attr('data-value');
2512 - self.refreshOptions(self.isFocused && inputMode !== 'single');
2513 - if (value_next) {
2514 - self.setActiveOption(self.getOption(value_next));
2515 - }
2516 - }
2517 -
2518 - // hide the menu if the maximum number of items have been selected or no options are left
2519 - if (!$options.length || self.isFull()) {
2520 - self.close();
2521 - } else {
2522 - self.positionDropdown();
2523 - }
2524 -
2525 - self.updatePlaceholder();
2526 - self.trigger('item_add', value, $item);
2133 +
2134 + self.updatePlaceholder();
2135 + self.trigger('item_add', value, $item);
2136 +
2137 + if (!self.isPending) {
2527 2138 self.updateOriginalInput({silent: silent});
2528 2139 }
2529 - });
2530 - },
2531 -
2532 - /**
2533 - * Removes the selected item matching
2534 - * the provided value.
2535 - *
2536 - * @param {string} value
2537 - */
2538 - removeItem: function(value, silent) {
2539 - var self = this;
2540 - var $item, i, idx;
2541 -
2542 - $item = (typeof value === 'object') ? value : self.getItem(value);
2543 - value = hash_key($item.attr('data-value'));
2544 - i = self.items.indexOf(value);
2545 -
2546 - if (i !== -1) {
2547 - $item.remove();
2548 - if ($item.hasClass('active')) {
2549 - idx = self.$activeItems.indexOf($item[0]);
2550 - self.$activeItems.splice(idx, 1);
2551 - }
2552 -
2553 - self.items.splice(i, 1);
2554 - self.lastQuery = null;
2555 - if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) {
2556 - self.removeOption(value, silent);
2557 - }
2558 -
2559 - if (i < self.caretPos) {
2560 - self.setCaret(self.caretPos - 1);
2561 - }
2562 -
2563 - self.refreshState();
2564 - self.updatePlaceholder();
2565 - self.updateOriginalInput({silent: silent});
2566 - self.positionDropdown();
2567 - self.trigger('item_remove', value, $item);
2568 2140 }
2569 - },
2570 -
2571 - /**
2572 - * Invokes the `create` method provided in the
2573 - * selectize options that should provide the data
2574 - * for the new item, given the user input.
2575 - *
2576 - * Once this completes, it will be added
2577 - * to the item list.
2578 - *
2579 - * @param {string} value
2580 - * @param {boolean} [triggerDropdown]
2581 - * @param {function} [callback]
2582 - * @return {boolean}
2583 - */
2584 - createItem: function(input, triggerDropdown) {
2585 - var self = this;
2586 - var caret = self.caretPos;
2587 - input = input || $.trim(self.$control_input.val() || '');
2588 -
2589 - var callback = arguments[arguments.length - 1];
2590 - if (typeof callback !== 'function') callback = function() {};
2591 -
2592 - if (typeof triggerDropdown !== 'boolean') {
2593 - triggerDropdown = true;
2141 + });
2142 + },
2143 +
2144 + removeItem: function(value, silent) {
2145 + var self = this;
2146 + var $item, i, idx;
2147 +
2148 + $item = (value instanceof $) ? value : self.getItem(value);
2149 + value = hash_key($item.attr('data-value'));
2150 + i = self.items.indexOf(value);
2151 +
2152 + if (i !== -1) {
2153 + self.trigger('item_before_remove', value, $item);
2154 + $item.remove();
2155 + if ($item.hasClass('active')) {
2156 + $item.removeClass('active');
2157 + idx = self.$activeItems.indexOf($item[0]);
2158 + self.$activeItems.splice(idx, 1);
2159 + $item.removeClass('active');
2594 2160 }
2595 -
2596 - if (!self.canCreate(input)) {
2597 - callback();
2598 - return false;
2161 +
2162 + self.items.splice(i, 1);
2163 + self.lastQuery = null;
2164 + if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) {
2165 + self.removeOption(value, silent);
2599 2166 }
2600 -
2601 - self.lock();
2602 -
2603 - var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) {
2604 - var data = {};
2605 - data[self.settings.labelField] = input;
2606 - data[self.settings.valueField] = input;
2607 - return data;
2608 - };
2609 -
2610 - var create = once(function(data) {
2611 - self.unlock();
2612 -
2613 - if (!data || typeof data !== 'object') return callback();
2614 - var value = hash_key(data[self.settings.valueField]);
2615 - if (typeof value !== 'string') return callback();
2616 -
2617 - self.setTextboxValue('');
2618 - self.addOption(data);
2619 - self.setCaret(caret);
2620 - self.addItem(value);
2621 - self.refreshOptions(triggerDropdown && self.settings.mode !== 'single');
2622 - callback(data);
2623 - });
2624 -
2625 - var output = setup.apply(this, [input, create]);
2626 - if (typeof output !== 'undefined') {
2627 - create(output);
2167 +
2168 + if (i < self.caretPos) {
2169 + self.setCaret(self.caretPos - 1);
2628 2170 }
2629 -
2630 - return true;
2631 - },
2632 -
2633 - /**
2634 - * Re-renders the selected item lists.
2635 - */
2636 - refreshItems: function() {
2637 - this.lastQuery = null;
2638 -
2639 - if (this.isSetup) {
2640 - this.addItem(this.items);
2641 - }
2642 -
2643 - this.refreshState();
2644 - this.updateOriginalInput();
2645 - },
2646 -
2647 - /**
2648 - * Updates all state-dependent attributes
2649 - * and CSS classes.
2650 - */
2651 - refreshState: function() {
2652 - var invalid, self = this;
2653 - if (self.isRequired) {
2654 - if (self.items.length) self.isInvalid = false;
2655 - self.$control_input.prop('required', invalid);
2656 - }
2657 - self.refreshClasses();
2658 - },
2659 -
2660 - /**
2661 - * Updates all state-dependent CSS classes.
2662 - */
2663 - refreshClasses: function() {
2664 - var self = this;
2665 - var isFull = self.isFull();
2666 - var isLocked = self.isLocked;
2667 -
2668 - self.$wrapper
2669 - .toggleClass('rtl', self.rtl);
2670 -
2671 - self.$control
2672 - .toggleClass('focus', self.isFocused)
2673 - .toggleClass('disabled', self.isDisabled)
2674 - .toggleClass('required', self.isRequired)
2675 - .toggleClass('invalid', self.isInvalid)
2676 - .toggleClass('locked', isLocked)
2677 - .toggleClass('full', isFull).toggleClass('not-full', !isFull)
2678 - .toggleClass('input-active', self.isFocused && !self.isInputHidden)
2679 - .toggleClass('dropdown-active', self.isOpen)
2680 - .toggleClass('has-options', !$.isEmptyObject(self.options))
2681 - .toggleClass('has-items', self.items.length > 0);
2682 -
2683 - self.$control_input.data('grow', !isFull && !isLocked);
2684 - },
2685 -
2686 - /**
2687 - * Determines whether or not more items can be added
2688 - * to the control without exceeding the user-defined maximum.
2689 - *
2690 - * @returns {boolean}
2691 - */
2692 - isFull: function() {
2693 - return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems;
2694 - },
2695 -
2696 - /**
2697 - * Refreshes the original <select> or <input>
2698 - * element to reflect the current state.
2699 - */
2700 - updateOriginalInput: function(opts) {
2701 - var i, n, options, label, self = this;
2702 - opts = opts || {};
2703 -
2704 - if (self.tagType === TAG_SELECT) {
2705 - options = [];
2706 - for (i = 0, n = self.items.length; i < n; i++) {
2707 - label = self.options[self.items[i]][self.settings.labelField] || '';
2708 - options.push('<option value="' + escape_html(self.items[i]) + '" selected="selected">' + escape_html(label) + '</option>');
2171 +
2172 + self.refreshState();
2173 + self.updatePlaceholder();
2174 + self.updateOriginalInput({silent: silent});
2175 + self.positionDropdown();
2176 + self.trigger('item_remove', value, $item);
2177 + }
2178 + },
2179 +
2180 + createItem: function(input, triggerDropdown) {
2181 + var self = this;
2182 + var caret = self.caretPos;
2183 + input = input || (self.$control_input.val() || '').trim();
2184 +
2185 + var callback = arguments[arguments.length - 1];
2186 + if (typeof callback !== 'function') callback = function() {};
2187 +
2188 + if (typeof triggerDropdown !== 'boolean') {
2189 + triggerDropdown = true;
2190 + }
2191 +
2192 + if (!self.canCreate(input)) {
2193 + callback();
2194 + return false;
2195 + }
2196 +
2197 + self.lock();
2198 +
2199 + var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) {
2200 + var data = {};
2201 + data[self.settings.labelField] = input;
2202 + var key = input;
2203 + if ( self.settings.formatValueToKey && typeof self.settings.formatValueToKey === 'function' ) {
2204 + key = self.settings.formatValueToKey.apply(this, [key]);
2205 + if (key === null || typeof key === 'undefined' || typeof key === 'object' || typeof key === 'function') {
2206 + throw new Error('Selectize "formatValueToKey" setting must be a function that returns a value other than object or function.');
2709 2207 }
2710 - if (!options.length && !this.$input.attr('multiple')) {
2711 - options.push('<option value="" selected="selected"></option>');
2712 - }
2713 - self.$input.html(options.join(''));
2714 - } else {
2715 - self.$input.val(self.getValue());
2716 - self.$input.attr('value',self.$input.val());
2717 2208 }
2718 -
2719 - if (self.isSetup) {
2720 - if (!opts.silent) {
2721 - self.trigger('change', self.$input.val());
2209 + data[self.settings.valueField] = key;
2210 + return data;
2211 + };
2212 +
2213 + var create = once(function(data) {
2214 + self.unlock();
2215 +
2216 + if (!data || typeof data !== 'object') return callback();
2217 + var value = hash_key(data[self.settings.valueField]);
2218 + if (typeof value !== 'string') return callback();
2219 +
2220 + self.setTextboxValue('');
2221 + self.addOption(data);
2222 + self.setCaret(caret);
2223 + self.addItem(value);
2224 + self.refreshOptions(triggerDropdown && self.settings.mode !== 'single');
2225 + callback(data);
2226 + });
2227 +
2228 + var output = setup.apply(this, [input, create]);
2229 + if (typeof output !== 'undefined') {
2230 + create(output);
2231 + }
2232 +
2233 + return true;
2234 + },
2235 +
2236 + refreshItems: function(silent) {
2237 + this.lastQuery = null;
2238 +
2239 + if (this.isSetup) {
2240 + this.addItem(this.items, silent);
2241 + }
2242 +
2243 + this.refreshState();
2244 + this.updateOriginalInput({silent: silent});
2245 + },
2246 +
2247 + refreshState: function() {
2248 + this.refreshValidityState();
2249 + this.refreshClasses();
2250 + },
2251 +
2252 + refreshValidityState: function() {
2253 + if (!this.isRequired) return false;
2254 +
2255 + var invalid = !this.items.length;
2256 +
2257 + this.isInvalid = invalid;
2258 + this.$control_input.prop('required', invalid);
2259 + this.$input.prop('required', !invalid);
2260 + },
2261 +
2262 + refreshClasses: function() {
2263 + var self = this;
2264 + var isFull = self.isFull();
2265 + var isLocked = self.isLocked;
2266 +
2267 + self.$wrapper
2268 + .toggleClass('rtl', self.rtl);
2269 +
2270 + self.$control
2271 + .toggleClass('focus', self.isFocused)
2272 + .toggleClass('disabled', self.isDisabled)
2273 + .toggleClass('required', self.isRequired)
2274 + .toggleClass('invalid', self.isInvalid)
2275 + .toggleClass('locked', isLocked)
2276 + .toggleClass('full', isFull).toggleClass('not-full', !isFull)
2277 + .toggleClass('input-active', self.isFocused && !self.isInputHidden)
2278 + .toggleClass('dropdown-active', self.isOpen)
2279 + .toggleClass('has-options', !$.isEmptyObject(self.options))
2280 + .toggleClass('has-items', self.items.length > 0);
2281 +
2282 + self.$control_input.data('grow', !isFull && !isLocked);
2283 + },
2284 +
2285 + isFull: function() {
2286 + return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems;
2287 + },
2288 +
2289 + updateOriginalInput: function(opts) {
2290 + var i, n, existing, fresh, old, $options, label, value, values, self = this;
2291 + opts = opts || {};
2292 +
2293 + if (self.tagType === TAG_SELECT) {
2294 + $options = self.$input.find('option');
2295 + existing = [];
2296 + fresh = [];
2297 + old = [];
2298 + values = [];
2299 +
2300 + $options.get().forEach(function(option) {
2301 + existing.push(option.value);
2302 + });
2303 +
2304 + self.items.forEach(function(item) {
2305 + label = self.options[item][self.settings.labelField] || '';
2306 +
2307 + values.push(item);
2308 +
2309 + if (existing.indexOf(item) != -1) {
2310 + return;
2722 2311 }
2312 +
2313 + fresh.push('<option value="' + escape_html(item) + '" selected="selected">' + escape_html(label) + '</option>');
2314 + });
2315 +
2316 + old = existing.filter(function(value) {
2317 + return values.indexOf(value) < 0;
2318 + }).map(function(value) {
2319 + return 'option[value="' + escape_html(value) + '"]';
2320 + });
2321 +
2322 + if (existing.length - old.length + fresh.length === 0 && !self.$input.attr('multiple')) {
2323 + fresh.push('<option value="" selected="selected"></option>');
2723 2324 }
2724 - },
2725 -
2726 - /**
2727 - * Shows/hide the input placeholder depending
2728 - * on if there items in the list already.
2729 - */
2730 - updatePlaceholder: function() {
2731 - if (!this.settings.placeholder) return;
2732 - var $input = this.$control_input;
2733 -
2734 - if (this.items.length) {
2735 - $input.removeAttr('placeholder');
2736 - } else {
2737 - $input.attr('placeholder', this.settings.placeholder);
2325 +
2326 + self.$input.find(old.join(', ')).remove();
2327 + self.$input.append(fresh.join(''));
2328 + } else {
2329 + self.$input.val(self.getValue());
2330 + self.$input.attr('value',self.$input.val());
2331 + }
2332 +
2333 + if (self.isSetup) {
2334 + if (!opts.silent) {
2335 + self.trigger('change', self.$input.val());
2738 2336 }
2739 - $input.triggerHandler('update', {force: true});
2740 - },
2741 -
2742 - /**
2743 - * Shows the autocomplete dropdown containing
2744 - * the available options.
2745 - */
2746 - open: function() {
2747 - var self = this;
2748 -
2749 - if (self.isLocked || self.isOpen || (self.settings.mode === 'multi' && self.isFull())) return;
2750 - self.focus();
2751 - self.isOpen = true;
2752 - self.refreshState();
2753 - self.$dropdown.css({visibility: 'hidden', display: 'block'});
2754 - self.positionDropdown();
2755 - self.$dropdown.css({visibility: 'visible'});
2756 - self.trigger('dropdown_open', self.$dropdown);
2757 - },
2758 -
2759 - /**
2760 - * Closes the autocomplete dropdown menu.
2761 - */
2762 - close: function() {
2763 - var self = this;
2764 - var trigger = self.isOpen;
2765 -
2766 - if (self.settings.mode === 'single' && self.items.length) {
2767 - self.hideInput();
2337 + }
2338 + },
2339 +
2340 + updatePlaceholder: function() {
2341 + if (!this.settings.placeholder) return;
2342 + var $input = this.$control_input;
2343 +
2344 + if (this.items.length) {
2345 + $input.removeAttr('placeholder');
2346 + } else {
2347 + $input.attr('placeholder', this.settings.placeholder);
2348 + }
2349 + $input.triggerHandler('update', {force: true});
2350 + },
2351 +
2352 + open: function() {
2353 + var self = this;
2354 +
2355 + if (
2356 + self.isLocked ||
2357 + self.isOpen ||
2358 + (self.settings.mode === "multi" && self.isFull())
2359 + )
2360 + return;
2361 + self.focus();
2362 + self.isOpen = true;
2363 + self.refreshState();
2364 + self.$dropdown.css({ visibility: 'hidden', display: 'block' });
2365 + self.setupDropdownHeight();
2366 + self.positionDropdown();
2367 + self.$dropdown.css({visibility: 'visible'});
2368 + self.trigger('dropdown_open', self.$dropdown);
2369 + },
2370 +
2371 + close: function() {
2372 + var self = this;
2373 + var trigger = self.isOpen;
2374 +
2375 + if (self.settings.mode === 'single' && self.items.length) {
2376 + self.hideInput();
2377 +
2378 + if (self.isBlurring) {
2379 + self.$control_input[0].blur();
2768 2380 }
2769 -
2770 - self.isOpen = false;
2771 - self.$dropdown.hide();
2772 - self.setActiveOption(null);
2773 - self.refreshState();
2774 -
2775 - if (trigger) self.trigger('dropdown_close', self.$dropdown);
2776 - },
2777 -
2778 - /**
2779 - * Calculates and applies the appropriate
2780 - * position of the dropdown.
2781 - */
2782 - positionDropdown: function() {
2783 - var $control = this.$control;
2784 - var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position();
2785 - offset.top += $control.outerHeight(true);
2786 -
2787 - this.$dropdown.css({
2788 - width : $control.outerWidth(),
2789 - top : offset.top,
2790 - left : offset.left
2791 - });
2792 - },
2793 -
2794 - /**
2795 - * Resets / clears all selected items
2796 - * from the control.
2797 - *
2798 - * @param {boolean} silent
2799 - */
2800 - clear: function(silent) {
2801 - var self = this;
2802 -
2803 - if (!self.items.length) return;
2804 - self.$control.children(':not(input)').remove();
2805 - self.items = [];
2806 - self.lastQuery = null;
2807 - self.setCaret(0);
2808 - self.setActiveItem(null);
2809 - self.updatePlaceholder();
2810 - self.updateOriginalInput({silent: silent});
2811 - self.refreshState();
2812 - self.showInput();
2813 - self.trigger('clear');
2814 - },
2815 -
2816 - /**
2817 - * A helper method for inserting an element
2818 - * at the current caret position.
2819 - *
2820 - * @param {object} $el
2821 - */
2822 - insertAtCaret: function($el) {
2823 - var caret = Math.min(this.caretPos, this.items.length);
2824 - if (caret === 0) {
2825 - this.$control.prepend($el);
2381 + }
2382 +
2383 + self.isOpen = false;
2384 + self.$dropdown.hide();
2385 + self.setActiveOption(null);
2386 + self.refreshState();
2387 +
2388 + if (trigger) self.trigger('dropdown_close', self.$dropdown);
2389 + },
2390 +
2391 + positionDropdown: function() {
2392 + var $control = this.$control;
2393 + var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position();
2394 + offset.top += $control.outerHeight(true);
2395 + var w = this.$wrapper[0].style.width !== 'fit-content' ? this.settings.dropdownParent === 'body' ? 'max-content' : '100%' : 'max-content';
2396 + if (this.settings.minWidth && this.settings.minWidth > w)
2397 + {
2398 + w = this.settings.minWidth;
2399 + }
2400 +
2401 + if (this.settings.dropdownParent !== 'body' && w === 'max-content' && $control.outerWidth(true) >= this.$dropdown.outerWidth(true)) {
2402 + w = '100%';
2403 + }
2404 +
2405 + this.$dropdown.css({
2406 + width : w,
2407 + minWidth : $control.outerWidth(true),
2408 + top : offset.top,
2409 + left : offset.left
2410 + });
2411 + },
2412 +
2413 + setupDropdownHeight: function () {
2414 + if (typeof this.settings.dropdownSize === 'object' && this.settings.dropdownSize.sizeType !== 'auto') {
2415 + var height = this.settings.dropdownSize.sizeValue;
2416 +
2417 + if (this.settings.dropdownSize.sizeType === 'numberItems') {
2418 + var $items = this.$dropdown_content.find('*').not('.optgroup, .highlight').not(this.settings.ignoreOnDropwdownHeight);
2419 + var totalHeight = 0;
2420 + var marginTop = 0;
2421 + var marginBottom = 0;
2422 + var separatorHeight = 0;
2423 +
2424 +
2425 + for (var i = 0; i < height; i++) {
2426 + var $item = $($items[i]);
2427 +
2428 + if ($item.length === 0) {
2429 + break;
2430 + }
2431 +
2432 + totalHeight += $item.outerHeight(true);
2433 + if (typeof $item.data('selectable') == 'undefined') {
2434 + if ($item.hasClass('optgroup-header')) {
2435 + var styles = window.getComputedStyle($item.parent()[0], ':before');
2436 +
2437 + if (styles) {
2438 + marginTop = styles.marginTop ? Number(styles.marginTop.replace(/\W*(\w)\w*/g, '$1')) : 0;
2439 + marginBottom = styles.marginBottom ? Number(styles.marginBottom.replace(/\W*(\w)\w*/g, '$1')) : 0;
2440 + separatorHeight = styles.borderTopWidth ? Number(styles.borderTopWidth.replace(/\W*(\w)\w*/g, '$1')) : 0;
2441 + }
2442 + }
2443 + height++;
2444 + }
2445 +
2446 + }
2447 +
2448 + var paddingTop = this.$dropdown_content.css('padding-top') ? Number(this.$dropdown_content.css('padding-top').replace(/\W*(\w)\w*/g, '$1')) : 0;
2449 + var paddingBottom = this.$dropdown_content.css('padding-bottom') ? Number(this.$dropdown_content.css('padding-bottom').replace(/\W*(\w)\w*/g, '$1')) : 0;
2450 +
2451 + height = (totalHeight + paddingTop + paddingBottom + marginTop + marginBottom + separatorHeight) + 'px';
2452 + } else if (this.settings.dropdownSize.sizeType !== 'fixedHeight') {
2453 + console.warn('Selectize.js - Value of "sizeType" must be "fixedHeight" or "numberItems');
2454 + return;
2455 + }
2456 +
2457 + this.$dropdown_content.css({ height: height, maxHeight: 'none' });
2458 + }
2459 + },
2460 +
2461 + clear: function(silent) {
2462 + var self = this;
2463 +
2464 + if (!self.items.length) return;
2465 + self.$control.children(':not(input)').remove();
2466 + self.items = [];
2467 + self.lastQuery = null;
2468 + self.setCaret(0);
2469 + self.setActiveItem(null);
2470 + self.updatePlaceholder();
2471 + self.updateOriginalInput({silent: silent});
2472 + self.refreshState();
2473 + self.showInput();
2474 + self.trigger('clear');
2475 + },
2476 +
2477 + insertAtCaret: function($el) {
2478 + var caret = Math.min(this.caretPos, this.items.length);
2479 + var el = $el[0];
2480 + var target = this.buffer || this.$control[0];
2481 +
2482 + if (caret === 0) {
2483 + target.insertBefore(el, target.firstChild);
2484 + } else {
2485 + target.insertBefore(el, target.childNodes[caret]);
2486 + }
2487 +
2488 + this.setCaret(caret + 1);
2489 + },
2490 +
2491 + deleteSelection: function(e) {
2492 + var i, n, direction, selection, values, caret, option_select, $option_select, $tail;
2493 + var self = this;
2494 +
2495 + direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1;
2496 + selection = getInputSelection(self.$control_input[0]);
2497 +
2498 + if (self.$activeOption && !self.settings.hideSelected) {
2499 + if (typeof self.settings.deselectBehavior === 'string' && self.settings.deselectBehavior === 'top') {
2500 + option_select = self.getFirstOption().attr('data-value');
2826 2501 } else {
2827 - $(this.$control[0].childNodes[caret]).before($el);
2828 - }
2829 - this.setCaret(caret + 1);
2830 - },
2831 -
2832 - /**
2833 - * Removes the current selected item(s).
2834 - *
2835 - * @param {object} e (optional)
2836 - * @returns {boolean}
2837 - */
2838 - deleteSelection: function(e) {
2839 - var i, n, direction, selection, values, caret, option_select, $option_select, $tail;
2840 - var self = this;
2841 -
2842 - direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1;
2843 - selection = getSelection(self.$control_input[0]);
2844 -
2845 - if (self.$activeOption && !self.settings.hideSelected) {
2846 2502 option_select = self.getAdjacentOption(self.$activeOption, -1).attr('data-value');
2847 2503 }
2848 -
2849 - // determine items that will be removed
2850 - values = [];
2851 -
2852 - if (self.$activeItems.length) {
2853 - $tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first'));
2854 - caret = self.$control.children(':not(input)').index($tail);
2855 - if (direction > 0) { caret++; }
2856 -
2857 - for (i = 0, n = self.$activeItems.length; i < n; i++) {
2858 - values.push($(self.$activeItems[i]).attr('data-value'));
2859 - }
2860 - if (e) {
2861 - e.preventDefault();
2862 - e.stopPropagation();
2863 - }
2864 - } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) {
2865 - if (direction < 0 && selection.start === 0 && selection.length === 0) {
2866 - values.push(self.items[self.caretPos - 1]);
2867 - } else if (direction > 0 && selection.start === self.$control_input.val().length) {
2868 - values.push(self.items[self.caretPos]);
2869 - }
2504 + }
2505 +
2506 + values = [];
2507 +
2508 + if (self.$activeItems.length) {
2509 + $tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first'));
2510 + caret = self.$control.children(':not(input)').index($tail);
2511 + if (direction > 0) { caret++; }
2512 +
2513 + for (i = 0, n = self.$activeItems.length; i < n; i++) {
2514 + values.push($(self.$activeItems[i]).attr('data-value'));
2870 2515 }
2871 -
2872 - // allow the callback to abort
2873 - if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) {
2874 - return false;
2516 + if (e) {
2517 + e.preventDefault();
2518 + e.stopPropagation();
2875 2519 }
2876 -
2877 - // perform removal
2878 - if (typeof caret !== 'undefined') {
2879 - self.setCaret(caret);
2520 + } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) {
2521 + if (direction < 0 && selection.start === 0 && selection.length === 0) {
2522 + values.push(self.items[self.caretPos - 1]);
2523 + } else if (direction > 0 && selection.start === self.$control_input.val().length) {
2524 + values.push(self.items[self.caretPos]);
2880 2525 }
2881 - while (values.length) {
2882 - self.removeItem(values.pop());
2526 + }
2527 +
2528 + if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) {
2529 + return false;
2530 + }
2531 +
2532 + if (typeof caret !== 'undefined') {
2533 + self.setCaret(caret);
2534 + }
2535 + while (values.length) {
2536 + self.removeItem(values.pop());
2537 + }
2538 +
2539 + self.showInput();
2540 + self.positionDropdown();
2541 + self.refreshOptions(true);
2542 +
2543 + if (option_select) {
2544 + $option_select = self.getOption(option_select);
2545 + if ($option_select.length) {
2546 + self.setActiveOption($option_select);
2883 2547 }
2884 -
2885 - self.showInput();
2886 - self.positionDropdown();
2887 - self.refreshOptions(true);
2888 -
2889 - // select previous option
2890 - if (option_select) {
2891 - $option_select = self.getOption(option_select);
2892 - if ($option_select.length) {
2893 - self.setActiveOption($option_select);
2894 - }
2548 + }
2549 +
2550 + return true;
2551 + },
2552 +
2553 + advanceSelection: function(direction, e) {
2554 + var tail, selection, idx, valueLength, cursorAtEdge, $tail;
2555 + var self = this;
2556 +
2557 + if (direction === 0) return;
2558 + if (self.rtl) direction *= -1;
2559 +
2560 + tail = direction > 0 ? 'last' : 'first';
2561 + selection = getInputSelection(self.$control_input[0]);
2562 +
2563 + if (self.isFocused && !self.isInputHidden) {
2564 + valueLength = self.$control_input.val().length;
2565 + cursorAtEdge = direction < 0
2566 + ? selection.start === 0 && selection.length === 0
2567 + : selection.start === valueLength;
2568 +
2569 + if (cursorAtEdge && !valueLength) {
2570 + self.advanceCaret(direction, e);
2895 2571 }
2896 -
2897 - return true;
2898 - },
2899 -
2900 - /**
2901 - * Selects the previous / next item (depending
2902 - * on the `direction` argument).
2903 - *
2904 - * > 0 - right
2905 - * < 0 - left
2906 - *
2907 - * @param {int} direction
2908 - * @param {object} e (optional)
2909 - */
2910 - advanceSelection: function(direction, e) {
2911 - var tail, selection, idx, valueLength, cursorAtEdge, $tail;
2912 - var self = this;
2913 -
2914 - if (direction === 0) return;
2915 - if (self.rtl) direction *= -1;
2916 -
2917 - tail = direction > 0 ? 'last' : 'first';
2918 - selection = getSelection(self.$control_input[0]);
2919 -
2920 - if (self.isFocused && !self.isInputHidden) {
2921 - valueLength = self.$control_input.val().length;
2922 - cursorAtEdge = direction < 0
2923 - ? selection.start === 0 && selection.length === 0
2924 - : selection.start === valueLength;
2925 -
2926 - if (cursorAtEdge && !valueLength) {
2927 - self.advanceCaret(direction, e);
2928 - }
2929 - } else {
2930 - $tail = self.$control.children('.active:' + tail);
2931 - if ($tail.length) {
2932 - idx = self.$control.children(':not(input)').index($tail);
2933 - self.setActiveItem(null);
2934 - self.setCaret(direction > 0 ? idx + 1 : idx);
2935 - }
2572 + } else {
2573 + $tail = self.$control.children('.active:' + tail);
2574 + if ($tail.length) {
2575 + idx = self.$control.children(':not(input)').index($tail);
2576 + self.setActiveItem(null);
2577 + self.setCaret(direction > 0 ? idx + 1 : idx);
2936 2578 }
2937 - },
2938 -
2939 - /**
2940 - * Moves the caret left / right.
2941 - *
2942 - * @param {int} direction
2943 - * @param {object} e (optional)
2944 - */
2945 - advanceCaret: function(direction, e) {
2946 - var self = this, fn, $adj;
2947 -
2948 - if (direction === 0) return;
2949 -
2950 - fn = direction > 0 ? 'next' : 'prev';
2951 - if (self.isShiftDown) {
2952 - $adj = self.$control_input[fn]();
2953 - if ($adj.length) {
2954 - self.hideInput();
2955 - self.setActiveItem($adj);
2956 - e && e.preventDefault();
2957 - }
2958 - } else {
2959 - self.setCaret(self.caretPos + direction);
2579 + }
2580 + },
2581 +
2582 + advanceCaret: function(direction, e) {
2583 + var self = this, fn, $adj;
2584 +
2585 + if (direction === 0) return;
2586 +
2587 + fn = direction > 0 ? 'next' : 'prev';
2588 + if (self.isShiftDown) {
2589 + $adj = self.$control_input[fn]();
2590 + if ($adj.length) {
2591 + self.hideInput();
2592 + self.setActiveItem($adj);
2593 + e && e.preventDefault();
2960 2594 }
2961 - },
2962 -
2963 - /**
2964 - * Moves the caret to the specified index.
2965 - *
2966 - * @param {int} i
2967 - */
2968 - setCaret: function(i) {
2969 - var self = this;
2970 -
2971 - if (self.settings.mode === 'single') {
2972 - i = self.items.length;
2973 - } else {
2974 - i = Math.max(0, Math.min(self.items.length, i));
2975 - }
2976 -
2977 - if(!self.isPending) {
2978 - // the input must be moved by leaving it in place and moving the
2979 - // siblings, due to the fact that focus cannot be restored once lost
2980 - // on mobile webkit devices
2981 - var j, n, fn, $children, $child;
2982 - $children = self.$control.children(':not(input)');
2983 - for (j = 0, n = $children.length; j < n; j++) {
2984 - $child = $($children[j]).detach();
2985 - if (j < i) {
2986 - self.$control_input.before($child);
2987 - } else {
2988 - self.$control.append($child);
2989 - }
2595 + } else {
2596 + self.setCaret(self.caretPos + direction);
2597 + }
2598 + },
2599 +
2600 + setCaret: function(i) {
2601 + var self = this;
2602 +
2603 + if (self.settings.mode === 'single') {
2604 + i = self.items.length;
2605 + } else {
2606 + i = Math.max(0, Math.min(self.items.length, i));
2607 + }
2608 +
2609 + if(!self.isPending) {
2610 + var j, n, fn, $children, $child;
2611 + $children = self.$control.children(':not(input)');
2612 + for (j = 0, n = $children.length; j < n; j++) {
2613 + $child = $($children[j]).detach();
2614 + if (j < i) {
2615 + self.$control_input.before($child);
2616 + } else {
2617 + self.$control.append($child);
2990 2618 }
2991 2619 }
2992 -
2993 - self.caretPos = i;
2994 - },
2995 -
2996 - /**
2997 - * Disables user input on the control. Used while
2998 - * items are being asynchronously created.
2999 - */
3000 - lock: function() {
3001 - this.close();
3002 - this.isLocked = true;
3003 - this.refreshState();
3004 - },
3005 -
3006 - /**
3007 - * Re-enables user input on the control.
3008 - */
3009 - unlock: function() {
3010 - this.isLocked = false;
3011 - this.refreshState();
3012 - },
3013 -
3014 - /**
3015 - * Disables user input on the control completely.
3016 - * While disabled, it cannot receive focus.
3017 - */
3018 - disable: function() {
3019 - var self = this;
3020 - self.$input.prop('disabled', true);
3021 - self.$control_input.prop('disabled', true).prop('tabindex', -1);
3022 - self.isDisabled = true;
3023 - self.lock();
3024 - },
3025 -
3026 - /**
3027 - * Enables the control so that it can respond
3028 - * to focus and user input.
3029 - */
3030 - enable: function() {
3031 - var self = this;
3032 - self.$input.prop('disabled', false);
3033 - self.$control_input.prop('disabled', false).prop('tabindex', self.tabIndex);
3034 - self.isDisabled = false;
3035 - self.unlock();
3036 - },
3037 -
3038 - /**
3039 - * Completely destroys the control and
3040 - * unbinds all event listeners so that it can
3041 - * be garbage collected.
3042 - */
3043 - destroy: function() {
3044 - var self = this;
3045 - var eventNS = self.eventNS;
3046 - var revertSettings = self.revertSettings;
3047 -
3048 - self.trigger('destroy');
3049 - self.off();
3050 - self.$wrapper.remove();
3051 - self.$dropdown.remove();
3052 -
3053 - self.$input
3054 - .html('')
3055 - .append(revertSettings.$children)
3056 - .removeAttr('tabindex')
3057 - .removeClass('selectized')
3058 - .attr({tabindex: revertSettings.tabindex})
3059 - .show();
3060 -
3061 - self.$control_input.removeData('grow');
3062 - self.$input.removeData('selectize');
3063 -
3064 - $(window).off(eventNS);
3065 - $(document).off(eventNS);
3066 - $(document.body).off(eventNS);
3067 -
3068 - delete self.$input[0].selectize;
3069 - },
3070 -
3071 - /**
3072 - * A helper method for rendering "item" and
3073 - * "option" templates, given the data.
3074 - *
3075 - * @param {string} templateName
3076 - * @param {object} data
3077 - * @returns {string}
3078 - */
3079 - render: function(templateName, data) {
3080 - var value, id, label;
3081 - var html = '';
3082 - var cache = false;
3083 - var self = this;
3084 - var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i;
3085 -
3086 - if (templateName === 'option' || templateName === 'item') {
3087 - value = hash_key(data[self.settings.valueField]);
3088 - cache = !!value;
2620 + }
2621 +
2622 + self.caretPos = i;
2623 + },
2624 +
2625 + lock: function() {
2626 + this.close();
2627 + this.isLocked = true;
2628 + this.refreshState();
2629 + },
2630 +
2631 + unlock: function() {
2632 + this.isLocked = false;
2633 + this.refreshState();
2634 + },
2635 +
2636 + disable: function() {
2637 + var self = this;
2638 + self.$input.prop('disabled', true);
2639 + self.$control_input.prop('disabled', true).prop('tabindex', -1);
2640 + self.isDisabled = true;
2641 + self.lock();
2642 + },
2643 +
2644 + enable: function() {
2645 + var self = this;
2646 + self.$input.prop('disabled', false);
2647 + self.$control_input.prop('disabled', false).prop('tabindex', self.tabIndex);
2648 + self.isDisabled = false;
2649 + self.unlock();
2650 + },
2651 +
2652 + destroy: function() {
2653 + var self = this;
2654 + var eventNS = self.eventNS;
2655 + var revertSettings = self.revertSettings;
2656 +
2657 + self.trigger('destroy');
2658 + self.off();
2659 + self.$wrapper.remove();
2660 + self.$dropdown.remove();
2661 +
2662 + self.$input
2663 + .html('')
2664 + .append(revertSettings.$children)
2665 + .removeAttr('tabindex')
2666 + .removeClass('selectized')
2667 + .attr({tabindex: revertSettings.tabindex})
2668 + .show();
2669 +
2670 + self.$control_input.removeData('grow');
2671 + self.$input.removeData('selectize');
2672 +
2673 + if (--Selectize.count == 0 && Selectize.$testInput) {
2674 + Selectize.$testInput.remove();
2675 + Selectize.$testInput = undefined;
2676 + }
2677 +
2678 + $(window).off(eventNS);
2679 + $(document).off(eventNS);
2680 + $(document.body).off(eventNS);
2681 +
2682 + delete self.$input[0].selectize;
2683 + },
2684 +
2685 + render: function(templateName, data) {
2686 + var value, id, label;
2687 + var html = '';
2688 + var cache = false;
2689 + var self = this;
2690 + var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i;
2691 +
2692 + if (templateName === 'option' || templateName === 'item') {
2693 + value = hash_key(data[self.settings.valueField]);
2694 + cache = !!value;
2695 + }
2696 +
2697 + if (cache) {
2698 + if (!isset(self.renderCache[templateName])) {
2699 + self.renderCache[templateName] = {};
3089 2700 }
3090 -
3091 - // pull markup from cache if it exists
3092 - if (cache) {
3093 - if (!isset(self.renderCache[templateName])) {
3094 - self.renderCache[templateName] = {};
3095 - }
3096 - if (self.renderCache[templateName].hasOwnProperty(value)) {
3097 - return self.renderCache[templateName][value];
3098 - }
2701 + if (self.renderCache[templateName].hasOwnProperty(value)) {
2702 + return self.renderCache[templateName][value];
3099 2703 }
3100 -
3101 - // render markup
3102 - html = self.settings.render[templateName].apply(this, [data, escape_html]);
3103 -
3104 - // add mandatory attributes
3105 - if (templateName === 'option' || templateName === 'option_create') {
3106 - html = html.replace(regex_tag, '<$1 data-selectable');
2704 + }
2705 +
2706 + html = $(self.settings.render[templateName].apply(this, [data, escape_html]));
2707 +
2708 + if (templateName === 'option' || templateName === 'option_create') {
2709 + if (!data[self.settings.disabledField]) {
2710 + html.attr('data-selectable', '');
3107 2711 }
3108 - if (templateName === 'optgroup') {
3109 - id = data[self.settings.optgroupValueField] || '';
3110 - html = html.replace(regex_tag, '<$1 data-group="' + escape_replace(escape_html(id)) + '"');
2712 + }
2713 + else if (templateName === 'optgroup') {
2714 + id = data[self.settings.optgroupValueField] || '';
2715 + html.attr('data-group', id);
2716 + if(data[self.settings.disabledField]) {
2717 + html.attr('data-disabled', '');
3111 2718 }
3112 - if (templateName === 'option' || templateName === 'item') {
3113 - html = html.replace(regex_tag, '<$1 data-value="' + escape_replace(escape_html(value || '')) + '"');
3114 - }
3115 -
3116 - // update cache
3117 - if (cache) {
3118 - self.renderCache[templateName][value] = html;
3119 - }
3120 -
3121 - return html;
3122 - },
3123 -
3124 - /**
3125 - * Clears the render cache for a template. If
3126 - * no template is given, clears all render
3127 - * caches.
3128 - *
3129 - * @param {string} templateName
3130 - */
3131 - clearCache: function(templateName) {
3132 - var self = this;
3133 - if (typeof templateName === 'undefined') {
3134 - self.renderCache = {};
3135 - } else {
3136 - delete self.renderCache[templateName];
3137 - }
3138 - },
3139 -
3140 - /**
3141 - * Determines whether or not to display the
3142 - * create item prompt, given a user input.
3143 - *
3144 - * @param {string} input
3145 - * @return {boolean}
3146 - */
3147 - canCreate: function(input) {
3148 - var self = this;
3149 - if (!self.settings.create) return false;
3150 - var filter = self.settings.createFilter;
3151 - return input.length
3152 - && (typeof filter !== 'function' || filter.apply(self, [input]))
3153 - && (typeof filter !== 'string' || new RegExp(filter).test(input))
3154 - && (!(filter instanceof RegExp) || filter.test(input));
3155 2719 }
3156 -
3157 - });
3158 -
3159 -
3160 - Selectize.count = 0;
3161 - Selectize.defaults = {
3162 - options: [],
3163 - optgroups: [],
3164 -
3165 - plugins: [],
3166 - delimiter: ',',
3167 - splitOn: null, // regexp or string for splitting up values from a paste command
3168 - persist: true,
3169 - diacritics: true,
3170 - create: false,
3171 - createOnBlur: false,
3172 - createFilter: null,
3173 - highlight: true,
3174 - openOnFocus: true,
3175 - maxOptions: 1000,
3176 - maxItems: null,
3177 - hideSelected: null,
3178 - addPrecedence: false,
3179 - selectOnTab: false,
3180 - preload: false,
3181 - allowEmptyOption: false,
3182 - closeAfterSelect: false,
3183 -
3184 - scrollDuration: 60,
3185 - loadThrottle: 300,
3186 - loadingClass: 'loading',
3187 -
3188 - dataAttr: 'data-data',
3189 - optgroupField: 'optgroup',
3190 - valueField: 'value',
3191 - labelField: 'text',
3192 - optgroupLabelField: 'label',
3193 - optgroupValueField: 'value',
3194 - lockOptgroupOrder: false,
3195 -
3196 - sortField: '$order',
3197 - searchField: ['text'],
3198 - searchConjunction: 'and',
3199 -
3200 - mode: null,
3201 - wrapperClass: 'selectize-control',
3202 - inputClass: 'selectize-input',
3203 - dropdownClass: 'selectize-dropdown',
3204 - dropdownContentClass: 'selectize-dropdown-content',
3205 -
3206 - dropdownParent: null,
3207 -
3208 - copyClassesToDropdown: true,
3209 -
3210 - /*
3211 - load : null, // function(query, callback) { ... }
3212 - score : null, // function(search) { ... }
3213 - onInitialize : null, // function() { ... }
3214 - onChange : null, // function(value) { ... }
3215 - onItemAdd : null, // function(value, $item) { ... }
3216 - onItemRemove : null, // function(value) { ... }
3217 - onClear : null, // function() { ... }
3218 - onOptionAdd : null, // function(value, data) { ... }
3219 - onOptionRemove : null, // function(value) { ... }
3220 - onOptionClear : null, // function() { ... }
3221 - onOptionGroupAdd : null, // function(id, data) { ... }
3222 - onOptionGroupRemove : null, // function(id) { ... }
3223 - onOptionGroupClear : null, // function() { ... }
3224 - onDropdownOpen : null, // function($dropdown) { ... }
3225 - onDropdownClose : null, // function($dropdown) { ... }
3226 - onType : null, // function(str) { ... }
3227 - onDelete : null, // function(values) { ... }
3228 - */
3229 -
3230 - render: {
3231 - /*
3232 - item: null,
3233 - optgroup: null,
3234 - optgroup_header: null,
3235 - option: null,
3236 - option_create: null
3237 - */
2720 + if (templateName === 'option' || templateName === 'item') {
2721 + html.attr('data-value', value || '');
3238 2722 }
3239 - };
3240 -
3241 -
3242 - $.fn.selectize = function(settings_user) {
3243 - var defaults = $.fn.selectize.defaults;
3244 - var settings = $.extend({}, defaults, settings_user);
3245 - var attr_data = settings.dataAttr;
3246 - var field_label = settings.labelField;
3247 - var field_value = settings.valueField;
3248 - var field_optgroup = settings.optgroupField;
3249 - var field_optgroup_label = settings.optgroupLabelField;
3250 - var field_optgroup_value = settings.optgroupValueField;
3251 -
3252 - /**
3253 - * Initializes selectize from a <input type="text"> element.
3254 - *
3255 - * @param {object} $input
3256 - * @param {object} settings_element
3257 - */
3258 - var init_textbox = function($input, settings_element) {
3259 - var i, n, values, option;
3260 -
3261 - var data_raw = $input.attr(attr_data);
3262 -
3263 - if (!data_raw) {
3264 - var value = $.trim($input.val() || '');
3265 - if (!settings.allowEmptyOption && !value.length) return;
3266 - values = value.split(settings.delimiter);
3267 - for (i = 0, n = values.length; i < n; i++) {
3268 - option = {};
3269 - option[field_label] = values[i];
3270 - option[field_value] = values[i];
3271 - settings_element.options.push(option);
3272 - }
3273 - settings_element.items = values;
3274 - } else {
3275 - settings_element.options = JSON.parse(data_raw);
3276 - for (i = 0, n = settings_element.options.length; i < n; i++) {
3277 - settings_element.items.push(settings_element.options[i][field_value]);
3278 - }
2723 +
2724 + if (cache) {
2725 + self.renderCache[templateName][value] = html[0];
2726 + }
2727 +
2728 + return html[0];
2729 + },
2730 +
2731 + clearCache: function(templateName) {
2732 + var self = this;
2733 + if (typeof templateName === 'undefined') {
2734 + self.renderCache = {};
2735 + } else {
2736 + delete self.renderCache[templateName];
2737 + }
2738 + },
2739 +
2740 + canCreate: function(input) {
2741 + var self = this;
2742 + if (!self.settings.create) return false;
2743 + var filter = self.settings.createFilter;
2744 + return input.length
2745 + && (typeof filter !== 'function' || filter.apply(self, [input]))
2746 + && (typeof filter !== 'string' || new RegExp(filter).test(input))
2747 + && (!(filter instanceof RegExp) || filter.test(input));
2748 + }
2749 +});
2750 +
2751 +Selectize.count = 0;
2752 +Selectize.defaults = {
2753 + options: [],
2754 + optgroups: [],
2755 +
2756 + plugins: [],
2757 + delimiter: ',',
2758 + splitOn: null,
2759 + persist: true,
2760 + diacritics: true,
2761 + create: false,
2762 + showAddOptionOnCreate: true,
2763 + createOnBlur: false,
2764 + createFilter: null,
2765 + highlight: true,
2766 + openOnFocus: true,
2767 + maxOptions: 1000,
2768 + maxItems: null,
2769 + hideSelected: null,
2770 + addPrecedence: false,
2771 + selectOnTab: true,
2772 + preload: false,
2773 + allowEmptyOption: false,
2774 + showEmptyOptionInDropdown: false,
2775 + emptyOptionLabel: '--',
2776 + setFirstOptionActive: false,
2777 + closeAfterSelect: false,
2778 + closeDropdownThreshold: 250,
2779 +
2780 + scrollDuration: 60,
2781 + deselectBehavior: 'previous',
2782 + loadThrottle: 300,
2783 + loadingClass: 'loading',
2784 +
2785 + dataAttr: 'data-data',
2786 + optgroupField: 'optgroup',
2787 + valueField: 'value',
2788 + labelField: 'text',
2789 + disabledField: 'disabled',
2790 + optgroupLabelField: 'label',
2791 + optgroupValueField: 'value',
2792 + lockOptgroupOrder: false,
2793 +
2794 + sortField: '$order',
2795 + searchField: ['text'],
2796 + searchConjunction: 'and',
2797 + respect_word_boundaries: false,
2798 + normalize: true,
2799 +
2800 + mode: null,
2801 + wrapperClass: '',
2802 + inputClass: '',
2803 + dropdownClass: '',
2804 + dropdownContentClass: '',
2805 +
2806 + dropdownParent: null,
2807 +
2808 + copyClassesToDropdown: true,
2809 + dropdownSize: {
2810 + sizeType: 'auto',
2811 + sizeValue: 'auto',
2812 + },
2813 +
2814 + ignoreOnDropwdownHeight: 'img, i',
2815 + search: true,
2816 + showArrow: true,
2817 +
2818 + render: {
2819 + }
2820 +};
2821 +
2822 +$.fn.selectize = function (settings_user) {
2823 + var defaults = $.fn.selectize.defaults;
2824 + var settings = $.extend({}, defaults, settings_user);
2825 + var attr_data = settings.dataAttr;
2826 + var field_label = settings.labelField;
2827 + var field_value = settings.valueField;
2828 + var field_disabled = settings.disabledField;
2829 + var field_optgroup = settings.optgroupField;
2830 + var field_optgroup_label = settings.optgroupLabelField;
2831 + var field_optgroup_value = settings.optgroupValueField;
2832 +
2833 + var init_textbox = function ($input, settings_element) {
2834 + var i, n, values, option;
2835 +
2836 + var data_raw = $input.attr(attr_data);
2837 +
2838 + if (!data_raw) {
2839 + var value = ($input.val() || '').trim();
2840 + if (!settings.allowEmptyOption && !value.length) return;
2841 + values = value.split(settings.delimiter);
2842 + for (i = 0, n = values.length; i < n; i++) {
2843 + option = {};
2844 + option[field_label] = values[i];
2845 + option[field_value] = values[i];
2846 + settings_element.options.push(option);
2847 + }
2848 + settings_element.items = values;
2849 + } else {
2850 + settings_element.options = JSON.parse(data_raw);
2851 + for (i = 0, n = settings_element.options.length; i < n; i++) {
2852 + settings_element.items.push(settings_element.options[i][field_value]);
2853 + }
2854 + }
2855 + };
2856 +
2857 + var init_select = function ($input, settings_element) {
2858 + var i, n, tagName, $children, order = 0;
2859 + var options = settings_element.options;
2860 + var optionsMap = {};
2861 +
2862 + var readData = function ($el) {
2863 + var data = attr_data && $el.attr(attr_data);
2864 + var allData = $el.data();
2865 + var obj = {};
2866 +
2867 + if (typeof data === 'string' && data.length) {
2868 + if (isJSON(data)) {
2869 + Object.assign(obj, JSON.parse(data))
2870 + } else {
2871 + obj[data] = data;
2872 + }
2873 + }
2874 +
2875 +
2876 + Object.assign(obj, allData);
2877 +
2878 + return obj || null;
2879 + };
2880 +
2881 + var addOption = function ($option, group) {
2882 + $option = $($option);
2883 +
2884 + var value = hash_key($option.val());
2885 + if (!value && !settings.allowEmptyOption) return;
2886 +
2887 + if (optionsMap.hasOwnProperty(value)) {
2888 + if (group) {
2889 + var arr = optionsMap[value][field_optgroup];
2890 + if (!arr) {
2891 + optionsMap[value][field_optgroup] = group;
2892 + } else if (!Array.isArray(arr)) {
2893 + optionsMap[value][field_optgroup] = [arr, group];
2894 + } else {
2895 + arr.push(group);
2896 + }
2897 + }
2898 + return;
2899 + }
2900 +
2901 + var option = readData($option) || {};
2902 + option[field_value] = option[field_value] || value;
2903 + option[field_label] = option[field_label] || $option.text() || option[field_value];
2904 + option[field_disabled] = option[field_disabled] || $option.prop('disabled');
2905 + option[field_optgroup] = option[field_optgroup] || group;
2906 + option.styles = $option.attr('style') || '';
2907 + option.classes = $option.attr('class') || '';
2908 +
2909 + optionsMap[value] = option;
2910 + options.push(option);
2911 +
2912 + if ($option.is(':selected')) {
2913 + settings_element.items.push(value);
2914 + }
2915 + };
2916 +
2917 + var addGroup = function ($optgroup) {
2918 + var i, n, id, optgroup, $options;
2919 +
2920 + $optgroup = $($optgroup);
2921 + id = $optgroup.attr('label');
2922 +
2923 + if (id) {
2924 + optgroup = readData($optgroup) || {};
2925 + optgroup[field_optgroup_label] = id;
2926 + optgroup[field_optgroup_value] = id;
2927 + optgroup[field_disabled] = $optgroup.prop('disabled');
2928 + settings_element.optgroups.push(optgroup);
2929 + }
2930 +
2931 + $options = $('option', $optgroup);
2932 + for (i = 0, n = $options.length; i < n; i++) {
2933 + addOption($options[i], id);
2934 + }
2935 + };
2936 +
2937 + settings_element.maxItems = $input.attr('multiple') ? null : 1;
2938 +
2939 + $children = $input.children();
2940 + for (i = 0, n = $children.length; i < n; i++) {
2941 + tagName = $children[i].tagName.toLowerCase();
2942 + if (tagName === 'optgroup') {
2943 + addGroup($children[i]);
2944 + } else if (tagName === 'option') {
2945 + addOption($children[i]);
2946 + }
2947 + }
2948 + };
2949 +
2950 + return this.each(function () {
2951 + if (this.selectize) return;
2952 +
2953 + var instance;
2954 + var $input = $(this);
2955 + var tag_name = this.tagName.toLowerCase();
2956 + var placeholder = $input.attr('placeholder') || $input.attr('data-placeholder');
2957 + if (!placeholder && !settings.allowEmptyOption) {
2958 + placeholder = $input.children('option[value=""]').text();
2959 + }
2960 + if (settings.allowEmptyOption && settings.showEmptyOptionInDropdown && !$input.children('option[value=""]').length) {
2961 + var input_html = $input.html();
2962 + var label = escape_html(settings.emptyOptionLabel || '--');
2963 + $input.html('<option value="">' + label + '</option>' + input_html);
2964 + }
2965 +
2966 + var settings_element = {
2967 + 'placeholder': placeholder,
2968 + 'options': [],
2969 + 'optgroups': [],
2970 + 'items': []
2971 + };
2972 +
2973 + if (tag_name === 'select') {
2974 + init_select($input, settings_element);
2975 + } else {
2976 + init_textbox($input, settings_element);
2977 + }
2978 +
2979 + instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings_user));
2980 + instance.settings_user = settings_user;
2981 + });
2982 +};
2983 +
2984 +$.fn.selectize.defaults = Selectize.defaults;
2985 +$.fn.selectize.support = {
2986 + validity: SUPPORTS_VALIDITY_API
2987 +};
2988 +
2989 +Selectize.define("auto_position", function () {
2990 + var self = this;
2991 +
2992 + const POSITION = {
2993 + top: 'top',
2994 + bottom: 'bottom',
2995 + };
2996 +
2997 + self.positionDropdown = (function () {
2998 + return function () {
2999 + const $control = this.$control;
3000 + const offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position();
3001 + offset.top += $control.outerHeight(true);
3002 +
3003 + const dropdownHeight = this.$dropdown.prop('scrollHeight') + 5;
3004 + const controlPosTop = this.$control.get(0).getBoundingClientRect().top;
3005 + const wrapperHeight = this.$wrapper.height();
3006 + const controlPosBottom = self.$control.get(0).getBoundingClientRect().bottom
3007 + const position =
3008 + controlPosTop + dropdownHeight + wrapperHeight > window.innerHeight &&
3009 + controlPosBottom - dropdownHeight - wrapperHeight >= 0 ?
3010 + POSITION.top :
3011 + POSITION.bottom;
3012 + let w = this.$wrapper[0].style.width !== 'fit-content' ? this.settings.dropdownParent === 'body' ? 'max-content' : '100%' : 'max-content';
3013 + const styles = {
3014 + width: w,
3015 + minWidth : $control.outerWidth(true),
3016 + left: offset.left
3017 + };
3018 +
3019 + if (position === POSITION.top) {
3020 + const styleToAdd = { bottom: offset.top, top: 'unset' };
3021 +
3022 + if (this.settings.dropdownParent === 'body') {
3023 + styleToAdd.top = offset.top - this.$dropdown.outerHeight(true) - $control.outerHeight(true);
3024 + styleToAdd.bottom = 'unset';
3025 + }
3026 + Object.assign(styles, styleToAdd);
3027 + this.$dropdown.addClass('selectize-position-top');
3028 + this.$control.addClass('selectize-position-top');
3029 + } else {
3030 + Object.assign(styles, { top: offset.top, bottom: 'unset' });
3031 + this.$dropdown.removeClass('selectize-position-top');
3032 + this.$control.removeClass('selectize-position-top');
3033 + }
3034 +
3035 + if (this.settings.dropdownParent !== 'body' && w === 'max-content' && $control.outerWidth(true) >= this.$dropdown.outerWidth(true)) {
3036 + w = '100%';
3037 + }
3038 +
3039 + this.$dropdown.css(styles);
3040 + };
3041 + }());
3042 +});
3043 +
3044 +Selectize.define('auto_select_on_type', function(options) {
3045 + var self = this;
3046 +
3047 + self.onBlur = (function() {
3048 + var originalBlur = self.onBlur;
3049 + return function(e) {
3050 + var $matchedItem = self.getFirstItemMatchedByTextContent(self.lastValue, true);
3051 + if (typeof $matchedItem.attr('data-value') !== 'undefined' && self.getValue() !== $matchedItem.attr('data-value'))
3052 + {
3053 + self.setValue($matchedItem.attr('data-value'));
3279 3054 }
3055 + return originalBlur.apply(this, arguments);
3056 + }
3057 + }());
3058 +});
3059 +
3060 +Selectize.define("autofill_disable", function (options) {
3061 + var self = this;
3062 +
3063 + self.setup = (function () {
3064 + var original = self.setup;
3065 + return function () {
3066 + original.apply(self, arguments);
3067 +
3068 + self.$control_input.attr({ name: nanoid(21), autocomplete: nanoid(21) });
3069 + };
3070 + })();
3071 +});
3072 +
3073 +Selectize.define("clear_button", function (options) {
3074 + var self = this;
3075 +
3076 + options = $.extend(
3077 + {
3078 + title: "Clear",
3079 + className: "clear",
3080 + label: "×",
3081 + html: function (data) {
3082 + return (
3083 + '<a class="' + data.className + '" title="' + data.title + '"> ' + data.label + '</a>'
3084 + );
3085 + },
3086 + },
3087 + options
3088 + );
3089 +
3090 + self.setup = (function () {
3091 + var original = self.setup;
3092 + return function () {
3093 + original.apply(self, arguments);
3094 + self.$button_clear = $(options.html(options));
3095 +
3096 + if (self.settings.mode === "single") self.$wrapper.addClass("single");
3097 +
3098 + self.$wrapper.append(self.$button_clear);
3099 +
3100 + if (self.getValue() === "" || self.getValue().length === 0) {
3101 + self.$wrapper.find("." + options.className).css("display", "none");
3102 + }
3103 +
3104 + self.on("change", function () {
3105 + if (self.getValue() === "" || self.getValue().length === 0) {
3106 + self.$wrapper.find("." + options.className).css("display", "none");
3107 + } else {
3108 + self.$wrapper.find("." + options.className).css("display", "");
3109 + }
3110 + });
3111 +
3112 + self.$wrapper.on("click", "." + options.className, function (e) {
3113 + e.preventDefault();
3114 + e.stopImmediatePropagation();
3115 + e.stopPropagation();
3116 +
3117 + if (self.isLocked) return;
3118 +
3119 + self.clear();
3120 + self.$wrapper.find("." + options.className).css("display", "none");
3121 + });
3122 + };
3123 + })();
3124 +});
3125 +
3126 +Selectize.define('drag_drop', function(options) {
3127 + if (!$.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');
3128 + if (this.settings.mode !== 'multi') return;
3129 + var self = this;
3130 +
3131 + self.lock = (function() {
3132 + var original = self.lock;
3133 + return function() {
3134 + var sortable = self.$control.data('sortable');
3135 + if (sortable) sortable.disable();
3136 + return original.apply(self, arguments);
3280 3137 };
3281 -
3282 - /**
3283 - * Initializes selectize from a <select> element.
3284 - *
3285 - * @param {object} $input
3286 - * @param {object} settings_element
3287 - */
3288 - var init_select = function($input, settings_element) {
3289 - var i, n, tagName, $children, order = 0;
3290 - var options = settings_element.options;
3291 - var optionsMap = {};
3292 -
3293 - var readData = function($el) {
3294 - var data = attr_data && $el.attr(attr_data);
3295 - if (typeof data === 'string' && data.length) {
3296 - return JSON.parse(data);
3138 + })();
3139 +
3140 + self.unlock = (function() {
3141 + var original = self.unlock;
3142 + return function() {
3143 + var sortable = self.$control.data('sortable');
3144 + if (sortable) sortable.enable();
3145 + return original.apply(self, arguments);
3146 + };
3147 + })();
3148 +
3149 + self.setup = (function() {
3150 + var original = self.setup;
3151 + return function() {
3152 + original.apply(this, arguments);
3153 +
3154 + var $control = self.$control.sortable({
3155 + items: '[data-value]',
3156 + forcePlaceholderSize: true,
3157 + disabled: self.isLocked,
3158 + start: function(e, ui) {
3159 + ui.placeholder.css('width', ui.helper.css('width'));
3160 + $control.addClass('dragging');
3161 + },
3162 + stop: function() {
3163 + $control.removeClass('dragging');
3164 + var active = self.$activeItems ? self.$activeItems.slice() : null;
3165 + var values = [];
3166 + $control.children('[data-value]').each(function() {
3167 + values.push($(this).attr('data-value'));
3168 + });
3169 + self.isFocused = false;
3170 + self.setValue(values);
3171 + self.isFocused = true;
3172 + self.setActiveItem(active);
3173 + self.positionDropdown();
3297 3174 }
3298 - return null;
3299 - };
3300 -
3301 - var addOption = function($option, group) {
3302 - $option = $($option);
3303 -
3304 - var value = hash_key($option.attr('value'));
3305 - if (!value && !settings.allowEmptyOption) return;
3306 -
3307 - // if the option already exists, it's probably been
3308 - // duplicated in another optgroup. in this case, push
3309 - // the current group to the "optgroup" property on the
3310 - // existing option so that it's rendered in both places.
3311 - if (optionsMap.hasOwnProperty(value)) {
3312 - if (group) {
3313 - var arr = optionsMap[value][field_optgroup];
3314 - if (!arr) {
3315 - optionsMap[value][field_optgroup] = group;
3316 - } else if (!$.isArray(arr)) {
3317 - optionsMap[value][field_optgroup] = [arr, group];
3318 - } else {
3319 - arr.push(group);
3320 - }
3321 - }
3322 - return;
3175 + });
3176 + };
3177 + })();
3178 +
3179 +});
3180 +
3181 +Selectize.define('dropdown_header', function(options) {
3182 + var self = this;
3183 +
3184 + options = $.extend({
3185 + title : 'Untitled',
3186 + headerClass : 'selectize-dropdown-header',
3187 + titleRowClass : 'selectize-dropdown-header-title',
3188 + labelClass : 'selectize-dropdown-header-label',
3189 + closeClass : 'selectize-dropdown-header-close',
3190 +
3191 + html: function(data) {
3192 + return (
3193 + '<div class="' + data.headerClass + '">' +
3194 + '<div class="' + data.titleRowClass + '">' +
3195 + '<span class="' + data.labelClass + '">' + data.title + '</span>' +
3196 + '<a href="javascript:void(0)" class="' + data.closeClass + '">&#xd7;</a>' +
3197 + '</div>' +
3198 + '</div>'
3199 + );
3200 + }
3201 + }, options);
3202 +
3203 + self.setup = (function() {
3204 + var original = self.setup;
3205 + return function() {
3206 + original.apply(self, arguments);
3207 + self.$dropdown_header = $(options.html(options));
3208 + self.$dropdown.prepend(self.$dropdown_header);
3209 + self.$dropdown_header.find('.' + options.closeClass).on('click', function () {
3210 + self.close();
3211 + });
3212 + };
3213 + })();
3214 +
3215 +});
3216 +
3217 +Selectize.define('optgroup_columns', function(options) {
3218 + var self = this;
3219 +
3220 + options = $.extend({
3221 + equalizeWidth : true,
3222 + equalizeHeight : true
3223 + }, options);
3224 +
3225 + this.getAdjacentOption = function($option, direction) {
3226 + var $options = $option.closest('[data-group]').find('[data-selectable]');
3227 + var index = $options.index($option) + direction;
3228 +
3229 + return index >= 0 && index < $options.length ? $options.eq(index) : $();
3230 + };
3231 +
3232 + this.onKeyDown = (function() {
3233 + var original = self.onKeyDown;
3234 + return function(e) {
3235 + var index, $option, $options, $optgroup;
3236 +
3237 + if (this.isOpen && (e.keyCode === KEY_LEFT || e.keyCode === KEY_RIGHT)) {
3238 + self.ignoreHover = true;
3239 + $optgroup = this.$activeOption.closest('[data-group]');
3240 + index = $optgroup.find('[data-selectable]').index(this.$activeOption);
3241 +
3242 + if(e.keyCode === KEY_LEFT) {
3243 + $optgroup = $optgroup.prev('[data-group]');
3244 + } else {
3245 + $optgroup = $optgroup.next('[data-group]');
3323 3246 }
3324 -
3325 - var option = readData($option) || {};
3326 - option[field_label] = option[field_label] || $option.text();
3327 - option[field_value] = option[field_value] || value;
3328 - option[field_optgroup] = option[field_optgroup] || group;
3329 -
3330 - optionsMap[value] = option;
3331 - options.push(option);
3332 -
3333 - if ($option.is(':selected')) {
3334 - settings_element.items.push(value);
3247 +
3248 + $options = $optgroup.find('[data-selectable]');
3249 + $option = $options.eq(Math.min($options.length - 1, index));
3250 + if ($option.length) {
3251 + this.setActiveOption($option);
3335 3252 }
3336 - };
3337 -
3338 - var addGroup = function($optgroup) {
3339 - var i, n, id, optgroup, $options;
3340 -
3341 - $optgroup = $($optgroup);
3342 - id = $optgroup.attr('label');
3343 -
3344 - if (id) {
3345 - optgroup = readData($optgroup) || {};
3346 - optgroup[field_optgroup_label] = id;
3347 - optgroup[field_optgroup_value] = id;
3348 - settings_element.optgroups.push(optgroup);
3349 - }
3350 -
3351 - $options = $('option', $optgroup);
3352 - for (i = 0, n = $options.length; i < n; i++) {
3353 - addOption($options[i], id);
3354 - }
3355 - };
3356 -
3357 - settings_element.maxItems = $input.attr('multiple') ? null : 1;
3358 -
3359 - $children = $input.children();
3360 - for (i = 0, n = $children.length; i < n; i++) {
3361 - tagName = $children[i].tagName.toLowerCase();
3362 - if (tagName === 'optgroup') {
3363 - addGroup($children[i]);
3364 - } else if (tagName === 'option') {
3365 - addOption($children[i]);
3366 - }
3253 + return;
3367 3254 }
3255 +
3256 + return original.apply(this, arguments);
3368 3257 };
3369 -
3370 - return this.each(function() {
3371 - if (this.selectize) return;
3372 -
3373 - var instance;
3374 - var $input = $(this);
3375 - var tag_name = this.tagName.toLowerCase();
3376 - var placeholder = $input.attr('placeholder') || $input.attr('data-placeholder');
3377 - if (!placeholder && !settings.allowEmptyOption) {
3378 - placeholder = $input.children('option[value=""]').text();
3258 + })();
3259 +
3260 + var getScrollbarWidth = function() {
3261 + var div;
3262 + var width = getScrollbarWidth.width;
3263 + var doc = document;
3264 +
3265 + if (typeof width === 'undefined') {
3266 + div = doc.createElement('div');
3267 + div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>';
3268 + div = div.firstChild;
3269 + doc.body.appendChild(div);
3270 + width = getScrollbarWidth.width = div.offsetWidth - div.clientWidth;
3271 + doc.body.removeChild(div);
3272 + }
3273 + return width;
3274 + };
3275 +
3276 + var equalizeSizes = function() {
3277 + var i, n, height_max, width, width_last, width_parent, $optgroups;
3278 +
3279 + $optgroups = $('[data-group]', self.$dropdown_content);
3280 + n = $optgroups.length;
3281 + if (!n || !self.$dropdown_content.width()) return;
3282 +
3283 + if (options.equalizeHeight) {
3284 + height_max = 0;
3285 + for (i = 0; i < n; i++) {
3286 + height_max = Math.max(height_max, $optgroups.eq(i).height());
3379 3287 }
3380 -
3381 - var settings_element = {
3382 - 'placeholder' : placeholder,
3383 - 'options' : [],
3384 - 'optgroups' : [],
3385 - 'items' : []
3386 - };
3387 -
3388 - if (tag_name === 'select') {
3389 - init_select($input, settings_element);
3390 - } else {
3391 - init_textbox($input, settings_element);
3288 + $optgroups.css({height: height_max});
3289 + }
3290 +
3291 + if (options.equalizeWidth) {
3292 + width_parent = self.$dropdown_content.innerWidth() - getScrollbarWidth();
3293 + width = Math.round(width_parent / n);
3294 + $optgroups.css({width: width});
3295 + if (n > 1) {
3296 + width_last = width_parent - width * (n - 1);
3297 + $optgroups.eq(n - 1).css({width: width_last});
3392 3298 }
3393 -
3394 - instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings_user));
3395 - });
3299 + }
3396 3300 };
3397 -
3398 - $.fn.selectize.defaults = Selectize.defaults;
3399 - $.fn.selectize.support = {
3400 - validity: SUPPORTS_VALIDITY_API
3401 - };
3402 -
3403 -
3404 - Selectize.define('drag_drop', function(options) {
3405 - if (!$.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');
3406 - if (this.settings.mode !== 'multi') return;
3407 - var self = this;
3408 -
3409 - self.lock = (function() {
3410 - var original = self.lock;
3411 - return function() {
3412 - var sortable = self.$control.data('sortable');
3413 - if (sortable) sortable.disable();
3414 - return original.apply(self, arguments);
3415 - };
3416 - })();
3417 -
3418 - self.unlock = (function() {
3419 - var original = self.unlock;
3420 - return function() {
3421 - var sortable = self.$control.data('sortable');
3422 - if (sortable) sortable.enable();
3423 - return original.apply(self, arguments);
3424 - };
3425 - })();
3426 -
3427 - self.setup = (function() {
3428 - var original = self.setup;
3429 - return function() {
3430 - original.apply(this, arguments);
3431 -
3432 - var $control = self.$control.sortable({
3433 - items: '[data-value]',
3434 - forcePlaceholderSize: true,
3435 - disabled: self.isLocked,
3436 - start: function(e, ui) {
3437 - ui.placeholder.css('width', ui.helper.css('width'));
3438 - $control.css({overflow: 'visible'});
3439 - },
3440 - stop: function() {
3441 - $control.css({overflow: 'hidden'});
3442 - var active = self.$activeItems ? self.$activeItems.slice() : null;
3443 - var values = [];
3444 - $control.children('[data-value]').each(function() {
3445 - values.push($(this).attr('data-value'));
3446 - });
3447 - self.setValue(values);
3448 - self.setActiveItem(active);
3449 - }
3450 - });
3451 - };
3452 - })();
3453 -
3454 - });
3455 -
3456 - Selectize.define('dropdown_header', function(options) {
3457 - var self = this;
3458 -
3459 - options = $.extend({
3460 - title : 'Untitled',
3461 - headerClass : 'selectize-dropdown-header',
3462 - titleRowClass : 'selectize-dropdown-header-title',
3463 - labelClass : 'selectize-dropdown-header-label',
3464 - closeClass : 'selectize-dropdown-header-close',
3465 -
3466 - html: function(data) {
3467 - return (
3468 - '<div class="' + data.headerClass + '">' +
3469 - '<div class="' + data.titleRowClass + '">' +
3470 - '<span class="' + data.labelClass + '">' + data.title + '</span>' +
3471 - '<a href="javascript:void(0)" class="' + data.closeClass + '">&times;</a>' +
3472 - '</div>' +
3473 - '</div>'
3474 - );
3301 +
3302 + if (options.equalizeHeight || options.equalizeWidth) {
3303 + hook.after(this, 'positionDropdown', equalizeSizes);
3304 + hook.after(this, 'refreshOptions', equalizeSizes);
3305 + }
3306 +
3307 +
3308 +});
3309 +
3310 +Selectize.define('read-only', function(options){
3311 + var self = this;
3312 + this.setup = (function() {
3313 + var original = self.setup;
3314 + return function() {
3315 + original.apply(this, arguments);
3316 + if(this.$dropdown.hasClass("read-only"))this.$control_input.attr('readonly', 'readonly');
3317 + };
3318 + })();
3319 + this.readonly = (function() {
3320 + return function(state) {
3321 + if(state){
3322 + this.$control_input.attr('readonly', 'readonly');
3323 + this.$dropdown.addClass("read-only")
3475 3324 }
3325 + else{
3326 + this.$control_input.removeAttr('readonly');
3327 + this.$dropdown.removeClass("read-only")
3328 + }
3329 + };
3330 + })();
3331 +});
3332 +
3333 +Selectize.define('remove_button', function (options) {
3334 + if (this.settings.mode === 'single') return;
3335 +
3336 + options = $.extend({
3337 + label : '&#xd7;',
3338 + title : 'Remove',
3339 + className : 'remove',
3340 + append : true
3476 3341 }, options);
3477 -
3478 - self.setup = (function() {
3479 - var original = self.setup;
3480 - return function() {
3481 - original.apply(self, arguments);
3482 - self.$dropdown_header = $(options.html(options));
3483 - self.$dropdown.prepend(self.$dropdown_header);
3342 +
3343 + var multiClose = function(thisRef, options) {
3344 +
3345 + var self = thisRef;
3346 + var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
3347 +
3348 + var append = function(html_container, html_element) {
3349 + var pos = html_container.search(/(<\/[^>]+>\s*)$/);
3350 + return html_container.substring(0, pos) + html_element + html_container.substring(pos);
3484 3351 };
3485 - })();
3486 -
3487 - });
3488 -
3489 - Selectize.define('optgroup_columns', function(options) {
3490 - var self = this;
3491 -
3492 - options = $.extend({
3493 - equalizeWidth : true,
3494 - equalizeHeight : true
3495 - }, options);
3496 -
3497 - this.getAdjacentOption = function($option, direction) {
3498 - var $options = $option.closest('[data-group]').find('[data-selectable]');
3499 - var index = $options.index($option) + direction;
3500 -
3501 - return index >= 0 && index < $options.length ? $options.eq(index) : $();
3352 +
3353 + thisRef.setup = (function() {
3354 + var original = self.setup;
3355 + return function() {
3356 + if (options.append) {
3357 + var render_item = self.settings.render.item;
3358 + self.settings.render.item = function(data) {
3359 + return append(render_item.apply(thisRef, arguments), html);
3360 + };
3361 + }
3362 +
3363 + original.apply(thisRef, arguments);
3364 +
3365 + thisRef.$control.on('click', '.' + options.className, function(e) {
3366 + e.preventDefault();
3367 + if (self.isLocked) return;
3368 +
3369 + var $item = $(e.currentTarget).parent();
3370 + self.setActiveItem($item);
3371 + if (self.deleteSelection()) {
3372 + self.setCaret(self.items.length);
3373 + }
3374 + return false;
3375 + });
3376 +
3377 + };
3378 + })();
3502 3379 };
3503 -
3504 - this.onKeyDown = (function() {
3505 - var original = self.onKeyDown;
3506 - return function(e) {
3507 - var index, $option, $options, $optgroup;
3508 -
3509 - if (this.isOpen && (e.keyCode === KEY_LEFT || e.keyCode === KEY_RIGHT)) {
3510 - self.ignoreHover = true;
3511 - $optgroup = this.$activeOption.closest('[data-group]');
3512 - index = $optgroup.find('[data-selectable]').index(this.$activeOption);
3513 -
3514 - if(e.keyCode === KEY_LEFT) {
3515 - $optgroup = $optgroup.prev('[data-group]');
3516 - } else {
3517 - $optgroup = $optgroup.next('[data-group]');
3380 +
3381 + multiClose(this, options);
3382 +});
3383 +
3384 +Selectize.define('restore_on_backspace', function(options) {
3385 + var self = this;
3386 +
3387 + options.text = options.text || function(option) {
3388 + return option[this.settings.labelField];
3389 + };
3390 +
3391 + this.onKeyDown = (function() {
3392 + var original = self.onKeyDown;
3393 + return function(e) {
3394 + var index, option;
3395 + if (e.keyCode === KEY_BACKSPACE && this.$control_input.val() === '' && !this.$activeItems.length) {
3396 + index = this.caretPos - 1;
3397 + if (index >= 0 && index < this.items.length) {
3398 + option = this.options[this.items[index]];
3399 + if (this.deleteSelection(e)) {
3400 + this.setTextboxValue(options.text.apply(this, [option]));
3401 + this.refreshOptions(true);
3518 3402 }
3519 -
3520 - $options = $optgroup.find('[data-selectable]');
3521 - $option = $options.eq(Math.min($options.length - 1, index));
3522 - if ($option.length) {
3523 - this.setActiveOption($option);
3524 - }
3403 + e.preventDefault();
3525 3404 return;
3526 3405 }
3527 -
3528 - return original.apply(this, arguments);
3529 - };
3530 - })();
3531 -
3532 - var getScrollbarWidth = function() {
3533 - var div;
3534 - var width = getScrollbarWidth.width;
3535 - var doc = document;
3536 -
3537 - if (typeof width === 'undefined') {
3538 - div = doc.createElement('div');
3539 - div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>';
3540 - div = div.firstChild;
3541 - doc.body.appendChild(div);
3542 - width = getScrollbarWidth.width = div.offsetWidth - div.clientWidth;
3543 - doc.body.removeChild(div);
3544 3406 }
3545 - return width;
3407 + return original.apply(this, arguments);
3546 3408 };
3547 -
3548 - var equalizeSizes = function() {
3549 - var i, n, height_max, width, width_last, width_parent, $optgroups;
3550 -
3551 - $optgroups = $('[data-group]', self.$dropdown_content);
3552 - n = $optgroups.length;
3553 - if (!n || !self.$dropdown_content.width()) return;
3554 -
3555 - if (options.equalizeHeight) {
3556 - height_max = 0;
3557 - for (i = 0; i < n; i++) {
3558 - height_max = Math.max(height_max, $optgroups.eq(i).height());
3409 + })();
3410 +});
3411 +
3412 +Selectize.define('select_on_focus', function(options) {
3413 + var self = this;
3414 +
3415 + self.on('focus', function() {
3416 + var originalFocus = self.onFocus;
3417 + return function(e) {
3418 + var value = self.getItem(self.getValue()).text();
3419 + self.clear();
3420 + self.setTextboxValue(value);
3421 + self.$control_input.select();
3422 + setTimeout( function () {
3423 + if (self.settings.selectOnTab) {
3424 + self.setActiveOption(self.getFirstItemMatchedByTextContent(value));
3559 3425 }
3560 - $optgroups.css({height: height_max});
3426 + self.settings.score = null;
3427 + },0);
3428 + return originalFocus.apply(this, arguments);
3429 + };
3430 + }());
3431 +
3432 + self.onBlur = (function() {
3433 + var originalBlur = self.onBlur;
3434 + return function(e) {
3435 + if (self.getValue() === "" && self.lastValidValue !== self.getValue()) {
3436 + self.setValue(self.lastValidValue);
3561 3437 }
3562 -
3563 - if (options.equalizeWidth) {
3564 - width_parent = self.$dropdown_content.innerWidth() - getScrollbarWidth();
3565 - width = Math.round(width_parent / n);
3566 - $optgroups.css({width: width});
3567 - if (n > 1) {
3568 - width_last = width_parent - width * (n - 1);
3569 - $optgroups.eq(n - 1).css({width: width_last});
3570 - }
3571 - }
3572 - };
3573 -
3574 - if (options.equalizeHeight || options.equalizeWidth) {
3575 - hook.after(this, 'positionDropdown', equalizeSizes);
3576 - hook.after(this, 'refreshOptions', equalizeSizes);
3438 + setTimeout( function () {
3439 + self.settings.score = function() {
3440 + return function() {
3441 + return 1;
3442 + };
3443 + };
3444 + }, 0 );
3445 + return originalBlur.apply(this, arguments);
3577 3446 }
3578 -
3579 -
3580 - });
3581 -
3582 - Selectize.define('remove_button', function(options) {
3583 - if (this.settings.mode === 'single') return;
3584 -
3585 - options = $.extend({
3586 - label : '&times;',
3587 - title : 'Remove',
3588 - className : 'remove',
3589 - append : true
3590 - }, options);
3591 -
3592 - var self = this;
3593 - var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
3594 -
3595 - /**
3596 - * Appends an element as a child (with raw HTML).
3597 - *
3598 - * @param {string} html_container
3599 - * @param {string} html_element
3600 - * @return {string}
3601 - */
3602 - var append = function(html_container, html_element) {
3603 - var pos = html_container.search(/(<\/[^>]+>\s*)$/);
3604 - return html_container.substring(0, pos) + html_element + html_container.substring(pos);
3605 - };
3606 -
3607 - this.setup = (function() {
3608 - var original = self.setup;
3609 - return function() {
3610 - // override the item rendering method to add the button to each
3611 - if (options.append) {
3612 - var render_item = self.settings.render.item;
3613 - self.settings.render.item = function(data) {
3614 - return append(render_item.apply(this, arguments), html);
3615 - };
3616 - }
3617 -
3618 - original.apply(this, arguments);
3619 -
3620 - // add event listener
3621 - this.$control.on('click', '.' + options.className, function(e) {
3622 - e.preventDefault();
3623 - if (self.isLocked) return;
3624 -
3625 - var $item = $(e.currentTarget).parent();
3626 - self.setActiveItem($item);
3627 - if (self.deleteSelection()) {
3628 - self.setCaret(self.items.length);
3629 - }
3630 - });
3631 -
3632 - };
3633 - })();
3634 -
3635 - });
3636 -
3637 - Selectize.define('restore_on_backspace', function(options) {
3638 - var self = this;
3639 -
3640 - options.text = options.text || function(option) {
3641 - return option[this.settings.labelField];
3642 - };
3643 -
3644 - this.onKeyDown = (function() {
3645 - var original = self.onKeyDown;
3646 - return function(e) {
3647 - var index, option;
3648 - if (e.keyCode === KEY_BACKSPACE && this.$control_input.val() === '' && !this.$activeItems.length) {
3649 - index = this.caretPos - 1;
3650 - if (index >= 0 && index < this.items.length) {
3651 - option = this.options[this.items[index]];
3652 - if (this.deleteSelection(e)) {
3653 - this.setTextboxValue(options.text.apply(this, [option]));
3654 - this.refreshOptions(true);
3655 - }
3656 - e.preventDefault();
3657 - return;
3658 - }
3659 - }
3660 - return original.apply(this, arguments);
3661 - };
3662 - })();
3663 - });
3664 -
3447 + }());
3448 + self.settings.score = function() {
3449 + return function() { return 1; };
3450 + };
3665 3451
3666 - return Selectize;
3667 -}));
3452 +});
3453 +
3454 +Selectize.define('tag_limit', function (options) {
3455 + const self = this
3456 + options.tagLimit = options.tagLimit
3457 + this.onBlur = (function (e) {
3458 + const original = self.onBlur
3459 +
3460 + return function (e) {
3461 + original.apply(this, e);
3462 + if (!e)
3463 + return
3464 + const $control = this.$control
3465 + const $items = $control.find('.item')
3466 + const limit = options.tagLimit
3467 + if (limit === undefined || $items.length <= limit)
3468 + return
3469 +
3470 + $items.toArray().forEach(function(item, index) {
3471 + if (index < limit)
3472 + return
3473 + $(item).hide()
3474 + });
3475 +
3476 + $control.append('<span><b>' + ($items.length - limit) + '</b></span>')
3477 + };
3478 + })()
3479 +
3480 + this.onFocus = (function (e) {
3481 + const original = self.onFocus
3482 +
3483 + return function (e) {
3484 + original.apply(this, e);
3485 + if (!e)
3486 + return
3487 + const $control = this.$control
3488 + const $items = $control.find('.item')
3489 + $items.show()
3490 + $control.find('span').remove()
3491 +
3492 + };
3493 + })()
3494 +});
3495 +
3496 + return Selectize;
3497 +}));