').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent);
$dropdown_content = $('
').addClass(settings.dropdownContentClass).appendTo($dropdown);
if(inputId = $input.attr('id')) {
$control_input.attr('id', inputId + '-selectized');
$("label[for='"+inputId+"']").attr('for', inputId + '-selectized');
}
if(self.settings.copyClassesToDropdown) {
$dropdown.addClass(classes);
}
$wrapper.css({
width: $input[0].style.width
});
if (self.plugins.names.length) {
classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
$wrapper.addClass(classes_plugins);
$dropdown.addClass(classes_plugins);
}
if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {
$input.attr('multiple', 'multiple');
}
if (self.settings.placeholder) {
$control_input.attr('placeholder', settings.placeholder);
}
// if splitOn was not passed in, construct it from the delimiter to allow pasting universally
if (!self.settings.splitOn && self.settings.delimiter) {
var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*');
}
if ($input.attr('autocorrect')) {
$control_input.attr('autocorrect', $input.attr('autocorrect'));
}
if ($input.attr('autocapitalize')) {
$control_input.attr('autocapitalize', $input.attr('autocapitalize'));
}
self.$wrapper = $wrapper;
self.$control = $control;
self.$control_input = $control_input;
self.$dropdown = $dropdown;
self.$dropdown_content = $dropdown_content;
$dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
$dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
autoGrow($control_input);
$control.on({
mousedown : function() { return self.onMouseDown.apply(self, arguments); },
click : function() { return self.onClick.apply(self, arguments); }
});
$control_input.on({
mousedown : function(e) { e.stopPropagation(); },
keydown : function() { return self.onKeyDown.apply(self, arguments); },
keyup : function() { return self.onKeyUp.apply(self, arguments); },
keypress : function() { return self.onKeyPress.apply(self, arguments); },
resize : function() { self.positionDropdown.apply(self, []); },
blur : function() { return self.onBlur.apply(self, arguments); },
focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },
paste : function() { return self.onPaste.apply(self, arguments); }
});
$document.on('keydown' + eventNS, function(e) {
self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
self.isShiftDown = e.shiftKey;
});
$document.on('keyup' + eventNS, function(e) {
if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
if (e.keyCode === KEY_CMD) self.isCmdDown = false;
});
$document.on('mousedown' + eventNS, function(e) {
if (self.isFocused) {
// prevent events on the dropdown scrollbar from causing the control to blur
if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) {
return false;
}
// blur on click outside
if (!self.$control.has(e.target).length && e.target !== self.$control[0]) {
self.blur(e.target);
}
}
});
$window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {
if (self.isOpen) {
self.positionDropdown.apply(self, arguments);
}
});
$window.on('mousemove' + eventNS, function() {
self.ignoreHover = false;
});
// store original children and tab index so that they can be
// restored when the destroy() method is called.
this.revertSettings = {
$children : $input.children().detach(),
tabindex : $input.attr('tabindex')
};
$input.attr('tabindex', -1).hide().after(self.$wrapper);
if ($.isArray(settings.items)) {
self.setValue(settings.items);
delete settings.items;
}
// feature detect for the validation API
if (SUPPORTS_VALIDITY_API) {
$input.on('invalid' + eventNS, function(e) {
e.preventDefault();
self.isInvalid = true;
self.refreshState();
});
}
self.updateOriginalInput();
self.refreshItems();
self.refreshState();
self.updatePlaceholder();
self.isSetup = true;
if ($input.is(':disabled')) {
self.disable();
}
self.on('change', this.onChange);
$input.data('selectize', self);
$input.addClass('selectized');
self.trigger('initialize');
// preload options
if (settings.preload === true) {
self.onSearchChange('');
}
},
/**
* Sets up default rendering functions.
*/
setupTemplates: function() {
var self = this;
var field_label = self.settings.labelField;
var field_optgroup = self.settings.optgroupLabelField;
var templates = {
'optgroup': function(data) {
return '
' + data.html + '
';
},
'optgroup_header': function(data, escape) {
return '';
},
'option': function(data, escape) {
return '
' + escape(data[field_label]) + '
';
},
'item': function(data, escape) {
return '
' + escape(data[field_label]) + '
';
},
'option_create': function(data, escape) {
return '
Add ' + escape(data.input) + '…
';
}
};
self.settings.render = $.extend({}, templates, self.settings.render);
},
/**
* Maps fired events to callbacks provided
* in the settings used when creating the control.
*/
setupCallbacks: function() {
var key, fn, callbacks = {
'initialize' : 'onInitialize',
'change' : 'onChange',
'item_add' : 'onItemAdd',
'item_remove' : 'onItemRemove',
'clear' : 'onClear',
'option_add' : 'onOptionAdd',
'option_remove' : 'onOptionRemove',
'option_clear' : 'onOptionClear',
'optgroup_add' : 'onOptionGroupAdd',
'optgroup_remove' : 'onOptionGroupRemove',
'optgroup_clear' : 'onOptionGroupClear',
'dropdown_open' : 'onDropdownOpen',
'dropdown_close' : 'onDropdownClose',
'type' : 'onType',
'load' : 'onLoad',
'focus' : 'onFocus',
'blur' : 'onBlur'
};
for (key in callbacks) {
if (callbacks.hasOwnProperty(key)) {
fn = this.settings[callbacks[key]];
if (fn) this.on(key, fn);
}
}
},
/**
* Triggered when the main control element
* has a click event.
*
* @param {object} e
* @return {boolean}
*/
onClick: function(e) {
var self = this;
// necessary for mobile webkit devices (manual focus triggering
// is ignored unless invoked within a click event)
if (!self.isFocused) {
self.focus();
e.preventDefault();
}
},
/**
* Triggered when the main control element
* has a mouse down event.
*
* @param {object} e
* @return {boolean}
*/
onMouseDown: function(e) {
var self = this;
var defaultPrevented = e.isDefaultPrevented();
var $target = $(e.target);
if (self.isFocused) {
// retain focus by preventing native handling. if the
// event target is the input it should not be modified.
// otherwise, text selection within the input won't work.
if (e.target !== self.$control_input[0]) {
if (self.settings.mode === 'single') {
// toggle dropdown
self.isOpen ? self.close() : self.open();
} else if (!defaultPrevented) {
self.setActiveItem(null);
}
return false;
}
} else {
// give control focus
if (!defaultPrevented) {
window.setTimeout(function() {
self.focus();
}, 0);
}
}
},
/**
* Triggered when the value of the control has been changed.
* This should propagate the event to the original DOM
* input / select element.
*/
onChange: function() {
this.$input.trigger('change');
},
/**
* Triggered on
paste.
*
* @param {object} e
* @returns {boolean}
*/
onPaste: function(e) {
var self = this;
if (self.isFull() || self.isInputHidden || self.isLocked) {
e.preventDefault();
return;
}
// If a regex or string is included, this will split the pasted
// input and create Items for each separate value
if (self.settings.splitOn) {
// Wait for pasted text to be recognized in value
setTimeout(function() {
var pastedText = self.$control_input.val();
if(!pastedText.match(self.settings.splitOn)){ return }
var splitInput = $.trim(pastedText).split(self.settings.splitOn);
for (var i = 0, n = splitInput.length; i < n; i++) {
self.createItem(splitInput[i]);
}
}, 0);
}
},
/**
* Triggered on
keypress.
*
* @param {object} e
* @returns {boolean}
*/
onKeyPress: function(e) {
if (this.isLocked) return e && e.preventDefault();
var character = String.fromCharCode(e.keyCode || e.which);
if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {
this.createItem();
e.preventDefault();
return false;
}
},
/**
* Triggered on
keydown.
*
* @param {object} e
* @returns {boolean}
*/
onKeyDown: function(e) {
var isInput = e.target === this.$control_input[0];
var self = this;
if (self.isLocked) {
if (e.keyCode !== KEY_TAB) {
e.preventDefault();
}
return;
}
switch (e.keyCode) {
case KEY_A:
if (self.isCmdDown) {
self.selectAll();
return;
}
break;
case KEY_ESC:
if (self.isOpen) {
e.preventDefault();
e.stopPropagation();
self.close();
}
return;
case KEY_N:
if (!e.ctrlKey || e.altKey) break;
case KEY_DOWN:
if (!self.isOpen && self.hasOptions) {
self.open();
} else if (self.$activeOption) {
self.ignoreHover = true;
var $next = self.getAdjacentOption(self.$activeOption, 1);
if ($next.length) self.setActiveOption($next, true, true);
}
e.preventDefault();
return;
case KEY_P:
if (!e.ctrlKey || e.altKey) break;
case KEY_UP:
if (self.$activeOption) {
self.ignoreHover = true;
var $prev = self.getAdjacentOption(self.$activeOption, -1);
if ($prev.length) self.setActiveOption($prev, true, true);
}
e.preventDefault();
return;
case KEY_RETURN:
if (self.isOpen && self.$activeOption) {
self.onOptionSelect({currentTarget: self.$activeOption});
e.preventDefault();
}
return;
case KEY_LEFT:
self.advanceSelection(-1, e);
return;
case KEY_RIGHT:
self.advanceSelection(1, e);
return;
case KEY_TAB:
if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {
self.onOptionSelect({currentTarget: self.$activeOption});
// Default behaviour is to jump to the next field, we only want this
// if the current field doesn't accept any more entries
if (!self.isFull()) {
e.preventDefault();
}
}
if (self.settings.create && self.createItem()) {
e.preventDefault();
}
return;
case KEY_BACKSPACE:
case KEY_DELETE:
self.deleteSelection(e);
return;
}
if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {
e.preventDefault();
return;
}
},
/**
* Triggered on
keyup.
*
* @param {object} e
* @returns {boolean}
*/
onKeyUp: function(e) {
var self = this;
if (self.isLocked) return e && e.preventDefault();
var value = self.$control_input.val() || '';
if (self.lastValue !== value) {
self.lastValue = value;
self.onSearchChange(value);
self.refreshOptions();
self.trigger('type', value);
}
},
/**
* Invokes the user-provide option provider / loader.
*
* Note: this function is debounced in the Selectize
* constructor (by `settings.loadThrottle` milliseconds)
*
* @param {string} value
*/
onSearchChange: function(value) {
var self = this;
var fn = self.settings.load;
if (!fn) return;
if (self.loadedSearches.hasOwnProperty(value)) return;
self.loadedSearches[value] = true;
self.load(function(callback) {
fn.apply(self, [value, callback]);
});
},
/**
* Triggered on
focus.
*
* @param {object} e (optional)
* @returns {boolean}
*/
onFocus: function(e) {
var self = this;
var wasFocused = self.isFocused;
if (self.isDisabled) {
self.blur();
e && e.preventDefault();
return false;
}
if (self.ignoreFocus) return;
self.isFocused = true;
if (self.settings.preload === 'focus') self.onSearchChange('');
if (!wasFocused) self.trigger('focus');
if (!self.$activeItems.length) {
self.showInput();
self.setActiveItem(null);
self.refreshOptions(!!self.settings.openOnFocus);
}
self.refreshState();
},
/**
* Triggered on
blur.
*
* @param {object} e
* @param {Element} dest
*/
onBlur: function(e, dest) {
var self = this;
if (!self.isFocused) return;
self.isFocused = false;
if (self.ignoreFocus) {
return;
} else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) {
// necessary to prevent IE closing the dropdown when the scrollbar is clicked
self.ignoreBlur = true;
self.onFocus(e);
return;
}
var deactivate = function() {
self.close();
self.setTextboxValue('');
self.setActiveItem(null);
self.setActiveOption(null);
self.setCaret(self.items.length);
self.refreshState();
// IE11 bug: element still marked as active
dest && dest.focus && dest.focus();
self.ignoreFocus = false;
self.trigger('blur');
};
self.ignoreFocus = true;
if (self.settings.create && self.settings.createOnBlur) {
self.createItem(null, false, deactivate);
} else {
deactivate();
}
},
/**
* Triggered when the user rolls over
* an option in the autocomplete dropdown menu.
*
* @param {object} e
* @returns {boolean}
*/
onOptionHover: function(e) {
if (this.ignoreHover) return;
this.setActiveOption(e.currentTarget, false);
},
/**
* Triggered when the user clicks on an option
* in the autocomplete dropdown menu.
*
* @param {object} e
* @returns {boolean}
*/
onOptionSelect: function(e) {
var value, $target, $option, self = this;
if (e.preventDefault) {
e.preventDefault();
e.stopPropagation();
}
$target = $(e.currentTarget);
if ($target.hasClass('create')) {
self.createItem(null, function() {
if (self.settings.closeAfterSelect) {
self.close();
}
});
} else {
value = $target.attr('data-value');
if (typeof value !== 'undefined') {
self.lastQuery = null;
self.setTextboxValue('');
self.addItem(value);
if (self.settings.closeAfterSelect) {
self.close();
} else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {
self.setActiveOption(self.getOption(value));
}
}
}
},
/**
* Triggered when the user clicks on an item
* that has been selected.
*
* @param {object} e
* @returns {boolean}
*/
onItemSelect: function(e) {
var self = this;
if (self.isLocked) return;
if (self.settings.mode === 'multi') {
e.preventDefault();
self.setActiveItem(e.currentTarget, e);
}
},
/**
* Invokes the provided method that provides
* results to a callback---which are then added
* as options to the control.
*
* @param {function} fn
*/
load: function(fn) {
var self = this;
var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);
self.loading++;
fn.apply(self, [function(results) {
self.loading = Math.max(self.loading - 1, 0);
if (results && results.length) {
self.addOption(results);
self.refreshOptions(self.isFocused && !self.isInputHidden);
}
if (!self.loading) {
$wrapper.removeClass(self.settings.loadingClass);
}
self.trigger('load', results);
}]);
},
/**
* Sets the input field of the control to the specified value.
*
* @param {string} value
*/
setTextboxValue: function(value) {
var $input = this.$control_input;
var changed = $input.val() !== value;
if (changed) {
$input.val(value).triggerHandler('update');
this.lastValue = value;
}
},
/**
* Returns the value of the control. If multiple items
* can be selected (e.g.