Programmatically Creating Context-Sensitive Help on a Web Page
By Scott Mitchell
Introduction
In last week's article, Creating Context-Sensitive Help on a Web Page,
we looked at how to associate rich help "tooltips" with certain regions in the browser. With a bit of client-side JavaScript
and HTML markup, this previous article illustrated how to have a help window with images and rich text appear when mousing
over an image or specific text. Such context-sensitive help is a great way to include extra information or a more detailed
explanation of data on a web page in a space-saving manner.
Last week's article aimed to simply lay the ground work for creating a context-sensitive help system and, as such, had a few
shortcomings. Most noticeably, the help window appeared immediately when mousing over its associated region and disappeared
immediately after mousing out of that region. This behavior introduced two usability issues: first, when moving the mouse over the
screen, if you happened to pass over a help region the help window appeared, which could be jarring; second, if a help window
contained links or was lengthy enough to require scrolling, when attempting to move the mouse from the help region to the
help window, the mouse would leave the help region and the help window would disappear.
In addition to these two end user usability problems, the context-sensitive help system presented last week wasn't very user friendly for us,
the page developer. All of the script and <div> and <iframe> elements
needed to be manually added to the web page to define the help windows and the onmouseover and
onmouseout event handlers for the help region. Ideally, all of the necessary script and markup could be added
programmatically, with a single line of code from the ASP.NET code-behind class.
In this article we'll see how to improve upon the rudimentary context-sensitive help system from last week by fixing the
aforementioned usability concerns and by creating an ASP.NET 2.0 class with methods to programmatically add the help
windows and needed script for a given help region. Read on to learn more!
Delaying Before Opening and Closing Help Windows
When mousing over a tooltip-enabled user interface element in a web page or Windows application, the tooltip doesn't immediately
appear, but only after hovering over its help region for a short time. The context-sensitive help system explored in
last week's article, however, immediately shows the help window when a help region is moused over. This immediacy can
have a jarring effect for the user if they move their mouse across their browser screen. Even though they didn't intend for
any context-sensitive help to popup, their mouse's flight across the screen may have inadvertently passed through one or more
help regions, thereby causing the help window to display. Granted, these help windows quickly disappeared as the user continued
to move their mouse across the screen, but the quick flash of the help window might confuse or annoy users.
To remedy this, we can introduce a slight delay before displaying a window. That is, when the user mouses over a help region,
rather than display the help window immediately, we wait for half a second (or what have you) and then show the help window.
If, in that half second waiting period, the user's mouse has left the help region, then we'd not show the help window.
JavaScript provides the setTimeout(expression, delay)
function to perform a given expression after a specified number of milliseconds delay. When using setTimeout it's
vital to understand that the function is non-blocking. That is, when the setTimeout statement is reached,
it adds the expression to a queue to be executed after the specified delay. The lines of JavaScript immediately after the
setTimeout function are then executed without pause. In other words, the delay specified before executing the
specified expression is asynchronous. The control flow does not wait at the setTimeout command until
the specified delay has passed.
To accomplish this, I broke out the csi_showHelpFloatWindow - which displays a specified help window - into
two functions:
csi_showHelpFloatWindow - accepts the same set of input parameters as before along with a new one,
waitDurationInMilliseconds. The waitDurationInMilliseconds parameter indicates how many milliseconds
to pause between mousing over a help region and displaying its help window. This function calls the
csi_showHelpFloatWindowInternal using the setTimeout function.
csi_showHelpFloatWindowInternal - does the actual work of displaying the help window.
Additionally, we need a flag variable that indicates whether or not to cancel the opening of the window. For example,
if a user mouses over a help region, the csi_showHelpFloatWindow is called and the setTimeout function
instructs that the csi_showHelpFloatWindowInternal function is to be called after, say, 500 milliseconds (half a second).
If the user moves their mouse out of the help region before the 500 millisecond delay has expired, we do not want to
display the help window. However, the csi_showHelpFloatWindowInternal function will still execute, there's no way
to cancel it based on the mouse movements.
Therefore, we use a flag variable that indicates whether or not the open command has
been cancelled - it's set to false in csi_showHelpFloatWindow and to true in the
onmouseout event handler of the help region. When the csi_showHelpFloatWindowInternal function executes,
it only proceeds if the cancel flag is false. Since there may be different help window types on a page (that is,
help windows with different windowIDs), a single
flag variable won't do. Rather, we need a flag for each unique help window type. This is accomplished through an array, as the
following JavaScript highlights:
// Array to hold the 'flags' to determine whether a window's open has been cancelled var csi_cancelOpenArray = new Array();
function csi_showHelpFloatWindow(windowID, objID, horizPadding, vertPadding, goRight, waitDurationInMilliseconds)
{
// keep the window open csi_cancelOpenArray[windowID] = false;
function csi_showHelpFloatWindowInternal(windowID, objID, horizPadding, vertPadding, goRight)
{
// exit function immediately if open command has been cancelled if (csi_cancelOpenArray[windowID] == true) return;
... Same logic for displaying the help window as seen in last week's article ...
}
When the user mouses out of a help region, we need to set the flag (csi_cancelOpenArray[windowID]) to
true. This is accomplished by updating the csi_hideHelpFloatWindow function (which, recall, is executed
from the help region's onmouseout event).
function csi_hideHelpFloatWindow(windowID)
{
// cancel the open
csi_cancelOpenArray[windowID] = true;
...
}
This same pattern is repeated to add a delay when between when mousing out of a help region whose help window is open, and
closing that help window. This delay allows the user to move their mouse from the help region to the help window, where they
can interact with the help window (scroll through its contents, click a hyperlink, and so on). To facilitate this, another
flag array is used (csi_cancelCloseArray), which is set to false when leaving a help region (in
the csi_hideHelpFloatWindow function) and set to true when mousing over a help region or when
mousing over the help window itself. As with the delay before opening, this is accomplished by splitting the csi_hideHelpFloatWindow function
into two functions:
csi_hideHelpFloatWindow - accepts an additional input parameter
waitDurationInMilliseconds, which indicates how many milliseconds
to pause between mousing out of a help region and hiding its help window. This function calls the
csi_hideHelpFloatWindowInternal using the setTimeout function.
csi_hideHelpFloatWindowInternal - does the actual work of hiding the help window.
// Array to hold the 'flags' to determine whether a window's close has been cancelled var csi_cancelCloseArray = new Array();
function csi_hideHelpFloatWindow(windowID, waitDurationInMilliseconds)
{
// cancel the open
csi_cancelOpenArray[windowID] = true;
// wait before hiding window csi_cancelCloseArray[windowID] = false;
function csi_hideHelpFloatWindowInternal(windowID)
{
// exit function immediately if close command has been cancelled if (csi_cancelCloseArray[windowID] == true) return;
... Same logic for hiding the help window as seen in last week's article ...
}
Additionally, the csi_showHelpFloatWindow and the onmouseover event of the help window set
the appropriate flag in csi_cancelCloseArray to true. Refer to the complete JavaScript and
code library available for download at the end of this article for more information.
Adding Context-Sensitive Help Programmatically
There are a number of client-side tasks we need to complete in order to associate a help window with a particular region,
including:
The JavaScript needed to show and hide the help windows
The CSS styling for the help window (its z-index, configuring it for absolute positioning, and so on)
The help window markup (the <div> and <iframe> tags)
Manually adding these various pieces to each page that needs context-sensitive help is tedious and error-prone. Instead, it
would be ideal to be able to associate a help window with a region on the screen through a single line of code from the
ASP.NET page's code-behind class. This lone method call would automatically inject the necessary JavaScript, CSS, and help window
markup.
To implement this, I created a class named ContextSensitiveHelp that contains the context-sensitive help-related methods.
ContextSensitiveHelp derives from the System.Web.UI.Page class.
To utilize its context-sensitive help-related methods in an ASP.NET page, adjust your ASP.NET page's code-behind class so
that it derives from ContextSensitiveHelp rather than from System.Web.UI.Page. See
Using a Custom Base Class for your ASP.NET Page's
Code-Behind Classes for more information on this technique and its advantages.
The ContextSensitiveHelp class contains a single page-level accessible method, AddContextSensitiveHelp,
which has a variety of overloads. At minimum, you must pass this method:
The Web control that serves as the help region (perhaps a Label Web control or an Image Web control),
The windowID (an arbitrary string that is used to identify a help window for opening and closing the window), and
The URL of the help web page to display in the help window
You can optionally include input parameters like whether the help window opens to the right or left of the help region,
the width and height of the window, the amount of horizontal and vertical padding between the help region and the help window,
and the delay time (in milliseconds) for opening and closing the help windows.
Whenever the AddContextSensitiveHelp method is called, the needed JavaScript and CSS is added (if needed)
and the help window markup is added.
For example, to associate a help window with a Label control named WelcomeLabel, you could add the following
line of code to the Page_Load event handler:
To try out the ContextSensitiveHelp class, download the project files available at the end of this article.
ContextSensitiveHelp and ASP.NET Version 1.x
The ContextSensitiveHelp class was created in Visual Studio 2005 and is intended to be used in an
ASP.NET 2.0 web application. It uses classes and methods new to
the .NET Framework 2.0 with regards to emitting the client-side script, and therefore won't work, as-is, in an ASP.NET 1.x
application. However, the script features new to 2.0 can be accomplished in 1.x with minor modifications. For more on
programmatically working with client-side script in an ASP.NET 1.x application, check out:
Working with
Client-Side Script.
Conclusion & Looking Forward...
We've made some great improvements to the context-sensitive help system first introduced in last week's article,
Creating Context-Sensitive Help on a Web Page. We saw
how to use the JavaScript setTimeout function to add a brief delay before showing a help window and how to leave the
help window displayed after mousing out of the help region. Furthermore, we examined the ContextSensitiveHelp class,
which provides a simple method for creating and associating a help window with a help region.
There are still a few shortcomings of the context-sensitive help system, though. For one, the help windows require an explicitly
specified height and width, and do not adapt to their actual content. For example, consider a help window sized at 400x350, but
whose help content contains just a few words. Ideally this help window would be smart enough to resize itself to, say, 200x150,
to reduce the whitespace in the window. Additionally, if the help region is near the border of the browser (at the right or bottom),
the help window will likely be cut off by the bottom or right of the browser. Ideally, the help system would be intelligent enough
to position itself to the left if it was going to be cut off on the right or to raise its top position if it was going to be
cut off at the bottom. Perhaps a future article will tackle these issues!