Skip to content

Commit

Permalink
Treegrid Example: Put aria-expanded on 1st cell in each row if only c…
Browse files Browse the repository at this point in the history
…ells can be focused (pull #369)

For issue #132: If the treegrid is configured so that rows can not be focused, then the first cell controls the expand/collapse behavior. In this case, aria-expanded will be on the first cell in each row instead of on the row.
  • Loading branch information
mcking65 authored Apr 21, 2017
2 parents 88baa21 + 7e1793c commit 238b600
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 12 deletions.
5 changes: 5 additions & 0 deletions examples/treegrid/css/treegrid-row-nav-primary-1.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
table-layout: fixed;
}

#treegrid tr {
cursor: default;
}

#treegrid-col1, #treegrid-col3 {
width: 30%;
}
Expand Down Expand Up @@ -67,6 +71,7 @@
}

#treegrid tr[aria-expanded] > td:first-child::before {
cursor: pointer;
/* Load both right away so there is no lag when we need the other */
background-image: url("expand-icon.svg"), url("expand-icon-highlighted.svg");
background-repeat: no-repeat;
Expand Down
80 changes: 69 additions & 11 deletions examples/treegrid/js/treegrid-row-nav-primary-1.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ function onReady (treegrid, doAllowRowFocus, doStartRowFocus) {
}
else {
setTabIndexForCellsInRow(rows[index], -1);
moveAriaExpandedToFirstCell(rows[index]);
}
propagateExpandedToFirstCell(rows[index]);
}

