Skip to content

Commit 2ebef69

Browse files
committed
Menu: Add classes option
Ref #7053 Ref gh-1411
1 parent f58277a commit 2ebef69

File tree

3 files changed

+88
-69
lines changed

3 files changed

+88
-69
lines changed

tests/unit/menu/menu.html

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<script src="../../../external/qunit/qunit.js"></script>
1010
<script src="../../../external/jquery-simulate/jquery.simulate.js"></script>
1111
<script src="../testsuite.js"></script>
12+
<script src="../../../external/qunit-assert-classes/qunit-assert-classes.js"></script>
1213
<script>
1314
TestHelpers.loadResources({
1415
css: [ "core", "menu" ],
@@ -52,7 +53,7 @@
5253
<div id="qunit-fixture">
5354

5455
<ul class="foo" id="menu1">
55-
<li class="foo"><div>Aberdeen</div></li>
56+
<li class="foo"><div>Aberdeen</div>
5657
<li class="foo"><div>Ada</div></li>
5758
<li class="foo"><div>Adamsville</div></li>
5859
<li class="foo"><div id="testID1">Addyston</div></li>
@@ -332,6 +333,18 @@
332333
<li class="foo"><div>-Saarland</div></li>
333334
</ul>
334335

336+
<ul class="foo" id="menu9">
337+
<li class="foo">
338+
<div>Aberdeen</div>
339+
<ul>
340+
<li class="foo"><div>Ada</div></li>
341+
</ul>
342+
</li>
343+
<li class="foo"><div>Ada</div></li>
344+
<li class="foo"><div>Adamsville</div></li>
345+
<li class="foo"><div>Addyston</div></li>
346+
<li class="foo"><div>Adelphi</div></li>
347+
</ul>
335348
</div>
336349
</body>
337350
</html>

tests/unit/menu/menu_core.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,23 @@
22

33
module( "menu: core" );
44

5-
test( "markup structure", function() {
6-
expect( 6 );
7-
var element = $( "#menu1" ).menu();
8-
ok( element.hasClass( "ui-menu" ), "main element is .ui-menu" );
9-
element.children().each(function( index ) {
10-
ok( $( this ).hasClass( "ui-menu-item" ), "child " + index + " is .ui-menu-item" );
11-
});
5+
test( "markup structure", function( assert ) {
6+
expect( 11 );
7+
var element = $( "#menu9" ).menu(),
8+
items = element.children(),
9+
firstItemChildren = items.eq( 0 ).children();
10+
11+
assert.hasClasses( element, "ui-menu ui-widget ui-widget-content" );
12+
assert.hasClasses( items[ 0 ], "ui-menu-item" );
13+
equal( items.eq( 0 ).children().length, 2, "Item has exactly 2 children when it has a sub menu" );
14+
assert.hasClasses( firstItemChildren[ 0 ], "ui-menu-item-wrapper" );
15+
assert.hasClasses( firstItemChildren[ 1 ], "ui-menu ui-widget ui-widget-content" );
16+
assert.hasClasses( firstItemChildren.eq( 1 ).children()[ 0 ], "ui-menu-item" );
17+
assert.hasClasses( firstItemChildren.eq( 1 ).children().eq( 0 ).children(), "ui-menu-item-wrapper" );
18+
assert.hasClasses( items[ 1 ], "ui-menu-item" );
19+
equal( items.eq( 1 ).children().length, 1, "Item has exactly 1 child when it does not have a sub menu" );
20+
assert.hasClasses( items[ 2 ], "ui-menu-item" );
21+
equal( items.eq( 2 ).children().length, 1, "Item has exactly 1 child when it does not have a sub menu" );
1222
});
1323

1424
test( "accessibility", function () {

ui/menu.js

Lines changed: 57 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ return $.widget( "ui.menu", {
3838
defaultElement: "<ul>",
3939
delay: 300,
4040
options: {
41+
classes: {},
4142
icons: {
4243
submenu: "ui-icon-caret-1-e"
4344
},
@@ -63,20 +64,19 @@ return $.widget( "ui.menu", {
6364
this.mouseHandled = false;
6465
this.element
6566
.uniqueId()
66-
.addClass( "ui-menu ui-widget ui-widget-content" )
67-
.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length )
6867
.attr({
6968
role: this.options.role,
7069
tabIndex: 0
7170
});
7271

7372
if ( this.options.disabled ) {
74-
this.element
75-
.addClass( "ui-state-disabled" )
76-
.attr( "aria-disabled", "true" );
73+
this._addClass( null, "ui-state-disabled" );
74+
this.element.attr( "aria-disabled", "true" );
7775
}
7876

77+
this._addClass( "ui-menu", "ui-widget ui-widget-content" );
7978
this._on({
79+
8080
// Prevent focus from sticking to links inside menu after clicking
8181
// them (focus should always stay on UL during navigation).
8282
"mousedown .ui-menu-item": function( event ) {
@@ -118,8 +118,8 @@ return $.widget( "ui.menu", {
118118
var target = $( event.currentTarget );
119119
// Remove ui-state-active class from siblings of the newly focused menu item
120120
// to avoid a jump caused by adjacent elements both having a class with a border
121-
target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" );
122-
121+
this._removeClass( target.siblings().children( ".ui-state-active" ),
122+
null, "ui-state-active" );
123123
this.focus( event, target );
124124
},
125125
mouseleave: "collapseAll",
@@ -159,11 +159,19 @@ return $.widget( "ui.menu", {
159159
},
160160

161161
_destroy: function() {
162+
var items = this.element.find( ".ui-menu-item" )
163+
.removeAttr( "role" )
164+
.removeAttr( "aria-disabled" ),
165+
submenus = items.children( ".ui-menu-item-wrapper" )
166+
.removeUniqueId()
167+
.removeAttr( "tabIndex" )
168+
.removeAttr( "role" )
169+
.removeAttr( "aria-haspopup" );
170+
162171
// Destroy (sub)menus
163172
this.element
164173
.removeAttr( "aria-activedescendant" )
165174
.find( ".ui-menu" ).addBack()
166-
.removeClass( "ui-menu ui-widget ui-widget-content ui-menu-icons ui-front" )
167175
.removeAttr( "role" )
168176
.removeAttr( "tabIndex" )
169177
.removeAttr( "aria-labelledby" )
@@ -173,26 +181,12 @@ return $.widget( "ui.menu", {
173181
.removeUniqueId()
174182
.show();
175183

176-
// Destroy menu items
177-
this.element.find( ".ui-menu-item" )
178-
.removeClass( "ui-menu-item" )
179-
.removeAttr( "role" )
180-
.removeAttr( "aria-disabled" )
181-
.children( ".ui-menu-item-wrapper" )
182-
.removeUniqueId()
183-
.removeClass( "ui-menu-item-wrapper ui-state-hover" )
184-
.removeAttr( "tabIndex" )
185-
.removeAttr( "role" )
186-
.removeAttr( "aria-haspopup" )
187-
.children().each(function() {
188-
var elem = $( this );
189-
if ( elem.data( "ui-menu-submenu-caret" ) ) {
190-
elem.remove();
191-
}
192-
});
193-
194-
// Destroy menu dividers
195-
this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" );
184+
submenus.children().each(function() {
185+
var elem = $( this );
186+
if ( elem.data( "ui-menu-submenu-caret" ) ) {
187+
elem.remove();
188+
}
189+
});
196190
},
197191

198192
_keydown: function( event ) {
@@ -286,16 +280,15 @@ return $.widget( "ui.menu", {
286280
},
287281

288282
refresh: function() {
289-
var menus, items,
283+
var menus, items, newSubmenus, newItems, newWrappers,
290284
that = this,
291285
icon = this.options.icons.submenu,
292286
submenus = this.element.find( this.options.menus );
293287

294-
this.element.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length );
288+
this._toggleClass( "ui-menu-icons", null, !!this.element.find( ".ui-icon" ).length );
295289

296290
// Initialize nested menus
297-
submenus.filter( ":not(.ui-menu)" )
298-
.addClass( "ui-menu ui-widget ui-widget-content ui-front" )
291+
newSubmenus = submenus.filter( ":not(.ui-menu)" )
299292
.hide()
300293
.attr({
301294
role: this.options.role,
@@ -305,38 +298,39 @@ return $.widget( "ui.menu", {
305298
.each(function() {
306299
var menu = $( this ),
307300
item = menu.prev(),
308-
submenuCaret = $( "<span>" )
309-
.addClass( "ui-menu-icon ui-icon " + icon )
310-
.data( "ui-menu-submenu-caret", true );
301+
submenuCaret = $( "<span>" ).data( "ui-menu-submenu-caret", true );
311302

303+
that._addClass( submenuCaret, "ui-menu-icon", "ui-icon " + icon );
312304
item
313305
.attr( "aria-haspopup", "true" )
314306
.prepend( submenuCaret );
315307
menu.attr( "aria-labelledby", item.attr( "id" ) );
316308
});
317309

310+
this._addClass( newSubmenus, "ui-menu", "ui-widget ui-widget-content ui-front" );
311+
318312
menus = submenus.add( this.element );
319313
items = menus.find( this.options.items );
320314

321315
// Initialize menu-items containing spaces and/or dashes only as dividers
322316
items.not( ".ui-menu-item" ).each(function() {
323317
var item = $( this );
324318
if ( that._isDivider( item ) ) {
325-
item.addClass( "ui-widget-content ui-menu-divider" );
319+
that._addClass( item, "ui-menu-divider", "ui-widget-content" );
326320
}
327321
});
328322

329323
// Don't refresh list items that are already adapted
330-
items.not( ".ui-menu-item, .ui-menu-divider" )
331-
.addClass( "ui-menu-item" )
332-
.children()
333-
.not( ".ui-menu" )
334-
.addClass( "ui-menu-item-wrapper" )
335-
.uniqueId()
336-
.attr({
337-
tabIndex: -1,
338-
role: this._itemRole()
339-
});
324+
newItems = items.not( ".ui-menu-item, .ui-menu-divider" );
325+
newWrappers = newItems.children()
326+
.not( ".ui-menu" )
327+
.uniqueId()
328+
.attr({
329+
tabIndex: -1,
330+
role: this._itemRole()
331+
});
332+
this._addClass( newItems, "ui-menu-item" )
333+
._addClass( newWrappers, "ui-menu-item-wrapper" );
340334

341335
// Add aria-disabled attribute to any disabled menu item
342336
items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
@@ -356,26 +350,27 @@ return $.widget( "ui.menu", {
356350

357351
_setOption: function( key, value ) {
358352
if ( key === "icons" ) {
359-
this.element.find( ".ui-menu-icon" )
360-
.removeClass( this.options.icons.submenu )
361-
.addClass( value.submenu );
353+
var icons = this.element.find( ".ui-menu-icon" );
354+
this._removeClass( icons, null, this.options.icons.submenu )
355+
._addClass( icons, null, value.submenu );
362356
}
363357
if ( key === "disabled" ) {
364-
this.element
365-
.toggleClass( "ui-state-disabled", !!value )
366-
.attr( "aria-disabled", value );
358+
this.element.attr( "aria-disabled", value );
359+
this._toggleClass( null, "ui-state-disabled", !!value );
367360
}
368361
this._super( key, value );
369362
},
370363

371364
focus: function( event, item ) {
372-
var nested, focused;
365+
var nested, focused, activeParent;
373366
this.blur( event, event && event.type === "focus" );
374367

375368
this._scrollIntoView( item );
376369

377370
this.active = item.first();
378-
focused = this.active.children( ".ui-menu-item-wrapper" ).addClass( "ui-state-active" );
371+
372+
focused = this.active.children( ".ui-menu-item-wrapper" );
373+
this._addClass( focused, null, "ui-state-active" );
379374

380375
// Only update aria-activedescendant if there's a role
381376
// otherwise we assume focus is managed elsewhere
@@ -384,11 +379,11 @@ return $.widget( "ui.menu", {
384379
}
385380

386381
// Highlight active parent menu item, if any
387-
this.active
382+
activeParent = this.active
388383
.parent()
389384
.closest( ".ui-menu-item" )
390-
.children( ".ui-menu-item-wrapper" )
391-
.addClass( "ui-state-active" );
385+
.children( ".ui-menu-item-wrapper" );
386+
this._addClass( activeParent, null, "ui-state-active" );
392387

393388
if ( event && event.type === "keydown" ) {
394389
this._close();
@@ -434,7 +429,8 @@ return $.widget( "ui.menu", {
434429
return;
435430
}
436431

437-
this.active.children( ".ui-menu-item-wrapper" ).removeClass( "ui-state-active" );
432+
this._removeClass( this.active.children( ".ui-menu-item-wrapper" ),
433+
null, "ui-state-active" );
438434
this.active = null;
439435

440436
this._trigger( "blur", event, { item: this.active } );
@@ -498,14 +494,14 @@ return $.widget( "ui.menu", {
498494
startMenu = this.active ? this.active.parent() : this.element;
499495
}
500496

501-
startMenu
497+
var active = startMenu
502498
.find( ".ui-menu" )
503499
.hide()
504500
.attr( "aria-hidden", "true" )
505501
.attr( "aria-expanded", "false" )
506502
.end()
507-
.find( ".ui-state-active" ).not( ".ui-menu-item-wrapper" )
508-
.removeClass( "ui-state-active" );
503+
.find( ".ui-state-active" ).not( ".ui-menu-item-wrapper" );
504+
this._removeClass( active, null, "ui-state-active" );
509505
},
510506

511507
_closeOnDocumentClick: function( event ) {

0 commit comments

Comments
 (0)