| | 1 | /* |
| | 2 | * Accordion 1.5 - jQuery menu widget |
| | 3 | * |
| | 4 | * Copyright (c) 2007 Jörn Zaefferer, Frank Marcia |
| | 5 | * |
| | 6 | * http://bassistance.de/jquery-plugins/jquery-plugin-accordion/ |
| | 7 | * |
| | 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 |
| | 11 | * |
| | 12 | * Revision: $Id: jquery.accordion.js 2880 2007-08-24 21:44:37Z joern.zaefferer $ |
| | 13 | * |
| | 14 | */ |
| | 15 | |
| | 16 | /** |
| | 17 | * Make the selected elements Accordion widgets. |
| | 18 | * |
| | 19 | * Semantic requirements: |
| | 20 | * |
| | 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 |
| | 24 | * all. |
| | 25 | * |
| | 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. |
| | 29 | * |
| | 30 | * Use activate(Number) to change the active content programmatically. |
| | 31 | * |
| | 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 |
| | 35 | * |
| | 36 | * @example jQuery('#nav').Accordion(); |
| | 37 | * @before <dl id="nav"> |
| | 38 | * <dt>Header 1</dt> |
| | 39 | * <dd>Content 1</dd> |
| | 40 | * <dt>Header 2</dt> |
| | 41 | * <dd>Content 2</dd> |
| | 42 | * </dl> |
| | 43 | * @desc Creates an Accordion from the given definition list |
| | 44 | * |
| | 45 | * @example jQuery('#nav').Accordion({ |
| | 46 | * header: '.title' |
| | 47 | * }); |
| | 48 | * @before <div id="nav"> |
| | 49 | * <div> |
| | 50 | * <div class="title">Header 1</div> |
| | 51 | * <div>Content 1</div> |
| | 52 | * </div> |
| | 53 | * <div> |
| | 54 | * <div class="title">Header 2</div> |
| | 55 | * <div>Content 2</div> |
| | 56 | * </div> |
| | 57 | * </div> |
| | 58 | * @desc Creates an Accordion from the given div structure |
| | 59 | * |
| | 60 | * @example jQuery('#nav').Accordion({ |
| | 61 | * header: '.head', |
| | 62 | * navigation: true |
| | 63 | * }); |
| | 64 | * @before <ul id="nav"> |
| | 65 | * <li> |
| | 66 | * <a class="head" href="books/">Books</a> |
| | 67 | * <ul> |
| | 68 | * <li><a href="books/fantasy/">Fantasy</a></li> |
| | 69 | * <li><a href="books/programming/">Programming</a></li> |
| | 70 | * </ul> |
| | 71 | * </li> |
| | 72 | * <li> |
| | 73 | * <a class="head" href="movies/">Movies</a> |
| | 74 | * <ul> |
| | 75 | * <li><a href="movies/fantasy/">Fantasy</a></li> |
| | 76 | * <li><a href="movies/programming/">Programming</a></li> |
| | 77 | * </ul> |
| | 78 | * </li> |
| | 79 | * </ul> |
| | 80 | * @after <ul id="nav"> |
| | 81 | * <li> |
| | 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> |
| | 86 | * </ul> |
| | 87 | * </li> |
| | 88 | * <li> |
| | 89 | * <a class="head" href="">Movies</a> |
| | 90 | * <ul> |
| | 91 | * <li><a class="current" href="movies/fantasy/">Fantasy</a></li> |
| | 92 | * <li><a href="movies/programming/">Programming</a></li> |
| | 93 | * </ul> |
| | 94 | * </li> |
| | 95 | * </ul> |
| | 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". |
| | 100 | * |
| | 101 | * @example jQuery('#accordion').Accordion().change(function(event, newHeader, oldHeader, newContent, oldContent) { |
| | 102 | * jQuery('#status').html(newHeader.text()); |
| | 103 | * }); |
| | 104 | * @desc Updates the element with id status with the text of the selected header every time the accordion changes |
| | 105 | * |
| | 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 |
| | 116 | * |
| | 117 | * @type jQuery |
| | 118 | * @see activate(Number) |
| | 119 | * @name Accordion |
| | 120 | * @cat Plugins/Accordion |
| | 121 | */ |
| | 122 | |
| | 123 | /** |
| | 124 | * Activate a content part of the Accordion programmatically. |
| | 125 | * |
| | 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). |
| | 128 | * |
| | 129 | * @example jQuery('#accordion').activate(1); |
| | 130 | * @desc Activate the second content of the Accordion contained in <div id="accordion">. |
| | 131 | * |
| | 132 | * @example jQuery('#accordion').activate("a:first"); |
| | 133 | * @desc Activate the first element matching the given expression. |
| | 134 | * |
| | 135 | * @example jQuery('#nav').activate(false); |
| | 136 | * @desc Close all content parts of the accordion. |
| | 137 | * |
| | 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. |
| | 140 | * |
| | 141 | * @type jQuery |
| | 142 | * @name activate |
| | 143 | * @cat Plugins/Accordion |
| | 144 | */ |
| | 145 | |
| | 146 | (function($) { |
| | 147 | |
| | 148 | jQuery.Accordion = {}; |
| | 149 | jQuery.extend(jQuery.Accordion, { |
| | 150 | defaults: { |
| | 151 | selectedClass: "selected", |
| | 152 | alwaysOpen: true, |
| | 153 | animated: 'slide', |
| | 154 | event: "click" |
| | 155 | }, |
| | 156 | Animations: { |
| | 157 | slide: function(settings, additions) { |
| | 158 | settings = jQuery.extend({ |
| | 159 | easing: "swing", |
| | 160 | duration: 300 |
| | 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 |
| | 167 | }); |
| | 168 | return; |
| | 169 | } |
| | 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"},{ |
| | 173 | step: function(n){ |
| | 174 | settings.toShow.height(Math.ceil(height - (jQuery.fn.stop ? n * height : n))); |
| | 175 | }, |
| | 176 | duration: settings.duration, |
| | 177 | easing: settings.easing, |
| | 178 | complete: settings.finished |
| | 179 | }); |
| | 180 | }, |
| | 181 | bounceslide: function(settings) { |
| | 182 | this.slide(settings, { |
| | 183 | easing: settings.down ? "bounceout" : "swing", |
| | 184 | duration: settings.down ? 1000 : 200 |
| | 185 | }); |
| | 186 | }, |
| | 187 | easeslide: function(settings) { |
| | 188 | this.slide(settings, { |
| | 189 | easing: "easeinout", |
| | 190 | duration: 700 |
| | 191 | }) |
| | 192 | } |
| | 193 | } |
| | 194 | }); |
| | 195 | |
| | 196 | jQuery.fn.extend({ |
| | 197 | nextUntil: function(expr) { |
| | 198 | var match = []; |
| | 199 | |
| | 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; |
| | 206 | |
| | 207 | // If we find a match then we need to stop |
| | 208 | if ( jQuery.filter( expr, [i] ).r.length ) break; |
| | 209 | |
| | 210 | // Otherwise, add it on to the stack |
| | 211 | match.push( i ); |
| | 212 | } |
| | 213 | }); |
| | 214 | |
| | 215 | return this.pushStack( match ); |
| | 216 | }, |
| | 217 | // the plugin method itself |
| | 218 | Accordion: function(settings) { |
| | 219 | if ( !this.length ) |
| | 220 | return this; |
| | 221 | |
| | 222 | // setup configuration |
| | 223 | settings = jQuery.extend({}, jQuery.Accordion.defaults, { |
| | 224 | // define context defaults |
| | 225 | header: $(':first-child', this)[0].tagName // take first childs tagName as header |
| | 226 | }, settings); |
| | 227 | |
| | 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; |
| | 233 | } else { |
| | 234 | settings.active = current.parent().parent().prev(); |
| | 235 | current.addClass("current"); |
| | 236 | } |
| | 237 | } |
| | 238 | } |
| | 239 | |
| | 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), |
| | 244 | running = 0; |
| | 245 | |
| | 246 | if ( settings.autoheight ) { |
| | 247 | var maxHeight = 0; |
| | 248 | headers.nextUntil(settings.header).each(function() { |
| | 249 | maxHeight = Math.max(maxHeight, $(this).height()); |
| | 250 | }).height(maxHeight); |
| | 251 | } |
| | 252 | |
| | 253 | headers |
| | 254 | .not(active || "") |
| | 255 | .nextUntil(settings.header) |
| | 256 | .hide(); |
| | 257 | active.addClass(settings.selectedClass); |
| | 258 | |
| | 259 | |
| | 260 | function findActive(selector) { |
| | 261 | return selector != undefined |
| | 262 | ? typeof selector == "number" |
| | 263 | ? headers.eq(selector) |
| | 264 | : headers.not(headers.not(selector)) |
| | 265 | : selector === false |
| | 266 | ? $("<div>") |
| | 267 | : headers.eq(0) |
| | 268 | } |
| | 269 | |
| | 270 | function toggle(toShow, toHide, data, clickedActive, down) { |
| | 271 | var finished = function(cancel) { |
| | 272 | running = cancel ? 0 : --running; |
| | 273 | if ( running ) |
| | 274 | return; |
| | 275 | // trigger custom change event |
| | 276 | container.trigger("change", data); |
| | 277 | }; |
| | 278 | |
| | 279 | // count elements to animate |
| | 280 | running = toHide.size() == 0 ? toShow.size() : toHide.size(); |
| | 281 | |
| | 282 | if ( settings.animated ) { |
| | 283 | if ( !settings.alwaysOpen && clickedActive ) { |
| | 284 | toShow.slideToggle(settings.animated); |
| | 285 | finished(true); |
| | 286 | } else { |
| | 287 | jQuery.Accordion.Animations[settings.animated]({ |
| | 288 | toShow: toShow, |
| | 289 | toHide: toHide, |
| | 290 | finished: finished, |
| | 291 | down: down |
| | 292 | }); |
| | 293 | } |
| | 294 | } else { |
| | 295 | if ( !settings.alwaysOpen && clickedActive ) { |
| | 296 | toShow.toggle(); |
| | 297 | } else { |
| | 298 | toHide.hide(); |
| | 299 | toShow.show(); |
| | 300 | } |
| | 301 | finished(true); |
| | 302 | } |
| | 303 | } |
| | 304 | |
| | 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 ); |
| | 312 | return; |
| | 313 | } |
| | 314 | // get the click target |
| | 315 | var clicked = $(event.target); |
| | 316 | |
| | 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(); |
| | 322 | |
| | 323 | var clickedActive = clicked[0] == active[0]; |
| | 324 | |
| | 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)) |
| | 327 | return; |
| | 328 | |
| | 329 | // switch classes |
| | 330 | active.toggleClass(settings.selectedClass); |
| | 331 | if ( !clickedActive ) { |
| | 332 | clicked.addClass(settings.selectedClass); |
| | 333 | } |
| | 334 | |
| | 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] ); |
| | 340 | |
| | 341 | active = clickedActive ? $([]) : clicked; |
| | 342 | toggle( toShow, toHide, data, clickedActive, down ); |
| | 343 | |
| | 344 | return !toShow.length; |
| | 345 | }; |
| | 346 | function activateHandler(event, index) { |
| | 347 | // IE manages to call activateHandler on normal clicks |
| | 348 | if ( arguments.length == 1 ) |
| | 349 | return; |
| | 350 | // call clickHandler with custom event |
| | 351 | clickHandler({ |
| | 352 | target: findActive(index)[0] |
| | 353 | }); |
| | 354 | }; |
| | 355 | |
| | 356 | return container |
| | 357 | .bind(settings.event, clickHandler) |
| | 358 | .bind("activate", activateHandler); |
| | 359 | }, |
| | 360 | activate: function(index) { |
| | 361 | return this.trigger('activate', [index]); |
| | 362 | } |
| | 363 | }); |
| | 364 | |
| | 365 | })(jQuery); |
| | 366 | No newline at end of file |