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