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