if (doStartRowFocus) {
Expand Down Expand Up @@ -149,7 +149,11 @@ function onReady (treegrid, doAllowRowFocus, doStartRowFocus) {
// The row with focus is the row that either has focus or an element
// inside of it has focus
function getRowWithFocus () {
var possibleRow = document.activeElement;
return getContainingRow(document.activeElement);
}

function getContainingRow (start) {
var possibleRow = start;
if (treegrid.contains(possibleRow)) {
while (possibleRow !== treegrid) {
if (possibleRow.localName === 'tr') {
Expand Down Expand Up @@ -304,12 +308,12 @@ function onReady (treegrid, doAllowRowFocus, doStartRowFocus) {
var cols = getNavigableCols(currentRow);
var currentCol = getColWithFocus(currentRow);
if (currentCol === cols[0] && currentRow.hasAttribute('aria-expanded')) {
changeExpanded(currentRow.getAttribute('aria-expanded') === 'false');
changeExpanded(isExpanded(currentRow));
}
}

function changeExpanded (doExpand) {
var currentRow = getRowWithFocus();
function changeExpanded (doExpand, row) {
var currentRow = row || getRowWithFocus();
if (!currentRow) {
return;
}
Expand Down Expand Up @@ -340,24 +344,40 @@ function onReady (treegrid, doAllowRowFocus, doStartRowFocus) {
}
}
if (didChange) {
currentRow.setAttribute('aria-expanded', doExpand);
propagateExpandedToFirstCell(currentRow);
setAriaExpanded(currentRow, doExpand);
return true;
}
}

// Mirror aria-expanded from the row to the first cell in that row
// (TBD is this a good idea? How else will screen reader user hear
// that the cell represents the opportunity to collapse/expand rows?)
function propagateExpandedToFirstCell (row) {
function moveAriaExpandedToFirstCell (row) {
var expandedValue = row.getAttribute('aria-expanded');
var firstCell = getNavigableCols(row)[0];
if (expandedValue) {
firstCell.setAttribute('aria-expanded', expandedValue);
row.removeAttribute('aria-expanded');
}
else {
firstCell.removeAttribute('aria-expanded');
}
}

function getAriaExpandedElem(row) {
return doAllowRowFocus ? row : getNavigableCols(row)[0];
}

function setAriaExpanded (row, doExpand) {
var elem = getAriaExpandedElem(row);
elem.setAttribute('aria-expanded', doExpand);
}

function isExpandable (row) {
var elem = getAriaExpandedElem(row);
return elem.hasAttribute('aria-expanded');
}

function isExpanded (row) {
var elem = getAriaExpandedElem(row);
return elem.getAttribute('aria-expanded') === 'true';
}

function onKeyDown (event) {
Expand Down Expand Up @@ -441,8 +461,46 @@ function onReady (treegrid, doAllowRowFocus, doStartRowFocus) {
event.preventDefault();
}

// Toggle row expansion if the click is over the expando triangle
// Since the triangle is a pseudo element we can't bind an event listener
// to it. Another option is to have an actual element with role="presentation"
function onClick (event) {
var target = event.target;
if (target.localName !== 'td') {
return;
}

var row = getContainingRow(event.target);
if (!isExpandable(row)) {
return;
}

// Determine if mouse coordinate is just to the left of the start of text
var range = document.createRange();
range.selectNodeContents(target.firstChild);
var left = range.getBoundingClientRect().left;
var EXPANDO_WIDTH = 20;

if (event.clientX < left && event.clientX > left - EXPANDO_WIDTH) {
changeExpanded(!isExpanded(row), row);
}
}

// Double click on row toggles expansion
function onDoubleClick (event) {
var row = getContainingRow(event.target);
if (row) {
if (isExpandable(row)) {
changeExpanded(!isExpanded(row), row);
}
event.preventDefault();
}
}

initAttributes();
treegrid.addEventListener('keydown', onKeyDown);
treegrid.addEventListener('click', onClick);
treegrid.addEventListener('dblclick', onDoubleClick);
// Polyfill for focusin necessary for Firefox < 52
window.addEventListener(window.onfocusin ? 'focusin' : 'focus',
onFocusIn, true);
Expand Down
2 changes: 1 addition & 1 deletion examples/treegrid/treegrid-row-nav-primary-1.html
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ <h2>Accessibility Features</h2>
<li>aria-level: used to set the current level of an item, 1, 2, 3, etc.</li>
<li>aria-posinset, aria-setsize: used on a row to indicate the position of an item within it's local group, such as item 3 of 5. Unfortunately, <a href="https://github.com/w3c/aria/issues/558">aria-posinset and aria-setsize are not currently legal in the spec (bug filed)</a>. Therefore, <a href="https://bugzilla.validator.nu/show_bug.cgi?id=1033">aria-posinset and aria-setsize are not currently supported on role="row" in the Nu Validator (bug filed)</a>. We'll need to work that in as this is the most practical way of implementing ARIA in many existing treegrid libraries (as opposed to rearchitecting the
DOM structure to contain rowgroups). Note that the <a href="https://github.com/w3c/aria/issues/553">ARIA spec's text for rowgroup states that it cannot be parented by a treegrid (bug filed)</a>.</li>
<li>aria-expanded (tristate): this attribute must be removed (not present) if the item cannot be expanded. For expandable items, the value is "true" or "false"</li>
<li>aria-expanded (tristate): this attribute must be removed (not present) if the item cannot be expanded. For expandable items, the value is "true" or "false".<br>IMPORTANT: aria-expanded is set on the row unless rows cannot receive focus, because it is an indicator to the user that something is actionable. If only the cell can get focused, then pressing Enter on the cell is the only way to indicate this, so the attribute needs to go there so that screen readers will announce it. It is not added to the cell if rows can receive focus, as this provides a confusing/redundant experience where both the row and first cell announce the expanded state. In this example, JavaScript is used to move the aria-expanded attribute down to the first cell in the ?cell=force version.</li>
<li>aria-hidden: set to "true" for child items that are currently hidden because the parent is collapsed.</li>
<li>aria-readonly: in ARIA 1.0, a grid/treegrid is editable by default. However, there is no default in ARIA 1.1. Firefox currently implements the ARIA 1.0 concept for this, which means that "editable" is read for every cell unless aria-readonly="true" is used on the treegrid. There needs to be some follow up with user agent developers on this, as ARIA 1.1 seems to be treating it more a tristate (don't care, false, true). In addition, <a href="https://github.com/w3c/aria/issues/550">a bug was filed on the spec regarding aria-readonly but it is possibly invalid</a>.</li>
<li>tabindex is set in the JS, as per the usual roving tabindex methodology. Specifically, we use tabindex="0" for the current item so that it is the subitem that gets focused if user tabs out and back in, and tabindex="-1" for all items where we want click-to-focus behavior enabled.</li>
Expand Down

0 comments on commit 238b600

Please sign in to comment.