2 * Accordion 1.5 - jQuery menu widget
4 * Copyright (c) 2007 Jörn Zaefferer, Frank Marcia
6 * http://bassistance.de/jquery-plugins/jquery-plugin-accordion/
8 * Dual licensed under the MIT and GPL licenses:
9 * http://www.opensource.org/licenses/mit-license.php
10 * http://www.gnu.org/licenses/gpl.html
12 * Revision: $Id: jquery.accordion.js 2880 2007-08-24 21:44:37Z joern.zaefferer $
17 * Make the selected elements Accordion widgets.
19 * Semantic requirements:
21 * If the structure of your container is flat with unique
22 * tags for header and content elements, eg. a definition list
23 * (dl > dt + dd), you don't have to specify any options at
26 * If your structure uses the same elements for header and
27 * content or uses some kind of nested structure, you have to
28 * specify the header elements, eg. via class, see the second example.
30 * Use activate(Number) to change the active content programmatically.
32 * A change event is triggered everytime the accordion changes. Apart from
33 * the event object, all arguments are jQuery objects.
34 * Arguments: event, newHeader, oldHeader, newContent, oldContent
36 * @example jQuery('#nav').Accordion();
37 * @before <dl id="nav">
43 * @desc Creates an Accordion from the given definition list
45 * @example jQuery('#nav').Accordion({
48 * @before <div id="nav">
50 * <div class="title">Header 1</div>
51 * <div>Content 1</div>
54 * <div class="title">Header 2</div>
55 * <div>Content 2</div>
58 * @desc Creates an Accordion from the given div structure
60 * @example jQuery('#nav').Accordion({
64 * @before <ul id="nav">
66 * <a class="head" href="books/">Books</a>
68 * <li><a href="books/fantasy/">Fantasy</a></li>
69 * <li><a href="books/programming/">Programming</a></li>
73 * <a class="head" href="movies/">Movies</a>
75 * <li><a href="movies/fantasy/">Fantasy</a></li>
76 * <li><a href="movies/programming/">Programming</a></li>
80 * @after <ul id="nav">
82 * <a class="head" href="">Books</a>
83 * <ul style="display: none">
84 * <li><a href="books/fantasy/">Fantasy</a></li>
85 * <li><a href="books/programming/">Programming</a></li>
89 * <a class="head" href="">Movies</a>
91 * <li><a class="current" href="movies/fantasy/">Fantasy</a></li>
92 * <li><a href="movies/programming/">Programming</a></li>
96 * @desc Creates an Accordion from the given navigation list, activating those accordion parts
97 * that match the current location.href. Assuming the user clicked on "Fantasy" in the "Movies" section,
98 * the accordion displayed after loading the page with the "Movies" section open and the "Fantasy" link highlighted
99 * with a class "current".
101 * @example jQuery('#accordion').Accordion().change(function(event, newHeader, oldHeader, newContent, oldContent) {
102 * jQuery('#status').html(newHeader.text());
104 * @desc Updates the element with id status with the text of the selected header every time the accordion changes
106 * @param Map options key/value pairs of optional settings.
107 * @option String|Element|jQuery|Boolean|Number active Selector for the active element. Set to false to display none at start. Default: first child
108 * @option String|Element|jQuery header Selector for the header element, eg. 'div.title', 'a.head'. Default: first child's tagname
109 * @option String|Number speed
110 * @option String selectedClass Class for active header elements. Default: 'selected'
111 * @option Boolean alwaysOpen Whether there must be one content element open. Default: true
112 * @option Boolean|String animated Choose your favorite animation, or disable them (set to false). In addition to the default, "bounceslide" and "easeslide" are supported (both require the easing plugin). Default: 'slide'
113 * @option String event The event on which to trigger the accordion, eg. "mouseover". Default: "click"
114 * @option Boolean navigation If set, looks for the anchor that matches location.href and activates it. Great for href-based pseudo-state-saving. Default: false
115 * @option Boolean autoheight If set, the highest content part is used as height reference for all other parts. Provides more consistent animations. Default: false
118 * @see activate(Number)
120 * @cat Plugins/Accordion
124 * Activate a content part of the Accordion programmatically.
126 * The index can be a zero-indexed number to match the position of the header to close
127 * or a string expression matching an element. Pass -1 to close all (only possible with alwaysOpen:false).
129 * @example jQuery('#accordion').activate(1);
130 * @desc Activate the second content of the Accordion contained in <div id="accordion">.
132 * @example jQuery('#accordion').activate("a:first");
133 * @desc Activate the first element matching the given expression.
135 * @example jQuery('#nav').activate(false);
136 * @desc Close all content parts of the accordion.
138 * @param String|Element|jQuery|Boolean|Number index An Integer specifying the zero-based index of the content to be
139 * activated or an expression specifying the element, or an element/jQuery object, or a boolean false to close all.
143 * @cat Plugins/Accordion
149 $.extend($.Accordion, {
151 selectedClass: "selected",
157 slide: function(settings, additions) {
158 settings = $.extend({
161 }, settings, additions);
162 if ( !settings.toHide.size() ) {
163 settings.toShow.animate({height: "show"}, {
164 duration: settings.duration,
165 easing: settings.easing,
166 complete: settings.finished
170 var height = settings.toHide.height();
171 settings.toShow.css({ height: 0, overflow: 'hidden' }).show();
172 settings.toHide.filter(":hidden").each(settings.finished).end().filter(":visible").animate({height:"hide"},{
174 settings.toShow.height(Math.ceil(height - ($.fn.stop ? n * height : n)));
176 duration: settings.duration,
177 easing: settings.easing,
178 complete: settings.finished
181 bounceslide: function(settings) {
182 this.slide(settings, {
183 easing: settings.down ? "bounceout" : "swing",
184 duration: settings.down ? 1000 : 200
187 easeslide: function(settings) {
188 this.slide(settings, {
197 nextUntil: function(expr) {
200 // We need to figure out which elements to push onto the array
201 this.each(function(){
202 // Traverse through the sibling nodes
203 for( var i = this.nextSibling; i; i = i.nextSibling ) {
204 // Make sure that we're only dealing with elements
205 if ( i.nodeType != 1 ) continue;
207 // If we find a match then we need to stop
208 if ( $.filter( expr, [i] ).r.length ) break;
210 // Otherwise, add it on to the stack
215 return this.pushStack( match );
217 // the plugin method itself
218 Accordion: function(settings) {
222 // setup configuration
223 settings = $.extend({}, $.Accordion.defaults, {
224 // define context defaults
225 header: $(':first-child', this)[0].tagName // take first childs tagName as header
228 if ( settings.navigation ) {
229 var current = this.find("a").filter(function() { return this.href == location.href; });
230 if ( current.length ) {
231 if ( current.filter(settings.header).length ) {
232 settings.active = current;
234 settings.active = current.parent().parent().prev();
235 current.addClass("current");
240 // calculate active if not specified, using the first header
241 var container = this,
242 headers = container.find(settings.header),
243 active = findActive(settings.active),
246 if ( settings.autoheight ) {
248 headers.nextUntil(settings.header).each(function() {
249 maxHeight = Math.max(maxHeight, $(this).height());
250 }).height(maxHeight);
255 .nextUntil(settings.header)
257 active.addClass(settings.selectedClass);
260 function findActive(selector) {
261 return selector != undefined
262 ? typeof selector == "number"
263 ? headers.eq(selector)
264 : headers.not(headers.not(selector))
270 function toggle(toShow, toHide, data, clickedActive, down) {
271 var finished = function(cancel) {
272 running = cancel ? 0 : --running;
275 // trigger custom change event
276 container.trigger("change", data);
279 // count elements to animate
280 running = toHide.size() == 0 ? toShow.size() : toHide.size();
282 if ( settings.animated ) {
283 if ( !settings.alwaysOpen && clickedActive ) {
284 toShow.slideToggle(settings.animated);
287 $.Accordion.Animations[settings.animated]({
295 if ( !settings.alwaysOpen && clickedActive ) {
305 function clickHandler(event) {
306 // called only when using activate(false) to close all parts programmatically
307 if ( !event.target && !settings.alwaysOpen ) {
308 active.toggleClass(settings.selectedClass);
309 var toHide = active.nextUntil(settings.header);
310 var toShow = active = $([]);
311 toggle( toShow, toHide );
314 // get the click target
315 var clicked = $(event.target);
317 // due to the event delegation model, we have to check if one
318 // of the parent elements is our actual header, and find that
319 if ( clicked.parents(settings.header).length )
320 while ( !clicked.is(settings.header) )
321 clicked = clicked.parent();
323 var clickedActive = clicked[0] == active[0];
325 // if animations are still active, or the active header is the target, ignore click
326 if(running || (settings.alwaysOpen && clickedActive) || !clicked.is(settings.header))
330 active.toggleClass(settings.selectedClass);
331 if ( !clickedActive ) {
332 clicked.addClass(settings.selectedClass);
335 // find elements to show and hide
336 var toShow = clicked.nextUntil(settings.header),
337 toHide = active.nextUntil(settings.header),
338 data = [clicked, active, toShow, toHide],
339 down = headers.index( active[0] ) > headers.index( clicked[0] );
341 active = clickedActive ? $([]) : clicked;
342 toggle( toShow, toHide, data, clickedActive, down );
344 return !toShow.length;
346 function activateHandler(event, index) {
347 // IE manages to call activateHandler on normal clicks
348 if ( arguments.length == 1 )
350 // call clickHandler with custom event
352 target: findActive(index)[0]
357 .bind(settings.event, clickHandler)
358 .bind("activate", activateHandler);
360 activate: function(index) {
361 return this.trigger('activate', [index]);