You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4301 lines
155 KiB
4301 lines
155 KiB
5 years ago
|
/*!
|
||
5 years ago
|
* # Fomantic-UI - Dropdown
|
||
|
* http://github.com/fomantic/Fomantic-UI/
|
||
5 years ago
|
*
|
||
|
*
|
||
|
* Released under the MIT license
|
||
|
* http://opensource.org/licenses/MIT
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Copyright 2019 The Gitea Authors
|
||
|
* Released under the MIT license
|
||
|
* http://opensource.org/licenses/MIT
|
||
|
* This version has been modified by Gitea to improve accessibility.
|
||
|
*/
|
||
|
|
||
|
;(function ($, window, document, undefined) {
|
||
|
|
||
|
'use strict';
|
||
|
|
||
5 years ago
|
$.isFunction = $.isFunction || function(obj) {
|
||
|
return typeof obj === "function" && typeof obj.nodeType !== "number";
|
||
|
};
|
||
|
|
||
5 years ago
|
window = (typeof window != 'undefined' && window.Math == Math)
|
||
|
? window
|
||
|
: (typeof self != 'undefined' && self.Math == Math)
|
||
|
? self
|
||
|
: Function('return this')()
|
||
|
;
|
||
|
|
||
|
$.fn.dropdown = function(parameters) {
|
||
|
var
|
||
|
$allModules = $(this),
|
||
|
$document = $(document),
|
||
|
|
||
|
moduleSelector = $allModules.selector || '',
|
||
|
|
||
|
hasTouch = ('ontouchstart' in document.documentElement),
|
||
5 years ago
|
clickEvent = hasTouch
|
||
|
? 'touchstart'
|
||
|
: 'click',
|
||
|
|
||
5 years ago
|
time = new Date().getTime(),
|
||
|
performance = [],
|
||
|
|
||
|
query = arguments[0],
|
||
|
methodInvoked = (typeof query == 'string'),
|
||
|
queryArguments = [].slice.call(arguments, 1),
|
||
|
lastAriaID = 1,
|
||
|
returnedValue
|
||
|
;
|
||
|
|
||
|
$allModules
|
||
|
.each(function(elementIndex) {
|
||
|
var
|
||
|
settings = ( $.isPlainObject(parameters) )
|
||
|
? $.extend(true, {}, $.fn.dropdown.settings, parameters)
|
||
|
: $.extend({}, $.fn.dropdown.settings),
|
||
|
|
||
|
className = settings.className,
|
||
|
message = settings.message,
|
||
|
fields = settings.fields,
|
||
|
keys = settings.keys,
|
||
|
metadata = settings.metadata,
|
||
|
namespace = settings.namespace,
|
||
|
regExp = settings.regExp,
|
||
|
selector = settings.selector,
|
||
|
error = settings.error,
|
||
|
templates = settings.templates,
|
||
|
|
||
|
eventNamespace = '.' + namespace,
|
||
|
moduleNamespace = 'module-' + namespace,
|
||
|
|
||
|
$module = $(this),
|
||
|
$context = $(settings.context),
|
||
|
$text = $module.find(selector.text),
|
||
|
$search = $module.find(selector.search),
|
||
|
$sizer = $module.find(selector.sizer),
|
||
|
$input = $module.find(selector.input),
|
||
|
$icon = $module.find(selector.icon),
|
||
5 years ago
|
$clear = $module.find(selector.clearIcon),
|
||
5 years ago
|
|
||
|
$combo = ($module.prev().find(selector.text).length > 0)
|
||
|
? $module.prev().find(selector.text)
|
||
|
: $module.prev(),
|
||
|
|
||
|
$menu = $module.children(selector.menu),
|
||
|
$item = $menu.find(selector.item),
|
||
5 years ago
|
$divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(),
|
||
5 years ago
|
|
||
|
activated = false,
|
||
|
itemActivated = false,
|
||
|
internalChange = false,
|
||
5 years ago
|
iconClicked = false,
|
||
5 years ago
|
element = this,
|
||
|
instance = $module.data(moduleNamespace),
|
||
|
|
||
5 years ago
|
selectActionActive,
|
||
5 years ago
|
initialLoad,
|
||
|
pageLostFocus,
|
||
|
willRefocus,
|
||
|
elementNamespace,
|
||
|
id,
|
||
|
selectObserver,
|
||
|
menuObserver,
|
||
|
module
|
||
|
;
|
||
|
|
||
|
module = {
|
||
|
|
||
|
initialize: function() {
|
||
|
module.debug('Initializing dropdown', settings);
|
||
|
|
||
|
if( module.is.alreadySetup() ) {
|
||
|
module.setup.reference();
|
||
|
}
|
||
|
else {
|
||
5 years ago
|
if (settings.ignoreDiacritics && !String.prototype.normalize) {
|
||
|
settings.ignoreDiacritics = false;
|
||
|
module.error(error.noNormalize, element);
|
||
|
}
|
||
5 years ago
|
|
||
|
module.setup.layout();
|
||
|
|
||
|
if(settings.values) {
|
||
|
module.change.values(settings.values);
|
||
|
}
|
||
|
|
||
|
module.refreshData();
|
||
|
|
||
|
module.save.defaults();
|
||
|
module.restore.selected();
|
||
|
|
||
|
module.create.id();
|
||
|
module.bind.events();
|
||
|
|
||
|
module.observeChanges();
|
||
|
module.instantiate();
|
||
|
|
||
|
module.aria.setup();
|
||
|
}
|
||
|
|
||
|
},
|
||
|
|
||
|
instantiate: function() {
|
||
|
module.verbose('Storing instance of dropdown', module);
|
||
|
instance = module;
|
||
|
$module
|
||
|
.data(moduleNamespace, module)
|
||
|
;
|
||
|
},
|
||
|
|
||
|
destroy: function() {
|
||
|
module.verbose('Destroying previous dropdown', $module);
|
||
|
module.remove.tabbable();
|
||
5 years ago
|
module.remove.active();
|
||
|
$menu.transition('stop all');
|
||
|
$menu.removeClass(className.visible).addClass(className.hidden);
|
||
5 years ago
|
$module
|
||
|
.off(eventNamespace)
|
||
|
.removeData(moduleNamespace)
|
||
|
;
|
||
|
$menu
|
||
|
.off(eventNamespace)
|
||
|
;
|
||
|
$document
|
||
|
.off(elementNamespace)
|
||
|
;
|
||
|
module.disconnect.menuObserver();
|
||
|
module.disconnect.selectObserver();
|
||
|
},
|
||
|
|
||
|
observeChanges: function() {
|
||
|
if('MutationObserver' in window) {
|
||
|
selectObserver = new MutationObserver(module.event.select.mutation);
|
||
|
menuObserver = new MutationObserver(module.event.menu.mutation);
|
||
|
module.debug('Setting up mutation observer', selectObserver, menuObserver);
|
||
|
module.observe.select();
|
||
|
module.observe.menu();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
disconnect: {
|
||
|
menuObserver: function() {
|
||
|
if(menuObserver) {
|
||
|
menuObserver.disconnect();
|
||
|
}
|
||
|
},
|
||
|
selectObserver: function() {
|
||
|
if(selectObserver) {
|
||
|
selectObserver.disconnect();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
observe: {
|
||
|
select: function() {
|
||
5 years ago
|
if(module.has.input() && selectObserver) {
|
||
5 years ago
|
selectObserver.observe($module[0], {
|
||
|
childList : true,
|
||
|
subtree : true
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
menu: function() {
|
||
5 years ago
|
if(module.has.menu() && menuObserver) {
|
||
5 years ago
|
menuObserver.observe($menu[0], {
|
||
|
childList : true,
|
||
|
subtree : true
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
create: {
|
||
|
id: function() {
|
||
|
id = (Math.random().toString(16) + '000000000').substr(2, 8);
|
||
|
elementNamespace = '.' + id;
|
||
|
module.verbose('Creating unique id for element', id);
|
||
|
},
|
||
|
userChoice: function(values) {
|
||
|
var
|
||
|
$userChoices,
|
||
|
$userChoice,
|
||
|
isUserValue,
|
||
|
html
|
||
|
;
|
||
|
values = values || module.get.userValues();
|
||
|
if(!values) {
|
||
|
return false;
|
||
|
}
|
||
5 years ago
|
values = Array.isArray(values)
|
||
5 years ago
|
? values
|
||
|
: [values]
|
||
|
;
|
||
|
$.each(values, function(index, value) {
|
||
|
if(module.get.item(value) === false) {
|
||
|
html = settings.templates.addition( module.add.variables(message.addResult, value) );
|
||
|
$userChoice = $('<div />')
|
||
|
.html(html)
|
||
|
.attr('data-' + metadata.value, value)
|
||
|
.attr('data-' + metadata.text, value)
|
||
|
.addClass(className.addition)
|
||
|
.addClass(className.item)
|
||
|
;
|
||
|
if(settings.hideAdditions) {
|
||
|
$userChoice.addClass(className.hidden);
|
||
|
}
|
||
|
$userChoices = ($userChoices === undefined)
|
||
|
? $userChoice
|
||
|
: $userChoices.add($userChoice)
|
||
|
;
|
||
|
module.verbose('Creating user choices for value', value, $userChoice);
|
||
|
}
|
||
|
});
|
||
|
return $userChoices;
|
||
|
},
|
||
|
userLabels: function(value) {
|
||
|
var
|
||
|
userValues = module.get.userValues()
|
||
|
;
|
||
|
if(userValues) {
|
||
|
module.debug('Adding user labels', userValues);
|
||
|
$.each(userValues, function(index, value) {
|
||
|
module.verbose('Adding custom user value');
|
||
|
module.add.label(value, value);
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
menu: function() {
|
||
|
$menu = $('<div />')
|
||
|
.addClass(className.menu)
|
||
|
.appendTo($module)
|
||
|
;
|
||
|
},
|
||
|
sizer: function() {
|
||
|
$sizer = $('<span />')
|
||
|
.addClass(className.sizer)
|
||
|
.insertAfter($search)
|
||
|
;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
search: function(query) {
|
||
|
query = (query !== undefined)
|
||
|
? query
|
||
|
: module.get.query()
|
||
|
;
|
||
|
module.verbose('Searching for query', query);
|
||
|
if(module.has.minCharacters(query)) {
|
||
|
module.filter(query);
|
||
|
}
|
||
|
else {
|
||
5 years ago
|
module.hide(null,true);
|
||
5 years ago
|
}
|
||
|
},
|
||
|
|
||
|
select: {
|
||
|
firstUnfiltered: function() {
|
||
|
module.verbose('Selecting first non-filtered element');
|
||
|
module.remove.selectedItem();
|
||
|
$item
|
||
|
.not(selector.unselectable)
|
||
|
.not(selector.addition + selector.hidden)
|
||
|
.eq(0)
|
||
|
.addClass(className.selected)
|
||
|
;
|
||
|
},
|
||
|
nextAvailable: function($selected) {
|
||
|
$selected = $selected.eq(0);
|
||
|
var
|
||
|
$nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0),
|
||
|
$prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0),
|
||
|
hasNext = ($nextAvailable.length > 0)
|
||
|
;
|
||
|
if(hasNext) {
|
||
|
module.verbose('Moving selection to', $nextAvailable);
|
||
|
$nextAvailable.addClass(className.selected);
|
||
|
}
|
||
|
else {
|
||
|
module.verbose('Moving selection to', $prevAvailable);
|
||
|
$prevAvailable.addClass(className.selected);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
aria: {
|
||
|
setup: function() {
|
||
|
var role = module.aria.guessRole();
|
||
|
if( role !== 'menu' ) {
|
||
|
return;
|
||
|
}
|
||
|
$module.attr('aria-busy', 'true');
|
||
|
$module.attr('role', 'menu');
|
||
|
$module.attr('aria-haspopup', 'menu');
|
||
|
$module.attr('aria-expanded', 'false');
|
||
|
$menu.find('.divider').attr('role', 'separator');
|
||
|
$item.attr('role', 'menuitem');
|
||
|
$item.each(function (index, item) {
|
||
|
if( !item.id ) {
|
||
|
item.id = module.aria.nextID('menuitem');
|
||
|
}
|
||
|
});
|
||
|
$text = $module
|
||
|
.find('> .text')
|
||
|
.eq(0)
|
||
|
;
|
||
|
if( $module.data('content') ) {
|
||
|
$text.attr('aria-hidden');
|
||
|
$module.attr('aria-label', $module.data('content'));
|
||
|
}
|
||
|
else {
|
||
|
$text.attr('id', module.aria.nextID('menutext'));
|
||
|
$module.attr('aria-labelledby', $text.attr('id'));
|
||
|
}
|
||
|
$module.attr('aria-busy', 'false');
|
||
|
},
|
||
|
nextID: function(prefix) {
|
||
|
var nextID;
|
||
|
do {
|
||
|
nextID = prefix + '_' + lastAriaID++;
|
||
|
} while( document.getElementById(nextID) );
|
||
|
return nextID;
|
||
|
},
|
||
|
setExpanded: function(expanded) {
|
||
|
if( $module.attr('aria-haspopup') ) {
|
||
|
$module.attr('aria-expanded', expanded);
|
||
|
}
|
||
|
},
|
||
|
refreshDescendant: function() {
|
||
|
if( $module.attr('aria-haspopup') !== 'menu' ) {
|
||
|
return;
|
||
|
}
|
||
|
var
|
||
|
$currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0),
|
||
|
$activeItem = $menu.children('.' + className.active).eq(0),
|
||
|
$selectedItem = ($currentlySelected.length > 0)
|
||
|
? $currentlySelected
|
||
|
: $activeItem
|
||
|
;
|
||
|
if( $selectedItem ) {
|
||
|
$module.attr('aria-activedescendant', $selectedItem.attr('id'));
|
||
|
}
|
||
|
else {
|
||
|
module.aria.removeDescendant();
|
||
|
}
|
||
|
},
|
||
|
removeDescendant: function() {
|
||
|
if( $module.attr('aria-haspopup') == 'menu' ) {
|
||
|
$module.removeAttr('aria-activedescendant');
|
||
|
}
|
||
|
},
|
||
|
guessRole: function() {
|
||
|
var
|
||
|
isIcon = $module.hasClass('icon'),
|
||
|
hasSearch = module.has.search(),
|
||
|
hasInput = ($input.length > 0),
|
||
|
isMultiple = module.is.multiple()
|
||
|
;
|
||
|
if ( !isIcon && !hasSearch && !hasInput && !isMultiple ) {
|
||
|
return 'menu';
|
||
|
}
|
||
|
return 'unknown';
|
||
|
}
|
||
|
},
|
||
|
|
||
|
setup: {
|
||
|
api: function() {
|
||
|
var
|
||
|
apiSettings = {
|
||
|
debug : settings.debug,
|
||
|
urlData : {
|
||
|
value : module.get.value(),
|
||
|
query : module.get.query()
|
||
|
},
|
||
|
on : false
|
||
|
}
|
||
|
;
|
||
|
module.verbose('First request, initializing API');
|
||
|
$module
|
||
|
.api(apiSettings)
|
||
|
;
|
||
|
},
|
||
|
layout: function() {
|
||
|
if( $module.is('select') ) {
|
||
|
module.setup.select();
|
||
|
module.setup.returnedObject();
|
||
|
}
|
||
|
if( !module.has.menu() ) {
|
||
|
module.create.menu();
|
||
|
}
|
||
5 years ago
|
if ( module.is.selection() && module.is.clearable() && !module.has.clearItem() ) {
|
||
|
module.verbose('Adding clear icon');
|
||
|
$clear = $('<i />')
|
||
|
.addClass('remove icon')
|
||
|
.insertBefore($text)
|
||
|
;
|
||
|
}
|
||
5 years ago
|
if( module.is.search() && !module.has.search() ) {
|
||
|
module.verbose('Adding search input');
|
||
|
$search = $('<input />')
|
||
|
.addClass(className.search)
|
||
|
.prop('autocomplete', 'off')
|
||
|
.insertBefore($text)
|
||
|
;
|
||
|
}
|
||
|
if( module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) {
|
||
|
module.create.sizer();
|
||
|
}
|
||
|
if(settings.allowTab) {
|
||
|
module.set.tabbable();
|
||
|
}
|
||
|
$item.attr('tabindex', '-1');
|
||
|
},
|
||
|
select: function() {
|
||
|
var
|
||
|
selectValues = module.get.selectValues()
|
||
|
;
|
||
|
module.debug('Dropdown initialized on a select', selectValues);
|
||
|
if( $module.is('select') ) {
|
||
|
$input = $module;
|
||
|
}
|
||
|
// see if select is placed correctly already
|
||
|
if($input.parent(selector.dropdown).length > 0) {
|
||
|
module.debug('UI dropdown already exists. Creating dropdown menu only');
|
||
|
$module = $input.closest(selector.dropdown);
|
||
|
if( !module.has.menu() ) {
|
||
|
module.create.menu();
|
||
|
}
|
||
|
$menu = $module.children(selector.menu);
|
||
|
module.setup.menu(selectValues);
|
||
|
}
|
||
|
else {
|
||
|
module.debug('Creating entire dropdown from select');
|
||
|
$module = $('<div />')
|
||
|
.attr('class', $input.attr('class') )
|
||
|
.addClass(className.selection)
|
||
|
.addClass(className.dropdown)
|
||
5 years ago
|
.html( templates.dropdown(selectValues, fields, settings.preserveHTML, settings.className) )
|
||
5 years ago
|
.insertBefore($input)
|
||
|
;
|
||
|
if($input.hasClass(className.multiple) && $input.prop('multiple') === false) {
|
||
|
module.error(error.missingMultiple);
|
||
|
$input.prop('multiple', true);
|
||
|
}
|
||
|
if($input.is('[multiple]')) {
|
||
|
module.set.multiple();
|
||
|
}
|
||
|
if ($input.prop('disabled')) {
|
||
|
module.debug('Disabling dropdown');
|
||
|
$module.addClass(className.disabled);
|
||
|
}
|
||
|
$input
|
||
5 years ago
|
.removeAttr('required')
|
||
5 years ago
|
.removeAttr('class')
|
||
|
.detach()
|
||
|
.prependTo($module)
|
||
|
;
|
||
|
}
|
||
|
module.refresh();
|
||
|
},
|
||
|
menu: function(values) {
|
||
5 years ago
|
$menu.html( templates.menu(values, fields,settings.preserveHTML,settings.className));
|
||
|
$item = $menu.find(selector.item);
|
||
|
$divider = settings.hideDividers ? $item.parent().children(selector.divider) : $();
|
||
5 years ago
|
},
|
||
|
reference: function() {
|
||
|
module.debug('Dropdown behavior was called on select, replacing with closest dropdown');
|
||
|
// replace module reference
|
||
|
$module = $module.parent(selector.dropdown);
|
||
|
instance = $module.data(moduleNamespace);
|
||
|
element = $module.get(0);
|
||
|
module.refresh();
|
||
|
module.setup.returnedObject();
|
||
|
},
|
||
|
returnedObject: function() {
|
||
|
var
|
||
|
$firstModules = $allModules.slice(0, elementIndex),
|
||
|
$lastModules = $allModules.slice(elementIndex + 1)
|
||
|
;
|
||
|
// adjust all modules to use correct reference
|
||
|
$allModules = $firstModules.add($module).add($lastModules);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
refresh: function() {
|
||
|
module.refreshSelectors();
|
||
|
module.refreshData();
|
||
|
},
|
||
|
|
||
|
refreshItems: function() {
|
||
5 years ago
|
$item = $menu.find(selector.item);
|
||
|
$divider = settings.hideDividers ? $item.parent().children(selector.divider) : $();
|
||
5 years ago
|
},
|
||
|
|
||
|
refreshSelectors: function() {
|
||
|
module.verbose('Refreshing selector cache');
|
||
|
$text = $module.find(selector.text);
|
||
|
$search = $module.find(selector.search);
|
||
|
$input = $module.find(selector.input);
|
||
|
$icon = $module.find(selector.icon);
|
||
|
$combo = ($module.prev().find(selector.text).length > 0)
|
||
|
? $module.prev().find(selector.text)
|
||
|
: $module.prev()
|
||
|
;
|
||
|
$menu = $module.children(selector.menu);
|
||
|
$item = $menu.find(selector.item);
|
||
5 years ago
|
$divider = settings.hideDividers ? $item.parent().children(selector.divider) : $();
|
||
5 years ago
|
},
|
||
|
|
||
|
refreshData: function() {
|
||
|
module.verbose('Refreshing cached metadata');
|
||
|
$item
|
||
|
.removeData(metadata.text)
|
||
|
.removeData(metadata.value)
|
||
|
;
|
||
|
},
|
||
|
|
||
|
clearData: function() {
|
||
|
module.verbose('Clearing metadata');
|
||
|
$item
|
||
|
.removeData(metadata.text)
|
||
|
.removeData(metadata.value)
|
||
|
;
|
||
|
$module
|
||
|
.removeData(metadata.defaultText)
|
||
|
.removeData(metadata.defaultValue)
|
||
|
.removeData(metadata.placeholderText)
|
||
|
;
|
||
|
},
|
||
|
|
||
|
toggle: function() {
|
||
|
module.verbose('Toggling menu visibility');
|
||
|
if( !module.is.active() ) {
|
||
|
module.show();
|
||
|
}
|
||
|
else {
|
||
|
module.hide();
|
||
|
}
|
||
|
},
|
||
|
|
||
5 years ago
|
show: function(callback, preventFocus) {
|
||
5 years ago
|
callback = $.isFunction(callback)
|
||
|
? callback
|
||
|
: function(){}
|
||
|
;
|
||
|
if(!module.can.show() && module.is.remote()) {
|
||
|
module.debug('No API results retrieved, searching before show');
|
||
|
module.queryRemote(module.get.query(), module.show);
|
||
|
}
|
||
|
if( module.can.show() && !module.is.active() ) {
|
||
|
module.debug('Showing dropdown');
|
||
|
if(module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered()) ) {
|
||
|
module.remove.message();
|
||
|
}
|
||
|
if(module.is.allFiltered()) {
|
||
|
return true;
|
||
|
}
|
||
|
if(settings.onShow.call(element) !== false) {
|
||
|
module.aria.setExpanded(true);
|
||
|
module.aria.refreshDescendant();
|
||
|
module.animate.show(function() {
|
||
|
if( module.can.click() ) {
|
||
|
module.bind.intent();
|
||
|
}
|
||
5 years ago
|
if(module.has.search() && !preventFocus) {
|
||
5 years ago
|
module.focusSearch();
|
||
|
}
|
||
|
module.set.visible();
|
||
|
callback.call(element);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
5 years ago
|
hide: function(callback, preventBlur) {
|
||
5 years ago
|
callback = $.isFunction(callback)
|
||
|
? callback
|
||
|
: function(){}
|
||
|
;
|
||
|
if( module.is.active() && !module.is.animatingOutward() ) {
|
||
|
module.debug('Hiding dropdown');
|
||
|
if(settings.onHide.call(element) !== false) {
|
||
|
module.aria.setExpanded(false);
|
||
|
module.aria.removeDescendant();
|
||
|
module.animate.hide(function() {
|
||
|
module.remove.visible();
|
||
5 years ago
|
// hidding search focus
|
||
|
if ( module.is.focusedOnSearch() && preventBlur !== true ) {
|
||
|
$search.blur();
|
||
|
}
|
||
5 years ago
|
callback.call(element);
|
||
|
});
|
||
|
}
|
||
5 years ago
|
} else if( module.can.click() ) {
|
||
|
module.unbind.intent();
|
||
5 years ago
|
}
|
||
|
},
|
||
|
|
||
|
hideOthers: function() {
|
||
|
module.verbose('Finding other dropdowns to hide');
|
||
|
$allModules
|
||
|
.not($module)
|
||
|
.has(selector.menu + '.' + className.visible)
|
||
|
.dropdown('hide')
|
||
|
;
|
||
|
},
|
||
|
|
||
|
hideMenu: function() {
|
||
|
module.verbose('Hiding menu instantaneously');
|
||
|
module.remove.active();
|
||
|
module.remove.visible();
|
||
|
$menu.transition('hide');
|
||
|
},
|
||
|
|
||
|
hideSubMenus: function() {
|
||
|
var
|
||
|
$subMenus = $menu.children(selector.item).find(selector.menu)
|
||
|
;
|
||
|
module.verbose('Hiding sub menus', $subMenus);
|
||
|
$subMenus.transition('hide');
|
||
|
},
|
||
|
|
||
|
bind: {
|
||
|
events: function() {
|
||
|
module.bind.keyboardEvents();
|
||
|
module.bind.inputEvents();
|
||
|
module.bind.mouseEvents();
|
||
|
},
|
||
|
keyboardEvents: function() {
|
||
|
module.verbose('Binding keyboard events');
|
||
|
$module
|
||
|
.on('keydown' + eventNamespace, module.event.keydown)
|
||
|
;
|
||
|
if( module.has.search() ) {
|
||
|
$module
|
||
|
.on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input)
|
||
|
;
|
||
|
}
|
||
|
if( module.is.multiple() ) {
|
||
|
$document
|
||
|
.on('keydown' + elementNamespace, module.event.document.keydown)
|
||
|
;
|
||
|
}
|
||
|
},
|
||
|
inputEvents: function() {
|
||
|
module.verbose('Binding input change events');
|
||
|
$module
|
||
|
.on('change' + eventNamespace, selector.input, module.event.change)
|
||
|
;
|
||
|
},
|
||
|
mouseEvents: function() {
|
||
|
module.verbose('Binding mouse events');
|
||
|
if(module.is.multiple()) {
|
||
|
$module
|
||
5 years ago
|
.on(clickEvent + eventNamespace, selector.label, module.event.label.click)
|
||
|
.on(clickEvent + eventNamespace, selector.remove, module.event.remove.click)
|
||
5 years ago
|
;
|
||
|
}
|
||
|
if( module.is.searchSelection() ) {
|
||
|
$module
|
||
|
.on('mousedown' + eventNamespace, module.event.mousedown)
|
||
|
.on('mouseup' + eventNamespace, module.event.mouseup)
|
||
|
.on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown)
|
||
|
.on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup)
|
||
5 years ago
|
.on(clickEvent + eventNamespace, selector.icon, module.event.icon.click)
|
||
|
.on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click)
|
||
5 years ago
|
.on('focus' + eventNamespace, selector.search, module.event.search.focus)
|
||
5 years ago
|
.on(clickEvent + eventNamespace, selector.search, module.event.search.focus)
|
||
5 years ago
|
.on('blur' + eventNamespace, selector.search, module.event.search.blur)
|
||
5 years ago
|
.on(clickEvent + eventNamespace, selector.text, module.event.text.focus)
|
||
5 years ago
|
;
|
||
|
if(module.is.multiple()) {
|
||
|
$module
|
||
5 years ago
|
.on(clickEvent + eventNamespace, module.event.click)
|
||
5 years ago
|
;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if(settings.on == 'click') {
|
||
|
$module
|
||
5 years ago
|
.on(clickEvent + eventNamespace, selector.icon, module.event.icon.click)
|
||
|
.on(clickEvent + eventNamespace, module.event.test.toggle)
|
||
5 years ago
|
;
|
||
|
}
|
||
|
else if(settings.on == 'hover') {
|
||
|
$module
|
||
|
.on('mouseenter' + eventNamespace, module.delay.show)
|
||
|
.on('mouseleave' + eventNamespace, module.delay.hide)
|
||
|
;
|
||
|
}
|
||
|
else {
|
||
|
$module
|
||
|
.on(settings.on + eventNamespace, module.toggle)
|
||
|
;
|
||
|
}
|
||
|
$module
|
||
|
.on('mousedown' + eventNamespace, module.event.mousedown)
|
||
|
.on('mouseup' + eventNamespace, module.event.mouseup)
|
||
|
.on('focus' + eventNamespace, module.event.focus)
|
||
5 years ago
|
.on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click)
|
||
5 years ago
|
;
|
||
|
if(module.has.menuSearch() ) {
|
||
|
$module
|
||
|
.on('blur' + eventNamespace, selector.search, module.event.search.blur)
|
||
|
;
|
||
|
}
|
||
|
else {
|
||
|
$module
|
||
|
.on('blur' + eventNamespace, module.event.blur)
|
||
|
;
|
||
|
}
|
||
|
}
|
||
|
$menu
|
||
5 years ago
|
.on((hasTouch ? 'touchstart' : 'mouseenter') + eventNamespace, selector.item, module.event.item.mouseenter)
|
||
5 years ago
|
.on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave)
|
||
|
.on('click' + eventNamespace, selector.item, module.event.item.click)
|
||
|
;
|
||
|
},
|
||
|
intent: function() {
|
||
|
module.verbose('Binding hide intent event to document');
|
||
|
if(hasTouch) {
|
||
|
$document
|
||
|
.on('touchstart' + elementNamespace, module.event.test.touch)
|
||
|
.on('touchmove' + elementNamespace, module.event.test.touch)
|
||
|
;
|
||
|
}
|
||
|
$document
|
||
5 years ago
|
.on(clickEvent + elementNamespace, module.event.test.hide)
|
||
5 years ago
|
;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
unbind: {
|
||
|
intent: function() {
|
||
|
module.verbose('Removing hide intent event from document');
|
||
|
if(hasTouch) {
|
||
|
$document
|
||
|
.off('touchstart' + elementNamespace)
|
||
|
.off('touchmove' + elementNamespace)
|
||
|
;
|
||
|
}
|
||
|
$document
|
||
5 years ago
|
.off(clickEvent + elementNamespace)
|
||
5 years ago
|
;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
filter: function(query) {
|
||
|
var
|
||
|
searchTerm = (query !== undefined)
|
||
|
? query
|
||
|
: module.get.query(),
|
||
|
afterFiltered = function() {
|
||
|
if(module.is.multiple()) {
|
||
|
module.filterActive();
|
||
|
}
|
||
|
if(query || (!query && module.get.activeItem().length == 0)) {
|
||
|
module.select.firstUnfiltered();
|
||
|
}
|
||
|
if( module.has.allResultsFiltered() ) {
|
||
|
if( settings.onNoResults.call(element, searchTerm) ) {
|
||
|
if(settings.allowAdditions) {
|
||
|
if(settings.hideAdditions) {
|
||
|
module.verbose('User addition with no menu, setting empty style');
|
||
|
module.set.empty();
|
||
|
module.hideMenu();
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
module.verbose('All items filtered, showing message', searchTerm);
|
||
|
module.add.message(message.noResults);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
module.verbose('All items filtered, hiding dropdown', searchTerm);
|
||
|
module.hideMenu();
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
module.remove.empty();
|
||
|
module.remove.message();
|
||
|
}
|
||
|
if(settings.allowAdditions) {
|
||
5 years ago
|
module.add.userSuggestion(module.escape.htmlEntities(query));
|
||
5 years ago
|
}
|
||
|
if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) {
|
||
|
module.show();
|
||
|
}
|
||
|
}
|
||
|
;
|
||
|
if(settings.useLabels && module.has.maxSelections()) {
|
||
|
return;
|
||
|
}
|
||
|
if(settings.apiSettings) {
|
||
|
if( module.can.useAPI() ) {
|
||
|
module.queryRemote(searchTerm, function() {
|
||
|
if(settings.filterRemoteData) {
|
||
|
module.filterItems(searchTerm);
|
||
|
}
|
||
5 years ago
|
var preSelected = $input.val();
|
||
|
if(!Array.isArray(preSelected)) {
|
||
|
preSelected = preSelected && preSelected!=="" ? preSelected.split(settings.delimiter) : [];
|
||
|
}
|
||
|
$.each(preSelected,function(index,value){
|
||
|
$item.filter('[data-value="'+value+'"]')
|
||
|
.addClass(className.filtered)
|
||
|
;
|
||
|
});
|
||
5 years ago
|
afterFiltered();
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
module.error(error.noAPI);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
module.filterItems(searchTerm);
|
||
|
afterFiltered();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
queryRemote: function(query, callback) {
|
||
|
var
|
||
|
apiSettings = {
|
||
|
errorDuration : false,
|
||
|
cache : 'local',
|
||
|
throttle : settings.throttle,
|
||
|
urlData : {
|
||
|
query: query
|
||
|
},
|
||
|
onError: function() {
|
||
|
module.add.message(message.serverError);
|
||
|
callback();
|
||
|
},
|
||
|
onFailure: function() {
|
||
|
module.add.message(message.serverError);
|
||
|
callback();
|
||
|
},
|
||
|
onSuccess : function(response) {
|
||
5 years ago
|
var
|
||
|
values = response[fields.remoteValues]
|
||
|
;
|
||
|
if (!Array.isArray(values)){
|
||
|
values = [];
|
||
|
}
|
||
5 years ago
|
module.remove.message();
|
||
|
module.setup.menu({
|
||
5 years ago
|
values: values
|
||
5 years ago
|
});
|
||
5 years ago
|
|
||
|
if(values.length===0 && !settings.allowAdditions) {
|
||
|
module.add.message(message.noResults);
|
||
|
}
|
||
5 years ago
|
callback();
|
||
|
}
|
||
|
}
|
||
|
;
|
||
|
if( !$module.api('get request') ) {
|
||
|
module.setup.api();
|
||
|
}
|
||
|
apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings);
|
||
|
$module
|
||
|
.api('setting', apiSettings)
|
||
|
.api('query')
|
||
|
;
|
||
|
},
|
||
|
|
||
|
filterItems: function(query) {
|
||
|
var
|
||
5 years ago
|
searchTerm = module.remove.diacritics(query !== undefined
|
||
5 years ago
|
? query
|
||
5 years ago
|
: module.get.query()
|
||
|
),
|
||
5 years ago
|
results = null,
|
||
|
escapedTerm = module.escape.string(searchTerm),
|
||
5 years ago
|
regExpFlags = (settings.ignoreSearchCase ? 'i' : '') + 'gm',
|
||
|
beginsWithRegExp = new RegExp('^' + escapedTerm, regExpFlags)
|
||
5 years ago
|
;
|
||
|
// avoid loop if we're matching nothing
|
||
|
if( module.has.query() ) {
|
||
|
results = [];
|
||
|
|
||
|
module.verbose('Searching for matching values', searchTerm);
|
||
|
$item
|
||
|
.each(function(){
|
||
|
var
|
||
|
$choice = $(this),
|
||
|
text,
|
||
|
value
|
||
|
;
|
||
5 years ago
|
if($choice.hasClass(className.unfilterable)) {
|
||
|
results.push(this);
|
||
|
return true;
|
||
|
}
|
||
5 years ago
|
if(settings.match === 'both' || settings.match === 'text') {
|
||
|
text = module.remove.diacritics(String(module.get.choiceText($choice, false)));
|
||
5 years ago
|
if(text.search(beginsWithRegExp) !== -1) {
|
||
|
results.push(this);
|
||
|
return true;
|
||
|
}
|
||
|
else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) {
|
||
|
results.push(this);
|
||
|
return true;
|
||
|
}
|
||
|
else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) {
|
||
|
results.push(this);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
5 years ago
|
if(settings.match === 'both' || settings.match === 'value') {
|
||
|
value = module.remove.diacritics(String(module.get.choiceValue($choice, text)));
|
||
5 years ago
|
if(value.search(beginsWithRegExp) !== -1) {
|
||
|
results.push(this);
|
||
|
return true;
|
||
|
}
|
||
|
else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, value)) {
|
||
|
results.push(this);
|
||
|
return true;
|
||
|
}
|
||
|
else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, value)) {
|
||
|
results.push(this);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
;
|
||
|
}
|
||
|
module.debug('Showing only matched items', searchTerm);
|
||
|
module.remove.filteredItem();
|
||
|
if(results) {
|
||
|
$item
|
||
|
.not(results)
|
||
|
.addClass(className.filtered)
|
||
|
;
|
||
|
}
|
||
5 years ago
|
|
||
|
if(!module.has.query()) {
|
||
|
$divider
|
||
|
.removeClass(className.hidden);
|
||
|
} else if(settings.hideDividers === true) {
|
||
|
$divider
|
||
|
.addClass(className.hidden);
|
||
|
} else if(settings.hideDividers === 'empty') {
|
||
|
$divider
|
||
|
.removeClass(className.hidden)
|
||
|
.filter(function() {
|
||
|
// First find the last divider in this divider group
|
||
|
// Dividers which are direct siblings are considered a group
|
||
|
var lastDivider = $(this).nextUntil(selector.item);
|
||
|
|
||
|
return (lastDivider.length ? lastDivider : $(this))
|
||
|
// Count all non-filtered items until the next divider (or end of the dropdown)
|
||
|
.nextUntil(selector.divider)
|
||
|
.filter(selector.item + ":not(." + className.filtered + ")")
|
||
|
// Hide divider if no items are found
|
||
|
.length === 0;
|
||
|
})
|
||
|
.addClass(className.hidden);
|
||
|
}
|
||
5 years ago
|
},
|
||
|
|
||
|
fuzzySearch: function(query, term) {
|
||
|
var
|
||
|
termLength = term.length,
|
||
|
queryLength = query.length
|
||
|
;
|
||
5 years ago
|
query = (settings.ignoreSearchCase ? query.toLowerCase() : query);
|
||
|
term = (settings.ignoreSearchCase ? term.toLowerCase() : term);
|
||
5 years ago
|
if(queryLength > termLength) {
|
||
|
return false;
|
||
|
}
|
||
|
if(queryLength === termLength) {
|
||
|
return (query === term);
|
||
|
}
|
||
|
search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) {
|
||
|
var
|
||
|
queryCharacter = query.charCodeAt(characterIndex)
|
||
|
;
|
||
|
while(nextCharacterIndex < termLength) {
|
||
|
if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) {
|
||
|
continue search;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
},
|
||
|
exactSearch: function (query, term) {
|
||
5 years ago
|
query = (settings.ignoreSearchCase ? query.toLowerCase() : query);
|
||
|
term = (settings.ignoreSearchCase ? term.toLowerCase() : term);
|
||
|
return term.indexOf(query) > -1;
|
||
|
|
||
5 years ago
|
},
|
||
|
filterActive: function() {
|
||
|
if(settings.useLabels) {
|
||
|
$item.filter('.' + className.active)
|
||
|
.addClass(className.filtered)
|
||
|
;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
focusSearch: function(skipHandler) {
|
||
|
if( module.has.search() && !module.is.focusedOnSearch() ) {
|
||
|
if(skipHandler) {
|
||
|
$module.off('focus' + eventNamespace, selector.search);
|
||
|
$search.focus();
|
||
|
$module.on('focus' + eventNamespace, selector.search, module.event.search.focus);
|
||
|
}
|
||
|
else {
|
||
|
$search.focus();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
5 years ago
|
blurSearch: function() {
|
||
|
if( module.has.search() ) {
|
||
|
$search.blur();
|
||
|
}
|
||
|
},
|
||
|
|
||
5 years ago
|
forceSelection: function() {
|
||
|
var
|
||
|
$currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0),
|
||
|
$activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0),
|
||
|
$selectedItem = ($currentlySelected.length > 0)
|
||
|
? $currentlySelected
|
||
|
: $activeItem,
|
||
|
hasSelected = ($selectedItem.length > 0)
|
||
|
;
|
||
5 years ago
|
if(settings.allowAdditions || (hasSelected && !module.is.multiple())) {
|
||
5 years ago
|
module.debug('Forcing partial selection to selected item', $selectedItem);
|
||
5 years ago
|
$selectedItem[0].click();
|
||
5 years ago
|
return;
|
||
5 years ago
|
}
|
||
|
else {
|
||
5 years ago
|
module.remove.searchTerm();
|
||
5 years ago
|
}
|
||
|
},
|
||
|
|
||
|
change: {
|
||
|
values: function(values) {
|
||
|
if(!settings.allowAdditions) {
|
||
|
module.clear();
|
||
|
}
|
||
|
module.debug('Creating dropdown with specified values', values);
|
||
|
module.setup.menu({values: values});
|
||
|
$.each(values, function(index, item) {
|
||
|
if(item.selected == true) {
|
||
5 years ago
|
module.debug('Setting initial selection to', item[fields.value]);
|
||
|
module.set.selected(item[fields.value]);
|
||
|
if(!module.is.multiple()) {
|
||
|
return false;
|
||
|
}
|
||
5 years ago
|
}
|
||
|
});
|
||
5 years ago
|
|
||
|
if(module.has.selectInput()) {
|
||
|
module.disconnect.selectObserver();
|
||
|
$input.html('');
|
||
|
$input.append('<option disabled selected value></option>');
|
||
|
$.each(values, function(index, item) {
|
||
|
var
|
||
|
value = settings.templates.deQuote(item[fields.value]),
|
||
|
name = settings.templates.escape(
|
||
5 years ago
|
item[fields.name] || '',
|
||
5 years ago
|
settings.preserveHTML
|
||
|
)
|
||
|
;
|
||
|
$input.append('<option value="' + value + '">' + name + '</option>');
|
||
|
});
|
||
|
module.observe.select();
|
||
|
}
|
||
5 years ago
|
}
|
||
|
},
|
||
|
|
||
|
event: {
|
||
|
change: function() {
|
||
|
if(!internalChange) {
|
||
|
module.debug('Input changed, updating selection');
|
||
|
module.set.selected();
|
||
|
}
|
||
|
},
|
||
|
focus: function() {
|
||
|
if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) {
|
||
|
module.show();
|
||
|
}
|
||
|
},
|
||
|
blur: function(event) {
|
||
|
pageLostFocus = (document.activeElement === this);
|
||
|
if(!activated && !pageLostFocus) {
|
||
|
module.remove.activeLabel();
|
||
|
module.hide();
|
||
|
}
|
||
|
},
|
||
|
mousedown: function() {
|
||
|
if(module.is.searchSelection()) {
|
||
|
// prevent menu hiding on immediate re-focus
|
||
|
willRefocus = true;
|
||
|
}
|
||
|
else {
|
||
|
// prevents focus callback from occurring on mousedown
|
||
|
activated = true;
|
||
|
}
|
||
|
},
|
||
|
mouseup: function() {
|
||
|
if(module.is.searchSelection()) {
|
||
|
// prevent menu hiding on immediate re-focus
|
||
|
willRefocus = false;
|
||
|
}
|
||
|
else {
|
||
|
activated = false;
|
||
|
}
|
||
|
},
|
||
|
click: function(event) {
|
||
|
var
|
||
|
$target = $(event.target)
|
||
|
;
|
||
|
// focus search
|
||
|
if($target.is($module)) {
|
||
|
if(!module.is.focusedOnSearch()) {
|
||
|
module.focusSearch();
|
||
|
}
|
||
|
else {
|
||
|
module.show();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
search: {
|
||
5 years ago
|
focus: function(event) {
|
||
5 years ago
|
activated = true;
|
||
|
if(module.is.multiple()) {
|
||
|
module.remove.activeLabel();
|
||
|
}
|
||
5 years ago
|
if(settings.showOnFocus || (event.type !== 'focus' && event.type !== 'focusin')) {
|
||
5 years ago
|
module.search();
|
||
|
}
|
||
|
},
|
||
|
blur: function(event) {
|
||
|
pageLostFocus = (document.activeElement === this);
|
||
|
if(module.is.searchSelection() && !willRefocus) {
|
||
|
if(!itemActivated && !pageLostFocus) {
|
||
|
if(settings.forceSelection) {
|
||
|
module.forceSelection();
|
||
5 years ago
|
} else if(!settings.allowAdditions){
|
||
|
module.remove.searchTerm();
|
||
5 years ago
|
}
|
||
|
module.hide();
|
||
|
}
|
||
|
}
|
||
|
willRefocus = false;
|
||
|
}
|
||
|
},
|
||
5 years ago
|
clearIcon: {
|
||
|
click: function(event) {
|
||
|
module.clear();
|
||
|
if(module.is.searchSelection()) {
|
||
|
module.remove.searchTerm();
|
||
|
}
|
||
|
module.hide();
|
||
|
event.stopPropagation();
|
||
|
}
|
||
|
},
|
||
5 years ago
|
icon: {
|
||
|
click: function(event) {
|
||
5 years ago
|
iconClicked=true;
|
||
|
if(module.has.search()) {
|
||
|
if(!module.is.active()) {
|
||
|
if(settings.showOnFocus){
|
||
|
module.focusSearch();
|
||
|
} else {
|
||
|
module.toggle();
|
||
|
}
|
||
|
} else {
|
||
|
module.blurSearch();
|
||
|
}
|
||
|
} else {
|
||
|
module.toggle();
|
||
|
}
|
||
5 years ago
|
}
|
||
|
},
|
||
|
text: {
|
||
|
focus: function(event) {
|
||
|
activated = true;
|
||
|
module.focusSearch();
|
||
|
}
|
||
|
},
|
||
|
input: function(event) {
|
||
|
if(module.is.multiple() || module.is.searchSelection()) {
|
||
|
module.set.filtered();
|
||
|
}
|
||
|
clearTimeout(module.timer);
|
||
|
module.timer = setTimeout(module.search, settings.delay.search);
|
||
|
},
|
||
|
label: {
|
||
|
click: function(event) {
|
||
|
var
|
||
|
$label = $(this),
|
||
|
$labels = $module.find(selector.label),
|
||
|
$activeLabels = $labels.filter('.' + className.active),
|
||
|
$nextActive = $label.nextAll('.' + className.active),
|
||
|
$prevActive = $label.prevAll('.' + className.active),
|
||
|
$range = ($nextActive.length > 0)
|
||
|
? $label.nextUntil($nextActive).add($activeLabels).add($label)
|
||
|
: $label.prevUntil($prevActive).add($activeLabels).add($label)
|
||
|
;
|
||
|
if(event.shiftKey) {
|
||
|
$activeLabels.removeClass(className.active);
|
||
|
$range.addClass(className.active);
|
||
|
}
|
||
|
else if(event.ctrlKey) {
|
||
|
$label.toggleClass(className.active);
|
||
|
}
|
||
|
else {
|
||
|
$activeLabels.removeClass(className.active);
|
||
|
$label.addClass(className.active);
|
||
|
}
|
||
|
settings.onLabelSelect.apply(this, $labels.filter('.' + className.active));
|
||
|
}
|
||
|
},
|
||
|
remove: {
|
||
|
click: function() {
|
||
|
var
|
||
|
$label = $(this).parent()
|
||
|
;
|
||
|
if( $label.hasClass(className.active) ) {
|
||
|
// remove all selected labels
|
||
|
module.remove.activeLabels();
|
||
|
}
|
||
|
else {
|
||
|
// remove this label only
|
||
|
module.remove.activeLabels( $label );
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
test: {
|
||
|
toggle: function(event) {
|
||
|
var
|
||
|
toggleBehavior = (module.is.multiple())
|
||
|
? module.show
|
||
|
: module.toggle
|
||
|
;
|
||
|
if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) {
|
||
|
return;
|
||
|
}
|
||
|
if( module.determine.eventOnElement(event, toggleBehavior) ) {
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
},
|
||
|
touch: function(event) {
|
||
|
module.determine.eventOnElement(event, function() {
|
||
|
if(event.type == 'touchstart') {
|
||
|
module.timer = setTimeout(function() {
|
||
|
module.hide();
|
||
|
}, settings.delay.touch);
|
||
|
}
|
||
|
else if(event.type == 'touchmove') {
|
||
|
clearTimeout(module.timer);
|
||
|
}
|
||
|
});
|
||
|
event.stopPropagation();
|
||
|
},
|
||
|
hide: function(event) {
|
||
5 years ago
|
if(module.determine.eventInModule(event, module.hide)){
|
||
|
if(element.id && $(event.target).attr('for') === element.id){
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
}
|
||
5 years ago
|
}
|
||
|
},
|
||
|
select: {
|
||
|
mutation: function(mutations) {
|
||
|
module.debug('<select> modified, recreating menu');
|
||
5 years ago
|
if(module.is.selectMutation(mutations)) {
|
||
5 years ago
|
module.disconnect.selectObserver();
|
||
|
module.refresh();
|
||
|
module.setup.select();
|
||
|
module.set.selected();
|
||
|
module.observe.select();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
menu: {
|
||
|
mutation: function(mutations) {
|
||
|
var
|
||
|
mutation = mutations[0],
|
||
|
$addedNode = mutation.addedNodes
|
||
|
? $(mutation.addedNodes[0])
|
||
|
: $(false),
|
||
|
$removedNode = mutation.removedNodes
|
||
|
? $(mutation.removedNodes[0])
|
||
|
: $(false),
|
||
|
$changedNodes = $addedNode.add($removedNode),
|
||
|
isUserAddition = $changedNodes.is(selector.addition) || $changedNodes.closest(selector.addition).length > 0,
|
||
|
isMessage = $changedNodes.is(selector.message) || $changedNodes.closest(selector.message).length > 0
|
||
|
;
|
||
|
if(isUserAddition || isMessage) {
|
||
|
module.debug('Updating item selector cache');
|
||
|
module.refreshItems();
|
||
|
}
|
||
|
else {
|
||
|
module.debug('Menu modified, updating selector cache');
|
||
|
module.refresh();
|
||
|
}
|
||
|
},
|
||
|
mousedown: function() {
|
||
|
itemActivated = true;
|
||
|
},
|
||
|
mouseup: function() {
|
||
|
itemActivated = false;
|
||
|
}
|
||
|
},
|
||
|
item: {
|
||
|
mouseenter: function(event) {
|
||
|
var
|
||
|
$target = $(event.target),
|
||
|
$item = $(this),
|
||
|
$subMenu = $item.children(selector.menu),
|
||
|
$otherMenus = $item.siblings(selector.item).children(selector.menu),
|
||
|
hasSubMenu = ($subMenu.length > 0),
|
||
|
isBubbledEvent = ($subMenu.find($target).length > 0)
|
||
|
;
|
||
|
if( !isBubbledEvent && hasSubMenu ) {
|
||
|
clearTimeout(module.itemTimer);
|
||
|
module.itemTimer = setTimeout(function() {
|
||
|
module.verbose('Showing sub-menu', $subMenu);
|
||
|
$.each($otherMenus, function() {
|
||
|
module.animate.hide(false, $(this));
|
||
|
});
|
||
|
module.animate.show(false, $subMenu);
|
||
|
}, settings.delay.show);
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
},
|
||
|
mouseleave: function(event) {
|
||
|
var
|
||
|
$subMenu = $(this).children(selector.menu)
|
||
|
;
|
||
|
if($subMenu.length > 0) {
|
||
|
clearTimeout(module.itemTimer);
|
||
|
module.itemTimer = setTimeout(function() {
|
||
|
module.verbose('Hiding sub-menu', $subMenu);
|
||
|
module.animate.hide(false, $subMenu);
|
||
|
}, settings.delay.hide);
|
||
|
}
|
||
|
},
|
||
|
click: function (event, skipRefocus) {
|
||
|
var
|
||
|
$choice = $(this),
|
||
|
$target = (event)
|
||
|
? $(event.target)
|
||
|
: $(''),
|
||
|
$subMenu = $choice.find(selector.menu),
|
||
|
text = module.get.choiceText($choice),
|
||
|
value = module.get.choiceValue($choice, text),
|
||
|
hasSubMenu = ($subMenu.length > 0),
|
||
|
isBubbledEvent = ($subMenu.find($target).length > 0)
|
||
|
;
|
||
|
// prevents IE11 bug where menu receives focus even though `tabindex=-1`
|
||
5 years ago
|
if (document.activeElement.tagName.toLowerCase() !== 'input') {
|
||
5 years ago
|
$(document.activeElement).blur();
|
||
|
}
|
||
|
if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) {
|
||
|
if(module.is.searchSelection()) {
|
||
|
if(settings.allowAdditions) {
|
||
|
module.remove.userAddition();
|
||
|
}
|
||
|
module.remove.searchTerm();
|
||
|
if(!module.is.focusedOnSearch() && !(skipRefocus == true)) {
|
||
|
module.focusSearch(true);
|
||
|
}
|
||
|
}
|
||
|
if(!settings.useLabels) {
|
||
|
module.remove.filteredItem();
|
||
|
module.set.scrollPosition($choice);
|
||
|
}
|
||
|
module.determine.selectAction.call(this, text, value);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
document: {
|
||
|
// label selection should occur even when element has no focus
|
||
|
keydown: function(event) {
|
||
|
var
|
||
|
pressedKey = event.which,
|
||
|
isShortcutKey = module.is.inObject(pressedKey, keys)
|
||
|
;
|
||
|
if(isShortcutKey) {
|
||
|
var
|
||
|
$label = $module.find(selector.label),
|
||
|
$activeLabel = $label.filter('.' + className.active),
|
||
|
activeValue = $activeLabel.data(metadata.value),
|
||
|
labelIndex = $label.index($activeLabel),
|
||
|
labelCount = $label.length,
|
||
|
hasActiveLabel = ($activeLabel.length > 0),
|
||
|
hasMultipleActive = ($activeLabel.length > 1),
|
||
|
isFirstLabel = (labelIndex === 0),
|
||
|
isLastLabel = (labelIndex + 1 == labelCount),
|
||
|
isSearch = module.is.searchSelection(),
|
||
|
isFocusedOnSearch = module.is.focusedOnSearch(),
|
||
|
isFocused = module.is.focused(),
|
||
5 years ago
|
caretAtStart = (isFocusedOnSearch && module.get.caretPosition(false) === 0),
|
||
|
isSelectedSearch = (caretAtStart && module.get.caretPosition(true) !== 0),
|
||
5 years ago
|
$nextLabel
|
||
|
;
|
||
|
if(isSearch && !hasActiveLabel && !isFocusedOnSearch) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(pressedKey == keys.leftArrow) {
|
||
|
// activate previous label
|
||
|
if((isFocused || caretAtStart) && !hasActiveLabel) {
|
||
|
module.verbose('Selecting previous label');
|
||
|
$label.last().addClass(className.active);
|
||
|
}
|
||
|
else if(hasActiveLabel) {
|
||
|
if(!event.shiftKey) {
|
||
|
module.verbose('Selecting previous label');
|
||
|
$label.removeClass(className.active);
|
||
|
}
|
||
|
else {
|
||
|
module.verbose('Adding previous label to selection');
|
||
|
}
|
||
|
if(isFirstLabel && !hasMultipleActive) {
|
||
|
$activeLabel.addClass(className.active);
|
||
|
}
|
||
|
else {
|
||
|
$activeLabel.prev(selector.siblingLabel)
|
||
|
.addClass(className.active)
|
||
|
.end()
|
||
|
;
|
||
|
}
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
}
|
||
|
else if(pressedKey == keys.rightArrow) {
|
||
|
// activate first label
|
||
|
if(isFocused && !hasActiveLabel) {
|
||
|
$label.first().addClass(className.active);
|
||
|
}
|
||
|
// activate next label
|
||
|
if(hasActiveLabel) {
|
||
|
if(!event.shiftKey) {
|
||
|
module.verbose('Selecting next label');
|
||
|
$label.removeClass(className.active);
|
||
|
}
|
||
|
else {
|
||
|
module.verbose('Adding next label to selection');
|
||
|
}
|
||
|
if(isLastLabel) {
|
||
|
if(isSearch) {
|
||
|
if(!isFocusedOnSearch) {
|
||
|
module.focusSearch();
|
||
|
}
|
||
|
else {
|
||
|
$label.removeClass(className.active);
|
||
|
}
|
||
|
}
|
||
|
else if(hasMultipleActive) {
|
||
|
$activeLabel.next(selector.siblingLabel).addClass(className.active);
|
||
|
}
|
||
|
else {
|
||
|
$activeLabel.addClass(className.active);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
$activeLabel.next(selector.siblingLabel).addClass(className.active);
|
||
|
}
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
}
|
||
|
else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) {
|
||
|
if(hasActiveLabel) {
|
||
|
module.verbose('Removing active labels');
|
||
|
if(isLastLabel) {
|
||
|
if(isSearch && !isFocusedOnSearch) {
|
||
|
module.focusSearch();
|
||
|
}
|
||
|
}
|
||
|
$activeLabel.last().next(selector.siblingLabel).addClass(className.active);
|
||
|
module.remove.activeLabels($activeLabel);
|
||
|
event.preventDefault();
|
||
|
}
|
||
5 years ago
|
else if(caretAtStart && !isSelectedSearch && !hasActiveLabel && pressedKey == keys.backspace) {
|
||
5 years ago
|
module.verbose('Removing last label on input backspace');
|
||
|
$activeLabel = $label.last().addClass(className.active);
|
||
|
module.remove.activeLabels($activeLabel);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
$activeLabel.removeClass(className.active);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
keydown: function(event) {
|
||
|
var
|
||
|
pressedKey = event.which,
|
||
|
isShortcutKey = module.is.inObject(pressedKey, keys)
|
||
|
;
|
||
|
if(isShortcutKey) {
|
||
|
var
|
||
|
$currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0),
|
||
|
$activeItem = $menu.children('.' + className.active).eq(0),
|
||
|
$selectedItem = ($currentlySelected.length > 0)
|
||
|
? $currentlySelected
|
||
|
: $activeItem,
|
||
|
$visibleItems = ($selectedItem.length > 0)
|
||
|
? $selectedItem.siblings(':not(.' + className.filtered +')').addBack()
|
||
|
: $menu.children(':not(.' + className.filtered +')'),
|
||
|
$subMenu = $selectedItem.children(selector.menu),
|
||
|
$parentMenu = $selectedItem.closest(selector.menu),
|
||
|
inVisibleMenu = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating) || $parentMenu.parent(selector.menu).length > 0),
|
||
|
hasSubMenu = ($subMenu.length> 0),
|
||
|
hasSelectedItem = ($selectedItem.length > 0),
|
||
|
selectedIsSelectable = ($selectedItem.not(selector.unselectable).length > 0),
|
||
|
delimiterPressed = (pressedKey == keys.delimiter && settings.allowAdditions && module.is.multiple()),
|
||
|
isAdditionWithoutMenu = (settings.allowAdditions && settings.hideAdditions && (pressedKey == keys.enter || delimiterPressed) && selectedIsSelectable),
|
||
|
$nextItem,
|
||
|
isSubMenuItem,
|
||
|
newIndex
|
||
|
;
|
||
|
// allow selection with menu closed
|
||
|
if(isAdditionWithoutMenu) {
|
||
|
module.verbose('Selecting item from keyboard shortcut', $selectedItem);
|
||
|
$selectedItem[0].click();
|
||
|
if(module.is.searchSelection()) {
|
||
|
module.remove.searchTerm();
|
||
|
}
|
||
5 years ago
|
if(module.is.multiple()){
|
||
|
event.preventDefault();
|
||
|
}
|
||
5 years ago
|
}
|
||
|
|
||
|
// visible menu keyboard shortcuts
|
||
|
if( module.is.visible() ) {
|
||
|
|
||
|
// enter (select or open sub-menu)
|
||
|
if(pressedKey == keys.enter || delimiterPressed) {
|
||
|
if(pressedKey == keys.enter && hasSelectedItem && hasSubMenu && !settings.allowCategorySelection) {
|
||
|
module.verbose('Pressed enter on unselectable category, opening sub menu');
|
||
|
pressedKey = keys.rightArrow;
|
||
|
}
|
||
|
else if(selectedIsSelectable) {
|
||
|
module.verbose('Selecting item from keyboard shortcut', $selectedItem);
|
||
|
$selectedItem[0].click();
|
||
|
if(module.is.searchSelection()) {
|
||
|
module.remove.searchTerm();
|
||
5 years ago
|
if(module.is.multiple()) {
|
||
|
$search.focus();
|
||
|
}
|
||
5 years ago
|
}
|
||
|
}
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
|
||
|
// sub-menu actions
|
||
|
if(hasSelectedItem) {
|
||
|
|
||
|
if(pressedKey == keys.leftArrow) {
|
||
|
|
||
|
isSubMenuItem = ($parentMenu[0] !== $menu[0]);
|
||
|
|
||
|
if(isSubMenuItem) {
|
||
|
module.verbose('Left key pressed, closing sub-menu');
|
||
|
module.animate.hide(false, $parentMenu);
|
||
|
$selectedItem
|
||
|
.removeClass(className.selected)
|
||
|
;
|
||
|
$parentMenu
|
||
|
.closest(selector.item)
|
||
|
.addClass(className.selected)
|
||
|
;
|
||
|
module.aria.refreshDescendant();
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// right arrow (show sub-menu)
|
||
|
if(pressedKey == keys.rightArrow) {
|
||
|
if(hasSubMenu) {
|
||
|
module.verbose('Right key pressed, opening sub-menu');
|
||
|
module.animate.show(false, $subMenu);
|
||
|
$selectedItem
|
||
|
.removeClass(className.selected)
|
||
|
;
|
||
|
$subMenu
|
||
|
.find(selector.item).eq(0)
|
||
|
.addClass(className.selected)
|
||
|
;
|
||
|
module.aria.refreshDescendant();
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// up arrow (traverse menu up)
|
||
|
if(pressedKey == keys.upArrow) {
|
||
|
$nextItem = (hasSelectedItem && inVisibleMenu)
|
||
|
? $selectedItem.prevAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
|
||
|
: $item.eq(0)
|
||
|
;
|
||
|
if($visibleItems.index( $nextItem ) < 0) {
|
||
|
module.verbose('Up key pressed but reached top of current menu');
|
||
|
event.preventDefault();
|
||
|
return;
|
||
|
}
|
||
|
else {
|
||
|
module.verbose('Up key pressed, changing active item');
|
||
|
$selectedItem
|
||
|
.removeClass(className.selected)
|
||
|
;
|
||
|
$nextItem
|
||
|
.addClass(className.selected)
|
||
|
;
|
||
|
module.aria.refreshDescendant();
|
||
|
module.set.scrollPosition($nextItem);
|
||
|
if(settings.selectOnKeydown && module.is.single()) {
|
||
|
module.set.selectedItem($nextItem);
|
||
|
}
|
||
|
}
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
|
||
|
// down arrow (traverse menu down)
|
||
|
if(pressedKey == keys.downArrow) {
|
||
|
$nextItem = (hasSelectedItem && inVisibleMenu)
|
||
|
? $nextItem = $selectedItem.nextAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
|
||
|
: $item.eq(0)
|
||
|
;
|
||
|
if($nextItem.length === 0) {
|
||
|
module.verbose('Down key pressed but reached bottom of current menu');
|
||
|
event.preventDefault();
|
||
|
return;
|
||
|
}
|
||
|
else {
|
||
|
module.verbose('Down key pressed, changing active item');
|
||
|
$item
|
||
|
.removeClass(className.selected)
|
||
|
;
|
||
|
$nextItem
|
||
|
.addClass(className.selected)
|
||
|
;
|
||
|
module.aria.refreshDescendant();
|
||
|
module.set.scrollPosition($nextItem);
|
||
|
if(settings.selectOnKeydown && module.is.single()) {
|
||
|
module.set.selectedItem($nextItem);
|
||
|
}
|
||
|
}
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
|
||
|
// page down (show next page)
|
||
|
if(pressedKey == keys.pageUp) {
|
||
|
module.scrollPage('up');
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
if(pressedKey == keys.pageDown) {
|
||
|
module.scrollPage('down');
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
|
||
|
// escape (close menu)
|
||
|
if(pressedKey == keys.escape) {
|
||
|
module.verbose('Escape key pressed, closing dropdown');
|
||
|
module.hide();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else {
|
||
|
// delimiter key
|
||
|
if(delimiterPressed) {
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
// down arrow (open menu)
|
||
|
if(pressedKey == keys.downArrow && !module.is.visible()) {
|
||
|
module.verbose('Down key pressed, showing dropdown');
|
||
|
module.show();
|
||
|
event.preventDefault();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if( !module.has.search() ) {
|
||
|
module.set.selectedLetter( String.fromCharCode(pressedKey) );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
trigger: {
|
||
|
change: function() {
|
||
|
var
|
||
|
events = document.createEvent('HTMLEvents'),
|
||
|
inputElement = $input[0]
|
||
|
;
|
||
|
if(inputElement) {
|
||
|
module.verbose('Triggering native change event');
|
||
|
events.initEvent('change', true, false);
|
||
|
inputElement.dispatchEvent(events);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
determine: {
|
||
|
selectAction: function(text, value) {
|
||
5 years ago
|
selectActionActive = true;
|
||
5 years ago
|
module.verbose('Determining action', settings.action);
|
||
|
if( $.isFunction( module.action[settings.action] ) ) {
|
||
|
module.verbose('Triggering preset action', settings.action, text, value);
|
||
|
module.action[ settings.action ].call(element, text, value, this);
|
||
|
}
|
||
|
else if( $.isFunction(settings.action) ) {
|
||
|
module.verbose('Triggering user action', settings.action, text, value);
|
||
|
settings.action.call(element, text, value, this);
|
||
|
}
|
||
|
else {
|
||
|
module.error(error.action, settings.action);
|
||
|
}
|
||
5 years ago
|
selectActionActive = false;
|
||
5 years ago
|
},
|
||
|
eventInModule: function(event, callback) {
|
||
|
var
|
||
|
$target = $(event.target),
|
||
|
inDocument = ($target.closest(document.documentElement).length > 0),
|
||
|
inModule = ($target.closest($module).length > 0)
|
||
|
;
|
||
|
callback = $.isFunction(callback)
|
||
|
? callback
|
||
|
: function(){}
|
||
|
;
|
||
|
if(inDocument && !inModule) {
|
||
|
module.verbose('Triggering event', callback);
|
||
|
callback();
|
||
|
return true;
|
||
|
}
|
||
|
else {
|
||
|
module.verbose('Event occurred in dropdown, canceling callback');
|
||
|
return false;
|
||
|
}
|
||
|
},
|
||
|
eventOnElement: function(event, callback) {
|
||
|
var
|
||
|
$target = $(event.target),
|
||
|
$label = $target.closest(selector.siblingLabel),
|
||
|
inVisibleDOM = document.body.contains(event.target),
|
||
5 years ago
|
notOnLabel = ($module.find($label).length === 0 || !(module.is.multiple() && settings.useLabels)),
|
||
5 years ago
|
notInMenu = ($target.closest($menu).length === 0)
|
||
|
;
|
||
|
callback = $.isFunction(callback)
|
||
|
? callback
|
||
|
: function(){}
|
||
|
;
|
||
|
if(inVisibleDOM && notOnLabel && notInMenu) {
|
||
|
module.verbose('Triggering event', callback);
|
||
|
callback();
|
||
|
return true;
|
||
|
}
|
||
|
else {
|
||
|
module.verbose('Event occurred in dropdown menu, canceling callback');
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
action: {
|
||
|
|
||
|
nothing: function() {},
|
||
|
|
||
|
activate: function(text, value, element) {
|
||
|
value = (value !== undefined)
|
||
|
? value
|
||
|
: text
|
||
|
;
|
||
|
if( module.can.activate( $(element) ) ) {
|
||
|
module.set.selected(value, $(element));
|
||
5 years ago
|
if(!module.is.multiple()) {
|
||
5 years ago
|
module.hideAndClear();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
select: function(text, value, element) {
|
||
|
value = (value !== undefined)
|
||
|
? value
|
||
|
: text
|
||
|
;
|
||
|
if( module.can.activate( $(element) ) ) {
|
||
|
module.set.value(value, text, $(element));
|
||
5 years ago
|
if(!module.is.multiple()) {
|
||
5 years ago
|
module.hideAndClear();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
combo: function(text, value, element) {
|
||
|
value = (value !== undefined)
|
||
|
? value
|
||
|
: text
|
||
|
;
|
||
|
module.set.selected(value, $(element));
|
||
|
module.hideAndClear();
|
||
|
},
|
||
|
|
||
|
hide: function(text, value, element) {
|
||
5 years ago
|
module.set.value(value, text, $(element));
|
||
5 years ago
|
module.hideAndClear();
|
||
|
}
|
||
|
|
||
|
},
|
||
|
|
||
|
get: {
|
||
|
id: function() {
|
||
|
return id;
|
||
|
},
|
||
|
defaultText: function() {
|
||
|
return $module.data(metadata.defaultText);
|
||
|
},
|
||
|
defaultValue: function() {
|
||
|
return $module.data(metadata.defaultValue);
|
||
|
},
|
||
|
placeholderText: function() {
|
||
|
if(settings.placeholder != 'auto' && typeof settings.placeholder == 'string') {
|
||
|
return settings.placeholder;
|
||
|
}
|
||
|
return $module.data(metadata.placeholderText) || '';
|
||
|
},
|
||
|
text: function() {
|
||
|
return $text.text();
|
||
|
},
|
||
|
query: function() {
|
||
|
return $.trim($search.val());
|
||
|
},
|
||
|
searchWidth: function(value) {
|
||
|
value = (value !== undefined)
|
||
|
? value
|
||
|
: $search.val()
|
||
|
;
|
||
|
$sizer.text(value);
|
||
|
// prevent rounding issues
|
||
|
return Math.ceil( $sizer.width() + 1);
|
||
|
},
|
||
|
selectionCount: function() {
|
||
|
var
|
||
|
values = module.get.values(),
|
||
|
count
|
||
|
;
|
||
|
count = ( module.is.multiple() )
|
||
5 years ago
|
? Array.isArray(values)
|
||
5 years ago
|
? values.length
|
||
|
: 0
|
||
|
: (module.get.value() !== '')
|
||
|
? 1
|
||
|
: 0
|
||
|
;
|
||
|
return count;
|
||
|
},
|
||
|
transition: function($subMenu) {
|
||
|
return (settings.transition == 'auto')
|
||
|
? module.is.upward($subMenu)
|
||
|
? 'slide up'
|
||
|
: 'slide down'
|
||
|
: settings.transition
|
||
|
;
|
||
|
},
|
||
|
userValues: function() {
|
||
|
var
|
||
|
values = module.get.values()
|
||
|
;
|
||
|
if(!values) {
|
||
|
return false;
|
||
|
}
|
||
5 years ago
|
values = Array.isArray(values)
|
||
5 years ago
|
? values
|
||
|
: [values]
|
||
|
;
|
||
|
return $.grep(values, function(value) {
|
||
|
return (module.get.item(value) === false);
|
||
|
});
|
||
|
},
|
||
|
uniqueArray: function(array) {
|
||
|
return $.grep(array, function (value, index) {
|
||
|
return $.inArray(value, array) === index;
|
||
|
});
|
||
|
},
|
||
5 years ago
|
caretPosition: function(returnEndPos) {
|
||
5 years ago
|
var
|
||
|
input = $search.get(0),
|
||
|
range,
|
||
|
rangeLength
|
||
|
;
|
||
5 years ago
|
if(returnEndPos && 'selectionEnd' in input){
|
||
|
return input.selectionEnd;
|
||
|
}
|
||
|
else if(!returnEndPos && 'selectionStart' in input) {
|
||
5 years ago
|
return input.selectionStart;
|
||
|
}
|
||
5 years ago
|
if (document.selection) {
|
||
5 years ago
|
input.focus();
|
||
|
range = document.selection.createRange();
|
||
|
rangeLength = range.text.length;
|
||
5 years ago
|
if(returnEndPos) {
|
||
|
return rangeLength;
|
||
|
}
|
||
5 years ago
|
range.moveStart('character', -input.value.length);
|
||
|
return range.text.length - rangeLength;
|
||
|
}
|
||
|
},
|
||
|
value: function() {
|
||
|
var
|
||
|
value = ($input.length > 0)
|
||
|
? $input.val()
|
||
|
: $module.data(metadata.value),
|
||
5 years ago
|
isEmptyMultiselect = (Array.isArray(value) && value.length === 1 && value[0] === '')
|
||
5 years ago
|
;
|
||
|
// prevents placeholder element from being selected when multiple
|
||
|
return (value === undefined || isEmptyMultiselect)
|
||
|
? ''
|
||
|
: value
|
||
|
;
|
||
|
},
|
||
|
values: function() {
|
||
|
var
|
||
|
value = module.get.value()
|
||
|
;
|
||
|
if(value === '') {
|
||
|
return '';
|
||
|
}
|
||
|
return ( !module.has.selectInput() && module.is.multiple() )
|
||
|
? (typeof value == 'string') // delimited string
|
||
5 years ago
|
? module.escape.htmlEntities(value).split(settings.delimiter)
|
||
5 years ago
|
: ''
|
||
|
: value
|
||
|
;
|
||
|
},
|
||
|
remoteValues: function() {
|
||
|
var
|
||
|
values = module.get.values(),
|
||
|
remoteValues = false
|
||
|
;
|
||
|
if(values) {
|
||
|
if(typeof values == 'string') {
|
||
|
values = [values];
|
||
|
}
|
||
|
$.each(values, function(index, value) {
|
||
|
var
|
||
|
name = module.read.remoteData(value)
|
||
|
;
|
||
|
module.verbose('Restoring value from session data', name, value);
|
||
|
if(name) {
|
||
|
if(!remoteValues) {
|
||
|
remoteValues = {};
|
||
|
}
|
||
|
remoteValues[value] = name;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return remoteValues;
|
||
|
},
|
||
|
choiceText: function($choice, preserveHTML) {
|
||
|
preserveHTML = (preserveHTML !== undefined)
|
||
|
? preserveHTML
|
||
|
: settings.preserveHTML
|
||
|
;
|
||
|
if($choice) {
|
||
|
if($choice.find(selector.menu).length > 0) {
|
||
|
module.verbose('Retrieving text of element with sub-menu');
|
||
|
$choice = $choice.clone();
|
||
|
$choice.find(selector.menu).remove();
|
||
|
$choice.find(selector.menuIcon).remove();
|
||
|
}
|
||
|
return ($choice.data(metadata.text) !== undefined)
|
||
|
? $choice.data(metadata.text)
|
||
|
: (preserveHTML)
|
||
|
? $.trim($choice.html())
|
||
|
: $.trim($choice.text())
|
||
|
;
|
||
|
}
|
||
|
},
|
||
|
choiceValue: function($choice, choiceText) {
|
||
|
choiceText = choiceText || module.get.choiceText($choice);
|
||
|
if(!$choice) {
|
||
|
return false;
|
||
|
}
|
||
|
return ($choice.data(metadata.value) !== undefined)
|
||
|
? String( $choice.data(metadata.value) )
|
||
|
: (typeof choiceText === 'string')
|
||
5 years ago
|
? $.trim(
|
||
|
settings.ignoreSearchCase
|
||
|
? choiceText.toLowerCase()
|
||
|
: choiceText
|
||
|
)
|
||
5 years ago
|
: String(choiceText)
|
||
|
;
|
||
|
},
|
||
|
inputEvent: function() {
|
||
|
var
|
||
|
input = $search[0]
|
||
|
;
|
||
|
if(input) {
|
||
|
return (input.oninput !== undefined)
|
||
|
? 'input'
|
||
|
: (input.onpropertychange !== undefined)
|
||
|
? 'propertychange'
|
||
|
: 'keyup'
|
||
|
;
|
||
|
}
|
||
|
return false;
|
||
|
},
|
||
|
selectValues: function() {
|
||
|
var
|
||
5 years ago
|
select = {},
|
||
|
oldGroup = []
|
||
5 years ago
|
;
|
||
|
select.values = [];
|
||
|
$module
|
||
|
.find('option')
|
||
|
.each(function() {
|
||
|
var
|
||
|
$option = $(this),
|
||
|
name = $option.html(),
|
||
|
disabled = $option.attr('disabled'),
|
||
|
value = ( $option.attr('value') !== undefined )
|
||
|
? $option.attr('value')
|
||
5 years ago
|
: name,
|
||
5 years ago
|
text = ( $option.data(metadata.text) !== undefined )
|
||
|
? $option.data(metadata.text)
|
||
|
: name,
|
||
5 years ago
|
group = $option.parent('optgroup')
|
||
5 years ago
|
;
|
||
|
if(settings.placeholder === 'auto' && value === '') {
|
||
|
select.placeholder = name;
|
||
|
}
|
||
|
else {
|
||
5 years ago
|
if(group.length !== oldGroup.length || group[0] !== oldGroup[0]) {
|
||
|
select.values.push({
|
||
|
type: 'header',
|
||
|
divider: settings.headerDivider,
|
||
|
name: group.attr('label') || ''
|
||
|
});
|
||
|
oldGroup = group;
|
||
|
}
|
||
5 years ago
|
select.values.push({
|
||
|
name : name,
|
||
|
value : value,
|
||
5 years ago
|
text : text,
|
||
5 years ago
|
disabled : disabled
|
||
|
});
|
||
|
}
|
||
|
})
|
||
|
;
|
||
|
if(settings.placeholder && settings.placeholder !== 'auto') {
|
||
|
module.debug('Setting placeholder value to', settings.placeholder);
|
||
|
select.placeholder = settings.placeholder;
|
||
|
}
|
||
|
if(settings.sortSelect) {
|
||
5 years ago
|
if(settings.sortSelect === true) {
|
||
|
select.values.sort(function(a, b) {
|
||
|
return a.name.localeCompare(b.name);
|
||
|
});
|
||
|
} else if(settings.sortSelect === 'natural') {
|
||
|
select.values.sort(function(a, b) {
|
||
|
return (a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
|
||
|
});
|
||
|
} else if($.isFunction(settings.sortSelect)) {
|
||
|
select.values.sort(settings.sortSelect);
|
||
|
}
|
||
5 years ago
|
module.debug('Retrieved and sorted values from select', select);
|
||
|
}
|
||
|
else {
|
||
|
module.debug('Retrieved values from select', select);
|
||
|
}
|
||
|
return select;
|
||
|
},
|
||
|
activeItem: function() {
|
||
|
return $item.filter('.' + className.active);
|
||
|
},
|
||
|
selectedItem: function() {
|
||
|
var
|
||
|
$selectedItem = $item.not(selector.unselectable).filter('.' + className.selected)
|
||
|
;
|
||
|
return ($selectedItem.length > 0)
|
||
|
? $selectedItem
|
||
|
: $item.eq(0)
|
||
|
;
|
||
|
},
|
||
|
itemWithAdditions: function(value) {
|
||
|
var
|
||
|
$items = module.get.item(value),
|
||
|
$userItems = module.create.userChoice(value),
|
||
|
hasUserItems = ($userItems && $userItems.length > 0)
|
||
|
;
|
||
|
if(hasUserItems) {
|
||
|
$items = ($items.length > 0)
|
||
|
? $items.add($userItems)
|
||
|
: $userItems
|
||
|
;
|
||
|
}
|
||
|
return $items;
|
||
|
},
|
||
|
item: function(value, strict) {
|
||
|
var
|
||
|
$selectedItem = false,
|
||
|
shouldSearch,
|
||
|
isMultiple
|
||
|
;
|
||
|
value = (value !== undefined)
|
||
|
? value
|
||
|
: ( module.get.values() !== undefined)
|
||
|
? module.get.values()
|
||
|
: module.get.text()
|
||
|
;
|
||
5 years ago
|
isMultiple = (module.is.multiple() && Array.isArray(value));
|
||
5 years ago
|
shouldSearch = (isMultiple)
|
||
|
? (value.length > 0)
|
||
|
: (value !== undefined && value !== null)
|
||
|
;
|
||
5 years ago
|
strict = (value === '' || value === false || value === true)
|
||
5 years ago
|
? true
|
||
|
: strict || false
|
||
|
;
|
||
|
if(shouldSearch) {
|
||
|
$item
|
||
|
.each(function() {
|
||
|
var
|
||
|
$choice = $(this),
|
||
|
optionText = module.get.choiceText($choice),
|
||
|
optionValue = module.get.choiceValue($choice, optionText)
|
||
|
;
|
||
|
// safe early exit
|
||
|
if(optionValue === null || optionValue === undefined) {
|
||
|
return;
|
||
|
}
|
||
|
if(isMultiple) {
|
||
5 years ago
|
if($.inArray(module.escape.htmlEntities(String(optionValue)), value) !== -1) {
|
||
5 years ago
|
$selectedItem = ($selectedItem)
|
||
|
? $selectedItem.add($choice)
|
||
|
: $choice
|
||
|
;
|
||
|
}
|
||
|
}
|
||
|
else if(strict) {
|
||
|
module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
|
||
5 years ago
|
if( optionValue === value) {
|
||
5 years ago
|
$selectedItem = $choice;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
5 years ago
|
if(settings.ignoreCase) {
|
||
|
optionValue = optionValue.toLowerCase();
|
||
|
value = value.toLowerCase();
|
||
|
}
|
||
5 years ago
|
if(module.escape.htmlEntities(String(optionValue)) === module.escape.htmlEntities(String(value))) {
|
||
5 years ago
|
module.verbose('Found select item by value', optionValue, value);
|
||
|
$selectedItem = $choice;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
;
|
||
|
}
|
||
|
return $selectedItem;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
check: {
|
||
|
maxSelections: function(selectionCount) {
|
||
|
if(settings.maxSelections) {
|
||
|
selectionCount = (selectionCount !== undefined)
|
||
|
? selectionCount
|
||
|
: module.get.selectionCount()
|
||
|
;
|
||
|
if(selectionCount >= settings.maxSelections) {
|
||
|
module.debug('Maximum selection count reached');
|
||
|
if(settings.useLabels) {
|
||
|
$item.addClass(className.filtered);
|
||
|
module.add.message(message.maxSelections);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
else {
|
||
|
module.verbose('No longer at maximum selection count');
|
||
|
module.remove.message();
|
||
|
module.remove.filteredItem();
|
||
|
if(module.is.searchSelection()) {
|
||
|
module.filterItems();
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
restore: {
|
||
5 years ago
|
defaults: function(preventChangeTrigger) {
|
||
|
module.clear(preventChangeTrigger);
|
||
5 years ago
|
module.restore.defaultText();
|
||
|
module.restore.defaultValue();
|
||
|
},
|
||
|
defaultText: function() {
|
||
|
var
|
||
|
defaultText = module.get.defaultText(),
|
||
|
placeholderText = module.get.placeholderText
|
||
|
;
|
||
|
if(defaultText === placeholderText) {
|
||
|
module.debug('Restoring default placeholder text', defaultText);
|
||
|
module.set.placeholderText(defaultText);
|
||
|
}
|
||
|
else {
|
||
|
module.debug('Restoring default text', defaultText);
|
||
|
module.set.text(defaultText);
|
||
|
}
|
||
|
},
|
||
|
placeholderText: function() {
|
||
|
module.set.placeholderText();
|
||
|
},
|
||
|
defaultValue: function() {
|
||
|
var
|
||
|
defaultValue = module.get.defaultValue()
|
||
|
;
|
||
|
if(defaultValue !== undefined) {
|
||
|
module.debug('Restoring default value', defaultValue);
|
||
|
if(defaultValue !== '') {
|
||
|
module.set.value(defaultValue);
|
||
|
module.set.selected();
|
||
|
}
|
||
|
else {
|
||
|
module.remove.activeItem();
|
||
|
module.remove.selectedItem();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
labels: function() {
|
||
|
if(settings.allowAdditions) {
|
||
|
if(!settings.useLabels) {
|
||
|
module.error(error.labels);
|
||
|
settings.useLabels = true;
|
||
|
}
|
||
|
module.debug('Restoring selected values');
|
||
|
module.create.userLabels();
|
||
|
}
|
||
|
module.check.maxSelections();
|
||
|
},
|
||
|
selected: function() {
|
||
|
module.restore.values();
|
||
|
if(module.is.multiple()) {
|
||
|
module.debug('Restoring previously selected values and labels');
|
||
|
module.restore.labels();
|
||
|
}
|
||
|
else {
|
||
|
module.debug('Restoring previously selected values');
|
||
|
}
|
||
|
},
|
||
|
values: function() {
|
||
|
// prevents callbacks from occurring on initial load
|
||
|
module.set.initialLoad();
|
||
|
if(settings.apiSettings && settings.saveRemoteData && module.get.remoteValues()) {
|
||
|
module.restore.remoteValues();
|
||
|
}
|
||
|
else {
|
||
|
module.set.selected();
|
||
|
}
|
||
5 years ago
|
var value = module.get.value();
|
||
|
if(value && value !== '' && !(Array.isArray(value) && value.length === 0)) {
|
||
|
$input.removeClass(className.noselection);
|
||
|
} else {
|
||
|
$input.addClass(className.noselection);
|
||
|
}
|
||
5 years ago
|
module.remove.initialLoad();
|
||
|
},
|
||
|
remoteValues: function() {
|
||
|
var
|
||
|
values = module.get.remoteValues()
|
||
|
;
|
||
|
module.debug('Recreating selected from session data', values);
|
||
|
if(values) {
|
||
|
if( module.is.single() ) {
|
||
|
$.each(values, function(value, name) {
|
||
|
module.set.text(name);
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
$.each(values, function(value, name) {
|
||
|
module.add.label(value, name);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
read: {
|
||
|
remoteData: function(value) {
|
||
|
var
|
||
|
name
|
||
|
;
|
||
|
if(window.Storage === undefined) {
|
||
|
module.error(error.noStorage);
|
||
|
return;
|
||
|
}
|
||
|
name = sessionStorage.getItem(value);
|
||
|
return (name !== undefined)
|
||
|
? name
|
||
|
: false
|
||
|
;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
save: {
|
||
|
defaults: function() {
|
||
|
module.save.defaultText();
|
||
|
module.save.placeholderText();
|
||
|
module.save.defaultValue();
|
||
|
},
|
||
|
defaultValue: function() {
|
||
|
var
|
||
|
value = module.get.value()
|
||
|
;
|
||
|
module.verbose('Saving default value as', value);
|
||
|
$module.data(metadata.defaultValue, value);
|
||
|
},
|
||
|
defaultText: function() {
|
||
|
var
|
||
|
text = module.get.text()
|
||
|
;
|
||
|
module.verbose('Saving default text as', text);
|
||
|
$module.data(metadata.defaultText, text);
|
||
|
},
|
||
|
placeholderText: function() {
|
||
|
var
|
||
|
text
|
||
|
;
|
||
|
if(settings.placeholder !== false && $text.hasClass(className.placeholder)) {
|
||
|
text = module.get.text();
|
||
|
module.verbose('Saving placeholder text as', text);
|
||
|
$module.data(metadata.placeholderText, text);
|
||
|
}
|
||
|
},
|
||
|
remoteData: function(name, value) {
|
||
|
if(window.Storage === undefined) {
|
||
|
module.error(error.noStorage);
|
||
|
return;
|
||
|
}
|
||
|
module.verbose('Saving remote data to session storage', value, name);
|
||
|
sessionStorage.setItem(value, name);
|
||
|
}
|
||
|
},
|
||
|
|
||
5 years ago
|
clear: function(preventChangeTrigger) {
|
||
5 years ago
|
if(module.is.multiple() && settings.useLabels) {
|
||
|
module.remove.labels();
|
||
|
}
|
||
|
else {
|
||
|
module.remove.activeItem();
|
||
|
module.remove.selectedItem();
|
||
5 years ago
|
module.remove.filteredItem();
|
||
5 years ago
|
}
|
||
|
module.set.placeholderText();
|
||
5 years ago
|
module.clearValue(preventChangeTrigger);
|
||
5 years ago
|
},
|
||
|
|
||
5 years ago
|
clearValue: function(preventChangeTrigger) {
|
||
|
module.set.value('', null, null, preventChangeTrigger);
|
||
5 years ago
|
},
|
||
|
|
||
|
scrollPage: function(direction, $selectedItem) {
|
||
|
var
|
||
|
$currentItem = $selectedItem || module.get.selectedItem(),
|
||
|
$menu = $currentItem.closest(selector.menu),
|
||
|
menuHeight = $menu.outerHeight(),
|
||
|
currentScroll = $menu.scrollTop(),
|
||
|
itemHeight = $item.eq(0).outerHeight(),
|
||
|
itemsPerPage = Math.floor(menuHeight / itemHeight),
|
||
|
maxScroll = $menu.prop('scrollHeight'),
|
||
|
newScroll = (direction == 'up')
|
||
|
? currentScroll - (itemHeight * itemsPerPage)
|
||
|
: currentScroll + (itemHeight * itemsPerPage),
|
||
|
$selectableItem = $item.not(selector.unselectable),
|
||
|
isWithinRange,
|
||
|
$nextSelectedItem,
|
||
|
elementIndex
|
||
|
;
|
||
|
elementIndex = (direction == 'up')
|
||
|
? $selectableItem.index($currentItem) - itemsPerPage
|
||
|
: $selectableItem.index($currentItem) + itemsPerPage
|
||
|
;
|
||
|
isWithinRange = (direction == 'up')
|
||
|
? (elementIndex >= 0)
|
||
|
: (elementIndex < $selectableItem.length)
|
||
|
;
|
||
|
$nextSelectedItem = (isWithinRange)
|
||
|
? $selectableItem.eq(elementIndex)
|
||
|
: (direction == 'up')
|
||
|
? $selectableItem.first()
|
||
|
: $selectableItem.last()
|
||
|
;
|
||
|
if($nextSelectedItem.length > 0) {
|
||
|
module.debug('Scrolling page', direction, $nextSelectedItem);
|
||
|
$currentItem
|
||
|
.removeClass(className.selected)
|
||
|
;
|
||
|
$nextSelectedItem
|
||
|
.addClass(className.selected)
|
||
|
;
|
||
|
if(settings.selectOnKeydown && module.is.single()) {
|
||
|
module.set.selectedItem($nextSelectedItem);
|
||
|
}
|
||
|
$menu
|
||
|
.scrollTop(newScroll)
|
||
|
;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
set: {
|
||
|
filtered: function() {
|
||
|
var
|
||
|
isMultiple = module.is.multiple(),
|
||
|
isSearch = module.is.searchSelection(),
|
||
|
isSearchMultiple = (isMultiple && isSearch),
|
||
|
searchValue = (isSearch)
|
||
|
? module.get.query()
|
||
|
: '',
|
||
|
hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0),
|
||
|
searchWidth = module.get.searchWidth(),
|
||
|
valueIsSet = searchValue !== ''
|
||
|
;
|
||
|
if(isMultiple && hasSearchValue) {
|
||
|
module.verbose('Adjusting input width', searchWidth, settings.glyphWidth);
|
||
|
$search.css('width', searchWidth);
|
||
|
}
|
||
|
if(hasSearchValue || (isSearchMultiple && valueIsSet)) {
|
||
|
module.verbose('Hiding placeholder text');
|
||
|
$text.addClass(className.filtered);
|
||
|
}
|
||
|
else if(!isMultiple || (isSearchMultiple && !valueIsSet)) {
|
||
|
module.verbose('Showing placeholder text');
|
||
|
$text.removeClass(className.filtered);
|
||
|
}
|
||
|
},
|
||
|
empty: function() {
|
||
|
$module.addClass(className.empty);
|
||
|
},
|
||
|
loading: function() {
|
||
|
$module.addClass(className.loading);
|
||
|
},
|
||
|
placeholderText: function(text) {
|
||
|
text = text || module.get.placeholderText();
|
||
|
module.debug('Setting placeholder text', text);
|
||
|
module.set.text(text);
|
||
|
$text.addClass(className.placeholder);
|
||
|
},
|
||
|
tabbable: function() {
|
||
|
if( module.is.searchSelection() ) {
|
||
|
module.debug('Added tabindex to searchable dropdown');
|
||
|
$search
|
||
|
.val('')
|
||
|
.attr('tabindex', 0)
|
||
|
;
|
||
|
$menu
|
||
|
.attr('tabindex', -1)
|
||
|
;
|
||
|
}
|
||
|
else {
|
||
|
module.debug('Added tabindex to dropdown');
|
||
|
if( $module.attr('tabindex') === undefined) {
|
||
|
$module
|
||
|
.attr('tabindex', 0)
|
||
|
;
|
||
|
$menu
|
||
|
.attr('tabindex', -1)
|
||
|
;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
initialLoad: function() {
|
||
|
module.verbose('Setting initial load');
|
||
|
initialLoad = true;
|
||
|
},
|
||
|
activeItem: function($item) {
|
||
|
if( settings.allowAdditions && $item.filter(selector.addition).length > 0 ) {
|
||
|
$item.addClass(className.filtered);
|
||
|
}
|
||
|
else {
|
||
|
$item.addClass(className.active);
|
||
|
}
|
||
|
},
|
||
|
partialSearch: function(text) {
|
||
|
var
|
||
|
length = module.get.query().length
|
||
|
;
|
||
|
$search.val( text.substr(0, length));
|
||
|
},
|
||
|
scrollPosition: function($item, forceScroll) {
|
||
|
var
|
||
|
edgeTolerance = 5,
|
||
|
$menu,
|
||
|
hasActive,
|
||
|
offset,
|
||
|
itemHeight,
|
||
|
itemOffset,
|
||
|
menuOffset,
|
||
|
menuScroll,
|
||
|
menuHeight,
|
||
|
abovePage,
|
||
|
belowPage
|
||
|
;
|
||
|
|
||
|
$item = $item || module.get.selectedItem();
|
||
|
$menu = $item.closest(selector.menu);
|
||
|
hasActive = ($item && $item.length > 0);
|
||
|
forceScroll = (forceScroll !== undefined)
|
||
|
? forceScroll
|
||
|
: false
|
||
|
;
|
||
5 years ago
|
if(module.get.activeItem().length === 0){
|
||
|
forceScroll = false;
|
||
|
}
|
||
5 years ago
|
if($item && $menu.length > 0 && hasActive) {
|
||
|
itemOffset = $item.position().top;
|
||
|
|
||
|
$menu.addClass(className.loading);
|
||
|
menuScroll = $menu.scrollTop();
|
||
|
menuOffset = $menu.offset().top;
|
||
|
itemOffset = $item.offset().top;
|
||
|
offset = menuScroll - menuOffset + itemOffset;
|
||
|
if(!forceScroll) {
|
||
|
menuHeight = $menu.height();
|
||
|
belowPage = menuScroll + menuHeight < (offset + edgeTolerance);
|
||
|
abovePage = ((offset - edgeTolerance) < menuScroll);
|
||
|
}
|
||
|
module.debug('Scrolling to active item', offset);
|
||
|
if(forceScroll || abovePage || belowPage) {
|
||
|
$menu.scrollTop(offset);
|
||
|
}
|
||
|
$menu.removeClass(className.loading);
|
||
|
}
|
||
|
},
|
||
|
text: function(text) {
|
||
5 years ago
|
if(settings.action === 'combo') {
|
||
|
module.debug('Changing combo button text', text, $combo);
|
||
|
if(settings.preserveHTML) {
|
||
|
$combo.html(text);
|
||
5 years ago
|
}
|
||
|
else {
|
||
5 years ago
|
$combo.text(text);
|
||
|
}
|
||
|
}
|
||
|
else if(settings.action === 'activate') {
|
||
|
if(text !== module.get.placeholderText()) {
|
||
|
$text.removeClass(className.placeholder);
|
||
|
}
|
||
|
module.debug('Changing text', text, $text);
|
||
|
$text
|
||
|
.removeClass(className.filtered)
|
||
|
;
|
||
|
if(settings.preserveHTML) {
|
||
|
$text.html(text);
|
||
|
}
|
||
|
else {
|
||
|
$text.text(text);
|
||
5 years ago
|
}
|
||
|
}
|
||
|
},
|
||
|
selectedItem: function($item) {
|
||
|
var
|
||
|
value = module.get.choiceValue($item),
|
||
|
searchText = module.get.choiceText($item, false),
|
||
|
text = module.get.choiceText($item, true)
|
||
|
;
|
||
|
module.debug('Setting user selection to item', $item);
|
||
|
module.remove.activeItem();
|
||
|
module.set.partialSearch(searchText);
|
||
|
module.set.activeItem($item);
|
||
|
module.set.selected(value, $item);
|
||
|
module.set.text(text);
|
||
|
},
|
||
|
selectedLetter: function(letter) {
|
||
|
var
|
||
|
$selectedItem = $item.filter('.' + className.selected),
|
||
|
alreadySelectedLetter = $selectedItem.length > 0 && module.has.firstLetter($selectedItem, letter),
|
||
|
$nextValue = false,
|
||
|
$nextItem
|
||
|
;
|
||
|
// check next of same letter
|
||
|
if(alreadySelectedLetter) {
|
||
|
$nextItem = $selectedItem.nextAll($item).eq(0);
|
||
|
if( module.has.firstLetter($nextItem, letter) ) {
|
||
|
$nextValue = $nextItem;
|
||
|
}
|
||
|
}
|
||
|
// check all values
|
||
|
if(!$nextValue) {
|
||
|
$item
|
||
|
.each(function(){
|
||
|
if(module.has.firstLetter($(this), letter)) {
|
||
|
$nextValue = $(this);
|
||
|
return false;
|
||
|
}
|
||
|
})
|
||
|
;
|
||
|
}
|
||
|
// set next value
|
||
|
if($nextValue) {
|
||
|
module.verbose('Scrolling to next value with letter', letter);
|
||
|
module.set.scrollPosition($nextValue);
|
||
|
$selectedItem.removeClass(className.selected);
|
||
|
$nextValue.addClass(className.selected);
|
||
|
module.aria.refreshDescendant();
|
||
|
if(settings.selectOnKeydown && module.is.single()) {
|
||
|
module.set.selectedItem($nextValue);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
direction: function($menu) {
|
||
|
if(settings.direction == 'auto') {
|
||
5 years ago
|
// reset position, remove upward if it's base menu
|
||
|
if (!$menu) {
|
||
|
module.remove.upward();
|
||
|
} else if (module.is.upward($menu)) {
|
||
|
//we need make sure when make assertion openDownward for $menu, $menu does not have upward class
|
||
|
module.remove.upward($menu);
|
||
|
}
|
||
5 years ago
|
|
||
|
if(module.can.openDownward($menu)) {
|
||
|
module.remove.upward($menu);
|
||
|
}
|
||
|
else {
|
||
|
module.set.upward($menu);
|
||
|
}
|
||
|
if(!module.is.leftward($menu) && !module.can.openRightward($menu)) {
|
||
|
module.set.leftward($menu);
|
||
|
}
|
||
|
}
|
||
|
else if(settings.direction == 'upward') {
|
||
|
module.set.upward($menu);
|
||
|
}
|
||
|
},
|
||
|
upward: function($currentMenu) {
|
||
|
var $element = $currentMenu || $module;
|
||
|
$element.addClass(className.upward);
|
||
|
},
|
||
|
leftward: function($currentMenu) {
|
||
|
var $element = $currentMenu || $menu;
|
||
|
$element.addClass(className.leftward);
|
||
|
},
|
||
5 years ago
|
value: function(value, text, $selected, preventChangeTrigger) {
|
||
|
if(value !== undefined && value !== '' && !(Array.isArray(value) && value.length === 0)) {
|
||
|
$input.removeClass(className.noselection);
|
||
|
} else {
|
||
|
$input.addClass(className.noselection);
|
||
|
}
|
||
5 years ago
|
var
|
||
|
escapedValue = module.escape.value(value),
|
||
|
hasInput = ($input.length > 0),
|
||
|
currentValue = module.get.values(),
|
||
|
stringValue = (value !== undefined)
|
||
|
? String(value)
|
||
|
: value,
|
||
|
newValue
|
||
|
;
|
||
|
if(hasInput) {
|
||
|
if(!settings.allowReselection && stringValue == currentValue) {
|
||
|
module.verbose('Skipping value update already same value', value, currentValue);
|
||
|
if(!module.is.initialLoad()) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( module.is.single() && module.has.selectInput() && module.can.extendSelect() ) {
|
||
|
module.debug('Adding user option', value);
|
||
|
module.add.optionValue(value);
|
||
|
}
|
||
|
module.debug('Updating input value', escapedValue, currentValue);
|
||
|
internalChange = true;
|
||
|
$input
|
||
|
.val(escapedValue)
|
||
|
;
|
||
|
if(settings.fireOnInit === false && module.is.initialLoad()) {
|
||
|
module.debug('Input native change event ignored on initial load');
|
||
|
}
|
||
5 years ago
|
else if(preventChangeTrigger !== true) {
|
||
5 years ago
|
module.trigger.change();
|
||
|
}
|
||
|
internalChange = false;
|
||
|
}
|
||
|
else {
|
||
|
module.verbose('Storing value in metadata', escapedValue, $input);
|
||
|
if(escapedValue !== currentValue) {
|
||
|
$module.data(metadata.value, stringValue);
|
||
|
}
|
||
|
}
|
||
|
if(settings.fireOnInit === false && module.is.initialLoad()) {
|
||
|
module.verbose('No callback on initial load', settings.onChange);
|
||
|
}
|
||
5 years ago
|
else if(preventChangeTrigger !== true) {
|
||
5 years ago
|
settings.onChange.call(element, value, text, $selected);
|
||
|
}
|
||
|
},
|
||
|
active: function() {
|
||
|
$module
|
||
|
.addClass(className.active)
|
||
|
;
|
||
|
},
|
||
|
multiple: function() {
|
||
|
$module.addClass(className.multiple);
|
||
|
},
|
||
|
visible: function() {
|
||
|
$module.addClass(className.visible);
|
||
|
},
|
||
|
exactly: function(value, $selectedItem) {
|
||
|
module.debug('Setting selected to exact values');
|
||
|
module.clear();
|
||
|
module.set.selected(value, $selectedItem);
|
||
|
},
|
||
|
selected: function(value, $selectedItem) {
|
||
|
var
|
||
5 years ago
|
isMultiple = module.is.multiple()
|
||
5 years ago
|
;
|
||
|
$selectedItem = (settings.allowAdditions)
|
||
|
? $selectedItem || module.get.itemWithAdditions(value)
|
||
|
: $selectedItem || module.get.item(value)
|
||
|
;
|
||
|
if(!$selectedItem) {
|
||
|
return;
|
||
|
}
|
||
|
module.debug('Setting selected menu item to', $selectedItem);
|
||
|
if(module.is.multiple()) {
|
||
|
module.remove.searchWidth();
|
||
|
}
|
||
|
if(module.is.single()) {
|
||
|
module.remove.activeItem();
|
||
|
module.remove.selectedItem();
|
||
|
}
|
||
|
else if(settings.useLabels) {
|
||
|
module.remove.selectedItem();
|
||
|
}
|
||
|
// select each item
|
||
|
$selectedItem
|
||
|
.each(function() {
|
||
|
var
|
||
|
$selected = $(this),
|
||
|
selectedText = module.get.choiceText($selected),
|
||
|
selectedValue = module.get.choiceValue($selected, selectedText),
|
||
|
|
||
|
isFiltered = $selected.hasClass(className.filtered),
|
||
|
isActive = $selected.hasClass(className.active),
|
||
|
isUserValue = $selected.hasClass(className.addition),
|
||
|
shouldAnimate = (isMultiple && $selectedItem.length == 1)
|
||
|
;
|
||
|
if(isMultiple) {
|
||
|
if(!isActive || isUserValue) {
|
||
|
if(settings.apiSettings && settings.saveRemoteData) {
|
||
|
module.save.remoteData(selectedText, selectedValue);
|
||
|
}
|
||
|
if(settings.useLabels) {
|
||
|
module.add.label(selectedValue, selectedText, shouldAnimate);
|
||
|
module.add.value(selectedValue, selectedText, $selected);
|
||
|
module.set.activeItem($selected);
|
||
|
module.filterActive();
|
||
|
module.select.nextAvailable($selectedItem);
|
||
|
}
|
||
|
else {
|
||
|
module.add.value(selectedValue, selectedText, $selected);
|
||
|
module.set.text(module.add.variables(message.count));
|
||
|
module.set.activeItem($selected);
|
||
|
}
|
||
|
}
|
||
5 years ago
|
else if(!isFiltered && (settings.useLabels || selectActionActive)) {
|
||
5 years ago
|
module.debug('Selected active value, removing label');
|
||
|
module.remove.selected(selectedValue);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if(settings.apiSettings && settings.saveRemoteData) {
|
||
|
module.save.remoteData(selectedText, selectedValue);
|
||
|
}
|
||
|
module.set.text(selectedText);
|
||
|
module.set.value(selectedValue, selectedText, $selected);
|
||
|
$selected
|
||
|
.addClass(className.active)
|
||
|
.addClass(className.selected)
|
||
|
;
|
||
|
}
|
||
|
})
|
||
|
;
|
||
5 years ago
|
module.remove.searchTerm();
|
||
5 years ago
|
}
|
||
|
},
|
||
|
|
||
|
add: {
|
||
|
label: function(value, text, shouldAnimate) {
|
||
|
var
|
||
|
$next = module.is.searchSelection()
|
||
|
? $search
|
||
|
: $text,
|
||
|
escapedValue = module.escape.value(value),
|
||
|
$label
|
||
|
;
|
||
|
if(settings.ignoreCase) {
|
||
|
escapedValue = escapedValue.toLowerCase();
|
||
|
}
|
||
|
$label = $('<a />')
|
||
|
.addClass(className.label)
|
||
|
.attr('data-' + metadata.value, escapedValue)
|
||
5 years ago
|
.html(templates.label(escapedValue, text, settings.preserveHTML, settings.className))
|
||
5 years ago
|
;
|
||
|
$label = settings.onLabelCreate.call($label, escapedValue, text);
|
||
|
|
||
|
if(module.has.label(value)) {
|
||
|
module.debug('User selection already exists, skipping', escapedValue);
|
||
|
return;
|
||
|
}
|
||
|
if(settings.label.variation) {
|
||
|
$label.addClass(settings.label.variation);
|
||
|
}
|
||
|
if(shouldAnimate === true) {
|
||
|
module.debug('Animating in label', $label);
|
||
|
$label
|
||
|
.addClass(className.hidden)
|
||
|
.insertBefore($next)
|
||
5 years ago
|
.transition({
|
||
|
animation : settings.label.transition,
|
||
|
debug : settings.debug,
|
||
|
verbose : settings.verbose,
|
||
|
duration : settings.label.duration
|
||
|
})
|
||
5 years ago
|
;
|
||
|
}
|
||
|
else {
|
||
|
module.debug('Adding selection label', $label);
|
||
|
$label
|
||
|
.insertBefore($next)
|
||
|
;
|
||
|
}
|
||
|
},
|
||
|
message: function(message) {
|
||
|
var
|
||
|
$message = $menu.children(selector.message),
|
||
|
html = settings.templates.message(module.add.variables(message))
|
||
|
;
|
||
|
if($message.length > 0) {
|
||
|
$message
|
||
|
.html(html)
|
||
|
;
|
||
|
}
|
||
|
else {
|
||
|
$message = $('<div/>')
|
||
|
.html(html)
|
||
|
.addClass(className.message)
|
||
|
.appendTo($menu)
|
||
|
;
|
||
|
}
|
||
|
},
|
||
|
optionValue: function(value) {
|
||
|
var
|
||
|
escapedValue = module.escape.value(value),
|
||
|
$option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'),
|
||
|
hasOption = ($option.length > 0)
|
||
|
;
|
||
|
if(hasOption) {
|
||
|
return;
|
||
|
}
|
||
|
// temporarily disconnect observer
|
||
|
module.disconnect.selectObserver();
|
||
|
if( module.is.single() ) {
|
||
|
module.verbose('Removing previous user addition');
|
||
|
$input.find('option.' + className.addition).remove();
|
||
|
}
|
||
|
$('<option/>')
|
||
|
.prop('value', escapedValue)
|
||
|
.addClass(className.addition)
|
||
|
.html(value)
|
||
|
.appendTo($input)
|
||
|
;
|
||
|
module.verbose('Adding user addition as an <option>', value);
|
||
|
module.observe.select();
|
||
|
},
|
||
|
userSuggestion: function(value) {
|
||
|
var
|
||
|
$addition = $menu.children(selector.addition),
|
||
|
$existingItem = module.get.item(value),
|
||
|
alreadyHasValue = $existingItem && $existingItem.not(selector.addition).length,
|
||
|
hasUserSuggestion = $addition.length > 0,
|
||
|
html
|
||
|
;
|
||
|
if(settings.useLabels && module.has.maxSelections()) {
|
||
|
return;
|
||
|
}
|
||
|
if(value === '' || alreadyHasValue) {
|
||
|
$addition.remove();
|
||
|
return;
|
||
|
}
|
||
|
if(hasUserSuggestion) {
|
||
|
$addition
|
||
|
.data(metadata.value, value)
|
||
|
.data(metadata.text, value)
|
||
|
.attr('data-' + metadata.value, value)
|
||
|
.attr('data-' + metadata.text, value)
|
||
|
.removeClass(className.filtered)
|
||
|
;
|
||
|
if(!settings.hideAdditions) {
|
||
|
html = settings.templates.addition( module.add.variables(message.addResult, value) );
|
||
|
$addition
|
||
|
.html(html)
|
||
|
;
|
||
|
}
|
||
|
module.verbose('Replacing user suggestion with new value', $addition);
|
||
|
}
|
||
|
else {
|
||
|
$addition = module.create.userChoice(value);
|
||
|
$addition
|
||
|
.prependTo($menu)
|
||
|
;
|
||
|
module.verbose('Adding item choice to menu corresponding with user choice addition', $addition);
|
||
|
}
|
||
|
if(!settings.hideAdditions || module.is.allFiltered()) {
|
||
|
$addition
|
||
|
.addClass(className.selected)
|
||
|
.siblings()
|
||
|
.removeClass(className.selected)
|
||
|
;
|
||
|
}
|
||
|
module.refreshItems();
|
||
|
},
|
||
|
variables: function(message, term) {
|
||
|
var
|
||
|
hasCount = (message.search('{count}') !== -1),
|
||
|
hasMaxCount = (message.search('{maxCount}') !== -1),
|
||
|
hasTerm = (message.search('{term}') !== -1),
|
||
|
count,
|
||
|
query
|
||
|
;
|
||
|
module.verbose('Adding templated variables to message', message);
|
||
|
if(hasCount) {
|
||
|
count = module.get.selectionCount();
|
||
|
message = message.replace('{count}', count);
|
||
|
}
|
||
|
if(hasMaxCount) {
|
||
|
count = module.get.selectionCount();
|
||
|
message = message.replace('{maxCount}', settings.maxSelections);
|
||
|
}
|
||
|
if(hasTerm) {
|
||
|
query = term || module.get.query();
|
||
|
message = message.replace('{term}', query);
|
||
|
}
|
||
|
return message;
|
||
|
},
|
||
|
value: function(addedValue, addedText, $selectedItem) {
|
||
|
var
|
||
|
currentValue = module.get.values(),
|
||
|
newValue
|
||
|
;
|
||
|
if(module.has.value(addedValue)) {
|
||
|
module.debug('Value already selected');
|
||
|
return;
|
||
|
}
|
||
|
if(addedValue === '') {
|
||
|
module.debug('Cannot select blank values from multiselect');
|
||
|
return;
|
||
|
}
|
||
|
// extend current array
|
||
5 years ago
|
if(Array.isArray(currentValue)) {
|
||
5 years ago
|
newValue = currentValue.concat([addedValue]);
|
||
|
newValue = module.get.uniqueArray(newValue);
|
||
|
}
|
||
|
else {
|
||
|
newValue = [addedValue];
|
||
|
}
|
||
|
// add values
|
||
|
if( module.has.selectInput() ) {
|
||
|
if(module.can.extendSelect()) {
|
||
|
module.debug('Adding value to select', addedValue, newValue, $input);
|
||
|
module.add.optionValue(addedValue);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
newValue = newValue.join(settings.delimiter);
|
||
|
module.debug('Setting hidden input to delimited value', newValue, $input);
|
||
|
}
|
||
|
|
||
|
if(settings.fireOnInit === false && module.is.initialLoad()) {
|
||
|
module.verbose('Skipping onadd callback on initial load', settings.onAdd);
|
||
|
}
|
||
|
else {
|
||
|
settings.onAdd.call(element, addedValue, addedText, $selectedItem);
|
||
|
}
|
||
5 years ago
|
module.set.value(newValue, addedText, $selectedItem);
|
||
5 years ago
|
module.check.maxSelections();
|
||
5 years ago
|
},
|
||
5 years ago
|
},
|
||
|
|
||
|
remove: {
|
||
|
active: function() {
|
||
|
$module.removeClass(className.active);
|
||
|
},
|
||
|
activeLabel: function() {
|
||
|
$module.find(selector.label).removeClass(className.active);
|
||
|
},
|
||
|
empty: function() {
|
||
|
$module.removeClass(className.empty);
|
||
|
},
|
||
|
loading: function() {
|
||
|
$module.removeClass(className.loading);
|
||
|
},
|
||
|
initialLoad: function() {
|
||
|
initialLoad = false;
|
||
|
},
|
||
|
upward: function($currentMenu) {
|
||
|
var $element = $currentMenu || $module;
|
||
|
$element.removeClass(className.upward);
|
||
|
},
|
||
|
leftward: function($currentMenu) {
|
||
|
var $element = $currentMenu || $menu;
|
||
|
$element.removeClass(className.leftward);
|
||
|
},
|
||
|
visible: function() {
|
||
|
$module.removeClass(className.visible);
|
||
|
},
|
||
|
activeItem: function() {
|
||
|
$item.removeClass(className.active);
|
||
|
},
|
||
|
filteredItem: function() {
|
||
|
if(settings.useLabels && module.has.maxSelections() ) {
|
||
|
return;
|
||
|
}
|
||
|
if(settings.useLabels && module.is.multiple()) {
|
||
|
$item.not('.' + className.active).removeClass(className.filtered);
|
||
|
}
|
||
|
else {
|
||
|
$item.removeClass(className.filtered);
|
||
|
}
|
||
5 years ago
|
if(settings.hideDividers) {
|
||
|
$divider.removeClass(className.hidden);
|
||
|
}
|
||
5 years ago
|
module.remove.empty();
|
||
|
},
|
||
|
optionValue: function(value) {
|
||
|
var
|
||
|
escapedValue = module.escape.value(value),
|
||
|
$option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'),
|
||
|
hasOption = ($option.length > 0)
|
||
|
;
|
||
|
if(!hasOption || !$option.hasClass(className.addition)) {
|
||
|
return;
|
||
|
}
|
||
|
// temporarily disconnect observer
|
||
|
if(selectObserver) {
|
||
|
selectObserver.disconnect();
|
||
|
module.verbose('Temporarily disconnecting mutation observer');
|
||
|
}
|
||
|
$option.remove();
|
||
|
module.verbose('Removing user addition as an <option>', escapedValue);
|
||
|
if(selectObserver) {
|
||
|
selectObserver.observe($input[0], {
|
||
|
childList : true,
|
||
|
subtree : true
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
message: function() {
|
||
|
$menu.children(selector.message).remove();
|
||
|
},
|
||
|
searchWidth: function() {
|
||
|
$search.css('width', '');
|
||
|
},
|
||
|
searchTerm: function() {
|
||
|
module.verbose('Cleared search term');
|
||
|
$search.val('');
|
||
|
module.set.filtered();
|
||
|
},
|
||
|
userAddition: function() {
|
||
|
$item.filter(selector.addition).remove();
|
||
|
},
|
||
|
selected: function(value, $selectedItem) {
|
||
|
$selectedItem = (settings.allowAdditions)
|
||
|
? $selectedItem || module.get.itemWithAdditions(value)
|
||
|
: $selectedItem || module.get.item(value)
|
||
|
;
|
||
|
|
||
|
if(!$selectedItem) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$selectedItem
|
||
|
.each(function() {
|
||
|
var
|
||
|
$selected = $(this),
|
||
|
selectedText = module.get.choiceText($selected),
|
||
|
selectedValue = module.get.choiceValue($selected, selectedText)
|
||
|
;
|
||
|
if(module.is.multiple()) {
|
||
|
if(settings.useLabels) {
|
||
|
module.remove.value(selectedValue, selectedText, $selected);
|
||
|
module.remove.label(selectedValue);
|
||
|
}
|
||
|
else {
|
||
|
module.remove.value(selectedValue, selectedText, $selected);
|
||
|
if(module.get.selectionCount() === 0) {
|
||
|
module.set.placeholderText();
|
||
|
}
|
||
|
else {
|
||
|
module.set.text(module.add.variables(message.count));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
module.remove.value(selectedValue, selectedText, $selected);
|
||
|
}
|
||
|
$selected
|
||
|
.removeClass(className.filtered)
|
||
|
.removeClass(className.active)
|
||
|
;
|
||
|
if(settings.useLabels) {
|
||
|
$selected.removeClass(className.selected);
|
||
|
}
|
||
|
})
|
||
|
;
|
||
|
},
|
||
|
selectedItem: function() {
|
||
|
$item.removeClass(className.selected);
|
||
|
},
|
||
|
value: function(removedValue, removedText, $removedItem) {
|
||
|
var
|
||
|
values = module.get.values(),
|
||
|
newValue
|
||
|
;
|
||
5 years ago
|
removedValue = module.escape.htmlEntities(removedValue);
|
||
5 years ago
|
if( module.has.selectInput() ) {
|
||
|
module.verbose('Input is <select> removing selected option', removedValue);
|
||
|
newValue = module.remove.arrayValue(removedValue, values);
|
||
|
module.remove.optionValue(removedValue);
|
||
|
}
|
||
|
else {
|
||
|
module.verbose('Removing from delimited values', removedValue);
|
||
|
newValue = module.remove.arrayValue(removedValue, values);
|
||
|
newValue = newValue.join(settings.delimiter);
|
||
|
}
|
||
|
if(settings.fireOnInit === false && module.is.initialLoad()) {
|
||
|
module.verbose('No callback on initial load', settings.onRemove);
|
||
|
}
|
||
|
else {
|
||
|
settings.onRemove.call(element, removedValue, removedText, $removedItem);
|
||
|
}
|
||
|
module.set.value(newValue, removedText, $removedItem);
|
||
|
module.check.maxSelections();
|
||
|
},
|
||
|
arrayValue: function(removedValue, values) {
|
||
5 years ago
|
if( !Array.isArray(values) ) {
|
||
5 years ago
|
values = [values];
|
||
|
}
|
||
|
values = $.grep(values, function(value){
|
||
|
return (removedValue != value);
|
||
|
});
|
||
|
module.verbose('Removed value from delimited string', removedValue, values);
|
||
|
return values;
|
||
|
},
|
||
|
label: function(value, shouldAnimate) {
|
||
|
var
|
||
|
$labels = $module.find(selector.label),
|
||
5 years ago
|
$removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(settings.ignoreCase ? value.toLowerCase() : value) +'"]')
|
||
5 years ago
|
;
|
||
|
module.verbose('Removing label', $removedLabel);
|
||
|
$removedLabel.remove();
|
||
|
},
|
||
|
activeLabels: function($activeLabels) {
|
||
|
$activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active);
|
||
|
module.verbose('Removing active label selections', $activeLabels);
|
||
|
module.remove.labels($activeLabels);
|
||
|
},
|
||
|
labels: function($labels) {
|
||
|
$labels = $labels || $module.find(selector.label);
|
||
|
module.verbose('Removing labels', $labels);
|
||
|
$labels
|
||
|
.each(function(){
|
||
|
var
|
||
|
$label = $(this),
|
||
|
value = $label.data(metadata.value),
|
||
|
stringValue = (value !== undefined)
|
||
|
? String(value)
|
||
|
: value,
|
||
|
isUserValue = module.is.userValue(stringValue)
|
||
|
;
|
||
|
if(settings.onLabelRemove.call($label, value) === false) {
|
||
|
module.debug('Label remove callback cancelled removal');
|
||
|
return;
|
||
|
}
|
||
|
module.remove.message();
|
||
|
if(isUserValue) {
|
||
|
module.remove.value(stringValue);
|
||
|
module.remove.label(stringValue);
|
||
|
}
|
||
|
else {
|
||
|
// selected will also remove label
|
||
|
module.remove.selected(stringValue);
|
||
|
}
|
||
|
})
|
||
|
;
|
||
|
},
|
||
|
tabbable: function() {
|
||
|
if( module.is.searchSelection() ) {
|
||
|
module.debug('Searchable dropdown initialized');
|
||
|
$search
|
||
|
.removeAttr('tabindex')
|
||
|
;
|
||
|
$menu
|
||
|
.removeAttr('tabindex')
|
||
|
;
|
||
|
}
|
||
|
else {
|
||
|
module.debug('Simple selection dropdown initialized');
|
||
|
$module
|
||
|
.removeAttr('tabindex')
|
||
|
;
|
||
|
$menu
|
||
|
.removeAttr('tabindex')
|
||
|
;
|
||
|
}
|
||
5 years ago
|
},
|
||
|
diacritics: function(text) {
|
||
|
return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text;
|
||
5 years ago
|
}
|
||
|
},
|
||
|
|
||
|
has: {
|
||
|
menuSearch: function() {
|
||
|
return (module.has.search() && $search.closest($menu).length > 0);
|
||
|
},
|
||
5 years ago
|
clearItem: function() {
|
||
|
return ($clear.length > 0);
|
||
|
},
|
||
5 years ago
|
search: function() {
|
||
|
return ($search.length > 0);
|
||
|
},
|
||
|
sizer: function() {
|
||
|
return ($sizer.length > 0);
|
||
|
},
|
||
|
selectInput: function() {
|
||
|
return ( $input.is('select') );
|
||
|
},
|
||
|
minCharacters: function(searchTerm) {
|
||
5 years ago
|
if(settings.minCharacters && !iconClicked) {
|
||
5 years ago
|
searchTerm = (searchTerm !== undefined)
|
||
|
? String(searchTerm)
|
||
|
: String(module.get.query())
|
||
|
;
|
||
|
return (searchTerm.length >= settings.minCharacters);
|
||
|
}
|
||
5 years ago
|
iconClicked=false;
|
||
5 years ago
|
return true;
|
||
|
},
|
||
|
firstLetter: function($item, letter) {
|
||
|
var
|
||
|
text,
|
||
|
firstLetter
|
||
|
;
|
||
|
if(!$item || $item.length === 0 || typeof letter !== 'string') {
|
||
|
return false;
|
||
|
}
|
||
|
text = module.get.choiceText($item, false);
|
||
|
letter = letter.toLowerCase();
|
||
|
firstLetter = String(text).charAt(0).toLowerCase();
|
||
|
return (letter == firstLetter);
|
||
|
},
|
||
|
input: function() {
|
||
|
return ($input.length > 0);
|
||
|
},
|
||
|
items: function() {
|
||
|
return ($item.length > 0);
|
||
|
},
|
||
|
menu: function() {
|
||
|
return ($menu.length > 0);
|
||
|
},
|
||
|
message: function() {
|
||
|
return ($menu.children(selector.message).length !== 0);
|
||
|
},
|
||
|
label: function(value) {
|
||
|
var
|
||
|
escapedValue = module.escape.value(value),
|
||
|
$labels = $module.find(selector.label)
|
||
|
;
|
||
|
if(settings.ignoreCase) {
|
||
|
escapedValue = escapedValue.toLowerCase();
|
||
|
}
|
||
|
return ($labels.filter('[data-' + metadata.value + '="' + module.escape.string(escapedValue) +'"]').length > 0);
|
||
|
},
|
||
|
maxSelections: function() {
|
||
|
return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections);
|
||
|
},
|
||
|
allResultsFiltered: function() {
|
||
|
var
|
||
|
$normalResults = $item.not(selector.addition)
|
||
|
;
|
||
|
return ($normalResults.filter(selector.unselectable).length === $normalResults.length);
|
||
|
},
|
||
|
userSuggestion: function() {
|
||
|
return ($menu.children(selector.addition).length > 0);
|
||
|
},
|
||
|
query: function() {
|
||
|
return (module.get.query() !== '');
|
||
|
},
|
||
|
value: function(value) {
|
||
|
return (settings.ignoreCase)
|
||
|
? module.has.valueIgnoringCase(value)
|
||
|
: module.has.valueMatchingCase(value)
|
||
|
;
|
||
|
},
|
||
|
valueMatchingCase: function(value) {
|
||
|
var
|
||
|
values = module.get.values(),
|
||
5 years ago
|
hasValue = Array.isArray(values)
|
||
5 years ago
|
? values && ($.inArray(value, values) !== -1)
|
||
|
: (values == value)
|
||
|
;
|
||
|
return (hasValue)
|
||
|
? true
|
||
|
: false
|
||
|
;
|
||
|
},
|
||
|
valueIgnoringCase: function(value) {
|
||
|
var
|
||
|
values = module.get.values(),
|
||
|
hasValue = false
|
||
|
;
|
||
5 years ago
|
if(!Array.isArray(values)) {
|
||
5 years ago
|
values = [values];
|
||
|
}
|
||
|
$.each(values, function(index, existingValue) {
|
||
|
if(String(value).toLowerCase() == String(existingValue).toLowerCase()) {
|
||
|
hasValue = true;
|
||
|
return false;
|
||
|
}
|
||
|
});
|
||
|
return hasValue;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
is: {
|
||
|
active: function() {
|
||
|
return $module.hasClass(className.active);
|
||
|
},
|
||
|
animatingInward: function() {
|
||
|
return $menu.transition('is inward');
|
||
|
},
|
||
|
animatingOutward: function() {
|
||
|
return $menu.transition('is outward');
|
||
|
},
|
||
|
bubbledLabelClick: function(event) {
|
||
|
return $(event.target).is('select, input') && $module.closest('label').length > 0;
|
||
|
},
|
||
|
bubbledIconClick: function(event) {
|
||
|
return $(event.target).closest($icon).length > 0;
|
||
|
},
|
||
|
alreadySetup: function() {
|
||
|
return ($module.is('select') && $module.parent(selector.dropdown).data(moduleNamespace) !== undefined && $module.prev().length === 0);
|
||
|
},
|
||
|
animating: function($subMenu) {
|
||
|
return ($subMenu)
|
||
|
? $subMenu.transition && $subMenu.transition('is animating')
|
||
|
: $menu.transition && $menu.transition('is animating')
|
||
|
;
|
||
|
},
|
||
|
leftward: function($subMenu) {
|
||
|
var $selectedMenu = $subMenu || $menu;
|
||
|
return $selectedMenu.hasClass(className.leftward);
|
||
|
},
|
||
5 years ago
|
clearable: function() {
|
||
|
return ($module.hasClass(className.clearable) || settings.clearable);
|
||
|
},
|
||
5 years ago
|
disabled: function() {
|
||
|
return $module.hasClass(className.disabled);
|
||
|
},
|
||
|
focused: function() {
|
||
|
return (document.activeElement === $module[0]);
|
||
|
},
|
||
|
focusedOnSearch: function() {
|
||
|
return (document.activeElement === $search[0]);
|
||
|
},
|
||
|
allFiltered: function() {
|
||
|
return( (module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered() );
|
||
|
},
|
||
|
hidden: function($subMenu) {
|
||
|
return !module.is.visible($subMenu);
|
||
|
},
|
||
|
initialLoad: function() {
|
||
|
return initialLoad;
|
||
|
},
|
||
|
inObject: function(needle, object) {
|
||
|
var
|
||
|
found = false
|
||
|
;
|
||
|
$.each(object, function(index, property) {
|
||
|
if(property == needle) {
|
||
|
found = true;
|
||
|
return true;
|
||
|
}
|
||
|
});
|
||
|
return found;
|
||
|
},
|
||
|
multiple: function() {
|
||
|
return $module.hasClass(className.multiple);
|
||
|
},
|
||
|
remote: function() {
|
||
|
return settings.apiSettings && module.can.useAPI();
|
||
|
},
|
||
|
single: function() {
|
||
|
return !module.is.multiple();
|
||
|
},
|
||
|
selectMutation: function(mutations) {
|
||
|
var
|
||
|
selectChanged = false
|
||
|
;
|
||
|
$.each(mutations, function(index, mutation) {
|
||
5 years ago
|
if($(mutation.target).is('select') || $(mutation.addedNodes).is('select')) {
|
||
5 years ago
|
selectChanged = true;
|
||
5 years ago
|
return false;
|
||
5 years ago
|
}
|
||
|
});
|
||
|
return selectChanged;
|
||
|
},
|
||
|
search: function() {
|
||
|
return $module.hasClass(className.search);
|
||
|
},
|
||
|
searchSelection: function() {
|
||
|
return ( module.has.search() && $search.parent(selector.dropdown).length === 1 );
|
||
|
},
|
||
|
selection: function() {
|
||
|
return $module.hasClass(className.selection);
|
||
|
},
|
||
|
userValue: function(value) {
|
||
|
return ($.inArray(value, module.get.userValues()) !== -1);
|
||
|
},
|
||
|
upward: function($menu) {
|
||
|
var $element = $menu || $module;
|
||
|
return $element.hasClass(className.upward);
|
||
|
},
|
||
|
visible: function($subMenu) {
|
||
|
return ($subMenu)
|
||
|
? $subMenu.hasClass(className.visible)
|
||
|
: $menu.hasClass(className.visible)
|
||
|
;
|
||
|
},
|
||
|
verticallyScrollableContext: function() {
|
||
|
var
|
||
|
overflowY = ($context.get(0) !== window)
|
||
|
? $context.css('overflow-y')
|
||
|
: false
|
||
|
;
|
||
|
return (overflowY == 'auto' || overflowY == 'scroll');
|
||
|
},
|
||
|
horizontallyScrollableContext: function() {
|
||
|
var
|
||
|
overflowX = ($context.get(0) !== window)
|
||
|
? $context.css('overflow-X')
|
||
|
: false
|
||
|
;
|
||
|
return (overflowX == 'auto' || overflowX == 'scroll');
|
||
|
}
|
||
|
},
|
||
|
|
||
|
can: {
|
||
|
activate: function($item) {
|
||
|
if(settings.useLabels) {
|
||
|
return true;
|
||
|
}
|
||
|
if(!module.has.maxSelections()) {
|
||
|
return true;
|
||
|
}
|
||
|
if(module.has.maxSelections() && $item.hasClass(className.active)) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
},
|
||
|
openDownward: function($subMenu) {
|
||
|
var
|
||
|
$currentMenu = $subMenu || $menu,
|
||
|
canOpenDownward = true,
|
||
|
onScreen = {},
|
||
|
calculations
|
||
|
;
|
||
|
$currentMenu
|
||
|
.addClass(className.loading)
|
||
|
;
|
||
|
calculations = {
|
||
|
context: {
|
||
|
offset : ($context.get(0) === window)
|
||
|
? { top: 0, left: 0}
|
||
|
: $context.offset(),
|
||
|
scrollTop : $context.scrollTop(),
|
||
|
height : $context.outerHeight()
|
||
|
},
|
||
|
menu : {
|
||
|
offset: $currentMenu.offset(),
|
||
|
height: $currentMenu.outerHeight()
|
||
|
}
|
||
|
};
|
||
|
if(module.is.verticallyScrollableContext()) {
|
||
|
calculations.menu.offset.top += calculations.context.scrollTop;
|
||
|
}
|
||
|
onScreen = {
|
||
|
above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.context.offset.top - calculations.menu.height,
|
||
|
below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top - calculations.context.offset.top + calculations.menu.height
|
||
|
};
|
||
|
if(onScreen.below) {
|
||
|
module.verbose('Dropdown can fit in context downward', onScreen);
|
||
|
canOpenDownward = true;
|
||
|
}
|
||
|
else if(!onScreen.below && !onScreen.above) {
|
||
|
module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen);
|
||
|
canOpenDownward = true;
|
||
|
}
|
||
|
else {
|
||
|
module.verbose('Dropdown cannot fit below, opening upward', onScreen);
|
||
|
canOpenDownward = false;
|
||
|
}
|
||
|
$currentMenu.removeClass(className.loading);
|
||
|
return canOpenDownward;
|
||
|
},
|
||
|
openRightward: function($subMenu) {
|
||
|
var
|
||
|
$currentMenu = $subMenu || $menu,
|
||
|
canOpenRightward = true,
|
||
|
isOffscreenRight = false,
|
||
|
calculations
|
||
|
;
|
||
|
$currentMenu
|
||
|
.addClass(className.loading)
|
||
|
;
|
||
|
calculations = {
|
||
|
context: {
|
||
|
offset : ($context.get(0) === window)
|
||
|
? { top: 0, left: 0}
|
||
|
: $context.offset(),
|
||
|
scrollLeft : $context.scrollLeft(),
|
||
|
width : $context.outerWidth()
|
||
|
},
|
||
|
menu: {
|
||
|
offset : $currentMenu.offset(),
|
||
|
width : $currentMenu.outerWidth()
|
||
|
}
|
||
|
};
|
||
|
if(module.is.horizontallyScrollableContext()) {
|
||
|
calculations.menu.offset.left += calculations.context.scrollLeft;
|
||
|
}
|
||
|
isOffscreenRight = (calculations.menu.offset.left - calculations.context.offset.left + calculations.menu.width >= calculations.context.scrollLeft + calculations.context.width);
|
||
|
if(isOffscreenRight) {
|
||
|
module.verbose('Dropdown cannot fit in context rightward', isOffscreenRight);
|
||
|
canOpenRightward = false;
|
||
|
}
|
||
|
$currentMenu.removeClass(className.loading);
|
||
|
return canOpenRightward;
|
||
|
},
|
||
|
click: function() {
|
||
|
return (hasTouch || settings.on == 'click');
|
||
|
},
|
||
|
extendSelect: function() {
|
||
|
return settings.allowAdditions || settings.apiSettings;
|
||
|
},
|
||
|
show: function() {
|
||
|
return !module.is.disabled() && (module.has.items() || module.has.message());
|
||
|
},
|
||
|
useAPI: function() {
|
||
|
return $.fn.api !== undefined;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
animate: {
|
||
|
show: function(callback, $subMenu) {
|
||
|
var
|
||
|
$currentMenu = $subMenu || $menu,
|
||
|
start = ($subMenu)
|
||
|
? function() {}
|
||
|
: function() {
|
||
|
module.hideSubMenus();
|
||
|
module.hideOthers();
|
||
|
module.set.active();
|
||
|
},
|
||
|
transition
|
||
|
;
|
||
|
callback = $.isFunction(callback)
|
||
|
? callback
|
||
|
: function(){}
|
||
|
;
|
||
|
module.verbose('Doing menu show animation', $currentMenu);
|
||
|
module.set.direction($subMenu);
|
||
|
transition = module.get.transition($subMenu);
|
||
|
if( module.is.selection() ) {
|
||
|
module.set.scrollPosition(module.get.selectedItem(), true);
|
||
|
}
|
||
|
if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) {
|
||
|
if(transition == 'none') {
|
||
|
start();
|
||
|
$currentMenu.transition('show');
|
||
|
callback.call(element);
|
||
|
}
|
||
|
else if($.fn.transition !== undefined && $module.transition('is supported')) {
|
||
|
$currentMenu
|
||
|
.transition({
|
||
|
animation : transition + ' in',
|
||
|
debug : settings.debug,
|
||
|
verbose : settings.verbose,
|
||
|
duration : settings.duration,
|
||
|
queue : true,
|
||
|
onStart : start,
|
||
|
onComplete : function() {
|
||
|
callback.call(element);
|
||
|
}
|
||
|
})
|
||
|
;
|
||
|
}
|
||
|
else {
|
||
|
module.error(error.noTransition, transition);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
hide: function(callback, $subMenu) {
|
||
|
var
|
||
|
$currentMenu = $subMenu || $menu,
|
||
|
start = ($subMenu)
|
||
|
? function() {}
|
||
|
: function() {
|
||
|
if( module.can.click() ) {
|
||
|
module.unbind.intent();
|
||
|
}
|
||
|
module.remove.active();
|
||
|
},
|
||
|
transition = module.get.transition($subMenu)
|
||
|
;
|
||
|
callback = $.isFunction(callback)
|
||
|
? callback
|
||
|
: function(){}
|
||
|
;
|
||
|
if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) {
|
||
|
module.verbose('Doing menu hide animation', $currentMenu);
|
||
|
|
||
|
if(transition == 'none') {
|
||
|
start();
|
||
|
$currentMenu.transition('hide');
|
||
|
callback.call(element);
|
||
|
}
|
||
|
else if($.fn.transition !== undefined && $module.transition('is supported')) {
|
||
|
$currentMenu
|
||
|
.transition({
|
||
|
animation : transition + ' out',
|
||
|
duration : settings.duration,
|
||
|
debug : settings.debug,
|
||
|
verbose : settings.verbose,
|
||
|
queue : false,
|
||
|
onStart : start,
|
||
|
onComplete : function() {
|
||
|
callback.call(element);
|
||
|
}
|
||
|
})
|
||
|
;
|
||
|
}
|
||
|
else {
|
||
|
module.error(error.transition);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
hideAndClear: function() {
|
||
|
module.remove.searchTerm();
|
||
|
if( module.has.maxSelections() ) {
|
||
|
return;
|
||
|
}
|
||
|
if(module.has.search()) {
|
||
|
module.hide(function() {
|
||
|
module.remove.filteredItem();
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
module.hide();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
delay: {
|
||
|
show: function() {
|
||
|
module.verbose('Delaying show event to ensure user intent');
|
||
|
clearTimeout(module.timer);
|
||
|
module.timer = setTimeout(module.show, settings.delay.show);
|
||
|
},
|
||
|
hide: function() {
|
||
|
module.verbose('Delaying hide event to ensure user intent');
|
||
|
clearTimeout(module.timer);
|
||
|
module.timer = setTimeout(module.hide, settings.delay.hide);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
escape: {
|
||
|
value: function(value) {
|
||
|
var
|
||
5 years ago
|
multipleValues = Array.isArray(value),
|
||
5 years ago
|
stringValue = (typeof value === 'string'),
|
||
|
isUnparsable = (!stringValue && !multipleValues),
|
||
|
hasQuotes = (stringValue && value.search(regExp.quote) !== -1),
|
||
|
values = []
|
||
|
;
|
||
|
if(isUnparsable || !hasQuotes) {
|
||
|
return value;
|
||
|
}
|
||
|
module.debug('Encoding quote values for use in select', value);
|
||
|
if(multipleValues) {
|
||
|
$.each(value, function(index, value){
|
||
|
values.push(value.replace(regExp.quote, '"'));
|
||
|
});
|
||
|
return values;
|
||
|
}
|
||
|
return value.replace(regExp.quote, '"');
|
||
|
},
|
||
|
string: function(text) {
|
||
|
text = String(text);
|
||
|
return text.replace(regExp.escape, '\\$&');
|
||
5 years ago
|
},
|
||
|
htmlEntities: function(string) {
|
||
|
var
|
||
5 years ago
|
badChars = /[<>"'`]/g,
|
||
5 years ago
|
shouldEscape = /[&<>"'`]/,
|
||
|
escape = {
|
||
|
"<": "<",
|
||
|
">": ">",
|
||
|
'"': """,
|
||
|
"'": "'",
|
||
|
"`": "`"
|
||
|
},
|
||
|
escapedChar = function(chr) {
|
||
|
return escape[chr];
|
||
|
}
|
||
|
;
|
||
|
if(shouldEscape.test(string)) {
|
||
5 years ago
|
string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&");
|
||
5 years ago
|
return string.replace(badChars, escapedChar);
|
||
|
}
|
||
|
return string;
|
||
5 years ago
|
}
|
||
|
},
|
||
|
|
||
|
setting: function(name, value) {
|
||
|
module.debug('Changing setting', name, value);
|
||
|
if( $.isPlainObject(name) ) {
|
||
|
$.extend(true, settings, name);
|
||
|
}
|
||
|
else if(value !== undefined) {
|
||
|
if($.isPlainObject(settings[name])) {
|
||
|
$.extend(true, settings[name], value);
|
||
|
}
|
||
|
else {
|
||
|
settings[name] = value;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
return settings[name];
|
||
|
}
|
||
|
},
|
||
|
internal: function(name, value) {
|
||
|
if( $.isPlainObject(name) ) {
|
||
|
$.extend(true, module, name);
|
||
|
}
|
||
|
else if(value !== undefined) {
|
||
|
module[name] = value;
|
||
|
}
|
||
|
else {
|
||
|
return module[name];
|
||
|
}
|
||
|
},
|
||
|
debug: function() {
|
||
|
if(!settings.silent && settings.debug) {
|
||
|
if(settings.performance) {
|
||
|
module.performance.log(arguments);
|
||
|
}
|
||
|
else {
|
||
|
module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
||
|
module.debug.apply(console, arguments);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
verbose: function() {
|
||
|
if(!settings.silent && settings.verbose && settings.debug) {
|
||
|
if(settings.performance) {
|
||
|
module.performance.log(arguments);
|
||
|
}
|
||
|
else {
|
||
|
module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
||
|
module.verbose.apply(console, arguments);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
error: function() {
|
||
|
if(!settings.silent) {
|
||
|
module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
|
||
|
module.error.apply(console, arguments);
|
||
|
}
|
||
|
},
|
||
|
performance: {
|
||
|
log: function(message) {
|
||
|
var
|
||
|
currentTime,
|
||
|
executionTime,
|
||
|
previousTime
|
||
|
;
|
||
|
if(settings.performance) {
|
||
|
currentTime = new Date().getTime();
|
||
|
previousTime = time || currentTime;
|
||
|
executionTime = currentTime - previousTime;
|
||
|
time = currentTime;
|
||
|
performance.push({
|
||
|
'Name' : message[0],
|
||
|
'Arguments' : [].slice.call(message, 1) || '',
|
||
|
'Element' : element,
|
||
|
'Execution Time' : executionTime
|
||
|
});
|
||
|
}
|
||
|
clearTimeout(module.performance.timer);
|
||
|
module.performance.timer = setTimeout(module.performance.display, 500);
|
||
|
},
|
||
|
display: function() {
|
||
|
var
|
||
|
title = settings.name + ':',
|
||
|
totalTime = 0
|
||
|
;
|
||
|
time = false;
|
||
|
clearTimeout(module.performance.timer);
|
||
|
$.each(performance, function(index, data) {
|
||
|
totalTime += data['Execution Time'];
|
||
|
});
|
||
|
title += ' ' + totalTime + 'ms';
|
||
|
if(moduleSelector) {
|
||
|
title += ' \'' + moduleSelector + '\'';
|
||
|
}
|
||
|
if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
|
||
|
console.groupCollapsed(title);
|
||
|
if(console.table) {
|
||
|
console.table(performance);
|
||
|
}
|
||
|
else {
|
||
|
$.each(performance, function(index, data) {
|
||
|
console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
|
||
|
});
|
||
|
}
|
||
|
console.groupEnd();
|
||
|
}
|
||
|
performance = [];
|
||
|
}
|
||
|
},
|
||
|
invoke: function(query, passedArguments, context) {
|
||
|
var
|
||
|
object = instance,
|
||
|
maxDepth,
|
||
|
found,
|
||
|
response
|
||
|
;
|
||
|
passedArguments = passedArguments || queryArguments;
|
||
|
context = element || context;
|
||
|
if(typeof query == 'string' && object !== undefined) {
|
||
|
query = query.split(/[\. ]/);
|
||
|
maxDepth = query.length - 1;
|
||
|
$.each(query, function(depth, value) {
|
||
|
var camelCaseValue = (depth != maxDepth)
|
||
|
? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
|
||
|
: query
|
||
|
;
|
||
|
if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
|
||
|
object = object[camelCaseValue];
|
||
|
}
|
||
|
else if( object[camelCaseValue] !== undefined ) {
|
||
|
found = object[camelCaseValue];
|
||
|
return false;
|
||
|
}
|
||
|
else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
|
||
|
object = object[value];
|
||
|
}
|
||
|
else if( object[value] !== undefined ) {
|
||
|
found = object[value];
|
||
|
return false;
|
||
|
}
|
||
|
else {
|
||
|
module.error(error.method, query);
|
||
|
return false;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
if ( $.isFunction( found ) ) {
|
||
|
response = found.apply(context, passedArguments);
|
||
|
}
|
||
|
else if(found !== undefined) {
|
||
|
response = found;
|
||
|
}
|
||
5 years ago
|
if(Array.isArray(returnedValue)) {
|
||
5 years ago
|
returnedValue.push(response);
|
||
|
}
|
||
|
else if(returnedValue !== undefined) {
|
||
|
returnedValue = [returnedValue, response];
|
||
|
}
|
||
|
else if(response !== undefined) {
|
||
|
returnedValue = response;
|
||
|
}
|
||
|
return found;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if(methodInvoked) {
|
||
|
if(instance === undefined) {
|
||
|
module.initialize();
|
||
|
}
|
||
|
module.invoke(query);
|
||
|
}
|
||
|
else {
|
||
|
if(instance !== undefined) {
|
||
|
instance.invoke('destroy');
|
||
|
}
|
||
|
module.initialize();
|
||
|
}
|
||
|
})
|
||
|
;
|
||
|
return (returnedValue !== undefined)
|
||
|
? returnedValue
|
||
|
: $allModules
|
||
|
;
|
||
|
};
|
||
|
|
||
|
$.fn.dropdown.settings = {
|
||
|
|
||
|
silent : false,
|
||
|
debug : false,
|
||
|
verbose : false,
|
||
|
performance : true,
|
||
|
|
||
|
on : 'click', // what event should show menu action on item selection
|
||
|
action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){})
|
||
|
|
||
|
values : false, // specify values to use for dropdown
|
||
|
|
||
5 years ago
|
clearable : false, // whether the value of the dropdown can be cleared
|
||
|
|
||
5 years ago
|
apiSettings : false,
|
||
|
selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used
|
||
|
minCharacters : 0, // Minimum characters required to trigger API call
|
||
|
|
||
|
filterRemoteData : false, // Whether API results should be filtered after being returned for query term
|
||
|
saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh
|
||
|
|
||
|
throttle : 200, // How long to wait after last user input to search remotely
|
||
|
|
||
|
context : window, // Context to use when determining if on screen
|
||
|
direction : 'auto', // Whether dropdown should always open in one direction
|
||
|
keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing
|
||
|
|
||
|
match : 'both', // what to match against with search selection (both, text, or label)
|
||
|
fullTextSearch : false, // search anywhere in value (set to 'exact' to require exact matches)
|
||
5 years ago
|
ignoreDiacritics : false, // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...)
|
||
|
hideDividers : false, // Whether to hide any divider elements (specified in selector.divider) that are sibling to any items when searched (set to true will hide all dividers, set to 'empty' will hide them when they are not followed by a visible item)
|
||
5 years ago
|
|
||
|
placeholder : 'auto', // whether to convert blank <select> values to placeholder text
|
||
|
preserveHTML : true, // preserve html when selecting value
|
||
|
sortSelect : false, // sort selection on init
|
||
|
|
||
|
forceSelection : true, // force a choice on blur with search selection
|
||
|
|
||
|
allowAdditions : false, // whether multiple select should allow user added values
|
||
5 years ago
|
ignoreCase : false, // whether to consider case sensitivity when creating labels
|
||
|
ignoreSearchCase : true, // whether to consider case sensitivity when filtering items
|
||
5 years ago
|
hideAdditions : true, // whether or not to hide special message prompting a user they can enter a value
|
||
|
|
||
|
maxSelections : false, // When set to a number limits the number of selections to this count
|
||
|
useLabels : true, // whether multiple select should filter currently active selections from choices
|
||
|
delimiter : ',', // when multiselect uses normal <input> the values will be delimited with this character
|
||
|
|
||
|
showOnFocus : true, // show menu on focus
|
||
|
allowReselection : false, // whether current value should trigger callbacks when reselected
|
||
|
allowTab : true, // add tabindex to element
|
||
|
allowCategorySelection : false, // allow elements with sub-menus to be selected
|
||
|
|
||
|
fireOnInit : false, // Whether callbacks should fire when initializing dropdown values
|
||
|
|
||
|
transition : 'auto', // auto transition will slide down or up based on direction
|
||
|
duration : 200, // duration of transition
|
||
|
|
||
|
glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width
|
||
|
|
||
5 years ago
|
headerDivider : true, // whether option headers should have an additional divider line underneath when converted from <select> <optgroup>
|
||
|
|
||
5 years ago
|
// label settings on multi-select
|
||
|
label: {
|
||
|
transition : 'scale',
|
||
|
duration : 200,
|
||
|
variation : false
|
||
|
},
|
||
|
|
||
|
// delay before event
|
||
|
delay : {
|
||
|
hide : 300,
|
||
|
show : 200,
|
||
|
search : 20,
|
||
|
touch : 50
|
||
|
},
|
||
|
|
||
|
/* Callbacks */
|
||
|
onChange : function(value, text, $selected){},
|
||
|
onAdd : function(value, text, $selected){},
|
||
|
onRemove : function(value, text, $selected){},
|
||
|
|
||
|
onLabelSelect : function($selectedLabels){},
|
||
|
onLabelCreate : function(value, text) { return $(this); },
|
||
|
onLabelRemove : function(value) { return true; },
|
||
|
onNoResults : function(searchTerm) { return true; },
|
||
|
onShow : function(){},
|
||
|
onHide : function(){},
|
||
|
|
||
|
/* Component */
|
||
|
name : 'Dropdown',
|
||
|
namespace : 'dropdown',
|
||
|
|
||
|
message: {
|
||
|
addResult : 'Add <b>{term}</b>',
|
||
|
count : '{count} selected',
|
||
|
maxSelections : 'Max {maxCount} selections',
|
||
|
noResults : 'No results found.',
|
||
|
serverError : 'There was an error contacting the server'
|
||
|
},
|
||
|
|
||
|
error : {
|
||
|
action : 'You called a dropdown action that was not defined',
|
||
|
alreadySetup : 'Once a select has been initialized behaviors must be called on the created ui dropdown',
|
||
|
labels : 'Allowing user additions currently requires the use of labels.',
|
||
|
missingMultiple : '<select> requires multiple property to be set to correctly preserve multiple values',
|
||
|
method : 'The method you called is not defined.',
|
||
|
noAPI : 'The API module is required to load resources remotely',
|
||
|
noStorage : 'Saving remote data requires session storage',
|
||
5 years ago
|
noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>',
|
||
|
noNormalize : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including <https://cdn.jsdelivr.net/npm/unorm@1.4.1/lib/unorm.min.js> as a polyfill.'
|
||
5 years ago
|
},
|
||
|
|
||
|
regExp : {
|
||
5 years ago
|
escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g,
|
||
5 years ago
|
quote : /"/g
|
||
|
},
|
||
|
|
||
|
metadata : {
|
||
|
defaultText : 'defaultText',
|
||
|
defaultValue : 'defaultValue',
|
||
|
placeholderText : 'placeholder',
|
||
|
text : 'text',
|
||
|
value : 'value'
|
||
|
},
|
||
|
|
||
|
// property names for remote query
|
||
|
fields: {
|
||
|
remoteValues : 'results', // grouping for api results
|
||
|
values : 'values', // grouping for all dropdown values
|
||
|
disabled : 'disabled', // whether value should be disabled
|
||
|
name : 'name', // displayed dropdown text
|
||
|
value : 'value', // actual dropdown value
|
||
5 years ago
|
text : 'text', // displayed text when selected
|
||
|
type : 'type', // type of dropdown element
|
||
|
image : 'image', // optional image path
|
||
|
imageClass : 'imageClass', // optional individual class for image
|
||
|
icon : 'icon', // optional icon name
|
||
|
iconClass : 'iconClass', // optional individual class for icon (for example to use flag instead)
|
||
|
class : 'class', // optional individual class for item/header
|
||
|
divider : 'divider' // optional divider append for group headers
|
||
5 years ago
|
},
|
||
|
|
||
|
keys : {
|
||
|
backspace : 8,
|
||
|
delimiter : 188, // comma
|
||
|
deleteKey : 46,
|
||
|
enter : 13,
|
||
|
escape : 27,
|
||
|
pageUp : 33,
|
||
|
pageDown : 34,
|
||
|
leftArrow : 37,
|
||
|
upArrow : 38,
|
||
|
rightArrow : 39,
|
||
|
downArrow : 40
|
||
|
},
|
||
|
|
||
|
selector : {
|
||
|
addition : '.addition',
|
||
5 years ago
|
divider : '.divider, .header',
|
||
5 years ago
|
dropdown : '.ui.dropdown',
|
||
|
hidden : '.hidden',
|
||
|
icon : '> .dropdown.icon',
|
||
|
input : '> input[type="hidden"], > select',
|
||
|
item : '.item',
|
||
|
label : '> .label',
|
||
|
remove : '> .label > .delete.icon',
|
||
|
siblingLabel : '.label',
|
||
|
menu : '.menu',
|
||
|
message : '.message',
|
||
|
menuIcon : '.dropdown.icon',
|
||
|
search : 'input.search, .menu > .search > input, .menu input.search',
|
||
|
sizer : '> input.sizer',
|
||
|
text : '> .text:not(.icon)',
|
||
5 years ago
|
unselectable : '.disabled, .filtered',
|
||
|
clearIcon : '> .remove.icon'
|
||
5 years ago
|
},
|
||
|
|
||
|
className : {
|
||
|
active : 'active',
|
||
|
addition : 'addition',
|
||
|
animating : 'animating',
|
||
|
disabled : 'disabled',
|
||
|
empty : 'empty',
|
||
|
dropdown : 'ui dropdown',
|
||
|
filtered : 'filtered',
|
||
|
hidden : 'hidden transition',
|
||
5 years ago
|
icon : 'icon',
|
||
|
image : 'image',
|
||
5 years ago
|
item : 'item',
|
||
|
label : 'ui label',
|
||
|
loading : 'loading',
|
||
|
menu : 'menu',
|
||
|
message : 'message',
|
||
|
multiple : 'multiple',
|
||
|
placeholder : 'default',
|
||
|
sizer : 'sizer',
|
||
|
search : 'search',
|
||
|
selected : 'selected',
|
||
|
selection : 'selection',
|
||
|
upward : 'upward',
|
||
|
leftward : 'left',
|
||
5 years ago
|
visible : 'visible',
|
||
|
clearable : 'clearable',
|
||
|
noselection : 'noselection',
|
||
|
delete : 'delete',
|
||
|
header : 'header',
|
||
|
divider : 'divider',
|
||
5 years ago
|
groupIcon : '',
|
||
|
unfilterable : 'unfilterable'
|
||
5 years ago
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
/* Templates */
|
||
|
$.fn.dropdown.settings.templates = {
|
||
5 years ago
|
deQuote: function(string) {
|
||
|
return String(string).replace(/"/g,"");
|
||
|
},
|
||
|
escape: function(string, preserveHTML) {
|
||
|
if (preserveHTML){
|
||
|
return string;
|
||
|
}
|
||
|
var
|
||
5 years ago
|
badChars = /[<>"'`]/g,
|
||
5 years ago
|
shouldEscape = /[&<>"'`]/,
|
||
|
escape = {
|
||
|
"<": "<",
|
||
|
">": ">",
|
||
|
'"': """,
|
||
|
"'": "'",
|
||
|
"`": "`"
|
||
|
},
|
||
|
escapedChar = function(chr) {
|
||
|
return escape[chr];
|
||
|
}
|
||
|
;
|
||
|
if(shouldEscape.test(string)) {
|
||
5 years ago
|
string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&");
|
||
5 years ago
|
return string.replace(badChars, escapedChar);
|
||
|
}
|
||
|
return string;
|
||
|
},
|
||
5 years ago
|
// generates dropdown from select values
|
||
5 years ago
|
dropdown: function(select, fields, preserveHTML, className) {
|
||
5 years ago
|
var
|
||
|
placeholder = select.placeholder || false,
|
||
5 years ago
|
html = '',
|
||
|
escape = $.fn.dropdown.settings.templates.escape
|
||
5 years ago
|
;
|
||
|
html += '<i class="dropdown icon"></i>';
|
||
5 years ago
|
if(placeholder) {
|
||
|
html += '<div class="default text">' + escape(placeholder,preserveHTML) + '</div>';
|
||
5 years ago
|
}
|
||
|
else {
|
||
|
html += '<div class="text"></div>';
|
||
|
}
|
||
5 years ago
|
html += '<div class="'+className.menu+'">';
|
||
|
html += $.fn.dropdown.settings.templates.menu(select, fields, preserveHTML,className);
|
||
5 years ago
|
html += '</div>';
|
||
|
return html;
|
||
|
},
|
||
|
|
||
|
// generates just menu from select
|
||
5 years ago
|
menu: function(response, fields, preserveHTML, className) {
|
||
5 years ago
|
var
|
||
5 years ago
|
values = response[fields.values] || [],
|
||
|
html = '',
|
||
|
escape = $.fn.dropdown.settings.templates.escape,
|
||
|
deQuote = $.fn.dropdown.settings.templates.deQuote
|
||
5 years ago
|
;
|
||
|
$.each(values, function(index, option) {
|
||
|
var
|
||
5 years ago
|
itemType = (option[fields.type])
|
||
|
? option[fields.type]
|
||
|
: 'item'
|
||
5 years ago
|
;
|
||
5 years ago
|
|
||
|
if( itemType === 'item' ) {
|
||
|
var
|
||
|
maybeText = (option[fields.text])
|
||
|
? ' data-text="' + deQuote(option[fields.text]) + '"'
|
||
|
: '',
|
||
|
maybeDisabled = (option[fields.disabled])
|
||
|
? className.disabled+' '
|
||
|
: ''
|
||
|
;
|
||
|
html += '<div class="'+ maybeDisabled + (option[fields.class] ? deQuote(option[fields.class]) : className.item)+'" data-value="' + deQuote(option[fields.value]) + '"' + maybeText + '>';
|
||
|
if(option[fields.image]) {
|
||
|
html += '<img class="'+(option[fields.imageClass] ? deQuote(option[fields.imageClass]) : className.image)+'" src="' + deQuote(option[fields.image]) + '">';
|
||
|
}
|
||
|
if(option[fields.icon]) {
|
||
|
html += '<i class="'+deQuote(option[fields.icon])+' '+(option[fields.iconClass] ? deQuote(option[fields.iconClass]) : className.icon)+'"></i>';
|
||
|
}
|
||
5 years ago
|
html += escape(option[fields.name] || '', preserveHTML);
|
||
5 years ago
|
html += '</div>';
|
||
|
} else if (itemType === 'header') {
|
||
5 years ago
|
var groupName = escape(option[fields.name] || '', preserveHTML),
|
||
5 years ago
|
groupIcon = option[fields.icon] ? deQuote(option[fields.icon]) : className.groupIcon
|
||
|
;
|
||
|
if(groupName !== '' || groupIcon !== '') {
|
||
|
html += '<div class="' + (option[fields.class] ? deQuote(option[fields.class]) : className.header) + '">';
|
||
|
if (groupIcon !== '') {
|
||
|
html += '<i class="' + groupIcon + ' ' + (option[fields.iconClass] ? deQuote(option[fields.iconClass]) : className.icon) + '"></i>';
|
||
|
}
|
||
|
html += groupName;
|
||
|
html += '</div>';
|
||
|
}
|
||
|
if(option[fields.divider]){
|
||
|
html += '<div class="'+className.divider+'"></div>';
|
||
|
}
|
||
|
}
|
||
5 years ago
|
});
|
||
|
return html;
|
||
|
},
|
||
|
|
||
|
// generates label for multiselect
|
||
5 years ago
|
label: function(value, text, preserveHTML, className) {
|
||
|
var
|
||
|
escape = $.fn.dropdown.settings.templates.escape;
|
||
|
return escape(text,preserveHTML) + '<i class="'+className.delete+' icon"></i>';
|
||
5 years ago
|
},
|
||
|
|
||
|
|
||
|
// generates messages like "No results"
|
||
|
message: function(message) {
|
||
|
return message;
|
||
|
},
|
||
|
|
||
|
// generates user addition to selection menu
|
||
|
addition: function(choice) {
|
||
|
return choice;
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
})( jQuery, window, document );
|