Accessibility Improvements in ASP.NET 2.0 - Part 1

©2004 Stonebroom Limited, England. By Alex Homer, alex@stonebroom.com

 

Some two years ago, I was honored to be invited to take part in the design review panel for ASP.NET, looking at the feature set proposed for version 2.0. That seems a long time ago now, but one of concerns I expressed at the time was that ASP.NET should provide better support for developers to add features to their pages that improve accessibility for all users. The World Wide Web Consortium (W3C) and many other industry bodies have long been pushing for wider support across all sites for disabled and disadvantaged users, many of whom use special types of user agent to access Web pages.

 

The W3C issues guidelines under the umbrella of the Web Accessibility Initiative (WAI), whose mission is "...to lead the Web to its full potential..." includes amongst its goals "...promoting a high degree of usability for people with disabilities" (see http://www.w3.org/WAI/ for more details). Other sites provide useful guides, software, and tools that make it easier to build and test pages that maximize accessibility. You only have to search the Web for "Web Accessibility Guidelines" to get some idea of the variety and number of sites devoted to this topic.

 

However, this article is not a general reference to accessibility issues, or a primer on building accessible pages. What it does cover is how the ASP.NET team at Microsoft has added new features to the Framework that you can take advantage of in your Web applications to more easily provide better support for alternative types of browsers and user agents. It involves little extra effort on the part of the developer, but can make the world of difference to users.

Accessibility and the Law

While the issues of providing reasonable accessibility for as large a proportion of the population as possible might seem to be justifiable on their own, you also have to consider the legal ramifications of not following such a policy. As in most walks of life, governments are unwilling to leave it to industry to manage their own affairs. To some extent, this is understandable – the W3C accessibility initiatives have been around for a while, yet recent surveys show that only a small number of “large commercial Web sites” (around 20%) have made any real attempts at implementing them.

 

In the US, the accepted standards are based on the Section 508 Standards for Electronic and Information Technology (see http://www.access-board.gov/sec508/guide/). These were originally aimed at companies providing products or services to government departments and public bodies, stating that: "Any organization or company that contracts with the Federal government must ensure that its Web site and electronic data are available to the public in a manner that is accessible to people with disabilities." The meaning of the phrase "contracts with" is becoming more tenuous as time goes by...

 

In Europe, in June 2002, the eEurope Action Plan was adopted by the Feira European Council. One of the specific targets of this plan is to improve access to the Web for people with disabilities and, of course, to adopt and implement the results of the Web Accessibility Initiative project. A statement from the council says that: “In Europe, we have now clearly established that actions must be taken to identify and remove these [accessibility] barriers. The European Commission is fully committed to this goal.”

 

In the United Kingdom, a 1999 Act of Parliament, The Disability Rights Commission Act is in force, and in 2003 the commission began a formal (and ongoing) investigation into Web site accessibility for disabled people. It is currently carrying out “… a systematic evaluation of the extent to which the current design of websites ... facilitates or hinders use by disabled people in England, Scotland and Wales.” It is also performing “…analysis of the reasons for any recurrent barriers identified by the evaluation …” and producing “… recommendations for further work which will contribute towards enabling disabled people to enjoy full access to, and use of, the Web.”

 

Probably the most direct and unequivocal indication of the way governments around the world are beginning to involve themselves in accessibility issues is from Australia. Dr Sev Ozdowski, the acting Disability Discrimination Commissioner at the time, said: “Under the Federal Disability Discrimination Act - as well as under equivalent laws in all Australian States - it is unlawful to discriminate against a person on the grounds of their disability by having a Web site which they cannot access." … “The World Wide Web Consortium has developed Web access guidelines, and non-compliance with them by the operators of Australian Web sites is in breach of the Act."

 

And, to demonstrate how the Australian law has already been applied, probably the most famous of all actions taken by disabled Web users was when Bruce Maguire (a blind person) cited the Act in a claim against the Sydney Organizing Committee for the Olympic Games, which were held in 1999/2000. He complained to the committee that images on the Olympics.com Web site had no text equivalent for screen readers and Braille displays. SOCOG failed to react to directives and were fined A$20,000.

 

There are similar clauses in the in the UK Disability Discrimination Act, the US Americans with Disabilities Act, the Canadian Human Rights Act, plus various provincial, state, and territorial human-rights codes. Following settlement of the claim and the subsequent fine on SOCOG, the Toronto-based Web Content Consultancy contenu.nu stated that: “The case of Maguire vs. SOCOG will inevitably come into play as a precedent for legal cases worldwide.”

Accessibility Issues in Web Pages

Web pages today are very different from the original vision of the pioneers the World Wide Web. They saw it as a sharing and publishing environment for scientific information, rather than the public network offering online retailing, the source of references material on every topic under the sun, and the general entertainment arena that it has now become.

 

Wide public access, and the continuing commercialization of the Web, has brought changes in the type of content it offers. The most remarkable change has been the move away from the functional, mainly text-based, types of pages. Today, the Web is ruled as much by designers and graphic artists as by developers and network specialists.

 

Web pages have become more complicated. No longer is it permissible for your company “home page” to contain a picture of your offices and a simple text menu for the services you offer. Now you have to have drop-down or pop-up menus with myriads of links, graphics (preferably animated), and dozens of headlines that lead to press releases, new product details, or testimonials of your services.

 

All this is fine if your visitors can look at the page and easily identify the areas that interest them, or scan up and down the links to other pages looking for what they want. And, should they stray to the wrong page, it’s usually pretty obvious from a quick glance. However, things are nowhere near this easy for all visitors.

 

A proportion of visitors will have difficulties with most current Web sites, which are designed almost without exception for people who have reasonable eyesight and are using a pointer device such as a mouse, trackball or graphics tablet. There are many people to whom one or both of these conditions cannot be applied. Often they will be using a specially designed user agent, or maybe a simple text-based browser. Or it might be aural page reader, which reads the contents of pages out loud, or a Braille reader that translates the text into a format that can be read by fingertip on a special output device.  

 

For all these types of device, even the most basic Web site or Web application (designed and tested only in a modern graphical browser such as Internet Explorer) can be hard to read. At best, navigation through the site can be challenging, and certain parts of the content may be meaningless. Graphics and pictures won’t be displayed on text-based devices, while color-blind users may not be able to distinguish between the different lines or pie segments in your charts. At worst, it may be completely impossible use online forms, to even access parts of the site at all.

Areas for Improvement

Accessibility problems for Web sites generally fall into three areas:

 

  • Navigation issues – how easy is it to find and load specific pages without images and without the traditional pointing devices?

 

  • Visual content issues – do parts of the site depend solely on images and/or colors to provide information, which color-blind, partially sighted and blind users will not be able to access?

 

  • Data input issues – on pages where the user has to interact with controls, is this possible without a mouse, graphics tablet or other type of pointing device? 

 

Unfortunately, it’s not easy to cleanly subdivide the techniques you should be using to improve accessibility into these three simple categories. Some features span more than one category, as you’ll see when we look at them in more detail.

Recommendations for Maximizing Accessibility

This section summarizes the recommendations for improving accessibility for visitors in your Web pages and Web applications. For more details see the W3C site at http://www.w3.org/TR/WCAG20/.

Assisting Navigation

  • Provide one or more hyperlinks that point to the content areas of each page as the first item in that page (before any headings, images, etc). These "skip to" links allow users to go straight to the content without having to navigate past your menu bars or other sets of site navigation links. Alternatively, use CSS absolute positioning to place the menu bars or site navigation links at the end of the page source, yet still allowing them to appear at the top or left of the page when rendered in the browser.
  • Make the structural layout of every page similar (if this is appropriate for the content)
  • Include a meaningful <title> element in every page
  • Avoid self-referring links (links that reference the current page)
  • Avoid meaningless text such as "click here" in hyperlinks
  • Lay out the content of HTML tables so they can be read from left to right (rather than vertically) and still make sense
  • Use <th> elements for the columns and row headings in a table
  • Add a scope attribute to all table row and column headers, to indicate that the header cell identifies the data in that row (scope="row") or column (scope="col"). The new and updated server controls in ASP.NET 2.0 make this much easier
  • Put control captions in the "expected" places on an HTML <form>, for example to the left of a textbox or list control and to the right of a radio button or checkbox
  • Use <label> elements on complex forms, with the for attribute to link them to the control to which they apply.

Identifying Elements and Accessing Alternate Content

  • Include a name and a title attribute on all <frame> elements and include a <noframes> section for user agents that don't support frames, explaining how to access the content in this case
  • Include a title attribute in all interactive elements, such as <a>, <input>, <select>, <textarea>, etc.
  • Include an alt attribute on every image element, containing a brief but meaningful text description of the image
  • Use the longdesc attribute or the widely accepted "d-link" approach to provide a link for any content that is graphical in nature to display a separate page containing a description of the content. Remember that aural content may not be suitable for all users, and so provide a text description or transcript where possible as well.
  • Always include alt="" (an empty string) on images that do not contribute to page content
  • Include an alt attribute in every <area> definition of a client-side image map, and avoid server-side image maps - which cannot be described to user in any kind of meaningful way

General Recommendations

  • Use generic font sizes such as x-small and large to allow the user to enlarge the display font in their browser
  • Provide alternative text content, where possible, inside all <object> or <applet> elements, but outside any <param> elements they might contain
  • Do not generate visual content with client-side script, or - if you must - use a <noscript> section for alternative content

CSS Style Sheets

  • Link your CSS style sheets using a <link> element so that the user agent can decide whether to download them or not, and they can be cached and reused in other pages if applicable:

<link rel="stylesheet" title="Default" href="defaultstyle.css" type="text/css" />

  • Consider providing multiple style sheets that specify (for example) monochrome colors or larger text sizes. Use a specific value for the title attribute of each one, and indicate that it is an alternative stylesheet by using rel="alternate stylesheet" in the <link> element.
  • Note that not all browsers and user agents allow the user to specify the stylesheet to be applied, though some allow users to specify their own custom stylesheet
  • Page readers and devices output content in the order it occurs in the page source. CSS absolute positioning can be used to position elements in Web browsers while providing optimum order for page readers, for example, menus and non-informational content can be placed visually at the top of the page, while being located at the end of the page source

Testing Your Sites and Applications

Appreciating that you need to improve accessibility in your own sites, or build it into new sites, is only half the battle. Knowing where to start, what to do, and how best to do it is not easy. There are tools to help you check your pages. Examples can be found at the Watchfire 'Bobby' Accessibility Test Tools site (http://bobby.watchfire.com/), UsableNet (http://www.usablenet.com/) and at http://cast.org/products/.  Guidelines and standards can be found at W3C’s own site: the Web Content Accessibility Guidelines are at http://www.w3.org/TR/WAI-WEBCONTENT/, and the W3C Policy and Laws Guide by Country are at http://www.w3.org/WAI/Policy/.

 

Other more direct techniques are to test your site using one of the specialist browsers or user agents. Try accessing your pages in a text-only browser such as Lynx (http://lynx.browser.org/) to see if you can navigate through all the pages without a mouse, and understand what they contain when there are no images. Or try an aural page reader, such as the IBM Home Page Reader (http://www-306.ibm.com/able/solution_offerings/hpr.html). Turn off your screen and see if you can navigate through your own site when the links and content are read out aloud.

Server Control Accessibility Enhancements in ASP.NET 2.0

In this article (which is divided into two parts), we'll demonstrate the main changes to the server controls provided with ASP.NET in comparison to those in ASP.NET 1.x, concentrating almost solely on accessibility issues. We've provided four example pages that you can experiment with, and which show the various new controls and properties in use.

 

  • Example 1, in this part of the article, demonstrates the new HtmlLink control, the AssociatedControlID property of the Label control and the use of a hotkey, plus the use of table captions and table row and column headers.
  • Example 2, which is the first of the three examples in part two of this article, demonstrates the use of the AssociatedHeaderCellID property and the use of headers and scope attributes when generating HTML tables dynamically.
  • Example 3 demonstrates the improved support for the alt attribute for images.
  • Example 4 demonstrates the new LabelAttributes and InputAttributes properties of the CheckBox control.

 

You can download the examples from http://www.daveandal.net/articles/v2accessibility/.

Example 1: Accessibility with HtmlLink and GridView Controls

The first example demonstrates some useful features (as concerns accessibility) of two new controls, as well as additional properties for some existing controls:

 

  • The new HtmlLink control is used automatically (instead of the HtmlGenericControl) when ASP.NET sees a <link> element that contains the runat="server" attribute in the page. The HtmlLink control exposes the Href property, allowing the href attribute of the resulting <link> element to be set programmatically. However, to set other attributes (for example rel and type when linking a CSS stylesheet), you have to resort to using the Attributes collection.
  • The Caption property, and the associated CaptionAlign property, can be used to display a descriptive caption above, below, or to the left or right of a table. These properties are available on the Calendar, DetailsView, FormView, GridView, Table, DataList and DataGrid controls.
  • The UseAccessibleHeader property, when set to True, causes a control that generates an HTML table to add a scope attribute to each header cell. Non-visual user agents can use this attribute to determine how the header relates to the cells within the table - in other words if it is a column header (scope="col") or a row header (scope="row"). This makes it easier for the user to understand what the contents of a table represent. This property is available on the Calendar, GridView, DataList and DataGrid controls.
  • The AccessibleHeaderText property is used to specify text that explains what each column header means in more detail, without being visible in the normal output (and therefore not disturbing the layout of the table). This property is available on controls that are used to generate columns or rows in a GridView and DetailsView control, namely BoundField, AutoGeneratedField, ButtonField, CommandField, CheckBoxField, HyperlinkField, ImageField, and TemplateField.
  • The RowHeaderColumn property is set to the name of the column in a GridView control that contains the value that most readily identifies each row (for example the product name column in a list of products). The values in this row are then displayed in <th> elements, rather than the usual <td> elements, and the scope="row" attribute is added to each one. This property is available only on the GridView control.
  • The AssociatedControlID property causes the Label control to generate a <label> element (rather than the usual <span> element). It also adds a for attribute to the <label> element, so that the browser will link it to the specified interactive control (such as a text box, check box or list control). This allows a hotkey to be defined for the Label control that will move the input focus directly to the associated control. This property is available only on the Label control.

Linking a CSS Stylesheet with the New HtmlLink Control

Although you can achieve the same effects with an HtmlGenericControl in ASP.NET 1.x, the new HtmlLink control makes it easier to declaratively or dynamically insert <link> elements into your pages and then manipulate the properties of these elements. A prime use for the <link> element, especially when maximizing accessibility, is to specify one or more stylesheets for your page(s). For example:

 

<link id="MyLink" runat="server" href="style.css" rel="stylesheet" type="text/css" />

 

The HtmlLink element exposes a property named Href that sets the href attribute of the resulting <link> element, and so the URL of the stylesheet can be manipulated at runtime by reading and setting the value of this property. The remaining attributes in the declaration do not correspond to properties of the control. However, the ASP.NET server controls simply copy any unrecognized attributes in the declaration directly on to the element that they generate.

 

An alternative approach is to dynamically generate the HtmlLink elements for a page. The declaration of the first part of the example page listed below (example1.aspx) contains no <link> elements:

 

example1.aspx

<html>

<head id="elemHead" runat="server">

<!-- link elements will be inserted here -->

<title>DataGrid Control Enhancements</title>

</head>

<body>

...

 

However, the server-side code contains a Page_Load event handler that creates new instances of the HtmlLink control, sets their properties, and inserts them into the page. It sets the Href property of each one to the URL of the stylesheet, but the remaining attributes can only be set dynamically by accessing the Attributes collection of the control directly.

<script runat="server">

  Sub Page_Load()

    ds1.ConnectionString = ConfigurationSettings. _

        ConnectionStrings("nwind").ConnectionString

 

    ' generate new HtmlLink control for "Standard" stylesheet

    Dim oLink1 As New HtmlLink()

    With oLink1

      ' set properties and add required attributes

      .Href = "style.css"

      .Attributes.Add("rel", "stylesheet")

      .Attributes.Add("type", "text/css")

      .Attributes.Add("title", "Default")

    End With

    ' insert into <head> section as a child control

    elemHead.Controls.Add(oLink1)

 

    ' repeat for "Large Text" alternate stylesheet link

    Dim oLink2 As New HtmlLink()

    With oLink2

      .Href = "style-large.css"

      .Attributes.Add("rel", "alternate stylesheet")

      .Attributes.Add("type", "text/css")

      .Attributes.Add("title", "Large Text")

    End With

    elemHead.Controls.Add(oLink2)

 

  End Sub

</script>

 

Notice that both stylesheet links have a title attribute added. This attribute specifies the text that the user will see for each one in the list of stylesheets, if their browser allows them to choose the stylesheet to use. This option is not available in Internet Explorer 6, which uses the last <link> element that contains the attribute rel="stylesheet". The usual approach is to use the title Default for the default stylesheet, and then rel="alternate stylesheet" and a descriptive value for the title attribute in all alternative stylesheet links. Internet Explorer will ignore stylesheet links that contain a rel="alternate stylesheet" attribute in the <link> element.

 

The new HtmlLink controls are inserted into the control tree of the page as children of the <head> element. The <head> element is declared as a server control (with the runat="server" attribute - see the listing above), and so it is represented in the control tree by an instance of the new HtmlHead control that is added to ASP.NET in version 2.0. The HtmlHead control (like almost all ASP.NET server controls) has a Controls collection for its child controls. The Add method of this collection adds the specified control, in our case the new HtmlLink controls, to the end of the Controls collection.

 

The output that is generated from the code shown above is:

 

<link href="style.css" rel="stylesheet" type="text/css" title="Default" />

<link href="style-large.css" rel="alternate stylesheet" type="text/css"

      title="Large Text" />

 

Figure 1 shows the page displayed in Mozilla 1.5, which allows the style sheet to be selected. You can see the effects of choosing the "Large Text" stylesheet in this screenshot.

 

Figure 1 - Choosing a stylesheet for example1.aspx in Mozilla

Connecting a Label Control and Specifying a HotKey

The next section of control declarations in the example1.aspx page demonstrates the use of the new AssociatedControlID property of the Label control, along with a couple of other properties that were available in ASP.NET 1.x. When you build pages that contain interactive controls, a useful feature for all users, not just those with special accessibility requirements, is to provide hotkeys that move the input focus directly to a specific control.

 

By using a combination of the AssociatedControlID, AccessKey and TabIndex properties, you can make forms or other pages containing interactive controls much easier for all your visitors to use. Setting the TabIndex allows you to control the order that the input focus moves from one control to the next. By default, the browser uses the order of the control declarations within the page. In complex forms, you can use the TabIndex property to make the input focus move down the columns of a table, rather than the default of moving across the rows, or force it to follow whatever path through the controls you require. Of course, the path you choose should be an "obvious" one, so as not to confuse users who will wonder where the input focus went if it jumps from control to control in some non-intuitive way!

 

The AccessKey property specifies the key that, when pressed in conjunction with the Alt key, will move the focus to that control on the page (there are some keys you can’t use, such as Alt-T, which opens the Tools menu in Internet Explorer). It's also useful to indicate the hotkey to the user by underlining that letter in the caption of the control - it works well with most controls, although it doesn’t work with the ASP.NET Button control. The listing below demonstrates the use of the TabIndex and AccessKey properties, along with underlining the corresponding letter in the Label control that acts as the caption of the TextBox:

 

...

<asp:Label id="lblProduct" runat="server"

           Text="<u>P</u>roduct Name:"

           AccessKey="P"

           AssociatedControlID="txtProduct"

           TabIndex="0" /> 

 

<asp:TextBox id="txtProduct" runat="server"

             TabIndex="1"

             Text="A" />

 

<asp:Button runat="server" id="bntGo" Text="Go"

            ToolTip="Start search for matching products"

            TabIndex="2" />

...

 

Because a Label control can’t receive the focus in the browser, the focus will move to the next control in the declarative or tabbing order of the page. So, in the code above, the TextBox will automatically get the focus when Alt-P is pressed. However, there is no external indication that this will happen, especially for users who depend on aural page readers or other specialist devices. The new AssociatedControlID property solves the problem by connecting the Label control to the interactive control that it refers to, by adding the for attribute to it. For example, the code listed above generates the following HTML when rendered to the client:

 

 

 

Figure 2 shows the appearance of the controls in Internet Explorer. Bear in mind, however, that not all graphical browsers fully support hotkeys and the use of associated labels.

 

Figure 2 - Using a combination of the AssociatedControlID, AccessKey and TabIndex properties

Captions, scope, and headers Attributes in the GridView Control

The final section of code in the example1.aspx page demonstrates the use of some accessibility-oriented properties of the new GridView control in ASP.NET 2.0. The UseAccessibleHeader property is also available on the Calendar, DataList and DataGrid controls, while the other two properties (AccessibleHeaderText and RowHeaderColumn) are specific to the new GridView control.

 

The code listing below shows the complete declaration of a GridView control, which is populated by a SqlDataSource control shown at the end of the listing (the ConnectionString property of the SqlDataSource control is set at runtime by code you saw earlier in the Page_Load event handler):

 

...

<asp:GridView id="MyGrid" runat="server"

              DataSourceID="ds1"

              DataKeyNames="ProductID"

              CaptionAlign="Top"

              Caption="<u>G</u>ridView Example"

              AccessKey="G"

              RowHeaderColumn="ProductName"             

              UseAccessibleHeader="True"

              SummaryViewColumn="ProductName"

              BorderWidth="1px"

              BorderColor="#E7E7FF"

              BorderStyle="None"

              BackColor="White"

              CellPadding="3"

              TabIndex="3"

              PagerSettings-Mode="Numeric"

              AutoGenerateColumns="False">

  <HeaderStyle ForeColor="#F7F7F7" Font-Bold="True"

              BackColor="#4A3C8C" />

  <RowStyle ForeColor="#4A3C8C" BackColor="#E7E7FF" />

  <AlternatingRowStyle BackColor="#F7F7F7" />

  <PagerStyle ForeColor="#4A3C8C"

              HorizontalAlign="Right" BackColor="#E7E7FF" />

  <FooterStyle ForeColor="#4A3C8C" BackColor="#B5C7DE" />

  <Columns>

    <asp:BoundField DataField="ProductID"

                    AccessibleHeaderText="Product Identifier"

                    HeaderText="ID"

                    ItemStyle-HorizontalAlign="Center">

      <ItemStyle HorizontalAlign="Center"></ItemStyle>

    </asp:BoundField>

    <asp:BoundField DataField="ProductName"

                    AccessibleHeaderText="Full Product Name"

                    HeaderText="Product"

                    ItemStyle-HorizontalAlign="Left">

      <ItemStyle HorizontalAlign="Left"></ItemStyle>

    </asp:BoundField>

    <asp:BoundField DataField="QuantityPerUnit"

                    AccessibleHeaderText="Quantity per Unit"

                    HeaderText="Packaging" />

    <asp:BoundField DataField="UnitPrice"

                    AccessibleHeaderText="Price per Unit"

                    HeaderText="Price"

                    DataFormatString="$ {0:F2}"

                    ItemStyle-HorizontalAlign="Right">

      <ItemStyle HorizontalAlign="Right"></ItemStyle>

    </asp:BoundField>

    <asp:BoundField DataField="UnitsInStock"

                    AccessibleHeaderText="Units in Stock"

                    HeaderText="Stock"

                    ItemStyle-HorizontalAlign="Right">

      <ItemStyle HorizontalAlign="Right"></ItemStyle>

    </asp:BoundField>

  </Columns>

</asp:GridView>

 

<asp:SqlDataSource id="ds1" runat="server"

     SelectCommand="SELECT ProductID, ProductName, QuantityPerUnit,

                    UnitPrice, UnitsInStock FROM Products"

     FilterExpression="ProductName LIKE '@ProductName%'">

  <FilterParameters>

    <asp:ControlParameter Name="ProductName" ControlID="txtProduct"

                          PropertyName="Text" />

  </FilterParameters>

</asp:SqlDataSource>

 

The section of the example page that this code creates is shown in Figure 3.

 

Figure 3 - The output generated by the GridView control in Internet Explorer

 

Adding a Table Caption

Most of the declaration is concerned with the mechanics of specifying the columns and the appearance of the control in a normal graphical Web browser. However, there are several sections that are aimed at improving accessibility for users of non-graphical client devices. The HTML table that the GridView control generates has a caption at the top, so that users can immediately understand what the content as a whole represents. This is aligned above the table, and uses a hotkey in the caption to allow the user to jump directly to the table:

 

  CaptionAlign="Top"

  Caption="<u>G</u>ridView Example"

  AccessKey="G"

 

Specifying Meaningful Column Names

Displaying a table of values is easy, as you can see from the code above. There are plenty of controls in ASP.NET that can take a source rowset and display its contents. However, we often spend more time worrying about the layout and appearance of the table, and little time thinking about things like what the column heading actually mean. After all, they have to be fairly short to avoid upsetting the layout of the table - especially when it contains, for example, just columns of numbers.

 

In a non-visual user agent or specialist page reader, it's hard to grasp what a table contains in the same way as a sighted user would (for example, by rapidly scanning over the values to get a feel for what they represent). Therefore, good informative column headings are extremely useful. One way to achieve this is to use an attribute to specify a "long description" of the column contents, which is not visible in an ordinary graphical browser.

 

Microsoft chose to take advantage of the abbr (abbreviation) attribute that is supported by all visible elements. You can specify the values to be placed in this attribute for any of the column types that are used in the new GridView and DetailsView controls in ASP.NET 2.0. These column types are: BoundField, AutoGeneratedField, ButtonField, CommandField, CheckBoxField, HyperlinkField, ImageField, and TemplateField.

 

You just turn off auto-generation of the columns in the control, and then specify the columns you require - setting the AccessibleHeaderText property of each one. In our example, we turn off automatic column generation by adding the attribute AutoGenerateColumns="False" to the declaration of the GridView control. Then we specify the columns we want. The code below is the declaration of the first two columns, showing how we can set the visual header text and the associated header text to different values, both of which are different to the name of the column in the source rowset (as identified by the DataField attribute):

 

    <asp:BoundField DataField="ProductID"

                    AccessibleHeaderText="Product Identifier"

                    HeaderText="ID"

                    ItemStyle-HorizontalAlign="Center">

    <asp:BoundField DataField="ProductName"

                    AccessibleHeaderText="Full Product Name"

                    HeaderText="Product"

                    ItemStyle-HorizontalAlign="Left">

 

Look back at Figure 3 to see the output that is generated in Internet Explorer. If you view the source of the page, you'll see the associated header text as in this (abbreviated) listing:

 

<table accesskey="G" tabindex="3" id="MyGrid">

 <caption align="Top"><u>G</u>ridView Example</caption>

 <tr>

  <th abbr="Product Identifier">ID</th>

  <th abbr="Full Product Name">Product</th>

  <th abbr="Quantity per Unit">Packaging</th>

  <th abbr="Price per Unit">Price</th>

  <th abbr="Units in Stock">Stock</th>

 </tr>

 ...

 ... data rows here ...

 ...

</table>

 

Specifying the Row Headers in a GridView Control

In Figure 3, you can also see that the values in the column headed "Product" are displayed in bold text. This column is generated from the values in the ProductName column of the source rowset, and it is the ideal column to provide a "friendly name" identifier for each row.

The values in the column that contains the "friendly name" of each product are effectively the headers for each row in the GridView control output. In other words, a visitor uses the row header value in conjunction with the value in the column header to uniquely identify any other value in that row. In plain English, the value in the price column of the first row in Figure 3 is "the Price of Tofu".

 

While this type of instant association in a table is easy for visually capable users, it's a lot more difficult for non-visual user agents or specialist page readers to achieve. To make it easier, two more properties of the GridView control have been set in this example. We set the RowHeaderColumn property to the name in the source rowset that provides the "friendly name" values, and set the UseAccessibleHeader property to True:

 

  RowHeaderColumn="ProductName"             

  UseAccessibleHeader="True"

 

This forces several changes in the output generated by the GridView control:

 

  • It forces the values in every row in the column defined by the RowHeaderColumn property to be placed in <th> elements (instead of <td> elements). All the column headers are also placed in <th> elements.
  • It adds the scope attribute to each <th> header cell. The column headers gain the attribute scope="col" to indicate that the value in this cell identifies or describes the values in this column. The row headers gain the attribute scope="row" to indicate that the value in this cell identifies or describes the values in the remaining cells of this row.

 

The abbreviated listing below shows some of the output generated by the GridView control in our example so that you can see the results more clearly:

 

<table accesskey="G" tabindex="3" id="MyGrid">

 <caption align="Top"><u>G</u>ridView Example</caption>

 <tr>

  <th scope="col" abbr="Product Identifier">ID</th>

  <th scope="col" abbr="Full Product Name">Product</th>

  <th scope="col" abbr="Quantity per Unit">Packaging</th>

  <th scope="col" abbr="Price per Unit">Price</th>

  <th scope="col" abbr="Units in Stock">Stock</th>

 </tr>

 <tr>

  <td>14</td>

  <th scope="row">Tofu</th>

  <td>40 - 100 g pkgs.</td>

  <td>$ 23.25</td>

  <td>35</td>

 </tr>

 ...

 ...

</table>

 

Specifying the headers Attribute for Cells in a GridView Control

While the accessibility improvements that result from setting the RowHeaderColumn and UseAccessibleHeader properties is admirable, and a huge advance on the abilities of the grid controls in ASP.NET 1.x, it's relatively easy to improve on this even more by adding in the headers attribute to each non-header cell.

 

  • Each cell in every row that is not a <th> header cell should have a headers attribute that identifies the row and column headers for this cell. For example, referring back to Figure 3, the cell containing the price of Tofu should carry the attribute: headers="id-of-Price-column-header, id-of-Tofu-row-header". If the column and row headers have ID values "ColumnHeader_Price" and "RowHeader_14" then the headers attribute should be: headers="ColumnHeader_Price,RowHeader_14".

 

To achieve this, we have to "interfere with" the generation of the output of the GridView control, by handling one of the events that is raised when each row is being created. The ideal event is RowDataBound, and to handle this we simply add the OnRowDataBound attribute to the declaration of the GridView control, specifying the name of the event handler we want to execute as each row is bound to the data source and the cells for the resulting HTML table row are being generated:

 

<asp:GridView id="MyGrid" runat="server"

     ...

     OnRowDataBound="AddHeadersAttr">

 

The AddHeadersAttr event handler is shown in the next two listings. While it might look complicated, all it does is examine each row - as it is being data bound - and add the appropriate attributes to each cell in that row. If the current row is the header row for the grid (denoted by having the value Header from the DataControlRowType enumeration for its RowType property), the code adds an id attribute to each <th> cell that is created in this row. The value used is the text string "ColumnHeading_" concatenated with the column heading text:

 

Sub AddHeadersAttr(ByVal sender As Object, ByVal e As GridViewRowEventArgs)

 

  If e.Row.RowType = DataControlRowType.Header Then

 

    ' this is the column header row, so add ID to each column using column name

    ' NOTE: cannot set ID property because this includes the ID of all parent

    ' controls as well, for example "MyGrid_ctl1_3" instead of just "3"

    For i As Integer = 0 To e.Row.Cells.Count - 1

      e.Row.Cells(i).Attributes.Add("id", _

                     "ColumnHeader_" & MyGrid.Columns(i).HeaderText)

    Next

 

  ...

 

If the current row in not a header row, we then check if it’s a data row (a row that is bound to the source data rowset). This type of row is denoted by the value DataRow for the RowType property. If it is a data row, it will have one value that is displayed in a <th> cell (the row header) and the remaining values displayed in <td> cells. So, as we iterate through the cells in the row, we have to check the control type.

 

If it's the row header (of type DataControlFieldHeaderCell), we add an id attribute to it as we did previously for the column headers, but this time using the text "RowHeader_" and the product ID value from this row. We can obtain the product ID from the DataKeys collection of the GridView control because we specified the attribute DataKeyNames="ProductID" when we declared the GridView control.

 

If the current cell in this row is an ordinary <td> data cell (of type DataControlFieldCell), we must instead add the appropriate headers attribute to it. This contains the ID values of the matching column and row headers, which can be built up using the current column name and the product ID (from the DataKeys collection) for the current row.

 

  ...

  ElseIf e.Row.RowType = DataControlRowType.DataRow Then

 

    ' this is a data row

    For i As Integer = 0 To e.Row.Cells.Count - 1

      Dim oCell As Object = e.Row.Cells(i)

      If TypeOf oCell Is DataControlFieldHeaderCell Then

 

        ' this is the row header, so add an ID to it using value of ProductID

        CType(oCell, DataControlFieldHeaderCell).Attributes.Add("id", _

              "RowHeader_" & MyGrid.DataKeys(e.Row.RowIndex).Value.ToString())

 

      Else

 

        ' this is a data cell, so add the appropriate headers attribute

        CType(