|
1 /*! |
|
2 * jQuery UI Accordion 1.12.1 |
|
3 * http://jqueryui.com |
|
4 * |
|
5 * Copyright jQuery Foundation and other contributors |
|
6 * Released under the MIT license. |
|
7 * http://jquery.org/license |
|
8 */ |
|
9 |
|
10 //>>label: Accordion |
|
11 //>>group: Widgets |
|
12 // jscs:disable maximumLineLength |
|
13 //>>description: Displays collapsible content panels for presenting information in a limited amount of space. |
|
14 // jscs:enable maximumLineLength |
|
15 //>>docs: http://api.jqueryui.com/accordion/ |
|
16 //>>demos: http://jqueryui.com/accordion/ |
|
17 //>>css.structure: ../../themes/base/core.css |
|
18 //>>css.structure: ../../themes/base/accordion.css |
|
19 //>>css.theme: ../../themes/base/theme.css |
|
20 |
|
21 ( function( factory ) { |
|
22 if ( typeof define === "function" && define.amd ) { |
|
23 |
|
24 // AMD. Register as an anonymous module. |
|
25 define( [ |
|
26 "jquery", |
|
27 "./core" |
|
28 ], factory ); |
|
29 } else { |
|
30 |
|
31 // Browser globals |
|
32 factory( jQuery ); |
|
33 } |
|
34 }( function( $ ) { |
|
35 |
|
36 return $.widget( "ui.accordion", { |
|
37 version: "1.12.1", |
|
38 options: { |
|
39 active: 0, |
|
40 animate: {}, |
|
41 classes: { |
|
42 "ui-accordion-header": "ui-corner-top", |
|
43 "ui-accordion-header-collapsed": "ui-corner-all", |
|
44 "ui-accordion-content": "ui-corner-bottom" |
|
45 }, |
|
46 collapsible: false, |
|
47 event: "click", |
|
48 header: "> li > :first-child, > :not(li):even", |
|
49 heightStyle: "auto", |
|
50 icons: { |
|
51 activeHeader: "ui-icon-triangle-1-s", |
|
52 header: "ui-icon-triangle-1-e" |
|
53 }, |
|
54 |
|
55 // Callbacks |
|
56 activate: null, |
|
57 beforeActivate: null |
|
58 }, |
|
59 |
|
60 hideProps: { |
|
61 borderTopWidth: "hide", |
|
62 borderBottomWidth: "hide", |
|
63 paddingTop: "hide", |
|
64 paddingBottom: "hide", |
|
65 height: "hide" |
|
66 }, |
|
67 |
|
68 showProps: { |
|
69 borderTopWidth: "show", |
|
70 borderBottomWidth: "show", |
|
71 paddingTop: "show", |
|
72 paddingBottom: "show", |
|
73 height: "show" |
|
74 }, |
|
75 |
|
76 _create: function() { |
|
77 var options = this.options; |
|
78 |
|
79 this.prevShow = this.prevHide = $(); |
|
80 this._addClass( "ui-accordion", "ui-widget ui-helper-reset" ); |
|
81 this.element.attr( "role", "tablist" ); |
|
82 |
|
83 // Don't allow collapsible: false and active: false / null |
|
84 if ( !options.collapsible && ( options.active === false || options.active == null ) ) { |
|
85 options.active = 0; |
|
86 } |
|
87 |
|
88 this._processPanels(); |
|
89 |
|
90 // handle negative values |
|
91 if ( options.active < 0 ) { |
|
92 options.active += this.headers.length; |
|
93 } |
|
94 this._refresh(); |
|
95 }, |
|
96 |
|
97 _getCreateEventData: function() { |
|
98 return { |
|
99 header: this.active, |
|
100 panel: !this.active.length ? $() : this.active.next() |
|
101 }; |
|
102 }, |
|
103 |
|
104 _createIcons: function() { |
|
105 var icon, children, |
|
106 icons = this.options.icons; |
|
107 |
|
108 if ( icons ) { |
|
109 icon = $( "<span>" ); |
|
110 this._addClass( icon, "ui-accordion-header-icon", "ui-icon " + icons.header ); |
|
111 icon.prependTo( this.headers ); |
|
112 children = this.active.children( ".ui-accordion-header-icon" ); |
|
113 this._removeClass( children, icons.header ) |
|
114 ._addClass( children, null, icons.activeHeader ) |
|
115 ._addClass( this.headers, "ui-accordion-icons" ); |
|
116 } |
|
117 }, |
|
118 |
|
119 _destroyIcons: function() { |
|
120 this._removeClass( this.headers, "ui-accordion-icons" ); |
|
121 this.headers.children( ".ui-accordion-header-icon" ).remove(); |
|
122 }, |
|
123 |
|
124 _destroy: function() { |
|
125 var contents; |
|
126 |
|
127 // Clean up main element |
|
128 this.element.removeAttr( "role" ); |
|
129 |
|
130 // Clean up headers |
|
131 this.headers |
|
132 .removeAttr( "role aria-expanded aria-selected aria-controls tabIndex" ) |
|
133 .removeUniqueId(); |
|
134 |
|
135 this._destroyIcons(); |
|
136 |
|
137 // Clean up content panels |
|
138 contents = this.headers.next() |
|
139 .css( "display", "" ) |
|
140 .removeAttr( "role aria-hidden aria-labelledby" ) |
|
141 .removeUniqueId(); |
|
142 |
|
143 if ( this.options.heightStyle !== "content" ) { |
|
144 contents.css( "height", "" ); |
|
145 } |
|
146 }, |
|
147 |
|
148 _setOption: function( key, value ) { |
|
149 if ( key === "active" ) { |
|
150 |
|
151 // _activate() will handle invalid values and update this.options |
|
152 this._activate( value ); |
|
153 return; |
|
154 } |
|
155 |
|
156 if ( key === "event" ) { |
|
157 if ( this.options.event ) { |
|
158 this._off( this.headers, this.options.event ); |
|
159 } |
|
160 this._setupEvents( value ); |
|
161 } |
|
162 |
|
163 this._super( key, value ); |
|
164 |
|
165 // Setting collapsible: false while collapsed; open first panel |
|
166 if ( key === "collapsible" && !value && this.options.active === false ) { |
|
167 this._activate( 0 ); |
|
168 } |
|
169 |
|
170 if ( key === "icons" ) { |
|
171 this._destroyIcons(); |
|
172 if ( value ) { |
|
173 this._createIcons(); |
|
174 } |
|
175 } |
|
176 }, |
|
177 |
|
178 _setOptionDisabled: function( value ) { |
|
179 this._super( value ); |
|
180 |
|
181 this.element.attr( "aria-disabled", value ); |
|
182 |
|
183 // Support: IE8 Only |
|
184 // #5332 / #6059 - opacity doesn't cascade to positioned elements in IE |
|
185 // so we need to add the disabled class to the headers and panels |
|
186 this._toggleClass( null, "ui-state-disabled", !!value ); |
|
187 this._toggleClass( this.headers.add( this.headers.next() ), null, "ui-state-disabled", |
|
188 !!value ); |
|
189 }, |
|
190 |
|
191 _keydown: function( event ) { |
|
192 if ( event.altKey || event.ctrlKey ) { |
|
193 return; |
|
194 } |
|
195 |
|
196 var keyCode = $.ui.keyCode, |
|
197 length = this.headers.length, |
|
198 currentIndex = this.headers.index( event.target ), |
|
199 toFocus = false; |
|
200 |
|
201 switch ( event.keyCode ) { |
|
202 case keyCode.RIGHT: |
|
203 case keyCode.DOWN: |
|
204 toFocus = this.headers[ ( currentIndex + 1 ) % length ]; |
|
205 break; |
|
206 case keyCode.LEFT: |
|
207 case keyCode.UP: |
|
208 toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; |
|
209 break; |
|
210 case keyCode.SPACE: |
|
211 case keyCode.ENTER: |
|
212 this._eventHandler( event ); |
|
213 break; |
|
214 case keyCode.HOME: |
|
215 toFocus = this.headers[ 0 ]; |
|
216 break; |
|
217 case keyCode.END: |
|
218 toFocus = this.headers[ length - 1 ]; |
|
219 break; |
|
220 } |
|
221 |
|
222 if ( toFocus ) { |
|
223 $( event.target ).attr( "tabIndex", -1 ); |
|
224 $( toFocus ).attr( "tabIndex", 0 ); |
|
225 $( toFocus ).trigger( "focus" ); |
|
226 event.preventDefault(); |
|
227 } |
|
228 }, |
|
229 |
|
230 _panelKeyDown: function( event ) { |
|
231 if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { |
|
232 $( event.currentTarget ).prev().trigger( "focus" ); |
|
233 } |
|
234 }, |
|
235 |
|
236 refresh: function() { |
|
237 var options = this.options; |
|
238 this._processPanels(); |
|
239 |
|
240 // Was collapsed or no panel |
|
241 if ( ( options.active === false && options.collapsible === true ) || |
|
242 !this.headers.length ) { |
|
243 options.active = false; |
|
244 this.active = $(); |
|
245 |
|
246 // active false only when collapsible is true |
|
247 } else if ( options.active === false ) { |
|
248 this._activate( 0 ); |
|
249 |
|
250 // was active, but active panel is gone |
|
251 } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { |
|
252 |
|
253 // all remaining panel are disabled |
|
254 if ( this.headers.length === this.headers.find( ".ui-state-disabled" ).length ) { |
|
255 options.active = false; |
|
256 this.active = $(); |
|
257 |
|
258 // activate previous panel |
|
259 } else { |
|
260 this._activate( Math.max( 0, options.active - 1 ) ); |
|
261 } |
|
262 |
|
263 // was active, active panel still exists |
|
264 } else { |
|
265 |
|
266 // make sure active index is correct |
|
267 options.active = this.headers.index( this.active ); |
|
268 } |
|
269 |
|
270 this._destroyIcons(); |
|
271 |
|
272 this._refresh(); |
|
273 }, |
|
274 |
|
275 _processPanels: function() { |
|
276 var prevHeaders = this.headers, |
|
277 prevPanels = this.panels; |
|
278 |
|
279 this.headers = this.element.find( this.options.header ); |
|
280 this._addClass( this.headers, "ui-accordion-header ui-accordion-header-collapsed", |
|
281 "ui-state-default" ); |
|
282 |
|
283 this.panels = this.headers.next().filter( ":not(.ui-accordion-content-active)" ).hide(); |
|
284 this._addClass( this.panels, "ui-accordion-content", "ui-helper-reset ui-widget-content" ); |
|
285 |
|
286 // Avoid memory leaks (#10056) |
|
287 if ( prevPanels ) { |
|
288 this._off( prevHeaders.not( this.headers ) ); |
|
289 this._off( prevPanels.not( this.panels ) ); |
|
290 } |
|
291 }, |
|
292 |
|
293 _refresh: function() { |
|
294 var maxHeight, |
|
295 options = this.options, |
|
296 heightStyle = options.heightStyle, |
|
297 parent = this.element.parent(); |
|
298 |
|
299 this.active = this._findActive( options.active ); |
|
300 this._addClass( this.active, "ui-accordion-header-active", "ui-state-active" ) |
|
301 ._removeClass( this.active, "ui-accordion-header-collapsed" ); |
|
302 this._addClass( this.active.next(), "ui-accordion-content-active" ); |
|
303 this.active.next().show(); |
|
304 |
|
305 this.headers |
|
306 .attr( "role", "tab" ) |
|
307 .each( function() { |
|
308 var header = $( this ), |
|
309 headerId = header.uniqueId().attr( "id" ), |
|
310 panel = header.next(), |
|
311 panelId = panel.uniqueId().attr( "id" ); |
|
312 header.attr( "aria-controls", panelId ); |
|
313 panel.attr( "aria-labelledby", headerId ); |
|
314 } ) |
|
315 .next() |
|
316 .attr( "role", "tabpanel" ); |
|
317 |
|
318 this.headers |
|
319 .not( this.active ) |
|
320 .attr( { |
|
321 "aria-selected": "false", |
|
322 "aria-expanded": "false", |
|
323 tabIndex: -1 |
|
324 } ) |
|
325 .next() |
|
326 .attr( { |
|
327 "aria-hidden": "true" |
|
328 } ) |
|
329 .hide(); |
|
330 |
|
331 // Make sure at least one header is in the tab order |
|
332 if ( !this.active.length ) { |
|
333 this.headers.eq( 0 ).attr( "tabIndex", 0 ); |
|
334 } else { |
|
335 this.active.attr( { |
|
336 "aria-selected": "true", |
|
337 "aria-expanded": "true", |
|
338 tabIndex: 0 |
|
339 } ) |
|
340 .next() |
|
341 .attr( { |
|
342 "aria-hidden": "false" |
|
343 } ); |
|
344 } |
|
345 |
|
346 this._createIcons(); |
|
347 |
|
348 this._setupEvents( options.event ); |
|
349 |
|
350 if ( heightStyle === "fill" ) { |
|
351 maxHeight = parent.height(); |
|
352 this.element.siblings( ":visible" ).each( function() { |
|
353 var elem = $( this ), |
|
354 position = elem.css( "position" ); |
|
355 |
|
356 if ( position === "absolute" || position === "fixed" ) { |
|
357 return; |
|
358 } |
|
359 maxHeight -= elem.outerHeight( true ); |
|
360 } ); |
|
361 |
|
362 this.headers.each( function() { |
|
363 maxHeight -= $( this ).outerHeight( true ); |
|
364 } ); |
|
365 |
|
366 this.headers.next() |
|
367 .each( function() { |
|
368 $( this ).height( Math.max( 0, maxHeight - |
|
369 $( this ).innerHeight() + $( this ).height() ) ); |
|
370 } ) |
|
371 .css( "overflow", "auto" ); |
|
372 } else if ( heightStyle === "auto" ) { |
|
373 maxHeight = 0; |
|
374 this.headers.next() |
|
375 .each( function() { |
|
376 var isVisible = $( this ).is( ":visible" ); |
|
377 if ( !isVisible ) { |
|
378 $( this ).show(); |
|
379 } |
|
380 maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); |
|
381 if ( !isVisible ) { |
|
382 $( this ).hide(); |
|
383 } |
|
384 } ) |
|
385 .height( maxHeight ); |
|
386 } |
|
387 }, |
|
388 |
|
389 _activate: function( index ) { |
|
390 var active = this._findActive( index )[ 0 ]; |
|
391 |
|
392 // Trying to activate the already active panel |
|
393 if ( active === this.active[ 0 ] ) { |
|
394 return; |
|
395 } |
|
396 |
|
397 // Trying to collapse, simulate a click on the currently active header |
|
398 active = active || this.active[ 0 ]; |
|
399 |
|
400 this._eventHandler( { |
|
401 target: active, |
|
402 currentTarget: active, |
|
403 preventDefault: $.noop |
|
404 } ); |
|
405 }, |
|
406 |
|
407 _findActive: function( selector ) { |
|
408 return typeof selector === "number" ? this.headers.eq( selector ) : $(); |
|
409 }, |
|
410 |
|
411 _setupEvents: function( event ) { |
|
412 var events = { |
|
413 keydown: "_keydown" |
|
414 }; |
|
415 if ( event ) { |
|
416 $.each( event.split( " " ), function( index, eventName ) { |
|
417 events[ eventName ] = "_eventHandler"; |
|
418 } ); |
|
419 } |
|
420 |
|
421 this._off( this.headers.add( this.headers.next() ) ); |
|
422 this._on( this.headers, events ); |
|
423 this._on( this.headers.next(), { keydown: "_panelKeyDown" } ); |
|
424 this._hoverable( this.headers ); |
|
425 this._focusable( this.headers ); |
|
426 }, |
|
427 |
|
428 _eventHandler: function( event ) { |
|
429 var activeChildren, clickedChildren, |
|
430 options = this.options, |
|
431 active = this.active, |
|
432 clicked = $( event.currentTarget ), |
|
433 clickedIsActive = clicked[ 0 ] === active[ 0 ], |
|
434 collapsing = clickedIsActive && options.collapsible, |
|
435 toShow = collapsing ? $() : clicked.next(), |
|
436 toHide = active.next(), |
|
437 eventData = { |
|
438 oldHeader: active, |
|
439 oldPanel: toHide, |
|
440 newHeader: collapsing ? $() : clicked, |
|
441 newPanel: toShow |
|
442 }; |
|
443 |
|
444 event.preventDefault(); |
|
445 |
|
446 if ( |
|
447 |
|
448 // click on active header, but not collapsible |
|
449 ( clickedIsActive && !options.collapsible ) || |
|
450 |
|
451 // allow canceling activation |
|
452 ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { |
|
453 return; |
|
454 } |
|
455 |
|
456 options.active = collapsing ? false : this.headers.index( clicked ); |
|
457 |
|
458 // When the call to ._toggle() comes after the class changes |
|
459 // it causes a very odd bug in IE 8 (see #6720) |
|
460 this.active = clickedIsActive ? $() : clicked; |
|
461 this._toggle( eventData ); |
|
462 |
|
463 // Switch classes |
|
464 // corner classes on the previously active header stay after the animation |
|
465 this._removeClass( active, "ui-accordion-header-active", "ui-state-active" ); |
|
466 if ( options.icons ) { |
|
467 activeChildren = active.children( ".ui-accordion-header-icon" ); |
|
468 this._removeClass( activeChildren, null, options.icons.activeHeader ) |
|
469 ._addClass( activeChildren, null, options.icons.header ); |
|
470 } |
|
471 |
|
472 if ( !clickedIsActive ) { |
|
473 this._removeClass( clicked, "ui-accordion-header-collapsed" ) |
|
474 ._addClass( clicked, "ui-accordion-header-active", "ui-state-active" ); |
|
475 if ( options.icons ) { |
|
476 clickedChildren = clicked.children( ".ui-accordion-header-icon" ); |
|
477 this._removeClass( clickedChildren, null, options.icons.header ) |
|
478 ._addClass( clickedChildren, null, options.icons.activeHeader ); |
|
479 } |
|
480 |
|
481 this._addClass( clicked.next(), "ui-accordion-content-active" ); |
|
482 } |
|
483 }, |
|
484 |
|
485 _toggle: function( data ) { |
|
486 var toShow = data.newPanel, |
|
487 toHide = this.prevShow.length ? this.prevShow : data.oldPanel; |
|
488 |
|
489 // Handle activating a panel during the animation for another activation |
|
490 this.prevShow.add( this.prevHide ).stop( true, true ); |
|
491 this.prevShow = toShow; |
|
492 this.prevHide = toHide; |
|
493 |
|
494 if ( this.options.animate ) { |
|
495 this._animate( toShow, toHide, data ); |
|
496 } else { |
|
497 toHide.hide(); |
|
498 toShow.show(); |
|
499 this._toggleComplete( data ); |
|
500 } |
|
501 |
|
502 toHide.attr( { |
|
503 "aria-hidden": "true" |
|
504 } ); |
|
505 toHide.prev().attr( { |
|
506 "aria-selected": "false", |
|
507 "aria-expanded": "false" |
|
508 } ); |
|
509 |
|
510 // if we're switching panels, remove the old header from the tab order |
|
511 // if we're opening from collapsed state, remove the previous header from the tab order |
|
512 // if we're collapsing, then keep the collapsing header in the tab order |
|
513 if ( toShow.length && toHide.length ) { |
|
514 toHide.prev().attr( { |
|
515 "tabIndex": -1, |
|
516 "aria-expanded": "false" |
|
517 } ); |
|
518 } else if ( toShow.length ) { |
|
519 this.headers.filter( function() { |
|
520 return parseInt( $( this ).attr( "tabIndex" ), 10 ) === 0; |
|
521 } ) |
|
522 .attr( "tabIndex", -1 ); |
|
523 } |
|
524 |
|
525 toShow |
|
526 .attr( "aria-hidden", "false" ) |
|
527 .prev() |
|
528 .attr( { |
|
529 "aria-selected": "true", |
|
530 "aria-expanded": "true", |
|
531 tabIndex: 0 |
|
532 } ); |
|
533 }, |
|
534 |
|
535 _animate: function( toShow, toHide, data ) { |
|
536 var total, easing, duration, |
|
537 that = this, |
|
538 adjust = 0, |
|
539 boxSizing = toShow.css( "box-sizing" ), |
|
540 down = toShow.length && |
|
541 ( !toHide.length || ( toShow.index() < toHide.index() ) ), |
|
542 animate = this.options.animate || {}, |
|
543 options = down && animate.down || animate, |
|
544 complete = function() { |
|
545 that._toggleComplete( data ); |
|
546 }; |
|
547 |
|
548 if ( typeof options === "number" ) { |
|
549 duration = options; |
|
550 } |
|
551 if ( typeof options === "string" ) { |
|
552 easing = options; |
|
553 } |
|
554 |
|
555 // fall back from options to animation in case of partial down settings |
|
556 easing = easing || options.easing || animate.easing; |
|
557 duration = duration || options.duration || animate.duration; |
|
558 |
|
559 if ( !toHide.length ) { |
|
560 return toShow.animate( this.showProps, duration, easing, complete ); |
|
561 } |
|
562 if ( !toShow.length ) { |
|
563 return toHide.animate( this.hideProps, duration, easing, complete ); |
|
564 } |
|
565 |
|
566 total = toShow.show().outerHeight(); |
|
567 toHide.animate( this.hideProps, { |
|
568 duration: duration, |
|
569 easing: easing, |
|
570 step: function( now, fx ) { |
|
571 fx.now = Math.round( now ); |
|
572 } |
|
573 } ); |
|
574 toShow |
|
575 .hide() |
|
576 .animate( this.showProps, { |
|
577 duration: duration, |
|
578 easing: easing, |
|
579 complete: complete, |
|
580 step: function( now, fx ) { |
|
581 fx.now = Math.round( now ); |
|
582 if ( fx.prop !== "height" ) { |
|
583 if ( boxSizing === "content-box" ) { |
|
584 adjust += fx.now; |
|
585 } |
|
586 } else if ( that.options.heightStyle !== "content" ) { |
|
587 fx.now = Math.round( total - toHide.outerHeight() - adjust ); |
|
588 adjust = 0; |
|
589 } |
|
590 } |
|
591 } ); |
|
592 }, |
|
593 |
|
594 _toggleComplete: function( data ) { |
|
595 var toHide = data.oldPanel, |
|
596 prev = toHide.prev(); |
|
597 |
|
598 this._removeClass( toHide, "ui-accordion-content-active" ); |
|
599 this._removeClass( prev, "ui-accordion-header-active" ) |
|
600 ._addClass( prev, "ui-accordion-header-collapsed" ); |
|
601 |
|
602 // Work around for rendering bug in IE (#5421) |
|
603 if ( toHide.length ) { |
|
604 toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className; |
|
605 } |
|
606 this._trigger( "activate", null, data ); |
|
607 } |
|
608 } ); |
|
609 |
|
610 } ) ); |