Accessibility/TableHeaders: Difference between revisions
| Line 530: | Line 530: | ||
===Header Tables approach=== | ===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 | 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===== | =====Header table hierarchy===== | ||
| Line 550: | Line 550: | ||
row | row | ||
header_cell | header_cell | ||
</pre> | |||
[[#Example_4|Example 4]] exposes more complex header tables. | |||
Column header table tree might be | |||
<pre> | |||
table | |||
row | |||
cell "Animals" colspan="2" | |||
row | |||
cell "Lion" | |||
cell "Triger" | |||
</pre> | |||
Row header table might be | |||
<pre> | |||
table | |||
row | |||
cell "Vegetables" rowspan="2" | |||
cell "Potato" | |||
row | |||
cell "Carrot" | |||
</pre> | </pre> | ||
Revision as of 09:38, 3 June 2009
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. So that IA2 header is a table accessible (i.e. accessible implementing IAccessibleTable interface) containing cells 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 is used to specify the kind of header cell, i.e. this cell is row or column 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 rolw="columnheader">Ivan</span>
</div>
<div role="row">
<span role="rowheader">Apples</span>
<span role="gridcell">10</span>
<span rolw="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>
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 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]
Implementation proposal
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 of simple column header table exposed for example 1, example 2, example 3 is
table
row
header_cell header_cell
Example of simple row header table exposed for example 1, example 2, example 3 is
table
row
header_cell
row
header_cell
Example 4 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"
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.