Personal tools

Accessibility/TableHeaders

From MozillaWiki

Jump to: navigation, search

Contents

Note

Proposals of this page are obsolete by new IA2 interfaces IAccessibleTable2 and IAccessibleTableCell.

Summary

This article is designed to cover implementation details of table headers in IAccessible2. As a matter of fact it is summary of discussion happen on IA2 mail list on this issue.

Terms

There is a difference in header term definition between IAccessible2 and markup tables. IA2 header is a table accessible (i.e. accessible implementing IAccessibleTable interface) with header cell accessibles describing the data cells or in other words rows or columns of the table. However, for example, ARIA header is cell element describing row or column of the table.

Here we will use the header term as an element containing header cells, where header cell is an element describing row or column of the table. This definition is close to IA2 terminology.

Tables used in Mozilla

Mozilla has couple of tables. These are ARIA grid/treegrid, HTML table and XUL tree and listbox.

ARIA grid/treegrid

ARIA has not markup for headers. However individual cells can be marked as header cells. ARIA provides role="rowheader" and role="columnheader" for this. ARIA implementation guide doesn't address how to find header cells for the given data cell and visa versa. We can use algorithm for HTML tables as the frist approaching. Attribute aria-describedby can be used similarly with @headers attribute on HTML tables.

HTML table

Natively HTML table has markup to provide column header only. The html:thead and html:tfoot elements are used for this. The thead and tfoot elements can contain several rows (html:tr) that contains individual cells (html:th or html:td).

However html:th can be used outside of html:thead or html:tfoot elements and be used to provide heading information as well. HTML 4 specification defines algorithm of finding heading information.

Also HTML specification defines @scope and @headers attributes on cells of the table. Attribute @scope placed on a cell is used to specify set of data cells described by this cell. Also @scope attribute defines if this cell is column or row header cell. Attribute @header used to link the data cell with header cells directly.

XUL tree and listbox

XUL tree and listbox has markup to provide column header only similar to HTML tables. XUL treecols and treecol elements of XUL tree serves for this. XUL listhead and listheaders elements are used for XUL listbox. XUL treecols and listhead elements are column headers, treecol and listheader elements are header cells.

Examples

This section provides several examples. Some of them might be pretty wild or even incorrect from point of view of table usage. Of couse first of all we should ensure we expose heading information correctly for correct cases. Secondly we should agree on how we should process wild cases.

Example 1

This is simple example of the table having column header.

Month Expenses
Jan $100
Feb $130
Mar $90

Here "Month" and "Expenses" are column header cells.

You can use HTML markup to create this table.

  <table>
    <thead>
      <tr>
        <th>Month</th>
        <th>Expenses</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <th>Jan</th>
        <th>100</th>
      </tr>
      <tr>
        <th>Feb</th>
        <th>130</th>
      </tr>
      <tr>
        <th>Mar</th>
        <th>90</th>
      </tr>
    </tbody>
  </table>

Note, the author can skip thead and tbody usage and it shouldn't change of table headers meaning exposed to AT. As well, you could use ARIA grid to create this table. In this case elements equivalent to th elements should have role="columnheader".

Example 2

This is simple example of the table having row header. It is the same like previous one excepting this table is rather row oriented than column oriented.

Month Jan Feb Mar
Expenses $100 $130 $90

Here "Month" and "Expenses" are row header cells. Below ARIA markup to create this table is cited. You could easy to transform this ARIA table into HTML table.

  <div role="grid">
    <div role="row">
      <span role="rowheader">Month</span>
      <span role="gridcell">Jan</span>
      <span role="gridcell">Feb</span>
      <span role="gridcell">Mar</span>
    </div>
    <div role="row">
      <span role="rowheader">Expenses</span>
      <span role="gridcell">100</span>
      <span role="gridcell">130</span>
      <span role="gridcell">90</span>
    </div>
  </table>
Example 3

It's simple example that exposes row and column headers the same time.

John Ivan
Apples 10 12
Oranges 1 9

Here "John" and "Ivan" cells are column header cells, "Apples", "Oranges" are row header cells. Note there is empty cell at position row=0, column=0. It describes neither row nor column.

The following ARIA markup can be used to create the table like this.

<div role="grid">
  <div role="row">
    <span role="gridcell"></span>
    <span role="columnheader">John</span>
    <span role="columnheader">Ivan</span>
  </div>
  <div role="row">
    <span role="rowheader">Apples</span>
    <span role="gridcell">10</span>
    <span role="gridcel">12</span>
  </div>
  <div role="row">
    <span role="rowheader">Oranges</span>
    <span role="gridcell">1</span>
    <span rolw="gridcell">9</span>
  </div>
</div>
Example 4

This example is more complex than previous one. The table has row and column header cells using row and column spans.

