AccordionView YUI Widget

accordionview 

AccordionView YUI Widget > accordionview > accordionview.js (source view)

(function() {

    /**
    *
    * By Marco van Hylckama Vlieg (marco@i-marco.nl)
    *
    * THIS IS A WORK IN PROGRESS
    *
    * Many, many thanks go out to Daniel Satyam Barreiro!
    * Please read his article about YUI widget development
    * http://yuiblog.com/blog/2008/06/24/buildingwidgets/
    * Without his excellent help and advice this widget would not
    * be half as good as it is now.
    */
    
    /**
    * The accordionview module provides a widget for managing content bound to an 'accordion'.
    * @module accordionview
    * @requires yahoo, dom, event, element, animation
    */
    
    var YUD = YAHOO.util.Dom, YUE = YAHOO.util.Event, YUA = YAHOO.util.Anim;
    
    /**
    * A widget to control accordion views.
    * @namespace YAHOO.widget
    * @class AccordionView
    * @extends YAHOO.util.Element
    * @constructor
    * @param {HTMLElement | String} el The id of the html element that represents the AccordionView. 
    * @param {Object} oAttr (optional) A key map of the AccordionView's 
    * initial oAttributes.  
    */

    var AccordionView = function(el, oAttr) {
        
        el = YUD.get(el);
        
        // some sensible defaults
        
        oAttr = oAttr || {};
        if(!el) {
            el = document.createElement(this.CONFIG.TAG_NAME);
        }
        if (el.id) {oAttr.id = el.id; }
        YAHOO.widget.AccordionView.superclass.constructor.call(this, el, oAttr); 

        this.initList(el, oAttr);
                
        // This refresh forces all defaults to be set
         
        this.refresh(['id', 'width','hoverActivated'],true);  
        
    };


    /**
     * @event panelClose
     * @description Fires before a panel closes.
     * See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
     * for more information on listening for this event.
     * @type YAHOO.util.CustomEvent
     */
    
    var panelCloseEvent = 'panelClose';

    /**
     * @event panelOpen
     * @description Fires before a panel opens.
     * See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
     * for more information on listening for this event.
     * @type YAHOO.util.CustomEvent
     */
    
    var panelOpenEvent = 'panelOpen';
    
    /**
     * @event afterPanelClose
     * @description Fires after a panel has finished closing.
     * See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
     * for more information on listening for this event.
     * @type YAHOO.util.CustomEvent
     */
    
    var afterPanelCloseEvent = 'afterPanelClose';
 
    /**
     * @event afterPanelOpen
     * @description Fires after a panel has finished opening.
     * See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
     * for more information on listening for this event.
     * @type YAHOO.util.CustomEvent
     */
    
    var afterPanelOpenEvent = 'afterPanelOpen';

    /**
     * @event stateChanged
     * @description Fires after the accordion has fully changed state (after opening and/or closing (a) panel(s)).
     * See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
     * for more information on listening for this event.
     * @type YAHOO.util.CustomEvent
     */
    
    var stateChangedEvent = 'stateChanged';
    
    /**
     * @event beforeStateChange
     * @description Fires before a state change.
     * This is useful to cancel an entire change operation
     * See <a href="YAHOO.util.Element.html#addListener">Element.addListener</a>
     * for more information on listening for this event.
     * @type YAHOO.util.CustomEvent
     */
    
    var beforeStateChangeEvent = 'beforeStateChange';

    YAHOO.widget.AccordionView = AccordionView;
    
    YAHOO.extend(AccordionView, YAHOO.util.Element, {
                
        /**
        * Initialize attributes for the Accordion
        * @param {Object} oAttr attributes key map
        * @method initAttributes
        */
        
        initAttributes: function (oAttr) {
            AccordionView.superclass.initAttributes.call(this, oAttr);
            var bAnimate = (YAHOO.env.modules.animation) ? true : false;    
            this.setAttributeConfig('id', {
                writeOnce: true,
                validator: function (value) {
                    return (/^[a-zA-Z][\w0-9\-_.:]*$/.test(value));
                },
                value: YUD.generateId(),
                method: function (value) {
                    this.get('element').id = value;
                }
            });
            this.setAttributeConfig('width', {
                value: '400px',
                method: function (value) {
                    this.setStyle('width', value);
                    }
                }
            );
            this.setAttributeConfig('animationSpeed', {
                value: 0.7
                }
            );
            this.setAttributeConfig('animate', {
                value: bAnimate,
                validator: YAHOO.lang.isBoolean
                }
            );          
            this.setAttributeConfig('collapsible', {
                value: false,
                validator: YAHOO.lang.isBoolean
                }
            );
            this.setAttributeConfig('expandable', {
                value: false,
                validator: YAHOO.lang.isBoolean
                }
            );
            this.setAttributeConfig('effect', {
                value: YAHOO.util.Easing.easeBoth,
                validator: YAHOO.lang.isString

                }
            );
            this.setAttributeConfig('hoverActivated', {
                    value: false,
                    validator: YAHOO.lang.isBoolean,
                    method: function (value) {
                            if (value) {
                                    YUE.on(this, 'mouseover', this._onMouseOver, this, true);                        
                            } else {
                                    YUE.removeListener(this, 'mouseover', this._onMouseOver);
                            }        
                    }
            });
            this.setAttributeConfig('_hoverTimeout', {
                value: 500,
                validator: YAHOO.lang.isInteger
                }
            );
        },
        
        /**
         * Configuration object containing tag names used in the AccordionView component.
         * See sourcecode for explanation in case you need to change this
         * @property CONFIG
         * @public
         * @type Object
         */
        
        CONFIG : {
          // tag name for the entire accordion
          TAG_NAME : 'UL',
          // tag name for the wrapper around a toggle + content pair
          ITEM_WRAPPER_TAG_NAME : 'LI',
          // tag name for the wrapper around the content for a panel
          CONTENT_WRAPPER_TAG_NAME : 'DIV'         
        },
 
        /**
         * Configuration object containing classes used in the AccordionView component.
         * See sourcecode for explanation in case you need to change this
         * @property CLASSES
         * @public
         * @type Object
         */
        
        CLASSES : {
            // the entire accordion
            ACCORDION : 'yui-accordionview',
            // the wrapper around a toggle + content pair
            PANEL : 'yui-accordion-panel',
            // the element that toggles a panel
            TOGGLE : 'yui-accordion-toggle',
            // the element that contains the content of a panel
            CONTENT : 'yui-accordion-content',
            // to indicate that a toggle is active
            ACTIVE : 'active',
            // to indicate that content is hidden
            HIDDEN : 'hidden',
            // the opened/closed indicator
            INDICATOR : 'indicator'                       
        },
        
        /**
        * Internal counter to make sure id's are unique
        * @property _idCounter
        * @private
        * @type Integer
        */
        
        _idCounter : '1',
        
        /**
        * Holds the timer for hover activated accordions
        * @property _hoverTimer
        * @private
        */
        
        _hoverTimer : null,      

        /**
        * Holds references to all accordion panels (list elements) in an array
        * @property _panels
        * @private
        * @type Array
        */

        _panels : null,
        
        /**
        * Keeps track of whether a panel is currently in the process of opening.
        * Used to time when a full change is finished (open and close panel)
        * @property _opening
        * @private
        * @type Boolean
        */        
        _opening : false,
        
        /**
        * Keeps track of whether a panel is currently in the process of closing.
        * Used to time when a full change is finished (open and close panel)
        * @property _closing
        * @private
        * @type Boolean
        */        
        
        _closing : false,
                
        /**
        * Whether we're running FF2 or older (or another derivate of Gecko < 1.9)
        * @property _ff2
        * @private
        * @type Boolean
        */
        
        _ff2 : (YAHOO.env.ua.gecko > 0 && YAHOO.env.ua.gecko < 1.9),

        /**
        * Whether we're running IE6 or IE7
        * @property _ie
        * @private
        * @type Boolean
        */

        _ie : (YAHOO.env.ua.ie < 8 && YAHOO.env.ua.ie > 0),

        /**
        * Whether we're ARIA capable (currently only IE8 ad FF3)
        * @property _ARIACapable
        * @private
        * @type Boolean
        */
        
        _ARIACapable : (YAHOO.env.ua.ie > 7 || YAHOO.env.ua.gecko >= 1.9),
        
        /**
        * Initialize the list / accordion
        * @param {HTMLElement} el The element for the accordion
        * @param {Object} oAttr attributes key map
        * @method initList
        * @public
        */

        initList : function(el, oAttr) {  
            YUD.addClass(el, this.CLASSES.ACCORDION);
            this._setARIA(el, 'role', 'tree');
            var aCollectedItems = [];
            var aListItems = el.getElementsByTagName(this.CONFIG.ITEM_WRAPPER_TAG_NAME);

            for(var i=0;i<aListItems.length;i++) {           
                if(YUD.hasClass(aListItems[i], 'nopanel')) {         
                    aCollectedItems.push({label: 'SINGLE_LINK', content: aListItems[i].innerHTML.replace(/^\s\s*/, '').replace(/\s\s*$/, '')});
                }
                else {
                    if(aListItems[i].parentNode === el) {
                        for (var eHeader = aListItems[i].firstChild; eHeader && eHeader.nodeType != 1; eHeader = eHeader.nextSibling) {
                        // This loop looks for the first non-textNode element
                        }
                        if (eHeader) {
                            for (var eContent = eHeader.nextSibling; eContent && eContent .nodeType != 1; eContent = eContent .nextSibling) {
                            // here we go for the second non-textNode element, if there was a first one
                            }
                        aCollectedItems.push({label: eHeader.innerHTML, content: (eContent && eContent.innerHTML)});
                        }
                    }
                }
            }
            el.innerHTML = '';
            if(aCollectedItems.length > 0) {
                this.addPanels(aCollectedItems);
            }
            
            if((oAttr.expandItem === 0) || (oAttr.expandItem > 0)) {                                   
                var eLink = this._panels[oAttr.expandItem].firstChild;
                var eContent = this._panels[oAttr.expandItem].firstChild.nextSibling;
                YUD.removeClass(eContent, this.CLASSES.HIDDEN);
                if(eLink && eContent) {
                    YUD.addClass(eLink, this.CLASSES.ACTIVE);
                    eLink.tabIndex = 0;
                    this._setARIA(eLink, 'aria-expanded', 'true');
                    this._setARIA(eContent, 'aria-hidden', 'false');
                }
            }
   
            this.initEvents();
        },
        
        /**
        * Attach all event listeners
        * @method initEvents
        * @public
        */
        
        initEvents : function() {
            
            if(true === this.get('hoverActivated')) {
                    this.on('mouseover', this._onMouseOver, this, true);        
                    this.on('mouseout', this._onMouseOut, this, true);         
            }
            
            this.on('click', this._onClick, this, true);
            this.on('keydown', this._onKeydown, this, true);
            
            // set this._opening and this._closing before open/close operations begin
            
            this.on('panelOpen', function(){this._opening = true;}, this, true);
            this.on('panelClose', function(){this._closing = true;}, this, true);
            
            // This makes sure that this._fixTabindexes is called after a change has
            // fully completed
            
            this.on('afterPanelClose', function(){
                this._closing = false;
                if(!this._closing && !this._opening) {
                    this._fixTabIndexes();
                }
            }, this, true);
            this.on('afterPanelOpen', function(){
                this._opening = false;
                if(!this._closing && !this._opening) {
                    this._fixTabIndexes();
                }
            }, this, true);
            
            /*
            This is needed when the hrefs are removed from links
            to be able to still hit enter to follow the link
            We only do this when we have ARIA support
            */
            
            if(this._ARIACapable) {
                this.on('keypress', function(ev){
                    var eCurrentPanel = YUD.getAncestorByClassName(YUE.getTarget(ev), this.CLASSES.PANEL);
                    var keyCode = YUE.getCharCode(ev);
                    if(keyCode === 13) {
                        this._onClick(eCurrentPanel.firstChild);
                        return false;
                    }
                });
            }    
        },
        
        
        /**
        * Wrapper around setAttribute to make sure we only set ARIA roles and states
        * in browsers that support it
        * @ethod _setARIA
        * @param {HTMLElement} el the element to set the attribute on
        * @param {String} sAttr the attribute name
        * @param {String} sValue the value for the attribute
        * @private
        */
        
        _setARIA : function(el, sAttr, sValue) {
            if(this._ARIACapable) {
                el.setAttribute(sAttr, sValue);
            }
        },
        
        /**
        * Closes all panels 
        * @method _collapseAccordion
        * @private
        */

        _collapseAccordion : function() {
            YUD.batch(this._panels, function(e) {
                var elContent = this.firstChild.nextSibling;
                if(elContent) { 
                    YUD.removeClass(e.firstChild, this.CLASSES.ACTIVE);
                    YUD.addClass(elContent, this.CLASSES.HIDDEN);
                    this._setARIA(elContent, 'aria-hidden', 'true');
                }
            }, this);
        },

        /**
        * Set tabIndex to 0 on the first item in case all panels are closed
        * or active. Otherwise set it to -1
        * @method _fixTabIndexes
        * @private
        */

        _fixTabIndexes : function() {
            var aLength = this._panels.length;
            var bAllClosed = true;
            for(var i=0;i<aLength;i++) {
                if(YUD.hasClass(this._panels[i].firstChild, this.CLASSES.ACTIVE)) {
                    this._panels[i].firstChild.tabIndex = 0;
                    bAllClosed = false;
                }
                else {
                    this._panels[i].firstChild.tabIndex = -1;
                }
            }
            if(bAllClosed) {
                this._panels[0].firstChild.tabIndex = 0;
            }
            // now everything is done so we can fire the stateChanged event
            this.fireEvent(stateChangedEvent);
        },

        /**
        * Adds an Accordion panel to the AccordionView instance.  
        * If no index is specified, the panel is added to the end of the tab list.
        * @method addPanel
        * @param {Object} oAttr A key map of the Panel's properties
        * @param {Integer} nIndex The position to add the tab. 
        */

        addPanel : function(oAttr, nIndex) {
            var oPanelParent = document.createElement(this.CONFIG.ITEM_WRAPPER_TAG_NAME);
            YUD.addClass(oPanelParent, this.CLASSES.PANEL);
            
            // single links that have no panel get class link and
            // no +/- indicator
            
            if(oAttr.label === 'SINGLE_LINK') {
                oPanelParent.innerHTML = oAttr.content;
                YUD.addClass(oPanelParent.firstChild, this.CLASSES.TOGGLE);
                YUD.addClass(oPanelParent.firstChild, 'link');
            }
            else {
                var elIndicator = document.createElement('span');
                YUD.addClass(elIndicator, this.CLASSES.INDICATOR);
                var elPanelLink = oPanelParent.appendChild(document.createElement('A'));
                elPanelLink.id = this.get('element').id + '-' + this._idCounter + '-label';               
                elPanelLink.innerHTML = oAttr.label || '';
                elPanelLink.appendChild(elIndicator);
                
                // if we use ARIA we remove the hrefs from links, UNLESS one has been
                // provided by the developer
                
                if(this._ARIACapable) {
                    if(oAttr.href) {
                        elPanelLink.href = oAttr.href;
                    }
                }
                else {
                    elPanelLink.href = oAttr.href || '#toggle';    
                }
                
                elPanelLink.tabIndex = -1;
                YUD.addClass(elPanelLink, this.CLASSES.TOGGLE);
                var elPanelContent = document.createElement(this.CONFIG.CONTENT_WRAPPER_TAG_NAME);
                elPanelContent.innerHTML = oAttr.content || '';
                YUD.addClass(elPanelContent, this.CLASSES.CONTENT);
                oPanelParent.appendChild(elPanelContent);
                this._setARIA(oPanelParent, 'role', 'presentation');
                this._setARIA(elPanelLink, 'role', 'treeitem');
                this._setARIA(elPanelContent, 'aria-labelledby', elPanelLink.id);
                this._setARIA(elIndicator, 'role', 'presentation');             
            }
            this._idCounter++;
            if(this._panels === null) {
                this._panels = [];
            }
            if((nIndex !== null) && (nIndex !== undefined)) {
                var ePanelBefore = this.getPanel(nIndex);
                this.insertBefore(oPanelParent, ePanelBefore);             
                var aNewPanels = this._panels.slice(0,nIndex);
                var aNewPanelsAfter = this._panels.slice(nIndex);
                aNewPanels.push(oPanelParent);
                for(i=0;i<aNewPanelsAfter.length;i++) {
                    aNewPanels.push(aNewPanelsAfter[i]);
                }
                this._panels = aNewPanels;               
            }
            else {
                this.appendChild(oPanelParent);
                if(this.get('element') === oPanelParent.parentNode) {
                    this._panels[this._panels.length] = oPanelParent;
                }
            }
            if(oAttr.label !== 'SINGLE_LINK') {
                if(oAttr.expand) {
                    if(!this.get('expandable')) {
                        this._collapseAccordion();
                    }
                    YUD.removeClass(elPanelContent, this.CLASSES.HIDDEN);
                    YUD.addClass(elPanelLink, this.CLASSES.ACTIVE);
                    this._setARIA(elPanelContent, 'aria-hidden', 'false');
                    this._setARIA(elPanelLink, 'aria-expanded', 'true');
                }
                else {
                    YUD.addClass(elPanelContent, 'hidden');
                    this._setARIA(elPanelContent, 'aria-hidden', 'true');
                    this._setARIA(elPanelLink, 'aria-expanded', 'false');
                }
            }
            var t= YAHOO.lang.later(0, this, function(){this._fixTabIndexes();this.fireEvent(stateChangedEvent);});
        },

        /**
        * Wrapper around addPanel to add multiple panels in one call
        * @method addPanels
        * @param {Array} oPanels array holding all individual panel configs
        */

        addPanels : function(oPanels) {
            for(var i=0;i<oPanels.length;i++) {
                this.addPanel(oPanels[i]);
            }
        },

        /**
        * Removes the specified Panel from the AccordionView.
        * @method removePanel
        * @param {Integer} index of the panel to be removed
        */

        removePanel : function(index) {
            this.removeChild(YUD.getElementsByClassName(this.CLASSES.PANEL, this.CONFIG.ITEM_WRAPPER_TAG_NAME, this)[index]); 
            var aNewPanels = []; 
            var nLength = this._panels.length;
            for(var i=0;i<nLength;i++) {
                if(i !== index) {
                    aNewPanels.push(this._panels[i]);
                }
            }
            this._panels = aNewPanels;
            var t= YAHOO.lang.later(0, this, function(){this._fixTabIndexes();this.fireEvent(stateChangedEvent);});
        },

        /**
        * Returns the HTMLElement of the panel at the specified index.
        * @method getPanel
        * @param {Integer} nIndex The position of the Panel.
        * @return {HTMLElement} the requested panel element
        */

        getPanel : function(nIndex) {
            return this._panels[nIndex];
        },

        /**
        * Returns the Array containing all panels
        * @method getPanels
        * @return {Array} An array with references to the panels in the correct order
        */

        getPanels : function() {
            return this._panels;
        },

        /**
        * Open a panel
        * @method openPanel
        * @param {Integer} nIndex The position of the Panel.
        * @return {Boolean} whether action resulted in opening a panel
        * that was previously closed
        */

        openPanel : function(nIndex) {
            var ePanelNode = this._panels[nIndex];
            if(!ePanelNode) {return false;} // invalid node
            if(YUD.hasClass(ePanelNode.firstChild, this.CLASSES.ACTIVE)) {return false;} // already open
            this._onClick(ePanelNode.firstChild);
            return true;
        },

        /**
        * Close a panel
        * @method closePanel
        * @param {Integer} nIndex The position of the Panel.
        * @return {Boolean} whether action resulted in closing a panel or not
        * that was previously open
        *
        * This method honors all constraints imposed by the properties collapsible and expandable
        * and will return false if the panel can't be closed because of a constraint in addition
        * to if it was already closed
        *
        */

        closePanel : function(nIndex) {
            var aItems = this._panels;
            var ePanelNode = aItems[nIndex];
            if(!ePanelNode) {return false;} // invalid node
            var ePanelLink = ePanelNode.firstChild;
            if(!YUD.hasClass(ePanelLink, this.CLASSES.ACTIVE)) {return true;} // already closed
            if(this.get('collapsible') === false) {
                if(this.get('expandable') === true) {
                    this.set('collapsible', true);
                    for(var i=0;i<aItems.length;i++) {
                        if((YUD.hasClass(aItems[i].firstChild, this.CLASSES.ACTIVE) && i !== nIndex)) {
                            this._onClick(ePanelLink);
                            this.set('collapsible', false);            
                            return true;
                        }
                    }
                    this.set('collapsible', false);                
                }
            } // can't collapse
            this._onClick(ePanelLink);
            return true;
        },

        /**
        * Keyboard event handler for keyboard control of the widget
        * @method _onKeydown
        * @param {Event} ev The Dom event
        * @private
        */

        _onKeydown : function(ev) {
            var eCurrentPanel = YUD.getAncestorByClassName(YUE.getTarget(ev), this.CLASSES.PANEL);
            var nKeyCode = YUE.getCharCode(ev);
            var nLength = this._panels.length;
            if(nKeyCode === 37 || nKeyCode === 38) {
                for(var i=0;i<nLength;i++) {
                    if((eCurrentPanel === this._panels[i]) && i>0) {
                        this._panels[i-1].firstChild.focus();
                        return;
                    } 
                }
            }
            if(nKeyCode === 39 || nKeyCode === 40) {
                for(var i=0;i<nLength;i++) {
                    if((eCurrentPanel === this._panels[i]) && i<nLength-1) {
                        this._panels[i+1].firstChild.focus();
                        return;
                    } 
                }
            }
        },

        /**
        * Mouseover event handler
        * @method _onMouseOver
        * @param {Event} ev The Dom event
        * @private
        */

        _onMouseOver : function(ev) {
            YUE.stopPropagation(ev);
            // must provide the TARGET or IE will destroy the event before we can
            // use it. Thanks Nicholas Zakas for pointing this out to me
            var target = YUE.getTarget(ev);
            this._hoverTimer = YAHOO.lang.later(this.get('_hoverTimeout'), this, function(){
                this._onClick(target);
             });
        },

        /**
        * Mouseout event handler
        * Cancels the timer set by AccordionView::_onMouseOver
        * @method _onMouseOut
        * @param {Event} ev The Dom event
        * @private
        */
        
        _onMouseOut : function() {
            if (this._hoverTimer) { 
                this._hoverTimer.cancel();
                this._hoverTimer = null;
            }
        },
        
        /**
        * Global event handler for mouse clicks
        * This method can accept both an event and a node so it can be called internally if needed
        * @method _onClick
        * @param {HTMLElement|Event} arg The Dom event or event target
        * @private
        */

        _onClick : function(arg) {
            var ev;
            if(arg.nodeType === undefined) {
                ev = YUE.getTarget(arg);
                if(!YUD.hasClass(ev, this.CLASSES.TOGGLE) && !YUD.hasClass(ev, this.CLASSES.INDICATOR)) {
                    return false;
                }
                if(YUD.hasClass(ev, 'link')) {
                    return true;
                }
                YUE.preventDefault(arg);
                YUE.stopPropagation(arg);
            }
            else {
                ev = arg;
            }

            var elClickedNode = ev;
            var that = this;
            /**
            *
            * helper function to fix IE problems with nested accordions
            * still looking for something better but for now this will have to do
            * @param {Object} el element to apply the fix to
            * @param {String} sHide whether to set visibility to hidden or visible
            *
            */

            function iehide(el, sHide) {
                if(that._ie) {
                    var aInnerAccordions = YUD.getElementsByClassName(that.CLASSES.ACCORDION, that.CONFIG.TAG_NAME, el);
                    if(aInnerAccordions[0]) {
                        YUD.setStyle(aInnerAccordions[0], 'visibility', sHide);
                    }
                }
            }

            /**
            *
            * Toggle an accordion panel
            * @param {Object} el element to toggle
            * @param {Object} elClicked the element that was clicked to toggle the corresponding panel
            *
            */

            function toggleItem(el, elClicked) {      
                var that = this;
                function fireEvent(type,panel) {
                    if (!YUD.hasClass(panel,that.CLASSES.PANEL)) {
                        panel = YUD.getAncestorByClassName(panel, that.CLASSES.PANEL);
                    }
                    for (var i = 0, p = panel; p.previousSibling; i++) {
                        p = p.previousSibling;
                    }
                    return that.fireEvent(type, {panel: panel, index: i});
                }
                      
                if(!elClicked) {
                    if(!el) { return false ;}
                    elClicked = el.parentNode.firstChild;
                }
                var oOptions = {};
                var nHeight = 0;

                var bHideAfter = (!YUD.hasClass(el, this.CLASSES.HIDDEN));

                if(this.get('animate')) {
                    if(!bHideAfter) {
                        // this eliminates a flash in Gecko < 1.9
                        if(this._ff2) {
                            YUD.addClass(el, 'almosthidden');
                            YUD.setStyle(el, 'width', this.get('width'));
                            }
                        YUD.removeClass(el, this.CLASSES.HIDDEN);
                        nHeight = el.offsetHeight;
                        YUD.setStyle(el, 'height', 0);
                        if(this._ff2) {
                            YUD.removeClass(el, 'almosthidden');
                            YUD.setStyle(el, 'width', 'auto');
                            }
                        oOptions = {height: {from: 0, to: nHeight}};
                    }
                    else {
                        nHeight = el.offsetHeight;
                        oOptions = {height: {from: nHeight, to: 0}};
                    }
                    var nSpeed = (this.get('animationSpeed')) ? this.get('animationSpeed') : 0.5;
                    var sEffect = (this.get('effect')) ? this.get('effect') : YAHOO.util.Easing.easeBoth;
                    var oAnimator = new YUA(el, oOptions, nSpeed, sEffect);
                    if(bHideAfter) {
                        if (this.fireEvent(panelCloseEvent, el) === false) { return; }
                        YUD.removeClass(elClicked, that.CLASSES.ACTIVE);
                        elClicked.tabIndex = -1;
                        iehide(el, 'hidden');
                        that._setARIA(el, 'aria-hidden', 'true');
                        that._setARIA(elClicked, 'aria-expanded', 'false');
                        oAnimator.onComplete.subscribe(function(){
                            YUD.addClass(el, that.CLASSES.HIDDEN);
                            YUD.setStyle(el, 'height', 'auto');
                            fireEvent('afterPanelClose', el);
                        });
                    }
                    else {
                        if (fireEvent(panelOpenEvent, el) === false) { return; }
                        //changed from visible to hidden so it doesn't show up behind the parent accordion until after the animation
                        iehide(el, 'hidden');
                        oAnimator.onComplete.subscribe(function(){
                            YUD.setStyle(el, 'height', 'auto');
                            //Added to make the inner accordion visible again
                            iehide(el, 'visible');
                            that._setARIA(el, 'aria-hidden', 'false');
                            that._setARIA(elClicked, 'aria-expanded', 'true');
                            elClicked.tabIndex = 0;
                            fireEvent(afterPanelOpenEvent, el);
                        });                 
                        YUD.addClass(elClicked, this.CLASSES.ACTIVE);
                    }
                    oAnimator.animate();
                }
                else {
                    if(bHideAfter) {
                        if (fireEvent(panelCloseEvent, el) === false) { return; }
                        YUD.addClass(el, that.CLASSES.HIDDEN);
                        YUD.setStyle(el, 'height', 'auto');
                        YUD.removeClass(elClicked, that.CLASSES.ACTIVE);
                        that._setARIA(el, 'aria-hidden', 'true');
                        that._setARIA(elClicked, 'aria-expanded', 'false');
                        elClicked.tabIndex = -1;
                        fireEvent(afterPanelCloseEvent, el);
                    }
                    else {
                        if (fireEvent(panelOpenEvent, el) === false) { return; }
                        YUD.removeClass(el, that.CLASSES.HIDDEN);
                        YUD.setStyle(el, 'height', 'auto');
                        YUD.addClass(elClicked, that.CLASSES.ACTIVE);
                        that._setARIA(el, 'aria-hidden', 'false');
                        that._setARIA(elClicked, 'aria-expanded', 'true');
                        elClicked.tabIndex = 0;
                        fireEvent(afterPanelOpenEvent, el);
                    }
                }
                return true;
            }
            var eTargetListNode = (elClickedNode.nodeName.toUpperCase() === 'SPAN') ? elClickedNode.parentNode.parentNode : elClickedNode.parentNode;

            var containedPanel = YUD.getElementsByClassName(this.CLASSES.CONTENT, this.CONFIG.CONTENT_WRAPPER_TAG_NAME, eTargetListNode)[0]; 
            if (this.fireEvent(beforeStateChangeEvent, this) === false) { return; }
            if(this.get('collapsible') === false) {
                if (!YUD.hasClass(containedPanel, this.CLASSES.HIDDEN)) {
                    return false;
                }
            }
            else {
                if(!YUD.hasClass(containedPanel, this.CLASSES.HIDDEN)) {
                    toggleItem.call(this, containedPanel);
                    return false;               
                }
            }
                    
            if(this.get('expandable') !== true) {
                var nLength = this._panels.length;
                for(var i=0; i<nLength; i++) {
                    var bMustToggle = YUD.hasClass(this._panels[i].firstChild.nextSibling, this.CLASSES.HIDDEN);
                    if(!bMustToggle) {
                        toggleItem.call(this,this._panels[i].firstChild.nextSibling);
                    }
                }
            }
            
            if(elClickedNode.nodeName.toUpperCase() === 'SPAN')  {
                toggleItem.call(this, containedPanel, elClickedNode.parentNode);
            }
            else {
                toggleItem.call(this, containedPanel, elClickedNode);
            }
            return true;
        },
        
        /**
        * Provides a readable name for the AccordionView instance.
        * @method toString
        * @return {String} String representation of the object 
        */
        toString : function() {
            var name = this.get('id') || this.get('tagName');
            return "AccordionView " + name; 
        }
    });    
})();
YAHOO.register("accordionview", YAHOO.widget.AccordionView, {version: "0.99", build: "33"});

Copyright © 2008 Marco van Hylckama Vlieg All rights reserved.