data tables are going to need some work yet, but the other stuff seems to be working 100%
357 lines
No EOL
8.5 KiB
JavaScript
357 lines
No EOL
8.5 KiB
JavaScript
(function () {
|
|
|
|
var Dom = YAHOO.util.Dom,
|
|
Event = YAHOO.util.Event,
|
|
UA = YAHOO.env.ua,
|
|
Lang = YAHOO.lang,
|
|
|
|
TabViewPrototype = YAHOO.widget.TabView.prototype,
|
|
fnTabViewInitAttributes = TabViewPrototype.initAttributes,
|
|
fnTabViewAddTab = TabViewPrototype.addTab,
|
|
|
|
m_bUseARIA = (UA.gecko && UA.gecko >= 1.9) || (UA.ie && UA.ie >= 8),
|
|
m_oTabListItems = {},
|
|
m_oTabIndexMap = {},
|
|
|
|
|
|
// Private constants for strings
|
|
|
|
_A = "A",
|
|
_ARIA_PREFIX = "aria-",
|
|
_USE_ARIA = "usearia",
|
|
_ELEMENT = "element",
|
|
_ACTIVE_TAB = "activeTab",
|
|
_ROLE = "role",
|
|
_PRESENTATION = "presentation",
|
|
_TAB = "tab",
|
|
_HREF = "href",
|
|
_CONTENT_EL = "contentEl",
|
|
_TAB_PANEL = "tabpanel",
|
|
_LABELLED_BY = "labelledby",
|
|
_DESCRIBED_BY = "describedby",
|
|
_ACTIVE_INDEX = "activeIndex",
|
|
_TAB_LIST = "tablist",
|
|
_KEY_PRESS = "keypress",
|
|
_KEY_DOWN = "keydown",
|
|
_ID = "id";
|
|
|
|
|
|
var setARIARole = function (element, role) {
|
|
|
|
element.setAttribute(_ROLE, role);
|
|
|
|
};
|
|
|
|
var setARIAProperty = function (element, property, value) {
|
|
|
|
element.setAttribute((_ARIA_PREFIX + property), value);
|
|
|
|
};
|
|
|
|
|
|
// Returns the <A> element representing each Tab in the TabView.
|
|
|
|
var getTabAnchor = function (element) {
|
|
|
|
var oTabAnchor;
|
|
|
|
if (Dom.getAncestorByClassName(element, this.TAB_PARENT_CLASSNAME)) {
|
|
|
|
if (element.nodeName.toUpperCase() === _A) {
|
|
oTabAnchor = element;
|
|
}
|
|
else {
|
|
oTabAnchor = Dom.getAncestorByTagName(element, _A);
|
|
}
|
|
|
|
}
|
|
|
|
return oTabAnchor;
|
|
|
|
};
|
|
|
|
|
|
var onKeyPress = function (event) {
|
|
|
|
var oTabAnchor = getTabAnchor.call(this, Event.getTarget(event)),
|
|
nCharCode = Event.getCharCode(event);
|
|
|
|
if (oTabAnchor && (nCharCode === 13 || nCharCode === 32) &&
|
|
(oTabAnchor.parentNode !== this.get(_ACTIVE_TAB).get(_ELEMENT))) {
|
|
|
|
this.set(_ACTIVE_INDEX, m_oTabIndexMap[this.get(_ID)][oTabAnchor.id]);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
var onKeyDown = function (event) {
|
|
|
|
var oCurrentTabAnchor = getTabAnchor.call(this, Event.getTarget(event)),
|
|
aTabListItems = m_oTabListItems[this.get(_ID)],
|
|
oCurrentTabLI,
|
|
oNextTabLI,
|
|
oNextTabAnchor,
|
|
bArrowKeyPressed = false;
|
|
|
|
|
|
if (oCurrentTabAnchor) {
|
|
|
|
oCurrentTabLI = oCurrentTabAnchor.parentNode;
|
|
|
|
switch (Event.getCharCode(event)) {
|
|
|
|
case 37: // Left
|
|
case 38: // Up
|
|
|
|
oNextTabLI = Dom.getPreviousSibling(oCurrentTabLI);
|
|
|
|
if (!oNextTabLI) {
|
|
oNextTabLI = aTabListItems[aTabListItems.length-1];
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
case 39: // Right
|
|
case 40: // Down
|
|
|
|
oNextTabLI = Dom.getNextSibling(oCurrentTabLI);
|
|
|
|
if (!oNextTabLI) {
|
|
oNextTabLI = aTabListItems[0];
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
if (oNextTabLI) {
|
|
|
|
oNextTabAnchor = Dom.getFirstChild(oNextTabLI);
|
|
|
|
oCurrentTabAnchor.tabIndex = -1;
|
|
oNextTabAnchor.tabIndex = 0;
|
|
|
|
oNextTabAnchor.focus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
Lang.augmentObject(TabViewPrototype, {
|
|
|
|
addTab: function (tab, index) {
|
|
|
|
fnTabViewAddTab.apply(this, arguments);
|
|
|
|
var sID = this.get(_ID),
|
|
aTabListItems = m_oTabListItems[sID],
|
|
oTabEl,
|
|
oTabAnchor,
|
|
oTabContentEl,
|
|
sTabId;
|
|
|
|
|
|
if (this.get(_USE_ARIA)) {
|
|
|
|
|
|
oTabEl = tab.get(_ELEMENT);
|
|
oTabAnchor = Dom.getFirstChild(oTabEl);
|
|
|
|
|
|
// Set the "tabIndex" attribute of each Tab's <A> element: The
|
|
// "tabIndex" of the active Tab's <A> element is set to 0, the others to -1.
|
|
// This improves the keyboard accessibility of the TabView by placing
|
|
// only one Tab in the browser's tab index by default, allowing the user
|
|
// to easily skip over the control when navigating the page with the tab key.
|
|
|
|
oTabAnchor.tabIndex = (this.get(_ACTIVE_TAB) === tab) ? 0 : -1;
|
|
|
|
|
|
// Create a map that links the ids of each Tab's <A> element to
|
|
// the Tab's "index" attribute to make it possible to retrieve a Tab
|
|
// instance reference by id.
|
|
|
|
sTabId = oTabAnchor.id || Dom.generateId(oTabAnchor);
|
|
|
|
m_oTabIndexMap[sID][sTabId] = this.getTabIndex(tab);
|
|
|
|
|
|
// Need to set the "role" attribute of each Tab's <LI> element to
|
|
// "presentation" so that Window-Eyes recognizes that each Tab belongs to
|
|
// the same TabList. Without this, Window-Eyes will announce each Tab as
|
|
// being "1 of 1" as opposed to "1 of 3," or "2 of 3".
|
|
|
|
setARIARole(oTabEl, _PRESENTATION);
|
|
|
|
setARIARole(oTabAnchor, _TAB);
|
|
|
|
|
|
|
|
// JAWS announces the value of the "href" attribute of each Tab's <A>
|
|
// element when it recieves focus. Ideally JAWS would allow the
|
|
// applied "role" attribute of "tab" to take precedence over the default
|
|
// role of the <A> element like NVDA and Window-Eyes do. It is
|
|
// possible to fix this problem by removing the "href" attribute from
|
|
// the <A>.
|
|
|
|
oTabAnchor.removeAttribute(_HREF);
|
|
|
|
|
|
oTabContentEl = tab.get(_CONTENT_EL);
|
|
|
|
|
|
aTabListItems[aTabListItems.length] = oTabContentEl;
|
|
|
|
|
|
setARIARole(oTabContentEl, _TAB_PANEL);
|
|
|
|
|
|
// Set the "aria-labelledby" attribute for the TabPanel <LI> element to
|
|
// the id of its corresponding Tab's <A> element. Doing so enables the
|
|
// screen reader to announce the label of the Tab for each TabPanel when
|
|
// the first element in a TabPanel receives focus, providing the user
|
|
// with some context as to where they are.
|
|
|
|
oTabContentEl.setAttribute(_LABELLED_BY, sTabId);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_setLabelledBy: function (id) {
|
|
|
|
var oTabList = Dom.getFirstChild(this.get(_ELEMENT));
|
|
|
|
if (this.get(_USE_ARIA) && oTabList) {
|
|
setARIAProperty(oTabList, _LABELLED_BY, id);
|
|
}
|
|
|
|
},
|
|
|
|
_setDescribedBy: function (id) {
|
|
|
|
var oTabList = Dom.getFirstChild(this.get(_ELEMENT));
|
|
|
|
if (this.get(_USE_ARIA) && oTabList) {
|
|
setARIAProperty(oTabList, _DESCRIBED_BY, id);
|
|
}
|
|
|
|
},
|
|
|
|
_setUseARIA: function (p_bUseARIA) {
|
|
|
|
var oTabList,
|
|
sID;
|
|
|
|
|
|
if (p_bUseARIA) {
|
|
|
|
sID = this.get(_ID);
|
|
|
|
if (!sID) {
|
|
this.set(_ID, Dom.generateId());
|
|
sID = this.get(_ID);
|
|
}
|
|
|
|
|
|
m_oTabListItems[sID] = [];
|
|
m_oTabIndexMap[sID] = {};
|
|
|
|
|
|
oTabList = Dom.getFirstChild(this.get(_ELEMENT));
|
|
|
|
|
|
// Set the "role" attribute of the <UL> encapsulating the Tabs to "tablist"
|
|
|
|
setARIARole(oTabList, _TAB_LIST);
|
|
|
|
|
|
// Add a keypress listener that toggles the active Tab instance when the user
|
|
// presses the Enter key. This is necessary because the removal of the "href"
|
|
// attribute from each Tab's <A> element (for JAWS support) causes the
|
|
// TabView's default Enter key support to stop working. Support for the Space
|
|
// Bar is also added as an additional convience for the user.
|
|
|
|
this.on(_KEY_PRESS, onKeyPress);
|
|
|
|
|
|
// Keydown event listener for the TabView that provides support for
|
|
// using the arrow keys to move focus between each Tab.
|
|
|
|
this.on(_KEY_DOWN, onKeyDown);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
initAttributes: function (p_oAttributes) {
|
|
|
|
/**
|
|
* @attribute usearia
|
|
* @description Boolean indicating if use of the WAI-ARIA Roles and States should
|
|
* be enabled.
|
|
* @type Boolean
|
|
* @default true for Firefox 3 and IE 8, false for all other browsers.
|
|
*/
|
|
this.setAttributeConfig(_USE_ARIA, {
|
|
|
|
value: (p_oAttributes.usearia || m_bUseARIA),
|
|
validator: Lang.isBoolean,
|
|
writeOnce: true,
|
|
method: this._setUseARIA
|
|
|
|
});
|
|
|
|
|
|
/**
|
|
* @attribute labelledby
|
|
* @description String representing the id of the element that labels the TabView.
|
|
* Maps directly to the <a href="http://www.w3.org/TR/wai-aria/#labelledby">
|
|
* <code>aria-labelledby</code></a> attribute.
|
|
* @type String
|
|
* @default null
|
|
*/
|
|
this.setAttributeConfig(_LABELLED_BY, {
|
|
|
|
value: p_oAttributes.labelledby,
|
|
validator: Lang.isString,
|
|
method: this._setLabelledBy
|
|
|
|
});
|
|
|
|
|
|
/**
|
|
* @attribute describedby
|
|
* @description String representing the id of the element that describes the TabView.
|
|
* Maps directly to the <a href="http://www.w3.org/TR/wai-aria/#describedby">
|
|
* <code>aria-describedby</code></a> attribute.
|
|
* @type String
|
|
* @default null
|
|
*/
|
|
this.setAttributeConfig(_DESCRIBED_BY, {
|
|
|
|
value: p_oAttributes.describedby,
|
|
validator: Lang.isString,
|
|
method: this._setDescribedBy
|
|
|
|
});
|
|
|
|
|
|
fnTabViewInitAttributes.call(this, p_oAttributes);
|
|
|
|
if (m_bUseARIA) {
|
|
this.set(_USE_ARIA, true);
|
|
}
|
|
|
|
}
|
|
|
|
}, "initAttributes", "_setUseARIA", "_setLabelledBy", "_setDescribedBy", "addTab");
|
|
|
|
}()); |