Animals
Lion Tiger
Vegetables Potato no no
Carrot no no

Here "Animals" is column header cell describing two columns of the table which are described by "Lion" and "Tiger" column header cells in their turn. "Vegetables" is row header cell describing two rows of the table which are described by "Potato" and "Carrot" row header cells. Note there is empty cell at (row=0, column=0) position (spanned at two rows and two columns) which describes nether column nor row like in previous example.

The following HTML markup can be used to create table like this.

<table>
  <tr>
    <th rowspan="2" colspan="2"></th>
    <th colspan="2">Animals</th>
  </tr>
  <tr>
    <th>Lion</th>
    <th>Tiger</th>
  </tr>
  <tr>
    <th rowspan="2">Vegetables</th>
    <th>Potato</th>
    <th>no</th>
    <th>no</th>
  </tr>
  <tr>
    <th>Carrot</th>
    <th>no</th>
    <th>no</th>
  </tr>
</table>
Example 5

Tables of this example expose row and column headers both and based on Example 3. The common cell at (row=0, column=0) position that is visually shared between column and row headers can be treated in different ways, either like row header cell or column header cell.

"Fruits" is column header cell in a table below. The author can use ARIA role="columnheader" or use scope="column" attribute on HTML th element to specify this.

Fruits John Ivan
Apples 10 12
Oranges 1 9

"Persons" is row header cell in a table below. The author can user ARIA role="rowheader" or use scope="row" attribute attribute on HTML th element to specify this.

Persons John Ivan
Apples 10 12
Oranges 1 9

"Fruits per persons" of the table below might be treated as row header cell and column header cell the same time. Neither ARIA role nor @scrope attribute provides useful information in this case.

Fruits per persons John Ivan
Apples 10 12
Oranges 1 9
Example 6

It's more trick example than the previous one.

Products Downloads number in
2008
Product #1 1 billion
Product #2 1 billion
2009
Product #1 2 billions
Product #2 2 billions

Here "Product", "Downloads number in" are column header cells, "Product#" are row header cells, "2008" and "2009" are column header cells for underlying data cells. Below is an example how to create this table in HTML.

<table>
  <tr>
    <th scope="col">Products</span>
    <th scope="col">Downloads number in</span>
  </div>
  <tr>
    <th colspan="2" align="center" id="2008">2008</th>
  </tr>
  <tr>
    <th scope="row">Product #1</th
    <th headers="2008">1 billion</span>
  </div>
  <tr>
    <td scope="row">Product #2</th>
    <td headers="2008">1 billion</th>
  </tr>
  <tr>
    <th colspan="2" align="center" id="2009">2009</th>
  </tr>
  <tr>
    <th scope="row">Product #1</th
    <th headers="2009">1 billion</span>
  </div>
  <tr>
    <td scope="row">Product #2</th>
    <td headers="2009">1 billion</th>
  </tr>
</table>
Example 7

This example uses thead and tfoot elements to introduce column headers. Note, tfoot duplicates information provided by thead. It makes sense for long tables.

Month Employee Quantity
January John Nobody 40
February Lily Worker 12
March John Nobody 30
Month Employee Quantity

The following HTML syntax makes this table.

  <table>
    <thead>
      <tr>
        <th>Month</th>
        <th>Employee</th>
        <th>Quantity</th>
      </tr>
    </thead>
    <tfoot>
      <tr>
        <th>Month</th>
        <th>Employee</th>
        <th>Quantity</th>
      </tr>
    </tfoot>
    <tbody>
     <tr>
        <th>January</th>
        <th>John Nobody</th>
        <th>40</th>
      </tr>
     <tr>
        <th>February</th>
        <th>Lily Worker</th>
        <th>12</th>
     </tr>
     <tr>
        <th>March</th>
        <th>John Nobody</th>
        <th>30</th>
      </tr>
   </tbody>
  </table>
Example 8

This example uses thead and tfoot elements to introduce column headers. Here's tfoot brings kind of summary of the table and contains data cell and rowheader cell.

  <table border="1">
    <thead>
      <tr>
        <th>Month</th>
        <th>Employee</th>
        <th>Quantity</th>
      </tr>
   </thead>
    <tfoot>
      <th colspan="2">Total</th>
      <th>82</th>
    </tfoot>
   <tbody>
     <tr>
        <th>January</th>
        <th>John Nobody</th>
        <th>40</th>
      </tr>
     <tr>
        <th>February</th>
        <th>Lily Worker</th>
        <th>12</th>
     </tr>
     <tr>
        <th>March</th>
        <th>John Nobody</th>
        <th>30</th>
      </tr>
   </tbody>
  </table>

