In this article I'll explore what JavaScript/DHTML is capable of and explain when you should use it and why.
JavaScript is a powerful yet often underused feature of most browser-based applications. Commonly JavaScript is relegated to simple form validations, but this is not the only capability of JavaScript. In this article I'll cover a powerful set of features you can incorporate in your applications today.
This article explores the following examples:
The examples (created in Internet Explorer) assume a basic familiarity with HTML. Please note that this article is an overview of JavaScript and DHTML, and except for a brief discussion of .NET, does not really address JavaScript's place in the creation of .NET User Controls.
What Is JavaScript and DHTML and What Can They Do?
Put simply, JavaScript is a no frills and yet flexible programming language supported by all major Web browsers. Although it is supported on most Web servers, and performs server-side processing that creates a Web page you can send to a browser, its real power comes from using it to manipulate the HTML of a Web page in a client browser environment.
The browser exposes the structure of a Web page to JavaScript through a document
object model or DOM. You typically refer to programming the DOM using JavaScript as using Dynamic Hyper Text Markup Language (DHTML).
Since the DOM exposes the current Web page as an object hierarchy of the pieces in the document, you can use JavaScript to manipulate its parts. If you want a section of the page to disappear for a while, find its representative
object in the hierarchy and set its style visibility
property to hidden. Later on you can set it back to visible. If you want to have a bouncing ball on the screen, add a new HTML IMG element, showing the ball, to the object hierarchy and reposition it every second using whatever
function you want to specify for the bouncing behavior. If you want to handle a collection of items without worrying about binding
events to each one, wait for
events to bubble up from child to parent in the DOM hierarchy and handle them at the container level.
You can easily handle all these tasks more by using JavaScript and DHTML. In fact, JavaScript also allows you to create simple class
objects, and you can reuse your favorite pieces of client-side code.
Quick Recap of the Web Server/Web Page Cycle
JavaScript is very good at manipulating Web pages, however, Macromedia Flash or server-side code may be a better choice if you have a lot to manipulate. Since every application is different, there are no global rules, but a quick discussion of the structure of a Web page may help you understand where the blocks and difficulties will appear in your own applications.
Figure 1 shows the simplified logical architecture of the cycle for a Web browser to request and send information .to a Web Server.
Note that the browser always initiates all activity. It sends a request composed of a URL, which may have extra information in a query string and/or in the case of a form post, all of the values of all the form variables. The Web Server processes this request, which typically creates and uses business
objects to handle
functions and/or access the database.
This fire-and-forget architecture has the advantage of serving many customers because the Web Server can quickly serve up pages, but it can also orphan the user experience.
Most of the advancements in Web design in the last few years have been attempts to solve the issues found in stateless environments. Consider the major concept of sessions and timeouts. The Web Server keeps resources alive for the specific browser's session until a timeout limit passes, after which the Web Server considers the session obsolete. The length of a timeout is based on guesses and each Web Server administrator can tinker with the settings so there is no reliable standard.
Another
method of preserving state is using cookies. Generally, an encrypted string of information is stored on the client machine that is only readable by the Web Server that issued it. Cookies reduce the need for sessions since they provide user-specific information in a just-in-time fashion to the Web Server that needs it to process a requested page.
How Does .NET Change the Web Page Cycle?
Microsoft's .NET architecture does not actually change the Web page cycle. However, it does improve the cycle.
The .NET Web Forms strategy relies on using a copy of the user interface controls defined on the Web Server. It creates and sends a Web Server version to the browser when the server receives a request for the page. Then, when an
event (that the web application is interested in) the browser is instructed to post the web page to the server passing in the web page and the
event that caused the post. The Web Server then manipulates a server side resident version of the user interface. Upon completion it resends the server side copy as an updated Web page with information already filled in back to the browser. This can happen so seamlessly that a user may only notice a flicker every once in a while as they use the form.
The general benefit of the client-side processing and the cost of posting more often, is to allow client Web page controls to have access to the traditionally unavailable resources of the Web Server. I admit that this is an important benefit, but the limitation is that your client must post back to the Web Server more often, which is very expensive so I suggest that you use it sparingly.
For example, you'll find that making good use of the .NET features could allow you to show the detail lines of a purchase order when a user selects a purchase order number. An infrequent round trip to the Web Server adds dramatic benefit by showing new information on the Web page that you can only get by accessing the database, which is protected behind the Web Server.
In contrast, attempting to constrain a text field to a valid date by using a character-by-character checking scheme doesn't utilize .NET features to its fullest. You'll find it prohibitively slow and problematic to implement a routine that posts each keystroke back to the server.
When Should I Use JavaScript and DHTML?
JavaScript and DHTML provide easy and clear control over a page's style and layout. User interface
functionality that does not require any new information outside the page is a good candidate for JavaScript, whereas if you need info from a database you should consider another kind of solution. Here is a quick list of easy to implement features/utilities that you get with JavaScript and DHTML:
- Menus: Includes drop-downs, rows of buttons, etc.
- Client-side fine control of state: You can post after any change to a control and handle
the
event on Web Server side. - Visual effects and behaviors: Includes mouse over effects, sliding drawers, showing and
hiding
objects, and simple motion. - Formatting tabular information or massive amounts of content consistently.
- Calendar for selecting dates.
Web page situations that suggest selecting an alternative to JavaScript:
- Need data from a database: Consider posting to a Web Server.
- Need a calculation dependent on a
compiled
object: Consider posting to a Web Server. - Need extensive graphical motions and behaviors: Consider using Macromedia Flash.
Introduction to JavaScript and Style Sheets
As a first example using JavaScript I've created a simple menu that binds behavior to the mouse using
events from the DOM. This example also introduces the topic of style sheets.
HTML supports various properties such as height, width, font color, and often borders. You can set properties directly in HTML or you can use style definitions, which you'll find are similar to styles in Microsoft Word and they're easy to reuse. Using styles has become so prevalent in browsers that they have become the desirable way to set the look and feel. Styles have even been expanded so that they offer more options than are available in HTML itself.
You can specify a style in many ways the following
methods are all equivalent ways to set the font and color of a tag:
(a) Attributes of a tag
<FONT Name="ARIAL" color="BLUE">Hello World</FONT>
(b) Inline style properties
<SPAN Style="FONT-FAMILY:Arial; COLOR:Blue">
Hello World</SPAN>
(c) Referenced style tag properties
<STYLE>
.MyStyle { FONT-FAMILY:Arial; COLOR:Blue}
</STYLE>
<SPAN Class="MyStyle">Hello World</SPAN>
Style sheets provide a way to define groups of styles for your Web page. You can make them specific to certain tag names and you can apply them automatically or you can make them generic and apply them to any tag explicitly. You can include these definitions in a Web page itself using the style tag or in a separate .css
file. In the latter case you can link them to the primary Web page by including a LINK tag as follows:
<HTML>
<HEAD>
<LINK src="MyStyleSheet.css">
</HEAD>
<BODY>
<SPAN class="CurrentOption">Home</SPAN>
<SPAN>Place Order</SPAN>
<SPAN>FAQ</SPAN>
...
</BODY>
</HTML>
For example, to automatically set all SPAN tags in the page above to have a black border around them you could add the following to your style sheet:
.SPAN
{
BORDER: 1px;
BORDER-COLOR: black;
}
To be specific, a tag has to declare itself with a particular
class. For example, the Home button uses the Style
class named CurrentOption as follows:
.CurrentOption
{
BORDER: 2px;
BORDER-COLOR: red;
}
You can make very robust style sheets. They can include multiple levels of hierarchy and filtering. A wide variety of properties are available for you to set by using styles, including positioning, borders, fills, fonts, colors, and more.
JavaScript as a Programming Language
JavaScript is a weakly typed scripted language that is case sensitive and built mainly of objects and
functions. Objects come in three basic types: built in variable types, Web page document
object model components, and custom JavaScript
objects that you can build.
Variables
Variables in JavaScript are actually pointers to
objects. You do not need to create common variable type
objects with the NEW keyword because you can assign values with no additional syntax. Think of their value as their default
property. Notice that the var bFound = false
; declaration line in the program below creates and initializes the bFound
object and sets its value to false
, whereas the var i; line merely has a value of undefined. All of these built in types have type-specific
methods that you can access as in the case of the bFound.toString()
that does a type conversion and returns a string
object.
Objects
Some special JavaScript core
objects are static and always available. Think of these objects as the function libraries of JavaScript as they group many useful
functions for easy use. For example, you can use the Math.random()
method (used later in the whiteboard example) to generate a random number.
You can build your own
objects using containment. These objects require creation using the NEW keyword and I'll cover them later in the whiteboard example.
Functions
Another key concept in JavaScript is the function. If you don't specify a return value, JavaScript treats the function as a void
function. In the following example the function receives two parameters, a string and a comma-delimited list. Local variables get defined and will have the value null
until an assignment happens. The Split
function is a very useful
function since JavaScript does not allow you to pass an indeterminate number of parameters. Often a delimited list can replace the need to pass and manage arrays.
Consider the following short program that contains the basic concepts comprising JavaScript:
// given a string and a comment delimited list,
// determine if the string is found in the list
// items and return true or false as a string
function isInList(pString, pList) {
// declare variables
var List;
var i;
var bFound = false;
// turn delimited string into an array
List = split(pList, ",");
// loop through all array positions
for (i=0; I<List.length; I++)
{
if (pString == List[i])
{
// found the string
bFound = true;
}
}
// return the answer as a string
return bFound.toString();
}
You can insert comments into your code by using the double back slash ‘//’ syntax, which tells JavaScrip to ignore the rest of the line. Curly braces ‘{}’ indicate a statement that contains code blocks. Functions and declarations use the parentheses '()'. All actual statements require an ending semi colon ';'.
Now that we've explored a basic JavaScript
function, I'll show you how to use
functions and bind them to
events in a Web page. You can use JavaScript in many places in your pages, and when Web pages run across script they may assume that your code block is JavaScript. However, it is a far better practice to be explicit. Consider the following examples that show how to bind a simple alert messagebox to an
event.
(a) Overloading an anchor tag to do something other than linking. (The ‘void’ in the line ensures that the page does not refresh or attempt to navigate):
<A HREF="javascript: void alert(2+2);">
The sum of 2+2</A>
(b) Inline directly on the tag:
<SPAN OnClick="javascript:alert(3+3);">
The sum of 3 + 3</A>
(c) Script bound to an
event through the script tag attributes:
<DIV Name="MyDiv">The sum of 4 + 4<.DIV>
<SCRIPT language="JavaScript"
for=MyDiv event="click">
alert(4+4);
</SCRIPT>
(d) Inline script calling a generally available
function:
<SCRIPT language="JavaScript">
function showSum() {
alert(5+5) ;
}
</SCRIPT>
<P onclick="showSum();"></P>
Behind the scenes in each of these examples the JavaScript is turned into a function and then referenced to handle the
event. What may not be apparent is that these functions are (in a way)
objects in JavaScript themselves and you can pass them to other
functions by using their name without parentheses.
Example 1 - Simple Menu
Listing 1 demonstrates how to create a simple menu composed of a table with each cell linked to a new page. Hovering your mouse pointer over a menu item changes its color and clicking the menu item reveals a message box that specifies where it might be linked to if this was a real application.
You can use StyleSheet properties and simple JavaScript
events to control the look, feel, and actions of the menu. Note how I've separated the layout, styles, and scripts for clarity. This does not need to be the case, however, and the page will
function the same if you specify the various pieces inline on the table and cell tags.
Listing 1 defines the menu as a table of three buttons. The styles explicitly reference the table cell elements (TDs) and provide the default colors. As various mouse
events occur in each cell, you can change the colors by assigning the MenuOptionHover
class and then returning to the MenuOptionNormal
class. Attributes not specified in a replacing style do not get overwritten such, as the align
attribute in the normal menu option style.
If you bind the Scripts to the MouseOver, MouseOut, and Click
events of the Table tag, the changes only occur if they detect that the
event has bubbled up from a table cell element (TD). This bubbling goes from the control generating the
event through its containing parent tags all the way up to the document and you can handle them at any level. You can even cancel an
event so that it does not continue to bubble up. One benefit of handling
events this way is that you don't have to change the code when you want to add a menu option.
Additionally, the Web page DOM is an open schema and even though the table cell (TD) tag does not support a jumpto
attribute natively you can still specify it in HTML and use JavaScript to read/write it. It does not automatically acquire any special handling.
Of course you can use the jumpto (or whatever you decide to call such an attribute) to perform window.navigate so the user doesn't see an alert box.
Programming the Web Page Document Object Model (DOM)
In the case of the Web page Document Object Model, consider that some
functions related to the browser itself, such as navigating to new pages and handling the recently navigated history, must be available to JavaScript. An object called the Navigator, which represents the root
object and the browser itself, commonly exposes these functions. Always available, the navigator is never defined by the content the user loads.
The Navigator creates instances of a Window
class that holds and contains Web page documents. The Window
class has its own set of functionality such as the parent coordinates of the window itself and the Web page document loading status. As the parent to the document, it also provides the
event handling and
events from the document that bubble up to the Window
object, but can end up trapped there by application developers embedding Web browsers in their desktop programs.
Whereas the Navigator and instances of Window
objects are predefined, a document
object hierarchy can look different for every Web page although it conforms to a particular allowable DOM schema. Put plainly, the document is a hierarchy of the objects that make up the page and changes to it can directly change how a page looks and acts. The document hierarchy for a page gets created as a page loads into the browser and gets parsed, but you can modify the document hierarchy through script at any time. Web pages that do not conform to the allowable HTML syntax of the DOM will yield unexpected results. By default, a browser ignores badly formed HTML. As a result, these “bad” pieces never show in the document for that page.
Although, the DOM is fairly well standardized by the W3C and the major browsers attempt to adhere to it, there are still some differences, especially with the newer features so consult your target browser's documentation. Figure 2 shows an overview of the more important
objects available in the Internet Explorer DOM.
Note: Not all HTML tags or all
objects available in the object model are included in Figure 2. Please refer to your browser documentation for complete details.
Example 2 ? A Better Data Grid, the WebGrid
The example I've created in this section expands on some of the concepts from the first example and shows how you can use the DOM to modify a page at runtime. In this case the page loads a scrollable table with some data and lets the user select a row with both hover and selected row
functionality.
Figure 3 shows the WebGrid in action. As you can see, it is possible to make elements on a Web page look like a conventional Windows user interface.
To show how to construct the WebGrid I'll examine three types of code: styles, layouts, and scripts.
The style sheet in Listing 2 supports styles for all of the logical pieces of a scrollable list (WebGrid) and emulates a normal Windows application look and feel. Listing 2 also hints at the considerable richness available and the ease with which you can apply it.
Listing 3 presents the basic layout and structure of a WebGrid. The outer DIV in Listing 3 has specific dimensions thereby putting boundaries on its area. If either of its child DIVs becomes larger than those dimensions then it examines the overflow setting of the outer DIV's ‘WebGrid’ style. To make the outer DIV automatically add scrollbars, set the overflow
property of the style to ‘auto’.
Define the two inner DIVs for their particular
functions. They share horizontal scrolling in common. You should always display both DIVs at their full size and let the outer DIV provide scrollbars so you can move hidden information into view.
The column headers have an additional, more complicated behavior since they do not scroll vertically, but instead need to always be seen. You can solve this by repositioning them vertically each time the outer div has a scroll
event so you can see them in the viewable area.
In the next part of the WebGrid example I've taken data and added it into the rough framework of the already defined HTML. In Listing 4 the code to load the grid takes an XML document and loads the WebGrid by creating column headers, rows, and cells and then passes in the XML document as data.
The code in Listing 4 modifies the Web page as it runs on the client browser and as you'll see in the whiteboard example this is very desirable. In this case the page retrieves its data from an XML document created and filled with example data by the Tester
function, the data is then passed to the load
function and used to append rows to the document.
In common practice, however, the data will probably be available server-side and the functions would run there instead of acting on the client
object model. In that case these same
functions would still exist but they would write out the table complete with all style references above and the client code would instead merely have to implement hover and select
functions. Listing 5 separates these
event code blocks.
Creating Your Own Objects
One of the nicest features of JavaScript is that you can create your own
object
classes. They do have limitations such as not being inheritable, but they can contain and manage other
objects as well as having properties and
methods.
An Object
class has a Constructor
function that creates the object by adding all properties and
methods to it. JavaScript inherits the basic Object()
and hands it to the constructor as the ‘this’ variable. Your constructor then can add properties and
methods using this.x = 100; , and this.toString = myFunction; syntax as desired. Your constructor can then return the ‘this’ variable and the calling
function gets an instance of the class.
The technique of adding
methods into an object may not be immediately apparent. All
functions when defined are prototypes
objects. To be exposed as a method on an object they need to be set to a property on the object by using the address of the function prototype instead of the result. Basically, this is the difference between using myFunction and myFunction(x,y).
There is a problem though, because JavaScript does not know that you intend a particular
function for a given
object. Nor does JavaScript distinguish between
functions of the same name in different .js
files to be different. Therefore if JavaScript runs across a myPrint
function it may overwrite a previous myPrint
function from a different
file.
The solution depends on your desired effect. In the case where you'll never use the function or it won't have visibility anywhere but in the particular
class, you can nest the function within the constructor. This is perfectly valid JavaScript syntax and defines the scope of the function prototype as only being within the constructor
function. The second technique is to define the functions with a naming convention so that their names are always unique. This alternate
method allows reuse of the function and inclusion in other
objects since its prototype has public scope.
Using Existing Objects in HTML, VML, VRML, XML, etc.
The basic purpose of the browser is to present a Web page to the user. In the first generation of browsers that meant exclusively HTML, which not only presented text and images but allowed a developer to specify the layout as well. As browsers and the Web matured, several other layout languages have emerged to handle more than just text and images.
VML in particular was created to handle drawing lines and symbols and associated tasks such as gradient fills and shading and so forth. This new language has its own tags beyond HTML that are interpreted by the browser and expose different
functions and behaviors.
While VML presents a rich two-dimensional toolkit to the Web designer, VRML presents a whole three-dimensional world modeling system where tags refer to shapes, light sources, and viewing angles.
And even while those examples expand the browser in new ways, XML as a simple and very HTML-like structure is also handled and presented with a collapsible and color-coded hierarchy.
As a taste of how far you can go very easily using the browser, and also to show how creating your own
objects can simplify coding, the following example creates an object to manage a whiteboard of VML lines as examples in Figure 4.
The Whiteboard
class is defined in Listing 6. It manages lists of lines as well as the currently being drawn line.
Examining the Whiteboard
class constructor shows a _pencolor
property defined with an underscore to give it a private scope under JavaScript. It then defines a pointer to the currently being drawn line as well as references to the canvas defining DIV tag and the lines DIV tag. The constructor then assigns the prototyped
functions as properties of the new whiteboard.
The Whiteboard (Figure 4) works by detecting mouse movements over a designated drawing area, (the ‘canvas’), and adding or extending VML lines when the mouse button is down. The lines drawn are absolutely positioned to the same top left coordinates as the canvas and collected nicely under another DIV. Their definitions are shown whenever the whiteboard is double-clicked. This is accomplished by displaying a message box containing the divLines.innerHTML
property, which shows all contained child tags, text, and current attributes.
This concept of being able to convert from
objects to text and from text to
objects makes programming the browser incredibly powerful since it allows dynamically constructing an html, style, or script string that you can add to the page at any time.
Now that we've defined the Whiteboard
object it is a simple matter to use it in a page. The HTML in Listing 7 uses the Whiteboard
object and shows how it simplifies the construction of the page.
As a line needs to be added, in Listing 7 the Whiteboard
object creates a new shape tag using the VML
namespace and adds it to the divLines part of the document. This DIV gets defined as a layer directly over the yellow canvas so that lines drawn on it appear to be on the canvas.
In fact the lines could have been added directly to the canvas DIV but the x and y positioning would not be as straight forward. Also, a large whiteboard would require scrolling, which would also necessitate more careful handling of the x and y positioning. You can handle these without much trouble by using different positioning x and y properties exposed on the window.event
object.
Conclusion
The web, and browsers in general, are here to stay. Besides just PCs, browsers and Web connectivity are becoming prevalent on printers, faxes, and handheld devices, and will probably have a place on all platforms moving forward.
JavaScript and DHTML can extend the power of current and future browser's allowing the simulation of many features of regular applications as well as providing new ones.
This client side
functionality can add those professional touches to your Web applications to really distinguish them and yourself.
Listing 1: Sample menu code
<HTML>
<HEAD>
<STYLE>
.MenuOptionHover
{
background-color: navy;
color: white;
}
.MenuOptionNormal
{
background-color: silver;
align=center;
color: black;
}
</STYLE>
</HEAD>
<BODY>
<TABLE id="MenuMain" border="1">
<TD class="MenuOptionNormal" jumpto="Home.asp">Home</TD>
<TD class="MenuOptionNormal"
jumpto="Billing.asp">Billing</TD>
<TD class="MenuOptionNormal"
jumpto="Customer.asp">Customer</TD>
<TD class="MenuOptionNormal"
jumpto="Service.asp">Service</TD>
<TD class="MenuOptionNormal"
jumpto="Orders.asp">Orders</TD>
<TD class="MenuOptionNormal"
jumpto="History.asp">History</TD>
<TD class="MenuOptionNormal" jumpto="Help.asp">Help</TD>
<TD class="MenuOptionNormal" jumpto="FAQ.asp">FAQ</TD>
</TABLE>
</BODY>
<SCRIPT LANGUAGE="JavaScript"
FOR="MenuMain" EVENT="onmouseover">
if (window.event.srcElement.tagName == "TD") {
window.event.srcElement.className = "MenuOptionHover";
}
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript"
FOR="MenuMain" EVENT="onmouseout">
if (window.event.srcElement.tagName == "TD") {
window.event.srcElement.className = "MenuOptionNormal";
}
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript" FOR="MenuMain" EVENT="onclick">
if (window.event.srcElement.tagName == "TD") {
alert("jumpto value is: " +
window.event.srcElement.getAttribute("jumpto"));
}
</SCRIPT>
</HTML>
Listing 2: Code for specifying styles
.WebGrid
{
BORDER-RIGHT: darkgray 1px solid;
PADDING-RIGHT: 0px;
BORDER-TOP: darkgray 1px solid;
PADDING-LEFT: 0px;
FONT-WEIGHT: normal;
FONT-SIZE: 8pt;
VISIBILITY: visible;
PADDING-BOTTOM: 0px;
OVERFLOW: auto;
PADDING-TOP: 0px;
BORDER-BOTTOM: darkgray 1px solid;
FONT-FAMILY: Arial;
POSITION: static
}
.ColumnHeaders
{
FONT-WEIGHT: bold;
FONT-SIZE: 8pt;
VISIBILITY: visible;
OVERFLOW: hidden;
COLOR: black;
FONT-FAMILY: Arial;
BACKGROUND-COLOR: lightgrey
}
.Rows
{
FONT-WEIGHT: normal;
FONT-SIZE: 8pt;
VISIBILITY: visible;
COLOR: black;
FONT-FAMILY: Arial;
BACKGROUND-COLOR: white
}
.Row
{
FONT-SIZE: 8pt;
OVERFLOW: hidden;
FONT-FAMILY: Arial;
HEIGHT: 22px
}
.ColumnHeader
{
OVERFLOW: hidden
}
.Cell
{
OVERFLOW: hidden;
FONT-FAMILY: Arial
FONT-SIZE: 8pt
}
.Hover
{
FONT-WEIGHT: normal;
FONT-SIZE: 8pt;
OVERFLOW: hidden;
COLOR: black;
FONT-FAMILY: Arial;
BACKGROUND-COLOR: lightgrey
}
.Selected
{
FONT-WEIGHT: bold;
FONT-SIZE: 8pt;
OVERFLOW: hidden;
COLOR: white;
FONT-FAMILY: Arial;
BACKGROUND-COLOR: navy
}
Listing 3: Code for displaying a grid in a Web page
<!-- WebGrid.Htm -->
<HTML>
<HEAD>
<LINK rel="stylesheet" type="text/css" href="WebGrid.css">
<SCRIPT src="WebGridDefiner.js"></SCRIPT>
<SCRIPT src="WebGridHandler.js"></SCRIPT>
</HEAD>
<BODY onload="JavaScript:loader(MyWebGrid);">
<DIV Class="WebGrid" ID="MyWebGrid"
style="width: 400px; height: 170px;"
selectedItemID="" ItemKeyField="Key">
<DIV Style="Position: Relative">
<TABLE cellspacing="0" cellpadding="3pt"
border="1px" bordercolor="Gray"
width="400px" style="table-layout: fixed;" ID="Table1">
<THEAD Class="ColumnHeaders">
<TR height="22px"> </TR>
</THEAD>
</TABLE>
</DIV>
<DIV Style="Position: Static">
<TABLE cellspacing="0"
cellpadding="3pt" border="1px"
bordercolor="LightGrey" width="400px"
style="table-layout: fixed;" ID="Table2">
<TBODY Class="Rows"></TBODY>
</TABLE>
</DIV>
</DIV>
</BODY>
<SCRIPT LANGUAGE="JavaScript"
EVENT="onactivate" FOR="MyWebGrid">
handleWebGrid(MyWebGrid, window.event.srcElement,
window.event.type);
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript"
EVENT="ondeactivate" FOR="MyWebGrid">
handleWebGrid(MyWebGrid, window.event.srcElement,
window.event.type);
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript"
EVENT="onkeypress" FOR="MyWebGrid">
handleWebGrid(MyWebGrid, window.event.srcElement,
window.event.type);
if (window.event.keyCode == 13)
{
alert("Selected " +
MyWebGrid.getAttribute("selectedItemID"));
}
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript"
EVENT="onscroll" FOR="MyWebGrid">
handleWebGrid(MyWebGrid, window.event.srcElement,
window.event.type);
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript"
EVENT="onmouseOver" FOR="MyWebGrid">
handleWebGrid(MyWebGrid, window.event.srcElement,
window.event.type);
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript"
EVENT="onmouseOut" FOR="MyWebGrid">
handleWebGrid(MyWebGrid, window.event.srcElement,
window.event.type);
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript"
EVENT="onclick" FOR="MyWebGrid">
handleWebGrid(MyWebGrid, window.event.srcElement,
window.event.type);
</SCRIPT>
<SCRIPT LANGUAGE="JavaScript"
EVENT="ondblclick" FOR="MyWebGrid">
handleWebGrid(MyWebGrid, window.event.srcElement,
window.event.type);
alert("Selected "+
MyWebGrid.getAttribute("selectedItemID"));
</SCRIPT>
</HTML>
Listing 4: JavaScript library for managing Web grids
// WegGridDefiner.js
// loads the column headers and then creates an xml
// dom document of test data and loads it as rows.
function loader(poWebGrid) {
var oData;
var oElement;
var i;
// add TH cells to the HTML in the Column Headers table
addColumnHeaderWebGrid(poWebGrid,
"Customer", "Cust#", "200px", "Left");
addColumnHeaderWebGrid(poWebGrid,
"Order", "PO#", "100px", "Left");
addColumnHeaderWebGrid(poWebGrid,
"Value", "Value", "200px", "Left");
addColumnHeaderWebGrid(poWebGrid,
"Blank", "", "200px", "Left");
oData = new ActiveXObject("Microsoft.XMLDOM");
oData.documentElement = oData.createElement("Root");
for (i=1; i<10; i++)
{
si = i.toString();
oElement = oData.createElement("foo");
oElement.setAttribute("Key", si);
oElement.setAttribute("Customer", "Customer" + si) ;
oElement.setAttribute("Order", "Order" + si) ;
oElement.setAttribute("Value", "Value" + si);
oElement.setAttribute("Blank", "");
oData.documentElement.appendChild(oElement);
}
loadWebGrid(poWebGrid, oData.documentElement);
}
// adds a new column header
function addColumnHeaderWebGrid(poWebGrid, psKeyField, psTitle,
psWidth, psAlign) {
var oColumnHeaders, oColumnHeader, oChildNode;
// find THEAD node and create new cell
oChildNode = poWebGrid.childNodes(0).childNodes(0)
oColumnHeaders = oChildNode.childNodes(0).childNodes(0);
oColumnHeader = document.createElement("<TH>");
// define new column header
oColumnHeader.id = psKeyField;
oColumnHeader.className = "ColumnHeader";
oColumnHeader.width = psWidth;
oColumnHeader.align = psAlign;
oColumnHeader.innerText = psTitle;
// add new column header to columnheaders row
oColumnHeaders.appendChild(oColumnHeader);
}
// adds rows into the web list from an XML document
function loadWebGrid(poWebGrid, poParentElement) {
var oElement;
var oRows;
var oRow;
var oCell;
var oColumnHeaders;
var oColumnHeader;
var oChildNode;
var iRow;
var iColumn;
// find rows and column define containers
oRows = poWebGrid.childNodes(1).childNodes(0).childNodes(0);
oChildNode = poWebGrid.childNodes(0).childNodes(0)
oColumnHeaders = oChildNode.childNodes(0).childNodes(0);
// load in new rows from the data
for (iRow = 0;
iRow < poParentElement.childNodes.length; iRow++)
{
oElement = poParentElement.childNodes(iRow);
// create row and assign safe ID from key
oRow = document.createElement("<TR>");
oRow.id =
oElement.getAttribute(poWebGrid.ItemKeyField);
oRow.className = "Row";
oRow.Height = "22px";
// add the columns cells to the row
for (iColumn=0; iColumn <
oColumnHeaders.childNodes.length; iColumn++)
{
oColumnHeader =
oColumnHeaders.childNodes(iColumn);
// create cell and format it according to the
//column definition
oCell = document.createElement("TD");
oCell.className = "Cell";
oCell.align = oColumnHeader.align;
oCell.width = oColumnHeader.width;
// add text from data
sText = oElement.getAttribute(oColumnHeader.id)
if (sText.length > 0) {
oCell.innerText = sText;
oCell.title = sText;
} else {
oCell.innerText = " ";
oCell.Title = "(blank/empty)";
}
// add the cell to the row
oRow.appendChild(oCell);
}
// add the row to the table
oRows.appendChild(oRow);
}
}
Listing 5: JavaScript code for handing Web grid events
// WebGridHandler.js
function selectRow(poWebGrid, poRow) {
// deselect any previous selection
if (poWebGrid.selectedItemID.length > 0)
{
if (poWebGrid.selectedItemID != poRow.id)
{
document.getElementById(poWebGrid.selectedItemID
).className = "" ;
}
}
// set selected
poRow.className = "Selected";
poWebGrid.selectedItemID = poRow.id;
// make sure completely visible
if (poWebGrid.childNodes(0).style.posTop >
poRow.style.posTop)
{
poWebGrid.scrollTop = 0;
}
}
function deselectRow(poWebGrid, poRow) {
if (poRow.id != poWebGrid.selectedItemID)
{
poRow.className = "";
}
}
function hoverRow(poWebGrid, poRow) {
if (poRow.id != poWebGrid.selectedItemID)
{
poRow.className = "Hover";
}
}
function handleWebGrid(poWebGrid, poSrcElement, psEvent)
{
var oRow;
switch (poSrcElement.tagName) {
case "DIV":
// listitems scrolling div?
if (poSrcElement.className=="WebGrid")
{
// handle listitems scroll and reposition
// of table header
if (psEvent!="")
{
// ignore any startup events
poWebGrid.childNodes(0).style.posTop =
poWebGrid.scrollTop;
}
}
break;
case "TABLE":
// handle table events - To be determined
break;
case "TH":
// handle column header events - To be determined
break;
case "TD":
// handle ListItem cell events - To be determined
// handle ListItem row events
oRow = poSrcElement.parentElement;
switch (psEvent)
{
case "mouseover":
hoverRow(poWebGrid, oRow);
break;
case "click":
selectRow(poWebGrid, oRow);
break;
case "activate":
selectRow(poWebGrid, oRow);
break;
case "mouseout":
deselectRow(poWebGrid, oRow);
break;
case "deactivate":
deselectRow(poWebGrid, oRow);
break;
}
}
}
Listing 6: Library for managing whiteboards in a Web page
// whiteboard object constructor
function WhiteBoard(psCanvas, psLines) {
// properties
this._penColor = "blue"; // pen color
this.Line = ""; // current line
this.Canvas = psCanvas; // background and boundary
// area in document
this.Lines = psLines; // lines collection area
// in document
this.zindex = 1; // increment to ensure newest
// line is always on top
// methods
this.addLine = addLine; // make the functions methods
this.updateLine = updateLine;
this.stopLine = stopLine;
this.deleteLine = deleteLine;
this.allLines = allLines;
}
// adds a new line to the document at the current point
function addLine(pX, pY) {
if (window.event.srcElement.id == this.Canvas)
{
this.Line = document.getElementById(this.Lines
).appendChild(document.createElement("v:shape"));
this.Line.id = "line" + Math.random().toString();
// set line appearance.
this.Line.strokecolor = this._penColor;
this.Line.stroked = "True";
this.Line.strokeweight = "3";
this.Line.coordsize = "1000,1000";
this.Line.filled = "False";
// defaults position. z-index keeps in foreground
this.Line.style.position = "absolute";
this.Line.style.left = 0;
this.Line.style.top = 0;
this.Line.style.width = "1000";
this.Line.style.height = "1000";
this.Line.style.zindex = this.zindex;
// set start position
this.Line.path.v = "m " + pX + "," + pY + " le";
}
}
// adds new segment to current drawn line if canvas coordinate
function updateLine(pButton, pX, pY)
{
if (pButton == 1)
{
if (window.event.srcElement.id == this.Canvas)
{
if (this.Line != "")
{
this.Line.path.v =
(this.Line.path.v).substring(0,
(this.Line.path.v).length - 1)
+ pX + "," + pY + " e";
} else {
this.addLine(pX, pY);
}
}
} else {
this.stopLine();
}
}
// ends line being drawn
function stopLine() {
this.Line = "";
}
// delete specified line
function deleteLine(psLineId) {
document.getElementById(psLineId).outerHTML = "";
}
// show all line info
function allLines() {
return document.getElementById(this.Lines).innerHTML;
}
Listing 7: Code for displaying a whiteboard in a Web page
<!-- WhiteBoard.htm -->
<html xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<title>Whiteboard Example</title>
<!-- reference VML behaviors -->
<style>
v\:* { behavior: url(#default#VML);}
</style>
<!-- absolute positioning of WhiteBoard -->
<style>
.Canvas {
POSITION:absolute;
TOP:120;LEFT:10;
WIDTH:400;HEIGHT:400;
Z-INDEX:0;
BACKGROUND-COLOR:#ffffcc;
BORDER-RIGHT: solid;
BORDER-TOP: solid;
BORDER-LEFT: solid;
BORDER-BOTTOM: solid
}
.Lines {
POSITION:absolute;
TOP:120;LEFT:10
}
</style>
<SCRIPT src="WhiteBoard.js"
LANGUAGE="JavaScript"></SCRIPT>
<SCRIPT language="JavaScript">
//-------------- Environment Routines -----------------
// update status message to show x,y position on
//whiteboard
function document_onmousemove() {
var x;
var y;
// over canvas ?
if (window.event.srcElement.id != "divCanvas")
{
x = 0;
y = 0;
} else {
x = window.event.offsetX;
y = window.event.offsetY;
}
// update message
window.status = "Position x: " + x + " y: " + y ;
}
// ensures that selection event is cancelled
function cancel_event() {
return false;
}
// bind status bar to show mouse coords and turn off
// selecting
document.onmousemove = document_onmousemove;
document.onselectstart = cancel_event;
//-------------- White Board Routines -----------------
var moWhiteBoard; // define as global for page
moWhiteBoard = new WhiteBoard("divCanvas", "divLines");
//------------------------------------------------//-->
</SCRIPT>
</head>
<body>
<!-- instructions -->
<STRONG>Hold the left mouse button down to draw.<BR>
Double click a line to erase it.<BR>
Double click background to see line definitions.<BR>
</STRONG>
<!-- drawing canvas, z-index keeps in background -->
<!-- Note: Line breaks in the attributes below need
to be removed and are for layout purposes only -->
<div id="divCanvas" class="Canvas"
onmousemove="Javascript:moWhiteBoard.updateLine(
window.event.button, window.event.offsetX,
window.event.offsetY);"
onmousedown="Javascript:moWhiteBoard.addLine(
window.event.offsetX, window.event.offsetY);"
onmouseup="Javascript:moWhiteBoard.stopLine();"
ondblclick="Javascript:alert(moWhiteBoard.allLines());">
</div>
<!-- collector for shapes being drawn. ?
<!--Note: overlaying over whiteboard -->
<!-- Note: Line breaks in the attributes below need
to be removed and are for layout purposes only -->
<div id="divLines" class="Lines"
ondblclick="Javascript:moWhiteBoard.
deleteLine(window.event.srcElement.id);">
</div>
</body>
</html>