mirror of
https://github.com/jquery/jquery-ui.git
synced 2024-11-21 11:04:24 +00:00
dc67d2c5d2
For instance, this is useful for the jquery-ui-rails gem, which does not
use jQuery UI's own minification, but relies on Rails to minify the
files where necessary. Rails in turn uses UglifyJS for JS and YUI for
CSS, both of which respect the /*! ... */ convention.
(cherry picked from commit 37dcc3e21d
, dropped menu, spinner and tooltip)
612 lines
16 KiB
JavaScript
612 lines
16 KiB
JavaScript
/*!
|
|
* jQuery UI Accordion @VERSION
|
|
*
|
|
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
* http://jquery.org/license
|
|
*
|
|
* http://docs.jquery.com/UI/Accordion
|
|
*
|
|
* Depends:
|
|
* jquery.ui.core.js
|
|
* jquery.ui.widget.js
|
|
*/
|
|
(function( $, undefined ) {
|
|
|
|
$.widget( "ui.accordion", {
|
|
options: {
|
|
active: 0,
|
|
animated: "slide",
|
|
autoHeight: true,
|
|
clearStyle: false,
|
|
collapsible: false,
|
|
event: "click",
|
|
fillSpace: false,
|
|
header: "> li > :first-child,> :not(li):even",
|
|
icons: {
|
|
header: "ui-icon-triangle-1-e",
|
|
headerSelected: "ui-icon-triangle-1-s"
|
|
},
|
|
navigation: false,
|
|
navigationFilter: function() {
|
|
return this.href.toLowerCase() === location.href.toLowerCase();
|
|
}
|
|
},
|
|
|
|
_create: function() {
|
|
var self = this,
|
|
options = self.options;
|
|
|
|
self.running = 0;
|
|
|
|
self.element
|
|
.addClass( "ui-accordion ui-widget ui-helper-reset" )
|
|
// in lack of child-selectors in CSS
|
|
// we need to mark top-LIs in a UL-accordion for some IE-fix
|
|
.children( "li" )
|
|
.addClass( "ui-accordion-li-fix" );
|
|
|
|
self.headers = self.element.find( options.header )
|
|
.addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" )
|
|
.bind( "mouseenter.accordion", function() {
|
|
if ( options.disabled ) {
|
|
return;
|
|
}
|
|
$( this ).addClass( "ui-state-hover" );
|
|
})
|
|
.bind( "mouseleave.accordion", function() {
|
|
if ( options.disabled ) {
|
|
return;
|
|
}
|
|
$( this ).removeClass( "ui-state-hover" );
|
|
})
|
|
.bind( "focus.accordion", function() {
|
|
if ( options.disabled ) {
|
|
return;
|
|
}
|
|
$( this ).addClass( "ui-state-focus" );
|
|
})
|
|
.bind( "blur.accordion", function() {
|
|
if ( options.disabled ) {
|
|
return;
|
|
}
|
|
$( this ).removeClass( "ui-state-focus" );
|
|
});
|
|
|
|
self.headers.next()
|
|
.addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" );
|
|
|
|
if ( options.navigation ) {
|
|
var current = self.element.find( "a" ).filter( options.navigationFilter ).eq( 0 );
|
|
if ( current.length ) {
|
|
var header = current.closest( ".ui-accordion-header" );
|
|
if ( header.length ) {
|
|
// anchor within header
|
|
self.active = header;
|
|
} else {
|
|
// anchor within content
|
|
self.active = current.closest( ".ui-accordion-content" ).prev();
|
|
}
|
|
}
|
|
}
|
|
|
|
self.active = self._findActive( self.active || options.active )
|
|
.addClass( "ui-state-default ui-state-active" )
|
|
.toggleClass( "ui-corner-all" )
|
|
.toggleClass( "ui-corner-top" );
|
|
self.active.next().addClass( "ui-accordion-content-active" );
|
|
|
|
self._createIcons();
|
|
self.resize();
|
|
|
|
// ARIA
|
|
self.element.attr( "role", "tablist" );
|
|
|
|
self.headers
|
|
.attr( "role", "tab" )
|
|
.bind( "keydown.accordion", function( event ) {
|
|
return self._keydown( event );
|
|
})
|
|
.next()
|
|
.attr( "role", "tabpanel" );
|
|
|
|
self.headers
|
|
.not( self.active || "" )
|
|
.attr({
|
|
"aria-expanded": "false",
|
|
"aria-selected": "false",
|
|
tabIndex: -1
|
|
})
|
|
.next()
|
|
.hide();
|
|
|
|
// make sure at least one header is in the tab order
|
|
if ( !self.active.length ) {
|
|
self.headers.eq( 0 ).attr( "tabIndex", 0 );
|
|
} else {
|
|
self.active
|
|
.attr({
|
|
"aria-expanded": "true",
|
|
"aria-selected": "true",
|
|
tabIndex: 0
|
|
});
|
|
}
|
|
|
|
// only need links in tab order for Safari
|
|
if ( !$.browser.safari ) {
|
|
self.headers.find( "a" ).attr( "tabIndex", -1 );
|
|
}
|
|
|
|
if ( options.event ) {
|
|
self.headers.bind( options.event.split(" ").join(".accordion ") + ".accordion", function(event) {
|
|
self._clickHandler.call( self, event, this );
|
|
event.preventDefault();
|
|
});
|
|
}
|
|
},
|
|
|
|
_createIcons: function() {
|
|
var options = this.options;
|
|
if ( options.icons ) {
|
|
$( "<span></span>" )
|
|
.addClass( "ui-icon " + options.icons.header )
|
|
.prependTo( this.headers );
|
|
this.active.children( ".ui-icon" )
|
|
.toggleClass(options.icons.header)
|
|
.toggleClass(options.icons.headerSelected);
|
|
this.element.addClass( "ui-accordion-icons" );
|
|
}
|
|
},
|
|
|
|
_destroyIcons: function() {
|
|
this.headers.children( ".ui-icon" ).remove();
|
|
this.element.removeClass( "ui-accordion-icons" );
|
|
},
|
|
|
|
destroy: function() {
|
|
var options = this.options;
|
|
|
|
this.element
|
|
.removeClass( "ui-accordion ui-widget ui-helper-reset" )
|
|
.removeAttr( "role" );
|
|
|
|
this.headers
|
|
.unbind( ".accordion" )
|
|
.removeClass( "ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
|
|
.removeAttr( "role" )
|
|
.removeAttr( "aria-expanded" )
|
|
.removeAttr( "aria-selected" )
|
|
.removeAttr( "tabIndex" );
|
|
|
|
this.headers.find( "a" ).removeAttr( "tabIndex" );
|
|
this._destroyIcons();
|
|
var contents = this.headers.next()
|
|
.css( "display", "" )
|
|
.removeAttr( "role" )
|
|
.removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled" );
|
|
if ( options.autoHeight || options.fillHeight ) {
|
|
contents.css( "height", "" );
|
|
}
|
|
|
|
return $.Widget.prototype.destroy.call( this );
|
|
},
|
|
|
|
_setOption: function( key, value ) {
|
|
$.Widget.prototype._setOption.apply( this, arguments );
|
|
|
|
if ( key == "active" ) {
|
|
this.activate( value );
|
|
}
|
|
if ( key == "icons" ) {
|
|
this._destroyIcons();
|
|
if ( value ) {
|
|
this._createIcons();
|
|
}
|
|
}
|
|
// #5332 - opacity doesn't cascade to positioned elements in IE
|
|
// so we need to add the disabled class to the headers and panels
|
|
if ( key == "disabled" ) {
|
|
this.headers.add(this.headers.next())
|
|
[ value ? "addClass" : "removeClass" ](
|
|
"ui-accordion-disabled ui-state-disabled" );
|
|
}
|
|
},
|
|
|
|
_keydown: function( event ) {
|
|
if ( this.options.disabled || event.altKey || event.ctrlKey ) {
|
|
return;
|
|
}
|
|
|
|
var keyCode = $.ui.keyCode,
|
|
length = this.headers.length,
|
|
currentIndex = this.headers.index( event.target ),
|
|
toFocus = false;
|
|
|
|
switch ( event.keyCode ) {
|
|
case keyCode.RIGHT:
|
|
case keyCode.DOWN:
|
|
toFocus = this.headers[ ( currentIndex + 1 ) % length ];
|
|
break;
|
|
case keyCode.LEFT:
|
|
case keyCode.UP:
|
|
toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
|
|
break;
|
|
case keyCode.SPACE:
|
|
case keyCode.ENTER:
|
|
this._clickHandler( { target: event.target }, event.target );
|
|
event.preventDefault();
|
|
}
|
|
|
|
if ( toFocus ) {
|
|
$( event.target ).attr( "tabIndex", -1 );
|
|
$( toFocus ).attr( "tabIndex", 0 );
|
|
toFocus.focus();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
resize: function() {
|
|
var options = this.options,
|
|
maxHeight;
|
|
|
|
if ( options.fillSpace ) {
|
|
if ( $.browser.msie ) {
|
|
var defOverflow = this.element.parent().css( "overflow" );
|
|
this.element.parent().css( "overflow", "hidden");
|
|
}
|
|
maxHeight = this.element.parent().height();
|
|
if ($.browser.msie) {
|
|
this.element.parent().css( "overflow", defOverflow );
|
|
}
|
|
|
|
this.headers.each(function() {
|
|
maxHeight -= $( this ).outerHeight( true );
|
|
});
|
|
|
|
this.headers.next()
|
|
.each(function() {
|
|
$( this ).height( Math.max( 0, maxHeight -
|
|
$( this ).innerHeight() + $( this ).height() ) );
|
|
})
|
|
.css( "overflow", "auto" );
|
|
} else if ( options.autoHeight ) {
|
|
maxHeight = 0;
|
|
this.headers.next()
|
|
.each(function() {
|
|
maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
|
|
})
|
|
.height( maxHeight );
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
activate: function( index ) {
|
|
// TODO this gets called on init, changing the option without an explicit call for that
|
|
this.options.active = index;
|
|
// call clickHandler with custom event
|
|
var active = this._findActive( index )[ 0 ];
|
|
this._clickHandler( { target: active }, active );
|
|
|
|
return this;
|
|
},
|
|
|
|
_findActive: function( selector ) {
|
|
return selector
|
|
? typeof selector === "number"
|
|
? this.headers.filter( ":eq(" + selector + ")" )
|
|
: this.headers.not( this.headers.not( selector ) )
|
|
: selector === false
|
|
? $( [] )
|
|
: this.headers.filter( ":eq(0)" );
|
|
},
|
|
|
|
// TODO isn't event.target enough? why the separate target argument?
|
|
_clickHandler: function( event, target ) {
|
|
var options = this.options;
|
|
if ( options.disabled ) {
|
|
return;
|
|
}
|
|
|
|
// called only when using activate(false) to close all parts programmatically
|
|
if ( !event.target ) {
|
|
if ( !options.collapsible ) {
|
|
return;
|
|
}
|
|
this.active
|
|
.removeClass( "ui-state-active ui-corner-top" )
|
|
.addClass( "ui-state-default ui-corner-all" )
|
|
.children( ".ui-icon" )
|
|
.removeClass( options.icons.headerSelected )
|
|
.addClass( options.icons.header );
|
|
this.active.next().addClass( "ui-accordion-content-active" );
|
|
var toHide = this.active.next(),
|
|
data = {
|
|
options: options,
|
|
newHeader: $( [] ),
|
|
oldHeader: options.active,
|
|
newContent: $( [] ),
|
|
oldContent: toHide
|
|
},
|
|
toShow = ( this.active = $( [] ) );
|
|
this._toggle( toShow, toHide, data );
|
|
return;
|
|
}
|
|
|
|
// get the click target
|
|
var clicked = $( event.currentTarget || target ),
|
|
clickedIsActive = clicked[0] === this.active[0];
|
|
|
|
// TODO the option is changed, is that correct?
|
|
// TODO if it is correct, shouldn't that happen after determining that the click is valid?
|
|
options.active = options.collapsible && clickedIsActive ?
|
|
false :
|
|
this.headers.index( clicked );
|
|
|
|
// if animations are still active, or the active header is the target, ignore click
|
|
if ( this.running || ( !options.collapsible && clickedIsActive ) ) {
|
|
return;
|
|
}
|
|
|
|
// find elements to show and hide
|
|
var active = this.active,
|
|
toShow = clicked.next(),
|
|
toHide = this.active.next(),
|
|
data = {
|
|
options: options,
|
|
newHeader: clickedIsActive && options.collapsible ? $([]) : clicked,
|
|
oldHeader: this.active,
|
|
newContent: clickedIsActive && options.collapsible ? $([]) : toShow,
|
|
oldContent: toHide
|
|
},
|
|
down = this.headers.index( this.active[0] ) > this.headers.index( clicked[0] );
|
|
|
|
// when the call to ._toggle() comes after the class changes
|
|
// it causes a very odd bug in IE 8 (see #6720)
|
|
this.active = clickedIsActive ? $([]) : clicked;
|
|
this._toggle( toShow, toHide, data, clickedIsActive, down );
|
|
|
|
// switch classes
|
|
active
|
|
.removeClass( "ui-state-active ui-corner-top" )
|
|
.addClass( "ui-state-default ui-corner-all" )
|
|
.children( ".ui-icon" )
|
|
.removeClass( options.icons.headerSelected )
|
|
.addClass( options.icons.header );
|
|
if ( !clickedIsActive ) {
|
|
clicked
|
|
.removeClass( "ui-state-default ui-corner-all" )
|
|
.addClass( "ui-state-active ui-corner-top" )
|
|
.children( ".ui-icon" )
|
|
.removeClass( options.icons.header )
|
|
.addClass( options.icons.headerSelected );
|
|
clicked
|
|
.next()
|
|
.addClass( "ui-accordion-content-active" );
|
|
}
|
|
|
|
return;
|
|
},
|
|
|
|
_toggle: function( toShow, toHide, data, clickedIsActive, down ) {
|
|
var self = this,
|
|
options = self.options;
|
|
|
|
self.toShow = toShow;
|
|
self.toHide = toHide;
|
|
self.data = data;
|
|
|
|
var complete = function() {
|
|
if ( !self ) {
|
|
return;
|
|
}
|
|
return self._completed.apply( self, arguments );
|
|
};
|
|
|
|
// trigger changestart event
|
|
self._trigger( "changestart", null, self.data );
|
|
|
|
// count elements to animate
|
|
self.running = toHide.size() === 0 ? toShow.size() : toHide.size();
|
|
|
|
if ( options.animated ) {
|
|
var animOptions = {};
|
|
|
|
if ( options.collapsible && clickedIsActive ) {
|
|
animOptions = {
|
|
toShow: $( [] ),
|
|
toHide: toHide,
|
|
complete: complete,
|
|
down: down,
|
|
autoHeight: options.autoHeight || options.fillSpace
|
|
};
|
|
} else {
|
|
animOptions = {
|
|
toShow: toShow,
|
|
toHide: toHide,
|
|
complete: complete,
|
|
down: down,
|
|
autoHeight: options.autoHeight || options.fillSpace
|
|
};
|
|
}
|
|
|
|
if ( !options.proxied ) {
|
|
options.proxied = options.animated;
|
|
}
|
|
|
|
if ( !options.proxiedDuration ) {
|
|
options.proxiedDuration = options.duration;
|
|
}
|
|
|
|
options.animated = $.isFunction( options.proxied ) ?
|
|
options.proxied( animOptions ) :
|
|
options.proxied;
|
|
|
|
options.duration = $.isFunction( options.proxiedDuration ) ?
|
|
options.proxiedDuration( animOptions ) :
|
|
options.proxiedDuration;
|
|
|
|
var animations = $.ui.accordion.animations,
|
|
duration = options.duration,
|
|
easing = options.animated;
|
|
|
|
if ( easing && !animations[ easing ] && !$.easing[ easing ] ) {
|
|
easing = "slide";
|
|
}
|
|
if ( !animations[ easing ] ) {
|
|
animations[ easing ] = function( options ) {
|
|
this.slide( options, {
|
|
easing: easing,
|
|
duration: duration || 700
|
|
});
|
|
};
|
|
}
|
|
|
|
animations[ easing ]( animOptions );
|
|
} else {
|
|
if ( options.collapsible && clickedIsActive ) {
|
|
toShow.toggle();
|
|
} else {
|
|
toHide.hide();
|
|
toShow.show();
|
|
}
|
|
|
|
complete( true );
|
|
}
|
|
|
|
// TODO assert that the blur and focus triggers are really necessary, remove otherwise
|
|
toHide.prev()
|
|
.attr({
|
|
"aria-expanded": "false",
|
|
"aria-selected": "false",
|
|
tabIndex: -1
|
|
})
|
|
.blur();
|
|
toShow.prev()
|
|
.attr({
|
|
"aria-expanded": "true",
|
|
"aria-selected": "true",
|
|
tabIndex: 0
|
|
})
|
|
.focus();
|
|
},
|
|
|
|
_completed: function( cancel ) {
|
|
this.running = cancel ? 0 : --this.running;
|
|
if ( this.running ) {
|
|
return;
|
|
}
|
|
|
|
if ( this.options.clearStyle ) {
|
|
this.toShow.add( this.toHide ).css({
|
|
height: "",
|
|
overflow: ""
|
|
});
|
|
}
|
|
|
|
// other classes are removed before the animation; this one needs to stay until completed
|
|
this.toHide.removeClass( "ui-accordion-content-active" );
|
|
// Work around for rendering bug in IE (#5421)
|
|
if ( this.toHide.length ) {
|
|
this.toHide.parent()[0].className = this.toHide.parent()[0].className;
|
|
}
|
|
|
|
this._trigger( "change", null, this.data );
|
|
}
|
|
});
|
|
|
|
$.extend( $.ui.accordion, {
|
|
version: "@VERSION",
|
|
animations: {
|
|
slide: function( options, additions ) {
|
|
options = $.extend({
|
|
easing: "swing",
|
|
duration: 300
|
|
}, options, additions );
|
|
if ( !options.toHide.size() ) {
|
|
options.toShow.animate({
|
|
height: "show",
|
|
paddingTop: "show",
|
|
paddingBottom: "show"
|
|
}, options );
|
|
return;
|
|
}
|
|
if ( !options.toShow.size() ) {
|
|
options.toHide.animate({
|
|
height: "hide",
|
|
paddingTop: "hide",
|
|
paddingBottom: "hide"
|
|
}, options );
|
|
return;
|
|
}
|
|
var overflow = options.toShow.css( "overflow" ),
|
|
percentDone = 0,
|
|
showProps = {},
|
|
hideProps = {},
|
|
fxAttrs = [ "height", "paddingTop", "paddingBottom" ],
|
|
originalWidth;
|
|
// fix width before calculating height of hidden element
|
|
var s = options.toShow;
|
|
originalWidth = s[0].style.width;
|
|
s.width( s.parent().width()
|
|
- parseFloat( s.css( "paddingLeft" ) )
|
|
- parseFloat( s.css( "paddingRight" ) )
|
|
- ( parseFloat( s.css( "borderLeftWidth" ) ) || 0 )
|
|
- ( parseFloat( s.css( "borderRightWidth" ) ) || 0 ) );
|
|
|
|
$.each( fxAttrs, function( i, prop ) {
|
|
hideProps[ prop ] = "hide";
|
|
|
|
var parts = ( "" + $.css( options.toShow[0], prop ) ).match( /^([\d+-.]+)(.*)$/ );
|
|
showProps[ prop ] = {
|
|
value: parts[ 1 ],
|
|
unit: parts[ 2 ] || "px"
|
|
};
|
|
});
|
|
options.toShow.css({ height: 0, overflow: "hidden" }).show();
|
|
options.toHide
|
|
.filter( ":hidden" )
|
|
.each( options.complete )
|
|
.end()
|
|
.filter( ":visible" )
|
|
.animate( hideProps, {
|
|
step: function( now, settings ) {
|
|
// only calculate the percent when animating height
|
|
// IE gets very inconsistent results when animating elements
|
|
// with small values, which is common for padding
|
|
if ( settings.prop == "height" ) {
|
|
percentDone = ( settings.end - settings.start === 0 ) ? 0 :
|
|
( settings.now - settings.start ) / ( settings.end - settings.start );
|
|
}
|
|
|
|
options.toShow[ 0 ].style[ settings.prop ] =
|
|
( percentDone * showProps[ settings.prop ].value )
|
|
+ showProps[ settings.prop ].unit;
|
|
},
|
|
duration: options.duration,
|
|
easing: options.easing,
|
|
complete: function() {
|
|
if ( !options.autoHeight ) {
|
|
options.toShow.css( "height", "" );
|
|
}
|
|
options.toShow.css({
|
|
width: originalWidth,
|
|
overflow: overflow
|
|
});
|
|
options.complete();
|
|
}
|
|
});
|
|
},
|
|
bounceslide: function( options ) {
|
|
this.slide( options, {
|
|
easing: options.down ? "easeOutBounce" : "swing",
|
|
duration: options.down ? 1000 : 200
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
})( jQuery );
|