selectize-rails 0.1.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  }));