assets/javascripts/semantic_ui/definitions/modules/dropdown.js in less-rails-semantic_ui-1.7.3.0 vs assets/javascripts/semantic_ui/definitions/modules/dropdown.js in less-rails-semantic_ui-1.8.0.0

- old
+ new

@@ -71,11 +71,18 @@ module = { initialize: function() { module.debug('Initializing dropdown', settings); - module.setup.layout(); + + if( module.is.alreadySetup() ) { + module.error(error.alreadySetup); + } + else { + module.setup.layout(); + } + module.save.defaults(); module.set.selected(); module.create.id(); @@ -102,20 +109,29 @@ module.remove.tabbable(); $module .off(eventNamespace) .removeData(moduleNamespace) ; + $menu + .off(eventNamespace) + ; $document .off(elementNamespace) ; }, observeChanges: function() { if('MutationObserver' in window) { observer = new MutationObserver(function(mutations) { - module.debug('DOM tree modified, updating selector cache'); - module.refresh(); + if( module.is.selectMutation(mutations) ) { + module.debug('<select> modified, recreating menu'); + module.setup.select(); + } + else { + module.debug('DOM tree modified, updating selector cache'); + module.refresh(); + } }); observer.observe(element, { childList : true, subtree : true }); @@ -160,28 +176,31 @@ module.set.tabbable(); } }, select: function() { var - selectValues = module.get.selectValues() + selectValues = module.get.selectValues() ; module.debug('Dropdown initialized on a select', selectValues); - // see if select exists inside a dropdown - $input = $module; - if($input.parents(selector.dropdown).length > 0) { - module.debug('Creating dropdown menu only from template'); + 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.find('.' + className.dropdown).length === 0) { - $('<div />') + $menu = $module.children(selector.menu); + if($menu.length === 0) { + $menu = $('<div />') .addClass(className.menu) - .html( settings.templates.menu( selectValues )) .appendTo($module) ; } + $menu.html( settings.templates.menu( selectValues )); } else { - module.debug('Creating entire dropdown from template'); + module.debug('Creating entire dropdown from select'); $module = $('<div />') .attr('class', $input.attr('class') ) .addClass(className.selection) .addClass(className.dropdown) .html( settings.templates.dropdown(selectValues) ) @@ -195,13 +214,18 @@ module.refresh(); } }, refresh: function() { + module.verbose('Refreshing selector cache'); $text = $module.find(selector.text); $search = $module.find(selector.search); $input = $module.find(selector.input); + $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); }, toggle: function() { @@ -217,11 +241,14 @@ show: function(callback) { callback = $.isFunction(callback) ? callback : function(){} ; - if( module.can.show() && !module.is.active() && !module.is.allFiltered() ) { + if( module.is.searchSelection() && module.is.allFiltered() ) { + return; + } + if( module.can.show() && !module.is.active() ) { module.debug('Showing dropdown'); module.animate.show(function() { if( module.can.click() ) { module.bind.intent(); } @@ -289,11 +316,10 @@ .on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter) ; }, mouseEvents: function() { module.verbose('Mouse detected binding mouse events'); - if( module.is.searchSelection() ) { $module .on('mousedown' + eventNamespace, selector.menu, module.event.menu.activate) .on('mouseup' + eventNamespace, selector.menu, module.event.menu.deactivate) .on('click' + eventNamespace, selector.search, module.show) @@ -463,91 +489,141 @@ clearTimeout(module.timer); module.timer = setTimeout(module.search, settings.delay.search); }, keydown: function(event) { var - $selectedItem = $item.not(className.filtered).filter('.' + className.selected).eq(0), - $visibleItems = $item.not('.' + className.filtered), + $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0), + $activeItem = $menu.children('.' + className.active).eq(0), + $selectedItem = ($currentlySelected.length > 0) + ? $currentlySelected + : $activeItem, + $visibleItems = ($selectedItem.length > 0) + ? $selectedItem.siblings(':not(.' + className.filtered +')').andSelf() + : $menu.children(':not(.' + className.filtered +')'), + $subMenu = $selectedItem.children(selector.menu), + $parentMenu = $selectedItem.closest(selector.menu), + isSubMenuItem = $parentMenu[0] !== $menu[0], + inVisibleMenu = $parentMenu.is(':visible'), pressedKey = event.which, keys = { - enter : 13, - escape : 27, - upArrow : 38, - downArrow : 40 + enter : 13, + escape : 27, + leftArrow : 37, + upArrow : 38, + rightArrow : 39, + downArrow : 40 }, - selectedClass = className.selected, - currentIndex = $visibleItems.index( $selectedItem ), - hasSelectedItem = ($selectedItem.length > 0), + hasSubMenu = ($subMenu.length> 0), + hasSelectedItem = ($selectedItem.length > 0), + lastVisibleIndex = ($visibleItems.size() - 1), $nextItem, newIndex ; - // default to activated choice if no selection present - if(!hasSelectedItem) { - $selectedItem = $item.filter('.' + className.active).eq(0); - hasSelectedItem = ($selectedItem.length > 0); - } - // close shortcuts - if(pressedKey == keys.escape) { - module.verbose('Escape key pressed, closing dropdown'); - module.hide(); - } - // open menu - if(pressedKey == keys.downArrow) { - module.verbose('Down key pressed, showing dropdown'); - module.show(); - } - // result shortcuts + // visible menu keyboard shortcuts if(module.is.visible()) { + // enter (select or sub-menu) if(pressedKey == keys.enter && hasSelectedItem) { - module.verbose('Enter key pressed, choosing selected item'); - module.event.item.click.call($selectedItem, event); + if(hasSubMenu && !settings.allowCategorySelection) { + module.verbose('Pressed enter on unselectable category, opening sub menu'); + pressedKey = keys.rightArrow; + } + else { + module.verbose('Enter key pressed, choosing selected item'); + module.event.item.click.call($selectedItem, event); + } + } + // left arrow (hide sub-menu) + if(pressedKey == keys.leftArrow) { + if(isSubMenuItem) { + module.verbose('Left key pressed, closing sub-menu'); + module.animate.hide(false, $parentMenu); + $selectedItem + .removeClass(className.selected) + ; + $parentMenu + .closest(selector.item) + .addClass(className.selected) + ; + } event.preventDefault(); - return false; } - else if(pressedKey == keys.upArrow) { - if(!hasSelectedItem) { - $nextItem = $visibleItems.eq(0); + // right arrow (show sub-menu) + if(pressedKey == keys.rightArrow) { + if(hasSubMenu) { + module.verbose('Right key pressed, opening sub-menu'); + module.animate.show(false, $subMenu); + $selectedItem + .removeClass(className.selected) + ; + $subMenu + .find(selector.item).eq(0) + .addClass(className.selected) + ; } - else { - $nextItem = $selectedItem.prevAll(selector.item + ':not(.' + className.filtered + ')').eq(0); + event.preventDefault(); + } + // up arrow (traverse menu up) + if(pressedKey == keys.upArrow) { + $nextItem = (hasSelectedItem && inVisibleMenu) + ? $selectedItem.prevAll(selector.item + ':not(.' + className.filtered + ')').eq(0) + : $item.eq(0) + ; + if($visibleItems.index( $nextItem ) < 0) { + module.verbose('Up key pressed but reached top of current menu'); + return; } - if(currentIndex !== 0) { + else { module.verbose('Up key pressed, changing active item'); - $item - .removeClass(selectedClass) + $selectedItem + .removeClass(className.selected) ; $nextItem - .addClass(selectedClass) + .addClass(className.selected) ; module.set.scrollPosition($nextItem); } event.preventDefault(); } - else if(pressedKey == keys.downArrow) { - if(!hasSelectedItem) { - $nextItem = $visibleItems.eq(0); + // down arrow (traverse menu down) + if(pressedKey == keys.downArrow) { + $nextItem = (hasSelectedItem && inVisibleMenu) + ? $nextItem = $selectedItem.nextAll(selector.item + ':not(.' + className.filtered + ')').eq(0) + : $item.eq(0) + ; + if($nextItem.length === 0) { + module.verbose('Down key pressed but reached bottom of current menu'); + return; } else { - $nextItem = $selectedItem.nextAll(selector.item + ':not(.' + className.filtered + ')').eq(0); - } - if(currentIndex + 1 < $visibleItems.length ) { module.verbose('Down key pressed, changing active item'); $item - .removeClass(selectedClass) + .removeClass(className.selected) ; $nextItem - .addClass(selectedClass) + .addClass(className.selected) ; module.set.scrollPosition($nextItem); } event.preventDefault(); } } else { + // enter (open menu) if(pressedKey == keys.enter) { + module.verbose('Enter key pressed, showing dropdown'); module.show(); } + // escape (close menu) + if(pressedKey == keys.escape) { + module.verbose('Escape key pressed, closing dropdown'); + module.hide(); + } + // down arrow (open menu) + if(pressedKey == keys.downArrow) { + module.verbose('Down key pressed, showing dropdown'); + module.show(); + } } }, test: { toggle: function(event) { if( module.determine.eventInMenu(event, module.toggle) ) { @@ -579,39 +655,37 @@ } }, item: { mouseenter: function(event) { var - $currentMenu = $(this).children(selector.menu), - $otherMenus = $(this).siblings(selector.item).children(selector.menu) + $subMenu = $(this).children(selector.menu), + $otherMenus = $(this).siblings(selector.item).children(selector.menu) ; - if( $currentMenu.length > 0 ) { + if( $subMenu.length > 0 ) { clearTimeout(module.itemTimer); module.itemTimer = setTimeout(function() { + module.verbose('Showing sub-menu', $subMenu); $.each($otherMenus, function() { module.animate.hide(false, $(this)); }); - module.verbose('Showing sub-menu', $currentMenu); - module.animate.show(false, $currentMenu); + module.animate.show(false, $subMenu); }, settings.delay.show); event.preventDefault(); } }, - mouseleave: function(event) { var - $currentMenu = $(this).children(selector.menu) + $subMenu = $(this).children(selector.menu) ; - if($currentMenu.length > 0) { + if($subMenu.length > 0) { clearTimeout(module.itemTimer); module.itemTimer = setTimeout(function() { - module.verbose('Hiding sub-menu', $currentMenu); - module.animate.hide(false, $currentMenu); + module.verbose('Hiding sub-menu', $subMenu); + module.animate.hide(false, $subMenu); }, settings.delay.hide); } }, - click: function (event) { var $choice = $(this), $target = $(event.target), $subMenu = $choice.find(selector.menu), @@ -619,27 +693,21 @@ value = module.get.choiceValue($choice, text), callback = function() { module.remove.searchTerm(); module.determine.selectAction(text, value); }, - openingSubMenu = ($subMenu.length > 0), - isSubItem = ($subMenu.find($target).length > 0) + hasSubMenu = ($subMenu.length > 0), + isBubbledEvent = ($subMenu.find($target).length > 0) ; - if(isSubItem) { - return false; - } - if(!openingSubMenu || settings.allowCategorySelection) { + if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) { callback(); } } - }, - resetStyle: function() { $(this).removeAttr('style'); } - }, determine: { selectAction: function(text, value) { module.verbose('Determining action', settings.action); @@ -689,35 +757,27 @@ action: { nothing: function() {}, - hide: function() { - module.hide(function() { - module.remove.filteredItem(); - }); - }, - - select: function(text, value) { + activate: function(text, value) { value = (value !== undefined) ? value : text ; module.set.selected(value); - module.set.value(value); module.hide(function() { module.remove.filteredItem(); }); }, - activate: function(text, value) { + select: function(text, value) { value = (value !== undefined) ? value : text ; module.set.selected(value); - module.set.value(value); module.hide(function() { module.remove.filteredItem(); }); }, @@ -725,14 +785,19 @@ value = (value !== undefined) ? value : text ; module.set.selected(value); - module.set.value(value); module.hide(function() { module.remove.filteredItem(); }); + }, + + hide: function() { + module.hide(function() { + module.remove.filteredItem(); + }); } }, get: { @@ -764,11 +829,11 @@ : $choice.text().trim() ; } }, choiceValue: function($choice, choiceText) { - choiceText = choiceText || module.get.choiceText($text); + choiceText = choiceText || module.get.choiceText($choice); return ($choice.data(metadata.value) !== undefined) ? $choice.data(metadata.value) : (typeof choiceText === 'string') ? choiceText.toLowerCase().trim() : choiceText.trim() @@ -898,21 +963,20 @@ var defaultText = $module.data(metadata.defaultText) ; module.debug('Restoring default text', defaultText); module.set.text(defaultText); - $text.addClass(settings.className.placeholder); + $text.addClass(className.placeholder); }, defaultValue: function() { var defaultValue = $module.data(metadata.defaultValue) ; if(defaultValue !== undefined) { module.debug('Restoring default value', defaultValue); if(defaultValue.length) { module.set.selected(defaultValue); - module.set.value(defaultValue); } else { module.remove.activeItem(); module.remove.selectedItem(); } @@ -921,20 +985,37 @@ }, save: { defaults: function() { module.save.defaultText(); + module.save.placeholderText(); module.save.defaultValue(); }, defaultValue: function() { $module.data(metadata.defaultValue, module.get.value() ); }, defaultText: function() { $module.data(metadata.defaultText, $text.text() ); + }, + placeholderText: function() { + if($text.hasClass(className.placeholder)) { + $module.data(metadata.placeholderText, $text.text()); + } } }, + clear: function() { + var + placeholderText = $module.data(metadata.placeholderText) + ; + module.set.text(placeholderText); + module.set.value(''); + module.remove.activeItem(); + module.remove.selectedItem(); + $text.addClass(className.placeholder); + }, + set: { filtered: function() { var searchValue = $search.val(), hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0) @@ -1058,24 +1139,25 @@ $module.addClass(className.visible); }, selected: function(value) { var $selectedItem = module.get.item(value), - selectedText + selectedText, + selectedValue ; if($selectedItem) { module.debug('Setting selected menu item to', $selectedItem); - module.remove.activeItem(); module.remove.selectedItem(); $selectedItem .addClass(className.active) .addClass(className.selected) ; - - selectedText = module.get.choiceText($selectedItem); + selectedText = module.get.choiceText($selectedItem); + selectedValue = module.get.choiceValue($selectedItem, selectedText); module.set.text(selectedText); + module.set.value(selectedValue); settings.onChange.call(element, value, selectedText, $selectedItem); } } }, @@ -1122,10 +1204,13 @@ is: { active: function() { return $module.hasClass(className.active); }, + alreadySetup: function() { + return ($module.is('select') && $module.parent(selector.dropdown).length > 0); + }, animating: function($subMenu) { return ($subMenu) ? $subMenu.is(':animated') || $subMenu.transition && $subMenu.transition('is animating') : $menu.is(':animated') || $menu.transition && $menu.transition('is animating') ; @@ -1137,10 +1222,22 @@ return ($subMenu) ? $subMenu.is(':hidden') : $menu.is(':hidden') ; }, + selectMutation: function(mutations) { + var + selectChanged = false + ; + $.each(mutations, function(index, mutation) { + if(mutation.target && $(mutation.target).is('select')) { + selectChanged = true; + return true; + } + }); + return selectChanged; + }, search: function() { return $module.hasClass(className.search); }, searchable: function() { return ($search.length > 0); @@ -1194,12 +1291,12 @@ if(settings.transition == 'auto') { settings.transition = module.is.upward() ? 'slide up' : 'slide down' ; + module.verbose('Automatically determining animation based on animation direction', settings.transition); } - if(settings.transition == 'none') { callback.call(element); } else if($.fn.transition !== undefined && $module.transition('is supported')) { $currentMenu @@ -1569,19 +1666,21 @@ name : 'Dropdown', namespace : 'dropdown', error : { - action : 'You called a dropdown action that was not defined', - method : 'The method you called is not defined.', - transition : 'The requested transition was not found' + action : 'You called a dropdown action that was not defined', + alreadySetup : 'Once a select has been initialized behaviors must be called on the created ui dropdown', + method : 'The method you called is not defined.', + transition : 'The requested transition was not found' }, metadata: { - defaultText : 'defaultText', - defaultValue : 'defaultValue', - text : 'text', - value : 'value' + defaultText : 'defaultText', + defaultValue : 'defaultValue', + placeholderText : 'placeholderText', + text : 'text', + value : 'value' }, selector : { dropdown : '.ui.dropdown', input : '> input[type="hidden"], > select',