﻿/*
* jQuery UI Accordion 1.8.4
*
* Copyright 2010, 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 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",
			    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",
				    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("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;
            }

            // switch classes
            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);
            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");
            }

            // find elements to show and hide
            var 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]);

            this.active = clickedIsActive ? $([]) : clicked;
            this._toggle(toShow, toHide, data, clickedIsActive, down);

            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",
			    tabIndex: -1
			})
			.blur();
            toShow.prev()
			.attr({
			    "aria-expanded": "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");

            this._trigger("change", null, this.data);
        }
    });

    $.extend($.ui.accordion, {
        version: "1.8.4",
        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(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,
				            overflow: overflow
				        });
				        options.complete();
				    }
				});
            },
            bounceslide: function(options) {
                this.slide(options, {
                    easing: options.down ? "easeOutBounce" : "swing",
                    duration: options.down ? 1000 : 200
                });
            }
        }
    });

})(jQuery);


