aboutsummaryrefslogtreecommitdiffstats
path: root/ui/jquery.ui.menu.js
diff options
context:
space:
mode:
Diffstat (limited to 'ui/jquery.ui.menu.js')
-rw-r--r--ui/jquery.ui.menu.js156
1 files changed, 80 insertions, 76 deletions
diff --git a/ui/jquery.ui.menu.js b/ui/jquery.ui.menu.js
index 54a85abf5..81fef2e7c 100644
--- a/ui/jquery.ui.menu.js
+++ b/ui/jquery.ui.menu.js
@@ -1,7 +1,8 @@
/*!
* jQuery UI Menu @VERSION
+ * http://jqueryui.com
*
- * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Copyright 2012 jQuery Foundation and other contributors
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
@@ -10,8 +11,9 @@
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
+ * jquery.ui.position.js
*/
-(function($) {
+(function( $, undefined ) {
var currentEventTarget = null;
@@ -20,6 +22,9 @@ $.widget( "ui.menu", {
defaultElement: "<ul>",
delay: 300,
options: {
+ icons: {
+ submenu: "ui-icon-carat-1-e"
+ },
menus: "ul",
position: {
my: "left top",
@@ -32,6 +37,7 @@ $.widget( "ui.menu", {
focus: null,
select: null
},
+
_create: function() {
this.activeMenu = this.element;
this.element
@@ -43,8 +49,8 @@ $.widget( "ui.menu", {
tabIndex: 0
})
// need to catch all clicks on disabled menu
- // not possible through _bind
- .bind( "click.menu", $.proxy(function( event ) {
+ // not possible through _on
+ .bind( "click" + this.eventNamespace, $.proxy(function( event ) {
if ( this.options.disabled ) {
event.preventDefault();
}
@@ -56,7 +62,7 @@ $.widget( "ui.menu", {
.attr( "aria-disabled", "true" );
}
- this._bind({
+ this._on({
// Prevent focus from sticking to links inside menu after clicking
// them (focus should always stay on UL during navigation).
"mousedown .ui-menu-item > a": function( event ) {
@@ -69,7 +75,10 @@ $.widget( "ui.menu", {
var target = $( event.target );
if ( target[0] !== currentEventTarget ) {
currentEventTarget = target[0];
- target.one( "click.menu", function( event ) {
+ // TODO: What are we trying to accomplish with this check?
+ // Clicking a menu item twice results in a select event with
+ // an empty ui.item.
+ target.one( "click" + this.eventNamespace, function( event ) {
currentEventTarget = null;
});
// Don't select disabled menu items
@@ -94,20 +103,11 @@ $.widget( "ui.menu", {
mouseleave: "collapseAll",
"mouseleave .ui-menu": "collapseAll",
focus: function( event ) {
- var menu = this.element,
- firstItem = menu.children( ".ui-menu-item" ).eq( 0 );
- if ( this._hasScroll() && !this.active ) {
- menu.children().each(function() {
- var currentItem = $( this );
- if ( currentItem.offset().top - menu.offset().top >= 0 ) {
- firstItem = currentItem;
- return false;
- }
- });
- } else if ( this.active ) {
- firstItem = this.active;
- }
- this.focus( event, firstItem );
+ // If there's already an active item, keep it active
+ // If not, activate the first item
+ var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 );
+
+ this.focus( event, item );
},
blur: function( event ) {
this._delay(function() {
@@ -121,9 +121,8 @@ $.widget( "ui.menu", {
this.refresh();
- // TODO: We probably shouldn't bind to document for each menu.
- // TODO: This isn't being cleaned up on destroy.
- this._bind( this.document, {
+ // Clicks outside of a menu collapse any open menus
+ this._on( this.document, {
click: function( event ) {
if ( !$( event.target ).closest( ".ui-menu" ).length ) {
this.collapseAll( event );
@@ -133,11 +132,11 @@ $.widget( "ui.menu", {
},
_destroy: function() {
- // destroy (sub)menus
+ // Destroy (sub)menus
this.element
.removeAttr( "aria-activedescendant" )
.find( ".ui-menu" ).andSelf()
- .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
+ .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" )
.removeAttr( "role" )
.removeAttr( "tabIndex" )
.removeAttr( "aria-labelledby" )
@@ -147,7 +146,7 @@ $.widget( "ui.menu", {
.removeUniqueId()
.show();
- // destroy menu items
+ // Destroy menu items
this.element.find( ".ui-menu-item" )
.removeClass( "ui-menu-item" )
.removeAttr( "role" )
@@ -158,16 +157,22 @@ $.widget( "ui.menu", {
.removeAttr( "tabIndex" )
.removeAttr( "role" )
.removeAttr( "aria-haspopup" )
- // TODO: is this correct? Don't these exist in the original markup?
- .children( ".ui-icon" )
- .remove();
+ .children().each( function() {
+ var elem = $( this );
+ if ( elem.data( "ui-menu-submenu-carat" ) ) {
+ elem.remove();
+ }
+ });
- // unbind currentEventTarget click event handler
- $( currentEventTarget ).unbind( "click.menu" );
+ // Destroy menu dividers
+ this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" );
+
+ // Unbind currentEventTarget click event handler
+ this._off( $( currentEventTarget ), "click" );
},
_keydown: function( event ) {
- var match, prev, character, skip,
+ var match, prev, character, skip, regex,
preventDefault = true;
function escape( value ) {
@@ -202,8 +207,6 @@ $.widget( "ui.menu", {
}
break;
case $.ui.keyCode.ENTER:
- this._activate( event );
- break;
case $.ui.keyCode.SPACE:
this._activate( event );
break;
@@ -224,20 +227,21 @@ $.widget( "ui.menu", {
character = prev + character;
}
+ regex = new RegExp( "^" + escape( character ), "i" );
match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
- return new RegExp( "^" + escape( character ), "i" )
- .test( $( this ).children( "a" ).text() );
+ return regex.test( $( this ).children( "a" ).text() );
});
match = skip && match.index( this.active.next() ) !== -1 ?
this.active.nextAll( ".ui-menu-item" ) :
match;
- // TODO: document what's going on here, character is reset to the original value
+ // If no matches on the current filter, reset to the last character pressed
+ // to move down the menu to the first item that starts with that character
if ( !match.length ) {
character = String.fromCharCode( event.keyCode );
+ regex = new RegExp( "^" + escape( character ), "i" );
match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
- return new RegExp( "^" + escape(character), "i" )
- .test( $( this ).children( "a" ).text() );
+ return regex.test( $( this ).children( "a" ).text() );
});
}
@@ -272,8 +276,9 @@ $.widget( "ui.menu", {
},
refresh: function() {
- // initialize nested menus
+ // Initialize nested menus
var menus,
+ icon = this.options.icons.submenu,
submenus = this.element.find( this.options.menus + ":not(.ui-menu)" )
.addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
.hide()
@@ -283,10 +288,10 @@ $.widget( "ui.menu", {
"aria-expanded": "false"
});
- // don't refresh list items that are already adapted
+ // Don't refresh list items that are already adapted
menus = submenus.add( this.element );
- menus.children( ":not( .ui-menu-item ):has( a )" )
+ menus.children( ":not(.ui-menu-item):has(a)" )
.addClass( "ui-menu-item" )
.attr( "role", "presentation" )
.children( "a" )
@@ -297,25 +302,28 @@ $.widget( "ui.menu", {
role: this._itemRole()
});
- // initialize unlinked menu-items containing spaces and/or dashes only as dividers
- menus.children( ":not(.ui-menu-item)" ).each( function() {
+ // Initialize unlinked menu-items containing spaces and/or dashes only as dividers
+ menus.children( ":not(.ui-menu-item)" ).each(function() {
var item = $( this );
- // hypen, em dash, en dash
+ // hyphen, em dash, en dash
if ( !/[^\-—–\s]/.test( item.text() ) ) {
item.addClass( "ui-widget-content ui-menu-divider" );
}
});
- // add aria-disabled attribute to any disabled menu item
+ // Add aria-disabled attribute to any disabled menu item
menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
submenus.each(function() {
var menu = $( this ),
- item = menu.prev( "a" );
+ item = menu.prev( "a" ),
+ submenuCarat = $( "<span>" )
+ .addClass( "ui-menu-icon ui-icon " + icon )
+ .data( "ui-menu-submenu-carat", true );
item
.attr( "aria-haspopup", "true" )
- .prepend( '<span class="ui-menu-icon ui-icon ui-icon-carat-1-e"></span>' );
+ .prepend( submenuCarat );
menu.attr( "aria-labelledby", item.attr( "id" ) );
});
},
@@ -335,13 +343,13 @@ $.widget( "ui.menu", {
this.active = item.first();
focused = this.active.children( "a" ).addClass( "ui-state-focus" );
- // only update aria-activedescendant if there's a role
+ // Only update aria-activedescendant if there's a role
// otherwise we assume focus is managed elsewhere
if ( this.options.role ) {
this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
}
- // highlight active parent menu item, if any
+ // Highlight active parent menu item, if any
this.active
.parent()
.closest( ".ui-menu-item" )
@@ -356,7 +364,7 @@ $.widget( "ui.menu", {
}, this.delay );
}
- nested = $( "> .ui-menu", item );
+ nested = item.children( ".ui-menu" );
if ( nested.length && ( /^mouse/.test( event.type ) ) ) {
this._startOpening(nested);
}
@@ -416,13 +424,10 @@ $.widget( "ui.menu", {
_open: function( submenu ) {
var position = $.extend({
of: this.active
- }, $.type( this.options.position ) === "function" ?
- this.options.position( this.active ) :
- this.options.position
- );
+ }, this.options.position );
clearTimeout( this.timer );
- this.element.find( ".ui-menu" ).not( submenu.parents() )
+ this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
.hide()
.attr( "aria-hidden", "true" );
@@ -436,11 +441,11 @@ $.widget( "ui.menu", {
collapseAll: function( event, all ) {
clearTimeout( this.timer );
this.timer = this._delay(function() {
- // if we were passed an event, look for the submenu that contains the event
+ // If we were passed an event, look for the submenu that contains the event
var currentMenu = all ? this.element :
$( event && event.target ).closest( this.element.find( ".ui-menu" ) );
- // if we found no valid submenu ancestor, use the main menu to close all sub menus anyway
+ // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway
if ( !currentMenu.length ) {
currentMenu = this.element;
}
@@ -475,7 +480,6 @@ $.widget( "ui.menu", {
if ( newItem && newItem.length ) {
this._close();
this.focus( event, newItem );
- return true;
}
},
@@ -489,11 +493,10 @@ $.widget( "ui.menu", {
if ( newItem && newItem.length ) {
this._open( newItem.parent() );
- // timeout so Firefox will not hide activedescendant change in expanding submenu from AT
+ // Delay so Firefox will not hide activedescendant change in expanding submenu from AT
this._delay(function() {
this.focus( event, newItem );
}, 20 );
- return true;
}
},
@@ -534,23 +537,24 @@ $.widget( "ui.menu", {
},
nextPage: function( event ) {
+ var item, base, height;
+
if ( !this.active ) {
- this._move( "next", "first", event );
+ this.next( event );
return;
}
if ( this.isLastItem() ) {
return;
}
if ( this._hasScroll() ) {
- var base = this.active.offset().top,
- height = this.element.height(),
- result;
+ base = this.active.offset().top;
+ height = this.element.height();
this.active.nextAll( ".ui-menu-item" ).each(function() {
- result = $( this );
- return $( this ).offset().top - base - height < 0;
+ item = $( this );
+ return item.offset().top - base - height < 0;
});
- this.focus( event, result );
+ this.focus( event, item );
} else {
this.focus( event, this.activeMenu.children( ".ui-menu-item" )
[ !this.active ? "first" : "last" ]() );
@@ -558,23 +562,23 @@ $.widget( "ui.menu", {
},
previousPage: function( event ) {
+ var item, base, height;
if ( !this.active ) {
- this._move( "next", "first", event );
+ this.next( event );
return;
}
if ( this.isFirstItem() ) {
return;
}
if ( this._hasScroll() ) {
- var base = this.active.offset().top,
- height = this.element.height(),
- result;
+ base = this.active.offset().top;
+ height = this.element.height();
this.active.prevAll( ".ui-menu-item" ).each(function() {
- result = $( this );
- return $(this).offset().top - base + height > 0;
+ item = $( this );
+ return item.offset().top - base + height > 0;
});
- this.focus( event, result );
+ this.focus( event, item );
} else {
this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() );
}
@@ -585,7 +589,7 @@ $.widget( "ui.menu", {
},
select: function( event ) {
- // save active reference before collapseAll triggers blur
+ // Save active reference before collapseAll triggers blur
var ui = {
item: this.active
};