Subversion Repositories SmartDukaan

Rev

Rev 162 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * jQuery UI Accordion 1.8.1
 *
 * Copyright (c) 2010 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:
 *      jquery.ui.core.js
 *      jquery.ui.widget.js
 */
(function($) {

$.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 o = this.options, self = this;
                this.running = 0;

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

                if ( o.navigation ) {
                        var current = this.element.find("a").filter(o.navigationFilter);
                        if ( current.length ) {
                                var header = current.closest(".ui-accordion-header");
                                if ( header.length ) {
                                        // anchor within header
                                        this.active = header;
                                } else {
                                        // anchor within content
                                        this.active = current.closest(".ui-accordion-content").prev();
                                }
                        }
                }

                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
                this._createIcons();

                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) {
                                self._clickHandler.call(self, event, this);
                                event.preventDefault();
                        });
                }

        },
        
        _createIcons: function() {
                var o = this.options;
                if (o.icons) {
                        $("<span/>").addClass("ui-icon " + o.icons.header).prependTo(this.headers);
                        this.active.find(".ui-icon").toggleClass(o.icons.header).toggleClass(o.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 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._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");
                if (o.autoHeight || o.fillHeight) {
                        contents.css("height", "");
                }

                return 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();
                        }
                }
                
        },

        _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:
                                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 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(true);
                        });

                        this.headers.next().each(function() {
                   $(this).height(Math.max(0, maxHeight - $(this).innerHeight() + $(this).height()));
                        }).css('overflow', 'auto');

                } else if ( o.autoHeight ) {
                        maxHeight = 0;
                        this.headers.next().each(function() {
                                maxHeight = Math.max(maxHeight, $(this).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 seperate target argument?
        _clickHandler: function(event, target) {

                var o = this.options;
                if (o.disabled)
                        return;

                // called only when using activate(false) to close all parts programmatically
                if (!event.target) {
                        if (!o.collapsible)
                                return;
                        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;
                }

                // get the click target
                var clicked = $(event.currentTarget || target);
                var 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?
                o.active = o.collapsible && clickedIsActive ? false : $('.ui-accordion-header', this.element).index(clicked);

                // if animations are still active, or the active header is the target, ignore click
                if (this.running || (!o.collapsible && clickedIsActive)) {
                        return;
                }

                // 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);
                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,
                                oldContent: toHide
                        },
                        down = this.headers.index( this.active[0] ) > this.headers.index( clicked[0] );

                this.active = clickedIsActive ? $([]) : clicked;
                this._toggle(toShow, toHide, data, clickedIsActive, down);

                return;

        },

        _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 (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 (o.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').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: ""
                        });
                }
                
                // other classes are removed before the animation; this one needs to stay until completed
                this.toHide.removeClass("ui-accordion-content-active");

                this._trigger('change', null, this.data);
        }

});


$.extend($.ui.accordion, {
        version: "1.8.1",
        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 = 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( parseInt(s.parent().width(),10) - parseInt(s.css("paddingLeft"),10) - parseInt(s.css("paddingRight"),10) - (parseInt(s.css("borderLeftWidth"),10) || 0) - (parseInt(s.css("borderRightWidth"),10) || 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);
                                        options.toShow.css({overflow: overflow});
                                        options.complete();
                                }
                        });
                },
                bounceslide: function(options) {
                        this.slide(options, {
                                easing: options.down ? "easeOutBounce" : "swing",
                                duration: options.down ? 1000 : 200
                        });
                }
        }
});

})(jQuery);