Here "Month" and "Employee" are column header cells for data cells below excepting "Total" cell, "Quantity" is column header cell, "Total" is row header cell for "82" data cell.

This table visually is presented like

Month Employee Quantity
January John Nobody 40
February Lily Worker 12
March John Nobody 30
Total 82

The following markup makes the table above correct for AT processing.

  <table border="1">
    <thead>
      <tr>
        <th id="month">Month</th>
        <th id="emp">Employee</th>
        <th scope="col">Quantity</th>
      </tr>
   </thead>
    <tfoot>
      <th colspan="2" scope="row">Total</th>
      <th>82</th>
    </tfoot>
   <tbody>
     <tr>
        <th headers="month">January</th>
        <th headers="emp">John Nobody</th>
        <th>40</th>
      </tr>
     <tr>
        <th headers="month">February</th>
        <th headers="emp">Lily Worker</th>
        <th>12</th>
     </tr>
     <tr>
        <th headers="month" >March</th>
        <th headers="emp">John Nobody</th>
        <th>30</th>
      </tr>
   </tbody>
  </table>
Example 9

The following example has been introduced on w3c. However adduced HTML markup was adopted from here.

Trip/date Meals Room Trans Total
San Jose
25 Aug 97 37.74 112.00 45.00
26 Aug 97 27.28 112.00 45.00
Subtotal 65.02 224.00 90.00 379.02
Seattle
27 Aug 97 96.25 109.00 36.00
28 Aug 97 35.00 109.00 36.00
Subtotal 131.25 218.00 72.00 421.25
Totals 196.27 442.00 162.00 800.27
<table border="1" cellpadding=2 cellspacing=3>
  <caption>Travel Expenses (actual cost, US$)</caption>

  <thead>
    <tr>
      <th><p><span id="t1-r1-l1">Trip</span>,<br><span id="t1-r1-l2">date</span></p></th>
      <th scope="column">Meals</th>
      <th scope="column">Room</th>
      <th scope="column" abbr="Transportation">Trans.</th>
      <th scope="column">Total</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <th scope="rowgroup" headers="t1-r1-l1">San Jose</th>
    </tr>
    <tr>
      <td scope="row" headers="t1-r1-l2">25 Aug 97</td>
      <td>37.74</td>
      <td>112.00</td>
      <td>45.00</td>
    </tr>
    <tr>
      <td scope="row" headers="t1-r1-l2">26 Aug 97</td>
      <td>27.28</td>
      <td>112.00</td>
      <td>45.00</td>
    </tr>
    <tr>
      <td scope="row">Subtotal</td>
      <td>65.02</td>
      <td>224.00</td>
      <td>90.00</td>
      <td>379.02</td>
    </tr>
  </tbody>

  <tbody>
    <tr>
      <th scope="rowgroup" headers="t1-r1-l1">Seattle</th>
    </tr>
    <tr>
      <td scope="row" headers="t1-r1-l2">27 Aug 97</td>
      <td>96.25</td>
      <td>109.00</td>
      <td>36.00</td>
    </tr>
    <tr>
      <td scope="row" headers="t1-r1-l2"> 28 Aug 97</td>
      <td>35.00</td>
      <td>109.00</td>
      <td>36.00</td>
    </tr>
    <tr>
      <td scope="row">Subtotal</td>
      <td>131.25</td>
      <td>218.00</td>
      <td>72.00</td>
      <td>421.25</td>
    </tr>
  </tbody>

  <tbody>
    <tr>
      <th scope="row">Totals</th>
      <td>196.27</td>
      <td>442.00</td>
      <td>162.00</td>
      <td>800.27</td>
    </tr>
  </tbody>
</table>

Ways to expose table headers in IA2

There are two ways to provide information about table headers to AT. These ways have both pluses and minuses.

Header Tables

The first way is to implement rowHeader and columnHeader of IAccessibleTable interface.

HRESULT rowHeader(
  [out] IAccessibleTable **accessibleTable,
  [out, retval] long * startingColumnIndex) [get]

HRESULT columnHeader(
  [out] IAccessibleTable **accessibleTable,
  [out, retval] long * startingRowIndex) [get]

Relations

The second way is to implement DESCRIBED_BY and DESCRIPTION_FOR accessible relations which link header cells and data cells each other. This way suggests header cell accessibles expose DESCRIPTION_FOR relation with multiple targets pointing to data cells and data cell accessible expose DESCRIPTION_BY relation with multiple targets pointing to header cells.

IAccessible2 provides relations attribute to get all exposed relations on this accessible.

HRESULT IAccessible2::relations(
  [in] long maxRelations,
  [out, size_is(maxRelations), length_is(*nRelations)] IAccessibleRelation ** relations,
  [out, retval] long * nRelations)	 [get]

