﻿/*
*   Name:           jquery.ryaccordion.2.5.js
Purpose:        Add accordion functionality to a collection of elements
By:             Corey Dutson
    
Changelog:

v2.5 - Corey Dutson
----
-fixed accordion hast tag issue (for reals this time)

V2.4 - Corey Dutson
----
- fixed a glaring opera scroll bug.

V2.3 - Corey Dutson
----
- fixed a hashtag issue with IE
- fixed handle-height close-y bug

V2.2 - Corey Dutson
----
- Removed code that was assigning IDs to the container element and children. No need for the IDs.
- Added code to add classes to the children of the container element that would show position

V2.1 - Corey Dutson
----
- Changed the deeplink functionality to address a rare but serious bug involving hash tags.
    
V2.0 - Corey Dutson
----
- Re-written with new plugin layout.
- Exposed functions: Create, Update, Destroy, and TogglePanel. 
- Additional error handling.
- If a section does not have a matching handle, it will be skipped and so will not be given any accordion functionality.
- Collapsed height no longer adds the handles margin when collapsing.
*/

(function ($) {
    jQuery.fn.ryaccordion = function (options, i) {
        if (this.length > 1) {
            var a = new Array();
            this.each(
                function (i) {
                    a.push($(this).ryaccordion(options, i));
                });
            return a;
        }
        var opts = $.extend({}, $().ryaccordion.defaults, options);
        var scrollElement = $('html,body');
        // Public functions

        this.Destroy = function (reInit) {
            var container = this;
            var reInit = (reInit != undefined) ? reInit : false;
            if (opts.beforeDestroyFunction != null && $.isFunction(opts.beforeDestroyFunction))
                opts.beforeDestroyFunction($(container), opts, reInit);

            $('html, body').stop();
            $(container).removeData('ryAccordion').removeClass(opts.containerClass);
            $(container).children(opts.contentElement).each(function (index) {
                var child = this;
                var $handle = $(child).children(opts.handle + ':first');
                if ($handle.size() > 0) {

                    $(child).removeData('rHeight').removeData('handleHeight').stop().removeClass(opts.openClass + ' ' + opts.contentClass).removeClass(opts.contentClass + '-' + (index + 1));
                    $(child).attr('style', $(child).data('origStyle'));

                    $handle.unbind('click.ryAccordion.onHandleClick');
                    $(child).find('a').each(function (index) {
                        $(this).unbind('click.ryAccordion.openSectionViaHash');
                    });
                }
            });

            if (opts.afterDestroyFunction != null && $.isFunction(opts.afterDestroyFunction))
                opts.afterDestroyFunction($(container), opts, reInit);
        }; // end destroy function

        this.Update = function (options) {
            opts = null;
            opts = $.extend({}, $().ryaccordion.defaults, options);
            this.Destroy(true);
            return this.Create();
        }

        //add classes
        // add click events
        this.Create = function (iteration) {
            var maxHeight = null;
            var adjustment = null;
            var container = this;

            if ($(container).data('ryAccordion') == true)
                return this;
            if (opts.beforeCreateFunction != null && $.isFunction(opts.beforeCreateFunction))
                opts.beforeCreateFunction($(container), opts);

            $(container).data('ryAccordion', true);

            $(container).addClass(opts.containerClass);
            $(container).children(opts.contentElement).each(function (index) {
                var child = this;
                var $handle = $(child).children(opts.handle + ':first');
                if ($handle.size() > 0) {
                    $(child).addClass(opts.contentClass).addClass(opts.contentClass + '-' + (index + 1))
                    $(child).data('origStyle', $(child).attr('style'));

                    if ($(child).outerHeight(true) > maxHeight)
                        maxHeight = $(child).outerHeight(true);

                    // can't use $.data here for some reason.
                    $(child).data('rHeight', ($(child).outerHeight(true)));
                    $(child).data('handleHeight', $handle.outerHeight());
                    $(child).css({ overflow: 'hidden', position: 'relative' });

                    if (opts.openAllOnStart) {
                        $(child).addClass(opts.openClass);
                        adjustment = $(child).outerHeight(true);
                    }
                    else if (opts.startAsOpen == true && index + 1 == (opts.startItem) && document.location.hash + '' == '') {
                        $(child).addClass(opts.openClass);
                        adjustment = $(child).outerHeight(true);
                        $(child).css({ height: ($(child).outerHeight(true) + $handle.outerHeight()) });
                    }
                    else
                        $(child).css({ height: $handle.outerHeight() });
                    $handle.bind('click.ryAccordion.onHandleClick', onHandleClick);
                }
            });


            // Add deeplink functionality to kid links
            $(container).children('.' + opts.contentClass).each(function (index) {
                var $child = $(this);
                var $handle = $child.children(opts.handle + ':first');

                $child.find('a').each(function (index) {
                    var anchor = this;
                    // hash isnt empty AND is for the same page

                    if (anchor.hash + '' != '' && anchor.hash.length > 1) {
                        // if this object exists in the accordion
                        if (container.find('#' + anchor.hash.slice(1)).size() > 0) {
                            $(anchor).bind('click.ryAccordion.openSectionViaHash', function () {
                                openSection(getSectionIdByHash(anchor.hash, container));
                                return false;
                            });
                        }
                    }
                });
            });

            if (maxHeight != null && opts.maintainHeight == true)
                $(container).height(maxHeight + $(container).height() - adjustment).css({ overflow: 'auto' });

            // Animate bug fix: http://www.zachstronaut.com/posts/2009/01/18/jquery-smooth-scroll-bugs.html - update 3
            $('html, body').each(function () {
                var initScrollTop = $(this).attr('scrollTop');
                $(this).attr('scrollTop', initScrollTop + 1);
                if ($(this).attr('scrollTop') == initScrollTop + 1) {
                    scrollElement = this.nodeName.toLowerCase();
                    $(this).attr('scrollTop', initScrollTop);
                    return false;
                }
            });

            // open tab based on hash.
            if (document.location.hash + '' != '' && document.location.hash.length > 1)
                openSection(getSectionIdByHash(document.location.hash, container));
            if (opts.afterCreateFunction != null && $.isFunction(opts.afterCreateFunction))
                opts.afterCreateFunction($(container), opts);

            return this;
        }; // end of create function

        this.TogglePanel = function (sectionNumber) {
            openSectionByNumber($(this), sectionNumber, opts);
        }

        //Private functions

        // worker function for getting the proper hash to jump to (right now it goes for the container)
        function getSectionIdByHash(hashValue, container) {
            if (hashValue + '' == '') return null;

            var returnVal = null;
            // if this object exists in the accordion
            if ($(container).find('#' + hashValue.slice(1)).size() > 0) {
                if ($(container).children('#' + hashValue.slice(1)).size() == 0)
                    returnVal = $('#' + hashValue.slice(1)).closest('.' + opts.contentClass).attr('id');
                else
                    returnVal = hashValue.slice(1);
            }
            return returnVal;
        }

        function openSectionByNumber(container, sectionNumber, opts) {
            togglePanel($(container).children(opts.contentElement + ':nth-child(' + sectionNumber + ')'));
        }

        // id should be container id, not handle id
        function openSection(id) {
            togglePanel($('#' + id));
        }

        //wrapper for handle clicks
        function onHandleClick(e) {
            togglePanel($(this).parent());
            return false;
        }

        // Worker function for toggling a panel (and it's siblings)
        function togglePanel(targetSection) {
            if (targetSection == null || !targetSection.hasClass(opts.contentClass))
                return false;
            var handle = $(targetSection).children(opts.handle + ':first');

            if ($(targetSection).hasClass(opts.openClass)) {
                if (opts.beforeToggleFunction != null && $.isFunction(opts.beforeToggleFunction))
                    opts.beforeToggleFunction($(targetSection), opts, 'closing');
            }
            else {
                if (opts.beforeToggleFunction != null && $.isFunction(opts.beforeToggleFunction))
                    opts.beforeToggleFunction($(targetSection), opts, 'opening');
            }

            $(targetSection).stop(true, true);

            // close section if it's open...
            if ($(targetSection).hasClass(opts.openClass)) {

                $(targetSection).animate({ height: $(targetSection).data('handleHeight') }, { easing: opts.easingMethod, duration: opts.speed, complete: function () {
                    if (opts.afterToggleFunction != null && $.isFunction(opts.afterToggleFunction))
                        opts.afterToggleFunction($(targetSection), opts, 'closed');
                }
                });
            } else { // ... otherwise, open section
                $(targetSection).animate({ height: $(targetSection).data('rHeight') + $(targetSection).data('handleHeight') }, { duration: opts.speed, easing: opts.easingMethod, complete: function () {
                    if (opts.scrollToContent) {

                        $(scrollElement).stop().animate({ scrollTop: $(targetSection).offset().top - opts.scrollOffset }, opts.scrollSpeed);
                        if (opts.afterToggleFunction != null && $.isFunction(opts.afterToggleFunction))
                            opts.afterToggleFunction($(targetSection), opts, 'opened');
                    }
                }
                });
            }

            if (!opts.enableMulti && !opts.openAllOnStart) {
                $(targetSection).siblings('.' + opts.openClass).each(function () {
                    var section = this;
                    $(section).animate(
                  { height: $(this).data('handleHeight') },
                  { easing: opts.easingMethod, duration: opts.speed }).removeClass(opts.openClass);
                });
            }
            $(targetSection).toggleClass(opts.openClass);
        };

        // Finally
        return this.Create(i);
    };

    jQuery.fn.ryaccordion.defaults = {
        speed: 'slow',
        handle: 'h3',
        enableMulti: false,
        maintainHeight: false,
        scrollToContent: true,
        scrollSpeed: 'slow',
        scrollOffset: 100,
        startAsOpen: false,
        startItem: 1,
        easingMethod: null,
        openAllOnStart: false,
        contentElement: 'div',
        containerClass: 'ry_accordion',
        contentClass: 'accordion_content',
        openClass: 'expanded',
        beforeToggleFunction: null,
        afterToggleFunction: null,
        beforeCreateFunction: null,
        afterCreateFunction: null,
        beforeDestroyFunction: null,
        afterDestroyFunction: null
    };
})(jQuery);
