Neil spotted this quirky behaviour in the TableHeaderCell control. Suppose you've got a Table web control that renders a <table> tag with a <th scope="col">, using code something like this:
Table table = new Table()
{
Rows =
{
new TableRow()
{
Cells =
{
new TableHeaderCell() { Scope = TableHeaderScope.Column, Text = "MyColumn" }
}
}
}
};
Then the rendered HTML won't be W3C compliant because a scope value of "column" isn't valid:
<table border="0">
<tr>
<th scope="column"></th>
</tr>
</table>
According to the W3C HTML and XHTML specs, the acceptable values for scope are [row|col|rowgroup|colgroup]. This is confirmed by the W3C Markup Validation Service, which reports the error:
Stepping into the source code for the TableHeaderCell control with Reflector or the .NET Reference Source, you can see the root of the problem:
protected override void AddAttributesToRender(HtmlTextWriter writer) {
TableHeaderScope scope = Scope;
if (scope != TableHeaderScope.NotSet) {
writer.AddAttribute(HtmlTextWriterAttribute.Scope, scope.ToString().ToLowerInvariant());
}
}
When the control sets the scope attribute's value, it uses a lowercase string representation of the TableHeaderScope.Column enumeration, i.e. "column". An enum value of TableHeaderScope.Col would've been ok, but TableHeaderScope.Column is invalid.
Workarounds
Neil has a good workaround by adding the scope attribute manually instead of using the TableHeaderCell.Scope property:
TableHeaderCell th = new TableHeaderCell();
th.Attributes["scope"] = "col";
I thought I'd have a go at extending this fix into a simple control adatper that makes sure TableHeaderCell always renders itself correctly. It works by checking whether the Scope property is set to TableHeaderScope.Column, and if so manually adds the attribute scope="col" instead. Here's the code:
public class TableHeaderCellAdapter : WebControlAdapter
{
protected override void RenderBeginTag(HtmlTextWriter writer)
{
TableHeaderCell th = (TableHeaderCell) this.Control;
if (th.Scope == TableHeaderScope.Column)
{
th.Scope = TableHeaderScope.NotSet;
th.Attributes["scope"] = "col";
}
base.RenderBeginTag(writer);
}
}
To associate the control adapter class with the TableHeaderCell control, add a browser definition file to the App_Browsers folder:
<!-- file: ~/App_Browsers/ControlAdapters.browser -->
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter
controlType="System.Web.UI.WebControls.TableHeaderCell"
adapterType="TableHeaderCellAdapter"
/>
</controlAdapters>
</browser>
</browsers>