This article are part of “What’s an usable table ?“, table usability guideline. The purpose of the table usability guideline is to explain how you can create usable complex table. The HTML 5 markup are used to illustrate the guideline methodology. Currently in progress, I am working on a javascript html table parser that would support the usable table guideline. Anything regards of HTML accessibility are not discussed here because one of my goal is to auto setup all the accessibility on the fly for a complex usable table.
Key Cell
Defined by a data cell element (td), a key cell can by compared to a primary key in a table inside a relational database. A key cell are always associated to a row cell header (th). Only one key cell can be associated to a header but two key cell can co-exist in the same table row. A key cell is always at the left of his cell header.
Product ID | Product Name | Col 1 | Col 2 | Col 3 | Col 4 |
---|---|---|---|---|---|
66-A | Famous | ++++ | ++++ | ++++ | ++++ |
94-C | Interresting | ++++ | ++++ | ++++ | ++++ |
27-F | Misterious | ++++ | ++++ | ++++ | ++++ |
68-H | Questionable | ++++ | ++++ | ++++ | ++++ |
30-S | Hottest | ++++ | ++++ | ++++ | ++++ |
04-W | Passable | ++++ | ++++ | ++++ | ++++ |
Source code
<table class="lined">
<caption>Basic "Key Cell" Example</caption>
<thead>
<tr>
<th>Product ID</th>
<th>Product Name</th>
<th>Col 1</th>
<th>Col 2</th>
<th>Col 3</th>
<th>Col 4</th>
</tr>
</thead>
<tbody>
<tr>
<td>66-A</td>
<th>Famous</th>
<td>++++</td>
<td>++++</td>
<td>++++</td>
<td>++++</td>
</tr>
<tr>
<td>94-C</td>
<th>Interresting</th>
<td>++++</td>
<td>++++</td>
<td>++++</td>
<td>++++</td>
</tr>
<tr>
<td>27-F</td>
<th>Misterious</th>
<td>++++</td>
<td>++++</td>
<td>++++</td>
<td>++++</td>
</tr>
<tr>
<td>68-H</td>
<th>Questionable</th>
<td>++++</td>
<td>++++</td>
<td>++++</td>
<td>++++</td>
</tr>
<tr>
<td>30-S</td>
<th>Hottest</th>
<td>++++</td>
<td>++++</td>
<td>++++</td>
<td>++++</td>
</tr>
<tr>
<td>04-W</td>
<th>Passable</th>
<td>++++</td>
<td>++++</td>
<td>++++</td>
<td>++++</td>
</tr>
</tbody>
</table>
As you can see in the previous table, the first table column are used to associate a product ID information to the product name. Whatever, it would not be clear to everybody, that doesn’t know/search for a specific product, to use the product ID. It’s most usable to use the product name. Keep in mind, when a entreprise market a product, he would use the product name and not the product key. The product key would be most useful for somebody that already know about it.
ID | Version | Release date |
---|---|---|
4.0.x | Ice Cream Sandwich | October 19, 2011 |
3.x.x | Honeycomb | February 22, 2011 |
2.3.x | Gingerbread | December 6, 2010 |
2.2 | Froyo | May 20, 2010 |
2.0, 2.1 | Eclair | October 26, 2009 |
1.6 | Donut | September 15, 2009 |
1.5 | Cupcake | April 30, 2009 |
Source Code
<table class="lined">
<caption>Another "Key Cell" Example - Android (Operating system) Release date</caption>
<thead>
<tr>
<th>ID</th>
<th>Version</th>
<th>Release date</th>
</tr>
</thead>
<tbody>
<tr>
<td>4.0.x</td>
<th>Ice Cream Sandwich</th>
<td>October 19, 2011</td>
</tr>
<tr>
<td>3.x.x</td>
<th>Honeycomb</th>
<td>February 22, 2011</td>
</tr>
<tr>
<td>2.3.x</td>
<th>Gingerbread</th>
<td>December 6, 2010</td>
</tr>
<tr>
<td>2.2</td>
<th>Froyo</th>
<td>May 20, 2010</td>
</tr>
<tr>
<td>2.0, 2.1</td>
<th>Eclair</th>
<td>October 26, 2009</td>
</tr>
<tr>
<td>1.6</td>
<th>Donut</th>
<td>September 15, 2009</td>
</tr>
<tr>
<td>1.5</td>
<th>Cupcake</th>
<td>April 30, 2009</td>
</tr>
</tbody>
</table>
If you check the similar table in wikipedia on the Adroid webpage, you will see the same relationship but displayed in an implicit way. They use emphasis in the first column to distinguish the key cell versus the row title.
Complex Key Cell Structure
Imagine you have serveral products and you want them to be classified by sector. You have an ID for each sector and an ID for each product. You would have a table like this:
SectorProduct NameCustomer PriceDistributor Price
ABC | Exterior | 2C57-ABC | BBQ | 135 $ | 83 $ |
---|---|---|---|---|---|
3C57-ABC | Mowers | 256 $ | 199 $ | ||
A584-ABC | Tractors | 2145 $ | 1687 $ | ||
XYZ | Interior | 2C57-XYZ | Table | 257 $ | 205 $ |
3C57-XYZ | Chair | 49 $ | 20 $ | ||
A584-XYZ | Recycling Center | 91 $ | 56 $ |
Source Code
<table class="lined">
<caption>Product Listing</caption>
<thead>
<th colspan="2">Sector</th>
<th colspan="2">Product Name</th>
<th>Customer Price</th>
<th>Distributor Price</th>
</thead>
<tbody>
<tr>
<td rowspan="3">ABC</td>
<th rowspan="3">Exterior</th>
<td>2C57-ABC</td>
<th>BBQ</th>
<td>135 $</td>
<td>83 $</td>
</tr>
<tr>
<td>3C57-ABC</td>
<th>Mowers</th>
<td>256 $</td>
<td>199 $</td>
</tr>
<tr>
<td>A584-ABC</td>
<th>Tractors</th>
<td>2145 $</td>
<td>1687 $</td>
</tr>
<tr>
<td rowspan="3">XYZ</td>
<th rowspan="3">Interior</th>
<td>2C57-XYZ</td>
<th>Table</th>
<td>257 $</td>
<td>205 $</td>
</tr>
<tr>
<td>3C57-XYZ</td>
<th>Chair</th>
<td>49 $</td>
<td>20 $</td>
</tr>
<tr>
<td>A584-XYZ</td>
<th>Recycling Center</th>
<td>91 $</td>
<td>56 $</td>
</tr>
</tbody>
</table>
Description Cell
There exist other technique on how to add descriptive cell to a cell header. One of those technique is to add in superscript a footnote. An other technique is to use the “details/summary” HTML 5 element in a cell header. Those two technique can be applied to any data cell in a table. When we look at the cell header (th), a thrid technique can be used. That technique is to add a data cell directly in the next column of (right of) the cell heading. A cell header can only have one descriptive data cell associated to.
Group | Description | Item | Col 1 | Col 2 | Col 3 |
---|---|---|---|---|---|
Heading 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lorem magna, mollis ac tempor id, bibendum ut turpis. Pellentesque odio ligula, egestas eu viverra ut, sodales vel sem. Nunc eu. | Sub Heading 1a | Data | Data | Data |
Sub Heading 1b | Data | Data | Data | ||
Sub Heading 1c | Data | Data | Data | ||
Sub Heading 1d | Data | Data | Data | ||
Sub Heading 1e | Data | Data | Data | ||
Sub Heading 1f | Data | Data | Data | ||
Heading 2 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lorem magna, mollis ac tempor id, bibendum ut turpis. Pellentesque odio ligula, egestas eu viverra ut, sodales vel sem. Nunc eu. | Sub Heading 2a | Data | Data | Data |
Sub Heading 2b | Data | Data | Data | ||
Sub Heading 2c | Data | Data | Data | ||
Sub Heading 2d | Data | Data | Data | ||
Sub Heading 2e | Data | Data | Data | ||
Sub Heading 2f | Data | Data | Data |
Source Code
<table class="lined">
<caption>"Description Cell" example</caption>
<thead>
<tr>
<th>Group</th>
<th>Description</th>
<th>Item</th>
<th>Col 1</th>
<th>Col 2</th>
<th>Col 3</th>
</tr>
</thead>
<tbody>
<tr>
<th rowspan="6">Heading 1</th>
<td rowspan="6">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lorem magna, mollis ac tempor id, bibendum ut turpis. Pellentesque odio ligula, egestas eu viverra ut, sodales vel sem. Nunc eu. </td>
<th>Sub Heading 1a</th>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Sub Heading 1b</th>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Sub Heading 1c</th>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Sub Heading 1d</th>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Sub Heading 1e</th>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Sub Heading 1f</th>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th rowspan="6">Heading 2</th>
<td rowspan="6">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lorem magna, mollis ac tempor id, bibendum ut turpis. Pellentesque odio ligula, egestas eu viverra ut, sodales vel sem. Nunc eu. </td>
<th>Sub Heading 2a</th>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Sub Heading 2b</th>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Sub Heading 2c</th>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Sub Heading 2d</th>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Sub Heading 2e</th>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Sub Heading 2f</th>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
</tbody>
</table>
The previous description cell are explicited defined when it is located between two cell header. Sometime they exist descrive cell but without any hiarchical cell. It is in that case the column grouping (colgroup element) are useful.
Column Group Header
Rarely used, the column grouping can became very useful if we need to have a distinction between the data column group and the header column group. Here I apply the same rule as defined for the table row grouping (thead, tfoot, tbody). In the HTML specification it’s said that, if present, the thead row group are defined before any tfoot and tbody. So if we check the row group structure the row group header section are always the first. Now let apply the same approch to the colgroup structure. We have, the first colgroup element are used as an header group. Offcourse, if in the table body there are no row heading, that means there would not have a colgroup used as an header group.
Descriptive cell defined with colgroup
<colgroup span="2" /><colgroup span="3" />
Item | Description | Col 1 | Col 2 | Col 3 |
---|---|---|---|---|
Heading 1 | Lorem ipsum dolor sit amet. | Data | Data | Data |
Heading 2 | Lorem ipsum dolor sit amet. | Data | Data | Data |
Heading 3 | Lorem ipsum dolor sit amet. | Data | Data | Data |
Heading 4 | Lorem ipsum dolor sit amet. | Data | Data | Data |
Heading 5 | Lorem ipsum dolor sit amet. | Data | Data | Data |
Heading 6 | Lorem ipsum dolor sit amet. | Data | Data | Data |
Source Code
<table class="lined">
<caption>"Description Cell" example - with one header per row</caption>
<colgroup span="2" /><colgroup span="3" />
<thead>
<tr>
<th>Item</th>
<th>Description</th>
<th>Col 1</th>
<th>Col 2</th>
<th>Col 3</th>
</tr>
</thead>
<tbody>
<tr>
<th>Heading 1</th>
<td>Lorem ipsum dolor sit amet.</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Heading 2</th>
<td>Lorem ipsum dolor sit amet.</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Heading 3</th>
<td>Lorem ipsum dolor sit amet.</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Heading 4</th>
<td>Lorem ipsum dolor sit amet.</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Heading 5</th>
<td>Lorem ipsum dolor sit amet.</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<th>Heading 6</th>
<td>Lorem ipsum dolor sit amet.</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
</tbody>
</table>
By using that technique, that create the possibility for a table parser to separate the real data vs descriptive information.
Conclusion
Now you know how to add key and descriptive cell to your table. In case of you might be interrested, here a snipped of the javascript table parser that make a distinction between a descriptive cell and a key cell. This are only to show quicly how it’s programaticly possible to understand the above theory.
// Parser the colgroup header section
var headerColgroup = undefined;
// If an cell header exist in that row....
if(lastHeadingColPos){
var headingRowCell = [];
var rowheader = undefined; // This are the lowest cell header level for this row
var colKeyCell = [];
for(i=0; i<lastHeadingColPos; i++){
// Check for description cell or key cell
if(row.cell[i].elem.nodeName.toLowerCase() == "td"){
if(!row.cell[i].type && row.cell[i-1] && !(row.cell[i-1].descCell) && row.cell[i-1].type == 1 && row.cell[i-1].height == row.cell[i].height){
row.cell[i].type = 5; // Update the cell type to "Descriptive"
row.cell[i-1].descCell = row.cell[i];
if(!row.desccell){row.desccell = [];}
row.desccell.push(row.cell[i]);
}
// Add this cell in the key cell collection for future analysis
if(!row.cell[i].type){
colKeyCell.push(row.cell[i]);
}
}
// Set for the most appropriate header that can represent this row
if(row.cell[i].elem.nodeName.toLowerCase() == "th"){
row.cell[i].type = 1; // Mark the cell to be an header cell
if(rowheader && rowheader.uid != row.cell[i].uid){
if(rowheader.height > row.cell[i].height){
// The current cell are a child of the previous rowheader
if(!rowheader.subheader){
rowheader.subheader = [];
rowheader.isgroup = true;
}
rowheader.subheader.push(row.cell[i]);
// Change the current row header
rowheader = row.cell[i];
headingRowCell.push(row.cell[i]);
} else {
// This case are either paralel heading of growing header, this are an error.
if(rowheader.height == row.cell[i].height){
errorTrigger('You can not have paralel row heading, do a cell merge to fix this');
} else {
errorTrigger('For a data row, the heading hiearchy need to be Generic to the specific');
}
}
}
// Initial rowheader reference
if(!rowheader){
rowheader = row.cell[i];
headingRowCell.push(row.cell[i]);
}
// Associate key cell
$.each(colKeyCell, function(){
if(!(this.type) && !(row.cell[i].key) && this.height == row.cell[i].height){
this.type = 4;
row.cell[i].key = this;
if(!row.keycell){row.keycell = [];}
row.keycell.push(this);
}
});
}
}
// All the cell that have no "type" in the colKeyCell collection are problematic cells;
$.each(colKeyCell, function(){
if(!(this.type)){
errorTrigger('You have a problematic cell, in your colgroup heading, that can not be understood by the parser');
if(!row.errorcell){row.errorcell = [];}
row.errorcell.push(this);
}
});
row.headerset = headingRowCell;
row.header = rowheader;
} else {
// There are no colgroup header,
lastHeadingColPos = 0;
}
Enjoy !! Don’t hesitate to leave a comments or contact me.