text: /*
  *   This content is licensed according to the W3C Software License at
  *   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
  *
  *   File:   Tree.js
  *
  *   Desc:   Tree widget that implements ARIA Authoring Practices
  *           for a tree being used as a file viewer
  */
  
  /**
   * ARIA Treeview example
   * @function onload
   * @desc  after page has loaded initialize all treeitems based on the role=treeitem
   */
  
  window.addEventListener('load', function () {
  
    var trees = document.querySelectorAll('[role="tree"]');
  
    for (var i = 0; i < trees.length; i++) {
      var t = new Tree(trees[i]);
      t.init();
    }
  
  });
  
  /*
  *   @constructor
  *
  *   @desc
  *       Tree item object for representing the state and user interactions for a
  *       tree widget
  *
  *   @param node
  *       An element with the role=tree attribute
  */
  
  var Tree = function (node) {
    // Check whether node is a DOM element
    if (typeof node !== 'object') {
      return;
    }
  
    this.domNode = node;
  
    this.treeitems = [];
    this.firstChars = [];
  
    this.firstTreeitem = null;
    this.lastTreeitem = null;
  
  };
  
  Tree.prototype.init = function () {
  
    function findTreeitems (node, tree, group) {
  
      var elem = node.firstElementChild;
      var ti = group;
  
      while (elem) {
  
        if (elem.tagName.toLowerCase() === 'li') {
          ti = new Treeitem(elem, tree, group);
          ti.init();
          tree.treeitems.push(ti);
          tree.firstChars.push(ti.label.substring(0, 1).toLowerCase());
        }
  
        if (elem.firstElementChild) {
          findTreeitems(elem, tree, ti);
        }
  
        elem = elem.nextElementSibling;
      }
    }
  
    // initialize pop up menus
    if (!this.domNode.getAttribute('role')) {
      this.domNode.setAttribute('role', 'tree');
    }
  
    findTreeitems(this.domNode, this, false);
  
    this.updateVisibleTreeitems();
  
    this.firstTreeitem.domNode.tabIndex = 0;
  
  };
  
  Tree.prototype.setFocusToItem = function (treeitem) {
  
    for (var i = 0; i < this.treeitems.length; i++) {
      var ti = this.treeitems[i];
  
      if (ti === treeitem) {
        ti.domNode.tabIndex = 0;
        ti.domNode.focus();
      }
      else {
        ti.domNode.tabIndex = -1;
      }
    }
  
  };
  
  Tree.prototype.setFocusToNextItem = function (currentItem) {
  
    var nextItem = false;
  
    for (var i = (this.treeitems.length - 1); i >= 0; i--) {
      var ti = this.treeitems[i];
      if (ti === currentItem) {
        break;
      }
      if (ti.isVisible) {
        nextItem = ti;
      }
    }
  
    if (nextItem) {
      this.setFocusToItem(nextItem);
    }
  
  };
  
  Tree.prototype.setFocusToPreviousItem = function (currentItem) {
  
    var prevItem = false;
  
    for (var i = 0; i < this.treeitems.length; i++) {
      var ti = this.treeitems[i];
      if (ti === currentItem) {
        break;
      }
      if (ti.isVisible) {
        prevItem = ti;
      }
    }
  
    if (prevItem) {
      this.setFocusToItem(prevItem);
    }
  };
  
  Tree.prototype.setFocusToParentItem = function (currentItem) {
  
    if (currentItem.groupTreeitem) {
      this.setFocusToItem(currentItem.groupTreeitem);
    }
  };
  
  Tree.prototype.setFocusToFirstItem = function () {
    this.setFocusToItem(this.firstTreeitem);
  };
  
  Tree.prototype.setFocusToLastItem = function () {
    this.setFocusToItem(this.lastTreeitem);
  };
  
  Tree.prototype.expandTreeitem = function (currentItem) {
  
    if (currentItem.isExpandable) {
      currentItem.domNode.setAttribute('aria-expanded', true);
      this.updateVisibleTreeitems();
    }
  
  };
  
  Tree.prototype.expandAllSiblingItems = function (currentItem) {
    for (var i = 0; i < this.treeitems.length; i++) {
      var ti = this.treeitems[i];
  
      if ((ti.groupTreeitem === currentItem.groupTreeitem) && ti.isExpandable) {
        this.expandTreeitem(ti);
      }
    }
  
  };
  
  Tree.prototype.collapseTreeitem = function (currentItem) {
  
    var groupTreeitem = false;
  
    if (currentItem.isExpanded()) {
      groupTreeitem = currentItem;
    }
    else {
      groupTreeitem = currentItem.groupTreeitem;
    }
  
    if (groupTreeitem) {
      groupTreeitem.domNode.setAttribute('aria-expanded', false);
      this.updateVisibleTreeitems();
      this.setFocusToItem(groupTreeitem);
    }
  
  };
  
  Tree.prototype.updateVisibleTreeitems = function () {
  
    this.firstTreeitem = this.treeitems[0];
  
    for (var i = 0; i < this.treeitems.length; i++) {
      var ti = this.treeitems[i];
  
      var parent = ti.domNode.parentNode;
  
      ti.isVisible = true;
  
      while (parent && (parent !== this.domNode)) {
  
        if (parent.getAttribute('aria-expanded') == 'false') {
          ti.isVisible = false;
        }
        parent = parent.parentNode;
      }
  
      if (ti.isVisible) {
        this.lastTreeitem = ti;
      }
    }
  
  };
  
  Tree.prototype.setFocusByFirstCharacter = function (currentItem, char) {
    var start, index, char = char.toLowerCase();
  
    // Get start index for search based on position of currentItem
    start = this.treeitems.indexOf(currentItem) + 1;
    if (start === this.treeitems.length) {
      start = 0;
    }
  
    // Check remaining slots in the menu
    index = this.getIndexFirstChars(start, char);
  
    // If not found in remaining slots, check from beginning
    if (index === -1) {
      index = this.getIndexFirstChars(0, char);
    }
  
    // If match was found...
    if (index > -1) {
      this.setFocusToItem(this.treeitems[index]);
    }
  };
  
  Tree.prototype.getIndexFirstChars = function (startIndex, char) {
    for (var i = startIndex; i < this.firstChars.length; i++) {
      if (this.treeitems[i].isVisible) {
        if (char === this.firstChars[i]) {
          return i;
        }
      }
    }
    return -1;
  };
  