Source code

Revision control

Copy as Markdown

Other Tools

/* Aria Singleton */
var Aria = {
Trees: new Array(), // instances of Aria.Tree Class
isEnabled: function(inNode){
// todo: this may need to check isEnabled on all parentNodes, inheritence of aria-enabled is ambiguous
if(inNode.getAttribute('aria-enabled') && inNode.getAttribute('aria-enabled').toLowerCase()=='false') return false;
else return true;
},
isExpanded: function(inNode){
if(inNode.getAttribute('aria-expanded') && inNode.getAttribute('aria-expanded').toLowerCase()=='false') return false;
else return true;
},
isTreeItem: function(inNode){
if(inNode.getAttribute('role') && inNode.getAttribute('role').toLowerCase()=='treeitem') return true;
else return false;
}
};
Aria.Tree = Class.create();
Aria.Tree.prototype = {
initialize: function(inNode){
this.el = $(inNode);
this.index = Aria.Trees.length; // each tree should know its index in the Aria singleton's list, in order to concatenate id strings
this.strActiveDescendant = this.el.getAttribute('aria-activedescendant');
this.strDefaultActiveDescendant = 'tree'+this.index+'_item0'; // default first item
if(!$(this.strActiveDescendant)) this.strActiveDescendant = this.strDefaultActiveDescendant; // set to default if no existing activedescendant
this.setActiveDescendant($(this.strActiveDescendant));
// set up event delegation on the tree node
Event.observe(this.el, 'click', this.handleClick.bindAsEventListener(this));
Event.observe(this.el, 'keydown', this.handleKeyPress.bindAsEventListener(this)); //webkit doesn't send keypress events for arrow keys, so use keydown instead
},
getActiveDescendant: function(inNode){
if(inNode){ // if inNode (from event target), sets the activedescendant to nearest ancestor treeitem
var el = $(inNode);
while(el != this.el){
if(Aria.isTreeItem(el)) break; // exit the loop; we have the treeitem
el = el.parentNode;
}
if(el == this.el) {
this.setActiveDescendant(); // set to default activedescendant
} else {
this.setActiveDescendant(el);
return el;
}
} else {
return $(this.el.getAttribute('aria-activedescendant'));
}
},
getNextTreeItem: function(inNode){
var el = $(inNode);
var originalElm = $(inNode);
while(!Aria.isTreeItem(el) || el == originalElm){
if(Aria.isExpanded(el) && el.down()){ // should be el.down('[role="treeitem"]');
var elements = el.getElementsByTagName('*');
for(var i=0, c=elements.length; i<c; i++){
if(Aria.isTreeItem(elements[i])) return elements[i];
}
}
if(el.next()){
el = el.next();
} else {
while(!el.parentNode.next() && el.parentNode != this.el){
el = el.parentNode;
}
if(el.parentNode == this.el) return originalElm; // if no next items in tree, return current treeitem
else el = el.parentNode.next();
}
}
return el;
},
getPreviousTreeItem: function(inNode){
var el = $(inNode);
var originalElm = $(inNode);
while(!Aria.isTreeItem(el) || el == originalElm){
if(el.previous()){
el = el.previous();
// recursively choose last child node of previous el, as long as it's not in an collapsed node
if (el.down() && Aria.isExpanded(el)){
el = el.down();
while (el.next() || (el.down() && Aria.isExpanded(el))){
if (el.next()) el = el.next();
else el = el.down();
}
}
} else {
if(el.parentNode == this.el) return originalElm; // if no previous items in tree, return current treeitem
el = el.parentNode;
}
}
if(el == this.el) return originalElm; // if no previous items in tree, return current treeitem
return el;
},
handleClick: function(inEvent){
var target = inEvent.target; // get the click target
var el = this.getActiveDescendant(target);
if(target.className.indexOf('expander')>-1){ // if it's an expander widget
this.toggleExpanded(el); // toggle the aria-expanded attribute on activedescendant
Event.stop(inEvent); // and stop the event
}
},
handleKeyPress: function(inEvent){
switch(inEvent.keyCode){
// case Event.KEY_PAGEUP: break;
// case Event.KEY_PAGEDOWN: break;
// case Event.KEY_END: break;
// case Event.KEY_HOME: break;
case Event.KEY_LEFT: this.keyLeft(); break;
case Event.KEY_UP: this.keyUp(); break;
case Event.KEY_RIGHT: this.keyRight(); break;
case Event.KEY_DOWN: this.keyDown(); break;
default:
return;
}
Event.stop(inEvent);
},
keyLeft: function(){
var el = this.activeDescendant;
if(Aria.isExpanded(el)){
el.setAttribute('aria-expanded','false');
this.setActiveDescendant(this.activeDescendant);
}
},
keyUp: function(){
var el = this.activeDescendant;
this.setActiveDescendant(this.getPreviousTreeItem(el));
},
keyRight: function(){
var el = this.activeDescendant;
if(!Aria.isExpanded(el)){
el.setAttribute('aria-expanded','true');
this.setActiveDescendant(this.activeDescendant);
}
},
keyDown: function(){
var el = this.activeDescendant;
this.setActiveDescendant(this.getNextTreeItem(el));
},
setActiveDescendant: function(inNode){
Element.removeClassName(this.activeDescendant,'activedescendant')
if($(inNode)) this.activeDescendant = $(inNode);
else this.activeDescendant = $(this.strDefaultActiveDescendant);
Element.addClassName(this.activeDescendant,'activedescendant')
this.strActiveDescendant = this.activeDescendant.id;
this.el.setAttribute('aria-activedescendant', this.activeDescendant.id);
},
toggleExpanded: function(inNode){
var el = $(inNode);
if(Aria.isExpanded(el)){
el.setAttribute('aria-expanded','false');
} else {
el.setAttribute('aria-expanded','true');
}
this.setActiveDescendant(el);
}
};