balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

accordion.js (15864B)


      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 } ) );