You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@senssoft.apache.org by ar...@apache.org on 2016/10/21 16:12:27 UTC
[34/48] incubator-senssoft-tap git commit: Cherry pick front end
bundle
http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/blob/db97ec6f/semantic/src/definitions/modules/dropdown.js
----------------------------------------------------------------------
diff --git a/semantic/src/definitions/modules/dropdown.js b/semantic/src/definitions/modules/dropdown.js
new file mode 100644
index 0000000..d388fc8
--- /dev/null
+++ b/semantic/src/definitions/modules/dropdown.js
@@ -0,0 +1,3717 @@
+/*!
+ * # Semantic UI - Dropdown
+ * http://github.com/semantic-org/semantic-ui/
+ *
+ *
+ * Released under the MIT license
+ * http://opensource.org/licenses/MIT
+ *
+ */
+
+;(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),
+
+ moduleSelector = $allModules.selector || '',
+
+ hasTouch = ('ontouchstart' in document.documentElement),
+ time = new Date().getTime(),
+ performance = [],
+
+ query = arguments[0],
+ methodInvoked = (typeof query == 'string'),
+ queryArguments = [].slice.call(arguments, 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),
+
+ $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),
+
+ activated = false,
+ itemActivated = false,
+ internalChange = false,
+ element = this,
+ instance = $module.data(moduleNamespace),
+
+ initialLoad,
+ pageLostFocus,
+ willRefocus,
+ elementNamespace,
+ id,
+ selectObserver,
+ menuObserver,
+ module
+ ;
+
+ module = {
+
+ initialize: function() {
+ module.debug('Initializing dropdown', settings);
+
+ if( module.is.alreadySetup() ) {
+ module.setup.reference();
+ }
+ else {
+ module.setup.layout();
+ module.refreshData();
+
+ module.save.defaults();
+ module.restore.selected();
+
+ module.create.id();
+ module.bind.events();
+
+ module.observeChanges();
+ module.instantiate();
+ }
+
+ },
+
+ 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();
+ $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(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
+ });
+ }
+ }
+ },
+
+ 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;
+ }
+ values = $.isArray(values)
+ ? 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 {
+ 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) {
+ $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);
+ }
+ }
+ },
+
+ 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();
+ }
+ 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();
+ }
+ },
+ 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)
+ .html( templates.dropdown(selectValues) )
+ .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
+ .removeAttr('class')
+ .detach()
+ .prependTo($module)
+ ;
+ }
+ module.refresh();
+ },
+ menu: function(values) {
+ $menu.html( templates.menu(values, fields));
+ $item = $menu.find(selector.item);
+ },
+ reference: function() {
+ module.debug('Dropdown behavior was called on select, replacing with closest dropdown');
+ // replace module reference
+ $module = $module.parent(selector.dropdown);
+ module.refresh();
+ module.setup.returnedObject();
+ // invoke method in context of current instance
+ if(methodInvoked) {
+ instance = module;
+ module.invoke(query);
+ }
+ },
+ 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() {
+ $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);
+ $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);
+ },
+
+ 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();
+ }
+ },
+
+ show: function(callback) {
+ callback = $.isFunction(callback)
+ ? callback
+ : function(){}
+ ;
+ 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.animate.show(function() {
+ if( module.can.click() ) {
+ module.bind.intent();
+ }
+ if(module.has.menuSearch()) {
+ module.focusSearch();
+ }
+ module.set.visible();
+ callback.call(element);
+ });
+ }
+ }
+ },
+
+ hide: function(callback) {
+ callback = $.isFunction(callback)
+ ? callback
+ : function(){}
+ ;
+ if( module.is.active() ) {
+ module.debug('Hiding dropdown');
+ if(settings.onHide.call(element) !== false) {
+ module.animate.hide(function() {
+ module.remove.visible();
+ callback.call(element);
+ });
+ }
+ }
+ },
+
+ 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() {
+ if(hasTouch) {
+ module.bind.touchEvents();
+ }
+ module.bind.keyboardEvents();
+ module.bind.inputEvents();
+ module.bind.mouseEvents();
+ },
+ touchEvents: function() {
+ module.debug('Touch device detected binding additional touch events');
+ if( module.is.searchSelection() ) {
+ // do nothing special yet
+ }
+ else if( module.is.single() ) {
+ $module
+ .on('touchstart' + eventNamespace, module.event.test.toggle)
+ ;
+ }
+ $menu
+ .on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter)
+ ;
+ },
+ 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
+ .on('click' + eventNamespace, selector.label, module.event.label.click)
+ .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('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
+ .on('click' + eventNamespace, module.event.click)
+ ;
+ }
+ }
+ else {
+ if(settings.on == 'click') {
+ $module
+ .on('click' + eventNamespace, selector.icon, module.event.icon.click)
+ .on('click' + eventNamespace, module.event.test.toggle)
+ ;
+ }
+ 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)
+ .on('blur' + eventNamespace, module.event.blur)
+ ;
+ }
+ $menu
+ .on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter)
+ .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
+ .on('click' + elementNamespace, module.event.test.hide)
+ ;
+ }
+ },
+
+ unbind: {
+ intent: function() {
+ module.verbose('Removing hide intent event from document');
+ if(hasTouch) {
+ $document
+ .off('touchstart' + elementNamespace)
+ .off('touchmove' + elementNamespace)
+ ;
+ }
+ $document
+ .off('click' + elementNamespace)
+ ;
+ }
+ },
+
+ filter: function(query) {
+ var
+ searchTerm = (query !== undefined)
+ ? query
+ : module.get.query(),
+ afterFiltered = function() {
+ if(module.is.multiple()) {
+ module.filterActive();
+ }
+ 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) {
+ module.add.userSuggestion(query);
+ }
+ 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() {
+ 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) {
+ module.remove.message();
+ module.setup.menu({
+ values: response[fields.remoteValues]
+ });
+ 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
+ searchTerm = (query !== undefined)
+ ? query
+ : module.get.query(),
+ results = null,
+ escapedTerm = module.escape.regExp(searchTerm),
+ beginsWithRegExp = new RegExp('^' + escapedTerm, 'igm')
+ ;
+ // 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
+ ;
+ if(settings.match == 'both' || settings.match == 'text') {
+ text = String(module.get.choiceText($choice, false));
+ 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;
+ }
+ }
+ if(settings.match == 'both' || settings.match == 'value') {
+ value = String(module.get.choiceValue($choice, text));
+
+ if(value.search(beginsWithRegExp) !== -1) {
+ results.push(this);
+ return true;
+ }
+ else if(settings.fullTextSearch && 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)
+ ;
+ }
+ },
+
+ fuzzySearch: function(query, term) {
+ var
+ termLength = term.length,
+ queryLength = query.length
+ ;
+ query = query.toLowerCase();
+ term = term.toLowerCase();
+ 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) {
+ 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(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.length > 0)
+ ;
+ 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();
+ }
+ }
+ },
+
+ 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: {
+ focus: function() {
+ activated = true;
+ if(module.is.multiple()) {
+ module.remove.activeLabel();
+ }
+ if(settings.showOnFocus) {
+ module.search();
+ }
+ },
+ blur: function(event) {
+ pageLostFocus = (document.activeElement === this);
+ if(!willRefocus) {
+ if(!itemActivated && !pageLostFocus) {
+ if(settings.forceSelection && module.has.query()) {
+ module.forceSelection();
+ }
+ module.hide();
+ }
+ }
+ willRefocus = false;
+ }
+ },
+ icon: {
+ click: function(event) {
+ module.toggle();
+ event.stopPropagation();
+ }
+ },
+ 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)) {
+ 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) {
+ 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
+ $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)
+ ;
+ 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(),
+ caretAtStart = (isFocusedOnSearch && module.get.caretPosition() === 0),
+ $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();
+ }
+ else if(caretAtStart && !hasActiveLabel && pressedKey == keys.backspace) {
+ 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);
+ 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) {
+ 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);
+ module.event.item.click.call($selectedItem, event);
+ if(module.is.searchSelection()) {
+ module.remove.searchTerm();
+ }
+ }
+ 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)
+ ;
+ 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) {
+ $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.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.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)
+ 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.select.firstUnfiltered();
+ 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) {
+ 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);
+ }
+ },
+ 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),
+ notOnLabel = ($module.find($label).length === 0),
+ 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));
+ if(module.is.multiple() && !module.is.allFiltered()) {
+ return;
+ }
+ else {
+ module.hideAndClear();
+ }
+ }
+ },
+
+ select: function(text, value, element) {
+ // mimics action.activate but does not select text
+ module.action.activate.call(element);
+ },
+
+ combo: function(text, value, element) {
+ value = (value !== undefined)
+ ? value
+ : text
+ ;
+ module.set.selected(value, $(element));
+ module.hideAndClear();
+ },
+
+ hide: function(text, value, element) {
+ module.set.value(value, text);
+ module.hideAndClear();
+ }
+
+ },
+
+ get: {
+ id: function() {
+ return id;
+ },
+ defaultText: function() {
+ return $module.data(metadata.defaultText);
+ },
+ defaultValue: function() {
+ return $module.data(metadata.defaultValue);
+ },
+ placeholderText: function() {
+ 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() )
+ ? $.isArray(values)
+ ? 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;
+ }
+ values = $.isArray(values)
+ ? 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;
+ });
+ },
+ caretPosition: function() {
+ var
+ input = $search.get(0),
+ range,
+ rangeLength
+ ;
+ if('selectionStart' in input) {
+ return input.selectionStart;
+ }
+ else if (document.selection) {
+ input.focus();
+ range = document.selection.createRange();
+ rangeLength = range.text.length;
+ range.moveStart('character', -input.value.length);
+ return range.text.length - rangeLength;
+ }
+ },
+ value: function() {
+ var
+ value = ($input.length > 0)
+ ? $input.val()
+ : $module.data(metadata.value),
+ isEmptyMultiselect = ($.isArray(value) && value.length === 1 && value[0] === '')
+ ;
+ // 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
+ ? value.split(settings.delimiter)
+ : ''
+ : 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')
+ ? $.trim(choiceText.toLowerCase())
+ : 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
+ select = {}
+ ;
+ 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')
+ : name
+ ;
+ if(settings.placeholder === 'auto' && value === '') {
+ select.placeholder = name;
+ }
+ else {
+ select.values.push({
+ name : name,
+ value : value,
+ disabled : disabled
+ });
+ }
+ })
+ ;
+ if(settings.placeholder && settings.placeholder !== 'auto') {
+ module.debug('Setting placeholder value to', settings.placeholder);
+ select.placeholder = settings.placeholder;
+ }
+ if(settings.sortSelect) {
+ select.values.sort(function(a, b) {
+ return (a.name > b.name)
+ ? 1
+ : -1
+ ;
+ });
+ 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()
+ ;
+ shouldSearch = (isMultiple)
+ ? (value.length > 0)
+ : (value !== undefined && value !== null)
+ ;
+ isMultiple = (module.is.multiple() && $.isArray(value));
+ strict = (value === '' || value === 0)
+ ? 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) {
+ if($.inArray( String(optionValue), value) !== -1 || $.inArray(optionText, value) !== -1) {
+ $selectedItem = ($selectedItem)
+ ? $selectedItem.add($choice)
+ : $choice
+ ;
+ }
+ }
+ else if(strict) {
+ module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
+ if( optionValue === value || optionText === value) {
+ $selectedItem = $choice;
+ return true;
+ }
+ }
+ else {
+ if( String(optionValue) == String(value) || optionText == value) {
+ 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: {
+ defaults: function() {
+ module.clear();
+ 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();
+ }
+ 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);
+ }
+ },
+
+ clear: function() {
+ if(module.is.multiple()) {
+ module.remove.labels();
+ }
+ else {
+ module.remove.activeItem();
+ module.remove.selectedItem();
+ }
+ module.set.placeholderText();
+ module.clearValue();
+ },
+
+ clearValue: function() {
+ module.set.value('');
+ },
+
+ 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.has.search() ) {
+ 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);
+ }
+ },
+ 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
+ ;
+ 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) {
+ if(settings.action !== 'select') {
+ if(settings.action == 'combo') {
+ module.debug('Changing combo button text', text, $combo);
+ if(settings.preserveHTML) {
+ $combo.html(text);
+ }
+ else {
+ $combo.text(text);
+ }
+ }
+ else {
+ 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);
+ }
+ }
+ }
+ },
+ 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,
+ $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);
+ if(settings.selectOnKeydown && module.is.single()) {
+ module.set.selectedItem($nextValue);
+ }
+ }
+ },
+ direction: function($menu) {
+ if(settings.direction == 'auto') {
+ if(module.is.onScreen($menu)) {
+ module.remove.upward($menu);
+ }
+ else {
+ module.set.upward($menu);
+ }
+ }
+ else if(settings.direction == 'upward') {
+ module.set.upward($menu);
+ }
+ },
+ upward: function($menu) {
+ 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(!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');
+ }
+ else {
+ 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);
+ }
+ else {
+ 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
+ isMultiple = module.is.multiple(),
+ $userSelectedItem
+ ;
+ $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),
+
<TRUNCATED>