Once you acquired IAccessibleRelation object for the interested accessible relation you can use targets method to get all accessible targets.

HRESULT IAccessibleRelation::targets(
  [in] long maxTargets,
  [out, size_is(maxTargets), length_is(*nTargets)] IUnknown ** targets,
  [out, retval] long * nTargets ) [get]

Header Tables approach

XUL trees and listboxes only have a persistent way to provide header from markup. Persistent way means tree or listbox elements have a table header if and only if header element is provided in markup. HTML tables haven't unique way to provide column header information. Moreover there is no markup for HTML table to provide row header, however html:th elements can be used to create row header visually. And ARIA hasn't markup for headers at all. But AT wants to deal with all tables in unique way. Here we need to figure our the structure of header tables so that this structure won't be depended on markup used to create a table.

Header table hierarchy

The header table tree should have usual structure for accessible tables.

Example 1, example 2 and example 3

These examples expose simple header tables.

Column header table exposed as

table
  row
    header_cell header_cell

Row header table exposed as

table
  row
    header_cell
  row
    header_cell
Example 4

It exposes more complex header tables.

Column header table tree might be

table
  row
    cell "Animals" colspan="2"
   row
    cell "Lion"
    cell "Triger"

Row header table might be

table
  row
    cell "Vegetables" rowspan="2"
    cell "Potato"
   row
    cell "Carrot"
Example 5

Provides couple of tables where header tables have similar structure with table headers from previous examples. Nothing unpredictable.

Example 6

Provides simple row header table. However the way how to expose the column header table might be a trick.

It might be simple column header like

  table
    row
      cell "Products"
      cell "Downloads number in"

but this table loose "2008" and "2009" column header cells and exposed heading information is not complete. If we would try to include "2008" and "2009" header cells into column header table then AT won't have any idea what header cells are used to describe a particular data cell.

One possible approach is IAccessibleTable should expose several header tables like

HRESULT getColumnHeaderCount(
  [out,retval] long  * headerCount) [get]

HRESULT columnHeaderAtIndex(
  [in] long headerIndex,
  [out] IAccessibleTable **accessibleTable,
  [out, retval] long * startingRowIndex) [get]

Here we would return three column header tables. If we would add object attribute specifies if cells in the header table are applied locally or affects on whole table then we will address the fact "Products" and "Downloads ..." column header are applied to all data cells, however "2008" and "2009" header cells describing underlying cells only. Any way it's not easy to change IAccessible2 specification. Thus header table approach can't expose tables like this correctly.

Example 7 and example 8

Ideally client should provide simple column header table for example 7 because bottom column headers duplicates the top column headers. However column header generated by tfoot element can't be ignored always, example 8 clearly shows this. This tfoot should be result of single-cell row header table. It's not clear how to expose this heading information to AT via header tables like we have in example 6 because "Month" isn't header cell for "Total" row header cell.

Example 9

This example is much similar to example 6. It's hard to expose it via table header approach.

Virtual tree

The idea is to expose a virtual tree for header tables, i.e. this tree is not linked with main accessible tree and root of the tree is accessible table for header.

Cell and row elements might be exposed twice as accessible objects (one accessible object is for main accessible tree, another one is virtual header tree). However these twin accessible objects are different. For example, this makes IAccessible navigation methods to work independently, so that if you run through parents of cell from virtual tree then you will achieve header table, if you run up from cell of main tree then you will achieve original table accessible. As well, "table-cell-index" returns index in header table for virtual cell and returns index in main table for normal cell.

State changes

If states are changed for the one accessible then states are changed for its twin accessible in another subtree. For example, if you call IAccessibleTable::selectColumn on virtual header table then it selects cells of primary table as well and visa versa.

Events and HitTest

Since virtual table header is not linked with main tree then it's impossible to find accessibles of virtual tree by HitTest. As well this is the reason AccessibleObjectFromEvent might not work. So no events should be expected from table header accessible tree. AT can listen original table accessibles on mutation event to requery table header accessible.

Relations approach

At the first sight relations approach is more flexible because it should address trick cases of heading usage, especially for the case of HTML tables where @headers attribute is used. Since aria-describedby can be used on data cell to point to non heading information then AT should ignore non header cell accessibles during heading information processing. Also there is one relations approach feature consisted in order of relation targets corresponds to DOM hierarchy, i.e. if heading information doesn't correspond to table cells DOM hierarchy then AT will announce heading wrong.

Example 1-Example 5 provides expected relation targets. For example, data cell at (row=2, column=2) of example 4 is described by "Vegetable", "Potato" and by "Animals", "Lion".

Another examples are handled in predictable way as well. For example, data cell at (row=6, column=1) of example 9 (cell data is "96.25") is described by "Seattle", "Trip" and "Meals" header cells.