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