selectize-rails 0.1.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5f5f5589d1ad2624b7e8dd134e6da8febf3d5e34
4
- data.tar.gz: 59832eaa0ba270ff1df87a7e4d46ac97c9943d93
3
+ metadata.gz: 593dca340fc41a504f86cf87414f89fd259c9e53
4
+ data.tar.gz: 191e50886524103975c3ed0b41d15d3ef02660b6
5
5
  SHA512:
6
- metadata.gz: 193daee9a81ac47beb089eb6bfa3a314505c0113f5bd92c14495c80b5c038fcc3a4654c40546e1c504e12686e9f0751f2d3158fa50c39be100031edd06cd0650
7
- data.tar.gz: 3979aa2049b5d43e9569a142db01c2c453df4fef9544598b857dc286e1a1396a14bd989458e048af25647f39a38030591baf6e3c90b457291792bb692de94673
6
+ metadata.gz: 72c76cb2a46c77530aad637ab86ed92ed72d3519afa0db9130d2283ce4acabc85872d8cbe7be31fb0f3edef23dcd36dd418e69b0f5cc67c11c415d748e90b212
7
+ data.tar.gz: 07f2912e36d5cc23cd8f5f00a8525f63bd7ecd631cda7d57ac9131fc000bd1f006598ed303d96f27be8cca6cdc3f38dc42c377101c969e2234d2339c422accbd
data/README.md CHANGED
@@ -35,6 +35,7 @@ See the [demo page of Brian Reavis](http://brianreavis.github.io/selectize.js/)
35
35
 
36
36
  | Version | Notes |
37
37
  |---------+---------------------------------------------------------------------------|
38
+ | 0.6.1 | update and set gem version equal to selectize.js version |
38
39
  | 0.1.0 | Initial release with plugin version 0.1.5 |
39
40
 
40
41
  ## License
@@ -1,5 +1,5 @@
1
1
  module Selectize
2
2
  module Rails
3
- VERSION = "0.1.0"
3
+ VERSION = "0.6.1"
4
4
  end
5
5
  end
@@ -1,6 +1,6 @@
1
- /*! selectize.js | https://github.com/brianreavis/selectize.js | Apache License (v2) */
1
+ /*! selectize.js - v0.6.1 | https://github.com/brianreavis/selectize.js | Apache License (v2) */
2
2
 
3
- (function (factory) {
3
+ (function(factory) {
4
4
  if (typeof exports === 'object') {
5
5
  factory(require('jquery'));
6
6
  } else if (typeof define === 'function' && define.amd) {
@@ -9,10 +9,10 @@
9
9
  factory(jQuery);
10
10
  }
11
11
  }(function ($) {
12
- "use strict";
13
-
14
- // --- src/contrib/highlight.js ---
15
-
12
+ "use strict";
13
+
14
+ /* --- file: "src/contrib/highlight.js" --- */
15
+
16
16
  /**
17
17
  * highlight v3 | MIT license | Johann Burkard <jb@eaio.com>
18
18
  * Highlights arbitrary terms in a node.
@@ -20,11 +20,11 @@
20
20
  * - Modified by Marshal <beatgates@gmail.com> 2011-6-24 (added regex)
21
21
  * - Modified by Brian Reavis <brian@thirdroute.com> 2012-8-27 (cleanup)
22
22
  */
23
-
23
+
24
24
  var highlight = function($element, pattern) {
25
25
  if (typeof pattern === 'string' && !pattern.length) return;
26
26
  var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;
27
-
27
+
28
28
  var highlight = function(node) {
29
29
  var skip = 0;
30
30
  if (node.nodeType === 3) {
@@ -47,12 +47,12 @@
47
47
  }
48
48
  return skip;
49
49
  };
50
-
50
+
51
51
  return $element.each(function() {
52
52
  highlight(this);
53
53
  });
54
54
  };
55
-
55
+
56
56
  var unhighlight = function($element) {
57
57
  return $element.find('span.highlight').each(function() {
58
58
  var parent = this.parentNode;
@@ -60,9 +60,56 @@
60
60
  parent.normalize();
61
61
  }).end();
62
62
  };
63
-
64
- // --- src/constants.js ---
65
-
63
+
64
+ /* --- file: "src/contrib/microevent.js" --- */
65
+
66
+ /**
67
+ * MicroEvent - to make any js object an event emitter
68
+ *
69
+ * - pure javascript - server compatible, browser compatible
70
+ * - dont rely on the browser doms
71
+ * - super simple - you get it immediatly, no mistery, no magic involved
72
+ *
73
+ * @author Jerome Etienne (https://github.com/jeromeetienne)
74
+ */
75
+
76
+ var MicroEvent = function() {};
77
+ MicroEvent.prototype = {
78
+ on: function(event, fct){
79
+ this._events = this._events || {};
80
+ this._events[event] = this._events[event] || [];
81
+ this._events[event].push(fct);
82
+ },
83
+ off: function(event, fct){
84
+ this._events = this._events || {};
85
+ if (event in this._events === false) return;
86
+ this._events[event].splice(this._events[event].indexOf(fct), 1);
87
+ },
88
+ trigger: function(event /* , args... */){
89
+ this._events = this._events || {};
90
+ if (event in this._events === false) return;
91
+ for (var i = 0; i < this._events[event].length; i++){
92
+ this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
93
+ }
94
+ }
95
+ };
96
+
97
+ /**
98
+ * Mixin will delegate all MicroEvent.js function in the destination object.
99
+ *
100
+ * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent
101
+ *
102
+ * @param {object} the object which will support MicroEvent
103
+ */
104
+ MicroEvent.mixin = function(destObject){
105
+ var props = ['on', 'off', 'trigger'];
106
+ for (var i = 0; i < props.length; i++){
107
+ destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
108
+ }
109
+ };
110
+
111
+ /* --- file: "src/constants.js" --- */
112
+
66
113
  /**
67
114
  * selectize - A highly customizable select control with autocomplete.
68
115
  * Copyright (c) 2013 Brian Reavis & contributors
@@ -78,9 +125,10 @@
78
125
  *
79
126
  * @author Brian Reavis <brian@thirdroute.com>
80
127
  */
81
-
128
+
82
129
  var IS_MAC = /Mac/.test(navigator.userAgent);
83
-
130
+
131
+ var KEY_A = 65;
84
132
  var KEY_COMMA = 188;
85
133
  var KEY_RETURN = 13;
86
134
  var KEY_ESC = 27;
@@ -91,12 +139,13 @@
91
139
  var KEY_BACKSPACE = 8;
92
140
  var KEY_DELETE = 46;
93
141
  var KEY_SHIFT = 16;
142
+ var KEY_CMD = IS_MAC ? 91 : 17;
94
143
  var KEY_CTRL = IS_MAC ? 18 : 17;
95
144
  var KEY_TAB = 9;
96
-
145
+
97
146
  var TAG_SELECT = 1;
98
147
  var TAG_INPUT = 2;
99
-
148
+
100
149
  var DIACRITICS = {
101
150
  'a': '[aÀÁÂÃÄÅàáâãäå]',
102
151
  'c': '[cÇç]',
@@ -109,130 +158,134 @@
109
158
  'y': '[yŸÿý]',
110
159
  'z': '[zŽž]'
111
160
  };
112
-
113
- // --- src/selectize.jquery.js ---
114
-
115
- var defaults = {
116
- delimiter: ',',
117
- persist: true,
118
- diacritics: true,
119
- create: false,
120
- highlight: true,
121
- openOnFocus: true,
122
- maxOptions: 1000,
123
- maxItems: null,
124
- hideSelected: null,
125
-
126
- scrollDuration: 60,
127
- loadThrottle: 300,
128
-
129
- dataAttr: 'data-data',
130
- sortField: null,
131
- sortDirection: 'asc',
132
- valueField: 'value',
133
- labelField: 'text',
134
- searchField: ['text'],
135
-
136
- mode: null,
137
- theme: 'default',
138
- wrapperClass: 'selectize-control',
139
- inputClass: 'selectize-input',
140
- dropdownClass: 'selectize-dropdown',
141
-
142
- load : null, // function(query, callback)
143
- score : null, // function(search)
144
- onChange : null, // function(value)
145
- onItemAdd : null, // function(value, $item) { ... }
146
- onItemRemove : null, // function(value) { ... }
147
- onClear : null, // function() { ... }
148
- onOptionAdd : null, // function(value, data) { ... }
149
- onOptionRemove : null, // function(value) { ... }
150
- onDropdownOpen : null, // function($dropdown) { ... }
151
- onDropdownClose : null, // function($dropdown) { ... }
152
- onType : null, // function(str) { ... }
153
-
154
- render: {
155
- item: null,
156
- option: null,
157
- option_create: null
158
- }
159
- };
160
-
161
- $.fn.selectize = function (settings) {
162
- var defaults = $.fn.selectize.defaults;
163
- settings = settings || {};
164
-
165
- return this.each(function() {
166
- var instance, value, values, i, n, data, dataAttr, settings_element, tagName;
167
- var $options, $option, $input = $(this);
168
-
169
- tagName = $input[0].tagName.toLowerCase();
170
-
171
- if (typeof settings === 'string') {
172
- instance = $input.data('selectize');
173
- instance[settings].apply(instance, Array.prototype.splice.apply(arguments, 1));
174
- } else {
175
- dataAttr = settings.dataAttr || defaults.dataAttr;
176
- settings_element = {};
177
- settings_element.placeholder = $input.attr('placeholder');
178
- settings_element.options = {};
179
- settings_element.items = [];
180
-
181
- if (tagName === 'select') {
182
- settings_element.maxItems = !!$input.attr('multiple') ? null : 1;
183
- $options = $input.children();
184
- for (i = 0, n = $options.length; i < n; i++) {
185
- $option = $($options[i]);
186
- value = $option.attr('value') || '';
187
- if (!value.length) continue;
188
- data = (dataAttr && $option.attr(dataAttr)) || {
189
- 'text' : $option.html(),
190
- 'value' : value
191
- };
192
-
193
- if (typeof data === 'string') data = JSON.parse(data);
194
- settings_element.options[value] = data;
195
- if ($option.is(':selected')) {
196
- settings_element.items.push(value);
197
- }
198
- }
199
- } else {
200
- value = $.trim($input.val() || '');
201
- if (value.length) {
202
- values = value.split(settings.delimiter || defaults.delimiter);
203
- for (i = 0, n = values.length; i < n; i++) {
204
- settings_element.options[values[i]] = {
205
- 'text' : values[i],
206
- 'value' : values[i]
207
- };
208
- }
209
- settings_element.items = values;
161
+
162
+ /* --- file: "src/plugins.js" --- */
163
+
164
+ var Plugins = {};
165
+
166
+ Plugins.mixin = function(Interface, interfaceName) {
167
+ Interface.plugins = {};
168
+
169
+ /**
170
+ * Initializes the provided functions.
171
+ * Acceptable formats:
172
+ *
173
+ * List (without options):
174
+ * ['a', 'b', 'c']
175
+ *
176
+ * List (with options)
177
+ * {'a': { ... }, 'b': { ... }, 'c': { ... }}
178
+ *
179
+ * @param {mixed} plugins
180
+ */
181
+ Interface.prototype.loadPlugins = function(plugins) {
182
+ var i, n, key;
183
+ this.plugins = [];
184
+ this.pluginSettings = {};
185
+
186
+ if ($.isArray(plugins)) {
187
+ for (i = 0, n = plugins.length; i < n; i++) {
188
+ this.loadPlugin(plugins[i]);
189
+ }
190
+ } else if (plugins) {
191
+ this.pluginSettings = $.extend({}, plugins);
192
+ for (key in plugins) {
193
+ if (plugins.hasOwnProperty(key)) {
194
+ this.loadPlugin(key);
210
195
  }
211
196
  }
212
-
213
- instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings));
214
- $input.data('selectize', instance);
215
- $input.addClass('selectized');
216
197
  }
217
- });
198
+ };
199
+
200
+ /**
201
+ * Initializes a plugin.
202
+ *
203
+ * @param {string} name
204
+ */
205
+ Interface.prototype.loadPlugin = function(name) {
206
+ var plugin, i, n;
207
+
208
+ if (this.plugins.indexOf(name) !== -1) return;
209
+ if (!Interface.plugins.hasOwnProperty(name)) {
210
+ throw new Error(interfaceName + ' unable to find "' + name + '" plugin');
211
+ }
212
+
213
+ plugin = Interface.plugins[name];
214
+
215
+ // initialize plugin and dependencies
216
+ this.plugins.push(name);
217
+ for (i = 0, n = plugin.dependencies.length; i < n; i++) {
218
+ this.loadPlugin(plugin.dependencies[i]);
219
+ }
220
+ plugin.fn.apply(this, [this.pluginSettings[name] || {}]);
221
+ };
222
+
223
+ /**
224
+ * Registers a plugin.
225
+ *
226
+ * @param {string} name
227
+ * @param {array} dependencies (optional)
228
+ * @param {function} fn
229
+ */
230
+ Interface.registerPlugin = function(name) {
231
+ var args = arguments;
232
+ Interface.plugins[name] = {
233
+ 'name' : name,
234
+ 'fn' : args[args.length - 1],
235
+ 'dependencies' : args.length === 3 ? args[1] : []
236
+ };
237
+ };
218
238
  };
219
-
220
- $.fn.selectize.defaults = defaults;
221
-
222
- // --- src/utils.js ---
223
-
239
+
240
+ /* --- file: "src/utils.js" --- */
241
+
224
242
  var isset = function(object) {
225
243
  return typeof object !== 'undefined';
226
244
  };
227
-
245
+
228
246
  var htmlEntities = function(str) {
229
247
  return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
230
248
  };
231
-
249
+
232
250
  var quoteRegExp = function(str) {
233
251
  return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
234
252
  };
235
-
253
+
254
+ var hook = {};
255
+
256
+ /**
257
+ * Wraps `method` on `self` so that `fn`
258
+ * is invoked before the original method.
259
+ *
260
+ * @param {object} self
261
+ * @param {string} method
262
+ * @param {function} fn
263
+ */
264
+ hook.before = function(self, method, fn) {
265
+ var original = self[method];
266
+ self[method] = function() {
267
+ fn.apply(self, arguments);
268
+ return original.apply(self, arguments);
269
+ };
270
+ };
271
+
272
+ /**
273
+ * Wraps `method` on `self` so that `fn`
274
+ * is invoked after the original method.
275
+ *
276
+ * @param {object} self
277
+ * @param {string} method
278
+ * @param {function} fn
279
+ */
280
+ hook.after = function(self, method, fn) {
281
+ var original = self[method];
282
+ self[method] = function() {
283
+ var result = original.apply(self, arguments);
284
+ fn.apply(self, arguments);
285
+ return result;
286
+ };
287
+ };
288
+
236
289
  var once = function(fn) {
237
290
  var called = false;
238
291
  return function() {
@@ -241,7 +294,7 @@
241
294
  fn.apply(this, arguments);
242
295
  };
243
296
  };
244
-
297
+
245
298
  var debounce = function(fn, delay) {
246
299
  var timeout;
247
300
  return function() {
@@ -253,7 +306,37 @@
253
306
  }, delay);
254
307
  };
255
308
  };
256
-
309
+
310
+ /**
311
+ * Debounce all fired events types listed in `types`
312
+ * while executing the provided `fn`.
313
+ *
314
+ * @param {object} self
315
+ * @param {array} types
316
+ * @param {function} fn
317
+ */
318
+ var debounce_events = function(self, types, fn) {
319
+ var type;
320
+ var trigger = self.trigger;
321
+ var event_args = {};
322
+
323
+ // override trigger method
324
+ self.trigger = function() {
325
+ event_args[arguments[0]] = arguments;
326
+ };
327
+
328
+ // invoke provided function
329
+ fn.apply(self, []);
330
+ self.trigger = trigger;
331
+
332
+ // trigger queued events
333
+ for (type in event_args) {
334
+ if (event_args.hasOwnProperty(type)) {
335
+ trigger.apply(self, event_args[type]);
336
+ }
337
+ }
338
+ };
339
+
257
340
  /**
258
341
  * A workaround for http://bugs.jquery.com/ticket/6696
259
342
  *
@@ -272,7 +355,7 @@
272
355
  return fn.apply(this, [e]);
273
356
  });
274
357
  };
275
-
358
+
276
359
  var getSelection = function(input) {
277
360
  var result = {};
278
361
  if ('selectionStart' in input) {
@@ -288,7 +371,7 @@
288
371
  }
289
372
  return result;
290
373
  };
291
-
374
+
292
375
  var transferStyles = function($from, $to, properties) {
293
376
  var styles = {};
294
377
  if (properties) {
@@ -301,7 +384,7 @@
301
384
  $to.css(styles);
302
385
  return $to;
303
386
  };
304
-
387
+
305
388
  var measureString = function(str, $parent) {
306
389
  var $test = $('<test>').css({
307
390
  position: 'absolute',
@@ -311,7 +394,7 @@
311
394
  padding: 0,
312
395
  whiteSpace: 'nowrap'
313
396
  }).text(str).appendTo('body');
314
-
397
+
315
398
  transferStyles($parent, $test, [
316
399
  'letterSpacing',
317
400
  'fontSize',
@@ -319,19 +402,22 @@
319
402
  'fontWeight',
320
403
  'textTransform'
321
404
  ]);
322
-
405
+
323
406
  var width = $test.width();
324
407
  $test.remove();
325
-
408
+
326
409
  return width;
327
410
  };
328
-
411
+
329
412
  var autoGrow = function($input) {
330
413
  var update = function(e) {
331
414
  var value, keyCode, printable, placeholder, width;
332
- var shift, character;
333
-
334
- e = e || window.event;
415
+ var shift, character, selection;
416
+ e = e || window.event || {};
417
+
418
+ if (e.metaKey || e.altKey) return;
419
+ if ($input.data('grow') === false) return;
420
+
335
421
  value = $input.val();
336
422
  if (e.type && e.type.toLowerCase() === 'keydown') {
337
423
  keyCode = e.keyCode;
@@ -341,8 +427,17 @@
341
427
  (keyCode >= 48 && keyCode <= 57) || // 0-9
342
428
  keyCode == 32 // space
343
429
  );
344
-
345
- if (printable) {
430
+
431
+ if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {
432
+ selection = getSelection($input[0]);
433
+ if (selection.length) {
434
+ value = value.substring(0, selection.start) + value.substring(selection.start + selection.length);
435
+ } else if (keyCode === KEY_BACKSPACE && selection.start) {
436
+ value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);
437
+ } else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {
438
+ value = value.substring(0, selection.start) + value.substring(selection.start + 1);
439
+ }
440
+ } else if (printable) {
346
441
  shift = e.shiftKey;
347
442
  character = String.fromCharCode(e.keyCode);
348
443
  if (shift) character = character.toUpperCase();
@@ -350,24 +445,25 @@
350
445
  value += character;
351
446
  }
352
447
  }
353
-
448
+
354
449
  placeholder = $input.attr('placeholder') || '';
355
450
  if (!value.length && placeholder.length) {
356
451
  value = placeholder;
357
452
  }
358
-
453
+
359
454
  width = measureString(value, $input) + 4;
360
455
  if (width !== $input.width()) {
361
456
  $input.width(width);
362
457
  $input.triggerHandler('resize');
363
458
  }
364
459
  };
460
+
365
461
  $input.on('keydown keyup update blur', update);
366
- update({});
462
+ update();
367
463
  };
368
-
369
- // --- src/selectize.js ---
370
-
464
+
465
+ /* --- file: "src/selectize.js" --- */
466
+
371
467
  /**
372
468
  * selectize.js
373
469
  * Copyright (c) 2013 Brian Reavis & contributors
@@ -383,42 +479,48 @@
383
479
  *
384
480
  * @author Brian Reavis <brian@thirdroute.com>
385
481
  */
386
-
482
+
387
483
  var Selectize = function($input, settings) {
484
+ var key, i, n;
388
485
  $input[0].selectize = this;
389
-
486
+
390
487
  this.$input = $input;
391
488
  this.tagType = $input[0].tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT;
392
489
  this.settings = settings;
393
-
490
+
394
491
  this.highlightedValue = null;
395
492
  this.isOpen = false;
493
+ this.isDisabled = false;
396
494
  this.isLocked = false;
397
495
  this.isFocused = false;
398
496
  this.isInputFocused = false;
497
+ this.isInputHidden = false;
399
498
  this.isSetup = false;
400
499
  this.isShiftDown = false;
500
+ this.isCmdDown = false;
401
501
  this.isCtrlDown = false;
402
502
  this.ignoreFocus = false;
503
+ this.ignoreHover = false;
403
504
  this.hasOptions = false;
404
505
  this.currentResults = null;
405
506
  this.lastValue = '';
406
507
  this.caretPos = 0;
407
508
  this.loading = 0;
408
509
  this.loadedSearches = {};
409
-
510
+
410
511
  this.$activeOption = null;
411
512
  this.$activeItems = [];
412
-
513
+
514
+ this.optgroups = {};
413
515
  this.options = {};
414
516
  this.userOptions = {};
415
517
  this.items = [];
416
518
  this.renderCache = {};
417
519
  this.onSearchChange = debounce(this.onSearchChange, this.settings.loadThrottle);
418
-
520
+
419
521
  if ($.isArray(settings.options)) {
420
- var key = settings.valueField;
421
- for (var i = 0; i < settings.options.length; i++) {
522
+ key = settings.valueField;
523
+ for (i = 0, n = settings.options.length; i < n; i++) {
422
524
  if (settings.options[i].hasOwnProperty(key)) {
423
525
  this.options[settings.options[i][key]] = settings.options[i];
424
526
  }
@@ -427,16 +529,39 @@
427
529
  $.extend(this.options, settings.options);
428
530
  delete this.settings.options;
429
531
  }
430
-
532
+
533
+ if ($.isArray(settings.optgroups)) {
534
+ key = settings.optgroupValueField;
535
+ for (i = 0, n = settings.optgroups.length; i < n; i++) {
536
+ if (settings.optgroups[i].hasOwnProperty(key)) {
537
+ this.optgroups[settings.optgroups[i][key]] = settings.optgroups[i];
538
+ }
539
+ }
540
+ } else if (typeof settings.optgroups === 'object') {
541
+ $.extend(this.optgroups, settings.optgroups);
542
+ delete this.settings.optgroups;
543
+ }
544
+
431
545
  // option-dependent defaults
432
546
  this.settings.mode = this.settings.mode || (this.settings.maxItems === 1 ? 'single' : 'multi');
433
547
  if (typeof this.settings.hideSelected !== 'boolean') {
434
548
  this.settings.hideSelected = this.settings.mode === 'multi';
435
549
  }
436
-
550
+
551
+ this.loadPlugins(this.settings.plugins);
552
+ this.setupCallbacks();
437
553
  this.setup();
438
554
  };
439
-
555
+
556
+ // mixins
557
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
558
+
559
+ MicroEvent.mixin(Selectize);
560
+ Plugins.mixin(Selectize, 'Selectize');
561
+
562
+ // methods
563
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
564
+
440
565
  /**
441
566
  * Creates all elements and sets up event bindings.
442
567
  */
@@ -446,54 +571,61 @@
446
571
  var $control;
447
572
  var $control_input;
448
573
  var $dropdown;
574
+ var $dropdown_content;
449
575
  var inputMode;
450
- var displayMode;
451
576
  var timeout_blur;
452
577
  var timeout_focus;
453
-
454
- $wrapper = $('<div>').addClass(this.settings.theme).addClass(this.settings.wrapperClass);
455
- $control = $('<div>').addClass(this.settings.inputClass).addClass('items').toggleClass('has-options', !$.isEmptyObject(this.options)).appendTo($wrapper);
456
- $control_input = $('<input type="text">').appendTo($control);
457
- $dropdown = $('<div>').addClass(this.settings.dropdownClass).hide().appendTo($wrapper);
458
-
459
- displayMode = this.$input.css('display');
578
+ var tab_index;
579
+ var classes;
580
+
581
+ tab_index = this.$input.attr('tabindex') || '';
582
+ classes = this.$input.attr('class') || '';
583
+ $wrapper = $('<div>').addClass(this.settings.theme).addClass(this.settings.wrapperClass).addClass(classes);
584
+ $control = $('<div>').addClass(this.settings.inputClass).addClass('items').toggleClass('has-options', !$.isEmptyObject(this.options)).appendTo($wrapper);
585
+ $control_input = $('<input type="text">').appendTo($control).attr('tabindex',tab_index);
586
+ $dropdown = $('<div>').addClass(this.settings.dropdownClass).hide().appendTo($wrapper);
587
+ $dropdown_content = $('<div>').addClass(this.settings.dropdownContentClass).appendTo($dropdown);
588
+
460
589
  $wrapper.css({
461
590
  width: this.$input[0].style.width,
462
- display: displayMode
591
+ display: this.$input.css('display')
463
592
  });
464
-
593
+
594
+ if (this.plugins.length) {
595
+ $wrapper.addClass('plugin-' + this.plugins.join(' plugin-'));
596
+ }
597
+
465
598
  inputMode = this.settings.mode;
466
599
  $wrapper.toggleClass('single', inputMode === 'single');
467
600
  $wrapper.toggleClass('multi', inputMode === 'multi');
468
-
601
+
469
602
  if ((this.settings.maxItems === null || this.settings.maxItems > 1) && this.tagType === TAG_SELECT) {
470
603
  this.$input.attr('multiple', 'multiple');
471
604
  }
472
-
605
+
473
606
  if (this.settings.placeholder) {
474
607
  $control_input.attr('placeholder', this.settings.placeholder);
475
608
  }
476
-
477
- this.$wrapper = $wrapper;
478
- this.$control = $control;
479
- this.$control_input = $control_input;
480
- this.$dropdown = $dropdown;
481
-
609
+
610
+ this.$wrapper = $wrapper;
611
+ this.$control = $control;
612
+ this.$control_input = $control_input;
613
+ this.$dropdown = $dropdown;
614
+ this.$dropdown_content = $dropdown_content;
615
+
482
616
  $control.on('mousedown', function(e) {
483
- if (e.currentTarget === self.$control[0]) {
484
- $control_input.trigger('focus');
485
- } else {
486
- self.focus(true);
617
+ if (!e.isDefaultPrevented()) {
618
+ window.setTimeout(function() {
619
+ self.focus(true);
620
+ }, 0);
487
621
  }
488
- e.preventDefault();
489
622
  });
490
-
491
- watchChildEvent($dropdown, 'mouseenter', '*', function() { return self.onOptionHover.apply(self, arguments); });
492
- watchChildEvent($dropdown, 'mousedown', '*', function() { return self.onOptionSelect.apply(self, arguments); });
623
+
624
+ $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
625
+ $dropdown.on('mousedown', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
493
626
  watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
494
-
495
627
  autoGrow($control_input);
496
-
628
+
497
629
  $control_input.on({
498
630
  mousedown : function(e) { e.stopPropagation(); },
499
631
  keydown : function() { return self.onKeyDown.apply(self, arguments); },
@@ -503,25 +635,20 @@
503
635
  blur : function() { return self.onBlur.apply(self, arguments); },
504
636
  focus : function() { return self.onFocus.apply(self, arguments); }
505
637
  });
506
-
638
+
507
639
  $(document).on({
508
640
  keydown: function(e) {
641
+ self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
509
642
  self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
510
643
  self.isShiftDown = e.shiftKey;
511
- if (self.isFocused && !self.isLocked) {
512
- var tagName = (e.target.tagName || '').toLowerCase();
513
- if (tagName === 'input' || tagName === 'textarea') return;
514
- if ([KEY_SHIFT, KEY_BACKSPACE, KEY_DELETE, KEY_ESC, KEY_LEFT, KEY_RIGHT, KEY_TAB].indexOf(e.keyCode) !== -1) {
515
- return self.onKeyDown.apply(self, arguments);
516
- }
517
- }
518
644
  },
519
645
  keyup: function(e) {
520
646
  if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
521
- else if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
647
+ if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
648
+ if (e.keyCode === KEY_CMD) self.isCmdDown = false;
522
649
  },
523
650
  mousedown: function(e) {
524
- if (self.isFocused && !self.isLocked) {
651
+ if (self.isFocused) {
525
652
  // prevent events on the dropdown scrollbar from causing the control to blur
526
653
  if (e.target === self.$dropdown[0]) {
527
654
  var ignoreFocus = self.ignoreFocus;
@@ -539,42 +666,80 @@
539
666
  }
540
667
  }
541
668
  });
542
-
669
+
543
670
  $(window).on({
544
671
  resize: function() {
545
672
  if (self.isOpen) {
546
673
  self.positionDropdown.apply(self, arguments);
547
674
  }
675
+ },
676
+ mousemove: function() {
677
+ self.ignoreHover = false;
548
678
  }
549
679
  });
550
-
551
- this.$input.hide().after(this.$wrapper);
552
-
680
+
681
+ this.$input.attr('tabindex',-1).hide().after(this.$wrapper);
682
+
553
683
  if ($.isArray(this.settings.items)) {
554
684
  this.setValue(this.settings.items);
555
685
  delete this.settings.items;
556
686
  }
557
-
687
+
558
688
  this.updateOriginalInput();
559
689
  this.refreshItems();
560
690
  this.updatePlaceholder();
561
691
  this.isSetup = true;
692
+
693
+ if (this.$input.is(':disabled')) {
694
+ this.disable();
695
+ }
696
+
697
+ // preload options
698
+ if (this.settings.preload) {
699
+ this.onSearchChange('');
700
+ }
701
+ };
702
+
703
+ /**
704
+ * Maps fired events to callbacks provided
705
+ * in the settings used when creating the control.
706
+ */
707
+ Selectize.prototype.setupCallbacks = function() {
708
+ var key, fn, callbacks = {
709
+ 'change' : 'onChange',
710
+ 'item_add' : 'onItemAdd',
711
+ 'item_remove' : 'onItemRemove',
712
+ 'clear' : 'onClear',
713
+ 'option_add' : 'onOptionAdd',
714
+ 'option_remove' : 'onOptionRemove',
715
+ 'option_clear' : 'onOptionClear',
716
+ 'dropdown_open' : 'onDropdownOpen',
717
+ 'dropdown_close' : 'onDropdownClose',
718
+ 'type' : 'onType'
719
+ };
720
+
721
+ for (key in callbacks) {
722
+ if (callbacks.hasOwnProperty(key)) {
723
+ fn = this.settings[callbacks[key]];
724
+ if (fn) this.on(key, fn);
725
+ }
726
+ }
562
727
  };
563
-
728
+
564
729
  /**
565
730
  * Triggers a callback defined in the user-provided settings.
566
731
  * Events: onItemAdd, onOptionAdd, etc
567
732
  *
568
733
  * @param {string} event
569
734
  */
570
- Selectize.prototype.trigger = function(event) {
735
+ Selectize.prototype.triggerCallback = function(event) {
571
736
  var args;
572
737
  if (typeof this.settings[event] === 'function') {
573
738
  args = Array.prototype.slice.apply(arguments, [1]);
574
- this.settings.event.apply(this, args);
739
+ this.settings[event].apply(this, args);
575
740
  }
576
741
  };
577
-
742
+
578
743
  /**
579
744
  * Triggered on <input> keypress.
580
745
  *
@@ -582,7 +747,7 @@
582
747
  * @returns {boolean}
583
748
  */
584
749
  Selectize.prototype.onKeyPress = function(e) {
585
- if (this.isLocked) return;
750
+ if (this.isLocked) return e && e.preventDefault();
586
751
  var character = String.fromCharCode(e.keyCode || e.which);
587
752
  if (this.settings.create && character === this.settings.delimiter) {
588
753
  this.createItem();
@@ -590,7 +755,7 @@
590
755
  return false;
591
756
  }
592
757
  };
593
-
758
+
594
759
  /**
595
760
  * Triggered on <input> keydown.
596
761
  *
@@ -598,62 +763,73 @@
598
763
  * @returns {boolean}
599
764
  */
600
765
  Selectize.prototype.onKeyDown = function(e) {
601
- if (this.isLocked) return;
602
766
  var isInput = e.target === this.$control_input[0];
603
-
604
- switch (e.keyCode || e.which) {
767
+
768
+ if (this.isLocked) {
769
+ if (e.keyCode !== KEY_TAB) {
770
+ e.preventDefault();
771
+ }
772
+ return;
773
+ }
774
+
775
+ switch (e.keyCode) {
776
+ case KEY_A:
777
+ if (this.isCmdDown) {
778
+ this.selectAll();
779
+ e.preventDefault();
780
+ return;
781
+ }
782
+ break;
605
783
  case KEY_ESC:
606
784
  this.blur();
607
785
  return;
608
786
  case KEY_DOWN:
609
- if (!this.isOpen && this.hasOptions && this.isInputFocused) {
787
+ if (!this.isOpen && this.hasOptions) {
610
788
  this.open();
611
789
  } else if (this.$activeOption) {
612
- var $next = this.$activeOption.next();
790
+ this.ignoreHover = true;
791
+ var $next = this.getAdjacentOption(this.$activeOption, 1);
613
792
  if ($next.length) this.setActiveOption($next, true, true);
614
793
  }
615
794
  e.preventDefault();
616
- break;
795
+ return;
617
796
  case KEY_UP:
618
797
  if (this.$activeOption) {
619
- var $prev = this.$activeOption.prev();
798
+ this.ignoreHover = true;
799
+ var $prev = this.getAdjacentOption(this.$activeOption, -1);
620
800
  if ($prev.length) this.setActiveOption($prev, true, true);
621
801
  }
622
802
  e.preventDefault();
623
- break;
803
+ return;
624
804
  case KEY_RETURN:
625
805
  if (this.$activeOption) {
626
806
  this.onOptionSelect({currentTarget: this.$activeOption});
627
807
  }
628
808
  e.preventDefault();
629
- break;
809
+ return;
630
810
  case KEY_LEFT:
631
811
  this.advanceSelection(-1, e);
632
- break;
812
+ return;
633
813
  case KEY_RIGHT:
634
814
  this.advanceSelection(1, e);
635
- break;
815
+ return;
636
816
  case KEY_TAB:
637
817
  if (this.settings.create && $.trim(this.$control_input.val()).length) {
638
818
  this.createItem();
639
819
  e.preventDefault();
640
820
  }
641
- break;
821
+ return;
642
822
  case KEY_BACKSPACE:
643
823
  case KEY_DELETE:
644
824
  this.deleteSelection(e);
645
- break;
646
- default:
647
- if (this.isFull()) {
648
- e.preventDefault();
649
- return;
650
- }
825
+ return;
651
826
  }
652
- if (!this.isFull()) {
653
- this.focus(true);
827
+ if (this.isFull() || this.isInputHidden) {
828
+ e.preventDefault();
829
+ return;
654
830
  }
655
831
  };
656
-
832
+
657
833
  /**
658
834
  * Triggered on <input> keyup.
659
835
  *
@@ -661,16 +837,16 @@
661
837
  * @returns {boolean}
662
838
  */
663
839
  Selectize.prototype.onKeyUp = function(e) {
664
- if (this.isLocked) return;
840
+ if (this.isLocked) return e && e.preventDefault();
665
841
  var value = this.$control_input.val() || '';
666
842
  if (this.lastValue !== value) {
667
843
  this.lastValue = value;
668
844
  this.onSearchChange(value);
669
845
  this.refreshOptions();
670
- this.trigger('onType', value);
846
+ this.trigger('type', value);
671
847
  }
672
848
  };
673
-
849
+
674
850
  /**
675
851
  * Invokes the user-provide option provider / loader.
676
852
  *
@@ -680,43 +856,38 @@
680
856
  * @param {string} value
681
857
  */
682
858
  Selectize.prototype.onSearchChange = function(value) {
683
- if (!this.settings.load) return;
684
- if (this.loadedSearches.hasOwnProperty(value)) return;
685
859
  var self = this;
686
- var $wrapper = this.$wrapper.addClass('loading');
687
-
688
- this.loading++;
689
- this.loadedSearches[value] = true;
690
- this.settings.load.apply(this, [value, function(results) {
691
- self.loading = Math.max(self.loading - 1, 0);
692
- if (results && results.length) {
693
- self.addOption(results);
694
- self.refreshOptions(false);
695
- if (self.isInputFocused) self.open();
696
- }
697
- if (!self.loading) {
698
- $wrapper.removeClass('loading');
699
- }
700
- }]);
860
+ var fn = self.settings.load;
861
+ if (!fn) return;
862
+ if (self.loadedSearches.hasOwnProperty(value)) return;
863
+ self.loadedSearches[value] = true;
864
+ self.load(function(callback) {
865
+ fn.apply(self, [value, callback]);
866
+ });
701
867
  };
702
-
868
+
703
869
  /**
704
870
  * Triggered on <input> focus.
705
871
  *
706
- * @param {object} e
872
+ * @param {object} e (optional)
707
873
  * @returns {boolean}
708
874
  */
709
875
  Selectize.prototype.onFocus = function(e) {
710
- this.showInput();
711
876
  this.isInputFocused = true;
712
877
  this.isFocused = true;
878
+ if (this.isDisabled) {
879
+ this.blur();
880
+ e.preventDefault();
881
+ return false;
882
+ }
713
883
  if (this.ignoreFocus) return;
714
-
884
+
885
+ this.showInput();
715
886
  this.setActiveItem(null);
716
- this.$control.addClass('focus');
717
887
  this.refreshOptions(!!this.settings.openOnFocus);
888
+ this.refreshClasses();
718
889
  };
719
-
890
+
720
891
  /**
721
892
  * Triggered on <input> blur.
722
893
  *
@@ -726,17 +897,16 @@
726
897
  Selectize.prototype.onBlur = function(e) {
727
898
  this.isInputFocused = false;
728
899
  if (this.ignoreFocus) return;
729
-
900
+
730
901
  this.close();
731
902
  this.setTextboxValue('');
903
+ this.setActiveItem(null);
732
904
  this.setActiveOption(null);
733
- this.setCaret(this.items.length, false);
734
- if (!this.$activeItems.length) {
735
- this.$control.removeClass('focus');
736
- this.isFocused = false;
737
- }
905
+ this.setCaret(this.items.length);
906
+ this.isFocused = false;
907
+ this.refreshClasses();
738
908
  };
739
-
909
+
740
910
  /**
741
911
  * Triggered when the user rolls over
742
912
  * an option in the autocomplete dropdown menu.
@@ -745,9 +915,10 @@
745
915
  * @returns {boolean}
746
916
  */
747
917
  Selectize.prototype.onOptionHover = function(e) {
918
+ if (this.ignoreHover) return;
748
919
  this.setActiveOption(e.currentTarget, false);
749
920
  };
750
-
921
+
751
922
  /**
752
923
  * Triggered when the user clicks on an option
753
924
  * in the autocomplete dropdown menu.
@@ -756,18 +927,22 @@
756
927
  * @returns {boolean}
757
928
  */
758
929
  Selectize.prototype.onOptionSelect = function(e) {
930
+ e.preventDefault && e.preventDefault();
931
+ e.stopPropagation && e.stopPropagation();
932
+ this.focus(false);
933
+
759
934
  var $target = $(e.currentTarget);
760
935
  if ($target.hasClass('create')) {
761
936
  this.createItem();
762
937
  } else {
763
938
  var value = $target.attr('data-value');
764
939
  if (value) {
765
- this.addItem(value);
766
940
  this.setTextboxValue('');
941
+ this.addItem(value);
767
942
  }
768
943
  }
769
944
  };
770
-
945
+
771
946
  /**
772
947
  * Triggered when the user clicks on an item
773
948
  * that has been selected.
@@ -777,22 +952,49 @@
777
952
  */
778
953
  Selectize.prototype.onItemSelect = function(e) {
779
954
  if (this.settings.mode === 'multi') {
780
- this.$control_input.trigger('blur');
955
+ e.preventDefault();
781
956
  this.setActiveItem(e.currentTarget, e);
782
- e.stopPropagation();
957
+ this.focus(false);
958
+ this.hideInput();
783
959
  }
784
960
  };
785
-
961
+
962
+ /**
963
+ * Invokes the provided method that provides
964
+ * results to a callback---which are then added
965
+ * as options to the control.
966
+ *
967
+ * @param {function} fn
968
+ */
969
+ Selectize.prototype.load = function(fn) {
970
+ var self = this;
971
+ var $wrapper = self.$wrapper.addClass('loading');
972
+
973
+ self.loading++;
974
+ fn.apply(self, [function(results) {
975
+ self.loading = Math.max(self.loading - 1, 0);
976
+ if (results && results.length) {
977
+ self.addOption(results);
978
+ self.refreshOptions(false);
979
+ if (self.isInputFocused) self.open();
980
+ }
981
+ if (!self.loading) {
982
+ $wrapper.removeClass('loading');
983
+ }
984
+ self.trigger('load', results);
985
+ }]);
986
+ };
987
+
786
988
  /**
787
989
  * Sets the input field of the control to the specified value.
788
990
  *
789
991
  * @param {string} value
790
992
  */
791
993
  Selectize.prototype.setTextboxValue = function(value) {
792
- this.$control_input.val(value);
994
+ this.$control_input.val(value).triggerHandler('update');
793
995
  this.lastValue = value;
794
996
  };
795
-
997
+
796
998
  /**
797
999
  * Returns the value of the control. If multiple items
798
1000
  * can be selected (e.g. <select multiple>), this returns
@@ -808,20 +1010,22 @@
808
1010
  return this.items.join(this.settings.delimiter);
809
1011
  }
810
1012
  };
811
-
1013
+
812
1014
  /**
813
1015
  * Resets the selected items to the given value.
814
1016
  *
815
1017
  * @param {mixed} value
816
1018
  */
817
1019
  Selectize.prototype.setValue = function(value) {
818
- this.clear();
819
- var items = $.isArray(value) ? value : [value];
820
- for (var i = 0, n = items.length; i < n; i++) {
821
- this.addItem(items[i]);
822
- }
1020
+ debounce_events(this, ['change'], function() {
1021
+ this.clear();
1022
+ var items = $.isArray(value) ? value : [value];
1023
+ for (var i = 0, n = items.length; i < n; i++) {
1024
+ this.addItem(items[i]);
1025
+ }
1026
+ });
823
1027
  };
824
-
1028
+
825
1029
  /**
826
1030
  * Sets the selected item.
827
1031
  *
@@ -832,9 +1036,9 @@
832
1036
  var eventName;
833
1037
  var i, idx, begin, end, item, swap;
834
1038
  var $last;
835
-
1039
+
836
1040
  $item = $($item);
837
-
1041
+
838
1042
  // clear the active selection
839
1043
  if (!$item.length) {
840
1044
  $(this.$activeItems).removeClass('active');
@@ -842,10 +1046,10 @@
842
1046
  this.isFocused = this.isInputFocused;
843
1047
  return;
844
1048
  }
845
-
1049
+
846
1050
  // modify selection
847
1051
  eventName = e && e.type.toLowerCase();
848
-
1052
+
849
1053
  if (eventName === 'mousedown' && this.isShiftDown && this.$activeItems.length) {
850
1054
  $last = this.$control.children('.active:last');
851
1055
  begin = Array.prototype.indexOf.apply(this.$control[0].childNodes, [$last[0]]);
@@ -875,10 +1079,10 @@
875
1079
  $(this.$activeItems).removeClass('active');
876
1080
  this.$activeItems = [$item.addClass('active')[0]];
877
1081
  }
878
-
1082
+
879
1083
  this.isFocused = !!this.$activeItems.length || this.isInputFocused;
880
1084
  };
881
-
1085
+
882
1086
  /**
883
1087
  * Sets the selected item in the dropdown menu
884
1088
  * of available options.
@@ -890,49 +1094,61 @@
890
1094
  Selectize.prototype.setActiveOption = function($option, scroll, animate) {
891
1095
  var height_menu, height_item, y;
892
1096
  var scroll_top, scroll_bottom;
893
-
1097
+
894
1098
  if (this.$activeOption) this.$activeOption.removeClass('active');
895
1099
  this.$activeOption = null;
896
-
1100
+
897
1101
  $option = $($option);
898
1102
  if (!$option.length) return;
899
-
1103
+
900
1104
  this.$activeOption = $option.addClass('active');
901
-
1105
+
902
1106
  if (scroll || !isset(scroll)) {
903
-
1107
+
904
1108
  height_menu = this.$dropdown.height();
905
1109
  height_item = this.$activeOption.outerHeight(true);
906
1110
  scroll = this.$dropdown.scrollTop() || 0;
907
1111
  y = this.$activeOption.offset().top - this.$dropdown.offset().top + scroll;
908
1112
  scroll_top = y;
909
1113
  scroll_bottom = y - height_menu + height_item;
910
-
1114
+
911
1115
  if (y + height_item > height_menu - scroll) {
912
1116
  this.$dropdown.stop().animate({scrollTop: scroll_bottom}, animate ? this.settings.scrollDuration : 0);
913
1117
  } else if (y < scroll) {
914
1118
  this.$dropdown.stop().animate({scrollTop: scroll_top}, animate ? this.settings.scrollDuration : 0);
915
1119
  }
916
-
1120
+
917
1121
  }
918
1122
  };
919
-
1123
+
1124
+ /**
1125
+ * Selects all items (CTRL + A).
1126
+ */
1127
+ Selectize.prototype.selectAll = function() {
1128
+ this.$activeItems = Array.prototype.slice.apply(this.$control.children(':not(input)').addClass('active'));
1129
+ this.isFocused = true;
1130
+ if (this.$activeItems.length) this.hideInput();
1131
+ };
1132
+
920
1133
  /**
921
1134
  * Hides the input element out of view, while
922
1135
  * retaining its focus.
923
1136
  */
924
1137
  Selectize.prototype.hideInput = function() {
925
- this.$control_input.css({opacity: 0});
926
- this.isInputFocused = false;
1138
+ this.close();
1139
+ this.setTextboxValue('');
1140
+ this.$control_input.css({opacity: 0, position: 'absolute', left: -10000});
1141
+ this.isInputHidden = true;
927
1142
  };
928
-
1143
+
929
1144
  /**
930
1145
  * Restores input visibility.
931
1146
  */
932
1147
  Selectize.prototype.showInput = function() {
933
- this.$control_input.css({opacity: 1});
1148
+ this.$control_input.css({opacity: 1, position: 'relative', left: 0});
1149
+ this.isInputHidden = false;
934
1150
  };
935
-
1151
+
936
1152
  /**
937
1153
  * Gives the control focus. If "trigger" is falsy,
938
1154
  * focus handlers won't be fired--causing the focus
@@ -941,20 +1157,24 @@
941
1157
  * @param {boolean} trigger
942
1158
  */
943
1159
  Selectize.prototype.focus = function(trigger) {
944
- var ignoreFocus = this.ignoreFocus;
945
- this.ignoreFocus = !trigger;
946
- this.$control_input[0].focus();
947
- this.ignoreFocus = ignoreFocus;
1160
+ if (this.isDisabled) return;
1161
+ var self = this;
1162
+ self.ignoreFocus = true;
1163
+ self.$control_input[0].focus();
1164
+ self.isInputFocused = true;
1165
+ window.setTimeout(function() {
1166
+ self.ignoreFocus = false;
1167
+ if (trigger) self.onFocus();
1168
+ }, 0);
948
1169
  };
949
-
1170
+
950
1171
  /**
951
1172
  * Forces the control out of focus.
952
1173
  */
953
1174
  Selectize.prototype.blur = function() {
954
1175
  this.$control_input.trigger('blur');
955
- this.setActiveItem(null);
956
1176
  };
957
-
1177
+
958
1178
  /**
959
1179
  * Splits a search string into an array of
960
1180
  * individual regexps to be used to match results.
@@ -965,11 +1185,11 @@
965
1185
  Selectize.prototype.parseSearchTokens = function(query) {
966
1186
  query = $.trim(String(query || '').toLowerCase());
967
1187
  if (!query || !query.length) return [];
968
-
1188
+
969
1189
  var i, n, regex, letter;
970
1190
  var tokens = [];
971
1191
  var words = query.split(/ +/);
972
-
1192
+
973
1193
  for (i = 0, n = words.length; i < n; i++) {
974
1194
  regex = quoteRegExp(words[i]);
975
1195
  if (this.settings.diacritics) {
@@ -984,10 +1204,10 @@
984
1204
  regex : new RegExp(regex, 'i')
985
1205
  });
986
1206
  }
987
-
1207
+
988
1208
  return tokens;
989
1209
  };
990
-
1210
+
991
1211
  /**
992
1212
  * Returns a function to be used to score individual results.
993
1213
  * Results will be sorted by the score (descending). Scores less
@@ -1000,14 +1220,14 @@
1000
1220
  Selectize.prototype.getScoreFunction = function(search) {
1001
1221
  var self = this;
1002
1222
  var tokens = search.tokens;
1003
-
1223
+
1004
1224
  var calculateFieldScore = (function() {
1005
1225
  if (!tokens.length) {
1006
1226
  return function() { return 0; };
1007
1227
  } else if (tokens.length === 1) {
1008
1228
  return function(value) {
1009
1229
  var score, pos;
1010
-
1230
+
1011
1231
  value = String(value || '').toLowerCase();
1012
1232
  pos = value.search(tokens[0].regex);
1013
1233
  if (pos === -1) return 0;
@@ -1018,7 +1238,7 @@
1018
1238
  } else {
1019
1239
  return function(value) {
1020
1240
  var score, pos, i, j;
1021
-
1241
+
1022
1242
  value = String(value || '').toLowerCase();
1023
1243
  score = 0;
1024
1244
  for (i = 0, j = tokens.length; i < j; i++) {
@@ -1031,7 +1251,7 @@
1031
1251
  };
1032
1252
  }
1033
1253
  })();
1034
-
1254
+
1035
1255
  var calculateScore = (function() {
1036
1256
  var fields = self.settings.searchField;
1037
1257
  if (typeof fields === 'string') {
@@ -1059,10 +1279,10 @@
1059
1279
  };
1060
1280
  }
1061
1281
  })();
1062
-
1282
+
1063
1283
  return calculateScore;
1064
1284
  };
1065
-
1285
+
1066
1286
  /**
1067
1287
  * Searches through available options and returns
1068
1288
  * a sorted array of matches. Includes options that
@@ -1088,20 +1308,20 @@
1088
1308
  Selectize.prototype.search = function(query, settings) {
1089
1309
  var self = this;
1090
1310
  var value, score, search, calculateScore;
1091
-
1311
+
1092
1312
  settings = settings || {};
1093
1313
  query = $.trim(String(query || '').toLowerCase());
1094
-
1314
+
1095
1315
  if (query !== this.lastQuery) {
1096
1316
  this.lastQuery = query;
1097
-
1317
+
1098
1318
  search = {
1099
1319
  query : query,
1100
1320
  tokens : this.parseSearchTokens(query),
1101
1321
  total : 0,
1102
1322
  items : []
1103
1323
  };
1104
-
1324
+
1105
1325
  // generate result scoring function
1106
1326
  if (this.settings.score) {
1107
1327
  calculateScore = this.settings.score.apply(this, [search]);
@@ -1111,7 +1331,7 @@
1111
1331
  } else {
1112
1332
  calculateScore = this.getScoreFunction(search);
1113
1333
  }
1114
-
1334
+
1115
1335
  // perform search and sort
1116
1336
  if (query.length) {
1117
1337
  for (value in this.options) {
@@ -1155,11 +1375,11 @@
1155
1375
  } else {
1156
1376
  search = $.extend(true, {}, this.currentResults);
1157
1377
  }
1158
-
1378
+
1159
1379
  // apply limits and return
1160
1380
  return this.prepareResults(search, settings);
1161
1381
  };
1162
-
1382
+
1163
1383
  /**
1164
1384
  * Filters out any items that have already been selected
1165
1385
  * and applies search limits.
@@ -1176,15 +1396,15 @@
1176
1396
  }
1177
1397
  }
1178
1398
  }
1179
-
1399
+
1180
1400
  search.total = search.items.length;
1181
1401
  if (typeof settings.limit === 'number') {
1182
1402
  search.items = search.items.slice(0, settings.limit);
1183
1403
  }
1184
-
1404
+
1185
1405
  return search;
1186
1406
  };
1187
-
1407
+
1188
1408
  /**
1189
1409
  * Refreshes the list of available options shown
1190
1410
  * in the autocomplete dropdown menu.
@@ -1195,80 +1415,150 @@
1195
1415
  if (typeof triggerDropdown === 'undefined') {
1196
1416
  triggerDropdown = true;
1197
1417
  }
1198
-
1199
- var i, n;
1418
+
1419
+ var i, n, groups, groups_order, option, optgroup, html, html_children;
1200
1420
  var hasCreateOption;
1201
1421
  var query = this.$control_input.val();
1202
1422
  var results = this.search(query, {});
1203
- var html = [];
1204
-
1423
+ var $active, $create;
1424
+ var $dropdown_content = this.$dropdown_content;
1425
+
1205
1426
  // build markup
1206
1427
  n = results.items.length;
1207
1428
  if (typeof this.settings.maxOptions === 'number') {
1208
1429
  n = Math.min(n, this.settings.maxOptions);
1209
1430
  }
1431
+
1432
+ // render and group available options individually
1433
+ groups = {};
1434
+
1435
+ if (this.settings.optgroupOrder) {
1436
+ groups_order = this.settings.optgroupOrder;
1437
+ for (i = 0; i < groups_order.length; i++) {
1438
+ groups[groups_order[i]] = [];
1439
+ }
1440
+ } else {
1441
+ groups_order = [];
1442
+ }
1443
+
1210
1444
  for (i = 0; i < n; i++) {
1211
- html.push(this.render('option', this.options[results.items[i].value]));
1445
+ option = this.options[results.items[i].value];
1446
+ optgroup = option[this.settings.optgroupField] || '';
1447
+ if (!this.optgroups.hasOwnProperty(optgroup)) {
1448
+ optgroup = '';
1449
+ }
1450
+ if (!groups.hasOwnProperty(optgroup)) {
1451
+ groups[optgroup] = [];
1452
+ groups_order.push(optgroup);
1453
+ }
1454
+ groups[optgroup].push(this.render('option', option));
1212
1455
  }
1213
-
1214
- this.$dropdown.html(html.join(''));
1215
-
1456
+
1457
+ // render optgroup headers & join groups
1458
+ html = [];
1459
+ for (i = 0, n = groups_order.length; i < n; i++) {
1460
+ optgroup = groups_order[i];
1461
+ if (this.optgroups.hasOwnProperty(optgroup) && groups[optgroup].length) {
1462
+ // render the optgroup header and options within it,
1463
+ // then pass it to the wrapper template
1464
+ html_children = this.render('optgroup_header', this.optgroups[optgroup]) || '';
1465
+ html_children += groups[optgroup].join('');
1466
+ html.push(this.render('optgroup', $.extend({}, this.optgroups[optgroup], {
1467
+ html: html_children
1468
+ })));
1469
+ } else {
1470
+ html.push(groups[optgroup].join(''));
1471
+ }
1472
+ }
1473
+
1474
+ $dropdown_content.html(html.join(''));
1475
+
1216
1476
  // highlight matching terms inline
1217
1477
  if (this.settings.highlight && results.query.length && results.tokens.length) {
1218
1478
  for (i = 0, n = results.tokens.length; i < n; i++) {
1219
- highlight(this.$dropdown, results.tokens[i].regex);
1479
+ highlight($dropdown_content, results.tokens[i].regex);
1220
1480
  }
1221
1481
  }
1222
-
1482
+
1223
1483
  // add "selected" class to selected options
1224
1484
  if (!this.settings.hideSelected) {
1225
1485
  for (i = 0, n = this.items.length; i < n; i++) {
1226
1486
  this.getOption(this.items[i]).addClass('selected');
1227
1487
  }
1228
1488
  }
1229
-
1489
+
1230
1490
  // add create option
1231
1491
  hasCreateOption = this.settings.create && results.query.length;
1232
1492
  if (hasCreateOption) {
1233
- this.$dropdown.prepend(this.render('option_create', {input: query}));
1493
+ $dropdown_content.prepend(this.render('option_create', {input: query}));
1494
+ $create = $($dropdown_content[0].childNodes[0]);
1234
1495
  }
1235
-
1496
+
1236
1497
  // activate
1237
1498
  this.hasOptions = results.items.length > 0 || hasCreateOption;
1238
1499
  if (this.hasOptions) {
1239
- this.setActiveOption(this.$dropdown[0].childNodes[hasCreateOption && results.items.length > 0 ? 1 : 0]);
1500
+ if (results.items.length > 0) {
1501
+ if ($create) {
1502
+ $active = this.getAdjacentOption($create, 1);
1503
+ } else {
1504
+ $active = $dropdown_content.find("[data-selectable]").first();
1505
+ }
1506
+ } else {
1507
+ $active = $create;
1508
+ }
1509
+ this.setActiveOption($active);
1240
1510
  if (triggerDropdown && !this.isOpen) { this.open(); }
1241
1511
  } else {
1242
1512
  this.setActiveOption(null);
1243
1513
  if (triggerDropdown && this.isOpen) { this.close(); }
1244
1514
  }
1245
1515
  };
1246
-
1516
+
1247
1517
  /**
1248
1518
  * Adds an available option. If it already exists,
1249
1519
  * nothing will happen. Note: this does not refresh
1250
1520
  * the options list dropdown (use `refreshOptions`
1251
1521
  * for that).
1252
1522
  *
1523
+ * Usage:
1524
+ *
1525
+ * this.addOption(value, data)
1526
+ * this.addOption(data)
1527
+ *
1253
1528
  * @param {string} value
1254
1529
  * @param {object} data
1255
1530
  */
1256
1531
  Selectize.prototype.addOption = function(value, data) {
1532
+ var i, n, optgroup;
1533
+
1257
1534
  if ($.isArray(value)) {
1258
- for (var i = 0, n = value.length; i < n; i++) {
1535
+ for (i = 0, n = value.length; i < n; i++) {
1259
1536
  this.addOption(value[i][this.settings.valueField], value[i]);
1260
1537
  }
1261
1538
  return;
1262
1539
  }
1263
-
1540
+
1541
+ value = value || '';
1264
1542
  if (this.options.hasOwnProperty(value)) return;
1265
- value = String(value);
1543
+
1266
1544
  this.userOptions[value] = true;
1267
1545
  this.options[value] = data;
1268
1546
  this.lastQuery = null;
1269
- this.trigger('onOptionAdd', value, data);
1547
+ this.trigger('option_add', value, data);
1548
+ };
1549
+
1550
+ /**
1551
+ * Registers a new optgroup for options
1552
+ * to be bucketed into.
1553
+ *
1554
+ * @param {string} id
1555
+ * @param {object} data
1556
+ */
1557
+ Selectize.prototype.addOptionGroup = function(id, data) {
1558
+ this.optgroups[id] = data;
1559
+ this.trigger('optgroup_add', value, data);
1270
1560
  };
1271
-
1561
+
1272
1562
  /**
1273
1563
  * Updates an option available for selection. If
1274
1564
  * it is visible in the selected items or options
@@ -1282,21 +1572,21 @@
1282
1572
  this.options[value] = data;
1283
1573
  if (isset(this.renderCache['item'])) delete this.renderCache['item'][value];
1284
1574
  if (isset(this.renderCache['option'])) delete this.renderCache['option'][value];
1285
-
1575
+
1286
1576
  if (this.items.indexOf(value) !== -1) {
1287
1577
  var $item = this.getItem(value);
1288
1578
  var $item_new = $(this.render('item', data));
1289
1579
  if ($item.hasClass('active')) $item_new.addClass('active');
1290
1580
  $item.replaceWith($item_new);
1291
1581
  }
1292
-
1582
+
1293
1583
  if (this.isOpen) {
1294
1584
  this.refreshOptions(false);
1295
1585
  }
1296
1586
  };
1297
-
1587
+
1298
1588
  /**
1299
- * Removes an option.
1589
+ * Removes a single option.
1300
1590
  *
1301
1591
  * @param {string} value
1302
1592
  */
@@ -1305,9 +1595,22 @@
1305
1595
  delete this.userOptions[value];
1306
1596
  delete this.options[value];
1307
1597
  this.lastQuery = null;
1308
- this.trigger('onOptionRemove', value);
1598
+ this.trigger('option_remove', value);
1599
+ this.removeItem(value);
1600
+ };
1601
+
1602
+ /**
1603
+ * Clears all options.
1604
+ */
1605
+ Selectize.prototype.clearOptions = function() {
1606
+ this.loadedSearches = {};
1607
+ this.userOptions = {};
1608
+ this.options = {};
1609
+ this.lastQuery = null;
1610
+ this.trigger('option_clear');
1611
+ this.clear();
1309
1612
  };
1310
-
1613
+
1311
1614
  /**
1312
1615
  * Returns the jQuery element of the option
1313
1616
  * matching the given value.
@@ -1316,9 +1619,24 @@
1316
1619
  * @returns {object}
1317
1620
  */
1318
1621
  Selectize.prototype.getOption = function(value) {
1319
- return this.$dropdown.children('[data-value="' + value.replace(/(['"])/g, '\\$1') + '"]:first');
1622
+ return value ? this.$dropdown_content.find('[data-selectable]').filter('[data-value="' + value.replace(/(['"])/g, '\\$1') + '"]:first') : $();
1320
1623
  };
1321
-
1624
+
1625
+ /**
1626
+ * Returns the jQuery element of the next or
1627
+ * previous selectable option.
1628
+ *
1629
+ * @param {object} $option
1630
+ * @param {int} direction can be 1 for next or -1 for previous
1631
+ * @return {object}
1632
+ */
1633
+ Selectize.prototype.getAdjacentOption = function($option, direction) {
1634
+ var $options = this.$dropdown.find('[data-selectable]');
1635
+ var index = $options.index($option) + direction;
1636
+
1637
+ return index >= 0 && index < $options.length ? $options.eq(index) : $();
1638
+ };
1639
+
1322
1640
  /**
1323
1641
  * Returns the jQuery element of the item
1324
1642
  * matching the given value.
@@ -1337,7 +1655,7 @@
1337
1655
  }
1338
1656
  return $();
1339
1657
  };
1340
-
1658
+
1341
1659
  /**
1342
1660
  * "Selects" an item. Adds it to the list
1343
1661
  * at the current caret position.
@@ -1345,65 +1663,60 @@
1345
1663
  * @param {string} value
1346
1664
  */
1347
1665
  Selectize.prototype.addItem = function(value) {
1348
- var $item;
1349
- var self = this;
1350
- var inputMode = this.settings.mode;
1351
- var isFull = this.isFull();
1352
- value = String(value);
1353
-
1354
- if (inputMode === 'single') this.clear();
1355
- if (inputMode === 'multi' && isFull) return;
1356
- if (this.items.indexOf(value) !== -1) return;
1357
- if (!this.options.hasOwnProperty(value)) return;
1358
-
1359
- $item = $(this.render('item', this.options[value]));
1360
- this.items.splice(this.caretPos, 0, value);
1361
- this.insertAtCaret($item);
1362
-
1363
- isFull = this.isFull();
1364
- this.$control.toggleClass('has-items', true);
1365
- this.$control.toggleClass('full', isFull).toggleClass('not-full', !isFull);
1366
-
1367
- if (this.isSetup) {
1368
- // remove the option from the menu
1369
- var options = this.$dropdown[0].childNodes;
1370
- for (var i = 0; i < options.length; i++) {
1371
- var $option = $(options[i]);
1372
- if ($option.attr('data-value') === value) {
1373
- $option.remove();
1374
- if ($option[0] === this.$activeOption[0]) {
1375
- this.setActiveOption(options.length ? $(options[0]).addClass('active') : null);
1376
- }
1377
- break;
1666
+ debounce_events(this, ['change'], function() {
1667
+ var $item, $option;
1668
+ var self = this;
1669
+ var inputMode = this.settings.mode;
1670
+ var i, active, options, value_next;
1671
+ value = String(value);
1672
+
1673
+ if (inputMode === 'single') this.clear();
1674
+ if (inputMode === 'multi' && this.isFull()) return;
1675
+ if (this.items.indexOf(value) !== -1) return;
1676
+ if (!this.options.hasOwnProperty(value)) return;
1677
+
1678
+ $item = $(this.render('item', this.options[value]));
1679
+ this.items.splice(this.caretPos, 0, value);
1680
+ this.insertAtCaret($item);
1681
+ this.refreshClasses();
1682
+
1683
+ if (this.isSetup) {
1684
+ // remove the option from the menu
1685
+ options = this.$dropdown_content.find('[data-selectable]');
1686
+ $option = this.getOption(value);
1687
+ value_next = this.getAdjacentOption($option, 1).attr('data-value');
1688
+ this.refreshOptions(true);
1689
+ if (value_next) {
1690
+ this.setActiveOption(this.getOption(value_next));
1378
1691
  }
1692
+
1693
+ // hide the menu if the maximum number of items have been selected or no options are left
1694
+ if (!options.length || (this.settings.maxItems !== null && this.items.length >= this.settings.maxItems)) {
1695
+ this.close();
1696
+ } else {
1697
+ this.positionDropdown();
1698
+ }
1699
+
1700
+ // restore focus to input
1701
+ if (this.isFocused) {
1702
+ window.setTimeout(function() {
1703
+ if (inputMode === 'single') {
1704
+ self.blur();
1705
+ self.focus(false);
1706
+ self.hideInput();
1707
+ } else {
1708
+ self.focus(false);
1709
+ }
1710
+ }, 0);
1711
+ }
1712
+
1713
+ this.updatePlaceholder();
1714
+ this.trigger('item_add', value, $item);
1715
+ this.updateOriginalInput();
1379
1716
  }
1380
-
1381
- // hide the menu if the maximum number of items have been selected or no options are left
1382
- if (!options.length || (this.settings.maxItems !== null && this.items.length >= this.settings.maxItems)) {
1383
- this.close();
1384
- } else {
1385
- this.positionDropdown();
1386
- }
1387
-
1388
- // restore focus to input
1389
- if (this.isFocused) {
1390
- window.setTimeout(function() {
1391
- if (inputMode === 'single') {
1392
- self.blur();
1393
- self.focus(false);
1394
- self.hideInput();
1395
- } else {
1396
- self.focus(false);
1397
- }
1398
- }, 0);
1399
- }
1400
-
1401
- this.updatePlaceholder();
1402
- this.updateOriginalInput();
1403
- this.trigger('onItemAdd', value, $item);
1404
- }
1717
+ });
1405
1718
  };
1406
-
1719
+
1407
1720
  /**
1408
1721
  * Removes the selected item matching
1409
1722
  * the provided value.
@@ -1412,38 +1725,36 @@
1412
1725
  */
1413
1726
  Selectize.prototype.removeItem = function(value) {
1414
1727
  var $item, i, idx;
1415
-
1728
+
1416
1729
  $item = (typeof value === 'object') ? value : this.getItem(value);
1417
1730
  value = String($item.attr('data-value'));
1418
1731
  i = this.items.indexOf(value);
1419
-
1732
+
1420
1733
  if (i !== -1) {
1421
1734
  $item.remove();
1422
1735
  if ($item.hasClass('active')) {
1423
1736
  idx = this.$activeItems.indexOf($item[0]);
1424
1737
  this.$activeItems.splice(idx, 1);
1425
1738
  }
1426
-
1739
+
1427
1740
  this.items.splice(i, 1);
1428
- this.$control.toggleClass('has-items', this.items.length > 0);
1429
- this.$control.removeClass('full').addClass('not-full');
1430
1741
  this.lastQuery = null;
1431
1742
  if (!this.settings.persist && this.userOptions.hasOwnProperty(value)) {
1432
1743
  this.removeOption(value);
1433
1744
  }
1434
- this.setCaret(i);
1435
- this.positionDropdown();
1436
- this.refreshOptions(false);
1437
-
1438
- if (!this.hasOptions) { this.close(); }
1439
- else if (this.isInputFocused) { this.open(); }
1440
-
1745
+
1746
+ if (i < this.caretPos) {
1747
+ this.setCaret(this.caretPos - 1);
1748
+ }
1749
+
1750
+ this.refreshClasses();
1441
1751
  this.updatePlaceholder();
1442
1752
  this.updateOriginalInput();
1443
- this.trigger('onItemRemove', value);
1753
+ this.positionDropdown();
1754
+ this.trigger('item_remove', value);
1444
1755
  }
1445
1756
  };
1446
-
1757
+
1447
1758
  /**
1448
1759
  * Invokes the `create` method provided in the
1449
1760
  * selectize options that should provide the data
@@ -1458,53 +1769,66 @@
1458
1769
  var caret = this.caretPos;
1459
1770
  if (!input.length) return;
1460
1771
  this.lock();
1461
- this.$control_input[0].blur();
1462
-
1772
+
1463
1773
  var setup = (typeof this.settings.create === 'function') ? this.settings.create : function(input) {
1464
1774
  var data = {};
1465
1775
  data[self.settings.labelField] = input;
1466
1776
  data[self.settings.valueField] = input;
1467
1777
  return data;
1468
1778
  };
1469
-
1779
+
1470
1780
  var create = once(function(data) {
1471
1781
  self.unlock();
1472
1782
  self.focus(false);
1473
-
1783
+
1474
1784
  var value = data && data[self.settings.valueField];
1475
1785
  if (!value) return;
1476
-
1786
+
1787
+ self.setTextboxValue('');
1477
1788
  self.addOption(value, data);
1478
- self.setCaret(caret, false);
1789
+ self.setCaret(caret);
1479
1790
  self.addItem(value);
1480
- self.refreshOptions(false);
1481
- self.setTextboxValue('');
1791
+ self.refreshOptions(true);
1792
+ self.focus(false);
1482
1793
  });
1483
-
1484
- var output = setup(input, create);
1485
- if (typeof output === 'object') {
1794
+
1795
+ var output = setup.apply(this, [input, create]);
1796
+ if (typeof output !== 'undefined') {
1486
1797
  create(output);
1487
1798
  }
1488
1799
  };
1489
-
1800
+
1490
1801
  /**
1491
1802
  * Re-renders the selected item lists.
1492
1803
  */
1493
1804
  Selectize.prototype.refreshItems = function() {
1494
- var isFull = this.isFull();
1495
1805
  this.lastQuery = null;
1496
- this.$control.toggleClass('full', isFull).toggleClass('not-full', !isFull);
1497
- this.$control.toggleClass('has-items', this.items.length > 0);
1498
-
1806
+
1499
1807
  if (this.isSetup) {
1500
1808
  for (var i = 0; i < this.items.length; i++) {
1501
1809
  this.addItem(this.items);
1502
1810
  }
1503
1811
  }
1504
-
1812
+
1813
+ this.refreshClasses();
1505
1814
  this.updateOriginalInput();
1506
1815
  };
1507
-
1816
+
1817
+ /**
1818
+ * Updates all state-dependent CSS classes.
1819
+ */
1820
+ Selectize.prototype.refreshClasses = function() {
1821
+ var isFull = this.isFull();
1822
+ var isLocked = this.isLocked;
1823
+ this.$control
1824
+ .toggleClass('focus', this.isFocused)
1825
+ .toggleClass('disabled', this.isDisabled)
1826
+ .toggleClass('locked', isLocked)
1827
+ .toggleClass('full', isFull).toggleClass('not-full', !isFull)
1828
+ .toggleClass('has-items', this.items.length > 0);
1829
+ this.$control_input.data('grow', !isFull && !isLocked);
1830
+ };
1831
+
1508
1832
  /**
1509
1833
  * Determines whether or not more items can be added
1510
1834
  * to the control without exceeding the user-defined maximum.
@@ -1514,14 +1838,14 @@
1514
1838
  Selectize.prototype.isFull = function() {
1515
1839
  return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems;
1516
1840
  };
1517
-
1841
+
1518
1842
  /**
1519
1843
  * Refreshes the original <select> or <input>
1520
1844
  * element to reflect the current state.
1521
1845
  */
1522
1846
  Selectize.prototype.updateOriginalInput = function() {
1523
1847
  var i, n, options;
1524
-
1848
+
1525
1849
  if (this.$input[0].tagName.toLowerCase() === 'select') {
1526
1850
  options = [];
1527
1851
  for (i = 0, n = this.items.length; i < n; i++) {
@@ -1534,13 +1858,13 @@
1534
1858
  } else {
1535
1859
  this.$input.val(this.getValue());
1536
1860
  }
1537
-
1861
+
1538
1862
  this.$input.trigger('change');
1539
1863
  if (this.isSetup) {
1540
- this.trigger('onChange', this.$input.val());
1864
+ this.trigger('change', this.$input.val());
1541
1865
  }
1542
1866
  };
1543
-
1867
+
1544
1868
  /**
1545
1869
  * Shows/hide the input placeholder depending
1546
1870
  * on if there items in the list already.
@@ -1548,7 +1872,7 @@
1548
1872
  Selectize.prototype.updatePlaceholder = function() {
1549
1873
  if (!this.settings.placeholder) return;
1550
1874
  var $input = this.$control_input;
1551
-
1875
+
1552
1876
  if (this.items.length) {
1553
1877
  $input.removeAttr('placeholder');
1554
1878
  } else {
@@ -1556,20 +1880,22 @@
1556
1880
  }
1557
1881
  $input.triggerHandler('update');
1558
1882
  };
1559
-
1883
+
1560
1884
  /**
1561
1885
  * Shows the autocomplete dropdown containing
1562
1886
  * the available options.
1563
1887
  */
1564
1888
  Selectize.prototype.open = function() {
1565
- if (this.isOpen || (this.settings.mode === 'multi' && this.isFull())) return;
1889
+ if (this.isLocked || this.isOpen || (this.settings.mode === 'multi' && this.isFull())) return;
1890
+ this.focus();
1566
1891
  this.isOpen = true;
1567
- this.positionDropdown();
1892
+ this.$dropdown.css({visibility: 'hidden', display: 'block'});
1568
1893
  this.$control.addClass('dropdown-active');
1569
- this.$dropdown.show();
1570
- this.trigger('onDropdownOpen', this.$dropdown);
1894
+ this.positionDropdown();
1895
+ this.$dropdown.css({visibility: 'visible'});
1896
+ this.trigger('dropdown_open', this.$dropdown);
1571
1897
  };
1572
-
1898
+
1573
1899
  /**
1574
1900
  * Closes the autocomplete dropdown menu.
1575
1901
  */
@@ -1577,10 +1903,11 @@
1577
1903
  if (!this.isOpen) return;
1578
1904
  this.$dropdown.hide();
1579
1905
  this.$control.removeClass('dropdown-active');
1906
+ this.setActiveOption(null);
1580
1907
  this.isOpen = false;
1581
- this.trigger('onDropdownClose', this.$dropdown);
1908
+ this.trigger('dropdown_close', this.$dropdown);
1582
1909
  };
1583
-
1910
+
1584
1911
  /**
1585
1912
  * Calculates and applies the appropriate
1586
1913
  * position of the dropdown.
@@ -1589,29 +1916,30 @@
1589
1916
  var $control = this.$control;
1590
1917
  var offset = $control.position();
1591
1918
  offset.top += $control.outerHeight(true);
1592
-
1919
+
1593
1920
  this.$dropdown.css({
1594
1921
  width : $control.outerWidth(),
1595
1922
  top : offset.top,
1596
1923
  left : offset.left
1597
1924
  });
1598
1925
  };
1599
-
1926
+
1600
1927
  /**
1601
1928
  * Resets / clears all selected items
1602
1929
  * from the control.
1603
1930
  */
1604
1931
  Selectize.prototype.clear = function() {
1605
1932
  if (!this.items.length) return;
1606
- this.$control.removeClass('has-items');
1607
1933
  this.$control.children(':not(input)').remove();
1608
1934
  this.items = [];
1609
1935
  this.setCaret(0);
1610
1936
  this.updatePlaceholder();
1611
1937
  this.updateOriginalInput();
1612
- this.trigger('onClear');
1938
+ this.refreshClasses();
1939
+ this.showInput();
1940
+ this.trigger('clear');
1613
1941
  };
1614
-
1942
+
1615
1943
  /**
1616
1944
  * A helper method for inserting an element
1617
1945
  * at the current caret position.
@@ -1627,42 +1955,60 @@
1627
1955
  }
1628
1956
  this.setCaret(caret + 1);
1629
1957
  };
1630
-
1958
+
1631
1959
  /**
1632
1960
  * Removes the current selected item(s).
1633
1961
  *
1634
1962
  * @param {object} e (optional)
1963
+ * @returns {boolean}
1635
1964
  */
1636
1965
  Selectize.prototype.deleteSelection = function(e) {
1637
1966
  var i, n, direction, selection, values, caret, $tail;
1638
-
1639
- direction = (e.keyCode === KEY_BACKSPACE) ? -1 : 1;
1967
+
1968
+ direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1;
1640
1969
  selection = getSelection(this.$control_input[0]);
1970
+
1971
+ // determine items that will be removed
1972
+ values = [];
1973
+
1641
1974
  if (this.$activeItems.length) {
1642
1975
  $tail = this.$control.children('.active:' + (direction > 0 ? 'last' : 'first'));
1643
- caret = Array.prototype.indexOf.apply(this.$control[0].childNodes, [$tail[0]]);
1644
- if (this.$activeItems.length > 1 && direction > 0) { caret--; }
1645
-
1646
- values = [];
1976
+ caret = this.$control.children(':not(input)').index($tail);
1977
+ if (direction > 0) { caret++; }
1978
+
1647
1979
  for (i = 0, n = this.$activeItems.length; i < n; i++) {
1648
1980
  values.push($(this.$activeItems[i]).attr('data-value'));
1649
1981
  }
1650
- while (values.length) {
1651
- this.removeItem(values.pop());
1982
+ if (e) {
1983
+ e.preventDefault();
1984
+ e.stopPropagation();
1652
1985
  }
1653
-
1654
- this.setCaret(caret);
1655
- e.preventDefault();
1656
- e.stopPropagation();
1657
- } else if ((this.isInputFocused || this.settings.mode === 'single') && this.items.length) {
1986
+ } else if ((this.isFocused || this.settings.mode === 'single') && this.items.length) {
1658
1987
  if (direction < 0 && selection.start === 0 && selection.length === 0) {
1659
- this.removeItem(this.items[this.caretPos - 1]);
1988
+ values.push(this.items[this.caretPos - 1]);
1660
1989
  } else if (direction > 0 && selection.start === this.$control_input.val().length) {
1661
- this.removeItem(this.items[this.caretPos]);
1990
+ values.push(this.items[this.caretPos]);
1662
1991
  }
1663
1992
  }
1993
+
1994
+ // allow the callback to abort
1995
+ if (!values.length || (typeof this.settings.onDelete === 'function' && this.settings.onDelete(values) === false)) {
1996
+ return false;
1997
+ }
1998
+
1999
+ // perform removal
2000
+ if (typeof caret !== 'undefined') {
2001
+ this.setCaret(caret);
2002
+ }
2003
+ while (values.length) {
2004
+ this.removeItem(values.pop());
2005
+ }
2006
+
2007
+ this.showInput();
2008
+ this.refreshOptions(true);
2009
+ return true;
1664
2010
  };
1665
-
2011
+
1666
2012
  /**
1667
2013
  * Selects the previous / next item (depending
1668
2014
  * on the `direction` argument).
@@ -1674,28 +2020,33 @@
1674
2020
  * @param {object} e (optional)
1675
2021
  */
1676
2022
  Selectize.prototype.advanceSelection = function(direction, e) {
2023
+ var tail, selection, idx, valueLength, cursorAtEdge, $tail;
2024
+
1677
2025
  if (direction === 0) return;
1678
- var tail = direction > 0 ? 'last' : 'first';
1679
- var selection = getSelection(this.$control_input[0]);
1680
-
1681
- if (this.isInputFocused) {
1682
- var valueLength = this.$control_input.val().length;
1683
- var cursorAtEdge = direction < 0
2026
+
2027
+ tail = direction > 0 ? 'last' : 'first';
2028
+ selection = getSelection(this.$control_input[0]);
2029
+
2030
+ if (this.isInputFocused && !this.isInputHidden) {
2031
+ valueLength = this.$control_input.val().length;
2032
+ cursorAtEdge = direction < 0
1684
2033
  ? selection.start === 0 && selection.length === 0
1685
2034
  : selection.start === valueLength;
1686
-
2035
+
1687
2036
  if (cursorAtEdge && !valueLength) {
1688
2037
  this.advanceCaret(direction, e);
1689
2038
  }
1690
2039
  } else {
1691
- var $tail = this.$control.children('.active:' + tail);
2040
+ $tail = this.$control.children('.active:' + tail);
1692
2041
  if ($tail.length) {
1693
- var idx = Array.prototype.indexOf.apply(this.$control[0].childNodes, [$tail[0]]);
2042
+ idx = this.$control.children(':not(input)').index($tail);
2043
+ this.setActiveItem(null);
1694
2044
  this.setCaret(direction > 0 ? idx + 1 : idx);
2045
+ this.showInput();
1695
2046
  }
1696
2047
  }
1697
2048
  };
1698
-
2049
+
1699
2050
  /**
1700
2051
  * Moves the caret left / right.
1701
2052
  *
@@ -1708,7 +2059,7 @@
1708
2059
  if (this.isShiftDown) {
1709
2060
  var $adj = this.$control_input[fn]();
1710
2061
  if ($adj.length) {
1711
- this.blur();
2062
+ this.hideInput();
1712
2063
  this.setActiveItem($adj);
1713
2064
  e && e.preventDefault();
1714
2065
  }
@@ -1716,52 +2067,72 @@
1716
2067
  this.setCaret(this.caretPos + direction);
1717
2068
  }
1718
2069
  };
1719
-
2070
+
1720
2071
  /**
1721
2072
  * Moves the caret to the specified index.
1722
2073
  *
1723
2074
  * @param {int} i
1724
- * @param {boolean} focus
1725
2075
  */
1726
- Selectize.prototype.setCaret = function(i, focus) {
1727
- if (this.settings.mode === 'single' || this.isFull()) {
2076
+ Selectize.prototype.setCaret = function(i) {
2077
+ if (this.settings.mode === 'single') {
1728
2078
  i = this.items.length;
1729
2079
  } else {
1730
2080
  i = Math.max(0, Math.min(this.items.length, i));
1731
2081
  }
1732
-
1733
- this.ignoreFocus = true;
1734
- this.$control_input.detach();
1735
- if (i === this.items.length) {
1736
- this.$control.append(this.$control_input);
1737
- } else {
1738
- this.$control_input.insertBefore(this.$control.children(':not(input)')[i]);
1739
- }
1740
- this.ignoreFocus = false;
1741
- if (focus && this.isSetup) {
1742
- this.focus(true);
2082
+
2083
+ // the input must be moved by leaving it in place and moving the
2084
+ // siblings, due to the fact that focus cannot be restored once lost
2085
+ // on mobile webkit devices
2086
+ var j, n, fn, $children, $child;
2087
+ $children = this.$control.children(':not(input)');
2088
+ for (j = 0, n = $children.length; j < n; j++) {
2089
+ $child = $($children[j]).detach();
2090
+ if (j < i) {
2091
+ this.$control_input.before($child);
2092
+ } else {
2093
+ this.$control.append($child);
2094
+ }
1743
2095
  }
1744
-
2096
+
1745
2097
  this.caretPos = i;
1746
2098
  };
1747
-
2099
+
1748
2100
  /**
1749
2101
  * Disables user input on the control. Used while
1750
2102
  * items are being asynchronously created.
1751
2103
  */
1752
2104
  Selectize.prototype.lock = function() {
2105
+ this.close();
1753
2106
  this.isLocked = true;
1754
- this.$control.addClass('locked');
2107
+ this.refreshClasses();
1755
2108
  };
1756
-
2109
+
1757
2110
  /**
1758
2111
  * Re-enables user input on the control.
1759
2112
  */
1760
2113
  Selectize.prototype.unlock = function() {
1761
2114
  this.isLocked = false;
1762
- this.$control.removeClass('locked');
2115
+ this.refreshClasses();
2116
+ };
2117
+
2118
+ /**
2119
+ * Disables user input on the control completely.
2120
+ * While disabled, it cannot receive focus.
2121
+ */
2122
+ Selectize.prototype.disable = function() {
2123
+ this.isDisabled = true;
2124
+ this.lock();
2125
+ };
2126
+
2127
+ /**
2128
+ * Enables the control so that it can respond
2129
+ * to focus and user input.
2130
+ */
2131
+ Selectize.prototype.enable = function() {
2132
+ this.isDisabled = false;
2133
+ this.unlock();
1763
2134
  };
1764
-
2135
+
1765
2136
  /**
1766
2137
  * A helper method for rendering "item" and
1767
2138
  * "option" templates, given the data.
@@ -1771,17 +2142,17 @@
1771
2142
  * @returns {string}
1772
2143
  */
1773
2144
  Selectize.prototype.render = function(templateName, data) {
1774
- cache = isset(cache) ? cache : true;
1775
-
1776
- var value, label;
2145
+ var value, id, label;
1777
2146
  var html = '';
1778
2147
  var cache = false;
1779
-
1780
- if (['option', 'item'].indexOf(templateName) !== -1) {
2148
+ var regex_tag = /^[\ ]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i;
2149
+
2150
+ if (templateName === 'option' || templateName === 'item') {
1781
2151
  value = data[this.settings.valueField];
1782
2152
  cache = isset(value);
1783
2153
  }
1784
-
2154
+
2155
+ // pull markup from cache if it exists
1785
2156
  if (cache) {
1786
2157
  if (!isset(this.renderCache[templateName])) {
1787
2158
  this.renderCache[templateName] = {};
@@ -1790,34 +2161,466 @@
1790
2161
  return this.renderCache[templateName][value];
1791
2162
  }
1792
2163
  }
1793
-
2164
+
2165
+ // render markup
1794
2166
  if (this.settings.render && typeof this.settings.render[templateName] === 'function') {
1795
- html = this.settings.render[templateName].apply(this, [data]);
2167
+ html = this.settings.render[templateName].apply(this, [data, htmlEntities]);
1796
2168
  } else {
1797
2169
  label = data[this.settings.labelField];
1798
2170
  switch (templateName) {
2171
+ case 'optgroup':
2172
+ html = '<div class="optgroup">' + data.html + "</div>";
2173
+ break;
2174
+ case 'optgroup_header':
2175
+ label = data[this.settings.optgroupLabelField];
2176
+ html = '<div class="optgroup-header">' + htmlEntities(label) + '</div>';
2177
+ break;
1799
2178
  case 'option':
1800
- html = '<div class="option">' + label + '</div>';
2179
+ html = '<div class="option">' + htmlEntities(label) + '</div>';
1801
2180
  break;
1802
2181
  case 'item':
1803
- html = '<div class="item">' + label + '</div>';
2182
+ html = '<div class="item">' + htmlEntities(label) + '</div>';
1804
2183
  break;
1805
2184
  case 'option_create':
1806
2185
  html = '<div class="create">Create <strong>' + htmlEntities(data.input) + '</strong>&hellip;</div>';
1807
2186
  break;
1808
2187
  }
1809
2188
  }
1810
-
1811
- if (isset(value)) {
1812
- html = html.replace(/^[\ ]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i, '<$1 data-value="' + value + '"');
2189
+
2190
+ // add mandatory attributes
2191
+ if (templateName === 'option' || templateName === 'option_create') {
2192
+ html = html.replace(regex_tag, '<$1 data-selectable');
2193
+ }
2194
+ if (templateName === 'optgroup') {
2195
+ id = data[this.settings.optgroupValueField] || '';
2196
+ html = html.replace(regex_tag, '<$1 data-group="' + htmlEntities(id) + '"');
1813
2197
  }
2198
+ if (templateName === 'option' || templateName === 'item') {
2199
+ html = html.replace(regex_tag, '<$1 data-value="' + htmlEntities(value || '') + '"');
2200
+ }
2201
+
2202
+ // update cache
1814
2203
  if (cache) {
1815
2204
  this.renderCache[templateName][value] = html;
1816
2205
  }
1817
-
2206
+
1818
2207
  return html;
1819
2208
  };
1820
2209
 
2210
+ Selectize.defaults = {
2211
+ plugins: [],
2212
+ delimiter: ',',
2213
+ persist: true,
2214
+ diacritics: true,
2215
+ create: false,
2216
+ highlight: true,
2217
+ openOnFocus: true,
2218
+ maxOptions: 1000,
2219
+ maxItems: null,
2220
+ hideSelected: null,
2221
+ preload: false,
2222
+
2223
+ scrollDuration: 60,
2224
+ loadThrottle: 300,
2225
+
2226
+ dataAttr: 'data-data',
2227
+ optgroupField: 'optgroup',
2228
+ sortField: null,
2229
+ sortDirection: 'asc',
2230
+ valueField: 'value',
2231
+ labelField: 'text',
2232
+ optgroupLabelField: 'label',
2233
+ optgroupValueField: 'value',
2234
+ optgroupOrder: null,
2235
+ searchField: ['text'],
2236
+
2237
+ mode: null,
2238
+ theme: 'default',
2239
+ wrapperClass: 'selectize-control',
2240
+ inputClass: 'selectize-input',
2241
+ dropdownClass: 'selectize-dropdown',
2242
+ dropdownContentClass: 'selectize-dropdown-content',
2243
+
2244
+ load : null, // function(query, callback)
2245
+ score : null, // function(search)
2246
+ onChange : null, // function(value)
2247
+ onItemAdd : null, // function(value, $item) { ... }
2248
+ onItemRemove : null, // function(value) { ... }
2249
+ onClear : null, // function() { ... }
2250
+ onOptionAdd : null, // function(value, data) { ... }
2251
+ onOptionRemove : null, // function(value) { ... }
2252
+ onOptionClear : null, // function() { ... }
2253
+ onDropdownOpen : null, // function($dropdown) { ... }
2254
+ onDropdownClose : null, // function($dropdown) { ... }
2255
+ onType : null, // function(str) { ... }
2256
+ onDelete : null, // function(values) { ... }
2257
+
2258
+ render: {
2259
+ item: null,
2260
+ optgroup: null,
2261
+ optgroup_header: null,
2262
+ option: null,
2263
+ option_create: null
2264
+ }
2265
+ };
2266
+
2267
+ /* --- file: "src/selectize.jquery.js" --- */
2268
+
2269
+ $.fn.selectize = function(settings) {
2270
+ settings = settings || {};
2271
+
2272
+ var defaults = $.fn.selectize.defaults;
2273
+ var dataAttr = settings.dataAttr || defaults.dataAttr;
2274
+
2275
+ /**
2276
+ * Initializes selectize from a <input type="text"> element.
2277
+ *
2278
+ * @param {object} $input
2279
+ * @param {object} settings
2280
+ */
2281
+ var init_textbox = function($input, settings_element) {
2282
+ var i, n, values, value = $.trim($input.val() || '');
2283
+ if (!value.length) return;
2284
+
2285
+ values = value.split(settings.delimiter || defaults.delimiter);
2286
+ for (i = 0, n = values.length; i < n; i++) {
2287
+ settings_element.options[values[i]] = {
2288
+ 'text' : values[i],
2289
+ 'value' : values[i]
2290
+ };
2291
+ }
2292
+
2293
+ settings_element.items = values;
2294
+ };
2295
+
2296
+ /**
2297
+ * Initializes selectize from a <select> element.
2298
+ *
2299
+ * @param {object} $input
2300
+ * @param {object} settings
2301
+ */
2302
+ var init_select = function($input, settings_element) {
2303
+ var i, n, tagName;
2304
+ var $children;
2305
+ settings_element.maxItems = !!$input.attr('multiple') ? null : 1;
2306
+
2307
+ var readData = function($el) {
2308
+ var data = dataAttr && $el.attr(dataAttr);
2309
+ if (typeof data === 'string' && data.length) {
2310
+ return JSON.parse(data);
2311
+ }
2312
+ return null;
2313
+ };
2314
+
2315
+ var addOption = function($option, group) {
2316
+ $option = $($option);
2317
+
2318
+ var value = $option.attr('value') || '';
2319
+ if (!value.length) return;
2320
+
2321
+ settings_element.options[value] = readData($option) || {
2322
+ 'text' : $option.html(),
2323
+ 'value' : value,
2324
+ 'optgroup' : group
2325
+ };
2326
+ if ($option.is(':selected')) {
2327
+ settings_element.items.push(value);
2328
+ }
2329
+ };
2330
+
2331
+ var addGroup = function($optgroup) {
2332
+ var i, n, $options = $('option', $optgroup);
2333
+ $optgroup = $($optgroup);
2334
+
2335
+ var id = $optgroup.attr('label');
2336
+ if (id && id.length) {
2337
+ settings_element.optgroups[id] = readData($optgroup) || {
2338
+ 'label': id
2339
+ };
2340
+ }
2341
+
2342
+ for (i = 0, n = $options.length; i < n; i++) {
2343
+ addOption($options[i], id);
2344
+ }
2345
+ };
2346
+
2347
+ $children = $input.children();
2348
+ for (i = 0, n = $children.length; i < n; i++) {
2349
+ tagName = $children[i].tagName.toLowerCase();
2350
+ if (tagName === 'optgroup') {
2351
+ addGroup($children[i]);
2352
+ } else if (tagName === 'option') {
2353
+ addOption($children[i]);
2354
+ }
2355
+ }
2356
+ };
2357
+
2358
+ return this.each(function() {
2359
+ var instance;
2360
+ var $input = $(this);
2361
+ var tag_name = $input[0].tagName.toLowerCase();
2362
+ var settings_element = {
2363
+ 'placeholder' : $input.attr('placeholder'),
2364
+ 'options' : {},
2365
+ 'optgroups' : {},
2366
+ 'items' : []
2367
+ };
2368
+
2369
+ if (tag_name === 'select') {
2370
+ init_select($input, settings_element);
2371
+ } else {
2372
+ init_textbox($input, settings_element);
2373
+ }
2374
+
2375
+ instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings));
2376
+ $input.data('selectize', instance);
2377
+ $input.addClass('selectized');
2378
+ });
2379
+ };
2380
+
2381
+ $.fn.selectize.defaults = Selectize.defaults;
2382
+
2383
+ /* --- file: "src/plugins/drag_drop/plugin.js" --- */
2384
+
2385
+ /**
2386
+ * Plugin: "drag_drop" (selectize.js)
2387
+ * Copyright (c) 2013 Brian Reavis & contributors
2388
+ *
2389
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
2390
+ * file except in compliance with the License. You may obtain a copy of the License at:
2391
+ * http://www.apache.org/licenses/LICENSE-2.0
2392
+ *
2393
+ * Unless required by applicable law or agreed to in writing, software distributed under
2394
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
2395
+ * ANY KIND, either express or implied. See the License for the specific language
2396
+ * governing permissions and limitations under the License.
2397
+ *
2398
+ * @author Brian Reavis <brian@thirdroute.com>
2399
+ */
2400
+
2401
+ Selectize.registerPlugin('drag_drop', function(options) {
2402
+ if (!$.fn.sortable) throw new Error('The "drag_drop" Selectize plugin requires jQuery UI "sortable".');
2403
+ if (this.settings.mode !== 'multi') return;
2404
+ var self = this;
2405
+
2406
+ this.setup = (function() {
2407
+ var original = self.setup;
2408
+ return function() {
2409
+ original.apply(this, arguments);
2410
+
2411
+ var $control = this.$control.sortable({
2412
+ items: '[data-value]',
2413
+ forcePlaceholderSize: true,
2414
+ start: function(e, ui) {
2415
+ ui.placeholder.css('width', ui.helper.css('width'));
2416
+ $control.css({overflow: 'visible'});
2417
+ },
2418
+ stop: function() {
2419
+ $control.css({overflow: 'hidden'});
2420
+ var active = this.$activeItems ? this.$activeItems.slice() : null;
2421
+ var values = [];
2422
+ $control.children('[data-value]').each(function() {
2423
+ values.push($(this).attr('data-value'));
2424
+ });
2425
+ self.setValue(values);
2426
+ self.setActiveItem(active);
2427
+ }
2428
+ });
2429
+ };
2430
+ })();
2431
+
2432
+ });
2433
+
2434
+ /* --- file: "src/plugins/optgroup_columns/plugin.js" --- */
2435
+
2436
+ /**
2437
+ * Plugin: "optgroup_columns" (selectize.js)
2438
+ * Copyright (c) 2013 Simon Hewitt & contributors
2439
+ *
2440
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
2441
+ * file except in compliance with the License. You may obtain a copy of the License at:
2442
+ * http://www.apache.org/licenses/LICENSE-2.0
2443
+ *
2444
+ * Unless required by applicable law or agreed to in writing, software distributed under
2445
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
2446
+ * ANY KIND, either express or implied. See the License for the specific language
2447
+ * governing permissions and limitations under the License.
2448
+ *
2449
+ * @author Simon Hewitt <si@sjhewitt.co.uk>
2450
+ */
2451
+
2452
+ Selectize.registerPlugin('optgroup_columns', function(options) {
2453
+ var self = this;
2454
+
2455
+ options = $.extend({
2456
+ equalizeWidth : true,
2457
+ equalizeHeight : true
2458
+ }, options);
2459
+
2460
+ this.getAdjacentOption = function($option, direction) {
2461
+ var $options = $option.closest('[data-group]').find('[data-selectable]');
2462
+ var index = $options.index($option) + direction;
2463
+
2464
+ return index >= 0 && index < $options.length ? $options.eq(index) : $();
2465
+ };
2466
+
2467
+ this.onKeyDown = (function() {
2468
+ var original = self.onKeyDown;
2469
+ return function(e) {
2470
+ var index, $option, $options, $optgroup;
2471
+
2472
+ if (this.isOpen && (e.keyCode === KEY_LEFT || e.keyCode === KEY_RIGHT)) {
2473
+ self.ignoreHover = true;
2474
+ $optgroup = this.$activeOption.closest('[data-group]');
2475
+ index = $optgroup.find('[data-selectable]').index(this.$activeOption);
2476
+
2477
+ if(e.keyCode === KEY_LEFT) {
2478
+ $optgroup = $optgroup.prev('[data-group]');
2479
+ } else {
2480
+ $optgroup = $optgroup.next('[data-group]');
2481
+ }
2482
+
2483
+ $options = $optgroup.find('[data-selectable]');
2484
+ $option = $options.eq(Math.min($options.length - 1, index));
2485
+ if ($option.length) {
2486
+ this.setActiveOption($option);
2487
+ }
2488
+ return;
2489
+ }
2490
+
2491
+ return original.apply(this, arguments);
2492
+ };
2493
+ })();
2494
+
2495
+ var equalizeSizes = function() {
2496
+ var i, n, height_max, width, width_last, width_parent, $optgroups;
2497
+
2498
+ $optgroups = $('[data-group]', self.$dropdown_content);
2499
+ n = $optgroups.length;
2500
+ if (!n || !self.$dropdown_content.width()) return;
2501
+
2502
+ if (options.equalizeHeight) {
2503
+ height_max = 0;
2504
+ for (i = 0; i < n; i++) {
2505
+ height_max = Math.max(height_max, $optgroups.eq(i).height());
2506
+ }
2507
+ $optgroups.css({height: height_max});
2508
+ }
2509
+
2510
+ if (options.equalizeWidth) {
2511
+ width_parent = self.$dropdown_content.innerWidth();
2512
+ width = Math.round(width_parent / n);
2513
+ $optgroups.css({width: width});
2514
+ if (n > 1) {
2515
+ width_last = width_parent - width * (n - 1);
2516
+ $optgroups.eq(n - 1).css({width: width_last});
2517
+ }
2518
+ }
2519
+ };
2520
+
2521
+ if (options.equalizeHeight || options.equalizeWidth) {
2522
+ hook.after(this, 'positionDropdown', equalizeSizes);
2523
+ hook.after(this, 'refreshOptions', equalizeSizes);
2524
+ }
2525
+
2526
+
2527
+ });
2528
+
2529
+ /* --- file: "src/plugins/remove_button/plugin.js" --- */
2530
+
2531
+ /**
2532
+ * Plugin: "remove_button" (selectize.js)
2533
+ * Copyright (c) 2013 Brian Reavis & contributors
2534
+ *
2535
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
2536
+ * file except in compliance with the License. You may obtain a copy of the License at:
2537
+ * http://www.apache.org/licenses/LICENSE-2.0
2538
+ *
2539
+ * Unless required by applicable law or agreed to in writing, software distributed under
2540
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
2541
+ * ANY KIND, either express or implied. See the License for the specific language
2542
+ * governing permissions and limitations under the License.
2543
+ *
2544
+ * @author Brian Reavis <brian@thirdroute.com>
2545
+ */
2546
+
2547
+ Selectize.registerPlugin('remove_button', function(options) {
2548
+ var self = this;
2549
+
2550
+ // override the item rendering method to add a "x" to each
2551
+ this.settings.render.item = function(data) {
2552
+ var label = data[self.settings.labelField];
2553
+ return '<div class="item">' + label + ' <a href="javascript:void(0)" class="remove" tabindex="-1" title="Remove">&times;</a></div>';
2554
+ };
2555
+
2556
+ // override the setup method to add an extra "click" handler
2557
+ // that listens for mousedown events on the "x"
2558
+ this.setup = (function() {
2559
+ var original = self.setup;
2560
+ return function() {
2561
+ original.apply(this, arguments);
2562
+ this.$control.on('click', '.remove', function(e) {
2563
+ e.preventDefault();
2564
+ var $item = $(e.target).parent();
2565
+ self.setActiveItem($item);
2566
+ if (self.deleteSelection()) {
2567
+ self.setCaret(self.items.length);
2568
+ }
2569
+ });
2570
+ };
2571
+ })();
2572
+
2573
+ });
2574
+
2575
+ /* --- file: "src/plugins/restore_on_backspace/plugin.js" --- */
2576
+
2577
+ /**
2578
+ * Plugin: "restore_on_backspace" (selectize.js)
2579
+ * Copyright (c) 2013 Brian Reavis & contributors
2580
+ *
2581
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
2582
+ * file except in compliance with the License. You may obtain a copy of the License at:
2583
+ * http://www.apache.org/licenses/LICENSE-2.0
2584
+ *
2585
+ * Unless required by applicable law or agreed to in writing, software distributed under
2586
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
2587
+ * ANY KIND, either express or implied. See the License for the specific language
2588
+ * governing permissions and limitations under the License.
2589
+ *
2590
+ * @author Brian Reavis <brian@thirdroute.com>
2591
+ */
2592
+
2593
+ (function() {
2594
+ Selectize.registerPlugin('restore_on_backspace', function(options) {
2595
+ var self = this;
2596
+
2597
+ options.text = options.text || function(option) {
2598
+ return option[this.settings.labelField];
2599
+ };
2600
+
2601
+ this.onKeyDown = (function(e) {
2602
+ var original = self.onKeyDown;
2603
+ return function(e) {
2604
+ var index, option;
2605
+ if (e.keyCode === KEY_BACKSPACE && this.$control_input.val() === '' && !this.$activeItems.length) {
2606
+ index = this.caretPos - 1;
2607
+ if (index >= 0 && index < this.items.length) {
2608
+ option = this.options[this.items[index]];
2609
+ if (this.deleteSelection(e)) {
2610
+ this.setTextboxValue(options.text.apply(this, [option]));
2611
+ this.refreshOptions(true);
2612
+ }
2613
+ e.preventDefault();
2614
+ return;
2615
+ }
2616
+ }
2617
+ return original.apply(this, arguments);
2618
+ };
2619
+ })();
2620
+
2621
+ });
2622
+ })();
2623
+
1821
2624
  return Selectize;
1822
2625
 
1823
2626
  }));