Merge "Special:Upload should not crash on failing previews"
[mediawiki.git] / resources / lib / jquery.ui / jquery.ui.accordion.js
blob55bbecba59db19e84260629d3e8bea1814fe9e09
1 /*!
2  * jQuery UI Accordion 1.9.2
3  * http://jqueryui.com
4  *
5  * Copyright 2012 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 ) {
17 var uid = 0,
18         hideProps = {},
19         showProps = {};
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";
26 $.widget( "ui.accordion", {
27         version: "1.9.2",
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                 },
40                 // callbacks
41                 activate: null,
42                 beforeActivate: null
43         },
45         _create: function() {
46                 var accordionId = this.accordionId = "ui-accordion-" +
47                                 (this.element.attr( "id" ) || ++uid),
48                         options = this.options;
50                 this.prevShow = this.prevHide = $();
51                 this.element.addClass( "ui-accordion ui-widget ui-helper-reset" );
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 );
58                 this.headers.next()
59                         .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
60                         .hide();
62                 // don't allow collapsible: false and active: false / null
63                 if ( !options.collapsible && (options.active === false || options.active == null) ) {
64                         options.active = 0;
65                 }
66                 // handle negative values
67                 if ( options.active < 0 ) {
68                         options.active += this.headers.length;
69                 }
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();
77                 this._createIcons();
78                 this.refresh();
80                 // ARIA
81                 this.element.attr( "role", "tablist" );
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" );
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();
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                 }
132                 this._on( this.headers, { keydown: "_keydown" });
133                 this._on( this.headers.next(), { keydown: "_panelKeyDown" });
134                 this._setupEvents( options.event );
135         },
137         _getCreateEventData: function() {
138                 return {
139                         header: this.active,
140                         content: !this.active.length ? $() : this.active.next()
141                 };
142         },
144         _createIcons: function() {
145                 var icons = this.options.icons;
146                 if ( icons ) {
147                         $( "<span>" )
148                                 .addClass( "ui-accordion-header-icon ui-icon " + icons.header )
149                                 .prependTo( this.headers );
150                         this.active.children( ".ui-accordion-header-icon" )
151                                 .removeClass( icons.header )
152                                 .addClass( icons.activeHeader );
153                         this.headers.addClass( "ui-accordion-icons" );
154                 }
155         },
157         _destroyIcons: function() {
158                 this.headers
159                         .removeClass( "ui-accordion-icons" )
160                         .children( ".ui-accordion-header-icon" )
161                                 .remove();
162         },
164         _destroy: function() {
165                 var contents;
167                 // clean up main element
168                 this.element
169                         .removeClass( "ui-accordion ui-widget ui-helper-reset" )
170                         .removeAttr( "role" );
172                 // clean up headers
173                 this.headers
174                         .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" )
175                         .removeAttr( "role" )
176                         .removeAttr( "aria-selected" )
177                         .removeAttr( "aria-controls" )
178                         .removeAttr( "tabIndex" )
179                         .each(function() {
180                                 if ( /^ui-accordion/.test( this.id ) ) {
181                                         this.removeAttribute( "id" );
182                                 }
183                         });
184                 this._destroyIcons();
186                 // clean up content panels
187                 contents = this.headers.next()
188                         .css( "display", "" )
189                         .removeAttr( "role" )
190                         .removeAttr( "aria-expanded" )
191                         .removeAttr( "aria-hidden" )
192                         .removeAttr( "aria-labelledby" )
193                         .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" )
194                         .each(function() {
195                                 if ( /^ui-accordion/.test( this.id ) ) {
196                                         this.removeAttribute( "id" );
197                                 }
198                         });
199                 if ( this.options.heightStyle !== "content" ) {
200                         contents.css( "height", "" );
201                 }
202         },
204         _setOption: function( key, value ) {
205                 if ( key === "active" ) {
206                         // _activate() will handle invalid values and update this.options
207                         this._activate( value );
208                         return;
209                 }
211                 if ( key === "event" ) {
212                         if ( this.options.event ) {
213                                 this._off( this.headers, this.options.event );
214                         }
215                         this._setupEvents( value );
216                 }
218                 this._super( key, value );
220                 // setting collapsible: false while collapsed; open first panel
221                 if ( key === "collapsible" && !value && this.options.active === false ) {
222                         this._activate( 0 );
223                 }
225                 if ( key === "icons" ) {
226                         this._destroyIcons();
227                         if ( value ) {
228                                 this._createIcons();
229                         }
230                 }
232                 // #5332 - opacity doesn't cascade to positioned elements in IE
233                 // so we need to add the disabled class to the headers and panels
234                 if ( key === "disabled" ) {
235                         this.headers.add( this.headers.next() )
236                                 .toggleClass( "ui-state-disabled", !!value );
237                 }
238         },
240         _keydown: function( event ) {
241                 if ( event.altKey || event.ctrlKey ) {
242                         return;
243                 }
245                 var keyCode = $.ui.keyCode,
246                         length = this.headers.length,
247                         currentIndex = this.headers.index( event.target ),
248                         toFocus = false;
250                 switch ( event.keyCode ) {
251                         case keyCode.RIGHT:
252                         case keyCode.DOWN:
253                                 toFocus = this.headers[ ( currentIndex + 1 ) % length ];
254                                 break;
255                         case keyCode.LEFT:
256                         case keyCode.UP:
257                                 toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
258                                 break;
259                         case keyCode.SPACE:
260                         case keyCode.ENTER:
261                                 this._eventHandler( event );
262                                 break;
263                         case keyCode.HOME:
264                                 toFocus = this.headers[ 0 ];
265                                 break;
266                         case keyCode.END:
267                                 toFocus = this.headers[ length - 1 ];
268                                 break;
269                 }
271                 if ( toFocus ) {
272                         $( event.target ).attr( "tabIndex", -1 );
273                         $( toFocus ).attr( "tabIndex", 0 );
274                         toFocus.focus();
275                         event.preventDefault();
276                 }
277         },
279         _panelKeyDown : function( event ) {
280                 if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
281                         $( event.currentTarget ).prev().focus();
282                 }
283         },
285         refresh: function() {
286                 var maxHeight, overflow,
287                         heightStyle = this.options.heightStyle,
288                         parent = this.element.parent();
291                 if ( heightStyle === "fill" ) {
292                         // IE 6 treats height like minHeight, so we need to turn off overflow
293                         // in order to get a reliable height
294                         // we use the minHeight support test because we assume that only
295                         // browsers that don't support minHeight will treat height as minHeight
296                         if ( !$.support.minHeight ) {
297                                 overflow = parent.css( "overflow" );
298                                 parent.css( "overflow", "hidden");
299                         }
300                         maxHeight = parent.height();
301                         this.element.siblings( ":visible" ).each(function() {
302                                 var elem = $( this ),
303                                         position = elem.css( "position" );
305                                 if ( position === "absolute" || position === "fixed" ) {
306                                         return;
307                                 }
308                                 maxHeight -= elem.outerHeight( true );
309                         });
310                         if ( overflow ) {
311                                 parent.css( "overflow", overflow );
312                         }
314                         this.headers.each(function() {
315                                 maxHeight -= $( this ).outerHeight( true );
316                         });
318                         this.headers.next()
319                                 .each(function() {
320                                         $( this ).height( Math.max( 0, maxHeight -
321                                                 $( this ).innerHeight() + $( this ).height() ) );
322                                 })
323                                 .css( "overflow", "auto" );
324                 } else if ( heightStyle === "auto" ) {
325                         maxHeight = 0;
326                         this.headers.next()
327                                 .each(function() {
328                                         maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
329                                 })
330                                 .height( maxHeight );
331                 }
332         },
334         _activate: function( index ) {
335                 var active = this._findActive( index )[ 0 ];
337                 // trying to activate the already active panel
338                 if ( active === this.active[ 0 ] ) {
339                         return;
340                 }
342                 // trying to collapse, simulate a click on the currently active header
343                 active = active || this.active[ 0 ];
345                 this._eventHandler({
346                         target: active,
347                         currentTarget: active,
348                         preventDefault: $.noop
349                 });
350         },
352         _findActive: function( selector ) {
353                 return typeof selector === "number" ? this.headers.eq( selector ) : $();
354         },
356         _setupEvents: function( event ) {
357                 var events = {};
358                 if ( !event ) {
359                         return;
360                 }
361                 $.each( event.split(" "), function( index, eventName ) {
362                         events[ eventName ] = "_eventHandler";
363                 });
364                 this._on( this.headers, events );
365         },
367         _eventHandler: function( event ) {
368                 var options = this.options,
369                         active = this.active,
370                         clicked = $( event.currentTarget ),
371                         clickedIsActive = clicked[ 0 ] === active[ 0 ],
372                         collapsing = clickedIsActive && options.collapsible,
373                         toShow = collapsing ? $() : clicked.next(),
374                         toHide = active.next(),
375                         eventData = {
376                                 oldHeader: active,
377                                 oldPanel: toHide,
378                                 newHeader: collapsing ? $() : clicked,
379                                 newPanel: toShow
380                         };
382                 event.preventDefault();
384                 if (
385                                 // click on active header, but not collapsible
386                                 ( clickedIsActive && !options.collapsible ) ||
387                                 // allow canceling activation
388                                 ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
389                         return;
390                 }
392                 options.active = collapsing ? false : this.headers.index( clicked );
394                 // when the call to ._toggle() comes after the class changes
395                 // it causes a very odd bug in IE 8 (see #6720)
396                 this.active = clickedIsActive ? $() : clicked;
397                 this._toggle( eventData );
399                 // switch classes
400                 // corner classes on the previously active header stay after the animation
401                 active.removeClass( "ui-accordion-header-active ui-state-active" );
402                 if ( options.icons ) {
403                         active.children( ".ui-accordion-header-icon" )
404                                 .removeClass( options.icons.activeHeader )
405                                 .addClass( options.icons.header );
406                 }
408                 if ( !clickedIsActive ) {
409                         clicked
410                                 .removeClass( "ui-corner-all" )
411                                 .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" );
412                         if ( options.icons ) {
413                                 clicked.children( ".ui-accordion-header-icon" )
414                                         .removeClass( options.icons.header )
415                                         .addClass( options.icons.activeHeader );
416                         }
418                         clicked
419                                 .next()
420                                 .addClass( "ui-accordion-content-active" );
421                 }
422         },
424         _toggle: function( data ) {
425                 var toShow = data.newPanel,
426                         toHide = this.prevShow.length ? this.prevShow : data.oldPanel;
428                 // handle activating a panel during the animation for another activation
429                 this.prevShow.add( this.prevHide ).stop( true, true );
430                 this.prevShow = toShow;
431                 this.prevHide = toHide;
433                 if ( this.options.animate ) {
434                         this._animate( toShow, toHide, data );
435                 } else {
436                         toHide.hide();
437                         toShow.show();
438                         this._toggleComplete( data );
439                 }
441                 toHide.attr({
442                         "aria-expanded": "false",
443                         "aria-hidden": "true"
444                 });
445                 toHide.prev().attr( "aria-selected", "false" );
446                 // if we're switching panels, remove the old header from the tab order
447                 // if we're opening from collapsed state, remove the previous header from the tab order
448                 // if we're collapsing, then keep the collapsing header in the tab order
449                 if ( toShow.length && toHide.length ) {
450                         toHide.prev().attr( "tabIndex", -1 );
451                 } else if ( toShow.length ) {
452                         this.headers.filter(function() {
453                                 return $( this ).attr( "tabIndex" ) === 0;
454                         })
455                         .attr( "tabIndex", -1 );
456                 }
458                 toShow
459                         .attr({
460                                 "aria-expanded": "true",
461                                 "aria-hidden": "false"
462                         })
463                         .prev()
464                                 .attr({
465                                         "aria-selected": "true",
466                                         tabIndex: 0
467                                 });
468         },
470         _animate: function( toShow, toHide, data ) {
471                 var total, easing, duration,
472                         that = this,
473                         adjust = 0,
474                         down = toShow.length &&
475                                 ( !toHide.length || ( toShow.index() < toHide.index() ) ),
476                         animate = this.options.animate || {},
477                         options = down && animate.down || animate,
478                         complete = function() {
479                                 that._toggleComplete( data );
480                         };
482                 if ( typeof options === "number" ) {
483                         duration = options;
484                 }
485                 if ( typeof options === "string" ) {
486                         easing = options;
487                 }
488                 // fall back from options to animation in case of partial down settings
489                 easing = easing || options.easing || animate.easing;
490                 duration = duration || options.duration || animate.duration;
492                 if ( !toHide.length ) {
493                         return toShow.animate( showProps, duration, easing, complete );
494                 }
495                 if ( !toShow.length ) {
496                         return toHide.animate( hideProps, duration, easing, complete );
497                 }
499                 total = toShow.show().outerHeight();
500                 toHide.animate( hideProps, {
501                         duration: duration,
502                         easing: easing,
503                         step: function( now, fx ) {
504                                 fx.now = Math.round( now );
505                         }
506                 });
507                 toShow
508                         .hide()
509                         .animate( showProps, {
510                                 duration: duration,
511                                 easing: easing,
512                                 complete: complete,
513                                 step: function( now, fx ) {
514                                         fx.now = Math.round( now );
515                                         if ( fx.prop !== "height" ) {
516                                                 adjust += fx.now;
517                                         } else if ( that.options.heightStyle !== "content" ) {
518                                                 fx.now = Math.round( total - toHide.outerHeight() - adjust );
519                                                 adjust = 0;
520                                         }
521                                 }
522                         });
523         },
525         _toggleComplete: function( data ) {
526                 var toHide = data.oldPanel;
528                 toHide
529                         .removeClass( "ui-accordion-content-active" )
530                         .prev()
531                                 .removeClass( "ui-corner-top" )
532                                 .addClass( "ui-corner-all" );
534                 // Work around for rendering bug in IE (#5421)
535                 if ( toHide.length ) {
536                         toHide.parent()[0].className = toHide.parent()[0].className;
537                 }
539                 this._trigger( "activate", null, data );
540         }
545 // DEPRECATED
546 if ( $.uiBackCompat !== false ) {
547         // navigation options
548         (function( $, prototype ) {
549                 $.extend( prototype.options, {
550                         navigation: false,
551                         navigationFilter: function() {
552                                 return this.href.toLowerCase() === location.href.toLowerCase();
553                         }
554                 });
556                 var _create = prototype._create;
557                 prototype._create = function() {
558                         if ( this.options.navigation ) {
559                                 var that = this,
560                                         headers = this.element.find( this.options.header ),
561                                         content = headers.next(),
562                                         current = headers.add( content )
563                                                 .find( "a" )
564                                                 .filter( this.options.navigationFilter )
565                                                 [ 0 ];
566                                 if ( current ) {
567                                         headers.add( content ).each( function( index ) {
568                                                 if ( $.contains( this, current ) ) {
569                                                         that.options.active = Math.floor( index / 2 );
570                                                         return false;
571                                                 }
572                                         });
573                                 }
574                         }
575                         _create.call( this );
576                 };
577         }( jQuery, jQuery.ui.accordion.prototype ) );
579         // height options
580         (function( $, prototype ) {
581                 $.extend( prototype.options, {
582                         heightStyle: null, // remove default so we fall back to old values
583                         autoHeight: true, // use heightStyle: "auto"
584                         clearStyle: false, // use heightStyle: "content"
585                         fillSpace: false // use heightStyle: "fill"
586                 });
588                 var _create = prototype._create,
589                         _setOption = prototype._setOption;
591                 $.extend( prototype, {
592                         _create: function() {
593                                 this.options.heightStyle = this.options.heightStyle ||
594                                         this._mergeHeightStyle();
596                                 _create.call( this );
597                         },
599                         _setOption: function( key ) {
600                                 if ( key === "autoHeight" || key === "clearStyle" || key === "fillSpace" ) {
601                                         this.options.heightStyle = this._mergeHeightStyle();
602                                 }
603                                 _setOption.apply( this, arguments );
604                         },
606                         _mergeHeightStyle: function() {
607                                 var options = this.options;
609                                 if ( options.fillSpace ) {
610                                         return "fill";
611                                 }
613                                 if ( options.clearStyle ) {
614                                         return "content";
615                                 }
617                                 if ( options.autoHeight ) {
618                                         return "auto";
619                                 }
620                         }
621                 });
622         }( jQuery, jQuery.ui.accordion.prototype ) );
624         // icon options
625         (function( $, prototype ) {
626                 $.extend( prototype.options.icons, {
627                         activeHeader: null, // remove default so we fall back to old values
628                         headerSelected: "ui-icon-triangle-1-s"
629                 });
631                 var _createIcons = prototype._createIcons;
632                 prototype._createIcons = function() {
633                         if ( this.options.icons ) {
634                                 this.options.icons.activeHeader = this.options.icons.activeHeader ||
635                                         this.options.icons.headerSelected;
636                         }
637                         _createIcons.call( this );
638                 };
639         }( jQuery, jQuery.ui.accordion.prototype ) );
641         // expanded active option, activate method
642         (function( $, prototype ) {
643                 prototype.activate = prototype._activate;
645                 var _findActive = prototype._findActive;
646                 prototype._findActive = function( index ) {
647                         if ( index === -1 ) {
648                                 index = false;
649                         }
650                         if ( index && typeof index !== "number" ) {
651                                 index = this.headers.index( this.headers.filter( index ) );
652                                 if ( index === -1 ) {
653                                         index = false;
654                                 }
655                         }
656                         return _findActive.call( this, index );
657                 };
658         }( jQuery, jQuery.ui.accordion.prototype ) );
660         // resize method
661         jQuery.ui.accordion.prototype.resize = jQuery.ui.accordion.prototype.refresh;
663         // change events
664         (function( $, prototype ) {
665                 $.extend( prototype.options, {
666                         change: null,
667                         changestart: null
668                 });
670                 var _trigger = prototype._trigger;
671                 prototype._trigger = function( type, event, data ) {
672                         var ret = _trigger.apply( this, arguments );
673                         if ( !ret ) {
674                                 return false;
675                         }
677                         if ( type === "beforeActivate" ) {
678                                 ret = _trigger.call( this, "changestart", event, {
679                                         oldHeader: data.oldHeader,
680                                         oldContent: data.oldPanel,
681                                         newHeader: data.newHeader,
682                                         newContent: data.newPanel
683                                 });
684                         } else if ( type === "activate" ) {
685                                 ret = _trigger.call( this, "change", event, {
686                                         oldHeader: data.oldHeader,
687                                         oldContent: data.oldPanel,
688                                         newHeader: data.newHeader,
689                                         newContent: data.newPanel
690                                 });
691                         }
692                         return ret;
693                 };
694         }( jQuery, jQuery.ui.accordion.prototype ) );
696         // animated option
697         // NOTE: this only provides support for "slide", "bounceslide", and easings
698         // not the full $.ui.accordion.animations API
699         (function( $, prototype ) {
700                 $.extend( prototype.options, {
701                         animate: null,
702                         animated: "slide"
703                 });
705                 var _create = prototype._create;
706                 prototype._create = function() {
707                         var options = this.options;
708                         if ( options.animate === null ) {
709                                 if ( !options.animated ) {
710                                         options.animate = false;
711                                 } else if ( options.animated === "slide" ) {
712                                         options.animate = 300;
713                                 } else if ( options.animated === "bounceslide" ) {
714                                         options.animate = {
715                                                 duration: 200,
716                                                 down: {
717                                                         easing: "easeOutBounce",
718                                                         duration: 1000
719                                                 }
720                                         };
721                                 } else {
722                                         options.animate = options.animated;
723                                 }
724                         }
726                         _create.call( this );
727                 };
728         }( jQuery, jQuery.ui.accordion.prototype ) );
731 })( jQuery );