Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

            
              <h1>ARIA Tree Example</h1>
<p>
  This pen provides an example of an ARIA tree that includes a variety of different ARIA tree functions. It provides some basic support via the keyboard to demonstrate how various different page components should provide ARIA support and respond to keyboard commands.  It is not meant to provide perfect accessibility - just some example accessibility implementations you can play with.
</p>
<!-- Create a element for the tree -->
<h2 id="animals_label">Animals of the World</h2>
<!-- Host the overall tree in a unordered list -->
<ul role="tree" aria-labelledby="animals_label" id="animals_tree">
</ul>
<script>
</script>


<h2>References</h2>
<p>
  It is intended to demonstrate the following functions:
  <ul>
    <li><a href='http://accessibleculture.org/articles/2013/02/not-so-simple-aria-tree-views-and-screen-readers/'>(Not so) Simple ARIA Tree Views and Screen Readers</a> - Great overview of how screen readers and ARIA tres </li>
    <li><a href='http://www.oaa-accessibility.org/examplep/treeview1/'>Example Treeview</a> - An example implementation of a treeview control from the OpenAjax Alliance.</li>
    <li><a href='http://www.w3.org/TR/2015/WD-wai-aria-practices-1.1-20150514/#TreeView'>WAI-ARIA Authoring Practices 1.1 - Tree View</a></li>
  </ul>
</p>
            
          
!
            
              @CHARSET "ISO-8859-1";
/* Show and hide the CSS */

ul {
  list-style: none;
  margin-left: 0;
  padding-left: 0;
}

li {
  padding-left: 1em;
  text-indent: -1em;
}
/* The collapse state CSS psuedo before element */

li.collapsedList:before {
  content: "+";
  padding-right: 5px;
}
/* The expanded state CSS psuedo before element */

li.expandedList:before {
  content: "-";
  padding-right: 5px;
}
            
          
!
            
              /*
 * This file contains all the ARIA tree class files.
 */

/*
 * Tree base CTOR function
 */
var Tree = function(treeName) {
	this.treeName = treeName;
	this.treeNodeList = [];
	this.treeNodes = [];
	this.rootNodes = [];
	this.currentFocusedNodeIndex = 0;
}

/*
 * Add a node to the tree
 */
Tree.prototype.addNode = function(treeNode) {
	this.treeNodes[treeNode.nodeName] = treeNode;
}
/* Return the named node */
Tree.prototype.getNode = function(nodeName) {
	return this.treeNodes[treeNode.nodeName];
}

/*
 * Add a node to the tree
 */
Tree.prototype.addRootNode = function(treeNode) {
	this.rootNodes.push(treeNode);
}

/*
 * Show the tree
 * TODO: Add in root element rendering with label into JavaScript
 */
Tree.prototype.render = function() {
	//Get the base element of the tree
	var rootElementNode = document.getElementById(this.treeName);

	//Iterate through the root nodes
	for (var i = 0; i < this.rootNodes.length; i++){
		var currentRootNode = this.rootNodes[i];
		rootElementNode.appendChild(currentRootNode.listElement);
	}
	
	/* Set the first element to get keyboard focus to have tabindex = 0 */
	this.rootNodes[0].spanElement.setAttribute("tabindex", "0");

	this.currentFocusedNodeIndex = 0;
	this.buildNodeList();
}

Tree.prototype.buildNodeList = function() {
	for (var i = 0; i < this.rootNodes.length; i++){
		this.buildNodeListRecursive(this.rootNodes[i]);
    //Setup the the sibling count and locations
    this.rootNodes[i].setPosition(this.rootNodes.length, (i+1));    
	}
}

Tree.prototype.buildNodeListRecursive = function(currentNode) {
	//Add me
	this.treeNodeList.push(currentNode);
	//Add my children
	for (var i = 0; i < currentNode.children.length; i++){
		this.buildNodeListRecursive(currentNode.children[i]);
    //Setup the the sibling count and locations
    currentNode.children[i].setPosition(currentNode.children.length, (i+1));
	}
}

/* Move focus to the next focusable node in the tree */
Tree.prototype.moveFocusToNextNode = function() {
	
	if(this.currentFocusedNodeIndex == (this.treeNodeList.length -1)){
		console.log("Tree.moveFocusToNextNode: Can't move focus forward");		
	} else {
		var currentNode = this.treeNodeList[this.currentFocusedNodeIndex];
		this.currentFocusedNodeIndex++;
		var nextNode = this.treeNodeList[this.currentFocusedNodeIndex];

		//Make sure my parent is expanded...
		if(this.treeNodeList[this.currentFocusedNodeIndex].parentNode != null){
			this.treeNodeList[this.currentFocusedNodeIndex].parentNode.expand();
		}
		nextNode.setFocus(true);
		currentNode.setFocus(false);
		console.log("Tree.moveFocusToNextNode: Moving focus from " + currentNode + " to " + nextNode);		
	}
}

