From 0f33062c95ec598f484a85bc4992a40ae04ee646 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sun, 18 Nov 2012 19:25:48 -0800 Subject: [PATCH 01/33] Adds unit test: correct sub-menu-less menu item event behavior Ensure open sub-menus close on sub-menu-less `click` or `mouseenter` events. See 9f53e0. --- tests/unit/menubar/menubar_events.js | 21 +++++++++++++++++++++ ui/jquery.ui.menubar.js | 10 +++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/tests/unit/menubar/menubar_events.js b/tests/unit/menubar/menubar_events.js index e2d30ba7a73..e0956d48111 100644 --- a/tests/unit/menubar/menubar_events.js +++ b/tests/unit/menubar/menubar_events.js @@ -27,4 +27,25 @@ test( "handle click on menu item", function() { equal( logOutput(), "click,(1,2),afterclick,(2,1),(3,3),(1,2)", "Click order not valid." ); }); +test( "hover over a menu item with no sub-menu should close open menu", function() { + expect( 2 ); + + var element = $( "#bar1" ).menubar(); + var links = $("#bar1 > li a"); + + var menuItemWithDropdown = links.eq(1); + var menuItemWithoutDropdown = links.eq(0); + + menuItemWithDropdown.trigger('click', {}); + menuItemWithoutDropdown.trigger('mouseenter', {}); + + equal( $(".ui-menu:visible").length, 0 ); + + menuItemWithDropdown.trigger('click', {}); + menuItemWithoutDropdown.trigger('click', {}); + + equal( $(".ui-menu:visible").length, 0 ); + +}); + })( jQuery ); diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 4936ed4d151..f0d20d8a296 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -138,11 +138,11 @@ $.widget( "ui.menubar", { } } else { // TODO use _on - input.bind( "click.menubar mouseenter.menubar", function( event ) { - if ( ( that.open && event.type === "mouseenter" ) || event.type === "click" ) { - that._close(); - } - }); + input.bind( "click mouseenter", function( event ) { + if ( ( that.open && event.type === "mouseenter" ) || event.type === "click" ) { + that._close(); + } + }); } input From 82d0056451393a1af7f9924b1ede6baf50489b09 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sun, 18 Nov 2012 19:35:39 -0800 Subject: [PATCH 02/33] Convert from bind() to _on() Per comment from @scottgonzalez --- ui/jquery.ui.menubar.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index f0d20d8a296..d47d22e1b9c 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -137,10 +137,13 @@ $.widget( "ui.menubar", { input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); } } else { - // TODO use _on - input.bind( "click mouseenter", function( event ) { - if ( ( that.open && event.type === "mouseenter" ) || event.type === "click" ) { - that._close(); + that._on( input, { + click: function( event ) { + if ( that.open ){ that._close(); } + }, + + mouseenter: function( event ) { + if ( that.open ){ that._close(); } } }); } From 15bd67ff10439f732ed5422bd17a7705a8afc050 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Mon, 19 Nov 2012 17:16:32 -0800 Subject: [PATCH 03/33] menubar: remove remaining bind() calls; code standards compliance. --- tests/unit/menubar/menubar_events.js | 24 +++-- ui/jquery.ui.menubar.js | 129 +++++++++++++++------------ 2 files changed, 82 insertions(+), 71 deletions(-) diff --git a/tests/unit/menubar/menubar_events.js b/tests/unit/menubar/menubar_events.js index e0956d48111..9c2fe3394ab 100644 --- a/tests/unit/menubar/menubar_events.js +++ b/tests/unit/menubar/menubar_events.js @@ -28,24 +28,22 @@ test( "handle click on menu item", function() { }); test( "hover over a menu item with no sub-menu should close open menu", function() { - expect( 2 ); + expect( 2 ); - var element = $( "#bar1" ).menubar(); - var links = $("#bar1 > li a"); + var element = $("#bar1").menubar(), + links = $("#bar1 > li a"), + menuItemWithDropdown = links.eq(1), + menuItemWithoutDropdown = links.eq(0); - var menuItemWithDropdown = links.eq(1); - var menuItemWithoutDropdown = links.eq(0); + menuItemWithDropdown.trigger("click"); + menuItemWithoutDropdown.trigger("mouseenter"); - menuItemWithDropdown.trigger('click', {}); - menuItemWithoutDropdown.trigger('mouseenter', {}); + equal($(".ui-menu:visible").length, 0, "After triggering a sub-menu, a mouseenter on a peer menu item should close the opened sub-menu"); - equal( $(".ui-menu:visible").length, 0 ); - - menuItemWithDropdown.trigger('click', {}); - menuItemWithoutDropdown.trigger('click', {}); - - equal( $(".ui-menu:visible").length, 0 ); + menuItemWithDropdown.trigger("click"); + menuItemWithoutDropdown.trigger("click"); + equal($(".ui-menu:visible").length, 0, "After triggering a sub-menu, a click on a peer menu item should close the opened sub-menu"); }); })( jQuery ); diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index d47d22e1b9c..732be027ed5 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -31,7 +31,7 @@ $.widget( "ui.menubar", { } }, _create: function() { - var that = this; + var that = this, subMenus; this.menuItems = this.element.children( this.options.items ); this.items = this.menuItems.children( "button, a" ); @@ -46,7 +46,7 @@ $.widget( "ui.menubar", { .attr( "role", "menubar" ); this._focusable( this.items ); this._hoverable( this.items ); - this.items.siblings( this.options.menuElement ) + subMenus = this.items.siblings( this.options.menuElement ) .menu({ position: { within: this.options.position.within @@ -58,78 +58,89 @@ $.widget( "ui.menubar", { $(event.target).prev().focus(); that._trigger( "select", event, ui ); }, - menus: that.options.menuElement + menus: this.options.menuElement }) .hide() .attr({ "aria-hidden": "true", "aria-expanded": "false" - }) - // TODO use _on - .bind( "keydown.menubar", function( event ) { + }); + this._on( subMenus, { + "keydown.menubar": function(event) { var menu = $( this ); if ( menu.is( ":hidden" ) ) { return; } switch ( event.keyCode ) { case $.ui.keyCode.LEFT: - that.previous( event ); + this.previous( event ); event.preventDefault(); break; case $.ui.keyCode.RIGHT: - that.next( event ); + this.next( event ); event.preventDefault(); break; } - }); + } + }); + if ( this.items.length > 0 ) { + } this.items.each(function() { var input = $(this), // TODO menu var is only used on two places, doesn't quite justify the .each - menu = input.next( that.options.menuElement ); + menu = input.next( that.options.menuElement ), + mouseBehaviorCallback, keyboardBehaviorCallback; - // might be a non-menu button - if ( menu.length ) { - // TODO use _on - input.bind( "click.menubar focus.menubar mouseenter.menubar", function( event ) { - // ignore triggered focus event - if ( event.type === "focus" && !event.originalEvent ) { - return; + mouseBehaviorCallback = function( event ) { + // ignore triggered focus event + if ( event.type === "focus" && !event.originalEvent ) { + return; + } + event.preventDefault(); + // TODO can we simplify or extractthis check? especially the last two expressions + // there's a similar active[0] == menu[0] check in _open + if ( event.type === "click" && menu.is( ":visible" ) && this.active && this.active[0] === menu[0] ) { + this._close(); + return; + } + if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { + if( this.options.autoExpand ) { + clearTimeout( this.closeTimer ); } + + this._open( event, menu ); + } + }; + + keyboardBehaviorCallback = function( event ) { + switch ( event.keyCode ) { + case $.ui.keyCode.SPACE: + case $.ui.keyCode.UP: + case $.ui.keyCode.DOWN: + this._open( event, $( this ).next() ); event.preventDefault(); - // TODO can we simplify or extractthis check? especially the last two expressions - // there's a similar active[0] == menu[0] check in _open - if ( event.type === "click" && menu.is( ":visible" ) && that.active && that.active[0] === menu[0] ) { - that._close(); - return; - } - if ( ( that.open && event.type === "mouseenter" ) || event.type === "click" || that.options.autoExpand ) { - if( that.options.autoExpand ) { - clearTimeout( that.closeTimer ); - } + break; + case $.ui.keyCode.LEFT: + this.previous( event ); + event.preventDefault(); + break; + case $.ui.keyCode.RIGHT: + this.next( event ); + event.preventDefault(); + break; + } + }; - that._open( event, menu ); - } - }) - // TODO use _on - .bind( "keydown", function( event ) { - switch ( event.keyCode ) { - case $.ui.keyCode.SPACE: - case $.ui.keyCode.UP: - case $.ui.keyCode.DOWN: - that._open( event, $( this ).next() ); - event.preventDefault(); - break; - case $.ui.keyCode.LEFT: - that.previous( event ); - event.preventDefault(); - break; - case $.ui.keyCode.RIGHT: - that.next( event ); - event.preventDefault(); - break; - } - }) - .attr( "aria-haspopup", "true" ); + // might be a non-menu button + if ( menu.length ) { + that._on(input, { + "click.menubar": mouseBehaviorCallback, + "focus.menubar": mouseBehaviorCallback, + "mouseenter.menubar": mouseBehaviorCallback, + "keydown": keyboardBehaviorCallback + }); + + input.attr( "aria-haspopup", "true" ); // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged if ( that.options.menuIcon ) { @@ -137,15 +148,17 @@ $.widget( "ui.menubar", { input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); } } else { - that._on( input, { - click: function( event ) { - if ( that.open ){ that._close(); } - }, + that._on(input, { + click: function(event) { + this._close(); + }, - mouseenter: function( event ) { - if ( that.open ){ that._close(); } - } - }); + mouseenter: function(event) { + if (this.open){ + this._close(); + } + } + }); } input From d4ca89b61351094e4a7677dd6dd2f1eec224480d Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 1 Dec 2012 07:46:34 -0800 Subject: [PATCH 04/33] menubar: rm stray, empty conditional --- ui/jquery.ui.menubar.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 732be027ed5..1e18311644b 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -83,8 +83,6 @@ $.widget( "ui.menubar", { } } }); - if ( this.items.length > 0 ) { - } this.items.each(function() { var input = $(this), // TODO menu var is only used on two places, doesn't quite justify the .each From f30c853f4336adeead087999cf8b518d1f5a7c30 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Mon, 3 Dec 2012 13:01:42 -0800 Subject: [PATCH 05/33] menubar: rm namespacing on events in _on --- ui/jquery.ui.menubar.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 1e18311644b..c6eb1f5aa73 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -66,7 +66,8 @@ $.widget( "ui.menubar", { "aria-expanded": "false" }); this._on( subMenus, { - "keydown.menubar": function(event) { + + "keydown": function(event) { var menu = $( this ); if ( menu.is( ":hidden" ) ) { return; @@ -132,9 +133,9 @@ $.widget( "ui.menubar", { // might be a non-menu button if ( menu.length ) { that._on(input, { - "click.menubar": mouseBehaviorCallback, - "focus.menubar": mouseBehaviorCallback, - "mouseenter.menubar": mouseBehaviorCallback, + "click": mouseBehaviorCallback, + "focus": mouseBehaviorCallback, + "mouseenter": mouseBehaviorCallback, "keydown": keyboardBehaviorCallback }); From ec67b65fd46ec82d4162650a4de327fd09cb4548 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Tue, 4 Dec 2012 07:33:04 -0800 Subject: [PATCH 06/33] menubar: decompose _create The `_create()` method had quite a lot going on in it that made it hard to refactor, analyze, or understand. Use sub-functions to encapsulate certain activities. --- ui/jquery.ui.menubar.js | 292 ++++++++++++++++++++++------------------ 1 file changed, 164 insertions(+), 128 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index c6eb1f5aa73..d716aecb111 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -31,94 +31,91 @@ $.widget( "ui.menubar", { } }, _create: function() { - var that = this, subMenus; + // Top-level items, typically li this.menuItems = this.element.children( this.options.items ); + + // (Typically) menuItem contained, event-able used to launch a sub-menu this.items = this.menuItems.children( "button, a" ); - this.menuItems - .addClass( "ui-menubar-item" ) - .attr( "role", "presentation" ); - // let only the first item receive focus - this.items.slice(1).attr( "tabIndex", -1 ); + // Keep track of open submenus + this.openSubmenus = 0; - this.element + // (Typically) a ul + var subMenus = this.items.siblings( this.options.menuElement ); + + this._prepareElement(this.element); + this._prepareMenuItems(this.menuItems); + this._prepareItems(this.items); + this._prepareSubMenus(subMenus); + }, + _prepareElement: function(element) { + element .addClass( "ui-menubar ui-widget-header ui-helper-clearfix" ) .attr( "role", "menubar" ); - this._focusable( this.items ); - this._hoverable( this.items ); - subMenus = this.items.siblings( this.options.menuElement ) - .menu({ - position: { - within: this.options.position.within - }, - select: function( event, ui ) { - ui.item.parents( "ul.ui-menu:last" ).hide(); - that._close(); - // TODO what is this targetting? there's probably a better way to access it - $(event.target).prev().focus(); - that._trigger( "select", event, ui ); - }, - menus: this.options.menuElement - }) - .hide() - .attr({ - "aria-hidden": "true", - "aria-expanded": "false" - }); - this._on( subMenus, { + this._bindPluginElementEvents(element); + }, + _prepareMenuItems: function(menuItems) { + menuItems.addClass( "ui-menubar-item" ) + .attr( "role", "presentation" ); + }, + _prepareItems: function(menubarTopLevelItems) { + // let only the first item receive focus + menubarTopLevelItems.slice(1).attr( "tabIndex", -1 ); - "keydown": function(event) { - var menu = $( this ); - if ( menu.is( ":hidden" ) ) { - return; - } - switch ( event.keyCode ) { - case $.ui.keyCode.LEFT: - this.previous( event ); - event.preventDefault(); - break; - case $.ui.keyCode.RIGHT: - this.next( event ); - event.preventDefault(); - break; - } - } - }); - this.items.each(function() { + this._focusable( menubarTopLevelItems ); + this._hoverable( menubarTopLevelItems ); + this._applyItemsEventBehavior( menubarTopLevelItems ); + }, + _applyItemsEventBehavior: function($topLevelItems) { + var menubar = this; + $topLevelItems.each(function() { var input = $(this), // TODO menu var is only used on two places, doesn't quite justify the .each - menu = input.next( that.options.menuElement ), - mouseBehaviorCallback, keyboardBehaviorCallback; + menu = input.next( menubar.options.menuElement ), + isNonmenuButton = menu.length; - mouseBehaviorCallback = function( event ) { - // ignore triggered focus event - if ( event.type === "focus" && !event.originalEvent ) { - return; - } - event.preventDefault(); - // TODO can we simplify or extractthis check? especially the last two expressions - // there's a similar active[0] == menu[0] check in _open - if ( event.type === "click" && menu.is( ":visible" ) && this.active && this.active[0] === menu[0] ) { - this._close(); - return; - } - if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { - if( this.options.autoExpand ) { - clearTimeout( this.closeTimer ); - } + if ( isNonmenuButton ) { + menubar._applyItemsKeyboardEvents(menubar, input, menu); + menubar._applyItemsMouseEvents(menubar, input, menu); + menubar._prepareMenuIcon(menubar, input); - this._open( event, menu ); - } - }; + input.attr( "aria-haspopup", "true" ); + } else { + menubar._applyNonMenuButtonEvents(menubar, input); + } + + menubar._prepareInputItem(menubar, input); + }); + }, + _prepareSubMenus: function(subMenus) { + var menubar = this; - keyboardBehaviorCallback = function( event ) { + subMenus.menu({ + position: { + within: this.options.position.within + }, + select: function( event, ui ) { + ui.item.parents( "ul.ui-menu:last" ).hide(); + menubar._close(); + // TODO what is this targetting? there's probably a better way to access it + $(event.target).prev().focus(); + menubar._trigger( "select", event, ui ); + }, + menus: this.options.menuElement + }) + .hide() + .attr({ + "aria-hidden": "true", + "aria-expanded": "false" + }); + + this._on( subMenus, { + "keydown": function(event) { + var menu = $( this ); + if ( menu.is( ":hidden" ) ) { + return; + } switch ( event.keyCode ) { - case $.ui.keyCode.SPACE: - case $.ui.keyCode.UP: - case $.ui.keyCode.DOWN: - this._open( event, $( this ).next() ); - event.preventDefault(); - break; case $.ui.keyCode.LEFT: this.previous( event ); event.preventDefault(); @@ -128,80 +125,119 @@ $.widget( "ui.menubar", { event.preventDefault(); break; } - }; - - // might be a non-menu button - if ( menu.length ) { - that._on(input, { - "click": mouseBehaviorCallback, - "focus": mouseBehaviorCallback, - "mouseenter": mouseBehaviorCallback, - "keydown": keyboardBehaviorCallback - }); - - input.attr( "aria-haspopup", "true" ); - - // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged - if ( that.options.menuIcon ) { - input.addClass( "ui-state-default" ).append( "" ); - input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); - } - } else { - that._on(input, { - click: function(event) { - this._close(); - }, - - mouseenter: function(event) { - if (this.open){ - this._close(); - } - } - }); - } - - input - .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) - .attr( "role", "menuitem" ) - .wrapInner( "" ); - - if ( that.options.buttons ) { - input.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" ); } }); - that._on( { + }, + _bindPluginElementEvents: function(myElement) { + var menubar = this; + menubar._on( myElement, { keydown: function( event ) { - if ( event.keyCode === $.ui.keyCode.ESCAPE && that.active && that.active.menu( "collapse", event ) !== true ) { - var active = that.active; - that.active.blur(); - that._close( event ); + if ( event.keyCode === $.ui.keyCode.ESCAPE && menubar.active && menubar.active.menu( "collapse", event ) !== true ) { + var active = menubar.active; + menubar.active.blur(); + menubar._close( event ); active.prev().focus(); } }, focusin: function( event ) { - clearTimeout( that.closeTimer ); + clearTimeout( menubar.closeTimer ); }, focusout: function( event ) { - that.closeTimer = setTimeout( function() { - that._close( event ); + menubar.closeTimer = setTimeout( function() { + menubar._close( event ); }, 150); }, "mouseleave .ui-menubar-item": function( event ) { - if ( that.options.autoExpand ) { - that.closeTimer = setTimeout( function() { - that._close( event ); + if ( menubar.options.autoExpand ) { + menubar.closeTimer = setTimeout( function() { + menubar._close( event ); }, 150); } }, "mouseenter .ui-menubar-item": function( event ) { - clearTimeout( that.closeTimer ); + clearTimeout( menubar.closeTimer ); } }); + }, + _prepareInputItem: function(menubar, $input) { + $input + .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) + .attr( "role", "menuitem" ) + .wrapInner( "" ); - // Keep track of open submenus - this.openSubmenus = 0; + if ( menubar.options.buttons ) { + $input.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" ); + } }, + _applyItemsKeyboardEvents: function(menubar, $input) { + var keyboardBehaviorCallback = function( event ) { + switch ( event.keyCode ) { + case $.ui.keyCode.SPACE: + case $.ui.keyCode.UP: + case $.ui.keyCode.DOWN: + this._open( event, $( this ).next() ); + event.preventDefault(); + break; + case $.ui.keyCode.LEFT: + this.previous( event ); + event.preventDefault(); + break; + case $.ui.keyCode.RIGHT: + this.next( event ); + event.preventDefault(); + break; + } + }; + menubar._on($input, { + "keydown": keyboardBehaviorCallback + }); + }, + _applyItemsMouseEvents: function(menubar, $input, menu) { + var mouseBehaviorCallback = function( event ) { + // ignore triggered focus event + if ( event.type === "focus" && !event.originalEvent ) { + return; + } + event.preventDefault(); + // TODO can we simplify or extractthis check? especially the last two expressions + // there's a similar active[0] == menu[0] check in _open + if ( event.type === "click" && menu.is( ":visible" ) && this.active && this.active[0] === menu[0] ) { + this._close(); + return; + } + if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { + if( this.options.autoExpand ) { + clearTimeout( this.closeTimer ); + } + this._open( event, menu ); + } + }; + menubar._on($input, { + "click": mouseBehaviorCallback, + "focus": mouseBehaviorCallback, + "mouseenter": mouseBehaviorCallback + }); + }, + _applyNonMenuButtonEvents: function(menubar, input) { + menubar._on(input, { + click: function(event) { + this._close(); + }, + mouseenter: function(event) { + if (this.open){ + this._close(); + } + } + }); + }, + _prepareMenuIcon: function(menubar, $input) { + // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged + if ( menubar.options.menuIcon ) { + $input.addClass( "ui-state-default" ).append( "" ); + $input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); + } + }, _destroy : function() { this.menuItems .removeClass( "ui-menubar-item" ) From c57319c0948e9e3a7c15879fb96fa9b0779a1740 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 8 Dec 2012 15:16:23 -0800 Subject: [PATCH 07/33] Revert "menubar: decompose _create" This reverts commit ec67b65fd46ec82d4162650a4de327fd09cb4548. This is not *quite* ready for pull request. --- ui/jquery.ui.menubar.js | 292 ++++++++++++++++++---------------------- 1 file changed, 128 insertions(+), 164 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index d716aecb111..c6eb1f5aa73 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -31,85 +31,42 @@ $.widget( "ui.menubar", { } }, _create: function() { - // Top-level items, typically li + var that = this, subMenus; this.menuItems = this.element.children( this.options.items ); - - // (Typically) menuItem contained, event-able used to launch a sub-menu this.items = this.menuItems.children( "button, a" ); - // Keep track of open submenus - this.openSubmenus = 0; - - // (Typically) a ul - var subMenus = this.items.siblings( this.options.menuElement ); - - this._prepareElement(this.element); - this._prepareMenuItems(this.menuItems); - this._prepareItems(this.items); - this._prepareSubMenus(subMenus); - }, - _prepareElement: function(element) { - element - .addClass( "ui-menubar ui-widget-header ui-helper-clearfix" ) - .attr( "role", "menubar" ); - this._bindPluginElementEvents(element); - }, - _prepareMenuItems: function(menuItems) { - menuItems.addClass( "ui-menubar-item" ) + this.menuItems + .addClass( "ui-menubar-item" ) .attr( "role", "presentation" ); - }, - _prepareItems: function(menubarTopLevelItems) { // let only the first item receive focus - menubarTopLevelItems.slice(1).attr( "tabIndex", -1 ); - - this._focusable( menubarTopLevelItems ); - this._hoverable( menubarTopLevelItems ); - this._applyItemsEventBehavior( menubarTopLevelItems ); - }, - _applyItemsEventBehavior: function($topLevelItems) { - var menubar = this; - $topLevelItems.each(function() { - var input = $(this), - // TODO menu var is only used on two places, doesn't quite justify the .each - menu = input.next( menubar.options.menuElement ), - isNonmenuButton = menu.length; - - if ( isNonmenuButton ) { - menubar._applyItemsKeyboardEvents(menubar, input, menu); - menubar._applyItemsMouseEvents(menubar, input, menu); - menubar._prepareMenuIcon(menubar, input); - - input.attr( "aria-haspopup", "true" ); - } else { - menubar._applyNonMenuButtonEvents(menubar, input); - } - - menubar._prepareInputItem(menubar, input); - }); - }, - _prepareSubMenus: function(subMenus) { - var menubar = this; - - subMenus.menu({ - position: { - within: this.options.position.within - }, - select: function( event, ui ) { - ui.item.parents( "ul.ui-menu:last" ).hide(); - menubar._close(); - // TODO what is this targetting? there's probably a better way to access it - $(event.target).prev().focus(); - menubar._trigger( "select", event, ui ); - }, - menus: this.options.menuElement - }) - .hide() - .attr({ - "aria-hidden": "true", - "aria-expanded": "false" - }); + this.items.slice(1).attr( "tabIndex", -1 ); + this.element + .addClass( "ui-menubar ui-widget-header ui-helper-clearfix" ) + .attr( "role", "menubar" ); + this._focusable( this.items ); + this._hoverable( this.items ); + subMenus = this.items.siblings( this.options.menuElement ) + .menu({ + position: { + within: this.options.position.within + }, + select: function( event, ui ) { + ui.item.parents( "ul.ui-menu:last" ).hide(); + that._close(); + // TODO what is this targetting? there's probably a better way to access it + $(event.target).prev().focus(); + that._trigger( "select", event, ui ); + }, + menus: this.options.menuElement + }) + .hide() + .attr({ + "aria-hidden": "true", + "aria-expanded": "false" + }); this._on( subMenus, { + "keydown": function(event) { var menu = $( this ); if ( menu.is( ":hidden" ) ) { @@ -127,117 +84,124 @@ $.widget( "ui.menubar", { } } }); - }, - _bindPluginElementEvents: function(myElement) { - var menubar = this; - menubar._on( myElement, { + this.items.each(function() { + var input = $(this), + // TODO menu var is only used on two places, doesn't quite justify the .each + menu = input.next( that.options.menuElement ), + mouseBehaviorCallback, keyboardBehaviorCallback; + + mouseBehaviorCallback = function( event ) { + // ignore triggered focus event + if ( event.type === "focus" && !event.originalEvent ) { + return; + } + event.preventDefault(); + // TODO can we simplify or extractthis check? especially the last two expressions + // there's a similar active[0] == menu[0] check in _open + if ( event.type === "click" && menu.is( ":visible" ) && this.active && this.active[0] === menu[0] ) { + this._close(); + return; + } + if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { + if( this.options.autoExpand ) { + clearTimeout( this.closeTimer ); + } + + this._open( event, menu ); + } + }; + + keyboardBehaviorCallback = function( event ) { + switch ( event.keyCode ) { + case $.ui.keyCode.SPACE: + case $.ui.keyCode.UP: + case $.ui.keyCode.DOWN: + this._open( event, $( this ).next() ); + event.preventDefault(); + break; + case $.ui.keyCode.LEFT: + this.previous( event ); + event.preventDefault(); + break; + case $.ui.keyCode.RIGHT: + this.next( event ); + event.preventDefault(); + break; + } + }; + + // might be a non-menu button + if ( menu.length ) { + that._on(input, { + "click": mouseBehaviorCallback, + "focus": mouseBehaviorCallback, + "mouseenter": mouseBehaviorCallback, + "keydown": keyboardBehaviorCallback + }); + + input.attr( "aria-haspopup", "true" ); + + // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged + if ( that.options.menuIcon ) { + input.addClass( "ui-state-default" ).append( "" ); + input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); + } + } else { + that._on(input, { + click: function(event) { + this._close(); + }, + + mouseenter: function(event) { + if (this.open){ + this._close(); + } + } + }); + } + + input + .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) + .attr( "role", "menuitem" ) + .wrapInner( "" ); + + if ( that.options.buttons ) { + input.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" ); + } + }); + that._on( { keydown: function( event ) { - if ( event.keyCode === $.ui.keyCode.ESCAPE && menubar.active && menubar.active.menu( "collapse", event ) !== true ) { - var active = menubar.active; - menubar.active.blur(); - menubar._close( event ); + if ( event.keyCode === $.ui.keyCode.ESCAPE && that.active && that.active.menu( "collapse", event ) !== true ) { + var active = that.active; + that.active.blur(); + that._close( event ); active.prev().focus(); } }, focusin: function( event ) { - clearTimeout( menubar.closeTimer ); + clearTimeout( that.closeTimer ); }, focusout: function( event ) { - menubar.closeTimer = setTimeout( function() { - menubar._close( event ); + that.closeTimer = setTimeout( function() { + that._close( event ); }, 150); }, "mouseleave .ui-menubar-item": function( event ) { - if ( menubar.options.autoExpand ) { - menubar.closeTimer = setTimeout( function() { - menubar._close( event ); + if ( that.options.autoExpand ) { + that.closeTimer = setTimeout( function() { + that._close( event ); }, 150); } }, "mouseenter .ui-menubar-item": function( event ) { - clearTimeout( menubar.closeTimer ); + clearTimeout( that.closeTimer ); } }); - }, - _prepareInputItem: function(menubar, $input) { - $input - .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) - .attr( "role", "menuitem" ) - .wrapInner( "" ); - if ( menubar.options.buttons ) { - $input.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" ); - } - }, - _applyItemsKeyboardEvents: function(menubar, $input) { - var keyboardBehaviorCallback = function( event ) { - switch ( event.keyCode ) { - case $.ui.keyCode.SPACE: - case $.ui.keyCode.UP: - case $.ui.keyCode.DOWN: - this._open( event, $( this ).next() ); - event.preventDefault(); - break; - case $.ui.keyCode.LEFT: - this.previous( event ); - event.preventDefault(); - break; - case $.ui.keyCode.RIGHT: - this.next( event ); - event.preventDefault(); - break; - } - }; - menubar._on($input, { - "keydown": keyboardBehaviorCallback - }); + // Keep track of open submenus + this.openSubmenus = 0; }, - _applyItemsMouseEvents: function(menubar, $input, menu) { - var mouseBehaviorCallback = function( event ) { - // ignore triggered focus event - if ( event.type === "focus" && !event.originalEvent ) { - return; - } - event.preventDefault(); - // TODO can we simplify or extractthis check? especially the last two expressions - // there's a similar active[0] == menu[0] check in _open - if ( event.type === "click" && menu.is( ":visible" ) && this.active && this.active[0] === menu[0] ) { - this._close(); - return; - } - if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { - if( this.options.autoExpand ) { - clearTimeout( this.closeTimer ); - } - this._open( event, menu ); - } - }; - menubar._on($input, { - "click": mouseBehaviorCallback, - "focus": mouseBehaviorCallback, - "mouseenter": mouseBehaviorCallback - }); - }, - _applyNonMenuButtonEvents: function(menubar, input) { - menubar._on(input, { - click: function(event) { - this._close(); - }, - mouseenter: function(event) { - if (this.open){ - this._close(); - } - } - }); - }, - _prepareMenuIcon: function(menubar, $input) { - // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged - if ( menubar.options.menuIcon ) { - $input.addClass( "ui-state-default" ).append( "" ); - $input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); - } - }, _destroy : function() { this.menuItems .removeClass( "ui-menubar-item" ) From 36b51359a1074b45ac6576676722c63a3001af04 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 8 Dec 2012 07:55:31 -0800 Subject: [PATCH 08/33] menubar: spacing / style fix --- ui/jquery.ui.menubar.js | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index c6eb1f5aa73..ac0994e0e1b 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -66,8 +66,7 @@ $.widget( "ui.menubar", { "aria-expanded": "false" }); this._on( subMenus, { - - "keydown": function(event) { + keydown: function( event ) { var menu = $( this ); if ( menu.is( ":hidden" ) ) { return; @@ -85,10 +84,10 @@ $.widget( "ui.menubar", { } }); this.items.each(function() { - var input = $(this), + var mouseBehaviorCallback, keyboardBehaviorCallback, + input = $(this), // TODO menu var is only used on two places, doesn't quite justify the .each - menu = input.next( that.options.menuElement ), - mouseBehaviorCallback, keyboardBehaviorCallback; + menu = input.next( that.options.menuElement ); mouseBehaviorCallback = function( event ) { // ignore triggered focus event @@ -96,14 +95,14 @@ $.widget( "ui.menubar", { return; } event.preventDefault(); - // TODO can we simplify or extractthis check? especially the last two expressions + // TODO can we simplify or extract this check? especially the last two expressions // there's a similar active[0] == menu[0] check in _open if ( event.type === "click" && menu.is( ":visible" ) && this.active && this.active[0] === menu[0] ) { this._close(); return; } if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { - if( this.options.autoExpand ) { + if ( this.options.autoExpand ) { clearTimeout( this.closeTimer ); } @@ -132,11 +131,11 @@ $.widget( "ui.menubar", { // might be a non-menu button if ( menu.length ) { - that._on(input, { - "click": mouseBehaviorCallback, - "focus": mouseBehaviorCallback, - "mouseenter": mouseBehaviorCallback, - "keydown": keyboardBehaviorCallback + that._on( input, { + click: mouseBehaviorCallback, + focus: mouseBehaviorCallback, + mouseenter: mouseBehaviorCallback, + keydown: keyboardBehaviorCallback }); input.attr( "aria-haspopup", "true" ); @@ -147,13 +146,13 @@ $.widget( "ui.menubar", { input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); } } else { - that._on(input, { - click: function(event) { + that._on( input, { + click: function( event ) { this._close(); }, - mouseenter: function(event) { - if (this.open){ + mouseenter: function( event ) { + if ( this.open ) { this._close(); } } From 1a9f136612066d2a66c0f215802ff596b8f4b17f Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 8 Dec 2012 14:50:11 -0800 Subject: [PATCH 09/33] menubar: add visual test --- tests/visual/menubar/menubar.html | 78 +++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/visual/menubar/menubar.html diff --git a/tests/visual/menubar/menubar.html b/tests/visual/menubar/menubar.html new file mode 100644 index 00000000000..f724abf39ab --- /dev/null +++ b/tests/visual/menubar/menubar.html @@ -0,0 +1,78 @@ + + + + + Menu Visual Test: Default + + + + + + + + + + + + +

Default menubar

+ + + + From ab98a005328e49f8d1f56a27e42936e0cde75b36 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 8 Dec 2012 14:50:18 -0800 Subject: [PATCH 10/33] menubar: restore keyboard navigation Per @jzaefferer, keyboard behavior was broken, this should be fixed now. --- ui/jquery.ui.menubar.js | 52 +++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index ac0994e0e1b..7a3dd83aa61 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -32,8 +32,8 @@ $.widget( "ui.menubar", { }, _create: function() { var that = this, subMenus; - this.menuItems = this.element.children( this.options.items ); - this.items = this.menuItems.children( "button, a" ); + this.menuItems = this.element.children( this.options.items ); // Top-level
  • s + this.items = this.menuItems.children( "button, a" ); // Links in those top-level
  • s this.menuItems .addClass( "ui-menubar-item" ) @@ -46,7 +46,7 @@ $.widget( "ui.menubar", { .attr( "role", "menubar" ); this._focusable( this.items ); this._hoverable( this.items ); - subMenus = this.items.siblings( this.options.menuElement ) + subMenus = this.items.siblings( this.options.menuElement ) // sub-contained
      .menu({ position: { within: this.options.position.within @@ -115,7 +115,7 @@ $.widget( "ui.menubar", { case $.ui.keyCode.SPACE: case $.ui.keyCode.UP: case $.ui.keyCode.DOWN: - this._open( event, $( this ).next() ); + that._open( event, $(event.target).next() ); event.preventDefault(); break; case $.ui.keyCode.LEFT: @@ -130,35 +130,47 @@ $.widget( "ui.menubar", { }; // might be a non-menu button - if ( menu.length ) { - that._on( input, { - click: mouseBehaviorCallback, - focus: mouseBehaviorCallback, - mouseenter: mouseBehaviorCallback, - keydown: keyboardBehaviorCallback - }); + that._on( input, { + click: mouseBehaviorCallback, + focus: mouseBehaviorCallback, + mouseenter: mouseBehaviorCallback, + keydown: keyboardBehaviorCallback + }); - input.attr( "aria-haspopup", "true" ); + input.attr( "aria-haspopup", "true" ); - // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged - if ( that.options.menuIcon ) { - input.addClass( "ui-state-default" ).append( "" ); - input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); - } - } else { + // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged + if ( that.options.menuIcon ) { + input.addClass( "ui-state-default" ).append( "" ); + input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); + } + + if ( !menu.length ) { + that._off( input, "click mouseenter keydown"); + that._hoverable( input ); that._on( input, { click: function( event ) { this._close(); }, - mouseenter: function( event ) { if ( this.open ) { this._close(); } + }, + keydown: function( event ) { + switch ( event.keyCode ) { + case $.ui.keyCode.LEFT: + this.previous( event ); + event.preventDefault(); + break; + case $.ui.keyCode.RIGHT: + this.next( event ); + event.preventDefault(); + break; + } } }); } - input .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) .attr( "role", "menuitem" ) From 2fe73a140ae8d17e22310390a52716689729dd29 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 8 Dec 2012 15:48:25 -0800 Subject: [PATCH 11/33] menubar: keyboard focus / mouse interaction 1. Select menubar via keyboard 2. Keyboard to an element with submenu 3. Press down 4. Mouse over another element with submenu 5. Unfocus the top-level menu item --- ui/jquery.ui.menubar.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 7a3dd83aa61..11d66332f26 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -101,6 +101,9 @@ $.widget( "ui.menubar", { this._close(); return; } + if ( event.type === "mouseenter" ) { + this.element.find(":focus").focusout(); + } if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { if ( this.options.autoExpand ) { clearTimeout( this.closeTimer ); From baf9882c33b55770c63b58a988153762c6527c7d Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 8 Dec 2012 15:51:24 -0800 Subject: [PATCH 12/33] menubar: more spaces --- ui/jquery.ui.menubar.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 11d66332f26..024c4782531 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -55,7 +55,7 @@ $.widget( "ui.menubar", { ui.item.parents( "ul.ui-menu:last" ).hide(); that._close(); // TODO what is this targetting? there's probably a better way to access it - $(event.target).prev().focus(); + $( event.target ).prev().focus(); that._trigger( "select", event, ui ); }, menus: this.options.menuElement @@ -83,9 +83,9 @@ $.widget( "ui.menubar", { } } }); - this.items.each(function() { + this.items.each( function() { var mouseBehaviorCallback, keyboardBehaviorCallback, - input = $(this), + input = $( this ), // TODO menu var is only used on two places, doesn't quite justify the .each menu = input.next( that.options.menuElement ); @@ -118,7 +118,7 @@ $.widget( "ui.menubar", { case $.ui.keyCode.SPACE: case $.ui.keyCode.UP: case $.ui.keyCode.DOWN: - that._open( event, $(event.target).next() ); + that._open( event, $( event.target ).next() ); event.preventDefault(); break; case $.ui.keyCode.LEFT: @@ -232,7 +232,7 @@ $.widget( "ui.menubar", { .removeAttr( "role" ) .removeAttr( "aria-haspopup" ) // TODO unwrap? - .children( "span.ui-button-text" ).each(function( i, e ) { + .children( "span.ui-button-text" ).each( function( i, e ) { var item = $( this ); item.parent().html( item.html() ); }) From 07009829ae7905c5d6c00a30661bb162917fa100 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 8 Dec 2012 19:45:23 -0800 Subject: [PATCH 13/33] menubar: visual test: add to index page --- tests/visual/index.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/visual/index.html b/tests/visual/index.html index 2dfb1f1ed22..285e0127353 100644 --- a/tests/visual/index.html +++ b/tests/visual/index.html @@ -47,6 +47,11 @@

      Menu

    • General
    +

    Menubar

    + +

    Position

    • General
    • From 2500c9620fe12a8d15610cfb2f54f88083d7c90a Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 8 Dec 2012 20:18:18 -0800 Subject: [PATCH 14/33] menubar: kbd / mouse interaction 1. Mouse over a menu 1. Click to expand the sub menu 1. Mouse away 1. Hit escape to collapse the sub menu 1. Key left, now there are multiple highlighted top-level menu items. Wrong. --- ui/jquery.ui.menubar.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 024c4782531..f7a1e49091c 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -189,6 +189,7 @@ $.widget( "ui.menubar", { var active = that.active; that.active.blur(); that._close( event ); + $(event.target).blur().mouseleave(); active.prev().focus(); } }, From bb87639f88c3c6cfd44a0e5f14e1856ac274917b Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Mon, 10 Dec 2012 08:29:46 -0800 Subject: [PATCH 15/33] menubar: spacing and formatting --- ui/jquery.ui.menubar.js | 22 +- ui/jquery.ui.menubar.js.orig | 591 +++++++++++++++++++++++++++++++++++ 2 files changed, 602 insertions(+), 11 deletions(-) create mode 100644 ui/jquery.ui.menubar.js.orig diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index f7a1e49091c..a1493157a7c 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -32,8 +32,10 @@ $.widget( "ui.menubar", { }, _create: function() { var that = this, subMenus; - this.menuItems = this.element.children( this.options.items ); // Top-level
    • s - this.items = this.menuItems.children( "button, a" ); // Links in those top-level
    • s + // Top-level elements containing the submenu-triggering elem + this.menuItems = this.element.children( this.options.items ); + // Links or buttons in menuItems, triggers of the submenus + this.items = this.menuItems.children( "button, a" ); this.menuItems .addClass( "ui-menubar-item" ) @@ -46,7 +48,8 @@ $.widget( "ui.menubar", { .attr( "role", "menubar" ); this._focusable( this.items ); this._hoverable( this.items ); - subMenus = this.items.siblings( this.options.menuElement ) // sub-contained
        + // Sub-contained container, typically a
          + subMenus = this.items.siblings( this.options.menuElement ) .menu({ position: { within: this.options.position.within @@ -149,7 +152,7 @@ $.widget( "ui.menubar", { } if ( !menu.length ) { - that._off( input, "click mouseenter keydown"); + that._off( input, "click mouseenter keydown" ); that._hoverable( input ); that._on( input, { click: function( event ) { @@ -161,15 +164,12 @@ $.widget( "ui.menubar", { } }, keydown: function( event ) { - switch ( event.keyCode ) { - case $.ui.keyCode.LEFT: + if ( event.keyCode === $.ui.keyCode.LEFT ) { this.previous( event ); event.preventDefault(); - break; - case $.ui.keyCode.RIGHT: + } else if ( event.keyCode === $.ui.keyCode.RIGHT ) { this.next( event ); event.preventDefault(); - break; } } }); @@ -189,7 +189,7 @@ $.widget( "ui.menubar", { var active = that.active; that.active.blur(); that._close( event ); - $(event.target).blur().mouseleave(); + $( event.target ).blur().mouseleave(); active.prev().focus(); } }, @@ -233,7 +233,7 @@ $.widget( "ui.menubar", { .removeAttr( "role" ) .removeAttr( "aria-haspopup" ) // TODO unwrap? - .children( "span.ui-button-text" ).each( function( i, e ) { + .children( "span.ui-button-text" ).each(function( i, e ) { var item = $( this ); item.parent().html( item.html() ); }) diff --git a/ui/jquery.ui.menubar.js.orig b/ui/jquery.ui.menubar.js.orig new file mode 100644 index 00000000000..a6aa5da234c --- /dev/null +++ b/ui/jquery.ui.menubar.js.orig @@ -0,0 +1,591 @@ +/* + * jQuery UI Menubar @VERSION + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Menubar + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.position.js + * jquery.ui.menu.js + */ +(function( $ ) { + +// TODO when mixing clicking menus and keyboard navigation, focus handling is broken +// there has to be just one item that has tabindex +$.widget( "ui.menubar", { + version: "@VERSION", + options: { + autoExpand: false, + buttons: false, + items: "li", + menuElement: "ul", + menuIcon: false, + position: { + my: "left top", + at: "left bottom" + } + }, + _create: function() { + // Top-level items, typically li + this.menuItems = this.element.children( this.options.items ); + + // (Typically) menuItem contained, event-able used to launch a sub-menu + this.items = this.menuItems.children( "button, a" ); + + // Keep track of open submenus + this.openSubmenus = 0; + + // (Typically) a ul + var subMenus = this.items.siblings( this.options.menuElement ); + + this._prepareElement(this.element); + this._prepareMenuItems(this.menuItems); + this._prepareItems(this.items); + this._prepareSubMenus(subMenus); + }, + _prepareElement: function(element) { + element + .addClass( "ui-menubar ui-widget-header ui-helper-clearfix" ) + .attr( "role", "menubar" ); +<<<<<<< HEAD + this._bindPluginElementEvents(element); + }, + _prepareMenuItems: function(menuItems) { + menuItems.addClass( "ui-menubar-item" ) + .attr( "role", "presentation" ); + }, + _prepareItems: function(menubarTopLevelItems) { + // let only the first item receive focus + menubarTopLevelItems.slice(1).attr( "tabIndex", -1 ); + + this._focusable( menubarTopLevelItems ); + this._hoverable( menubarTopLevelItems ); + this._applyItemsEventBehavior( menubarTopLevelItems ); + }, + _applyItemsEventBehavior: function($topLevelItems) { + var menubar = this; + $topLevelItems.each(function() { + var input = $(this), +||||||| parent of c2e58ce... menubar: spacing / style fix + this._focusable( this.items ); + this._hoverable( this.items ); + subMenus = this.items.siblings( this.options.menuElement ) + .menu({ + position: { + within: this.options.position.within + }, + select: function( event, ui ) { + ui.item.parents( "ul.ui-menu:last" ).hide(); + that._close(); + // TODO what is this targetting? there's probably a better way to access it + $(event.target).prev().focus(); + that._trigger( "select", event, ui ); + }, + menus: this.options.menuElement + }) + .hide() + .attr({ + "aria-hidden": "true", + "aria-expanded": "false" + }); + this._on( subMenus, { + + "keydown": function(event) { + var menu = $( this ); + if ( menu.is( ":hidden" ) ) { + return; + } + switch ( event.keyCode ) { + case $.ui.keyCode.LEFT: + this.previous( event ); + event.preventDefault(); + break; + case $.ui.keyCode.RIGHT: + this.next( event ); + event.preventDefault(); + break; + } + } + }); + this.items.each(function() { + var input = $(this), +======= + this._focusable( this.items ); + this._hoverable( this.items ); + subMenus = this.items.siblings( this.options.menuElement ) + .menu({ + position: { + within: this.options.position.within + }, + select: function( event, ui ) { + ui.item.parents( "ul.ui-menu:last" ).hide(); + that._close(); + // TODO what is this targetting? there's probably a better way to access it + $(event.target).prev().focus(); + that._trigger( "select", event, ui ); + }, + menus: this.options.menuElement + }) + .hide() + .attr({ + "aria-hidden": "true", + "aria-expanded": "false" + }); + this._on( subMenus, { + keydown: function( event ) { + var menu = $( this ); + if ( menu.is( ":hidden" ) ) { + return; + } + switch ( event.keyCode ) { + case $.ui.keyCode.LEFT: + this.previous( event ); + event.preventDefault(); + break; + case $.ui.keyCode.RIGHT: + this.next( event ); + event.preventDefault(); + break; + } + } + }); + this.items.each(function() { + var mouseBehaviorCallback, keyboardBehaviorCallback, + input = $(this), +>>>>>>> c2e58ce... menubar: spacing / style fix + // TODO menu var is only used on two places, doesn't quite justify the .each +<<<<<<< HEAD + menu = input.next( menubar.options.menuElement ), + isNonmenuButton = menu.length; +||||||| parent of c2e58ce... menubar: spacing / style fix + menu = input.next( that.options.menuElement ), + mouseBehaviorCallback, keyboardBehaviorCallback; +======= + menu = input.next( that.options.menuElement ); +>>>>>>> c2e58ce... menubar: spacing / style fix + +<<<<<<< HEAD + if ( isNonmenuButton ) { + menubar._applyItemsKeyboardEvents(menubar, input, menu); + menubar._applyItemsMouseEvents(menubar, input, menu); + menubar._prepareMenuIcon(menubar, input); +||||||| parent of c2e58ce... menubar: spacing / style fix + mouseBehaviorCallback = function( event ) { + // ignore triggered focus event + if ( event.type === "focus" && !event.originalEvent ) { + return; + } + event.preventDefault(); + // TODO can we simplify or extractthis check? especially the last two expressions + // there's a similar active[0] == menu[0] check in _open + if ( event.type === "click" && menu.is( ":visible" ) && this.active && this.active[0] === menu[0] ) { + this._close(); + return; + } + if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { + if( this.options.autoExpand ) { + clearTimeout( this.closeTimer ); + } +======= + mouseBehaviorCallback = function( event ) { + // ignore triggered focus event + if ( event.type === "focus" && !event.originalEvent ) { + return; + } + event.preventDefault(); + // TODO can we simplify or extract this check? especially the last two expressions + // there's a similar active[0] == menu[0] check in _open + if ( event.type === "click" && menu.is( ":visible" ) && this.active && this.active[0] === menu[0] ) { + this._close(); + return; + } + if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { + if ( this.options.autoExpand ) { + clearTimeout( this.closeTimer ); + } +>>>>>>> c2e58ce... menubar: spacing / style fix + + input.attr( "aria-haspopup", "true" ); + } else { + menubar._applyNonMenuButtonEvents(menubar, input); + } + + menubar._prepareInputItem(menubar, input); + }); + }, + _prepareSubMenus: function(subMenus) { + var menubar = this; + + subMenus.menu({ + position: { + within: this.options.position.within + }, + select: function( event, ui ) { + ui.item.parents( "ul.ui-menu:last" ).hide(); + menubar._close(); + // TODO what is this targetting? there's probably a better way to access it + $(event.target).prev().focus(); + menubar._trigger( "select", event, ui ); + }, + menus: this.options.menuElement + }) + .hide() + .attr({ + "aria-hidden": "true", + "aria-expanded": "false" + }); + + this._on( subMenus, { + "keydown": function(event) { + var menu = $( this ); + if ( menu.is( ":hidden" ) ) { + return; + } + switch ( event.keyCode ) { + case $.ui.keyCode.LEFT: + this.previous( event ); + event.preventDefault(); + break; + case $.ui.keyCode.RIGHT: + this.next( event ); + event.preventDefault(); + break; + } +<<<<<<< HEAD +||||||| parent of c2e58ce... menubar: spacing / style fix + }; + + // might be a non-menu button + if ( menu.length ) { + that._on(input, { + "click": mouseBehaviorCallback, + "focus": mouseBehaviorCallback, + "mouseenter": mouseBehaviorCallback, + "keydown": keyboardBehaviorCallback + }); + + input.attr( "aria-haspopup", "true" ); + + // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged + if ( that.options.menuIcon ) { + input.addClass( "ui-state-default" ).append( "" ); + input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); + } + } else { + that._on(input, { + click: function(event) { + this._close(); + }, + + mouseenter: function(event) { + if (this.open){ + this._close(); + } + } + }); + } + + input + .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) + .attr( "role", "menuitem" ) + .wrapInner( "" ); + + if ( that.options.buttons ) { + input.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" ); +======= + }; + + // might be a non-menu button + if ( menu.length ) { + that._on( input, { + click: mouseBehaviorCallback, + focus: mouseBehaviorCallback, + mouseenter: mouseBehaviorCallback, + keydown: keyboardBehaviorCallback + }); + + input.attr( "aria-haspopup", "true" ); + + // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged + if ( that.options.menuIcon ) { + input.addClass( "ui-state-default" ).append( "" ); + input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); + } + } else { + that._on( input, { + click: function( event ) { + this._close(); + }, + + mouseenter: function( event ) { + if ( this.open ) { + this._close(); + } + } + }); + } + + input + .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) + .attr( "role", "menuitem" ) + .wrapInner( "" ); + + if ( that.options.buttons ) { + input.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" ); +>>>>>>> c2e58ce... menubar: spacing / style fix + } + }); + }, + _bindPluginElementEvents: function(myElement) { + var menubar = this; + menubar._on( myElement, { + keydown: function( event ) { + if ( event.keyCode === $.ui.keyCode.ESCAPE && menubar.active && menubar.active.menu( "collapse", event ) !== true ) { + var active = menubar.active; + menubar.active.blur(); + menubar._close( event ); + active.prev().focus(); + } + }, + focusin: function( event ) { + clearTimeout( menubar.closeTimer ); + }, + focusout: function( event ) { + menubar.closeTimer = setTimeout( function() { + menubar._close( event ); + }, 150); + }, + "mouseleave .ui-menubar-item": function( event ) { + if ( menubar.options.autoExpand ) { + menubar.closeTimer = setTimeout( function() { + menubar._close( event ); + }, 150); + } + }, + "mouseenter .ui-menubar-item": function( event ) { + clearTimeout( menubar.closeTimer ); + } + }); + }, + _prepareInputItem: function(menubar, $input) { + $input + .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) + .attr( "role", "menuitem" ) + .wrapInner( "" ); + + if ( menubar.options.buttons ) { + $input.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" ); + } + }, + _applyItemsKeyboardEvents: function(menubar, $input) { + var keyboardBehaviorCallback = function( event ) { + switch ( event.keyCode ) { + case $.ui.keyCode.SPACE: + case $.ui.keyCode.UP: + case $.ui.keyCode.DOWN: + this._open( event, $( this ).next() ); + event.preventDefault(); + break; + case $.ui.keyCode.LEFT: + this.previous( event ); + event.preventDefault(); + break; + case $.ui.keyCode.RIGHT: + this.next( event ); + event.preventDefault(); + break; + } + }; + menubar._on($input, { + "keydown": keyboardBehaviorCallback + }); + }, + _applyItemsMouseEvents: function(menubar, $input, menu) { + var mouseBehaviorCallback = function( event ) { + // ignore triggered focus event + if ( event.type === "focus" && !event.originalEvent ) { + return; + } + event.preventDefault(); + // TODO can we simplify or extractthis check? especially the last two expressions + // there's a similar active[0] == menu[0] check in _open + if ( event.type === "click" && menu.is( ":visible" ) && this.active && this.active[0] === menu[0] ) { + this._close(); + return; + } + if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { + if( this.options.autoExpand ) { + clearTimeout( this.closeTimer ); + } + + this._open( event, menu ); + } + }; + menubar._on($input, { + "click": mouseBehaviorCallback, + "focus": mouseBehaviorCallback, + "mouseenter": mouseBehaviorCallback + }); + }, + _applyNonMenuButtonEvents: function(menubar, input) { + menubar._on(input, { + click: function(event) { + this._close(); + }, + mouseenter: function(event) { + if (this.open){ + this._close(); + } + } + }); + }, + _prepareMenuIcon: function(menubar, $input) { + // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged + if ( menubar.options.menuIcon ) { + $input.addClass( "ui-state-default" ).append( "" ); + $input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); + } + }, + _destroy : function() { + this.menuItems + .removeClass( "ui-menubar-item" ) + .removeAttr( "role" ); + + this.element + .removeClass( "ui-menubar ui-widget-header ui-helper-clearfix" ) + .removeAttr( "role" ) + .unbind( ".menubar" ); + + this.items + .unbind( ".menubar" ) + .removeClass( "ui-button ui-widget ui-button-text-only ui-menubar-link ui-state-default" ) + .removeAttr( "role" ) + .removeAttr( "aria-haspopup" ) + // TODO unwrap? + .children( "span.ui-button-text" ).each(function( i, e ) { + var item = $( this ); + item.parent().html( item.html() ); + }) + .end() + .children( ".ui-icon" ).remove(); + + this.element.find( ":ui-menu" ) + .menu( "destroy" ) + .show() + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "tabindex" ) + .unbind( ".menubar" ); + }, + + _close: function() { + if ( !this.active || !this.active.length ) { + return; + } + this.active + .menu( "collapseAll" ) + .hide() + .attr({ + "aria-hidden": "true", + "aria-expanded": "false" + }); + this.active + .prev() + .removeClass( "ui-state-active" ) + .removeAttr( "tabIndex" ); + this.active = null; + this.open = false; + this.openSubmenus = 0; + }, + + _open: function( event, menu ) { + // on a single-button menubar, ignore reopening the same menu + if ( this.active && this.active[0] === menu[0] ) { + return; + } + // TODO refactor, almost the same as _close above, but don't remove tabIndex + if ( this.active ) { + this.active + .menu( "collapseAll" ) + .hide() + .attr({ + "aria-hidden": "true", + "aria-expanded": "false" + }); + this.active + .prev() + .removeClass( "ui-state-active" ); + } + // set tabIndex -1 to have the button skipped on shift-tab when menu is open (it gets focus) + var button = menu.prev().addClass( "ui-state-active" ).attr( "tabIndex", -1 ); + this.active = menu + .show() + .position( $.extend({ + of: button + }, this.options.position ) ) + .removeAttr( "aria-hidden" ) + .attr( "aria-expanded", "true" ) + .menu("focus", event, menu.children( ".ui-menu-item" ).first() ) + // TODO need a comment here why both events are triggered + .focus() + .focusin(); + this.open = true; + }, + + next: function( event ) { + if ( this.open && this.active.data( "menu" ).active.has( ".ui-menu" ).length ) { + // Track number of open submenus and prevent moving to next menubar item + this.openSubmenus++; + return; + } + this.openSubmenus = 0; + this._move( "next", "first", event ); + }, + + previous: function( event ) { + if ( this.open && this.openSubmenus ) { + // Track number of open submenus and prevent moving to previous menubar item + this.openSubmenus--; + return; + } + this.openSubmenus = 0; + this._move( "prev", "last", event ); + }, + + _move: function( direction, filter, event ) { + var next, + wrapItem; + if ( this.open ) { + next = this.active.closest( ".ui-menubar-item" )[ direction + "All" ]( this.options.items ).first().children( ".ui-menu" ).eq( 0 ); + wrapItem = this.menuItems[ filter ]().children( ".ui-menu" ).eq( 0 ); + } else { + if ( event ) { + next = $( event.target ).closest( ".ui-menubar-item" )[ direction + "All" ]( this.options.items ).children( ".ui-menubar-link" ).eq( 0 ); + wrapItem = this.menuItems[ filter ]().children( ".ui-menubar-link" ).eq( 0 ); + } else { + next = wrapItem = this.menuItems.children( "a" ).eq( 0 ); + } + } + + if ( next.length ) { + if ( this.open ) { + this._open( event, next ); + } else { + next.removeAttr( "tabIndex")[0].focus(); + } + } else { + if ( this.open ) { + this._open( event, wrapItem ); + } else { + wrapItem.removeAttr( "tabIndex")[0].focus(); + } + } + } +}); + +}( jQuery )); From b74695ace668032abeab19684973b79b58acf448 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Mon, 10 Dec 2012 11:10:54 -0800 Subject: [PATCH 16/33] menubar: rm erroneously committed .orig file --- ui/jquery.ui.menubar.js.orig | 591 ----------------------------------- 1 file changed, 591 deletions(-) delete mode 100644 ui/jquery.ui.menubar.js.orig diff --git a/ui/jquery.ui.menubar.js.orig b/ui/jquery.ui.menubar.js.orig deleted file mode 100644 index a6aa5da234c..00000000000 --- a/ui/jquery.ui.menubar.js.orig +++ /dev/null @@ -1,591 +0,0 @@ -/* - * jQuery UI Menubar @VERSION - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Menubar - * - * Depends: - * jquery.ui.core.js - * jquery.ui.widget.js - * jquery.ui.position.js - * jquery.ui.menu.js - */ -(function( $ ) { - -// TODO when mixing clicking menus and keyboard navigation, focus handling is broken -// there has to be just one item that has tabindex -$.widget( "ui.menubar", { - version: "@VERSION", - options: { - autoExpand: false, - buttons: false, - items: "li", - menuElement: "ul", - menuIcon: false, - position: { - my: "left top", - at: "left bottom" - } - }, - _create: function() { - // Top-level items, typically li - this.menuItems = this.element.children( this.options.items ); - - // (Typically) menuItem contained, event-able used to launch a sub-menu - this.items = this.menuItems.children( "button, a" ); - - // Keep track of open submenus - this.openSubmenus = 0; - - // (Typically) a ul - var subMenus = this.items.siblings( this.options.menuElement ); - - this._prepareElement(this.element); - this._prepareMenuItems(this.menuItems); - this._prepareItems(this.items); - this._prepareSubMenus(subMenus); - }, - _prepareElement: function(element) { - element - .addClass( "ui-menubar ui-widget-header ui-helper-clearfix" ) - .attr( "role", "menubar" ); -<<<<<<< HEAD - this._bindPluginElementEvents(element); - }, - _prepareMenuItems: function(menuItems) { - menuItems.addClass( "ui-menubar-item" ) - .attr( "role", "presentation" ); - }, - _prepareItems: function(menubarTopLevelItems) { - // let only the first item receive focus - menubarTopLevelItems.slice(1).attr( "tabIndex", -1 ); - - this._focusable( menubarTopLevelItems ); - this._hoverable( menubarTopLevelItems ); - this._applyItemsEventBehavior( menubarTopLevelItems ); - }, - _applyItemsEventBehavior: function($topLevelItems) { - var menubar = this; - $topLevelItems.each(function() { - var input = $(this), -||||||| parent of c2e58ce... menubar: spacing / style fix - this._focusable( this.items ); - this._hoverable( this.items ); - subMenus = this.items.siblings( this.options.menuElement ) - .menu({ - position: { - within: this.options.position.within - }, - select: function( event, ui ) { - ui.item.parents( "ul.ui-menu:last" ).hide(); - that._close(); - // TODO what is this targetting? there's probably a better way to access it - $(event.target).prev().focus(); - that._trigger( "select", event, ui ); - }, - menus: this.options.menuElement - }) - .hide() - .attr({ - "aria-hidden": "true", - "aria-expanded": "false" - }); - this._on( subMenus, { - - "keydown": function(event) { - var menu = $( this ); - if ( menu.is( ":hidden" ) ) { - return; - } - switch ( event.keyCode ) { - case $.ui.keyCode.LEFT: - this.previous( event ); - event.preventDefault(); - break; - case $.ui.keyCode.RIGHT: - this.next( event ); - event.preventDefault(); - break; - } - } - }); - this.items.each(function() { - var input = $(this), -======= - this._focusable( this.items ); - this._hoverable( this.items ); - subMenus = this.items.siblings( this.options.menuElement ) - .menu({ - position: { - within: this.options.position.within - }, - select: function( event, ui ) { - ui.item.parents( "ul.ui-menu:last" ).hide(); - that._close(); - // TODO what is this targetting? there's probably a better way to access it - $(event.target).prev().focus(); - that._trigger( "select", event, ui ); - }, - menus: this.options.menuElement - }) - .hide() - .attr({ - "aria-hidden": "true", - "aria-expanded": "false" - }); - this._on( subMenus, { - keydown: function( event ) { - var menu = $( this ); - if ( menu.is( ":hidden" ) ) { - return; - } - switch ( event.keyCode ) { - case $.ui.keyCode.LEFT: - this.previous( event ); - event.preventDefault(); - break; - case $.ui.keyCode.RIGHT: - this.next( event ); - event.preventDefault(); - break; - } - } - }); - this.items.each(function() { - var mouseBehaviorCallback, keyboardBehaviorCallback, - input = $(this), ->>>>>>> c2e58ce... menubar: spacing / style fix - // TODO menu var is only used on two places, doesn't quite justify the .each -<<<<<<< HEAD - menu = input.next( menubar.options.menuElement ), - isNonmenuButton = menu.length; -||||||| parent of c2e58ce... menubar: spacing / style fix - menu = input.next( that.options.menuElement ), - mouseBehaviorCallback, keyboardBehaviorCallback; -======= - menu = input.next( that.options.menuElement ); ->>>>>>> c2e58ce... menubar: spacing / style fix - -<<<<<<< HEAD - if ( isNonmenuButton ) { - menubar._applyItemsKeyboardEvents(menubar, input, menu); - menubar._applyItemsMouseEvents(menubar, input, menu); - menubar._prepareMenuIcon(menubar, input); -||||||| parent of c2e58ce... menubar: spacing / style fix - mouseBehaviorCallback = function( event ) { - // ignore triggered focus event - if ( event.type === "focus" && !event.originalEvent ) { - return; - } - event.preventDefault(); - // TODO can we simplify or extractthis check? especially the last two expressions - // there's a similar active[0] == menu[0] check in _open - if ( event.type === "click" && menu.is( ":visible" ) && this.active && this.active[0] === menu[0] ) { - this._close(); - return; - } - if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { - if( this.options.autoExpand ) { - clearTimeout( this.closeTimer ); - } -======= - mouseBehaviorCallback = function( event ) { - // ignore triggered focus event - if ( event.type === "focus" && !event.originalEvent ) { - return; - } - event.preventDefault(); - // TODO can we simplify or extract this check? especially the last two expressions - // there's a similar active[0] == menu[0] check in _open - if ( event.type === "click" && menu.is( ":visible" ) && this.active && this.active[0] === menu[0] ) { - this._close(); - return; - } - if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { - if ( this.options.autoExpand ) { - clearTimeout( this.closeTimer ); - } ->>>>>>> c2e58ce... menubar: spacing / style fix - - input.attr( "aria-haspopup", "true" ); - } else { - menubar._applyNonMenuButtonEvents(menubar, input); - } - - menubar._prepareInputItem(menubar, input); - }); - }, - _prepareSubMenus: function(subMenus) { - var menubar = this; - - subMenus.menu({ - position: { - within: this.options.position.within - }, - select: function( event, ui ) { - ui.item.parents( "ul.ui-menu:last" ).hide(); - menubar._close(); - // TODO what is this targetting? there's probably a better way to access it - $(event.target).prev().focus(); - menubar._trigger( "select", event, ui ); - }, - menus: this.options.menuElement - }) - .hide() - .attr({ - "aria-hidden": "true", - "aria-expanded": "false" - }); - - this._on( subMenus, { - "keydown": function(event) { - var menu = $( this ); - if ( menu.is( ":hidden" ) ) { - return; - } - switch ( event.keyCode ) { - case $.ui.keyCode.LEFT: - this.previous( event ); - event.preventDefault(); - break; - case $.ui.keyCode.RIGHT: - this.next( event ); - event.preventDefault(); - break; - } -<<<<<<< HEAD -||||||| parent of c2e58ce... menubar: spacing / style fix - }; - - // might be a non-menu button - if ( menu.length ) { - that._on(input, { - "click": mouseBehaviorCallback, - "focus": mouseBehaviorCallback, - "mouseenter": mouseBehaviorCallback, - "keydown": keyboardBehaviorCallback - }); - - input.attr( "aria-haspopup", "true" ); - - // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged - if ( that.options.menuIcon ) { - input.addClass( "ui-state-default" ).append( "" ); - input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); - } - } else { - that._on(input, { - click: function(event) { - this._close(); - }, - - mouseenter: function(event) { - if (this.open){ - this._close(); - } - } - }); - } - - input - .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) - .attr( "role", "menuitem" ) - .wrapInner( "" ); - - if ( that.options.buttons ) { - input.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" ); -======= - }; - - // might be a non-menu button - if ( menu.length ) { - that._on( input, { - click: mouseBehaviorCallback, - focus: mouseBehaviorCallback, - mouseenter: mouseBehaviorCallback, - keydown: keyboardBehaviorCallback - }); - - input.attr( "aria-haspopup", "true" ); - - // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged - if ( that.options.menuIcon ) { - input.addClass( "ui-state-default" ).append( "" ); - input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); - } - } else { - that._on( input, { - click: function( event ) { - this._close(); - }, - - mouseenter: function( event ) { - if ( this.open ) { - this._close(); - } - } - }); - } - - input - .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) - .attr( "role", "menuitem" ) - .wrapInner( "" ); - - if ( that.options.buttons ) { - input.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" ); ->>>>>>> c2e58ce... menubar: spacing / style fix - } - }); - }, - _bindPluginElementEvents: function(myElement) { - var menubar = this; - menubar._on( myElement, { - keydown: function( event ) { - if ( event.keyCode === $.ui.keyCode.ESCAPE && menubar.active && menubar.active.menu( "collapse", event ) !== true ) { - var active = menubar.active; - menubar.active.blur(); - menubar._close( event ); - active.prev().focus(); - } - }, - focusin: function( event ) { - clearTimeout( menubar.closeTimer ); - }, - focusout: function( event ) { - menubar.closeTimer = setTimeout( function() { - menubar._close( event ); - }, 150); - }, - "mouseleave .ui-menubar-item": function( event ) { - if ( menubar.options.autoExpand ) { - menubar.closeTimer = setTimeout( function() { - menubar._close( event ); - }, 150); - } - }, - "mouseenter .ui-menubar-item": function( event ) { - clearTimeout( menubar.closeTimer ); - } - }); - }, - _prepareInputItem: function(menubar, $input) { - $input - .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) - .attr( "role", "menuitem" ) - .wrapInner( "" ); - - if ( menubar.options.buttons ) { - $input.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" ); - } - }, - _applyItemsKeyboardEvents: function(menubar, $input) { - var keyboardBehaviorCallback = function( event ) { - switch ( event.keyCode ) { - case $.ui.keyCode.SPACE: - case $.ui.keyCode.UP: - case $.ui.keyCode.DOWN: - this._open( event, $( this ).next() ); - event.preventDefault(); - break; - case $.ui.keyCode.LEFT: - this.previous( event ); - event.preventDefault(); - break; - case $.ui.keyCode.RIGHT: - this.next( event ); - event.preventDefault(); - break; - } - }; - menubar._on($input, { - "keydown": keyboardBehaviorCallback - }); - }, - _applyItemsMouseEvents: function(menubar, $input, menu) { - var mouseBehaviorCallback = function( event ) { - // ignore triggered focus event - if ( event.type === "focus" && !event.originalEvent ) { - return; - } - event.preventDefault(); - // TODO can we simplify or extractthis check? especially the last two expressions - // there's a similar active[0] == menu[0] check in _open - if ( event.type === "click" && menu.is( ":visible" ) && this.active && this.active[0] === menu[0] ) { - this._close(); - return; - } - if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { - if( this.options.autoExpand ) { - clearTimeout( this.closeTimer ); - } - - this._open( event, menu ); - } - }; - menubar._on($input, { - "click": mouseBehaviorCallback, - "focus": mouseBehaviorCallback, - "mouseenter": mouseBehaviorCallback - }); - }, - _applyNonMenuButtonEvents: function(menubar, input) { - menubar._on(input, { - click: function(event) { - this._close(); - }, - mouseenter: function(event) { - if (this.open){ - this._close(); - } - } - }); - }, - _prepareMenuIcon: function(menubar, $input) { - // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged - if ( menubar.options.menuIcon ) { - $input.addClass( "ui-state-default" ).append( "" ); - $input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); - } - }, - _destroy : function() { - this.menuItems - .removeClass( "ui-menubar-item" ) - .removeAttr( "role" ); - - this.element - .removeClass( "ui-menubar ui-widget-header ui-helper-clearfix" ) - .removeAttr( "role" ) - .unbind( ".menubar" ); - - this.items - .unbind( ".menubar" ) - .removeClass( "ui-button ui-widget ui-button-text-only ui-menubar-link ui-state-default" ) - .removeAttr( "role" ) - .removeAttr( "aria-haspopup" ) - // TODO unwrap? - .children( "span.ui-button-text" ).each(function( i, e ) { - var item = $( this ); - item.parent().html( item.html() ); - }) - .end() - .children( ".ui-icon" ).remove(); - - this.element.find( ":ui-menu" ) - .menu( "destroy" ) - .show() - .removeAttr( "aria-hidden" ) - .removeAttr( "aria-expanded" ) - .removeAttr( "tabindex" ) - .unbind( ".menubar" ); - }, - - _close: function() { - if ( !this.active || !this.active.length ) { - return; - } - this.active - .menu( "collapseAll" ) - .hide() - .attr({ - "aria-hidden": "true", - "aria-expanded": "false" - }); - this.active - .prev() - .removeClass( "ui-state-active" ) - .removeAttr( "tabIndex" ); - this.active = null; - this.open = false; - this.openSubmenus = 0; - }, - - _open: function( event, menu ) { - // on a single-button menubar, ignore reopening the same menu - if ( this.active && this.active[0] === menu[0] ) { - return; - } - // TODO refactor, almost the same as _close above, but don't remove tabIndex - if ( this.active ) { - this.active - .menu( "collapseAll" ) - .hide() - .attr({ - "aria-hidden": "true", - "aria-expanded": "false" - }); - this.active - .prev() - .removeClass( "ui-state-active" ); - } - // set tabIndex -1 to have the button skipped on shift-tab when menu is open (it gets focus) - var button = menu.prev().addClass( "ui-state-active" ).attr( "tabIndex", -1 ); - this.active = menu - .show() - .position( $.extend({ - of: button - }, this.options.position ) ) - .removeAttr( "aria-hidden" ) - .attr( "aria-expanded", "true" ) - .menu("focus", event, menu.children( ".ui-menu-item" ).first() ) - // TODO need a comment here why both events are triggered - .focus() - .focusin(); - this.open = true; - }, - - next: function( event ) { - if ( this.open && this.active.data( "menu" ).active.has( ".ui-menu" ).length ) { - // Track number of open submenus and prevent moving to next menubar item - this.openSubmenus++; - return; - } - this.openSubmenus = 0; - this._move( "next", "first", event ); - }, - - previous: function( event ) { - if ( this.open && this.openSubmenus ) { - // Track number of open submenus and prevent moving to previous menubar item - this.openSubmenus--; - return; - } - this.openSubmenus = 0; - this._move( "prev", "last", event ); - }, - - _move: function( direction, filter, event ) { - var next, - wrapItem; - if ( this.open ) { - next = this.active.closest( ".ui-menubar-item" )[ direction + "All" ]( this.options.items ).first().children( ".ui-menu" ).eq( 0 ); - wrapItem = this.menuItems[ filter ]().children( ".ui-menu" ).eq( 0 ); - } else { - if ( event ) { - next = $( event.target ).closest( ".ui-menubar-item" )[ direction + "All" ]( this.options.items ).children( ".ui-menubar-link" ).eq( 0 ); - wrapItem = this.menuItems[ filter ]().children( ".ui-menubar-link" ).eq( 0 ); - } else { - next = wrapItem = this.menuItems.children( "a" ).eq( 0 ); - } - } - - if ( next.length ) { - if ( this.open ) { - this._open( event, next ); - } else { - next.removeAttr( "tabIndex")[0].focus(); - } - } else { - if ( this.open ) { - this._open( event, wrapItem ); - } else { - wrapItem.removeAttr( "tabIndex")[0].focus(); - } - } - } -}); - -}( jQuery )); From 706d8821c96824233774387f13e5c8270dc0afca Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Mon, 10 Dec 2012 12:57:26 -0800 Subject: [PATCH 17/33] menubar: spacing --- ui/jquery.ui.menubar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index a1493157a7c..5546cd48c00 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -86,7 +86,7 @@ $.widget( "ui.menubar", { } } }); - this.items.each( function() { + this.items.each(function() { var mouseBehaviorCallback, keyboardBehaviorCallback, input = $( this ), // TODO menu var is only used on two places, doesn't quite justify the .each From 8237fb24e3049ce70c5883e975e71784b6d47b04 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sun, 30 Dec 2012 12:53:00 -0800 Subject: [PATCH 18/33] menubar: massive refactor for readability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emergent in the menubar widget since my first patchfix (9f53e0a76) is that there is significant behavioral difference between: * A menuitem that *does* have a sub-menu * A menuitem that *does not* have a sub-menu Typically, from an OO POV, the caller should be able to call `menuItem.close()` on either one of these cases and not have to bear the burden of knowing whether or not said `menuItem` has submenus in it. Ergo, "properly" speaking, I would want to create two new constructors and assign the logic there. But the, "properly" those things should be their own class, perhaps rightly a class that should be part of the JQuery UI widget set. As an intermediary measure I have tried to use a very simple procedural style (à la C) so that the code is small, comprehensible, and doesn't rely on using `$.each` iteration to apply or remove behavior to elements. I believe, by using this behavior, the code is much more digestible. It as, at the very least, for me. Beyond the coding philosophy part of the refactor, I attempted to bring standard sane behavior for "menu item without submenu." This behavior may need to be modified as, up to the point of writing, there has been no fully definition of these menuItems' standard of behavior. --- tests/visual/menubar/menubar.html | 1 + ui/jquery.ui.menubar.js | 450 +++++++++++++++++++----------- 2 files changed, 290 insertions(+), 161 deletions(-) diff --git a/tests/visual/menubar/menubar.html b/tests/visual/menubar/menubar.html index f724abf39ab..b09f5073161 100644 --- a/tests/visual/menubar/menubar.html +++ b/tests/visual/menubar/menubar.html @@ -41,6 +41,7 @@

          Default menubar

        • Quit
        +
      • Stubby
      • Edit
          diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 5546cd48c00..9b51107f7dd 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -30,44 +30,117 @@ $.widget( "ui.menubar", { at: "left bottom" } }, + _create: function() { - var that = this, subMenus; // Top-level elements containing the submenu-triggering elem this.menuItems = this.element.children( this.options.items ); // Links or buttons in menuItems, triggers of the submenus - this.items = this.menuItems.children( "button, a" ); + this.items = []; - this.menuItems - .addClass( "ui-menubar-item" ) - .attr( "role", "presentation" ); - // let only the first item receive focus - this.items.slice(1).attr( "tabIndex", -1 ); + this._initializeMenubarsBoundElement(); + this._initializeWidget(); + this._initializeMenuItems(); + // Keep track of open submenus + this.openSubmenus = 0; + }, + + _initializeMenubarsBoundElement: function() { this.element .addClass( "ui-menubar ui-widget-header ui-helper-clearfix" ) .attr( "role", "menubar" ); - this._focusable( this.items ); - this._hoverable( this.items ); - // Sub-contained container, typically a
            - subMenus = this.items.siblings( this.options.menuElement ) - .menu({ - position: { - within: this.options.position.within - }, - select: function( event, ui ) { - ui.item.parents( "ul.ui-menu:last" ).hide(); - that._close(); - // TODO what is this targetting? there's probably a better way to access it - $( event.target ).prev().focus(); - that._trigger( "select", event, ui ); - }, - menus: this.options.menuElement - }) - .hide() - .attr({ - "aria-hidden": "true", - "aria-expanded": "false" - }); + }, + + _initializeWidget: function() { + var menubar = this; + + this._on( { + keydown: function( event ) { + if ( event.keyCode === $.ui.keyCode.ESCAPE && menubar.active && menubar.active.menu( "collapse", event ) !== true ) { + var active = menubar.active; + menubar.active.blur(); + menubar._close( event ); + $( event.target ).blur().mouseleave(); + active.prev().focus(); + } + }, + focusin: function( event ) { + clearTimeout( menubar.closeTimer ); + }, + focusout: function( event ) { + menubar.closeTimer = setTimeout( function() { + menubar._close( event ); + }, 150); + }, + "mouseleave .ui-menubar-item": function( event ) { + if ( menubar.options.autoExpand ) { + menubar.closeTimer = setTimeout( function() { + menubar._close( event ); + }, 150); + } + }, + "mouseenter .ui-menubar-item": function( event ) { + clearTimeout( menubar.closeTimer ); + } + }); + }, + + _initializeMenuItems: function() { + var $item, + menubar = this, + seenFirstItem = false; + + this.menuItems + .addClass( "ui-menubar-item" ) + .attr( "role", "presentation" ); + + $.each( this.menuItems, function( index, menuItem ){ + menubar._initializeMenuItem( $( menuItem ), menubar, seenFirstItem ); + }); + }, + + _initializeMenuItem: function( $menuItem, menubar ) { + var $item = $menuItem.children("button, a"); + + menubar._determineSubmenuStatus( $menuItem, menubar ); + if ( $menuItem.data("hasSubMenu") ) { + menubar._initializeSubMenu( $menuItem, menubar ); + } + + $item.data( "parentMenuItem", $menuItem ); + menubar.items.push( $item ); + menubar._initializeItem( $item, menubar ); + }, + + _determineSubmenuStatus: function ( $menuItem, menubar ) { + var subMenus = $menuItem.children( menubar.options.menuElement ), + hasSubMenu = subMenus.length > 0; + $menuItem.data( "hasSubMenu", hasSubMenu ); + }, + + _initializeSubMenu: function( $menuItem, menubar ){ + var subMenus = $menuItem.children( menubar.options.menuElement ); + + subMenus + .menu({ + position: { + within: this.options.position.within + }, + select: function( event, ui ) { + ui.item.parents( "ul.ui-menu:last" ).hide(); + menubar._close(); + // TODO what is this targetting? there's probably a better way to access it + $( event.target ).prev().focus(); + menubar._trigger( "select", event, ui ); + }, + menus: this.options.menuElement + }) + .hide() + .attr({ + "aria-hidden": "true", + "aria-expanded": "false" + }); + this._on( subMenus, { keydown: function( event ) { var menu = $( this ); @@ -86,12 +159,52 @@ $.widget( "ui.menubar", { } } }); - this.items.each(function() { - var mouseBehaviorCallback, keyboardBehaviorCallback, - input = $( this ), - // TODO menu var is only used on two places, doesn't quite justify the .each - menu = input.next( that.options.menuElement ); + }, + + _initializeItem: function( $anItem, menubar ) { + //only the first item is eligible to receive the focus + var menuItemHasSubMenu = $anItem.data("parentMenuItem").data("hasSubMenu"); + + // Only the first item is tab-able + if ( menubar.items.length === 1 ) { + $anItem.attr( "tabindex", 1 ); + } else { + $anItem.attr( "tabIndex", -1 ); + } + + this._focusable( this.items ); + this._hoverable( this.items ); + this._applyDOMPropertiesOnItem( $anItem, menubar); + + if ( menuItemHasSubMenu ) { + this.__applyMouseBehaviorForSubmenuHavingMenuItem( $anItem, menubar ); + this.__applyKeyboardBehaviorForSubmenuHavingMenuItem( $anItem, menubar ); + + $anItem.attr( "aria-haspopup", "true" ); + } else { + this.__applyMouseBehaviorForSubmenulessMenuItem( $anItem, menubar ); + this.__applyKeyboardBehaviorForSubmenulessMenuItem( $anItem, menubar ); + } + + if ( menubar.options.menuIcon ) { + $anItem.addClass( "ui-state-default" ).append( "" ); + $anItem.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); + } + }, + + _applyDOMPropertiesOnItem: function( $item, menubar) { + $item + .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) + .attr( "role", "menuitem" ) + .wrapInner( "" ); + if ( menubar.options.buttons ) { + $item.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" ); + } + }, + + __applyMouseBehaviorForSubmenuHavingMenuItem: function ( input, menubar ) { + var menu = input.next( menubar.options.menuElement ), mouseBehaviorCallback = function( event ) { // ignore triggered focus event if ( event.type === "focus" && !event.originalEvent ) { @@ -111,110 +224,74 @@ $.widget( "ui.menubar", { if ( this.options.autoExpand ) { clearTimeout( this.closeTimer ); } - this._open( event, menu ); } }; - keyboardBehaviorCallback = function( event ) { - switch ( event.keyCode ) { - case $.ui.keyCode.SPACE: - case $.ui.keyCode.UP: - case $.ui.keyCode.DOWN: - that._open( event, $( event.target ).next() ); - event.preventDefault(); - break; - case $.ui.keyCode.LEFT: - this.previous( event ); - event.preventDefault(); - break; - case $.ui.keyCode.RIGHT: - this.next( event ); - event.preventDefault(); - break; - } - }; - - // might be a non-menu button - that._on( input, { - click: mouseBehaviorCallback, - focus: mouseBehaviorCallback, - mouseenter: mouseBehaviorCallback, - keydown: keyboardBehaviorCallback - }); - - input.attr( "aria-haspopup", "true" ); - - // TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged - if ( that.options.menuIcon ) { - input.addClass( "ui-state-default" ).append( "" ); - input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); - } + menubar._on( input, { + click: mouseBehaviorCallback, + focus: mouseBehaviorCallback, + mouseenter: mouseBehaviorCallback + }); + }, - if ( !menu.length ) { - that._off( input, "click mouseenter keydown" ); - that._hoverable( input ); - that._on( input, { - click: function( event ) { - this._close(); - }, - mouseenter: function( event ) { - if ( this.open ) { - this._close(); - } - }, - keydown: function( event ) { - if ( event.keyCode === $.ui.keyCode.LEFT ) { - this.previous( event ); - event.preventDefault(); - } else if ( event.keyCode === $.ui.keyCode.RIGHT ) { - this.next( event ); - event.preventDefault(); - } - } - }); + __applyKeyboardBehaviorForSubmenuHavingMenuItem: function( input, menubar ) { + var keyboardBehaviorCallback = function( event ) { + switch ( event.keyCode ) { + case $.ui.keyCode.SPACE: + case $.ui.keyCode.UP: + case $.ui.keyCode.DOWN: + menubar._open( event, $( event.target ).next() ); + event.preventDefault(); + break; + case $.ui.keyCode.LEFT: + this.previous( event ); + event.preventDefault(); + break; + case $.ui.keyCode.RIGHT: + this.next( event ); + event.preventDefault(); + break; } - input - .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) - .attr( "role", "menuitem" ) - .wrapInner( "" ); + }; - if ( that.options.buttons ) { - input.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" ); - } + menubar._on( input, { + keydown: keyboardBehaviorCallback }); - that._on( { - keydown: function( event ) { - if ( event.keyCode === $.ui.keyCode.ESCAPE && that.active && that.active.menu( "collapse", event ) !== true ) { - var active = that.active; - that.active.blur(); - that._close( event ); - $( event.target ).blur().mouseleave(); - active.prev().focus(); + }, + + __applyMouseBehaviorForSubmenulessMenuItem: function( $anItem, menubar ) { + menubar._off( $anItem, "click mouseenter" ); + menubar._hoverable( $anItem ); + menubar._on( $anItem, { + click: function( event ) { + if ( this.active ) { + this._close(); + } else { + this.open = true; + this.active = $( $anItem ).parent(); } }, - focusin: function( event ) { - clearTimeout( that.closeTimer ); - }, - focusout: function( event ) { - that.closeTimer = setTimeout( function() { - that._close( event ); - }, 150); - }, - "mouseleave .ui-menubar-item": function( event ) { - if ( that.options.autoExpand ) { - that.closeTimer = setTimeout( function() { - that._close( event ); - }, 150); + mouseenter: function( event ) { + if ( this.open ) { + this._close(); } - }, - "mouseenter .ui-menubar-item": function( event ) { - clearTimeout( that.closeTimer ); } }); - - // Keep track of open submenus - this.openSubmenus = 0; + }, + __applyKeyboardBehaviorForSubmenulessMenuItem: function( $anItem, menubar ) { + var behavior = function( event ) { + if ( event.keyCode === $.ui.keyCode.LEFT ) { + this.previous( event ); + event.preventDefault(); + } else if ( event.keyCode === $.ui.keyCode.RIGHT ) { + this.next( event ); + event.preventDefault(); + } + }; + menubar._on( $anItem, { + keydown: behavior + }); }, _destroy : function() { @@ -253,31 +330,10 @@ $.widget( "ui.menubar", { if ( !this.active || !this.active.length ) { return; } - this.active - .menu( "collapseAll" ) - .hide() - .attr({ - "aria-hidden": "true", - "aria-expanded": "false" - }); - this.active - .prev() - .removeClass( "ui-state-active" ) - .removeAttr( "tabIndex" ); - this.active = null; - this.open = false; - this.openSubmenus = 0; - }, - _open: function( event, menu ) { - // on a single-button menubar, ignore reopening the same menu - if ( this.active && this.active[0] === menu[0] ) { - return; - } - // TODO refactor, almost the same as _close above, but don't remove tabIndex - if ( this.active ) { + if ( this.active.closest( this.options.items ).data("hasSubMenu") ) { this.active - .menu( "collapseAll" ) + .menu("collapseAll") .hide() .attr({ "aria-hidden": "true", @@ -285,10 +341,46 @@ $.widget( "ui.menubar", { }); this.active .prev() - .removeClass( "ui-state-active" ); + .removeClass( "ui-state-active" ) + .removeAttr( "tabIndex" ); + this.active.closest( this.options.items ).removeClass("ui-state-active"); + } else { + this.active + .attr({ + "aria-hidden": "true", + "aria-expanded": "false" + }); + } + + this.active = null; + this.open = false; + this.openSubmenus = 0; + }, + + _open: function( event, menu ) { + var button, + menuItem = menu.closest(".ui-menubar-item"); + + if ( this.active && this.active.length ) { + // TODO refactor, almost the same as _close above, but don't remove tabIndex + if ( this.active.closest( this.options.items ).data("hasSubMenu") ) { + this.active + .menu( "collapseAll" ) + .hide() + .attr({ + "aria-hidden": "true", + "aria-expanded": "false" + }); + this.active.closest(this.options.items) + .removeClass( "ui-state-active" ); + } else { + this.active.removeClass( "ui-state-active" ); + } } + // set tabIndex -1 to have the button skipped on shift-tab when menu is open (it gets focus) - var button = menu.prev().addClass( "ui-state-active" ).attr( "tabIndex", -1 ); + button = menuItem.addClass( "ui-state-active" ).attr( "tabIndex", -1 ); + this.active = menu .show() .position( $.extend({ @@ -300,17 +392,22 @@ $.widget( "ui.menubar", { // TODO need a comment here why both events are triggered .focus() .focusin(); + this.open = true; }, next: function( event ) { - if ( this.open && this.active.data( "menu" ).active.has( ".ui-menu" ).length ) { + if ( this.open && + this.active.closest( this.options.items ).data("hasSubMenu") && + this.active.data("menu").active && + this.active.data( "menu" ).active.has( ".ui-menu" ).length ) { // Track number of open submenus and prevent moving to next menubar item this.openSubmenus++; return; } this.openSubmenus = 0; this._move( "next", "first", event ); + }, previous: function( event ) { @@ -326,9 +423,12 @@ $.widget( "ui.menubar", { _move: function( direction, filter, event ) { var next, wrapItem; + if ( this.open ) { - next = this.active.closest( ".ui-menubar-item" )[ direction + "All" ]( this.options.items ).first().children( ".ui-menu" ).eq( 0 ); - wrapItem = this.menuItems[ filter ]().children( ".ui-menu" ).eq( 0 ); + next = this.active. + closest( ".ui-menubar-item" )[ direction + "All" ]( this.options.items ) + .first(); + wrapItem = this.menuItems[ filter ](); } else { if ( event ) { next = $( event.target ).closest( ".ui-menubar-item" )[ direction + "All" ]( this.options.items ).children( ".ui-menubar-link" ).eq( 0 ); @@ -338,20 +438,48 @@ $.widget( "ui.menubar", { } } - if ( next.length ) { - if ( this.open ) { - this._open( event, next ); + if ( !next.length ) { + next = wrapItem; + } + + if ( this.open ) { + if ( next.data("hasSubMenu") ) { + this._open( event, next.children("ul") ); } else { - next.removeAttr( "tabIndex")[0].focus(); + this._submenuless_open( event, next ); } } else { - if ( this.open ) { - this._open( event, wrapItem ); - } else { - wrapItem.removeAttr( "tabIndex")[0].focus(); + next.removeAttr( "tabIndex")[0].focus(); + } + + }, + + _submenuless_open: function( event, next ) { + var button, + menuItem = next.closest(".ui-menubar-item"); + + if ( this.active && this.active.length ) { + // TODO refactor, almost the same as _close above, but don't remove tabIndex + if ( this.active.closest( this.options.items ) ) { + this.active + .menu( "collapseAll" ) + .hide() + .attr({ + "aria-hidden": "true", + "aria-expanded": "false" + }); } + this.active.closest(this.options.items) + .removeClass( "ui-state-active" ); } + + // set tabIndex -1 to have the button skipped on shift-tab when menu is open (it gets focus) + button = menuItem.attr( "tabIndex", -1 ); + + this.open = true; + this.active = menuItem; } + }); }( jQuery )); From 1af1f8c56410cf9099911deba7a093ef7931ea61 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Thu, 17 Jan 2013 11:20:02 -0800 Subject: [PATCH 19/33] meubar: formatting per JQuery style guide * foo( "this" ) -> foo("this") * Ensure padding space around argument calls ( foo, bar ) --- ui/jquery.ui.menubar.js | 133 ++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 67 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 9b51107f7dd..45c1d8ea9f5 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -47,7 +47,7 @@ $.widget( "ui.menubar", { _initializeMenubarsBoundElement: function() { this.element - .addClass( "ui-menubar ui-widget-header ui-helper-clearfix" ) + .addClass("ui-menubar ui-widget-header ui-helper-clearfix") .attr( "role", "menubar" ); }, @@ -68,15 +68,15 @@ $.widget( "ui.menubar", { clearTimeout( menubar.closeTimer ); }, focusout: function( event ) { - menubar.closeTimer = setTimeout( function() { + menubar.closeTimer = setTimeout (function() { menubar._close( event ); - }, 150); + }, 150 ); }, "mouseleave .ui-menubar-item": function( event ) { if ( menubar.options.autoExpand ) { menubar.closeTimer = setTimeout( function() { menubar._close( event ); - }, 150); + }, 150 ); } }, "mouseenter .ui-menubar-item": function( event ) { @@ -91,12 +91,12 @@ $.widget( "ui.menubar", { seenFirstItem = false; this.menuItems - .addClass( "ui-menubar-item" ) + .addClass("ui-menubar-item") .attr( "role", "presentation" ); $.each( this.menuItems, function( index, menuItem ){ menubar._initializeMenuItem( $( menuItem ), menubar, seenFirstItem ); - }); + } ); }, _initializeMenuItem: function( $menuItem, menubar ) { @@ -122,29 +122,29 @@ $.widget( "ui.menubar", { var subMenus = $menuItem.children( menubar.options.menuElement ); subMenus - .menu({ - position: { - within: this.options.position.within - }, - select: function( event, ui ) { - ui.item.parents( "ul.ui-menu:last" ).hide(); - menubar._close(); - // TODO what is this targetting? there's probably a better way to access it - $( event.target ).prev().focus(); - menubar._trigger( "select", event, ui ); - }, - menus: this.options.menuElement - }) - .hide() - .attr({ - "aria-hidden": "true", - "aria-expanded": "false" - }); + .menu({ + position: { + within: this.options.position.within + }, + select: function( event, ui ) { + ui.item.parents("ul.ui-menu:last").hide(); + menubar._close(); + // TODO what is this targetting? there's probably a better way to access it + $( event.target ).prev().focus(); + menubar._trigger( "select", event, ui ); + }, + menus: this.options.menuElement + }) + .hide() + .attr({ + "aria-hidden": "true", + "aria-expanded": "false" + }); this._on( subMenus, { keydown: function( event ) { var menu = $( this ); - if ( menu.is( ":hidden" ) ) { + if ( menu.is(":hidden") ) { return; } switch ( event.keyCode ) { @@ -187,19 +187,19 @@ $.widget( "ui.menubar", { } if ( menubar.options.menuIcon ) { - $anItem.addClass( "ui-state-default" ).append( "" ); - $anItem.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" ); + $anItem.addClass("ui-state-default").append(""); + $anItem.removeClass("ui-button-text-only").addClass("ui-button-text-icon-secondary"); } }, _applyDOMPropertiesOnItem: function( $item, menubar) { $item - .addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" ) + .addClass("ui-button ui-widget ui-button-text-only ui-menubar-link") .attr( "role", "menuitem" ) - .wrapInner( "" ); + .wrapInner(""); if ( menubar.options.buttons ) { - $item.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" ); + $item.removeClass("ui-menubar-link").addClass("ui-state-default"); } }, @@ -213,7 +213,7 @@ $.widget( "ui.menubar", { event.preventDefault(); // TODO can we simplify or extract this check? especially the last two expressions // there's a similar active[0] == menu[0] check in _open - if ( event.type === "click" && menu.is( ":visible" ) && this.active && this.active[0] === menu[0] ) { + if ( event.type === "click" && menu.is(":visible") && this.active && this.active[0] === menu[0] ) { this._close(); return; } @@ -296,34 +296,34 @@ $.widget( "ui.menubar", { _destroy : function() { this.menuItems - .removeClass( "ui-menubar-item" ) - .removeAttr( "role" ); + .removeClass("ui-menubar-item") + .removeAttr("role"); this.element - .removeClass( "ui-menubar ui-widget-header ui-helper-clearfix" ) - .removeAttr( "role" ) - .unbind( ".menubar" ); + .removeClass("ui-menubar ui-widget-header ui-helper-clearfix") + .removeAttr("role") + .unbind(".menubar"); this.items - .unbind( ".menubar" ) - .removeClass( "ui-button ui-widget ui-button-text-only ui-menubar-link ui-state-default" ) - .removeAttr( "role" ) - .removeAttr( "aria-haspopup" ) + .unbind(".menubar") + .removeClass("ui-button ui-widget ui-button-text-only ui-menubar-link ui-state-default") + .removeAttr("role") + .removeAttr("aria-haspopup") // TODO unwrap? - .children( "span.ui-button-text" ).each(function( i, e ) { + .children("span.ui-button-text").each(function( i, e ) { var item = $( this ); item.parent().html( item.html() ); }) .end() - .children( ".ui-icon" ).remove(); + .children(".ui-icon").remove(); - this.element.find( ":ui-menu" ) - .menu( "destroy" ) + this.element.find(":ui-menu") + .menu("destroy") .show() - .removeAttr( "aria-hidden" ) - .removeAttr( "aria-expanded" ) - .removeAttr( "tabindex" ) - .unbind( ".menubar" ); + .removeAttr("aria-hidden") + .removeAttr("aria-expanded") + .removeAttr("tabindex") + .unbind(".menubar"); }, _close: function() { @@ -341,8 +341,8 @@ $.widget( "ui.menubar", { }); this.active .prev() - .removeClass( "ui-state-active" ) - .removeAttr( "tabIndex" ); + .removeClass("ui-state-active") + .removeAttr("tabIndex"); this.active.closest( this.options.items ).removeClass("ui-state-active"); } else { this.active @@ -365,30 +365,30 @@ $.widget( "ui.menubar", { // TODO refactor, almost the same as _close above, but don't remove tabIndex if ( this.active.closest( this.options.items ).data("hasSubMenu") ) { this.active - .menu( "collapseAll" ) + .menu("collapseAll") .hide() .attr({ "aria-hidden": "true", "aria-expanded": "false" }); this.active.closest(this.options.items) - .removeClass( "ui-state-active" ); + .removeClass("ui-state-active"); } else { - this.active.removeClass( "ui-state-active" ); + this.active.removeClass("ui-state-active"); } } // set tabIndex -1 to have the button skipped on shift-tab when menu is open (it gets focus) - button = menuItem.addClass( "ui-state-active" ).attr( "tabIndex", -1 ); + button = menuItem.addClass("ui-state-active").attr( "tabIndex", -1 ); this.active = menu .show() .position( $.extend({ of: button }, this.options.position ) ) - .removeAttr( "aria-hidden" ) - .attr( "aria-expanded", "true" ) - .menu("focus", event, menu.children( ".ui-menu-item" ).first() ) + .removeAttr("aria-hidden") + .attr("aria-expanded", "true") + .menu("focus", event, menu.children(".ui-menu-item").first() ) // TODO need a comment here why both events are triggered .focus() .focusin(); @@ -400,7 +400,7 @@ $.widget( "ui.menubar", { if ( this.open && this.active.closest( this.options.items ).data("hasSubMenu") && this.active.data("menu").active && - this.active.data( "menu" ).active.has( ".ui-menu" ).length ) { + this.active.data("menu").active.has(".ui-menu").length ) { // Track number of open submenus and prevent moving to next menubar item this.openSubmenus++; return; @@ -426,15 +426,15 @@ $.widget( "ui.menubar", { if ( this.open ) { next = this.active. - closest( ".ui-menubar-item" )[ direction + "All" ]( this.options.items ) - .first(); + closest(".ui-menubar-item")[ direction + "All" ]( this.options.items ) + .first(); wrapItem = this.menuItems[ filter ](); } else { if ( event ) { - next = $( event.target ).closest( ".ui-menubar-item" )[ direction + "All" ]( this.options.items ).children( ".ui-menubar-link" ).eq( 0 ); - wrapItem = this.menuItems[ filter ]().children( ".ui-menubar-link" ).eq( 0 ); + next = $( event.target ).closest(".ui-menubar-item")[ direction + "All" ]( this.options.items ).children(".ui-menubar-link").eq( 0 ); + wrapItem = this.menuItems[ filter ]().children(".ui-menubar-link").eq( 0 ); } else { - next = wrapItem = this.menuItems.children( "a" ).eq( 0 ); + next = wrapItem = this.menuItems.children("a").eq( 0 ); } } @@ -449,9 +449,8 @@ $.widget( "ui.menubar", { this._submenuless_open( event, next ); } } else { - next.removeAttr( "tabIndex")[0].focus(); + next.removeAttr("tabIndex")[0].focus(); } - }, _submenuless_open: function( event, next ) { @@ -462,7 +461,7 @@ $.widget( "ui.menubar", { // TODO refactor, almost the same as _close above, but don't remove tabIndex if ( this.active.closest( this.options.items ) ) { this.active - .menu( "collapseAll" ) + .menu("collapseAll") .hide() .attr({ "aria-hidden": "true", @@ -470,7 +469,7 @@ $.widget( "ui.menubar", { }); } this.active.closest(this.options.items) - .removeClass( "ui-state-active" ); + .removeClass("ui-state-active"); } // set tabIndex -1 to have the button skipped on shift-tab when menu is open (it gets focus) From 908fea5722f5852eef83cafaf5808c01b2155bf5 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Fri, 18 Jan 2013 08:40:27 -0800 Subject: [PATCH 20/33] menubar: mark active menuItem with .ui-state-focus --- ui/jquery.ui.menubar.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 45c1d8ea9f5..007c52b2741 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -176,6 +176,8 @@ $.widget( "ui.menubar", { this._hoverable( this.items ); this._applyDOMPropertiesOnItem( $anItem, menubar); + this.__applyMouseAndKeyboardBehaviorForMenuItem ( $anItem, menubar ); + if ( menuItemHasSubMenu ) { this.__applyMouseBehaviorForSubmenuHavingMenuItem( $anItem, menubar ); this.__applyKeyboardBehaviorForSubmenuHavingMenuItem( $anItem, menubar ); @@ -192,6 +194,17 @@ $.widget( "ui.menubar", { } }, + __applyMouseAndKeyboardBehaviorForMenuItem: function( $anItem, menubar ) { + menubar._on( $anItem, { + focus: function( event ){ + $anItem.closest( this.options.items ).addClass("ui-state-focus"); + }, + focusout: function( event ){ + $anItem.closest( this.options.items ).removeClass("ui-state-focus"); + } + } ); + }, + _applyDOMPropertiesOnItem: function( $item, menubar) { $item .addClass("ui-button ui-widget ui-button-text-only ui-menubar-link") From bb8e2e0e40fab1aaa8031c5612e69288a14cf7a6 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Fri, 18 Jan 2013 08:52:50 -0800 Subject: [PATCH 21/33] menubar: re-open submenu when returning to it after hover on menu-less item Per @jzaefferer: > Something I didn't write down on the wiki page, > but seems like it should get adressed: When > clicking to open a submenu, hovering the > non-menu-item closes the menu, and another hover > on the closed menu should probably reopen it, > instead of requiring another click. When it gets > closed by a click, it should require the click to > reopen. If that makes sense to you to, we can put > it on the wiki. --- ui/jquery.ui.menubar.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 007c52b2741..8183f7e05ec 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -232,6 +232,10 @@ $.widget( "ui.menubar", { } if ( event.type === "mouseenter" ) { this.element.find(":focus").focusout(); + if ( this.stashedOpenMenu ) { + this._open( event, menu); + } + this.stashedOpenMenu = undefined; } if ( ( this.open && event.type === "mouseenter" ) || event.type === "click" || this.options.autoExpand ) { if ( this.options.autoExpand ) { @@ -287,6 +291,7 @@ $.widget( "ui.menubar", { }, mouseenter: function( event ) { if ( this.open ) { + this.stashedOpenMenu = this.active; this._close(); } } From 0efe66ea050227f8a951a35114229ed4649aab28 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Fri, 18 Jan 2013 09:02:29 -0800 Subject: [PATCH 22/33] menubar: relocate focus event onto *item* v. menuItem I should have finished my coffee before I committed this. --- ui/jquery.ui.menubar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 8183f7e05ec..5c17227cd4e 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -197,10 +197,10 @@ $.widget( "ui.menubar", { __applyMouseAndKeyboardBehaviorForMenuItem: function( $anItem, menubar ) { menubar._on( $anItem, { focus: function( event ){ - $anItem.closest( this.options.items ).addClass("ui-state-focus"); + $anItem.addClass("ui-state-focus"); }, focusout: function( event ){ - $anItem.closest( this.options.items ).removeClass("ui-state-focus"); + $anItem.removeClass("ui-state-focus"); } } ); }, From fdc200d9dd20d5e5246992af80bd84ccf25c57e7 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Fri, 18 Jan 2013 09:59:14 -0800 Subject: [PATCH 23/33] menubar: fix pixel-shifting visual error --- ui/jquery.ui.menubar.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 5c17227cd4e..c4187d5c633 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -103,6 +103,7 @@ $.widget( "ui.menubar", { var $item = $menuItem.children("button, a"); menubar._determineSubmenuStatus( $menuItem, menubar ); + menubar._styleMenuItem( $menuItem, menubar ); if ( $menuItem.data("hasSubMenu") ) { menubar._initializeSubMenu( $menuItem, menubar ); } @@ -118,6 +119,13 @@ $.widget( "ui.menubar", { $menuItem.data( "hasSubMenu", hasSubMenu ); }, + _styleMenuItem: function( $menuItem, menubar ) { + $menuItem.css({ + "border-width" : "1px", + "border-style" : "hidden" + }); + }, + _initializeSubMenu: function( $menuItem, menubar ){ var subMenus = $menuItem.children( menubar.options.menuElement ); From aafdc1716ac4418c59625fc86f20fe9000a04d88 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 19 Jan 2013 20:56:14 -0800 Subject: [PATCH 24/33] menubar: apply drop-down glyph only on menus w/ subMenu --- ui/jquery.ui.menubar.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index c4187d5c633..76daf4064e0 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -191,15 +191,14 @@ $.widget( "ui.menubar", { this.__applyKeyboardBehaviorForSubmenuHavingMenuItem( $anItem, menubar ); $anItem.attr( "aria-haspopup", "true" ); + if ( menubar.options.menuIcon ) { + $anItem.addClass("ui-state-default").append(""); + $anItem.removeClass("ui-button-text-only").addClass("ui-button-text-icon-secondary"); + } } else { this.__applyMouseBehaviorForSubmenulessMenuItem( $anItem, menubar ); this.__applyKeyboardBehaviorForSubmenulessMenuItem( $anItem, menubar ); } - - if ( menubar.options.menuIcon ) { - $anItem.addClass("ui-state-default").append(""); - $anItem.removeClass("ui-button-text-only").addClass("ui-button-text-icon-secondary"); - } }, __applyMouseAndKeyboardBehaviorForMenuItem: function( $anItem, menubar ) { From 1e8089b31bb8998f9f0f14c5c7152826fe375d52 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Wed, 13 Mar 2013 21:35:12 -0700 Subject: [PATCH 25/33] LEFT cursor in an expanded menu approximates ESCAPE --- ui/jquery.ui.menubar.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 76daf4064e0..e3b281808fb 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -151,13 +151,17 @@ $.widget( "ui.menubar", { this._on( subMenus, { keydown: function( event ) { - var menu = $( this ); + var parentButton, + menu = $( this ); if ( menu.is(":hidden") ) { return; } switch ( event.keyCode ) { case $.ui.keyCode.LEFT: - this.previous( event ); + parentButton = menubar.active.prev(".ui-button").focus(); + menubar.active.blur(); + menubar._close( event ); + parentButton.focus(); event.preventDefault(); break; case $.ui.keyCode.RIGHT: @@ -269,6 +273,7 @@ $.widget( "ui.menubar", { event.preventDefault(); break; case $.ui.keyCode.LEFT: + debugger; this.previous( event ); event.preventDefault(); break; From 09d51e863308863d4443a64e5a30287a3a2fd59c Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 16 Mar 2013 11:22:37 -0700 Subject: [PATCH 26/33] Remove unused variable: seenFirstItem --- ui/jquery.ui.menubar.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index e3b281808fb..16989d335cd 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -87,15 +87,14 @@ $.widget( "ui.menubar", { _initializeMenuItems: function() { var $item, - menubar = this, - seenFirstItem = false; + menubar = this; this.menuItems .addClass("ui-menubar-item") .attr( "role", "presentation" ); $.each( this.menuItems, function( index, menuItem ){ - menubar._initializeMenuItem( $( menuItem ), menubar, seenFirstItem ); + menubar._initializeMenuItem( $( menuItem ), menubar ); } ); }, From a4a95ccce7131d4e1f13c5eeb44b3157c8d178cc Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 16 Mar 2013 11:23:10 -0700 Subject: [PATCH 27/33] Do not remove tabIndex varaible When this is removed it makes the menu item with a submenu eligible for tabbing / shift-tabbing to / from. --- ui/jquery.ui.menubar.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 16989d335cd..9b7abfeb177 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -370,8 +370,7 @@ $.widget( "ui.menubar", { }); this.active .prev() - .removeClass("ui-state-active") - .removeAttr("tabIndex"); + .removeClass("ui-state-active"); this.active.closest( this.options.items ).removeClass("ui-state-active"); } else { this.active @@ -478,7 +477,7 @@ $.widget( "ui.menubar", { this._submenuless_open( event, next ); } } else { - next.removeAttr("tabIndex")[0].focus(); + next.focus(); } }, From b2a381415eecbb20d11457306b25bf5d908b5632 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 16 Mar 2013 11:47:57 -0700 Subject: [PATCH 28/33] rm stray debugger --- ui/jquery.ui.menubar.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 9b7abfeb177..20b16273011 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -272,7 +272,6 @@ $.widget( "ui.menubar", { event.preventDefault(); break; case $.ui.keyCode.LEFT: - debugger; this.previous( event ); event.preventDefault(); break; From bab98f21558cd7f7352d7910f15d6cb4cf04966c Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Sat, 16 Mar 2013 11:48:06 -0700 Subject: [PATCH 29/33] Change selector for next .ui-menubar-link -> .ui-button --- ui/jquery.ui.menubar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 20b16273011..1c306625325 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -458,7 +458,7 @@ $.widget( "ui.menubar", { wrapItem = this.menuItems[ filter ](); } else { if ( event ) { - next = $( event.target ).closest(".ui-menubar-item")[ direction + "All" ]( this.options.items ).children(".ui-menubar-link").eq( 0 ); + next = $( event.target ).closest(".ui-menubar-item")[ direction + "All" ]( this.options.items ).children(".ui-button").eq( 0 ); wrapItem = this.menuItems[ filter ]().children(".ui-menubar-link").eq( 0 ); } else { next = wrapItem = this.menuItems.children("a").eq( 0 ); From 79b06b21c5db21dbb2e808a7d7e3265085ad169f Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Fri, 22 Mar 2013 08:09:55 -0700 Subject: [PATCH 30/33] Repair LEFT cursor --- ui/jquery.ui.menubar.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 1c306625325..a9e8fdb84bb 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -157,10 +157,17 @@ $.widget( "ui.menubar", { } switch ( event.keyCode ) { case $.ui.keyCode.LEFT: - parentButton = menubar.active.prev(".ui-button").focus(); - menubar.active.blur(); - menubar._close( event ); - parentButton.focus(); + parentButton = menubar.active.prev(".ui-button"); + + if ( parentButton.parent().prev().data('hasSubMenu') ) { + menubar.active.blur(); + menubar._open( event, parentButton.parent().prev().find(".ui-menu") ); + } else { + parentButton.parent().prev().find(".ui-button").focus(); + menubar._close( event ); + this.open = true; + } + event.preventDefault(); break; case $.ui.keyCode.RIGHT: From 480de7f82a6a5da15a499912722f43e2de91ffec Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Fri, 22 Mar 2013 08:11:05 -0700 Subject: [PATCH 31/33] Refactor _move by menuItems knowing their neighbors --- ui/jquery.ui.menubar.js | 51 ++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index a9e8fdb84bb..e181260f1a8 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -95,14 +95,33 @@ $.widget( "ui.menubar", { $.each( this.menuItems, function( index, menuItem ){ menubar._initializeMenuItem( $( menuItem ), menubar ); + menubar._initializeMenuItemNeighbors( $( menuItem ), menubar, index ); } ); }, + _initializeMenuItemNeighbors: function( $menuItem, menubar, index ) { + var collectionLength = this.menuItems.toArray().length, + isFirstElement = ( index === 0 ), + isLastElement = ( index === ( collectionLength - 1 ) ); + + if ( isFirstElement ) { + $menuItem.data( "prevMenuItem", $( this.menuItems[collectionLength - 1]) ); + $menuItem.data( "nextMenuItem", $( this.menuItems[index+1]) ); + } else if ( isLastElement ) { + $menuItem.data( "nextMenuItem", $( this.menuItems[0]) ); + $menuItem.data( "prevMenuItem", $( this.menuItems[index-1]) ); + } else { + $menuItem.data( "nextMenuItem", $( this.menuItems[index+1]) ); + $menuItem.data( "prevMenuItem", $( this.menuItems[index-1]) ); + } + }, + _initializeMenuItem: function( $menuItem, menubar ) { var $item = $menuItem.children("button, a"); menubar._determineSubmenuStatus( $menuItem, menubar ); menubar._styleMenuItem( $menuItem, menubar ); + if ( $menuItem.data("hasSubMenu") ) { menubar._initializeSubMenu( $menuItem, menubar ); } @@ -431,7 +450,7 @@ $.widget( "ui.menubar", { }, next: function( event ) { - if ( this.open && + if ( this.open && this.active && this.active.closest( this.options.items ).data("hasSubMenu") && this.active.data("menu").active && this.active.data("menu").active.has(".ui-menu").length ) { @@ -458,33 +477,19 @@ $.widget( "ui.menubar", { var next, wrapItem; - if ( this.open ) { - next = this.active. - closest(".ui-menubar-item")[ direction + "All" ]( this.options.items ) - .first(); - wrapItem = this.menuItems[ filter ](); - } else { - if ( event ) { - next = $( event.target ).closest(".ui-menubar-item")[ direction + "All" ]( this.options.items ).children(".ui-button").eq( 0 ); - wrapItem = this.menuItems[ filter ]().children(".ui-menubar-link").eq( 0 ); - } else { - next = wrapItem = this.menuItems.children("a").eq( 0 ); - } - } - - if ( !next.length ) { - next = wrapItem; - } + var closestMenuItem = $( event.target ).closest(".ui-menubar-item"), + nextMenuItem = closestMenuItem.data( direction + "MenuItem" ), + focusableTarget = nextMenuItem.find("a, button"); if ( this.open ) { - if ( next.data("hasSubMenu") ) { - this._open( event, next.children("ul") ); + if ( nextMenuItem.data("hasSubMenu") ) { + this._open( event, nextMenuItem.children(".ui-menu") ); } else { - this._submenuless_open( event, next ); + this._submenuless_open( event, nextMenuItem ); } - } else { - next.focus(); } + + focusableTarget.focus(); }, _submenuless_open: function( event, next ) { From 56f6469b169c37be33544e1f90f383b19610fb33 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Fri, 22 Mar 2013 08:24:05 -0700 Subject: [PATCH 32/33] Method rename --- ui/jquery.ui.menubar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index e181260f1a8..37b6a21830a 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -95,11 +95,11 @@ $.widget( "ui.menubar", { $.each( this.menuItems, function( index, menuItem ){ menubar._initializeMenuItem( $( menuItem ), menubar ); - menubar._initializeMenuItemNeighbors( $( menuItem ), menubar, index ); + menubar._identifyMenuItemsNeighbors( $( menuItem ), menubar, index ); } ); }, - _initializeMenuItemNeighbors: function( $menuItem, menubar, index ) { + _identifyMenuItemsNeighbors: function( $menuItem, menubar, index ) { var collectionLength = this.menuItems.toArray().length, isFirstElement = ( index === 0 ), isLastElement = ( index === ( collectionLength - 1 ) ); From 100f5528f0f2d25980299db2bcbc29b547ff0dc0 Mon Sep 17 00:00:00 2001 From: "Steven G. Harms" Date: Fri, 22 Mar 2013 08:10:43 -0700 Subject: [PATCH 33/33] Correct submenus triggering bad focusout behavior --- ui/jquery.ui.menubar.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/jquery.ui.menubar.js b/ui/jquery.ui.menubar.js index 37b6a21830a..3e8a143bd0d 100644 --- a/ui/jquery.ui.menubar.js +++ b/ui/jquery.ui.menubar.js @@ -194,6 +194,9 @@ $.widget( "ui.menubar", { event.preventDefault(); break; } + }, + focusout: function( event ) { + event.stopImmediatePropagation(); } }); },