From 1b762fc5d8442b0564dcc53e5cd4ed7d99fe782d Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 30 Mar 2021 19:27:06 +0200 Subject: [PATCH] Remove vendored copy of fomantic-dropdown (#15193) jQuery 3.6.0 seems to have broke the dropdown focus handling (focus would get stuck on the dropdown) in this module which we have vendored on top of fomantic for accessibility improvements. Either downgrading jQuery to 3.5.1 or removing the vendor copy seems to resolve the issue and I opted for removing the copy because I think such changes should be done upstream and the removal also lightens the JS by 155kB before minify/gzip. Fixes: https://github.com/go-gitea/gitea/issues/15172 Co-authored-by: Lunny Xiao Co-authored-by: techknowlogick --- .eslintrc | 1 - web_src/js/index.js | 1 - web_src/js/vendor/semanticdropdown.js | 4300 ------------------------- 3 files changed, 4302 deletions(-) delete mode 100644 web_src/js/vendor/semanticdropdown.js diff --git a/.eslintrc b/.eslintrc index 5f031f97f..032b609f9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,7 +2,6 @@ root: true reportUnusedDisableDirectives: true ignorePatterns: - - /web_src/js/vendor - /templates/base/head.tmpl - /templates/repo/activity.tmpl - /templates/repo/view_file.tmpl diff --git a/web_src/js/index.js b/web_src/js/index.js index aa9b3be00..28f2087e3 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -5,7 +5,6 @@ import './publicpath.js'; import Vue from 'vue'; import {htmlEscape} from 'escape-goat'; import 'jquery.are-you-sure'; -import './vendor/semanticdropdown.js'; import initMigration from './features/migration.js'; import initContextPopups from './features/contextpopup.js'; diff --git a/web_src/js/vendor/semanticdropdown.js b/web_src/js/vendor/semanticdropdown.js deleted file mode 100644 index c9144adf5..000000000 --- a/web_src/js/vendor/semanticdropdown.js +++ /dev/null @@ -1,4300 +0,0 @@ -/*! - * # Fomantic-UI - Dropdown - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/* - * Copyright 2019 The Gitea Authors - * Released under the MIT license - * http://opensource.org/licenses/MIT - * This version has been modified by Gitea to improve accessibility. - */ - -;(function ($, window, document, undefined) { - -'use strict'; - -$.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 || '', - - hasTouch = ('ontouchstart' in document.documentElement), - clickEvent = hasTouch - ? 'touchstart' - : 'click', - - time = new Date().getTime(), - performance = [], - - query = arguments[0], - methodInvoked = (typeof query == 'string'), - queryArguments = [].slice.call(arguments, 1), - lastAriaID = 1, - returnedValue - ; - - $allModules - .each(function(elementIndex) { - var - settings = ( $.isPlainObject(parameters) ) - ? $.extend(true, {}, $.fn.dropdown.settings, parameters) - : $.extend({}, $.fn.dropdown.settings), - - className = settings.className, - message = settings.message, - fields = settings.fields, - keys = settings.keys, - metadata = settings.metadata, - namespace = settings.namespace, - regExp = settings.regExp, - selector = settings.selector, - error = settings.error, - templates = settings.templates, - - eventNamespace = '.' + namespace, - moduleNamespace = 'module-' + namespace, - - $module = $(this), - $context = $(settings.context), - $text = $module.find(selector.text), - $search = $module.find(selector.search), - $sizer = $module.find(selector.sizer), - $input = $module.find(selector.input), - $icon = $module.find(selector.icon), - $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, - instance = $module.data(moduleNamespace), - - selectActionActive, - initialLoad, - pageLostFocus, - willRefocus, - elementNamespace, - id, - selectObserver, - menuObserver, - 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.change.values(settings.values); - } - - module.refreshData(); - - module.save.defaults(); - module.restore.selected(); - - module.create.id(); - module.bind.events(); - - module.observeChanges(); - module.instantiate(); - - module.aria.setup(); - } - - }, - - instantiate: function() { - module.verbose('Storing instance of dropdown', module); - instance = module; - $module - .data(moduleNamespace, module) - ; - }, - - destroy: function() { - module.verbose('Destroying previous dropdown', $module); - module.remove.tabbable(); - 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(); - }, - - observeChanges: function() { - if('MutationObserver' in window) { - selectObserver = new MutationObserver(module.event.select.mutation); - menuObserver = new MutationObserver(module.event.menu.mutation); - module.debug('Setting up mutation observer', selectObserver, menuObserver); - module.observe.select(); - module.observe.menu(); - } - }, - - disconnect: { - menuObserver: function() { - if(menuObserver) { - menuObserver.disconnect(); - } - }, - selectObserver: function() { - if(selectObserver) { - selectObserver.disconnect(); - } - } - }, - observe: { - select: function() { - if(module.has.input() && selectObserver) { - selectObserver.observe($module[0], { - childList : true, - subtree : true - }); - } - }, - menu: function() { - if(module.has.menu() && menuObserver) { - 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 = 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(module.has.minCharacters(query)) { - 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); - } - } - }, - - aria: { - setup: function() { - var role = module.aria.guessRole(); - if( role !== 'menu' ) { - return; - } - $module.attr('aria-busy', 'true'); - $module.attr('role', 'menu'); - $module.attr('aria-haspopup', 'menu'); - $module.attr('aria-expanded', 'false'); - $menu.find('.divider').attr('role', 'separator'); - $item.attr('role', 'menuitem'); - $item.each(function (index, item) { - if( !item.id ) { - item.id = module.aria.nextID('menuitem'); - } - }); - $text = $module - .find('> .text') - .eq(0) - ; - if( $module.data('content') ) { - $text.attr('aria-hidden'); - $module.attr('aria-label', $module.data('content')); - } - else { - $text.attr('id', module.aria.nextID('menutext')); - $module.attr('aria-labelledby', $text.attr('id')); - } - $module.attr('aria-busy', 'false'); - }, - nextID: function(prefix) { - var nextID; - do { - nextID = prefix + '_' + lastAriaID++; - } while( document.getElementById(nextID) ); - return nextID; - }, - setExpanded: function(expanded) { - if( $module.attr('aria-haspopup') ) { - $module.attr('aria-expanded', expanded); - } - }, - refreshDescendant: function() { - if( $module.attr('aria-haspopup') !== 'menu' ) { - return; - } - var - $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0), - $activeItem = $menu.children('.' + className.active).eq(0), - $selectedItem = ($currentlySelected.length > 0) - ? $currentlySelected - : $activeItem - ; - if( $selectedItem ) { - $module.attr('aria-activedescendant', $selectedItem.attr('id')); - } - else { - module.aria.removeDescendant(); - } - }, - removeDescendant: function() { - if( $module.attr('aria-haspopup') == 'menu' ) { - $module.removeAttr('aria-activedescendant'); - } - }, - guessRole: function() { - var - isIcon = $module.hasClass('icon'), - hasSearch = module.has.search(), - hasInput = ($input.length > 0), - isMultiple = module.is.multiple() - ; - if ( !isIcon && !hasSearch && !hasInput && !isMultiple ) { - return 'menu'; - } - return 'unknown'; - } - }, - - setup: { - api: function() { - var - apiSettings = { - debug : settings.debug, - urlData : { - value : module.get.value(), - query : module.get.query() - }, - on : false - } - ; - module.verbose('First request, initializing API'); - $module - .api(apiSettings) - ; - }, - layout: function() { - if( $module.is('select') ) { - module.setup.select(); - module.setup.returnedObject(); - } - if( !module.has.menu() ) { - module.create.menu(); - } - if ( module.is.selection() && 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', 'off') - .insertBefore($text) - ; - } - if( module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) { - module.create.sizer(); - } - if(settings.allowTab) { - module.set.tabbable(); - } - $item.attr('tabindex', '-1'); - }, - select: function() { - var - selectValues = module.get.selectValues() - ; - module.debug('Dropdown initialized on a select', selectValues); - if( $module.is('select') ) { - $input = $module; - } - // see if select is placed correctly already - if($input.parent(selector.dropdown).length > 0) { - module.debug('UI dropdown already exists. Creating dropdown menu only'); - $module = $input.closest(selector.dropdown); - if( !module.has.menu() ) { - module.create.menu(); - } - $menu = $module.children(selector.menu); - module.setup.menu(selectValues); - } - else { - module.debug('Creating entire dropdown from select'); - $module = $('
') - .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); - } - $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) - ; - }, - - 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(!module.can.show() && module.is.remote()) { - module.debug('No API results retrieved, searching before show'); - module.queryRemote(module.get.query(), module.show); - } - if( module.can.show() && !module.is.active() ) { - module.debug('Showing dropdown'); - if(module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered()) ) { - module.remove.message(); - } - if(module.is.allFiltered()) { - return true; - } - if(settings.onShow.call(element) !== false) { - module.aria.setExpanded(true); - module.aria.refreshDescendant(); - module.animate.show(function() { - if( module.can.click() ) { - module.bind.intent(); - } - 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.aria.setExpanded(false); - module.aria.removeDescendant(); - module.animate.hide(function() { - module.remove.visible(); - // hidding search focus - if ( module.is.focusedOnSearch() && preventBlur !== true ) { - $search.blur(); - } - callback.call(element); - }); - } - } else if( module.can.click() ) { - module.unbind.intent(); - } - }, - - hideOthers: function() { - module.verbose('Finding other dropdowns to hide'); - $allModules - .not($module) - .has(selector.menu + '.' + className.visible) - .dropdown('hide') - ; - }, - - hideMenu: function() { - module.verbose('Hiding menu instantaneously'); - module.remove.active(); - module.remove.visible(); - $menu.transition('hide'); - }, - - hideSubMenus: function() { - var - $subMenus = $menu.children(selector.item).find(selector.menu) - ; - module.verbose('Hiding sub menus', $subMenus); - $subMenus.transition('hide'); - }, - - bind: { - events: function() { - module.bind.keyboardEvents(); - module.bind.inputEvents(); - module.bind.mouseEvents(); - }, - keyboardEvents: function() { - module.verbose('Binding keyboard events'); - $module - .on('keydown' + eventNamespace, module.event.keydown) - ; - if( module.has.search() ) { - $module - .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input) - ; - } - if( module.is.multiple() ) { - $document - .on('keydown' + elementNamespace, module.event.document.keydown) - ; - } - }, - inputEvents: function() { - module.verbose('Binding input change events'); - $module - .on('change' + eventNamespace, selector.input, module.event.change) - ; - }, - mouseEvents: function() { - module.verbose('Binding mouse events'); - if(module.is.multiple()) { - $module - .on(clickEvent + eventNamespace, selector.label, module.event.label.click) - .on(clickEvent + 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(clickEvent + eventNamespace, selector.icon, module.event.icon.click) - .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) - .on('focus' + eventNamespace, selector.search, module.event.search.focus) - .on(clickEvent + eventNamespace, selector.search, module.event.search.focus) - .on('blur' + eventNamespace, selector.search, module.event.search.blur) - .on(clickEvent + eventNamespace, selector.text, module.event.text.focus) - ; - if(module.is.multiple()) { - $module - .on(clickEvent + eventNamespace, module.event.click) - ; - } - } - else { - if(settings.on == 'click') { - $module - .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) - .on(clickEvent + 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(clickEvent + 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((hasTouch ? 'touchstart' : '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(clickEvent + 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(clickEvent + 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) : []; - } - $.each(preSelected,function(index,value){ - $item.filter('[data-value="'+value+'"]') - .addClass(className.filtered) - ; - }); - 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) { - var - values = response[fields.remoteValues] - ; - if (!Array.isArray(values)){ - values = []; - } - module.remove.message(); - module.setup.menu({ - values: values - }); - - if(values.length===0 && !settings.allowAdditions) { - module.add.message(message.noResults); - } - 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 = 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); - $selectedItem[0].click(); - return; - } - else { - module.remove.searchTerm(); - } - }, - - change: { - values: function(values) { - if(!settings.allowAdditions) { - module.clear(); - } - module.debug('Creating dropdown with specified values', values); - module.setup.menu({values: values}); - $.each(values, function(index, item) { - if(item.selected == true) { - 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: { - 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(event) { - activated = true; - if(module.is.multiple()) { - module.remove.activeLabel(); - } - if(settings.showOnFocus || (event.type !== 'focus' && event.type !== 'focusin')) { - 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(); - } - } - }, - text: { - focus: function(event) { - activated = true; - module.focusSearch(); - } - }, - input: function(event) { - if(module.is.multiple() || module.is.searchSelection()) { - module.set.filtered(); - } - clearTimeout(module.timer); - module.timer = setTimeout(module.search, settings.delay.search); - }, - label: { - click: function(event) { - var - $label = $(this), - $labels = $module.find(selector.label), - $activeLabels = $labels.filter('.' + className.active), - $nextActive = $label.nextAll('.' + className.active), - $prevActive = $label.prevAll('.' + className.active), - $range = ($nextActive.length > 0) - ? $label.nextUntil($nextActive).add($activeLabels).add($label) - : $label.prevUntil($prevActive).add($activeLabels).add($label) - ; - if(event.shiftKey) { - $activeLabels.removeClass(className.active); - $range.addClass(className.active); - } - else if(event.ctrlKey) { - $label.toggleClass(className.active); - } - else { - $activeLabels.removeClass(className.active); - $label.addClass(className.active); - } - settings.onLabelSelect.apply(this, $labels.filter('.' + className.active)); - } - }, - remove: { - click: function() { - var - $label = $(this).parent() - ; - if( $label.hasClass(className.active) ) { - // remove all selected labels - module.remove.activeLabels(); - } - else { - // remove this label only - module.remove.activeLabels( $label ); - } - } - }, - test: { - toggle: function(event) { - var - toggleBehavior = (module.is.multiple()) - ? module.show - : module.toggle - ; - if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) { - return; - } - if( module.determine.eventOnElement(event, toggleBehavior) ) { - event.preventDefault(); - } - }, - touch: function(event) { - module.determine.eventOnElement(event, function() { - if(event.type == 'touchstart') { - module.timer = setTimeout(function() { - module.hide(); - }, settings.delay.touch); - } - else if(event.type == 'touchmove') { - clearTimeout(module.timer); - } - }); - event.stopPropagation(); - }, - hide: function(event) { - if(module.determine.eventInModule(event, module.hide)){ - if(element.id && $(event.target).attr('for') === element.id){ - event.preventDefault(); - } - } - } - }, - select: { - mutation: function(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); - 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 - $labels = $module.find(selector.label), - $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(settings.ignoreCase ? value.toLowerCase() : value) +'"]') - ; - module.verbose('Removing label', $removedLabel); - $removedLabel.remove(); - }, - activeLabels: function($activeLabels) { - $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active); - module.verbose('Removing active label selections', $activeLabels); - module.remove.labels($activeLabels); - }, - labels: function($labels) { - $labels = $labels || $module.find(selector.label); - module.verbose('Removing labels', $labels); - $labels - .each(function(){ - var - $label = $(this), - value = $label.data(metadata.value), - stringValue = (value !== undefined) - ? String(value) - : value, - isUserValue = module.is.userValue(stringValue) - ; - if(settings.onLabelRemove.call($label, value) === false) { - module.debug('Label remove callback cancelled removal'); - return; - } - module.remove.message(); - if(isUserValue) { - module.remove.value(stringValue); - module.remove.label(stringValue); - } - else { - // selected will also remove label - module.remove.selected(stringValue); - } - }) - ; - }, - tabbable: function() { - if( module.is.searchSelection() ) { - module.debug('Searchable dropdown initialized'); - $search - .removeAttr('tabindex') - ; - $menu - .removeAttr('tabindex') - ; - } - else { - module.debug('Simple selection dropdown initialized'); - $module - .removeAttr('tabindex') - ; - $menu - .removeAttr('tabindex') - ; - } - }, - 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); - }, - 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(), - hasValue = Array.isArray(values) - ? values && ($.inArray(value, values) !== -1) - : (values == value) - ; - return (hasValue) - ? true - : false - ; - }, - valueIgnoringCase: function(value) { - var - values = module.get.values(), - 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; - }, - 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(); - }, - single: function() { - return !module.is.multiple(); - }, - selectMutation: function(mutations) { - var - selectChanged = false - ; - $.each(mutations, function(index, mutation) { - if($(mutation.target).is('select') || $(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; - } - onScreen = { - above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.context.offset.top - calculations.menu.height, - below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top - calculations.context.offset.top + calculations.menu.height - }; - if(onScreen.below) { - module.verbose('Dropdown can fit in context downward', onScreen); - canOpenDownward = true; - } - else if(!onScreen.below && !onScreen.above) { - module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen); - canOpenDownward = true; - } - else { - module.verbose('Dropdown cannot fit below, opening upward', onScreen); - canOpenDownward = false; - } - $currentMenu.removeClass(className.loading); - return canOpenDownward; - }, - openRightward: function($subMenu) { - var - $currentMenu = $subMenu || $menu, - canOpenRightward = true, - isOffscreenRight = false, - calculations - ; - $currentMenu - .addClass(className.loading) - ; - calculations = { - context: { - offset : ($context.get(0) === window) - ? { top: 0, left: 0} - : $context.offset(), - scrollLeft : $context.scrollLeft(), - width : $context.outerWidth() - }, - menu: { - offset : $currentMenu.offset(), - width : $currentMenu.outerWidth() - } - }; - if(module.is.horizontallyScrollableContext()) { - calculations.menu.offset.left += calculations.context.scrollLeft; - } - isOffscreenRight = (calculations.menu.offset.left - calculations.context.offset.left + calculations.menu.width >= calculations.context.scrollLeft + calculations.context.width); - if(isOffscreenRight) { - module.verbose('Dropdown cannot fit in context rightward', isOffscreenRight); - canOpenRightward = false; - } - $currentMenu.removeClass(className.loading); - return canOpenRightward; - }, - click: function() { - return (hasTouch || settings.on == 'click'); - }, - extendSelect: function() { - return settings.allowAdditions || settings.apiSettings; - }, - show: function() { - return !module.is.disabled() && (module.has.items() || module.has.message()); - }, - useAPI: function() { - return $.fn.api !== undefined; - } - }, - - animate: { - show: function(callback, $subMenu) { - var - $currentMenu = $subMenu || $menu, - start = ($subMenu) - ? function() {} - : function() { - module.hideSubMenus(); - module.hideOthers(); - module.set.active(); - }, - transition - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - module.verbose('Doing menu show animation', $currentMenu); - module.set.direction($subMenu); - transition = module.get.transition($subMenu); - if( module.is.selection() ) { - module.set.scrollPosition(module.get.selectedItem(), true); - } - if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) { - if(transition == 'none') { - start(); - $currentMenu.transition('show'); - callback.call(element); - } - else if($.fn.transition !== undefined && $module.transition('is supported')) { - $currentMenu - .transition({ - animation : transition + ' in', - debug : settings.debug, - verbose : settings.verbose, - duration : settings.duration, - queue : true, - onStart : start, - onComplete : function() { - callback.call(element); - } - }) - ; - } - else { - module.error(error.noTransition, transition); - } - } - }, - hide: function(callback, $subMenu) { - var - $currentMenu = $subMenu || $menu, - start = ($subMenu) - ? function() {} - : function() { - if( module.can.click() ) { - module.unbind.intent(); - } - module.remove.active(); - }, - transition = module.get.transition($subMenu) - ; - callback = $.isFunction(callback) - ? callback - : function(){} - ; - if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) { - module.verbose('Doing menu hide animation', $currentMenu); - - if(transition == 'none') { - start(); - $currentMenu.transition('hide'); - callback.call(element); - } - else if($.fn.transition !== undefined && $module.transition('is supported')) { - $currentMenu - .transition({ - animation : transition + ' out', - duration : settings.duration, - debug : settings.debug, - verbose : settings.verbose, - queue : false, - onStart : start, - onComplete : function() { - callback.call(element); - } - }) - ; - } - else { - module.error(error.transition); - } - } - } - }, - - hideAndClear: function() { - module.remove.searchTerm(); - if( module.has.maxSelections() ) { - return; - } - if(module.has.search()) { - module.hide(function() { - module.remove.filteredItem(); - }); - } - else { - module.hide(); - } - }, - - delay: { - show: function() { - module.verbose('Delaying show event to ensure user intent'); - clearTimeout(module.timer); - module.timer = setTimeout(module.show, settings.delay.show); - }, - hide: function() { - module.verbose('Delaying hide event to ensure user intent'); - clearTimeout(module.timer); - module.timer = setTimeout(module.hide, settings.delay.hide); - } - }, - - escape: { - value: function(value) { - var - 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 : false, // 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 : true, // show menu on focus - allowReselection : false, // whether current value should trigger callbacks when reselected - allowTab : true, // add tabindex to element - allowCategorySelection : false, // allow elements with sub-menus to be selected - - fireOnInit : false, // Whether callbacks should fire when initializing dropdown values - - transition : 'auto', // auto transition will slide down or up based on direction - duration : 200, // duration of transition - - glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width - - 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 - 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 - }, - - keys : { - backspace : 8, - delimiter : 188, // comma - deleteKey : 46, - enter : 13, - escape : 27, - pageUp : 33, - pageDown : 34, - leftArrow : 37, - upArrow : 38, - rightArrow : 39, - downArrow : 40 - }, - - selector : { - addition : '.addition', - 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 : '> input.sizer', - text : '> .text:not(.icon)', - unselectable : '.disabled, .filtered', - clearIcon : '> .remove.icon' - }, - - className : { - active : 'active', - addition : 'addition', - animating : 'animating', - 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', - upward : 'upward', - leftward : 'left', - visible : 'visible', - clearable : 'clearable', - noselection : 'noselection', - delete : 'delete', - header : 'header', - divider : 'divider', - groupIcon : '', - unfilterable : 'unfilterable' - } - -}; - -/* Templates */ -$.fn.dropdown.settings.templates = { - deQuote: function(string) { - return String(string).replace(/"/g,""); - }, - 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' - ; - - if( itemType === 'item' ) { - var - maybeText = (option[fields.text]) - ? ' data-text="' + deQuote(option[fields.text]) + '"' - : '', - maybeDisabled = (option[fields.disabled]) - ? className.disabled+' ' - : '' - ; - html += '
'; - if(option[fields.image]) { - html += ''; - } - if(option[fields.icon]) { - html += ''; - } - html += escape(option[fields.name] || '', preserveHTML); - 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 );