Accessibility/TableHeaders
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="gridcell">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.