Tree.prototype.moveFocusToPreviousNode = function() {
	if(this.currentFocusedNodeIndex == 0){
		console.log("Tree.moveFocusToNextNode: At first node - can't move up");		
	} else {
		var currentNode = this.treeNodeList[this.currentFocusedNodeIndex];
		currentNode.setFocus(false);
		this.currentFocusedNodeIndex--;
		var nextNode = this.treeNodeList[this.currentFocusedNodeIndex];
		nextNode.setFocus(true);
		console.log("Tree.moveFocusToPreviousNode: Moving focus from " + currentNode + " to " + nextNode);		
	}	
}

Tree.prototype.moveFocusToFirstNode = function() {
	var currentNode = this.treeNodeList[this.currentFocusedNodeIndex];
	currentNode.setFocus(false);
	this.currentFocusedNodeIndex = 0;
	var nextNode = this.treeNodeList[this.currentFocusedNodeIndex];
	nextNode.setFocus(true);
	console.log("Tree.moveFocusToPreviousNode: Moving focus from " + currentNode + " to first node " + nextNode);		
}

Tree.prototype.moveFocusToLastNode = function() {
	var currentNode = this.treeNodeList[this.currentFocusedNodeIndex];
	currentNode.setFocus(false);
	this.currentFocusedNodeIndex = (this.treeNodeList.length - 1);
	var nextNode = this.treeNodeList[this.currentFocusedNodeIndex];
	nextNode.setFocus(true);
	console.log("Tree.moveFocusToPreviousNode: Moving focus from " + currentNode + " to last node " + nextNode);		
}

Tree.prototype.moveFocusToNode = function(targetNode) {
	var currentNode = this.treeNodeList[this.currentFocusedNodeIndex];
	currentNode.setFocus(false);
	this.currentFocusedNodeIndex = this.treeNodeList.indexOf(targetNode);
	targetNode.setFocus(true);
}

Tree.prototype.expandAll = function() {
	for (var i = 0; i < this.treeNodeList.length; i++){
		this.treeNodeList[i].expand();
	}
}

/* 
 * TreeNode Class
 * 
 * Provides the core functions for a node
 */
var TreeNode = function(nodeName, parentTree) {
	this.nodeName = nodeName;
	this.parentTree = parentTree;
	this.expanded = false;
	this.hasFocus = false;
	this.setupNode();
	this.parentTree.addNode(this);
	this.children = [];
	this.parentNode = null;
}

TreeNode.prototype.setupNode = function() {

	//Setup the list Element
	var listElement = document.createElement("LI");
	this.listElement = listElement;

	//Setup the span element - this handles the events
	var spanElement = document.createElement("SPAN");
	this.spanElement = spanElement;
	spanElement.addEventListener("click", this, false);
	//Give it a role of treeitem
	spanElement.setAttribute("role", "treeitem");
	//spanElement.addEventListener("keypress", this, false);
	//Use keyup - keypress doesn't work 
	spanElement.addEventListener("keydown", this, false);
	spanElement.addEventListener("focus", this, false);
	spanElement.addEventListener("blur", this, false);
	//Add in the actual text for this node
	spanElement.appendChild(document.createTextNode(this.nodeName));

	//Put the elements together
	listElement.appendChild(spanElement);
	this.setFocus(false);
}

/** Set that the element has focus or not */
TreeNode.prototype.setFocus = function(focusBoolean){
	this.hasFocus = focusBoolean;
	if(focusBoolean){
		this.spanElement.setAttribute("tabindex", "0");
		//Make sure I am expanded...
		this.expand();
		//make sure my parent is exposed
		if(this.parentNode != null) this.parentNode.expand();
		this.setVisualFocus();
	} else {		
		this.spanElement.setAttribute("tabindex", "-1");		
		this.clearVisualFocus();
	}
}

/** 
 * Clear the visual focus.  A corner case there we are dealing with is when we receive tab.
 */
TreeNode.prototype.setPosition = function(siblingCount, siblingLocation){
	this.spanElement.setAttribute("aria-setsize", siblingCount);
	this.spanElement.setAttribute("aria-posinset", siblingLocation);
}

/** 
 * Clear the visual focus.  A corner case there we are dealing with is when we receive tab.
 */
TreeNode.prototype.clearVisualFocus = function(){
	this.spanElement.setAttribute("aria-selected", "false");
	this.spanElement.style.outline = "0";
	//this.spanElement.style.border = null;
	this.spanElement.blur();		
}

/** 
 * Clear the visual focus.  A corner case there we are dealing with is when we receive tab.
 */
TreeNode.prototype.setVisualFocus = function(){
	this.spanElement.setAttribute("aria-selected", "true");		
	//Fun with focus indicators - you can use either of these - weird behavior if you don't override this
	//Ideally probably move this into your CSS class for span:active and span:focus
	this.spanElement.style.outline = "1px dashed black";
	//this.spanElement.style.border = "thick solid #0000FF";
	this.spanElement.focus();
}

TreeNode.prototype.handleEvent = function(e) {
	console.log("Event coming in: " + e + " type: " + e.type);
	switch (e.type) {
		case "click":
			this.toggleTree(this);
			break;
		case "keydown":
			this.handleKeyPress(e);
			break;
		case "blur":
			this.clearVisualFocus();
			break;
		case "focus":
			this.setVisualFocus();
			break;
	}
}

