/* 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-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); } };