/*! * # Fomantic-UI 2.9.0-beta.301+42e68bc - Dropdown * http://github.com/fomantic/Fomantic-UI/ * * * Released under the MIT license * http://opensource.org/licenses/MIT * */ ;(function ($, window, document, undefined) { 'use strict'; $.isFunction = $.isFunction || function(obj) { return typeof obj === "function" && typeof obj.nodeType !== "number"; }; 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 || '', 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 = [window,document].indexOf(settings.context) < 0 ? $(document).find(settings.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), $clear = $module.find(selector.clearIcon), $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), $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(), activated = false, itemActivated = false, internalChange = false, iconClicked = false, element = this, focused = false, instance = $module.data(moduleNamespace), selectActionActive, initialLoad, pageLostFocus, willRefocus, elementNamespace, id, selectObserver, menuObserver, classObserver, module ; module = { initialize: function() { module.debug('Initializing dropdown', settings); if( module.is.alreadySetup() ) { module.setup.reference(); } else { if (settings.ignoreDiacritics && !String.prototype.normalize) { settings.ignoreDiacritics = false; module.error(error.noNormalize, element); } module.setup.layout(); if(settings.values) { module.set.initialLoad(); module.change.values(settings.values); module.remove.initialLoad(); } 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.remove.active(); $menu.transition('stop all'); $menu.removeClass(className.visible).addClass(className.hidden); $module .off(eventNamespace) .removeData(moduleNamespace) ; $menu .off(eventNamespace) ; $document .off(elementNamespace) ; module.disconnect.menuObserver(); module.disconnect.selectObserver(); module.disconnect.classObserver(); }, observeChanges: function() { if('MutationObserver' in window) { selectObserver = new MutationObserver(module.event.select.mutation); menuObserver = new MutationObserver(module.event.menu.mutation); classObserver = new MutationObserver(module.event.class.mutation); module.debug('Setting up mutation observer', selectObserver, menuObserver, classObserver); module.observe.select(); module.observe.menu(); module.observe.class(); } }, disconnect: { menuObserver: function() { if(menuObserver) { menuObserver.disconnect(); } }, selectObserver: function() { if(selectObserver) { selectObserver.disconnect(); } }, classObserver: function() { if(classObserver) { classObserver.disconnect(); } } }, observe: { select: function() { if(module.has.input() && selectObserver) { selectObserver.observe($module[0], { attributes: true, childList : true, subtree : true }); } }, menu: function() { if(module.has.menu() && menuObserver) { menuObserver.observe($menu[0], { childList : true, subtree : true }); } }, class: function() { if(module.has.search() && classObserver) { classObserver.observe($module[0], { attributes : true }); } } }, create: { id: function() { id = (Math.random().toString(16) + '000000000').slice(2, 10); 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 = Array.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 = $('
') .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 = $('
') .addClass(className.menu) .appendTo($module) ; }, sizer: function() { $sizer = $('') .addClass(className.sizer) .insertAfter($search) ; } }, search: function(query) { query = (query !== undefined) ? query : module.get.query() ; module.verbose('Searching for query', query); if(settings.fireOnInit === false && module.is.initialLoad()) { module.verbose('Skipping callback on initial load', settings.onSearch); } else if(module.has.minCharacters(query) && settings.onSearch.call(element, query) !== false) { module.filter(query); } else { module.hide(null,true); } }, 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.clearable() && !module.has.clearItem() ) { module.verbose('Adding clear icon'); $clear = $('') .addClass('remove icon') .insertBefore($text) ; } if( module.is.search() && !module.has.search() ) { module.verbose('Adding search input'); $search = $('') .addClass(className.search) .prop('autocomplete', module.is.chrome() ? 'fomantic-search' : '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 = $('
') .attr('class', $input.attr('class') ) .addClass(className.selection) .addClass(className.dropdown) .html( templates.dropdown(selectValues, fields, settings.preserveHTML, settings.className) ) .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); } if($input.is('[required]')) { settings.forceSelection = true; } $input .removeAttr('required') .removeAttr('class') .detach() .prependTo($module) ; } module.refresh(); }, menu: function(values) { $menu.html( templates.menu(values, fields,settings.preserveHTML,settings.className)); $item = $menu.find(selector.item); $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); }, reference: function() { module.debug('Dropdown behavior was called on select, replacing with closest dropdown'); // replace module reference $module = $module.parent(selector.dropdown); instance = $module.data(moduleNamespace); element = $module.get(0); module.refresh(); module.setup.returnedObject(); }, returnedObject: function() { var $firstModules = $allModules.slice(0, elementIndex), $lastModules = $allModules.slice(elementIndex + 1) ; // adjust all modules to use correct reference $allModules = $firstModules.add($module).add($lastModules); } }, refresh: function() { module.refreshSelectors(); module.refreshData(); }, refreshItems: function() { $item = $menu.find(selector.item); $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); }, 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); $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); }, 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) ; }, clearItems: function() { $menu.empty(); module.refreshItems(); }, toggle: function() { module.verbose('Toggling menu visibility'); if( !module.is.active() ) { module.show(); } else { module.hide(); } }, show: function(callback, preventFocus) { callback = $.isFunction(callback) ? callback : function(){} ; if ((focused || iconClicked) && module.is.remote() && module.is.noApiCache()) { module.clearItems(); } if(!module.can.show() && module.is.remote()) { module.debug('No API results retrieved, searching before show'); module.queryRemote(module.get.query(), module.show, [callback, preventFocus]); } 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() { module.bind.intent(); if(module.has.search() && !preventFocus) { module.focusSearch(); } module.set.visible(); callback.call(element); }); } } }, hide: function(callback, preventBlur) { callback = $.isFunction(callback) ? callback : function(){} ; if( module.is.active() && !module.is.animatingOutward() ) { module.debug('Hiding dropdown'); if(settings.onHide.call(element) !== false) { module.animate.hide(function() { module.remove.visible(); // hiding search focus if ( module.is.focusedOnSearch() && preventBlur !== true ) { $search.blur(); } callback.call(element); }); // Hide submenus explicitly. On some browsers (esp. mobile), they will not automatically receive a // mouseleave event var $subMenu = $module.find(selector.menu); if($subMenu.length > 0) { module.verbose('Hiding sub-menu', $subMenu); $subMenu.each(function() { module.animate.hide(false, $(this)); }); } } } else { module.unbind.intent(); } iconClicked = false; focused = false; }, hideOthers: function() { module.verbose('Finding other dropdowns to hide'); $allModules .not($module) .has(selector.menu + '.' + className.visible) .dropdown('hide') ; }, hideMenu: function() { module.verbose('Hiding menu instantaneously'); module.remove.active(); module.remove.visible(); $menu.transition('hide'); }, hideSubMenus: function() { var $subMenus = $menu.children(selector.item).find(selector.menu) ; module.verbose('Hiding sub menus', $subMenus); $subMenus.transition('hide'); }, bind: { events: function() { module.bind.keyboardEvents(); module.bind.inputEvents(); module.bind.mouseEvents(); }, keyboardEvents: function() { module.verbose('Binding keyboard events'); $module .on('keydown' + eventNamespace, module.event.keydown) ; if( module.has.search() ) { $module .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input) ; } if( module.is.multiple() ) { $document .on('keydown' + elementNamespace, module.event.document.keydown) ; } }, inputEvents: function() { module.verbose('Binding input change events'); $module .on('change' + eventNamespace, selector.input, module.event.change) ; if(module.is.multiple() && module.is.searchSelection()) { $module .on('paste' + eventNamespace, selector.search, module.event.paste) ; } }, 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('click' + eventNamespace, selector.clearIcon, module.event.clearIcon.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) .on('click' + eventNamespace, module.event.search.focus) ; } } 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) .on('touchstart' + eventNamespace, module.event.test.toggle) .on('touchstart' + eventNamespace, selector.icon, module.event.icon.click) ; } 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('click' + eventNamespace, selector.clearIcon, module.event.clearIcon.click) ; if(module.has.menuSearch() ) { $module .on('blur' + eventNamespace, selector.search, module.event.search.blur) ; } else { $module .on('blur' + eventNamespace, module.event.blur) ; } } $menu .on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter) .on('touchstart' + 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'); $document .on('click' + elementNamespace, module.event.test.hide) ; } }, unbind: { intent: function() { module.verbose('Removing hide intent event from document'); $document .off('click' + elementNamespace) ; } }, filter: function(query) { var searchTerm = (query !== undefined) ? query : module.get.query(), afterFiltered = function() { if(module.is.multiple()) { module.filterActive(); } if(query || (!query && module.get.activeItem().length == 0)) { module.select.firstUnfiltered(); } if( module.has.allResultsFiltered() ) { if( settings.onNoResults.call(element, searchTerm) ) { if(settings.allowAdditions) { if(settings.hideAdditions) { module.verbose('User addition with no menu, setting empty style'); module.set.empty(); module.hideMenu(); } } else { module.verbose('All items filtered, showing message', searchTerm); module.add.message(message.noResults); } } else { module.verbose('All items filtered, hiding dropdown', searchTerm); module.hideMenu(); } } else { module.remove.empty(); module.remove.message(); } if(settings.allowAdditions) { module.add.userSuggestion(module.escape.htmlEntities(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() { if(settings.filterRemoteData) { module.filterItems(searchTerm); } var preSelected = $input.val(); if(!Array.isArray(preSelected)) { preSelected = preSelected && preSelected!=="" ? preSelected.split(settings.delimiter) : []; } if (module.is.multiple()) { $.each(preSelected,function(index,value){ $item.filter('[data-value="'+value+'"]') .addClass(className.filtered) ; }); } module.focusSearch(true); afterFiltered(); }); } else { module.error(error.noAPI); } } else { module.filterItems(searchTerm); afterFiltered(); } }, queryRemote: function(query, callback, callbackParameters) { if(!Array.isArray(callbackParameters)){ callbackParameters = [callbackParameters]; } var apiSettings = { errorDuration : false, cache : 'local', throttle : settings.throttle, urlData : { query: query } }, apiCallbacks = { onError: function(errorMessage, $module, xhr) { module.add.message(message.serverError); iconClicked = false; focused = false; callback.apply(null, callbackParameters); if(typeof settings.apiSettings.onError === 'function') { settings.apiSettings.onError.call(this, errorMessage, $module, xhr); } }, onFailure: function(response, $module, xhr) { module.add.message(message.serverError); iconClicked = false; focused = false; callback.apply(null, callbackParameters); if(typeof settings.apiSettings.onFailure === 'function') { settings.apiSettings.onFailure.call(this, response, $module, xhr); } }, onSuccess : function(response, $module, xhr) { var values = response[fields.remoteValues] ; if (!Array.isArray(values)){ values = []; } module.remove.message(); var menuConfig = {}; menuConfig[fields.values] = values; module.setup.menu(menuConfig); if(values.length===0 && !settings.allowAdditions) { module.add.message(message.noResults); } else { var value = module.is.multiple() ? module.get.values() : module.get.value(); if (value !== '') { module.verbose('Value(s) present after click icon, select value(s) in items'); module.set.selected(value, null, true, true); } } iconClicked = false; focused = false; callback.apply(null, callbackParameters); if(typeof settings.apiSettings.onSuccess === 'function') { settings.apiSettings.onSuccess.call(this, response, $module, xhr); } } } ; if( !$module.api('get request') ) { module.setup.api(); } apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings, apiCallbacks); $module .api('setting', apiSettings) .api('query') ; }, filterItems: function(query) { var searchTerm = module.remove.diacritics(query !== undefined ? query : module.get.query() ), results = null, escapedTerm = module.escape.string(searchTerm), regExpFlags = (settings.ignoreSearchCase ? 'i' : '') + 'gm', beginsWithRegExp = new RegExp('^' + escapedTerm, regExpFlags) ; // 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($choice.hasClass(className.unfilterable)) { results.push(this); return true; } if(settings.match === 'both' || settings.match === 'text') { text = module.remove.diacritics(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 = module.remove.diacritics(String(module.get.choiceValue($choice, text))); if(value.search(beginsWithRegExp) !== -1) { results.push(this); return true; } else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, value)) { results.push(this); return true; } else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, value)) { results.push(this); return true; } } }) ; } module.debug('Showing only matched items', searchTerm); module.remove.filteredItem(); if(results) { $item .not(results) .addClass(className.filtered) ; } if(!module.has.query()) { $divider .removeClass(className.hidden); } else if(settings.hideDividers === true) { $divider .addClass(className.hidden); } else if(settings.hideDividers === 'empty') { $divider .removeClass(className.hidden) .filter(function() { // First find the last divider in this divider group // Dividers which are direct siblings are considered a group var lastDivider = $(this).nextUntil(selector.item); return (lastDivider.length ? lastDivider : $(this)) // Count all non-filtered items until the next divider (or end of the dropdown) .nextUntil(selector.divider) .filter(selector.item + ":not(." + className.filtered + ")") // Hide divider if no items are found .length === 0; }) .addClass(className.hidden); } }, fuzzySearch: function(query, term) { var termLength = term.length, queryLength = query.length ; query = (settings.ignoreSearchCase ? query.toLowerCase() : query); term = (settings.ignoreSearchCase ? term.toLowerCase() : term); 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 = (settings.ignoreSearchCase ? query.toLowerCase() : query); term = (settings.ignoreSearchCase ? term.toLowerCase() : term); return term.indexOf(query) > -1; }, 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(); } } }, blurSearch: function() { if( module.has.search() ) { $search.blur(); } }, 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(settings.allowAdditions || (hasSelected && !module.is.multiple())) { module.debug('Forcing partial selection to selected item', $selectedItem); module.event.item.click.call($selectedItem, {}, true); } else { module.remove.searchTerm(); } }, change: { values: function(values) { if(!settings.allowAdditions) { module.clear(); } module.debug('Creating dropdown with specified values', values); var menuConfig = {}; menuConfig[fields.values] = values; module.setup.menu(menuConfig); $.each(values, function(index, item) { if(item.selected === true) { module.debug('Setting initial selection to', item[fields.value]); module.set.selected(item[fields.value]); if(!module.is.multiple()) { return false; } } }); if(module.has.selectInput()) { module.disconnect.selectObserver(); $input.html(''); $input.append(''); $.each(values, function(index, item) { var value = settings.templates.deQuote(item[fields.value]), name = settings.templates.escape( item[fields.name] || '', settings.preserveHTML ) ; $input.append(''); }); module.observe.select(); } } }, event: { paste: function(event) { var pasteValue = (event.originalEvent.clipboardData || window.clipboardData).getData('text'), tokens = pasteValue.split(settings.delimiter) ; tokens.forEach(function(value){ module.set.selected(module.escape.htmlEntities(value.trim()), null, true, true); }); event.preventDefault(); }, change: function() { if(!internalChange) { module.debug('Input changed, updating selection'); module.set.selected(); } }, focus: function() { if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) { focused = true; 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(event) { activated = true; if(module.is.multiple()) { module.remove.activeLabel(); } if(!focused && !module.is.active() && (settings.showOnFocus || (event.type !== 'focus' && event.type !== 'focusin')) && event.type !== 'touchstart') { focused = true; module.search(); } }, blur: function(event) { pageLostFocus = (document.activeElement === this); if(module.is.searchSelection() && !willRefocus) { if(!itemActivated && !pageLostFocus) { if(settings.forceSelection) { module.forceSelection(); } else if(!settings.allowAdditions){ module.remove.searchTerm(); } module.hide(); } } willRefocus = false; } }, clearIcon: { click: function(event) { module.clear(); if(module.is.searchSelection()) { module.remove.searchTerm(); } module.hide(); event.stopPropagation(); } }, icon: { click: function(event) { iconClicked=true; if(module.has.search()) { if(!module.is.active()) { if(settings.showOnFocus){ module.focusSearch(); } else { module.toggle(); } } else { module.blurSearch(); } } else { 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)); event.stopPropagation(); } }, remove: { click: function(event) { 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 ); } event.stopPropagation(); } }, test: { toggle: function(event) { var toggleBehavior = (module.is.multiple()) ? module.show : module.toggle ; if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) { return; } if (!module.is.multiple() || (module.is.multiple() && !module.is.active())) { focused = true; } if( module.determine.eventOnElement(event, toggleBehavior) && event.type !== 'touchstart') { // do not preventDefault of touchstart; so emulated mouseenter is triggered on first touch and not later // (when selecting an item). The double-showing of the dropdown through both events does not hurt. event.preventDefault(); } }, hide: function(event) { if(module.determine.eventInModule(event, module.hide)){ if(element.id && $(event.target).attr('for') === element.id){ event.preventDefault(); } } } }, class: { mutation: function(mutations) { mutations.forEach(function(mutation) { if(mutation.attributeName === "class") { module.check.disabled(); } }); } }, select: { mutation: function(mutations) { if(module.is.selectMutation(mutations)) { module.debug(' removing selected option', removedValue); newValue = module.remove.arrayValue(removedValue, values); module.remove.optionValue(removedValue); } else { module.verbose('Removing from delimited values', removedValue); newValue = module.remove.arrayValue(removedValue, values); newValue = newValue.join(settings.delimiter); } if(settings.fireOnInit === false && module.is.initialLoad()) { module.verbose('No callback on initial load', settings.onRemove); } else { settings.onRemove.call(element, removedValue, removedText, $removedItem); } module.set.value(newValue, removedText, $removedItem, preventChangeTrigger); module.check.maxSelections(); }, arrayValue: function(removedValue, values) { if( !Array.isArray(values) ) { values = [values]; } values = $.grep(values, function(value){ return (removedValue != value); }); module.verbose('Removed value from delimited string', removedValue, values); return values; }, label: function(value, shouldAnimate) { var escapedValue = module.escape.value(value), $labels = $module.find(selector.label), $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(settings.ignoreCase ? escapedValue.toLowerCase() : escapedValue) +'"]') ; module.verbose('Removing label', $removedLabel); $removedLabel.remove(); }, activeLabels: function($activeLabels) { $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active); module.verbose('Removing active label selections', $activeLabels); module.remove.labels($activeLabels); }, labels: function($labels, preventChangeTrigger) { $labels = $labels || $module.find(selector.label); module.verbose('Removing labels', $labels); $labels .each(function(){ var $label = $(this), value = $label.data(metadata.value), stringValue = (value !== undefined) ? String(value) : value, isUserValue = module.is.userValue(stringValue) ; if(settings.onLabelRemove.call($label, value) === false) { module.debug('Label remove callback cancelled removal'); return; } module.remove.message(); if(isUserValue) { module.remove.value(stringValue, stringValue, module.get.item(stringValue), preventChangeTrigger); module.remove.label(stringValue); } else { // selected will also remove label module.remove.selected(stringValue, false, preventChangeTrigger); } }) ; }, tabbable: function() { if( module.is.searchSelection() ) { module.debug('Searchable dropdown initialized'); $search .removeAttr('tabindex') ; $menu .removeAttr('tabindex') ; } else { module.debug('Simple selection dropdown initialized'); $module .removeAttr('tabindex') ; $menu .removeAttr('tabindex') ; } }, diacritics: function(text) { return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text; } }, has: { menuSearch: function() { return (module.has.search() && $search.closest($menu).length > 0); }, clearItem: function() { return ($clear.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 && !iconClicked) { searchTerm = (searchTerm !== undefined) ? String(searchTerm) : String(module.get.query()) ; return (searchTerm.length >= settings.minCharacters); } iconClicked=false; return true; }, firstLetter: function($item, letter) { var text, firstLetter ; if(!$item || $item.length === 0 || typeof letter !== 'string') { return false; } text = module.get.choiceText($item, false); letter = letter.toLowerCase(); firstLetter = String(text).charAt(0).toLowerCase(); return (letter == firstLetter); }, input: function() { return ($input.length > 0); }, items: function() { return ($item.length > 0); }, menu: function() { return ($menu.length > 0); }, subMenu: function($currentMenu) { return ($currentMenu || $menu).find(selector.menu).length > 0; }, message: function() { return ($menu.children(selector.message).length !== 0); }, label: function(value) { var escapedValue = module.escape.value(value), $labels = $module.find(selector.label) ; if(settings.ignoreCase) { escapedValue = escapedValue.toLowerCase(); } return ($labels.filter('[data-' + metadata.value + '="' + module.escape.string(escapedValue) +'"]').length > 0); }, maxSelections: function() { return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections); }, allResultsFiltered: function() { var $normalResults = $item.not(selector.addition) ; return ($normalResults.filter(selector.unselectable).length === $normalResults.length); }, userSuggestion: function() { return ($menu.children(selector.addition).length > 0); }, query: function() { return (module.get.query() !== ''); }, value: function(value) { return (settings.ignoreCase) ? module.has.valueIgnoringCase(value) : module.has.valueMatchingCase(value) ; }, valueMatchingCase: function(value) { var values = module.get.values(true), hasValue = Array.isArray(values) ? values && ($.inArray(value, values) !== -1) : (values == value) ; return (hasValue) ? true : false ; }, valueIgnoringCase: function(value) { var values = module.get.values(true), hasValue = false ; if(!Array.isArray(values)) { values = [values]; } $.each(values, function(index, existingValue) { if(String(value).toLowerCase() == String(existingValue).toLowerCase()) { hasValue = true; return false; } }); return hasValue; } }, is: { active: function() { return $module.hasClass(className.active); }, animatingInward: function() { return $menu.transition('is inward'); }, animatingOutward: function() { return $menu.transition('is outward'); }, bubbledLabelClick: function(event) { return $(event.target).is('select, input') && $module.closest('label').length > 0; }, bubbledIconClick: function(event) { return $(event.target).closest($icon).length > 0; }, edge: function() { return !!window.chrome && !!window.StyleMedia; }, chrome: function() { return !!window.chrome && !window.StyleMedia; }, alreadySetup: function() { return ($module.is('select') && $module.parent(selector.dropdown).data(moduleNamespace) !== undefined && $module.prev().length === 0); }, animating: function($subMenu) { return ($subMenu) ? $subMenu.transition && $subMenu.transition('is animating') : $menu.transition && $menu.transition('is animating') ; }, leftward: function($subMenu) { var $selectedMenu = $subMenu || $menu; return $selectedMenu.hasClass(className.leftward); }, clearable: function() { return ($module.hasClass(className.clearable) || settings.clearable); }, disabled: function() { return $module.hasClass(className.disabled); }, focused: function() { return (document.activeElement === $module[0]); }, focusedOnSearch: function() { return (document.activeElement === $search[0]); }, allFiltered: function() { return( (module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered() ); }, hidden: function($subMenu) { return !module.is.visible($subMenu); }, initialLoad: function() { return initialLoad; }, inObject: function(needle, object) { var found = false ; $.each(object, function(index, property) { if(property == needle) { found = true; return true; } }); return found; }, multiple: function() { return $module.hasClass(className.multiple); }, remote: function() { return settings.apiSettings && module.can.useAPI(); }, noApiCache: function() { return settings.apiSettings && !settings.apiSettings.cache }, single: function() { return !module.is.multiple(); }, selectMutation: function(mutations) { var selectChanged = false ; $.each(mutations, function(index, mutation) { if($(mutation.target).is('select, option, optgroup') || $(mutation.addedNodes).is('select')) { selectChanged = true; return false; } }); return selectChanged; }, search: function() { return $module.hasClass(className.search); }, searchSelection: function() { return ( module.has.search() && $search.parent(selector.dropdown).length === 1 ); }, selection: function() { return $module.hasClass(className.selection); }, userValue: function(value) { return ($.inArray(value, module.get.userValues()) !== -1); }, upward: function($menu) { var $element = $menu || $module; return $element.hasClass(className.upward); }, visible: function($subMenu) { return ($subMenu) ? $subMenu.hasClass(className.visible) : $menu.hasClass(className.visible) ; }, verticallyScrollableContext: function() { var overflowY = ($context.get(0) !== window) ? $context.css('overflow-y') : false ; return (overflowY == 'auto' || overflowY == 'scroll'); }, horizontallyScrollableContext: function() { var overflowX = ($context.get(0) !== window) ? $context.css('overflow-X') : false ; return (overflowX == 'auto' || overflowX == 'scroll'); } }, can: { activate: function($item) { if(settings.useLabels) { return true; } if(!module.has.maxSelections()) { return true; } if(module.has.maxSelections() && $item.hasClass(className.active)) { return true; } return false; }, openDownward: function($subMenu) { var $currentMenu = $subMenu || $menu, canOpenDownward = true, onScreen = {}, calculations ; $currentMenu .addClass(className.loading) ; calculations = { context: { offset : ($context.get(0) === window) ? { top: 0, left: 0} : $context.offset(), scrollTop : $context.scrollTop(), height : $context.outerHeight() }, menu : { offset: $currentMenu.offset(), height: $currentMenu.outerHeight() } }; if(module.is.verticallyScrollableContext()) { calculations.menu.offset.top += calculations.context.scrollTop; } if(module.has.subMenu($currentMenu)) { calculations.menu.height += $currentMenu.find(selector.menu).first().outerHeight(); } onScreen = { above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.context.offset.top - calculations.menu.height, below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top - calculations.context.offset.top + calculations.menu.height }; if(onScreen.below) { module.verbose('Dropdown can fit in context downward', onScreen); canOpenDownward = true; } else if(!onScreen.below && !onScreen.above) { module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen); canOpenDownward = true; } else { module.verbose('Dropdown cannot fit below, opening upward', onScreen); canOpenDownward = false; } $currentMenu.removeClass(className.loading); return canOpenDownward; }, openRightward: function($subMenu) { var $currentMenu = $subMenu || $menu, canOpenRightward = true, isOffscreenRight = false, calculations ; $currentMenu .addClass(className.loading) ; calculations = { context: { offset : ($context.get(0) === window) ? { top: 0, left: 0} : $context.offset(), scrollLeft : $context.scrollLeft(), width : $context.outerWidth() }, menu: { offset : $currentMenu.offset(), width : $currentMenu.outerWidth() } }; if(module.is.horizontallyScrollableContext()) { calculations.menu.offset.left += calculations.context.scrollLeft; } isOffscreenRight = (calculations.menu.offset.left - calculations.context.offset.left + calculations.menu.width >= calculations.context.scrollLeft + calculations.context.width); if(isOffscreenRight) { module.verbose('Dropdown cannot fit in context rightward', isOffscreenRight); canOpenRightward = false; } $currentMenu.removeClass(className.loading); return canOpenRightward; }, extendSelect: function() { return settings.allowAdditions || settings.apiSettings; }, show: function() { return !module.is.disabled() && (module.has.items() || module.has.message()); }, useAPI: function() { return $.fn.api !== undefined; } }, animate: { show: function(callback, $subMenu) { var $currentMenu = $subMenu || $menu, start = ($subMenu) ? function() {} : function() { module.hideSubMenus(); module.hideOthers(); module.set.active(); }, transition ; callback = $.isFunction(callback) ? callback : function(){} ; module.verbose('Doing menu show animation', $currentMenu); module.set.direction($subMenu); transition = settings.transition.showMethod || module.get.transition($subMenu); if( module.is.selection() ) { module.set.scrollPosition(module.get.selectedItem(), true); } if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) { if(transition === 'none') { start(); $currentMenu.transition({ displayType: module.get.displayType() }).transition('show'); callback.call(element); } else if($.fn.transition !== undefined && $module.transition('is supported')) { $currentMenu .transition({ animation : transition + ' in', debug : settings.debug, verbose : settings.verbose, duration : settings.transition.showDuration || settings.duration, queue : true, onStart : start, displayType: module.get.displayType(), onComplete : function() { callback.call(element); } }) ; } else { module.error(error.noTransition, transition); } } }, hide: function(callback, $subMenu) { var $currentMenu = $subMenu || $menu, start = ($subMenu) ? function() {} : function() { module.unbind.intent(); module.remove.active(); }, transition = settings.transition.hideMethod || module.get.transition($subMenu) ; callback = $.isFunction(callback) ? callback : function(){} ; if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) { module.verbose('Doing menu hide animation', $currentMenu); if(transition === 'none') { start(); $currentMenu.transition({ displayType: module.get.displayType() }).transition('hide'); callback.call(element); } else if($.fn.transition !== undefined && $module.transition('is supported')) { $currentMenu .transition({ animation : transition + ' out', duration : settings.transition.hideDuration || settings.duration, debug : settings.debug, verbose : settings.verbose, queue : false, onStart : start, displayType: module.get.displayType(), onComplete : function() { callback.call(element); } }) ; } else { module.error(error.transition); } } } }, hideAndClear: function() { module.remove.searchTerm(); if( module.has.maxSelections() ) { return; } if(module.has.search()) { module.hide(function() { module.remove.filteredItem(); }); } else { module.hide(); } }, delay: { show: function() { module.verbose('Delaying show event to ensure user intent'); clearTimeout(module.timer); module.timer = setTimeout(module.show, settings.delay.show); }, hide: function() { module.verbose('Delaying hide event to ensure user intent'); clearTimeout(module.timer); module.timer = setTimeout(module.hide, settings.delay.hide); } }, escape: { value: function(value) { var multipleValues = Array.isArray(value), stringValue = (typeof value === 'string'), isUnparsable = (!stringValue && !multipleValues), hasQuotes = (stringValue && value.search(regExp.quote) !== -1), values = [] ; if(isUnparsable || !hasQuotes) { return value; } module.debug('Encoding quote values for use in select', value); if(multipleValues) { $.each(value, function(index, value){ values.push(value.replace(regExp.quote, '"')); }); return values; } return value.replace(regExp.quote, '"'); }, string: function(text) { text = String(text); return text.replace(regExp.escape, '\\$&'); }, htmlEntities: function(string) { var badChars = /[<>"'`]/g, shouldEscape = /[&<>"'`]/, escape = { "<": "<", ">": ">", '"': """, "'": "'", "`": "`" }, escapedChar = function(chr) { return escape[chr]; } ; if(shouldEscape.test(string)) { string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); return string.replace(badChars, escapedChar); } return string; } }, setting: function(name, value) { module.debug('Changing setting', name, value); if( $.isPlainObject(name) ) { $.extend(true, settings, name); } else if(value !== undefined) { if($.isPlainObject(settings[name])) { $.extend(true, settings[name], value); } else { settings[name] = value; } } else { return settings[name]; } }, internal: function(name, value) { if( $.isPlainObject(name) ) { $.extend(true, module, name); } else if(value !== undefined) { module[name] = value; } else { return module[name]; } }, debug: function() { if(!settings.silent && settings.debug) { if(settings.performance) { module.performance.log(arguments); } else { module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); module.debug.apply(console, arguments); } } }, verbose: function() { if(!settings.silent && settings.verbose && settings.debug) { if(settings.performance) { module.performance.log(arguments); } else { module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); module.verbose.apply(console, arguments); } } }, error: function() { if(!settings.silent) { module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); module.error.apply(console, arguments); } }, performance: { log: function(message) { var currentTime, executionTime, previousTime ; if(settings.performance) { currentTime = new Date().getTime(); previousTime = time || currentTime; executionTime = currentTime - previousTime; time = currentTime; performance.push({ 'Name' : message[0], 'Arguments' : [].slice.call(message, 1) || '', 'Element' : element, 'Execution Time' : executionTime }); } clearTimeout(module.performance.timer); module.performance.timer = setTimeout(module.performance.display, 500); }, display: function() { var title = settings.name + ':', totalTime = 0 ; time = false; clearTimeout(module.performance.timer); $.each(performance, function(index, data) { totalTime += data['Execution Time']; }); title += ' ' + totalTime + 'ms'; if(moduleSelector) { title += ' \'' + moduleSelector + '\''; } if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { console.groupCollapsed(title); if(console.table) { console.table(performance); } else { $.each(performance, function(index, data) { console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); }); } console.groupEnd(); } performance = []; } }, invoke: function(query, passedArguments, context) { var object = instance, maxDepth, found, response ; passedArguments = passedArguments || queryArguments; context = element || context; if(typeof query == 'string' && object !== undefined) { query = query.split(/[\. ]/); maxDepth = query.length - 1; $.each(query, function(depth, value) { var camelCaseValue = (depth != maxDepth) ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) : query ; if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { object = object[camelCaseValue]; } else if( object[camelCaseValue] !== undefined ) { found = object[camelCaseValue]; return false; } else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { object = object[value]; } else if( object[value] !== undefined ) { found = object[value]; return false; } else { module.error(error.method, query); return false; } }); } if ( $.isFunction( found ) ) { response = found.apply(context, passedArguments); } else if(found !== undefined) { response = found; } if(Array.isArray(returnedValue)) { returnedValue.push(response); } else if(returnedValue !== undefined) { returnedValue = [returnedValue, response]; } else if(response !== undefined) { returnedValue = response; } return found; } }; if(methodInvoked) { if(instance === undefined) { module.initialize(); } module.invoke(query); } else { if(instance !== undefined) { instance.invoke('destroy'); } module.initialize(); } }) ; return (returnedValue !== undefined) ? returnedValue : $allModules ; }; $.fn.dropdown.settings = { silent : false, debug : false, verbose : false, performance : true, on : 'click', // what event should show menu action on item selection action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){}) values : false, // specify values to use for dropdown clearable : false, // whether the value of the dropdown can be cleared apiSettings : false, selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used minCharacters : 0, // Minimum characters required to trigger API call filterRemoteData : false, // Whether API results should be filtered after being returned for query term saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh throttle : 200, // How long to wait after last user input to search remotely context : window, // Context to use when determining if on screen direction : 'auto', // Whether dropdown should always open in one direction keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing match : 'both', // what to match against with search selection (both, text, or label) fullTextSearch : 'exact', // search anywhere in value (set to 'exact' to require exact matches) ignoreDiacritics : false, // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...) hideDividers : false, // Whether to hide any divider elements (specified in selector.divider) that are sibling to any items when searched (set to true will hide all dividers, set to 'empty' will hide them when they are not followed by a visible item) placeholder : 'auto', // whether to convert blank the values will be delimited with this character showOnFocus : false, // 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 displayType : false, // displayType of transition glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width headerDivider : true, // whether option headers should have an additional divider line underneath when converted from requires multiple property to be set to correctly preserve multiple values', method : 'The method you called is not defined.', noAPI : 'The API module is required to load resources remotely', noStorage : 'Saving remote data requires session storage', noTransition : 'This module requires ui transitions ', noNormalize : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including as a polyfill.' }, regExp : { escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g, quote : /"/g }, metadata : { defaultText : 'defaultText', defaultValue : 'defaultValue', placeholderText : 'placeholder', text : 'text', value : 'value' }, // property names for remote query fields: { remoteValues : 'results', // grouping for api results values : 'values', // grouping for all dropdown values disabled : 'disabled', // whether value should be disabled name : 'name', // displayed dropdown text description : 'description', // displayed dropdown description descriptionVertical : 'descriptionVertical', // whether description should be vertical value : 'value', // actual dropdown value text : 'text', // displayed text when selected type : 'type', // type of dropdown element image : 'image', // optional image path imageClass : 'imageClass', // optional individual class for image icon : 'icon', // optional icon name iconClass : 'iconClass', // optional individual class for icon (for example to use flag instead) class : 'class', // optional individual class for item/header divider : 'divider', // optional divider append for group headers actionable : 'actionable' // optional actionable item }, keys : { backspace : 8, deleteKey : 46, enter : 13, escape : 27, pageUp : 33, pageDown : 34, leftArrow : 37, upArrow : 38, rightArrow : 39, downArrow : 40 }, selector : { addition : '.addition', divider : '.divider, .header', dropdown : '.ui.dropdown', hidden : '.hidden', icon : '> .dropdown.icon', input : '> input[type="hidden"], > select', item : '.item', label : '> .label', remove : '> .label > .delete.icon', siblingLabel : '.label', menu : '.menu', message : '.message', menuIcon : '.dropdown.icon', search : 'input.search, .menu > .search > input, .menu input.search', sizer : '> span.sizer', text : '> .text:not(.icon)', unselectable : '.disabled, .filtered', clearIcon : '> .remove.icon' }, className : { active : 'active', addition : 'addition', animating : 'animating', description : 'description', descriptionVertical : 'vertical', disabled : 'disabled', empty : 'empty', dropdown : 'ui dropdown', filtered : 'filtered', hidden : 'hidden transition', icon : 'icon', image : 'image', item : 'item', label : 'ui label', loading : 'loading', menu : 'menu', message : 'message', multiple : 'multiple', placeholder : 'default', sizer : 'sizer', search : 'search', selected : 'selected', selection : 'selection', text : 'text', upward : 'upward', leftward : 'left', visible : 'visible', clearable : 'clearable', noselection : 'noselection', delete : 'delete', header : 'header', divider : 'divider', groupIcon : '', unfilterable : 'unfilterable', actionable : 'actionable' } }; /* Templates */ $.fn.dropdown.settings.templates = { deQuote: function(string, encode) { return String(string).replace(/"/g,encode ? """ : ""); }, escape: function(string, preserveHTML) { if (preserveHTML){ return string; } var badChars = /[<>"'`]/g, shouldEscape = /[&<>"'`]/, escape = { "<": "<", ">": ">", '"': """, "'": "'", "`": "`" }, escapedChar = function(chr) { return escape[chr]; } ; if(shouldEscape.test(string)) { string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); return string.replace(badChars, escapedChar); } return string; }, // generates dropdown from select values dropdown: function(select, fields, preserveHTML, className) { var placeholder = select.placeholder || false, html = '', escape = $.fn.dropdown.settings.templates.escape ; html += ''; if(placeholder) { html += '
' + escape(placeholder,preserveHTML) + '
'; } else { html += '
'; } html += '
'; html += $.fn.dropdown.settings.templates.menu(select, fields, preserveHTML,className); html += '
'; return html; }, // generates just menu from select menu: function(response, fields, preserveHTML, className) { var values = response[fields.values] || [], html = '', escape = $.fn.dropdown.settings.templates.escape, deQuote = $.fn.dropdown.settings.templates.deQuote ; $.each(values, function(index, option) { var itemType = (option[fields.type]) ? option[fields.type] : 'item', isMenu = itemType.indexOf('menu') !== -1 ; if( itemType === 'item' || isMenu) { var maybeText = (option[fields.text]) ? ' data-text="' + deQuote(option[fields.text],true) + '"' : '', maybeActionable = (option[fields.actionable]) ? className.actionable+' ' : '', maybeDisabled = (option[fields.disabled]) ? className.disabled+' ' : '', maybeDescriptionVertical = (option[fields.descriptionVertical]) ? className.descriptionVertical+' ' : '', hasDescription = (escape(option[fields.description] || '', preserveHTML) != '') ; html += '
'; if (isMenu) { html += ''; } if(option[fields.image]) { html += ''; } if(option[fields.icon]) { html += ''; } if(hasDescription){ html += ''+ escape(option[fields.description] || '', preserveHTML) + ''; html += (!isMenu) ? '' : ''; } if (isMenu) { html += ''; } html += escape(option[fields.name] || '', preserveHTML); if (isMenu) { html += ''; html += '
'; html += $.fn.dropdown.settings.templates.menu(option, fields, preserveHTML, className); html += '
'; } else if(hasDescription){ html += '
'; } html += '
'; } else if (itemType === 'header') { var groupName = escape(option[fields.name] || '', preserveHTML), groupIcon = option[fields.icon] ? deQuote(option[fields.icon]) : className.groupIcon ; if(groupName !== '' || groupIcon !== '') { html += '
'; if (groupIcon !== '') { html += ''; } html += groupName; html += '
'; } if(option[fields.divider]){ html += '
'; } } }); return html; }, // generates label for multiselect label: function(value, text, preserveHTML, className) { var escape = $.fn.dropdown.settings.templates.escape; return escape(text,preserveHTML) + ''; }, // generates messages like "No results" message: function(message) { return message; }, // generates user addition to selection menu addition: function(choice) { return choice; } }; })( jQuery, window, document );