Skip to content

Commit d657538

Browse files
petersendiditmikesherov
authored andcommitted
Accordion: Enhance refresh to allow adding/removing panels. Fixes #4672 - Accordion: ability to add/remove panels
1 parent 8e1ceba commit d657538

File tree

2 files changed

+188
-88
lines changed

2 files changed

+188
-88
lines changed

tests/unit/accordion/accordion_methods.js

+62-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ test( "enable/disable", function() {
3030
});
3131

3232
test( "refresh", function() {
33-
expect( 6 );
33+
expect( 17 );
3434
var element = $( "#navigation" )
3535
.parent()
3636
.height( 300 )
@@ -43,6 +43,67 @@ test( "refresh", function() {
4343
element.parent().height( 500 );
4444
element.accordion( "refresh" );
4545
equalHeight( element, 455 );
46+
47+
element = $( "#list1" );
48+
element.accordion();
49+
state( element, 1, 0, 0 );
50+
51+
// disable panel via markup
52+
element.find( "h3.bar" ).eq( 1 ).addClass( "ui-state-disabled" );
53+
element.accordion( "refresh" );
54+
state( element, 1, 0, 0 );
55+
56+
// don't add multiple icons
57+
element.accordion( "refresh" );
58+
equal( element.find( ".ui-accordion-header-icon" ).length, 3 );
59+
60+
// add a panel
61+
element
62+
.append("<h3 class='bar' id='new_1'>new 1</h3>")
63+
.append("<div class='foo' id='new_1_panel'>new 1</div>");
64+
element.accordion( "refresh" );
65+
state( element, 1, 0, 0, 0 );
66+
67+
// remove all tabs
68+
element.find( "h3.bar, div.foo" ).remove();
69+
element.accordion( "refresh" );
70+
state( element );
71+
equal( element.accordion( "option", "active" ), false, "no active accordion panel" );
72+
73+
// add panels
74+
element
75+
.append("<h3 class='bar' id='new_2'>new 2</h3>")
76+
.append("<div class='foo' id='new_2_panel'>new 2</div>")
77+
.append("<h3 class='bar' id='new_3'>new 3</h3>")
78+
.append("<div class='foo' id='new_3_panel'>new 3</div>")
79+
.append("<h3 class='bar' id='new_4'>new 4</h3>")
80+
.append("<div class='foo' id='new_4_panel'>new 4</div>")
81+
.append("<h3 class='bar' id='new_5'>new 5</h3>")
82+
.append("<div class='foo' id='new_5_panel'>new 5</div>");
83+
element.accordion( "refresh" );
84+
state( element, 1, 0, 0, 0 );
85+
86+
// activate third tab
87+
element.accordion( "option", "active", 2 );
88+
state( element, 0, 0, 1, 0 );
89+
90+
// remove fourth panel, third panel should stay active
91+
element.find( "h3.bar" ).eq( 3 ).remove();
92+
element.find( "div.foo" ).eq( 3 ).remove();
93+
element.accordion( "refresh" );
94+
state( element, 0, 0, 1 );
95+
96+
// remove third (active) panel, second panel should become active
97+
element.find( "h3.bar" ).eq( 2 ).remove();
98+
element.find( "div.foo" ).eq( 2 ).remove();
99+
element.accordion( "refresh" );
100+
state( element, 0, 1 );
101+
102+
// remove first panel, previously active panel (now first) should stay active
103+
element.find( "h3.bar" ).eq( 0 ).remove();
104+
element.find( "div.foo" ).eq( 0 ).remove();
105+
element.accordion( "refresh" );
106+
state( element, 1 );
46107
});
47108

48109
test( "widget", function() {

ui/jquery.ui.accordion.js

+126-87
Original file line numberDiff line numberDiff line change
@@ -43,95 +43,23 @@ $.widget( "ui.accordion", {
4343
},
4444

4545
_create: function() {
46-
var accordionId = this.accordionId = "ui-accordion-" +
47-
(this.element.attr( "id" ) || ++uid),
48-
options = this.options;
49-
46+
var options = this.options;
5047
this.prevShow = this.prevHide = $();
51-
this.element.addClass( "ui-accordion ui-widget ui-helper-reset" );
52-
53-
this.headers = this.element.find( options.header )
54-
.addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" );
55-
this._hoverable( this.headers );
56-
this._focusable( this.headers );
57-
58-
this.headers.next()
59-
.addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
60-
.hide();
48+
this.element.addClass( "ui-accordion ui-widget ui-helper-reset" )
49+
// ARIA
50+
.attr( "role", "tablist" );
6151

6252
// don't allow collapsible: false and active: false / null
6353
if ( !options.collapsible && (options.active === false || options.active == null) ) {
6454
options.active = 0;
6555
}
56+
57+
this._processPanels();
6658
// handle negative values
6759
if ( options.active < 0 ) {
6860
options.active += this.headers.length;
6961
}
70-
this.active = this._findActive( options.active )
71-
.addClass( "ui-accordion-header-active ui-state-active" )
72-
.toggleClass( "ui-corner-all ui-corner-top" );
73-
this.active.next()
74-
.addClass( "ui-accordion-content-active" )
75-
.show();
76-
77-
this._createIcons();
78-
this.refresh();
79-
80-
// ARIA
81-
this.element.attr( "role", "tablist" );
82-
83-
this.headers
84-
.attr( "role", "tab" )
85-
.each(function( i ) {
86-
var header = $( this ),
87-
headerId = header.attr( "id" ),
88-
panel = header.next(),
89-
panelId = panel.attr( "id" );
90-
if ( !headerId ) {
91-
headerId = accordionId + "-header-" + i;
92-
header.attr( "id", headerId );
93-
}
94-
if ( !panelId ) {
95-
panelId = accordionId + "-panel-" + i;
96-
panel.attr( "id", panelId );
97-
}
98-
header.attr( "aria-controls", panelId );
99-
panel.attr( "aria-labelledby", headerId );
100-
})
101-
.next()
102-
.attr( "role", "tabpanel" );
103-
104-
this.headers
105-
.not( this.active )
106-
.attr({
107-
"aria-selected": "false",
108-
tabIndex: -1
109-
})
110-
.next()
111-
.attr({
112-
"aria-expanded": "false",
113-
"aria-hidden": "true"
114-
})
115-
.hide();
116-
117-
// make sure at least one header is in the tab order
118-
if ( !this.active.length ) {
119-
this.headers.eq( 0 ).attr( "tabIndex", 0 );
120-
} else {
121-
this.active.attr({
122-
"aria-selected": "true",
123-
tabIndex: 0
124-
})
125-
.next()
126-
.attr({
127-
"aria-expanded": "true",
128-
"aria-hidden": "false"
129-
});
130-
}
131-
132-
this._on( this.headers, { keydown: "_keydown" });
133-
this._on( this.headers.next(), { keydown: "_panelKeyDown" });
134-
this._setupEvents( options.event );
62+
this._refresh();
13563
},
13664

13765
_getCreateEventData: function() {
@@ -283,9 +211,114 @@ $.widget( "ui.accordion", {
283211
},
284212

285213
refresh: function() {
214+
var options = this.options;
215+
this._processPanels();
216+
217+
// was collapsed or no panel
218+
if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) {
219+
options.active = false;
220+
this.active = $();
221+
// active false only when collapsible is true
222+
} if ( options.active === false ) {
223+
this._activate( 0 );
224+
// was active, but active panel is gone
225+
} else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
226+
// all remaining panel are disabled
227+
if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) {
228+
options.active = false;
229+
this.active = $();
230+
// activate previous panel
231+
} else {
232+
this._activate( Math.max( 0, options.active - 1 ) );
233+
}
234+
// was active, active panel still exists
235+
} else {
236+
// make sure active index is correct
237+
options.active = this.headers.index( this.active );
238+
}
239+
240+
this._destroyIcons();
241+
242+
this._refresh();
243+
},
244+
245+
_processPanels: function() {
246+
this.headers = this.element.find( this.options.header )
247+
.addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" );
248+
249+
this.headers.next()
250+
.addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
251+
.filter(":not(.ui-accordion-content-active)")
252+
.hide();
253+
},
254+
255+
_refresh: function() {
286256
var maxHeight,
287-
heightStyle = this.options.heightStyle,
288-
parent = this.element.parent();
257+
options = this.options,
258+
heightStyle = options.heightStyle,
259+
parent = this.element.parent(),
260+
accordionId = this.accordionId = "ui-accordion-" +
261+
(this.element.attr( "id" ) || ++uid);
262+
263+
this.active = this._findActive( options.active )
264+
.addClass( "ui-accordion-header-active ui-state-active" )
265+
.toggleClass( "ui-corner-all ui-corner-top" );
266+
this.active.next()
267+
.addClass( "ui-accordion-content-active" )
268+
.show();
269+
270+
this.headers
271+
.attr( "role", "tab" )
272+
.each(function( i ) {
273+
var header = $( this ),
274+
headerId = header.attr( "id" ),
275+
panel = header.next(),
276+
panelId = panel.attr( "id" );
277+
if ( !headerId ) {
278+
headerId = accordionId + "-header-" + i;
279+
header.attr( "id", headerId );
280+
}
281+
if ( !panelId ) {
282+
panelId = accordionId + "-panel-" + i;
283+
panel.attr( "id", panelId );
284+
}
285+
header.attr( "aria-controls", panelId );
286+
panel.attr( "aria-labelledby", headerId );
287+
})
288+
.next()
289+
.attr( "role", "tabpanel" );
290+
291+
this.headers
292+
.not( this.active )
293+
.attr({
294+
"aria-selected": "false",
295+
tabIndex: -1
296+
})
297+
.next()
298+
.attr({
299+
"aria-expanded": "false",
300+
"aria-hidden": "true"
301+
})
302+
.hide();
303+
304+
// make sure at least one header is in the tab order
305+
if ( !this.active.length ) {
306+
this.headers.eq( 0 ).attr( "tabIndex", 0 );
307+
} else {
308+
this.active.attr({
309+
"aria-selected": "true",
310+
tabIndex: 0
311+
})
312+
.next()
313+
.attr({
314+
"aria-expanded": "true",
315+
"aria-hidden": "false"
316+
});
317+
}
318+
319+
this._createIcons();
320+
321+
this._setupEvents( options.event );
289322

290323
if ( heightStyle === "fill" ) {
291324
maxHeight = parent.height();
@@ -342,14 +375,20 @@ $.widget( "ui.accordion", {
342375
},
343376

344377
_setupEvents: function( event ) {
345-
var events = {};
346-
if ( !event ) {
347-
return;
378+
var events = {
379+
keydown: "_keydown"
380+
};
381+
if ( event ) {
382+
$.each( event.split(" "), function( index, eventName ) {
383+
events[ eventName ] = "_eventHandler";
384+
});
348385
}
349-
$.each( event.split(" "), function( index, eventName ) {
350-
events[ eventName ] = "_eventHandler";
351-
});
386+
387+
this._off( this.headers.add( this.headers.next() ) );
352388
this._on( this.headers, events );
389+
this._on( this.headers.next(), { keydown: "_panelKeyDown" });
390+
this._hoverable( this.headers );
391+
this._focusable( this.headers );
353392
},
354393

355394
_eventHandler: function( event ) {

0 commit comments

Comments
 (0)