/* * jQuery UI Accordion @VERSION * * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. * * http://docs.jquery.com/UI/Accordion * * Depends: * ui.core.js */ (function($) { $.widget("ui.accordion", { _init: function() { var o = this.options, self = this; this.running = 0; // if the user set the alwaysOpen option on init // then we need to set the collapsible option // if they set both on init, collapsible will take priority if (o.collapsible == $.ui.accordion.defaults.collapsible && o.alwaysOpen != $.ui.accordion.defaults.alwaysOpen) { o.collapsible = !o.alwaysOpen; } if ( o.navigation ) { var current = this.element.find("a").filter(o.navigationFilter); if ( current.length ) { if ( current.filter(o.header).length ) { this.active = current; } else { this.active = current.parent().parent().prev(); current.addClass("ui-accordion-content-active"); } } } this.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 if (this.element[0].nodeName == "UL") { this.element.children("li").addClass("ui-accordion-li-fix"); } this.headers = this.element.find(o.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all") .bind("mouseenter.accordion", function(){ $(this).addClass('ui-state-hover'); }) .bind("mouseleave.accordion", function(){ $(this).removeClass('ui-state-hover'); }) .bind("focus.accordion", function(){ $(this).addClass('ui-state-focus'); }) .bind("blur.accordion", function(){ $(this).removeClass('ui-state-focus'); }); this.headers .next() .addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom"); this.active = this._findActive(this.active || o.active).toggleClass("ui-state-default").toggleClass("ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top"); this.active.next().addClass('ui-accordion-content-active'); //Append icon elements $("").addClass("ui-icon " + o.icons.header).prependTo(this.headers); this.active.find(".ui-icon").toggleClass(o.icons.header).toggleClass(o.icons.headerSelected); // IE7-/Win - Extra vertical space in lists fixed if ($.browser.msie) { this.element.find('a').css('zoom', '1'); } this.resize(); //ARIA this.element.attr('role','tablist'); this.headers .attr('role','tab') .bind('keydown', function(event) { return self._keydown(event); }) .next() .attr('role','tabpanel'); this.headers .not(this.active || "") .attr('aria-expanded','false') .attr("tabIndex", "-1") .next() .hide(); // make sure at least one header is in the tab order if (!this.active.length) { this.headers.eq(0).attr('tabIndex','0'); } else { this.active .attr('aria-expanded','true') .attr('tabIndex', '0'); } // only need links in taborder for Safari if (!$.browser.safari) this.headers.find('a').attr('tabIndex','-1'); if (o.event) { this.headers.bind((o.event) + ".accordion", function(event) { return self._clickHandler.call(self, event, this); }); } }, destroy: function() { var o = this.options; this.element .removeClass("ui-accordion ui-widget ui-helper-reset") .removeAttr("role") .unbind('.accordion') .removeData('accordion'); this.headers .unbind(".accordion") .removeClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-corner-top") .removeAttr("role").removeAttr("aria-expanded").removeAttr("tabindex"); this.headers.find("a").removeAttr("tabindex"); this.headers.children(".ui-icon").remove(); 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"); if (o.autoHeight || o.fillHeight) { contents.css("height", ""); } }, _setData: function(key, value) { if(key == 'alwaysOpen') { key = 'collapsible'; value = !value; } $.widget.prototype._setData.apply(this, arguments); }, _keydown: function(event) { var o = this.options, keyCode = $.ui.keyCode; if (o.disabled || event.altKey || event.ctrlKey) return; var length = this.headers.length; var currentIndex = this.headers.index(event.target); var 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: return this._clickHandler({ target: event.target }, event.target); } if (toFocus) { $(event.target).attr('tabIndex','-1'); $(toFocus).attr('tabIndex','0'); toFocus.focus(); return false; } return true; }, resize: function() { var o = this.options, maxHeight; if (o.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(); }); var maxPadding = 0; this.headers.next().each(function() { maxPadding = Math.max(maxPadding, $(this).innerHeight() - $(this).height()); }).height(Math.max(0, maxHeight - maxPadding)) .css('overflow', 'auto'); } else if ( o.autoHeight ) { maxHeight = 0; this.headers.next().each(function() { maxHeight = Math.max(maxHeight, $(this).outerHeight()); }).height(maxHeight); } }, activate: function(index) { // call clickHandler with custom event var active = this._findActive(index)[0]; this._clickHandler({ target: active }, active); }, _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)"); }, _clickHandler: function(event, target) { var o = this.options; if (o.disabled) return false; // called only when using activate(false) to close all parts programmatically if (!event.target && o.collapsible) { this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all") .find(".ui-icon").removeClass(o.icons.headerSelected).addClass(o.icons.header); this.active.next().addClass('ui-accordion-content-active'); var toHide = this.active.next(), data = { options: o, newHeader: $([]), oldHeader: o.active, newContent: $([]), oldContent: toHide }, toShow = (this.active = $([])); this._toggle(toShow, toHide, data); return false; } // get the click target var clicked = $(event.currentTarget || target); var clickedIsActive = clicked[0] == this.active[0]; // if animations are still active, or the active header is the target, ignore click if (this.running || (!o.collapsible && clickedIsActive)) { return false; } // switch classes this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all") .find(".ui-icon").removeClass(o.icons.headerSelected).addClass(o.icons.header); this.active.next().addClass('ui-accordion-content-active'); if (!clickedIsActive) { clicked.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top") .find(".ui-icon").removeClass(o.icons.header).addClass(o.icons.headerSelected); clicked.next().addClass('ui-accordion-content-active'); } // find elements to show and hide var toShow = clicked.next(), toHide = this.active.next(), data = { options: o, newHeader: clickedIsActive && o.collapsible ? $([]) : clicked, oldHeader: this.active, newContent: clickedIsActive && o.collapsible ? $([]) : toShow.find('> *'), oldContent: toHide.find('> *') }, down = this.headers.index( this.active[0] ) > this.headers.index( clicked[0] ); this.active = clickedIsActive ? $([]) : clicked; this._toggle(toShow, toHide, data, clickedIsActive, down); return false; }, _toggle: function(toShow, toHide, data, clickedIsActive, down) { var o = this.options, self = this; this.toShow = toShow; this.toHide = toHide; this.data = data; var complete = function() { if(!self) return; return self._completed.apply(self, arguments); }; // trigger changestart event this._trigger("changestart", null, this.data); // count elements to animate this.running = toHide.size() === 0 ? toShow.size() : toHide.size(); if (o.animated) { var animOptions = {}; if ( o.collapsible && clickedIsActive ) { animOptions = { toShow: $([]), toHide: toHide, complete: complete, down: down, autoHeight: o.autoHeight || o.fillSpace }; } else { animOptions = { toShow: toShow, toHide: toHide, complete: complete, down: down, autoHeight: o.autoHeight || o.fillSpace }; } if (!o.proxied) { o.proxied = o.animated; } if (!o.proxiedDuration) { o.proxiedDuration = o.duration; } o.animated = $.isFunction(o.proxied) ? o.proxied(animOptions) : o.proxied; o.duration = $.isFunction(o.proxiedDuration) ? o.proxiedDuration(animOptions) : o.proxiedDuration; var animations = $.ui.accordion.animations, duration = o.duration, easing = o.animated; if (!animations[easing]) { animations[easing] = function(options) { this.slide(options, { easing: easing, duration: duration || 700 }); }; } animations[easing](animOptions); } else { if (o.collapsible && clickedIsActive) { toShow.toggle(); } else { toHide.hide(); toShow.show(); } complete(true); } toHide.prev().attr('aria-expanded','false').attr("tabIndex", "-1").blur(); toShow.prev().attr('aria-expanded','true').attr("tabIndex", "0").focus(); }, _completed: function(cancel) { var o = this.options; this.running = cancel ? 0 : --this.running; if (this.running) return; if (o.clearStyle) { this.toShow.add(this.toHide).css({ height: "", overflow: "" }); } this._trigger('change', null, this.data); } }); $.extend($.ui.accordion, { version: "@VERSION", defaults: { active: null, alwaysOpen: true, //deprecated, use collapsible 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(); } }, animations: { slide: function(options, additions) { options = $.extend({ easing: "swing", duration: 300 }, options, additions); if ( !options.toHide.size() ) { options.toShow.animate({height: "show"}, options); return; } if ( !options.toShow.size() ) { options.toHide.animate({height: "hide"}, options); return; } var overflow = options.toShow.css('overflow'), percentDone, 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( parseInt(s.parent().width(),10) - parseInt(s.css("paddingLeft"),10) - parseInt(s.css("paddingRight"),10) - parseInt(s.css("borderLeftWidth"),10) - parseInt(s.css("borderRightWidth"),10) ); $.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.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); options.toShow.css({overflow: overflow}); options.complete(); } }); }, bounceslide: function(options) { this.slide(options, { easing: options.down ? "easeOutBounce" : "swing", duration: options.down ? 1000 : 200 }); }, easeslide: function(options) { this.slide(options, { easing: "easeinout", duration: 700 }); } } }); })(jQuery);