TreeNode.prototype.handleKeyPress = function(e) {
	console.log("Keyboard event: " + e.keyCode);
	
	switch (e.keyCode) {
	case 40:
		console.log(" Keyboard event: Arrow Down - move to the next node in the tree");
		this.parentTree.moveFocusToNextNode();
		e.stopPropagation();
		e.preventDefault();
		break;
	case 38:
		console.log(" Keyboard event: Arrow Up - move to the previous node in the tree");
		// Do something for "up arrow" key press.
		this.parentTree.moveFocusToPreviousNode();		
		e.stopPropagation();
		e.preventDefault();
		break;
	case 37:
		// Do something for "left arrow" key press.
		console.log(" Keyboard event: Arrow Left - close the current node");
		//If I am already collapsed move focus to my parent and collapse
		if(!this.expanded){
			if(this.parentNode != null){
				this.parentTree.moveFocusToNode(this.parentNode);
				this.parentNode.collapse();
			}
		} else {			
			this.collapse();
		}
		e.stopPropagation();
		e.preventDefault();
		break;
	case 39:
		// Do something for "right arrow" key press.
		console.log(" Keyboard event: Arrow Right - expand the current node");
		this.expand();
		e.stopPropagation();
		e.preventDefault();
		break;
	case 106:
		console.log("Keyboard event: * - expand all nodes");
		this.parentTree.expandAll();
		break;
	case 35:
		console.log("Keyboard event: End - go to last node");
		this.parentTree.moveFocusToLastNode();		
		e.stopPropagation();
		e.preventDefault();
		break;
	case 36:
		this.parentTree.moveFocusToFirstNode();		
		e.stopPropagation();
		e.preventDefault();
		console.log("Keyboard event: Home - move to the first visible node in the tree");
		break;
	case 13:
		// Do something for "enter" or "return" key press.
		console.log(" Keyboard event: Enter - if we had a default node behavior this would trigger");	    
		break;
	default:
		return; // Quit when this doesn't handle the key event.
	}
}



//Handle the case where we are adding children to the tree node
TreeNode.prototype.addChild = function(childNode) {
	if (this.childList == null) {
		this.childList = document.createElement("UL");
		this.listElement.appendChild(this.childList);
		this.childList.setAttribute("role", "group");
		this.collapse();
	}
	this.childList.appendChild(childNode.listElement);
	this.children.push(childNode);
	childNode.parentNode = this;
}

//Say if I am a leaf node or not
TreeNode.prototype.isLeaf = function() {
	return (this.childList == null);
}

//Doing this via JavaScript as otherwise the inheritance rules in CSS are a total mess.  This is due to the fact that a parent list might be expanded while a sub-list might be hidden

TreeNode.prototype.expand = function() {
	if(this.childList != null){
		this.listElement.className = 'expandedList';
		this.spanElement.setAttribute("aria-expanded", "true");
		this.spanElement.setAttribute("aria-hidden", "false");
		this.childList.style.display = "block";
		this.expanded = true;
	}
}

TreeNode.prototype.collapse = function() {
	if(this.childList != null){
		this.listElement.className = 'collapsedList';
		this.spanElement.setAttribute("aria-expanded", "false");
		this.spanElement.setAttribute("aria-hidden", "true");
		this.childList.style.display = "none";
		this.expanded = false;
	}
}

/* The toggle function */
TreeNode.prototype.toggleTree = function() {
	//Handle the hide case first
	if (this.expanded) {
		this.collapse();
	} else {
		this.expand();
	}
}

/* The toggle function */
TreeNode.prototype.toString = function() {
	return this.nodeName;
}

//Do the actual code activation here...

// Setup the actual tree
var animalsTree = new Tree("animals_tree");

//Setup the nodes
var jungleAnimals = new TreeNode("Jungle Animals", animalsTree);
jungleAnimals.addChild(new TreeNode("Lions", animalsTree));
jungleAnimals.addChild(new TreeNode("Antelopes", animalsTree));
jungleAnimals.addChild(new TreeNode("Zebras", animalsTree));
var oceanAnimals = new TreeNode("Ocean Animals", animalsTree);
oceanAnimals.addChild(new TreeNode("Tuna", animalsTree));
oceanAnimals.addChild(new TreeNode("Tiger Shark", animalsTree));
var childAnimals = new TreeNode("Reef Fish", animalsTree);
oceanAnimals.addChild(childAnimals);
childAnimals.addChild(new TreeNode("Angel", animalsTree));
childAnimals.addChild(new TreeNode("Blue Tang", animalsTree));
childAnimals.addChild(new TreeNode("Rock Beauty", animalsTree));
var plainsAnimals = new TreeNode("Plains Animals", animalsTree);
plainsAnimals.addChild(new TreeNode("Lions", animalsTree));
plainsAnimals.addChild(new TreeNode("Antelopes", animalsTree));
plainsAnimals.addChild(new TreeNode("Zebras", animalsTree));

animalsTree.addRootNode(jungleAnimals);
animalsTree.addRootNode(oceanAnimals);
animalsTree.addRootNode(plainsAnimals);
animalsTree.render();
            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.

Console