blazer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of blazer might be problematic. Click here for more details.

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