assets/javascripts/semantic_ui/definitions/modules/dropdown.js in less-rails-semantic_ui-2.1.8.2 vs assets/javascripts/semantic_ui/definitions/modules/dropdown.js in less-rails-semantic_ui-2.2.1.0
- old
+ new
@@ -1,20 +1,26 @@
/*!
* # Semantic UI - Dropdown
* http://github.com/semantic-org/semantic-ui/
*
*
- * Copyright 2015 Contributors
* Released under the MIT license
* http://opensource.org/licenses/MIT
*
*/
-;(function ( $, window, document, undefined ) {
+;(function ($, window, document, undefined) {
"use strict";
+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),
@@ -53,10 +59,11 @@
$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),
$combo = ($module.prev().find(selector.text).length > 0)
? $module.prev().find(selector.text)
@@ -71,10 +78,11 @@
element = this,
instance = $module.data(moduleNamespace),
initialLoad,
pageLostFocus,
+ willRefocus,
elementNamespace,
id,
selectObserver,
menuObserver,
module
@@ -123,41 +131,52 @@
.off(eventNamespace)
;
$document
.off(elementNamespace)
;
- if(selectObserver) {
- selectObserver.disconnect();
- }
- if(menuObserver) {
- menuObserver.disconnect();
- }
+ module.disconnect.menuObserver();
+ module.disconnect.selectObserver();
},
observeChanges: function() {
if('MutationObserver' in window) {
- selectObserver = new MutationObserver(function(mutations) {
- module.debug('<select> modified, recreating menu');
- module.setup.select();
- });
- menuObserver = new MutationObserver(function(mutations) {
- module.debug('Menu modified, updating selector cache');
- module.refresh();
- });
+ 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(menuObserver) {
+ menuObserver.disconnect();
+ }
+ }
+ },
+ observe: {
+ select: function() {
if(module.has.input()) {
selectObserver.observe($input[0], {
childList : true,
subtree : true
});
}
+ },
+ menu: function() {
if(module.has.menu()) {
menuObserver.observe($menu[0], {
childList : true,
subtree : true
});
}
- module.debug('Setting up mutation observer', selectObserver, menuObserver);
}
},
create: {
id: function() {
@@ -188,10 +207,13 @@
.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);
@@ -214,28 +236,40 @@
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);
- module.filter(query);
+ if(module.has.minCharacters(query)) {
+ module.filter(query);
+ }
+ else {
+ module.hide();
+ }
},
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) {
@@ -258,11 +292,15 @@
setup: {
api: function() {
var
apiSettings = {
- debug : settings.debug,
+ debug : settings.debug,
+ urlData : {
+ value : module.get.value(),
+ query : module.get.query()
+ },
on : false
}
;
module.verbose('First request, initializing API');
$module
@@ -283,10 +321,13 @@
.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();
}
},
select: function() {
@@ -322,12 +363,12 @@
}
if($input.is('[multiple]')) {
module.set.multiple();
}
if ($input.prop('disabled')) {
- module.debug('Disabling dropdown')
- $module.addClass(className.disabled)
+ module.debug('Disabling dropdown');
+ $module.addClass(className.disabled);
}
$input
.removeAttr('class')
.detach()
.prependTo($module)
@@ -364,10 +405,14 @@
refresh: function() {
module.refreshSelectors();
module.refreshData();
},
+ refreshItems: function() {
+ $item = $menu.find(selector.item);
+ },
+
refreshSelectors: function() {
module.verbose('Refreshing selector cache');
$text = $module.find(selector.text);
$search = $module.find(selector.search);
$input = $module.find(selector.input);
@@ -384,10 +429,18 @@
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)
;
@@ -408,21 +461,24 @@
? callback
: function(){}
;
if( module.can.show() && !module.is.active() ) {
module.debug('Showing dropdown');
- if(module.is.multiple() && !module.has.search() && module.is.allFiltered()) {
- return true;
- }
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.animate.show(function() {
if( module.can.click() ) {
module.bind.intent();
}
+ if(module.has.menuSearch()) {
+ module.focusSearch();
+ }
module.set.visible();
callback.call(element);
});
}
}
@@ -521,15 +577,17 @@
.on('click' + eventNamespace, selector.remove, module.event.remove.click)
;
}
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)
.on('click' + eventNamespace, selector.icon, module.event.icon.click)
- .on('click' + eventNamespace, selector.search, module.show)
.on('focus' + eventNamespace, selector.search, module.event.search.focus)
+ .on('click' + eventNamespace, selector.search, module.event.search.focus)
.on('blur' + eventNamespace, selector.search, module.event.search.blur)
.on('click' + eventNamespace, selector.text, module.event.text.focus)
;
if(module.is.multiple()) {
$module
@@ -607,21 +665,29 @@
module.filterActive();
}
module.select.firstUnfiltered();
if( module.has.allResultsFiltered() ) {
if( settings.onNoResults.call(element, searchTerm) ) {
- if(!settings.allowAdditions) {
+ 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) {
module.add.userSuggestion(query);
}
@@ -650,13 +716,14 @@
},
queryRemote: function(query, callback) {
var
apiSettings = {
- errorDuration : false,
- throttle : settings.throttle,
- urlData : {
+ errorDuration : false,
+ cache : 'local',
+ throttle : settings.throttle,
+ urlData : {
query: query
},
onError: function() {
module.add.message(message.serverError);
callback();
@@ -709,14 +776,18 @@
text = String(module.get.choiceText($choice, false));
if(text.search(beginsWithRegExp) !== -1) {
results.push(this);
return true;
}
- else if(settings.fullTextSearch && module.fuzzySearch(searchTerm, text)) {
+ 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;
+ }
}
if(settings.match == 'both' || settings.match == 'value') {
value = String(module.get.choiceValue($choice, text));
if(value.search(beginsWithRegExp) !== -1) {
@@ -765,45 +836,62 @@
}
return false;
}
return true;
},
-
+ exactSearch: function (query, term) {
+ query = query.toLowerCase();
+ term = term.toLowerCase();
+ if(term.indexOf(query) > -1) {
+ return true;
+ }
+ return false;
+ },
filterActive: function() {
if(settings.useLabels) {
$item.filter('.' + className.active)
.addClass(className.filtered)
;
}
},
- focusSearch: function() {
- if( module.is.search() && !module.is.focusedOnSearch() ) {
- $search[0].focus();
+ 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();
+ }
}
},
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.size() > 0)
+ hasSelected = ($selectedItem.length > 0)
;
- if( module.has.query() ) {
- if(hasSelected) {
- module.debug('Forcing partial selection to selected item', $selectedItem);
- module.event.item.click.call($selectedItem);
- return;
+ if(hasSelected) {
+ module.debug('Forcing partial selection to selected item', $selectedItem);
+ module.event.item.click.call($selectedItem, {}, true);
+ return;
+ }
+ else {
+ if(settings.allowAdditions) {
+ module.set.selected(module.get.query());
+ module.remove.searchTerm();
}
else {
module.remove.searchTerm();
}
}
- module.hide();
},
event: {
change: function() {
if(!internalChange) {
@@ -814,63 +902,71 @@
focus: function() {
if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) {
module.show();
}
},
- click: function(event) {
- var
- $target = $(event.target)
- ;
- // focus search
- if($target.is($module) && !module.is.focusedOnSearch()) {
- module.focusSearch();
- }
- },
blur: function(event) {
pageLostFocus = (document.activeElement === this);
if(!activated && !pageLostFocus) {
module.remove.activeLabel();
module.hide();
}
},
- // prevents focus callback from occurring on mousedown
mousedown: function() {
- activated = true;
+ 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() {
- activated = false;
+ 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: {
focus: function() {
activated = true;
if(module.is.multiple()) {
module.remove.activeLabel();
}
if(settings.showOnFocus) {
module.search();
- module.show();
}
},
blur: function(event) {
pageLostFocus = (document.activeElement === this);
- if(!itemActivated && !pageLostFocus) {
- if(module.is.multiple()) {
- module.remove.activeLabel();
+ if(!willRefocus) {
+ if(!itemActivated && !pageLostFocus) {
+ if(settings.forceSelection && module.has.query()) {
+ module.forceSelection();
+ }
module.hide();
}
- else if(settings.forceSelection) {
- module.forceSelection();
- }
- else {
- module.hide();
- }
}
- else if(pageLostFocus) {
- if(settings.forceSelection) {
- module.forceSelection();
- }
- }
+ willRefocus = false;
}
},
icon: {
click: function(event) {
module.toggle();
@@ -936,10 +1032,13 @@
var
toggleBehavior = (module.is.multiple())
? module.show
: module.toggle
;
+ if(module.is.bubbledLabelClick(event)) {
+ return;
+ }
if( module.determine.eventOnElement(event, toggleBehavior) ) {
event.preventDefault();
}
},
touch: function(event) {
@@ -957,32 +1056,64 @@
},
hide: function(event) {
module.determine.eventInModule(event, module.hide);
}
},
+ select: {
+ mutation: function(mutations) {
+ module.debug('<select> modified, recreating menu');
+ module.setup.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
- $subMenu = $(this).children(selector.menu),
- $otherMenus = $(this).siblings(selector.item).children(selector.menu)
+ $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( $subMenu.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);
+ module.animate.show(false, $subMenu);
}, settings.delay.show);
event.preventDefault();
}
},
mouseleave: function(event) {
@@ -991,17 +1122,15 @@
;
if($subMenu.length > 0) {
clearTimeout(module.itemTimer);
module.itemTimer = setTimeout(function() {
module.verbose('Hiding sub-menu', $subMenu);
- module.animate.hide(false, $subMenu);
+ module.animate.hide(false, $subMenu);
}, settings.delay.hide);
}
},
- touchend: function() {
- },
- click: function (event) {
+ click: function (event, skipRefocus) {
var
$choice = $(this),
$target = (event)
? $(event.target)
: $(''),
@@ -1010,13 +1139,21 @@
value = module.get.choiceValue($choice, text),
hasSubMenu = ($subMenu.length > 0),
isBubbledEvent = ($subMenu.find($target).length > 0)
;
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.remove.searchTerm();
module.set.scrollPosition($choice);
}
module.determine.selectAction.call(this, text, value);
}
}
@@ -1148,23 +1285,33 @@
$activeItem = $menu.children('.' + className.active).eq(0),
$selectedItem = ($currentlySelected.length > 0)
? $currentlySelected
: $activeItem,
$visibleItems = ($selectedItem.length > 0)
- ? $selectedItem.siblings(':not(.' + className.filtered +')').andSelf()
+ ? $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()),
+ $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);
+ module.event.item.click.call($selectedItem, event);
+ if(module.is.searchSelection()) {
+ module.remove.searchTerm();
+ }
+ }
+
// visible menu keyboard shortcuts
if( module.is.visible() ) {
// enter (select or open sub-menu)
if(pressedKey == keys.enter || delimiterPressed) {
@@ -1180,42 +1327,45 @@
}
}
event.preventDefault();
}
- // left arrow (hide sub-menu)
- if(pressedKey == keys.leftArrow) {
+ // sub-menu actions
+ if(hasSelectedItem) {
- isSubMenuItem = ($parentMenu[0] !== $menu[0]);
+ if(pressedKey == keys.leftArrow) {
- 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)
- ;
- event.preventDefault();
+ 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)
+ ;
+ 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)
- ;
- 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)
+ ;
+ event.preventDefault();
+ }
}
}
// up arrow (traverse menu up)
if(pressedKey == keys.upArrow) {
@@ -1235,10 +1385,13 @@
;
$nextItem
.addClass(className.selected)
;
module.set.scrollPosition($nextItem);
+ if(settings.selectOnKeydown && module.is.single()) {
+ module.set.selectedItem($nextItem);
+ }
}
event.preventDefault();
}
// down arrow (traverse menu down)
@@ -1259,10 +1412,14 @@
;
$nextItem
.addClass(className.selected)
;
module.set.scrollPosition($nextItem);
+ if(settings.selectOnKeydown && module.is.single()) {
+ module.set.activeItem($nextItem);
+ module.set.selected(module.get.choiceValue($nextItem), $nextItem);
+ }
}
event.preventDefault();
}
// page down (show next page)
@@ -1286,19 +1443,20 @@
// delimiter key
if(delimiterPressed) {
event.preventDefault();
}
// down arrow (open menu)
- if(pressedKey == keys.downArrow) {
+ if(pressedKey == keys.downArrow && !module.is.visible()) {
module.verbose('Down key pressed, showing dropdown');
+ module.select.firstUnfiltered();
module.show();
event.preventDefault();
}
}
}
else {
- if( module.is.selection() && !module.is.search() ) {
+ if( !module.has.search() ) {
module.set.selectedLetter( String.fromCharCode(pressedKey) );
}
}
}
},
@@ -1320,15 +1478,15 @@
determine: {
selectAction: function(text, value) {
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(this, 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(this, text, value);
+ settings.action.call(element, text, value, this);
}
else {
module.error(error.action, settings.action);
}
},
@@ -1352,20 +1510,21 @@
return false;
}
},
eventOnElement: function(event, callback) {
var
- $target = $(event.target),
- $label = $target.closest(selector.siblingLabel),
- notOnLabel = ($module.find($label).length === 0),
- notInMenu = ($target.closest($menu).length === 0)
+ $target = $(event.target),
+ $label = $target.closest(selector.siblingLabel),
+ inVisibleDOM = document.body.contains(event.target),
+ notOnLabel = ($module.find($label).length === 0),
+ notInMenu = ($target.closest($menu).length === 0)
;
callback = $.isFunction(callback)
? callback
: function(){}
;
- if(notOnLabel && notInMenu) {
+ if(inVisibleDOM && notOnLabel && notInMenu) {
module.verbose('Triggering event', callback);
callback();
return true;
}
else {
@@ -1377,42 +1536,42 @@
action: {
nothing: function() {},
- activate: function(text, value) {
+ activate: function(text, value, element) {
value = (value !== undefined)
? value
: text
;
- if( module.can.activate( $(this) ) ) {
- module.set.selected(value, $(this));
+ if( module.can.activate( $(element) ) ) {
+ module.set.selected(value, $(element));
if(module.is.multiple() && !module.is.allFiltered()) {
return;
}
else {
module.hideAndClear();
}
}
},
- select: function(text, value) {
+ select: function(text, value, element) {
// mimics action.activate but does not select text
- module.action.activate.call(this);
+ module.action.activate.call(element);
},
- combo: function(text, value) {
+ combo: function(text, value, element) {
value = (value !== undefined)
? value
: text
;
- module.set.selected(value, $(this));
+ module.set.selected(value, $(element));
module.hideAndClear();
},
- hide: function(text, value) {
- module.set.value(value);
+ hide: function(text, value, element) {
+ module.set.value(value, text);
module.hideAndClear();
}
},
@@ -1433,12 +1592,18 @@
return $text.text();
},
query: function() {
return $.trim($search.val());
},
- searchWidth: function(characterCount) {
- return (characterCount * settings.glyphWidth) + 'em';
+ 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
@@ -1500,17 +1665,18 @@
},
value: function() {
var
value = ($input.length > 0)
? $input.val()
- : $module.data(metadata.value)
+ : $module.data(metadata.value),
+ isEmptyMultiselect = ($.isArray(value) && value.length === 1 && value[0] === '')
;
// prevents placeholder element from being selected when multiple
- if($.isArray(value) && value.length === 1 && value[0] === '') {
- return '';
- }
- return value;
+ return (value === undefined || isEmptyMultiselect)
+ ? ''
+ : value
+ ;
},
values: function() {
var
value = module.get.value()
;
@@ -1531,20 +1697,21 @@
;
if(values) {
if(typeof values == 'string') {
values = [values];
}
- remoteValues = {};
$.each(values, function(index, value) {
var
name = module.read.remoteData(value)
;
module.verbose('Restoring value from session data', name, value);
- remoteValues[value] = (name)
- ? name
- : value
- ;
+ if(name) {
+ if(!remoteValues) {
+ remoteValues = {};
+ }
+ remoteValues[value] = name;
+ }
});
}
return remoteValues;
},
choiceText: function($choice, preserveHTML) {
@@ -1552,11 +1719,11 @@
? preserveHTML
: settings.preserveHTML
;
if($choice) {
if($choice.find(selector.menu).length > 0) {
- module.verbose('Retreiving text of element with sub-menu');
+ 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)
@@ -1633,11 +1800,11 @@
;
});
module.debug('Retrieved and sorted values from select', select);
}
else {
- module.debug('Retreived values from select', select);
+ module.debug('Retrieved values from select', select);
}
return select;
},
activeItem: function() {
return $item.filter('.' + className.active);
@@ -1774,10 +1941,13 @@
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) {
@@ -1812,19 +1982,14 @@
else {
module.debug('Restoring previously selected values');
}
},
values: function() {
- // prevents callbacks from occuring on initial load
+ // prevents callbacks from occurring on initial load
module.set.initialLoad();
- if(settings.apiSettings) {
- if(settings.saveRemoteData) {
- module.restore.remoteValues();
- }
- else {
- module.clearValue();
- }
+ if(settings.apiSettings && settings.saveRemoteData && module.get.remoteValues()) {
+ module.restore.remoteValues();
}
else {
module.set.selected();
}
module.remove.initialLoad();
@@ -1959,10 +2124,13 @@
.removeClass(className.selected)
;
$nextSelectedItem
.addClass(className.selected)
;
+ if(settings.selectOnKeydown && module.is.single()) {
+ module.set.selectedItem($nextSelectedItem);
+ }
$menu
.scrollTop(newScroll)
;
}
},
@@ -1975,11 +2143,11 @@
isSearchMultiple = (isMultiple && isSearch),
searchValue = (isSearch)
? module.get.query()
: '',
hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0),
- searchWidth = module.get.searchWidth(searchValue.length),
+ searchWidth = module.get.searchWidth(),
valueIsSet = searchValue !== ''
;
if(isMultiple && hasSearchValue) {
module.verbose('Adjusting input width', searchWidth, settings.glyphWidth);
$search.css('width', searchWidth);
@@ -1991,10 +2159,13 @@
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();
@@ -2105,10 +2276,16 @@
$text.text(text);
}
}
}
},
+ selectedItem: function($item) {
+ module.debug('Setting user selection to item', $item);
+ module.remove.activeItem();
+ module.set.activeItem($item);
+ module.set.selected(module.get.choiceValue($item), $item);
+ },
selectedLetter: function(letter) {
var
$selectedItem = $item.filter('.' + className.selected),
alreadySelectedLetter = $selectedItem.length > 0 && module.has.firstLetter($selectedItem, letter),
$nextValue = false,
@@ -2136,10 +2313,13 @@
if($nextValue) {
module.verbose('Scrolling to next value with letter', letter);
module.set.scrollPosition($nextValue);
$selectedItem.removeClass(className.selected);
$nextValue.addClass(className.selected);
+ if(settings.selectOnKeydown && module.is.single()) {
+ module.set.selectedItem($nextValue);
+ }
}
},
direction: function($menu) {
if(settings.direction == 'auto') {
if(module.is.onScreen($menu)) {
@@ -2157,46 +2337,47 @@
var $element = $menu || $module;
$element.addClass(className.upward);
},
value: function(value, text, $selected) {
var
+ escapedValue = module.escape.value(value),
hasInput = ($input.length > 0),
isAddition = !module.has.value(value),
currentValue = module.get.values(),
stringValue = (value !== undefined)
? String(value)
: value,
newValue
;
if(hasInput) {
- if(stringValue == currentValue) {
+ 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', value, currentValue);
+ module.debug('Updating input value', escapedValue, currentValue);
internalChange = true;
$input
- .val(value)
+ .val(escapedValue)
;
if(settings.fireOnInit === false && module.is.initialLoad()) {
module.debug('Input native change event ignored on initial load');
}
else {
module.trigger.change();
}
internalChange = false;
}
else {
- module.verbose('Storing value in metadata', value, $input);
- if(value !== currentValue) {
+ 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);
@@ -2232,10 +2413,13 @@
;
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) {
@@ -2297,21 +2481,22 @@
label: function(value, text, shouldAnimate) {
var
$next = module.is.searchSelection()
? $search
: $text,
+ escapedValue = module.escape.value(value),
$label
;
$label = $('<a />')
.addClass(className.label)
- .attr('data-value', value)
- .html(templates.label(value, text))
+ .attr('data-value', escapedValue)
+ .html(templates.label(escapedValue, text))
;
- $label = settings.onLabelCreate.call($label, value, text);
+ $label = settings.onLabelCreate.call($label, escapedValue, text);
if(module.has.label(value)) {
- module.debug('Label already exists, skipping', value);
+ module.debug('Label already exists, skipping', escapedValue);
return;
}
if(settings.label.variation) {
$label.addClass(settings.label.variation);
}
@@ -2348,38 +2533,31 @@
;
}
},
optionValue: function(value) {
var
- $option = $input.find('option[value="' + value + '"]'),
- hasOption = ($option.length > 0)
+ escapedValue = module.escape.value(value),
+ $option = $input.find('option[value="' + escapedValue + '"]'),
+ hasOption = ($option.length > 0)
;
if(hasOption) {
return;
}
// temporarily disconnect observer
- if(selectObserver) {
- selectObserver.disconnect();
- module.verbose('Temporarily disconnecting mutation observer', value);
- }
+ module.disconnect.selectObserver();
if( module.is.single() ) {
module.verbose('Removing previous user addition');
$input.find('option.' + className.addition).remove();
}
$('<option/>')
- .prop('value', value)
+ .prop('value', escapedValue)
.addClass(className.addition)
.html(value)
.appendTo($input)
;
module.verbose('Adding user addition as an <option>', value);
- if(selectObserver) {
- selectObserver.observe($input[0], {
- childList : true,
- subtree : true
- });
- }
+ module.observe.select();
},
userSuggestion: function(value) {
var
$addition = $menu.children(selector.addition),
$existingItem = module.get.item(value),
@@ -2392,32 +2570,41 @@
}
if(value === '' || alreadyHasValue) {
$addition.remove();
return;
}
- $item
- .removeClass(className.selected)
- ;
if(hasUserSuggestion) {
- html = settings.templates.addition( module.add.variables(message.addResult, value) );
$addition
- .html(html)
+ .data(metadata.value, value)
+ .data(metadata.text, value)
.attr('data-' + metadata.value, value)
.attr('data-' + metadata.text, value)
.removeClass(className.filtered)
- .addClass(className.selected)
;
+ 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)
- .addClass(className.selected)
;
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),
@@ -2486,10 +2673,13 @@
$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;
@@ -2512,41 +2702,49 @@
$item.not('.' + className.active).removeClass(className.filtered);
}
else {
$item.removeClass(className.filtered);
}
+ module.remove.empty();
},
optionValue: function(value) {
var
- $option = $input.find('option[value="' + value + '"]'),
- hasOption = ($option.length > 0)
+ escapedValue = module.escape.value(value),
+ $option = $input.find('option[value="' + 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', value);
+ module.verbose('Temporarily disconnecting mutation observer');
}
$option.remove();
- module.verbose('Removing user addition as an <option>', value);
+ 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)
;
@@ -2655,10 +2853,11 @@
;
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 {
@@ -2689,16 +2888,32 @@
}
}
},
has: {
+ menuSearch: function() {
+ return (module.has.search() && $search.closest($menu).length > 0);
+ },
search: function() {
return ($search.length > 0);
},
+ sizer: function() {
+ return ($sizer.length > 0);
+ },
selectInput: function() {
return ( $input.is('select') );
},
+ minCharacters: function(searchTerm) {
+ if(settings.minCharacters) {
+ searchTerm = (searchTerm !== undefined)
+ ? String(searchTerm)
+ : String(module.get.query())
+ ;
+ return (searchTerm.length >= settings.minCharacters);
+ }
+ return true;
+ },
firstLetter: function($item, letter) {
var
text,
firstLetter
;
@@ -2722,20 +2937,27 @@
message: function() {
return ($menu.children(selector.message).length !== 0);
},
label: function(value) {
var
- $labels = $module.find(selector.label)
+ escapedValue = module.escape.value(value),
+ $labels = $module.find(selector.label)
;
- return ($labels.filter('[data-value="' + value +'"]').length > 0);
+ return ($labels.filter('[data-value="' + escapedValue +'"]').length > 0);
},
maxSelections: function() {
return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections);
},
allResultsFiltered: function() {
- return ($item.filter(selector.unselectable).length === $item.length);
+ 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) {
var
@@ -2753,10 +2975,13 @@
is: {
active: function() {
return $module.hasClass(className.active);
},
+ bubbledLabelClick: function(event) {
+ return $(event.target).is('select, input') && $module.closest('label').length > 0;
+ },
alreadySetup: function() {
return ($module.is('select') && $module.parent(selector.dropdown).length > 0 && $module.prev().length === 0);
},
animating: function($subMenu) {
return ($subMenu)
@@ -2772,11 +2997,11 @@
},
focusedOnSearch: function() {
return (document.activeElement === $search[0]);
},
allFiltered: function() {
- return( (module.is.multiple() || module.has.search()) && !module.has.message() && module.has.allResultsFiltered() );
+ 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() {
@@ -3029,10 +3254,30 @@
module.timer = setTimeout(module.hide, settings.delay.hide);
}
},
escape: {
+ value: function(value) {
+ var
+ multipleValues = $.isArray(value),
+ stringValue = (typeof value === 'string'),
+ isUnparsable = (!stringValue && !multipleValues),
+ hasQuotes = (stringValue && value.search(regExp.quote) !== -1),
+ values = []
+ ;
+ if(!module.has.selectInput() || 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, '"');
+ },
regExp: function(text) {
text = String(text);
return text.replace(regExp.escape, '\\$&');
}
},
@@ -3041,11 +3286,16 @@
module.debug('Changing setting', name, value);
if( $.isPlainObject(name) ) {
$.extend(true, settings, name);
}
else if(value !== undefined) {
- settings[name] = value;
+ if($.isPlainObject(settings[name])) {
+ $.extend(true, settings[name], value);
+ }
+ else {
+ settings[name] = value;
+ }
}
else {
return settings[name];
}
},
@@ -3059,34 +3309,36 @@
else {
return module[name];
}
},
debug: function() {
- if(settings.debug) {
+ 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.verbose && settings.debug) {
+ 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() {
- module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
- module.error.apply(console, arguments);
+ 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,
@@ -3213,50 +3465,56 @@
;
};
$.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(){})
apiSettings : false,
- 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
+ selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used
+ minCharacters : 0, // Minimum characters required to trigger API call
+ 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
+ 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
+ fullTextSearch : false, // search anywhere in value (set to 'exact' to require exact matches)
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
+ 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.0714, // widest glyph width in em (W is 1.0714 em) used to calculate multiselect input width
+ glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width
// label settings on multi-select
label: {
transition : 'scale',
duration : 200,
@@ -3306,10 +3564,11 @@
noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>'
},
regExp : {
escape : /[-[\]{}()*+?.,\\^$|#\s]/g,
+ quote : /"/g
},
metadata : {
defaultText : 'defaultText',
defaultValue : 'defaultValue',
@@ -3318,14 +3577,16 @@
value : 'value'
},
// property names for remote query
fields: {
- remoteValues : 'results', // grouping for api results
- values : 'values', // grouping for all dropdown values
- name : 'name', // displayed dropdown text
- value : 'value' // actual dropdown value
+ 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
+ text : 'text' // displayed text when selected
},
keys : {
backspace : 8,
delimiter : 188, // comma
@@ -3341,39 +3602,43 @@
},
selector : {
addition : '.addition',
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',
+ search : 'input.search, .menu > .search > input, .menu input.search',
+ sizer : '> input.sizer',
text : '> .text:not(.icon)',
unselectable : '.disabled, .filtered'
},
className : {
active : 'active',
addition : 'addition',
animating : 'animating',
disabled : 'disabled',
+ empty : 'empty',
dropdown : 'ui dropdown',
filtered : 'filtered',
hidden : 'hidden transition',
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',
visible : 'visible'
@@ -3414,10 +3679,20 @@
var
values = response[fields.values] || {},
html = ''
;
$.each(values, function(index, option) {
- html += '<div class="item" data-value="' + option[fields.value] + '">' + option[fields.name] + '</div>';
+ var
+ maybeText = (option[fields.text])
+ ? 'data-text="' + option[fields.text] + '"'
+ : '',
+ maybeDisabled = (option[fields.disabled])
+ ? 'disabled '
+ : ''
+ ;
+ html += '<div class="'+ maybeDisabled +'item" data-value="' + option[fields.value] + '"' + maybeText + '>'
+ html += option[fields.name];
+ html += '</div>';
});
return html;
},
// generates label for multiselect