Beruflich Dokumente
Kultur Dokumente
Stephen Walther
SuperExpert.com
Applies to:
Microsoft ASP.NET 2.0
Microsoft Visual Studio 2005
Microsoft Visual Web Developer
Summary: Microsoft ASP.NET 2.0 has many features to help you design and build Web
sites that are compliant with XHTML and accessibility standards. This article looks at
how and why you should be building these standards-compliant sites. (78 printed pages)
Contents
Introduction
Building XHTML Web Sites
Versions of the XHTML Standard
Creating XHTML Pages
XHTML and ASP.NET Controls
Validating XHTML Pages
XHTML and DOCTYPE Switching
XHTML and MIME Types
Configuring XHTML Conformance
Accessibility Standards
Accessibility Improvements in ASP.NET 2.0
Creating Accessible Images
Creating Accessible Forms
Creating Accessible Navigation
Creating Accessible Data
Creating Accessible XHTML
Creating Accessible Scripts
Validating Pages for Accessibility
Accessing the Amazon Web Services
The Default Page
XHTML Features of the Default Page
Accessibility Features of the Default Page
The Search Page
XHTML Features of the Search Page
Accessibility Features of the Search Page
The Master Page
XHTML Features of the Master Page
Accessibility Features of the Master Page
Introduction
Web standards enable you to build Web sites that are accessible to the broadest possible
audience with the least amount of work. The promise of Web standards is that you can
design a page once and have the page appear and function in exactly the same way in any
modern browser. For example, when built against standards, a page that was designed to
display a certain way in Microsoft Internet Explorer can appear the same way in other
browsers, such as Mozilla Firefox, Netscape Navigator, Opera, Camino, and Safari,
without requiring you to perform any additional work.
An additional benefit of Web standards is that they make your Web sites more easily
accessible to persons with disabilities. This is a broad audience that includes everyone
from a middle-aged person with failing eyesight, to a person who just broke his or her
arm while skiing, to a person who is completely blind. Standards prevent you from
unintentionally blocking persons with temporary or permanent disabilities from your Web
pages.
The Microsoft ASP.NET 2.0 framework was designed to be the best framework for
building Web sites that meet public Web standards. In particular, every control in the
ASP.NET 2.0 framework was extensively reviewed and tested against both XHTML and
accessibility standards. Furthermore, Microsoft Visual Studio 2005 includes new tools for
validating your Web pages against both XHTML and accessibility standards.
The purpose of this paper is to provide you with an overview of XHTML and
accessibility standards, and explain how you can take advantage of ASP.NET 2.0 and
Visual Studio 2005 to meet these standards. At the end of this paper, you are provided
with a step-by-step walkthrough for creating an ASP.NET 2.0 Web site that satisfies both
XHTML and accessibility standards.
In pursuit of the first goal, the W3C has been steadily removing purely presentational
elements and attributes from HTML (a process that they started with HTML 4.0). For
example, XHTML 1.0 Strict does not include elements such as the <font> tag, or
attributes such as the bgcolor attribute, because these elements and attributes are used
solely to describe the appearance of a document, and they have nothing to do with a
document's structure.
The W3C has been attempting to wean Web site designers and developers away from the
idea that any particular tag should have any particular appearance. For example, you
might think that the purpose of an <h1> tag (the heading tag) is to render large, bold text
in a page. That would be wrong. The <h1> tag is used to mark a heading in a document,
and nothing else. It is up to the browser to determine how the heading tag should be
rendered. A screen reader used by a person with reduced eyesight might read aloud the
contents of a heading tag with a booming, authoritative voice. A PDA, which doesn't
support multiple font sizes, might render the contents of a heading tag with blinking text.
You should not attempt to use page elements, such as the <h1> tag, to control the
appearance of a Web page. Instead, you should indicate the appearance of a Web page
through the use of Cascading Style Sheets. Preferably, the Cascading Style Sheets should
be external Cascading Style Sheets. Use tags and attributes to mark up the structure of a
document, and use Style Sheets to control the document's presentation.
The second goal of XHTML is to enforce the stricter rules of XML on HTML developers.
In the words of the W3C, "XHTML 1.0 is a reformulation of HTML 4.01 as an XML 1.0
application" (http://www.w3.org/MarkUp/). In other words, when you build a Web page
using XHTML, you are actually creating an XML document.
An XML document has a much stricter syntax than an HTML document. For example,
XML is case-sensitive, all XML attributes must be quoted, and XML tags cannot overlap.
Forcing Web site developers and designers to follow the rules of a more demanding
language has many benefits.
One benefit is that pages written with XHTML markup are more cross-browser, cross-
device, and cross-operating system compatible. If you open a traditional HTML page in a
browser, the browser will make every effort to render the page. The browser will attempt
to render the page even if your HTML is a total mess. For example, Internet Explorer
(and Firefox and Opera) will display the following HTML page just fine.
Internet Explorer happily displays this page, even though the page is missing opening
<html> and <body> tags, the <b> tag has no matching closing tag, and the case of the
opening and closing <i> tags is inconsistent. All major browsers will accommodate
almost any "tag soup" of HTML tags and desperately attempt to render something.
If you write your Web pages with the stricter rules of XHTML, however, there is more of
a chance that your Web pages will work consistently with current browsers, and that they
will continue to work with new versions of current browsers introduced in the future.
Few companies have the resources to test their Web sites against every browser running
on every operating system and every device. If you write your pages against Web
standards, you don't have to.
XHTML 1.0 Transitional contains all of the tags and attributes from HTML 4.01
Transitional. The XHTML 1.0 Transitional standard was introduced to enable existing
HTML designers and developers to migrate to XHTML without experiencing too much
shock and pain.
XHTML 1.0 Strict differs from XHTML 1.0 Transitional by enforcing a cleaner
separation between document structure and presentation. Unlike XHTML 1.0
Transitional, XHTML 1.0 Strict forces you to use Cascading Style Sheets to control the
appearance of your pages.
XHTML 1.0 Frameset documents are intended to be documents that use the <frameset>
tag to partition a browser into multiple frames (XHTML 1.0 Transitional and Strict pages
cannot contain the <frameset> tag).
The W3C has also published XHTML 1.1 as a recommendation (on May 31, 2001).
XHTML 1.1 is very similar to XHTML 1.0 Strict. The primary difference is that XHTML
1.1 can be extended with additional modules in order to support new elements. You can,
for example, build XHTML 1.1 pages that also include elements from the MathML (the
Mathematical Markup Language), or SVG (the Scalable Vector Language), or a custom
module of your own creation.
Finally, the W3C is working on a recommendation for XHTML 2.0. Because XHTML
2.0 is still in the draft stage, and no Web browser currently supports this standard, we
won't discuss it in this paper.
The ASP.NET 2.0 framework and Visual Studio 2005 are targeted at XHTML 1.0
Transitional. This is the least restrictive of the XHTML standards, and it is the standard
that is the most compatible with existing HTML pages. However, you can also build
ASP.NET 2.0 pages that target the XHTML 1.0 Strict standard or even the XHTML 1.1
standard (see the later section, Configuring XHTML Conformance).
A valid XHTML page must include an XHTML DOCTYPE before any of its
content. When you create a new ASP.NET page in Visual Studio 2005 or
Microsoft Visual Web Developer, the correct DOCTYPE for XHTML 1.0
Transitional is automatically included in the page. Here are the four standard
XHTML DOCTYPES:
XHTML 1.1
The opening <html> tag of an XHTML page must specify a default namespace of
http://www.w3.org/1999/xhtml. Here's a sample of a valid opening <html> tag for
an XHTML 1.0 Transitional page.
<html xml:lang="en" lang="en">
XML is case-sensitive. Therefore, there is a difference between the <p> tag and
the <P> tag. Only the former is a valid XHTML paragraph tag.
Always wrap attribute values in either double or single quotation marks. For
example, the following is invalid XHTML.
<a href=SomePage.aspx>Next</a>
In this case, the href attribute is missing quotation marks. The following is valid
XHTML.
<a href="SomePage.aspx">Next</a>
You can configure Visual Studio 2005 and Visual Web Developer to automatically
quote attribute values, by selecting the menu option Tools, Options, Format.
5. All non-empty elements that have an opening tag must have a matching closing
tag.
If you have an opening <p> tag, then you must include a closing </p> tag to mark
the end of the paragraph. In the case of tags that never contain any content, such
as the <br> tag, you can either supply a both an opening and closing <br></br>
tag, or you can use the empty element shorthand <br />.
Furthermore, existing HTML browsers have problems with the empty element
shorthand <br /> unless you are careful to add a space before the closing slash.
So, you should add a <br> element to a page using <br [space] /> and not <br/>.
You can nest tags, but you are not allowed to overlap tags. For example, the
following XHTML is valid.
All attributes must have a value, even when it looks a little strange. For example,
the tag <input type="checkbox" checked /> is invalid XHTML, because the
checked attribute does not have a value. The tag should be written <input
type="checkbox" checked="checked" />.
In HTML, you use the name attribute to identify <a>, <applet>, <form>,
<frame>, <iframe>, <img>, and <map> elements. While you can use the name
attribute when building XHTML 1.0 Transitional pages, the name attribute has
been removed from the XHTML 1.0 Strict and XHTML 1.1 standards. You should
use the id attribute to identify these elements instead.
If you use special characters such as < or &, or entity references such as < or
& in a script or style sheet, then you'll need to mark the contents of your
script or style sheet as a CDATA (character data) section, as follows.
<script type="text/javascript">
<![CDATA[
function isLess(a, b) {
if (a < b)
return true;
}
]]>
</script>
Notice that the JavaScript function contained in the script includes a < character.
If you do not wrap the script in a CDATA section, then the < character would be
interpreted as marking the start of an XHTML tag.
Using a CDATA section will not work with all browsers. For example, Internet
Explorer considers a CDATA section in a <script> tag a syntax error. You can
avoid this problem by adding JavaScript comments, as follows.
<script type="text/javascript">
/* <![CDATA[ */
function isLess(a, b) {
if (a < b)
return true;
}
/* ]]> */
</script>
JavaScript uses /* and */ to mark the beginning and end of a comment. Therefore,
the CDATA section is hidden from the JavaScript, but not from the browser that
parses the page. In general, it is a better idea to place your style rules and scripts
in external files and reference the files from your XHTML pages. Using external
style sheets and scripts enables you to avoid all of these issues.
Three points need to be clarified here. First, the source code of a page that contains
ASP.NET controls will not validate as XHTML. When validating an ASP.NET page, you
need to validate the rendered content of the page (everything that you see when you
select View Source in Internet Explorer) and not the source of the page.
Second, there is nothing that prevents you from writing invalid XHTML when creating
an ASP.NET page. You can, of course, add any tag to an ASP.NET page that you want.
For example, if you add a <font> tag to your page, then your page will not validate as
XHTML 1.0 Strict.
Finally, there are no guarantees when you use custom ASP.NET controls. If you buy a
third-party ASP.NET control—for example, a super enhanced DataGrid control—the
control may or may not render valid XHTML. It's the control vendor's responsibility to
do the right thing.
You can hover your mouse over any squiggle to view a ToolTip that contains the
validation error or warning message (see Figure 1). Alternatively, you can view a list of
validation errors and warnings in the Error List window (select View, Other Windows,
Error List).
Figure 1. Validating an XHTML document (Click the graphic for a larger image.)
By default, Visual Studio 2005 and Visual Web Developer are configured to validate
pages against the Internet Explorer 6.0 schema. If you want to validate your pages against
an XHTML schema, then you need to select one of the XHTML schemas from the drop-
down list in the toolbar, or you can select Tools, Options, Validation to select a target
schema.
As an alternative, you can validate your ASP.NET pages by using the W3C validation
service. The W3C validation service enables you to validate a page by supplying a URL
or by uploading the source of an XHTML page.
The Opera browser (Opera 7+) supports the same two rendering modes (Quirks and
Standards) as Internet Explorer (for details, see
http://www.opera.com/docs/specs/doctype/).
Mozilla Firefox 1+ supports three rendering modes: Quirks mode, Almost Standards
mode, and Standards mode. Firefox's Almost Standards mode corresponds to Internet
Explorer's and Opera's Standards mode. When a page contains a valid XHTML 1.0
Transitional DOCTYPE (and it is served with a text/html MIME type), Firefox renders
the page in Almost Standards mode. When a page contains either an XHTML 1.0 Strict or
XHTML 1.1 DOCTYPE (or the page is served with an XML MIME type), the page is
rendered in Standards mode (for details, see http://www.mozilla.org/docs/web-
developer/quirks/doctypes.html).
You can determine a browser's current rendering mode by temporarily adding the
following client-side script to a page (this script works in the latest versions of Internet
Explorer, Firefox, and Opera).
<script type="text/javascript">
alert( document.compatMode );
</script>
You need to care about the browser rendering mode, because it affects the way in which
Cascading Style Sheets are applied to the page. If you convert your existing HTML pages
into XHTML pages, they might look very different when you open them in your browser.
For example, Internet Explorer calculates the size of page elements in different ways,
depending on the rendering mode (it uses a different CSS Box Model). In Quirks mode,
the width of an element is calculated by summing the width of the element's content,
padding, borders, and margins. In Standards mode, the width of an element is calculated
by taking into account only the width of the element's content.
If you want your Web pages to appear in the same way across browsers, then it is a good
idea to trigger Standards mode (in Internet Explorer and Opera) and Almost Standards
mode (in Firefox), by including an XHTML 1.0 Transitional DOCTYPE. Fortunately,
Visual Studio 2005 and Visual Web Developer automatically add this DOCTYPE, by
default, to every new page ASP.NET page.
A browser uses the MIME type to determine how a page (or other resource) should be
handled. For instance, if a browser gets a file from a Web server that has a recognizable
image MIME type, the browser attempts to interpret and render the file as an image. If a
browser gets a file that has an application/msword MIME type, the browser might
automatically open Microsoft Word to display the document (the exact behavior here
depends on the browser and how it is configured).
The W3C has introduced a MIME type for XHTML documents. This new MIME type is
application/xhtml+xml. The W3C recommends that you use the application/xhtml+xml
MIME type when serving XHTML documents, because XHTML pages should be
interpreted in a stricter way than legacy HTML pages.
You can serve an ASP.NET page with a particular MIME type by including the
ContentType attribute in a page directive. For example, including the following directive
at the top of an ASP.NET page causes the page to be served as application/xhtml+xml.
There is one glaring problem with the W3C's recommendation: not all browsers
recognize application/xhtml+xml. In particular, Internet Explorer (the most popular Web
browser in the history of the world) does not recognize the application/xhtml+xml MIME
type. Therefore, serving your XHTML pages using the recommended
application/xhtml+xml MIME type is not a viable option.
There are three ways that you can work around this problem. You can serve your
XHTML pages by using the text/html MIME type, you can serve your XHTML pages by
using the application/xml (or text/xml) MIME type, or you can use content negotiation.
Let's explore each of these options.
The first option, serving your pages as text/html, is the easiest option. An ASP.NET page
is served with this MIME type by default. Better yet, the W3C recommends this option
when serving pages to existing HTML browsers (see http://www.w3.org/TR/xhtml-
media-types/). If you are creating XHTML 1.0 Transitional pages, and the primary
audience for your Web application is using a browser that does not understand the
application/xhtml+xml MIME type, then serving your pages as text/html seems perfectly
sensible. After all, the XHTML 1.0 Transitional standard was introduced to make it easier
for developers to migrate existing HTML pages to XHTML.
This claim is controversial. For example, Ian Hickson argues that XHTML pages should
never be served as text/html, because this option promotes sloppy, broken XHTML pages
(see http://hixie.ch/advocacy/xhtml). He recommends that authors stick to HTML 4.0
until more browsers completely support XHTML standards.
The second option is to serve your XHTML pages as XML, using either the
application/xml or text/xml MIME type. When Internet Explorer is served an XML
document, the document is parsed as an XML document and rendered to the browser.
(The document is represented by the XML DOM exposed by the
document.XMLDocument object.)
The advantage of serving an XHTML document as XML is that any problems with the
XHTML document will be caught by Internet Explorer's XML parser. For example, if
your document contains overlapping tags, or if the value of an attribute is not wrapped in
quotation marks, then the document is not rendered, and an error message is displayed
(see Figure 4). XHTML purists consider this behavior a good thing, because it prevents
you from writing malformed XHTML.
Figure 4. Displaying XML in Internet Explorer
The problem with this approach is that Internet Explorer, by default, renders the source of
an XML document. So, if you serve an XHTML document as XML, your Web site
visitors will see the source of your XHTML documents and not the desired rendered
output. The W3C suggests a "trick" for getting around this problem (see
http://www.w3.org/MarkUp/2004/xhtml-faq#ie): If you transform an XHTML document
into HTML by using an XSLT transformation, then your document will be parsed as
XML and displayed as HTML.
For example, the ASP.NET page in Listing 1 will be served as an XML document but
transformed into an HTML document. The resulting page displays correctly in Internet
Explorer, Opera, and Firefox.
Listing 1. XMLPage.aspx
The page directive causes this page to be rendered as text/xml. The second line in the
listing refers to an XSLT style sheet, named copy.xsl, that performs an identity
transformation on the current document. In other words, it does absolutely nothing,
except copy all of the elements from the original XML document into a new HTML
document. The source for copy.xsl is contained in Listing 2.
Listing 2. Copy.xsl
<stylesheet version="1.0"
xmlns="http://www.w3.org/1999/XSL/Transform">
<template match="/">
<copy-of select="."/>
</template>
</stylesheet>
This solution works, but it doesn't seem very elegant. You do get the extra validation step
when the XML document is parsed. However, if you are building your ASP.NET pages in
Visual Studio 2005 or Visual Web Developer, the same validation is performed by the
development environment in Source view. At the end of the day, Internet Explorer
receives the same document as it would get if you had sent it text/html.
The third option, content negotiation, best combines the spirit of the W3C
recommendations with the greatest degree of browser compatibility (see
http://www.w3.org/2003/01/xhtml-mimetype/content-negotiation). When you use content
negotiation, you serve an ASP.NET page with different MIME types to different
browsers. If a browser claims that it supports XHTML, then you serve it XHTML;
otherwise, you serve the browser the page with the text/html MIME type.
The Global.asax in Listing 3 contains the necessary code for serving different MIME
types to different browsers. If you add this file to your Web project, then the MIME type
of every ASP.NET page will be modified with each request. When a page is served to
Firefox or Opera, the page will be served as application/xhtml+xml. Internet Explorer 6,
on the other hand, will receive text/html pages.
Listing 3. Global.asax
<script runat="server">
</script>
For example, if you are feeling ambitious, you might decide to build an XHTML 1.0
Strict, or even an XHTML 1.1, Web site. After all, the goal of the XHTML 1.0
Transitional standard is to act as a springboard to these more restrictive standards.
Because, by default, the ASP.NET 2.0 framework targets XHTML 1.0 Transitional, some
of the ASP.NET controls will render attributes that are not compatible with XHTML 1.0
Strict or XHTML 1.1.
Alternatively, you might discover that the XHTML 1.0 Transitional standard is too
restrictive. Microsoft had to make several changes to existing ASP.NET 1.1 controls in
order to comply with the XHTML 1.0 Transitional standard. Some of these changes
might break an existing ASP.NET 1.1 Web site.
In order to keep everyone happy, Microsoft created a new configuration option, named
xhtmlConformance, that you can set in your Web site's configuration file. The new
configuration option enables you to specify the level of XHTML conformance of your
Web pages. It looks like this.
<configuration>
<system.web>
<xhtmlConformance
mode="transitional" />
</system.web>
</configuration>
By default, xhtmlConformance is set to the value transitional. However, you can also
set this option to the value strict or legacy.
If you set the xhtmlConformance option to strict, then certain attributes will no longer
be rendered by the standard ASP.NET controls. For example, the ASP.NET <form>
control will no longer render a name attribute. Unless your ASP.NET pages contain (non-
standards-compliant) client-side scripts, you won't notice any changes when switching
from transitional to strict mode.
If you set the xhtmlConformance option to legacy, then the ASP.NET framework will
revert to ASP.NET 1.1 rendering behavior for some elements and attributes (but not all).
In this case, the ASP.NET framework will render content that is not compatible with any
XHTML standard, and your pages will no longer validate against the XHTML standards.
For example, in legacy mode, the <br> tag is not rendered with its required XHTML
closing slash (<br />). Setting xhtmlConformance to legacy mode only makes sense
when you run into a problem migrating an existing ASP.NET 1.1 application to ASP.NET
2.0.
It is worth emphasizing, once again, that a broad audience of Web site users has one form
of disability or another. Think of the members of your own family and consider how
many of them would have trouble interacting with a Web page. I have aging relatives who
are blind or who are losing their motor coordination. My guess is that many readers of
this paper also have aging parents or grandparents who would find it challenging to use
most Web sites.
There are many good reasons for building accessible Web sites: financial, moral, legal,
and so on. Let's concentrate, however, on the legal motivations. In the United States, any
Web site developed by a federal agency is required by Section 508 of the Rehabilitation
Act to be accessible to persons with disabilities. This law applies to federal agencies and
companies that contract with federal agencies (see http://www.section508.gov).
Other countries have similar requirements. For example, in Canada, the Treasury Board
Common Look and Feel Standards require that Web sites developed by federal agencies
be accessible. In Australia, the Disability Discrimination Act requires that all Web sites
hosted on Australian servers (regardless of whether or not it is a government Web site) be
accessible. (For more details on accessibility laws, see http://www.w3.org/WAI/Policy.)
I don't know any Web site developer who would intentionally build a Web site that is not
accessible to persons with disabilities. The problem is that most developers are not
familiar with the various accessibility standards.
In the following sections of this paper, you'll be provided with an overview of the two
most important accessibility standards: the WCAG and Section 508 standards You'll also
learn how to build accessible Web pages by using ASP.NET controls. Finally, you'll learn
how to "validate" your Web pages for accessibility.
Accessibility Standards
Almost all accessibility standards and laws derive from the W3C Web Content
Accessibility 1.0 Guidelines (WCAG). These guidelines were first published by the
World Wide Web Consortium as a recommendation on May 5, 1999 (see
http://www.w3.org/TR/WCAG10).
The WCAG consists of 14 guidelines. Each guideline, in turn, consists of one or more
checkpoints that further clarify the guideline. Each checkpoint is ranked with a priority
between 1 and 3. To make it easier to implement the guidelines, the W3C has published a
set of documents that contain techniques for following the guidelines (see
http://www.w3.org/TR/WCAG10-TECHS/).
You can claim different levels of conformance with the WCAG guidelines. If you claim
that your Web site satisfies all priority 1 checkpoints, then you can display a logo that
claims Conformance Level A. When a Web site meets all priority 1 and 2 checkpoints,
the Web site can display a logo for Conformance Level Double-A. Finally, a Web site that
satisfies all checkpoints can display the logo for Conformance Level Triple-A (see
http://www.w3.org/WAI/WCAG1-Conformance.html).
The Section 508 guidelines derive from the WCAG guidelines. In the United States,
federal agencies (and companies who contract with federal agencies) need to be most
concerned with this set of guidelines, because these guidelines have the force of law. You
can read the complete text of the Section 508 guidelines at the Section 508 Web site.
The ASP.NET 2.0 framework was designed to enable you to meet all WCAG priority 1
and priority 2 checkpoints, and all Section 508 guidelines. These guidelines were taken
very seriously. Every developer working on the ASP.NET 2.0 framework was required to
review and test every ASP.NET control for accessibility. Furthermore, every developer
had a screen reader installed on his or her desktop so that pages could be tested against
the guidelines.
Each and every image in a Web page should include an alt attribute. The alt attribute is
used to represent alternate text read by a screen reader or other assistive device. Here's
how you use the alt attribute.
The alt attribute should contain a description of the image. It should never, under any
circumstances, simply contain the filename of the image. The purpose of the alt attribute
is to convey the same information to someone who is blind as the image conveys to
someone who is sighted. Writing the value of an alt attribute requires human
interpretation of the meaning of the element. For this reason, the process of creating alt
attributes cannot be automated.
Every ASP.NET control that displays an image includes a method for supplying alternate
text for the image. For example, the ASP.NET Image control includes an AlternateText
property. If you use an Image control, then you need to set the AlternateText attribute to
a meaningful value.
<asp:Image ImageUrl="Products23.gif"
AlternateText="Image of Products" Runat="Server" />
If an image is used only as a design element, then you should set its alt attribute to an
empty string. If an image has no useful information to convey, then there is no reason to
clutter up a screen reader's narration of the page.
Special measures had to be taken in the ASP.NET 2.0 framework to enable you to render
empty AlternateText. If you assign empty text to an attribute of an ASP.NET control,
then the ASP.NET control will not render the attribute at all. For example, imagine that
you add the following ASP.NET Image control to a page.
Notice that the alt attribute has disappeared. This is the default behavior of all ASP.NET
control attributes. When you do not assign an attribute a value, it is not rendered.
Unfortunately, in this case, we really want to render an empty value for the alt attribute.
To work around this problem, a new property was introduced into the ASP.NET 2.0
framework to enable you to display empty alternate text with an Image control: the
GenerateEmptyAlternateText property.
<asp:Image ImageUrl="PageDivider.gif"
GenerateEmptyAlternateText="true" Runat="Server" />
The longdesc attribute accepts either a relative or absolute URL for its value. The URL
should link to a page that contains a textual description of the contents of the image.
Here's a sample of how you can use this attribute with the <img> tag.
<asp:Image
ImageUrl="OrgChart.gif"
AlternateText="Company Organization Chart"
DescriptionUrl="/OrgChartDescription.aspx"
Runat="server" />
<table>
<tr>
<td>First Name:</td>
<td><input name="txtFirstName" /></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="txtLastName" /></td>
</tr>
</table>
This form displays input fields for a person's first name and last name. In this case,
because the form is displayed in a table, it might be difficult for a user of a screen reader
to associate the proper label with the proper form field. In HTML 4.0, a new tag was
introduced to enable you to associate a form field label with a form field: the <label> tag.
Here's how the previous form should be written using a <label> tag.
<table>
<tr>
<td><label for="txtFirstName">First Name:</label></td>
<td><input name="txtFirstName" id="txtFirstName" /></td>
</tr>
<tr>
<td><label for="txtLastName">Last Name:</label></td>
<td><input name="txtLastName" id="txtLastName" /></td>
</tr>
</table>
The <label> tag explicitly associates the form field labels with their corresponding form
fields. Notice that the <input> fields include an id attribute, because the value of the for
attribute must be an input field's id and not its name attribute.
Normally, the ASP.NET Label control generates a <span> tag. However, if you provide
an AssociatedControlId property when declaring an ASP.NET Label control, then the
control renders a <label> tag. Here's how you can generate an accessible form with
ASP.NET Label and TextBox controls.
<table>
<tr>
<td><asp:Label AssociatedControlID="txtFirstName"
runat="server">First Name:</asp:Label></td>
<td><asp:TextBox ID="txtFirstName" runat="server" /></td>
</tr>
<tr>
<td><asp:Label AssociatedControlID="txtLastName"
runat="server">Last Name:</asp:Label></td>
<td><asp:TextBox ID="txtLastName" runat="server" /></td>
</tr>
</table>
When providing a label for an ASP.NET control, you should use the ASP.NET Label
control instead of the HTML <label> tag. When you assign an ID to an ASP.NET control
such as the TextBox control, the ID that is rendered to the browser might be a different
ID than the ID that you assigned to the control. Therefore, if you use a <label> tag, the
ID in the <label> tag might not match the ID of the rendered TextBox control. If, on the
other hand, you use the ASP.NET Label control, you don't have to worry about this issue.
Large forms can also create problems for individuals interacting with a Web page through
a screen reader. When listening to a large form, it is easy to lose track of the section of the
form that you are listening to. When displaying a large form, it is a good idea to divide
the form into bite-sized chunks. You can divide a single form into multiple sections by
using the <fieldset> tag. Here's a sample of how you can use this tag.
<fieldset>
<legend>Contact Information</legend>
</fieldset>
<fieldset>
<legend>Payment Information</legend>
</fieldset>
</div>
</form>
This form is divided into two subforms, using the <fieldset> tag. The <legend> tag is
used to label the purpose of the subforms. When displayed in Internet Explorer, Firefox,
and Opera, the subforms are visually divided into separate areas by a border (see Figure
5). However, it is important to keep in mind that the primary purpose of the <fieldset>
tag is accessibility. If you don't like the visual appearance of the <fieldset> tag, then you
can modify the appearance of the tag through a style sheet rule, or you can completely
hide the tag by using the CSS display or visibility attribute.
Figure 5. The <fieldset> tag
People with low vision are not the only users of a Web page who might find a Web form
challenging. Individuals who have reduced motor coordination can also experience
difficulty when interacting with a form.
When building a Web form, it is always a good idea to include accesskey and tabindex
attributes for each of the form fields. The accesskey attribute enables someone who
cannot use a mouse to navigate directly to any form field. The tabindex attribute enables
you to control the tabbing order of the form fields. Both attributes make life easier for
someone who must interact with your page through a keyboard (or an assistive device
that acts like a keyboard).
Here's a sample form that uses both the accesskey and tabindex attributes.
<asp:Label
AssociatedControlID="txtFirstName"
AccessKey="f"
runat="server"><u>F</u>irst Name</asp:Label>
<asp:TextBox
id="txtFirstName"
TabIndex="1"
Runat="server" />
<br />
<asp:Label
AssociatedControlID="txtLastName"
AccessKey="l"
runat="server"><u>L</u>ast Name</asp:Label>
<asp:TextBox
id="txtLastName"
TabIndex="2"
Runat="server" />
The tabindex attribute is used to control the tab order of the form fields. Because the first
form field has a tabindex value of 1, any other elements in the page that appear before
the form are skipped when the user first hits the TAB key.
When using Internet Explorer or Firefox, pressing ALT+F automatically moves focus to
the First Name text box. If you press ALT+L, then focus is automatically moved to the
Last Name text box. When using Opera, you must first press SHIFT+ESC before
selecting an access key.
Notice that the first letter of both the First Name and Last Name labels are underlined.
Underlining the letter provides the user of the Web site with a visual indication of the
access keys. This is the standard way to mark access keys in Microsoft Windows
applications. However, there are other proposed methods for indicating access keys in a
form (see http://www.cs.tut.fi/~jkorpela/forms/accesskey.html).
One problem with using underlines to indicate access keys is the fact that you cannot
underline characters in a button, and hyperlinks are already underlined. For example, the
following Button control does not work as you would expect and hope.
<asp:Button
Text="<u>S</u>ubmit"
Runat="server" />
When this ASP.NET Button control is rendered, the actual text <u>S</u>ubmit is
displayed, instead of an underlined S character. The ASP.NET Button control renders an
HTML <input type="submit"> tag, and, unfortunately, the <input type="submit"> tag
does not support underlining.
You might think that you could get around this problem by using a style rule.
Unfortunately, there currently is no cross-browser compatible method of underlining a
single character in an <input type="submit"> tag using Cascading Style Sheets.
You can get around this problem if you are willing to use client-side JavaScript in the
page. The page in Listing 4 contains JavaScript that displays or hides all of the access
keys, depending on whether the ALT key is held down. When you hold down the ALT
key, boxes pop up, displaying the access key keyboard combinations (see Figure 6). This
script works in both Internet Explorer and Firefox (Opera does not use the ALT key to
select access keys).
Figure 6. AccessKeys.aspx
Listing 4. AccessKeys.aspx
<style type="text/css">
.accessKey
{
display:none;
position:absolute;
z-index:5000;
padding:3px;
border:solid 1px black;
background-color: #ffffe0
}
</style>
<script type="text/javascript">
/* <![CDATA[ */
window.onload = function()
{
document.onkeydown = displayAccessKeys;
}
function displayAccessKeys(e)
{
if (!e) e = window.event;
if (e.keyCode == 18)
{
toggleAccessKeys();
document.onkeydown = null;
document.onkeyup = hideAccessKeys;
}
}
function hideAccessKeys(e)
{
if (!e) e = window.event;
if (e.keyCode == 18)
{
toggleAccessKeys();
document.onkeyup = null;
document.onkeydown = displayAccessKeys;
}
}
function toggleAccessKeys()
{
var spans = document.getElementsByTagName('span');
for (var k=0;k<spans.length;k++)
if (spans[k].className == 'accessKey' )
{
if ( 'inline' != spans[k].style.display)
spans[k].style.display = 'inline';
else
spans[k].style.display = 'none';
}
}
/* ]]> */
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<table>
<tr>
<td>
<asp:Label
ID="lblFirstName"
AssociatedControlID="txtFirstName"
AccessKey="f"
runat="server">First Name</asp:Label>
</td>
<td>
<asp:TextBox ID="txtFirstName" runat="server" />
<span class="accessKey">access key is f</span>
</td>
</tr>
<tr>
<td>
<asp:Label
ID="lblLastName"
AssociatedControlID="txtLastName"
AccessKey="l"
runat="server">Last Name:</asp:Label>
</td>
<td>
<asp:TextBox ID="txtLastName" runat="server" />
<span class="accessKey">access key is l</span>
</td>
</tr>
<tr>
<td colspan="2">
<asp:Button Text="Submit" runat="server" />
<span class="accessKey">access key is s</span>
</td>
</tr>
</table>
</div>
</form>
</body>
</html>
The page in Listing 4 contains a style sheet and client-side JavaScript. The style sheet
hides the contents of any <span> tag identified by the accessKey class. The JavaScript
detects when the ALT key has been pressed, and reveals the contents of the <span> tags.
Note that this page will function even when style sheets and JavaScript are disabled on a
Web browser. In that case, the access key help will always be displayed (see Figure 7).
Unfortunately, if you are forced to use a screen reader, this is precisely your experience
when you visit almost any Web page. Most Web sites include on every page a navigation
bar that contains a list of links to the various sections of the Web site. If you are using a
screen reader, then you must listen to each of these navigation links, one at a time,
whenever you open a page.
With one simple modification to a navigation bar, you can dramatically improve the
accessibility of your Web pages. You simply need to add a method for someone to skip all
of the navigation links. You can do this with a "Skip Navigation link."
For example, the CNN Web site includes a navigation bar that lists the different sections
of the CNN Web site (World, U.S., Weather, and so on). However, the designers of the
CNN Web site have done something smart. If you view the source of the page, you'll
notice that the following link appears above the navigation bar.
When you view the home page of the CNN Web site, you never see this link. The image
contained in the link is a transparent single-pixel image. However, if you access this page
with a screen reader, then the alternate text associated with the image is read. A person
who is blind can choose to skip all of the navigation links and move directly to the main
content area of the Web page (The equivalent of pressing 0 in an automated voice system
and navigating directly to the operator).
Skip Navigation links have been integrated into several of the standard ASP.NET 2.0
controls. In particular, the Menu, TreeView, SiteMapPath, Wizard, and
CreateUserWizard controls all support Skip Navigation links.
For example, the page in Listing 5 includes an ASP.NET Menu control. This control is
used to display a list of links to other pages in the Web site.
Listing 5. SiteMenu.aspx
<asp:Menu
id="Menu1"
Runat="server">
<Items>
<asp:MenuItem Text="Home" NavigateUrl="Home.aspx" />
<asp:MenuItem Text="Products" NavigateUrl="Products.aspx" />
<asp:MenuItem Text="Services" NavigateUrl="Services.aspx" />
<asp:MenuItem Text="About" NavigateUrl="About.aspx" />
</Items>
</asp:Menu>
<hr />
</div>
</form>
</body>
</html>
If you view the source of the page in Listing 5, you'll see that the following link appears
at the top of the menu.
This link contains a zero-width and zero-height image that does not appear when you
view the page. However, someone accessing this page through a screen reader can select
the Skip Navigation link to skip to the end of the menu.
By default, the Skip Navigation link contains the text Skip Navigation Links. You can
modify this value by changing the Menu control's SkipLinkText property.
Presenting information in HTML tables, if not done right, can create accessibility
problems. When the content of an HTML table is read aloud, you can easily lose track of
your current position in the table. For example, imagine that you use an HTML table to
display a list of product information. When the content of the table is read by a screen
reader, you can easily get confused about whether a certain table cell represents
information about the product name, the number of products on order, or a code for the
warehouse that stores the products.
When you look at an HTML table, you can determine the meaning of a particular cell by
glancing at either the column or row heading. In order to make tables accessible to
persons who are using screen readers, you need to explicitly mark the table headings, and
explicitly associate the headings with each cell.
When you create a table to display data, you should always use the proper tags to mark
the column and row headings. A table heading should always be marked with the <th>
tag, as follows.
<table>
<thead>
<tr>
<th>Product Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>Milk</td>
<td>$2.33</td>
</tr>
<tr>
<td>Cereal</td>
<td>$5.61</td>
</tr>
</tbody>
</table>
In this example, the <th> tag is used to mark the two column headings: Product Name
and Price.
Some designers avoid using the <th> tag because they do not like the default visual
appearance of it. In most browsers, the contents of a <th> tag are centered and bolded.
However, it is important to remember that tags should never be used to control
presentation. If you want the column headings to look like normal table cells, then you
should add a style rule such as the following.
<style type="text/css">
th {text-align:left;font-weight:normal}
</style>
In order to make a table accessible, you should also explicitly indicate the heading or
headings associated with each cell. There are several attributes that you can use for this
purpose: scope, headers, and axis.
The scope attribute can be used to indicate whether a table heading is a column heading
or a row heading. For example, the following table contains both column headings and
row headings, marked with <th> tags that use the scope attribute.
<table>
<thead>
<tr>
<th></th>
<th scope="col">First Train</th>
<th scope="col">Last Train</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Alewife</th>
<td>5:24am</td>
<td>12:15am</td>
</tr>
<tr>
<th scope="row">Braintree</th>
<td>5:15am</td>
<td>12:18am</td>
</tr>
</tbody>
</table>
This table contains the schedule for the Boston subway Red Line (see Figure 8). Notice
that each of the column headings include a scope="col" attribute, and each of the row
headings include a scope="row" attribute.
The axis attribute enables you to categorize a table heading. For example, in the subway
schedule table, the attribute axis="location" could be added to each heading that
represents a location (the Alewife and Braintree headings). The axis attribute accepts a
comma delimited list of categories.
The page in Listing 6 contains a more complicated version of the Boston subway
schedule that uses both the headers and axis attributes (see Figure 9).
Listing 6. Subway.aspx
<style type="text/css">
caption {color:white;background-color:red;font-size:xx-large}
table {width:500px;border-collapse:collapse}
td,th {padding:5px}
td {border:1px solid black}
tbody th {text-align:right}
.headerRow th {font-size:x-large;text-align:left}
</style>
</head>
<body>
<form id="form1" runat="server">
<div>
<table
summary="This table contains the schedule of train
departures for the Red Line">
<caption>Red Line Schedule</caption>
<thead>
<tr>
<th></th>
<th id="hdrFirstTrain" axis="train">First Train</th>
<th id="hdrLastTrain" axis="train">Last Train</th>
</tr>
</thead>
<tbody>
<tr class="headerRow">
<th id="hdrWeekday" axis="day" colspan="3">Weekday</th>
</tr>
<tr>
<th id="hdrAlewife1" axis="location">Alewife</th>
<td headers="hdrAlwife1 hdrWeekday hdrFirstTrain">5:24am</td>
<td headers="hdrAlwife1 hdrWeekday hdrLastTrain">12:15am</td>
</tr>
<tr>
<th id="hdrBraintree1" axis="location">Braintree</th>
<td headers="hdrBraintree1 hdrWeekday hdrFirstTrain">5:15am</td>
<td headers="hdrBraintree1 hdrWeekday hdrLastTrain">12:18am</td>
</tr>
<tr class="headerRow">
<th id="hdrSaturday" axis="day" colspan="3">Saturday</th>
</tr>
<tr>
<th id="hdrAlewife2" axis="location">Alewife</th>
<td headers="hdrAlewife2 hdrSaturday hdrFirstTrain">8:24am</td>
<td headers="hdrAlewife2 hdrSaturday hdrLastTrain">11:15pm</td>
</tr>
<tr>
<th id="hdrBraintree2" axis="location">Braintree</th>
<td
headers="hdrBraintree2 hdrSaturday hdrFirstTrain">7:16am</td>
<td
headers="hdrBraintree2 hdrSaturday hdrLastTrain">10:18pm</td>
</tr>
</tbody>
</table>
</div>
</form>
</body>
</html>
Notice that each table cell contains a headers attribute. The headers attribute represents
a space delimited list of IDs that correspond to column and row headings. Each cell in the
subway schedule table has an associated location, day, and train heading.
Also, notice that each <th> tag has an axis attribute that is used to represent the category
associated with the heading. For example, the Weekday and Saturday headings are both
associated with the day axis. The First Train and Last Train headings are associated
with the train axis.
Finally, notice that the table in Listing 6 contains both a summary attribute and a
<caption> tag. The summary attribute works very much like the alt attribute. You can
use the summary attribute to provide a description of the table that is not rendered by the
browser. The contents of the <caption> tag, on the other hand, are rendered by the
browser. You should use the <caption> tag to label the purpose of a table.
If you use the ASP.NET 2.0 GridView or DetailsView controls to display database data
in an HTML table, then the generated HTML table is accessible by default. For example,
Listing 7 contains an ASP.NET page that displays the contents of the Titles database table
by using a GridView control.
Listing 7. DisplayTitles.aspx
<asp:GridView
id="grdTitles"
DataSourceId="srcTitles"
Runat="server" />
<asp:SqlDataSource
id="srcTitles"
ConnectionString=
"Server=localhost;Trusted_Connection=true;Database=Pubs"
SelectCommand="Select * FROM Titles"
Runat="server" />
</div>
</form>
</body>
</html>
In Listing 7, the GridView control is bound to a SqlDataSource control that represents
the records from the Titles database table. When the ASP.NET page in Listing 7 is
opened in a browser, the contents of the Titles database table is displayed in an HTML
table (see Figure 10).
Notice that the GridView control automatically generates <th> tags for each of the
column headers. Furthermore, if you select View Source in your browser, you can see
that scope="col" attributes are automatically generated for each column heading.
Notice that the GridView control does not have a Summary property. However, like
most ASP.NET controls, the GridView control supports expando attributes. You can
declare any attribute you please when you declare the GridView control, and the attribute
will be rendered to the browser. So, if you want to add a summary to a GridView, declare
the summary attribute as follows.
<asp:GridView
id="grdTitles"
DataSourceId="srcTitles"
summary="Displays the contents of the Titles database table"
Runat="server" />
The default behavior of the GridView control is great for displaying a simple table of
data in an accessible manner. However, if you need to display a more complicated table,
such as a set of nested tables, then you must perform additional work.
Imagine, for example, that you want to display a list of product categories and, under
each category, you want to display a list of matching products. In other words, you want
to create a single page Master/Detail form (see Figure 11). In that case, you'll need to
include the headers attribute for each table cell.
Listing 8. NestedRepeaters.aspx
<script runat="server">
Sub Page_Load()
Dim dad As New SqlDataAdapter("SELECT * FROM PRODUCTS", _
"Server=localhost;Trusted_Connection=true;Database=Northwind")
dad.Fill(dtblProducts)
End Sub
</script>
<html >
<head runat="server">
<title>Untitled Page</title>
<style type="text/css">
.categoryRow {background-color:yellow}
</style>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Repeater
id="grdCategories"
DataSourceId="srcCategories"
Runat="server">
<HeaderTemplate>
<table>
<thead>
<th id="hdrID">ID</th>
<th id="hdrName">Name</th>
<th id="hdrPrice">Price</th>
</thead>
<tbody>
</HeaderTemplate>
<ItemTemplate>
<tr class="categoryRow">
<th colspan="3"
id='<%# GetCategoryHeader(Container.ItemIndex) %>'>
<%# Eval("CategoryName") %>
</th>
</tr>
<asp:Repeater
id="grdProducts"
DataSource='<%# GetProducts(Eval("CategoryID")) %>'
Runat="server">
<ItemTemplate>
<tr>
<th
id='<%# GetProductHeader(Eval("ProductID")) %>'>
<%# Eval("ProductID") %>
</th>
<td
headers='<%# GetHeaders(Container, "hdrName") %>'>
<%#Eval("ProductName")%>
</td>
<td headers=
'<%# GetHeaders(Container, "hdrPrice") %>'>
<%#Eval("UnitPrice", "{0:c}")%>
</td>
</tr>
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
<FooterTemplate>
</tbody>
</table>
</FooterTemplate>
</asp:Repeater>
<asp:SqlDataSource
id="srcCategories"
ConnectionString=
"Server=localhost;Trusted_Connection=true;Database=Northwind"
SelectCommand="SELECT * FROM Categories"
Runat="server" />
</div>
</form>
</body>
</html>
In Listing 8, the outer Repeater control is used to list the product categories, and the
inner Repeater control is used to list the matching products. Two helper functions are
used to generate the id values for the Category Name and Product ID headers: the
GetProductHeader and GetCategoryHeader functions. A separate helper function,
named GetHeaders, is used to generate the values used with the headers attribute.
The ASP.NET page in Listing 8 generates an HTML table that looks like this.
<table>
<thead>
<th id="hdrID">ID</th>
<th id="hdrName">Name</th>
<th id="hdrPrice">Price</th>
</thead>
<tbody>
<tr class="categoryRow">
<th colspan="3" id='hdrCategory0'>
Beverages
</th>
</tr>
<tr>
<th id='hdrProduct1'>
1
</th>
<td headers='hdrCategory0 hdrProduct1 hdrName'>
Chai 2
</td>
<td headers='hdrCategory0 hdrProduct1 hdrPrice'>
$18.55
</td>
</tr>
<tr>
<th id='hdrProduct2'>
2
</th>
<td headers='hdrCategory0 hdrProduct2 hdrName'>
Chang
</td>
<td headers='hdrCategory0 hdrProduct2 hdrPrice'>
$19.00
</td>
</tr>
.... remainder of the table
Notice that each <td> tag contains a proper headers attribute.
In particular, when designing Web pages, you should separate the structure of a document
from its presentation. Use tags to represent the structure of your Web pages, and use
Cascading Style Sheets to control the appearance of your Web pages.
For example, never use the <blockquote> element purely to indent a block of text. The
purpose of the <blockquote> element is to create a citation for a source. If you want to
indent text, you should use the Cascading Style Sheet margin attribute instead.
You should also strive to use <table> tags only when representing tables of data. While
using <table> tags to layout a Web page is currently a common practice, try to use <div>
tags instead. For example, the page in Listing 9 has a three-column layout, but does not
contain a single <table> tag (see Figure 12).
Figure 12. Tableless page layout (Click the graphic for a larger image.)
Listing 9. Tableless.aspx
#leftColumn
{
float:left;
width:150px;
border:1px solid black;
padding:10px;
}
#middleColumn
{
float:left;
width:430px;
padding:10px;
}
#rightColumn
{
float:right;
width:150px;
border:1px solid black;
padding:10px;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<div id="content">
<div id="leftColumn">
Left column contents...
Left column contents...
Left column contents...
Left column contents...
Left column contents...
Left column contents...
Left column contents...
Left column contents...
Left column contents...
Left column contents...
</div>
<div id="middleColumn">
Middle column contents...
Middle column contents...
Middle column contents...
Middle column contents...
Middle column contents...
Middle column contents...
Middle column contents...
Middle column contents...
Middle column contents...
</div>
<div id="rightColumn">
Right column contents...
Right column contents...
Right column contents...
Right column contents...
Right column contents...
Right column contents...
Right column contents...
Right column contents...
</div>
</div>
</form>
</body>
</html>
The page in Listing 9 contains four <div> tags. The first <div> tag, named content, is
used to specify the width of the page's content area. The remaining three <div> tags—
named left, middle, and right—divide the content area into three columns. This page
displays correctly in Internet Explorer 6, Firefox, and Opera 8. (To view some really
beautiful pages that do not use HTML tables for layout, see http://csszengarden.com.)
The WCAG guidelines recognize that it is not always possible to avoid using <table>
tags to create page layouts, because older browsers do not fully support the Cascading
Style Sheet standards (see WCAG Guideline 5). In those cases in which you cannot avoid
using tables for layout, you should verify that the content of the tables makes sense when
linearized (that is, read in table-cell order).
Because the ASP.NET framework must be compatible with browsers both old and new,
some of the ASP.NET controls do, in fact, use <table> tags for layout. For example, the
ASP.NET 2.0 Login control uses the <table> tag to control the layout of the user name
and password input fields.
6.3 Ensure that pages are usable when scripts, applets, or other programmatic objects are
turned off or not supported. If this is not possible, provide equivalent information on an
alternative accessible page. [Priority 1]
The problem is that several ASP.NET controls require client-side JavaScript in order to
function. The prime example of this is the ASP.NET LinkButton control. The
LinkButton control uses JavaScript to submit the form containing the control to the Web
server.
There is no good solution to this problem. If you are required to build a Web site that
meets all accessibility guidelines, then you need to be very careful about using client-side
scripts. You might need to avoid using certain ASP.NET controls that depend on
JavaScript, such as the LinkButton control.
Unfortunately, this guideline is difficult to follow when building a modern Web site. The
assumption seems to be that Web sites are more like magazines than applications. Modern
Web sites tend to include dynamic, client-side content. For example, many real estate
Web sites include a JavaScript mortgage calculator. It is not clear what the text equivalent
of a JavaScript mortgage calculator would look like.
For example, in order to make a Web page accessible, every image in the page must
contain meaningful alternate text. Currently, no machine can determine whether a
fragment of text has the same meaning as an image. At best, an accessibility validator can
only provide you with a list of things that you should check.
Visual Studio 2005 (but not Visual Web Developer) includes an Accessibility Checker.
You can open the Accessibility Checker from the toolbar. or you can select the menu
option Tools, Check Accessibility (see Figure 13).
Figure 13. Visual Studio 2005 Accessibility Checker (Click the graphic for a larger
image.)
The Accessibility Checker provides you with options for validating your Web site against
WCAG Priority 1 checkpoints, WCAG Priority 2 checkpoints, or Section 508 guidelines.
You can view the results of validating your Web site by opening the Error List (select the
menu option View, Other Windows, Error List).
The Visual Studio 2005 Accessibility Checker also provides you with the option of
displaying a "manual checklist" of accessibility issues. If you select this option, the same
static list of accessibility issues is displayed in the Error List window whenever you
validate your Web site for accessibility. This checklist contains issues that cannot be
automatically validated by the Accessibility Checker.
If you are building Web sites with Visual Web Developer, you can also check your Web
pages for accessibility. To do this, you'll need to use one of the online Accessibility
Checkers. Here are links to two of the most popular online accessibility checkers:
• Bobby
• WAVE
The goal is to create a Web site that is completely standards compliant. Our Web site will
validate as XHTML 1.0 Strict (and even XHTML 1.1). Furthermore, the Web site will be
accessible to persons with disabilities. It will satisfy both section 508 and WCAG
(priority 1 and priority 2) accessibility requirements.
We will build an online bookstore called the Super Super Bookstore Web site. We'll
retrieve all of our book listings for our bookstore through the Amazon E-Commerce Web
services. The Amazon E-Commerce Web services provide us with plenty of free sample
data to play with (for more information about the Amazon Web Services, see
http://www.amazon.com/gp/aws/landing.html).
To keep things simple, our Web site will consist of only two ASP.NET pages:
Behind the scenes, the Web site uses several new features of the ASP.NET 2.0
framework. For example, the Web site uses a Master Page to create a common page
layout, and a Theme to create a common page style. Finally, the sample site uses the new
GridView and ObjectDataSource controls for data access.
Imports Microsoft.VisualBasic
''' <summary>
''' Attempts to get books in category from cache.
''' If not in cache, call Amazon Web service
''' </summary>
Public Function GetBooks(ByVal CategoryId As String) _
As AmazonServices.Item()
Dim context As HttpContext = HttpContext.Current
Dim Books As AmazonServices.Item()
If IsNothing(context.Cache(CategoryId)) Then
Books = GetBooksFromAmazon(CategoryId)
context.Cache(CategoryId) = Books
Else
Books = CType(context.Cache(CategoryId), _
AmazonServices.Item())
End If
Return Books
End Function
''' <summary>
''' Retrieves books in certain category from Web service
''' </summary>
Public Function GetBooksFromAmazon(ByVal CategoryId As String) _
As AmazonServices.Item()
Dim service As New AmazonServices.AWSECommerceService()
If IsNothing(response) Then
Return Nothing
End If
Return response.Items(0).Item
End Function
''' <summary>
''' Searches for books by calling Amazon Web service
''' </summary>
Public Function SearchBooksFromAmazon(ByVal Author As String, _
ByVal Title As String, ByVal Keywords As String, _
ByVal PowerSearch As String) As AmazonServices.Item()
' Don't search if nothing to search for
If IsNothing(PowerSearch) And IsNothing(Author) And _
IsNothing(Title) And IsNothing(Keywords) Then
Return Nothing
End If
If IsNothing(response) Then
Return Nothing
End If
Return response.Items(0).Item
End Function
''' <summary>
''' The Amazon Author property represents a list of authors.
''' Therefore, we create a comma separated list
''' </summary>
Public Shared Function FormatAuthor(ByVal Authors As String()) _
As String
If Not IsNothing(Authors) Then
Return String.Join(", ", Authors)
Else
Return "Not Listed"
End If
End Function
''' <summary>
''' Formats Amazon ListPrice into US currency
''' </summary>
Public Shared Function FormatPrice(ByVal Price As String) As String
If Not IsNothing(Price) Then
Return "$" & Price.Insert(Price.Length - 2, ".")
Else
Return "Not Listed"
End If
End Function
''' <summary>
''' Formats tooltip for the link to the book details
''' </summary>
Public Shared Function _
FormatDetailsTooltip(ByVal Title As String) As String
If Not IsNothing(Title) Then
Return String.Format("Link to {0} details", Title)
Else
Return "Link to details"
End If
End Function
''' <summary>
''' If there is no book cover, we fall back to displaying our image
''' </summary>
Public Shared Function FormatBookCover(ByVal Url As String) _
As String
If Not IsNothing(Url) Then
Return Url
Else
Return "Images/NoBookCover.gif"
End If
End Function
End Class
The two most important functions in the class are called GetBooksFromAmazon and
SearchBooksFromAmazon. The first function is called from the Default.aspx page to
display the book listings by category. The second function is called from the Search.aspx
page to enable users to search for books.
Both functions use a Web Service proxy class named AmazonServices. This proxy class
was created by selecting the menu option Web site, Add Web Reference, and entering
the URL http://soap.amazon.com/onca/soap?Service=AWSECommerceService. This is
the proper URL for accessing United States Amazon data.
<script runat="server">
Sub Page_Load()
Dim categoryIndex As Integer = 0
If Not IsNothing(Request("index")) Then
categoryIndex = Int32.Parse(Request("index"))
End If
MenuCategories.Items(categoryIndex).Selected = True
End Sub
</script>
</div>
<div id="middleColumn">
<asp:GridView
id="grdBooks"
DataSourceID="srcBooks"
AutoGenerateColumns="false"
CssClass="books"
HeaderStyle-CssClass="booksHeader"
EmptyDataText="No matching results"
Runat="server">
<Columns>
<asp:TemplateField HeaderText="Book Cover Image">
<ItemTemplate>
<asp:Image
id="imgBook"
ImageUrl='<%#Amazon.FormatBookCover(Eval("SmallImage.Ur
l"))%>'
AlternateText="Book cover image"
Runat="server" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Book Information">
<ItemTemplate>
<h2><%#Server.HtmlEncode(Eval("ItemAttributes.Title"))%></h
2>
Authors:
<%#Amazon.FormatAuthor(Eval("ItemAttributes.Author"))%>
<br />Price:
<%#Amazon.FormatPrice(Eval("ItemAttributes.ListPrice.Amount
"))%>
<br />Sales Rank:
<%#Eval("SalesRank")%>
<br />
<asp:HyperLink
id="lnkDetails"
NavigateUrl='<%#Eval("DetailPageURL")%>'
Text="View Details"
Tooltip=
'<%#Amazon.FormatDetailsTooltip(Eval("ItemAttributes.Title"))%>'
Runat="server" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:ObjectDataSource
id="srcBooks"
TypeName="Amazon"
SelectMethod="GetBooks"
Runat="server">
<SelectParameters>
<asp:ControlParameter
Name="CategoryId"
ControlId="menuCategories"
DefaultValue="1" />
</SelectParameters>
</asp:ObjectDataSource>
</div>
</asp:Content>
This page uses two ASP.NET controls to display the book listings: the Menu control and
the GridView control. The Menu control is used to display the list of book categories,
and the GridView control is used to display the list of books.
ASP.NET 2.0 Themes make it easier to follow web standards, because they enable you to
separate all of your presentational content from your pages. The sample site includes a
Theme, named SiteTheme, that contains a single style sheet. This Theme is automatically
associated with every page, using the following configuration setting in the Web.Config
file.
<pages
styleSheetTheme="SiteTheme"
masterPageFile="SiteMaster.master" />
You should notice that HTML tables are not used to create the page layout. Although
neither the XHTML standard nor accessibility standards prohibit you from using tables
for page layout, both standards encourage you to avoid it. In the sample site, the page
layout is completely determined by the external style sheet. The page itself is divided into
two columns by two <div> elements. The external style sheet contains rules for
positioning the two <div> elements.
Finally, the sample site uses content negotiation when serving pages. When a page is
requested from the Web site, using a browser that understands the application/xhtml+xml
MIME type, the page is served with this MIME type; otherwise, the page is served as
text/html.
The content negotiation is accomplished with the following event handler in the
Global.asax file.
In order to satisfy this requirement, extra work had to be done when implementing the
menu. By default, the ASP.NET Menu control renders JavaScript for each menu item to
handle the client click event. However, when a menu item is provided with a
NavigateUrl property, the menu item no longer uses JavaScript.
In the sample site, each menu item is provided with a NavigateUrl property that points
back to the Default.aspx page. When you click a menu item, the Default.aspx page is
reloaded. The Page_Load event handler is used to detect which menu item was clicked,
and this subroutine updates the menu with the current menu selection.
The advantage of using a Menu control is that a Menu control automatically generates a
Skip Navigation link. If you tab through each of the elements in the Default.aspx page,
you'll notice (if you look at your browser's status bar) that there is a hidden link that skips
the contents of the menu. The Menu control enables you to automatically satisfy the
WCAG and Section 508 guidelines that require you to provide a method of skipping
repetitive navigation links.
Figure 15. The search page (Click the graphic for a larger image.)
<script runat="server">
<fieldset class="quickSearch">
<legend>Quick Search</legend>
<asp:Label
Text="Author:"
AssociatedControlID="txtAuthor"
AccessKey="a"
Runat="server" />
<asp:TextBox
id="txtAuthor"
ToolTip="Search by author"
Runat="server" />
<span class="accessKey">access key is a</span>
<br />
<asp:Label
Text="Title:"
AssociatedControlID="txtTitle"
AccessKey="t"
Runat="server" />
<asp:TextBox
id="txtTitle"
ToolTip="Search by title"
Runat="server" />
<span class="accessKey">access key is t</span>
<br />
<asp:Label
Text="Keywords:"
AssociatedControlID="txtKeywords"
AccessKey="k"
Runat="server" />
<asp:TextBox
id="txtKeywords"
ToolTip="Search by keywords"
Runat="server" />
<span class="accessKey">access key is k</span>
<br />
<asp:Button
id="btnQuickSearch"
Text="Quick Search Now"
ToolTip="Peform quick search"
AccessKey="s"
Runat="server" OnClick="btnQuickSearch_Click" />
<span class="accessKey">access key is s</span>
</fieldset>
<br />
<fieldset class="powerSearch">
<legend>Power Search</legend>
<asp:Label
Text="Query:"
AssociatedControlID="txtPowerSearch"
AccessKey="q"
Runat="server" />
<asp:TextBox
id="txtPowerSearch"
ToolTip="Power search query text"
TextMode="MultiLine"
Columns="20"
Rows="3"
Runat="server" />
<span class="accessKey">access key is q</span>
<br />
<asp:Button
id="btnPowerSearch"
Text="Power Search Now"
ToolTip="Perform power search"
AccessKey="p"
Runat="server" OnClick="btnPowerSearch_Click" />
<span class="accessKey">access key is p</span>
</fieldset>
</div>
<div id="middleColumn">
<asp:GridView
id="grdBooks"
DataSourceID="srcBooks"
AutoGenerateColumns="false"
CssClass="books"
HeaderStyle-CssClass="booksHeader"
EmptyDataText="No matching results"
Runat="server">
<Columns>
<asp:TemplateField HeaderText="Book Cover Image">
<ItemTemplate>
<asp:Image
id="imgBook"
ImageUrl=
'<%#Amazon.FormatBookCover(Eval("SmallImage.Url"))%>'
AlternateText="Book cover image"
Runat="server" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Book Information">
<ItemTemplate>
<h2><%#Server.HtmlEncode(Eval("ItemAttributes.Title"))%></h
2>
Authors:
<%#Amazon.FormatAuthor(Eval("ItemAttributes.Author"))%>
<br />Price:
<%#Amazon.FormatPrice(
Eval("ItemAttributes.ListPrice.Amount"))%>
<br />Sales Rank:
<%#Eval("SalesRank")%>
<br />
<asp:HyperLink
id="lnkDetails"
NavigateUrl='<%#Eval("DetailPageURL")%>'
Text="View Details"
Tooltip=
'<%#Amazon.FormatDetailsTooltip(Eval("ItemAttributes.Title"))%>'
Runat="server" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:ObjectDataSource
id="srcBooks"
TypeName="Amazon"
SelectMethod="SearchBooksFromAmazon"
Runat="server">
<SelectParameters>
<asp:ControlParameter
Name="Author"
ControlId="txtAuthor"
ConvertEmptyStringToNull="true" />
<asp:ControlParameter
Name="Title"
ControlId="txtTitle"
ConvertEmptyStringToNull="true" />
<asp:ControlParameter
Name="Keywords"
ControlId="txtKeywords"
ConvertEmptyStringToNull="true" />
<asp:ControlParameter
Name="PowerSearch"
ControlId="txtPowerSearch"
ConvertEmptyStringToNull="true" />
</SelectParameters>
</asp:ObjectDataSource>
</div>
</asp:Content>
Notice that the form is divided into subforms with the HTML <fieldset> tag. The
<fieldset> tag enables you to group logically related form elements. The accessibility
guidelines require you to use the <fieldset> tag when working with complex forms (see
WCAG 12.3).
Notice, furthermore, that each form field is explicitly associated with its label. Each
ASP.NET control includes an AssociatedControlID property that points to its
corresponding form field. These explicit associations between labels and fields help users
of screen readers determine the purpose of particular form fields.
Finally, notice that each Label control is assigned an access key. The access keys enable
you to easily navigate the form fields without using a mouse. For example, if you press
ALT+A, you can enter the name of an author. If you then press ALT+S, the Quick Search
form is submitted, and the results are displayed in the GridView. In other words, you can
easily perform searches without touching the mouse.
If you press the ALT key, the access keys are automatically displayed (see Figure 16).
This is accomplished through JavaScript. Notice that there is a <span> tag that appears
after each form field. For example, the Title search field is implemented with the
following code.
<asp:Label
Text="Title:"
AssociatedControlID="txtTitle"
AccessKey="t"
Runat="server" />
<asp:TextBox
id="txtTitle"
ToolTip="Search by title"
Runat="server" />
<span class="accessKey">access key is t</span>
When you press the ALT key, client-side JavaScript executes, and the contents of the
<span> tag are displayed.
Figure 16. Search form access keys
You might worry about this functionality, because, according to the accessibility
guidelines, the page must continue to work when JavaScript and style sheets are turned
off (WCAG guideline 6). Fortunately, the page does work when both JavaScript and style
sheets are disabled. In that case, the contents of the <span> tags are no longer hidden,
and the access keys are always displayed (see Figure 17).
Figure 17. Search form degrading gracefully
<pages
styleSheetTheme="SiteTheme"
masterPageFile="SiteMaster.master" />
If Profile.AccessibleStyleSheet Then
lnkAccessibleStyle.Visible = True
lnkStyle.Text = "Normal Text Version"
lnkStyle.NavigateUrl = Request.Path & "?normal=1"
Else
lnkAccessibleStyle.Visible = False
lnkStyle.Text = "Large Text Version"
lnkStyle.NavigateUrl = Request.Path & "?large=1"
End If
End Sub
</script>
<html >
<head runat="server">
<title>Super Super Books</title>
<script type="text/javascript"
src="Scripts/AccessKeys.js"></script>
<link id="lnkAccessibleStyle"
type="text/css"
rel="Stylesheet"
href="~/Styles/Accessible.css"
runat="server" />
</head>
<body>
<form id="form1" runat="server">
<div id="content">
<div id="banner">
<asp:HyperLink
id="lnkStyle"
ToolTip="Modify the size of all text in this Web site"
Runat="server" />
<br />
<asp:Menu
id="MenuSite"
ToolTip="Web site navigation menu"
CssClass="menuSite"
Orientation="Horizontal"
StaticTopSeparatorImageUrl="Images/bullet.gif"
Runat="server">
<Items>
<asp:MenuItem
Text="Home"
ToolTip="Navigate to home page"
NavigateUrl="~/Default.aspx" />
<asp:MenuItem
Text="Search"
Tooltip="Navigate to search page"
NavigateUrl="~/search.aspx" />
</Items>
</asp:Menu>
</div>
<hr />
<hr />
<a href="http://validator.w3.org/check?uri=referer"
title="Explanation of XHTML 1.0 Conformance">
<img
src="http://www.w3.org/Icons/valid-xhtml10"
alt="Valid XHTML 1.0 icon" class="icon"/></a>
<a href="http://jigsaw.w3.org/css-validator/"
title="Explanation of CSS Conformance">
<img
src="http://jigsaw.w3.org/css-validator/images/vcss"
alt="Valid CSS icon" class="icon" /></a>
<a href="http://www.w3.org/WAI/WCAG1AA-Conformance"
title="Explanation of Level Double-A Conformance">
<img height="32" width="88"
src="http://www.w3.org/WAI/wcag1AA"
alt="Level Double-A conformance icon,
W3C-WAI Web Content Accessibility Guidelines 1.0"
class="icon" /></a>
</div>
</form>
</body>
</html>
A user needs to make this selection only once. If someone selects the Large Text version
of the Web site, this preference is automatically recorded and used whenever the person
returns to the Web site. This functionality is implemented by taking advantage of yet
another new feature of the ASP.NET 2.0 framework: Profiles. A Profile enables you to
store user settings across multiple visits to a Web site.
<anonymousIdentification enabled="true"/>
<profile>
<properties>
<add
name="AccessibleStyleSheet"
type="Boolean"
defaultValue="false" />
</properties>
</profile>
This Profile defines a Boolean property named AccessibleStyleSheet. Once the property
is defined in the Web.Config file, you can read or set the property in any ASP.NET page,
through the Profile property exposed by the Page class. For example, to set the
AccessibleStyleSheet property to the value True, and display the Large Text version of
the Web site, you would write the following.
Profile.AccessibleStyleSheet = true
The Master Page includes all of the logic for selecting the Normal Text or Large Text
version of the Web site. A HyperLink control is used to enable Web site visitors to make
this selection. After the HyperLink is clicked, the Page_Load event handler detects the
user's selection and sets the AccessibleStyleSheet Profile property.
The code here would have been simpler if a LinkButton control could have been used
instead of a HyperLink control. Once again, however, the accessibility guidelines
prevent us from doing this, because a LinkButton control depends on client-side
JavaScript.
When the Large Text version is selected, a reference to an additional style sheet is added
to the page. The style sheet includes a single rule.
body
{
font-size: x-large;
}
This rule sets the body font size to the value x-large. Because all of the fonts specified in
the primary style sheet (contained in the SiteTheme folder) use relative sizes, modifying
the font size of the body element automatically increases the font size of all elements in
the Web site.
Conclusion
Web standards are a good thing. By following Web standards, you can make your Web
sites accessible to the broadest possible audience with the least amount of work. Your
Web sites will be compatible with more browsers, and they will be more likely to
continue to work in the future.
The ASP.NET 2.0 framework was designed to enable you to easily build Web sites that
satisfy Web standards. The framework enables you to easily build XHTML Web sites. In
the ASP.NET 2.0 framework, all ASP.NET controls render XHTML elements and
attributes by default. Furthermore, Visual Studio 2005 and Visual Web Developer allow
you to automatically validate your pages against the XHTML standards while you build
them.
The ASP.NET 2.0 framework also makes it easier to build Web sites that are accessible to
persons with disabilities. The controls in the ASP.NET 2.0 framework include a host of
new properties designed with accessibility in mind. For example, every ASP.NET control
that renders an image enables you to render alternate text for the image. Furthermore, all
the new navigation controls include Skip Navigation links to make it easier for persons
with disabilities to navigate your Web site.