One challenge that Web developers often face is creating a user interface that matches the application users’ needs. Specifically, users might need high performance from Web-based application. To improve performance in this situation, analyze the application’s work flow. For example, if an application presents data to the user, does the server retrieve this data each time the user performs a task? If so (and as is the case with server-side code in many applications), each time the user clicks a button, that action triggers another trip to the server. The time to complete each task depends on the speed of the Internet or LAN connection and the demand for computing resources at the time the user performs the task.
As a case in point, an intranet and extranet application I recently developed for a client called for a feature that lets users customize courseware for an online class by creating, reviewing, and adding or removing chapters from the courseware. This task is similar to adding items to an order in an order-entry system. The courseware’s table of contents is dynamic, changing each time a user adds or removes a chapter.
To optimize this application’s performance, I wanted to avoid a design in which the application made a round-trip to the server each time a user added or removed a chapter from the courseware’s table of contents. I needed a way to push the list of available chapters to the browser and let users manipulate the list without involving round-trips to the server.
The courseware’s original scope was 10 chapters, and users could expand the courseware to 15 chapters. The application needed to let the user describe the courseware and maintain the description and table of contents over time. I stored the table of contents and other information about the courseware in a SQL Server database across three tables—Titles, Chapters, TitleChapter. The chapter maintenance feature maintained only the TitleChapter table. For this discussion, I modeled these tables after those in the SQL Server Pubs database.
I also decided to push the data to the browser by using standard Active Server Pages (ASP) code on the server. With this method, I could generate data from SQL Server by using ADO, then send the data to the browser in an HTML stream. This method made it easy to generate the data by using stored procedures, ADO, and server script, which in turn enabled me to use the standard I had available for the application without invoking RDS. Also, keeping the data on the client cut the load to the SQL Server machine by reserving trips to the database for the final update, when the user was finished working with the application. Until then, the data stayed on the client.
Creating the Application
Screen 1 shows the interface for the main page of an example book. The book’s title, The Busy Executive’s Database Guide, is at the top, and the chapters are shown in the table in the order they’ll appear in the book. Let’s walk through some of the lessons I learned while building this page.
I needed the table of chapters and its contents to be in an HTML form to let the application code publish the data back to the server. I could have used RDS to manage a local Recordset, but then some of the database information, such as login info and stored procedure names, would go to the client where unauthorized users could access it. Instead of RDS, I relied on DHTML to manage the data on the client. The application sends the chapters to the client in HTML, then lets the user work with the table of contents on the client. When the application submits the table back to the server, the table of contents is in a simple HTML form that the application ships back to the server when the user clicks the Update Now button.
To create the table, I wrote a loop that stepped through the recordset containing the TitleChapter data and output the correct HTML. To name the table definition, I used the following ID tag:
Naming the table with the ID tags lets me use script to interact with the table at the client.
Next, I used standard HTML to output the table’s header. Then I used ASP script to build the data part of the table. The following two lines of ASP script set the sequence variable used in the loop (iChapterNo) and start the loop through the recordset:
<%do while not rsTitleChapter.eof%>
DHTML makes each HTML element addressable as an object. To quickly and efficiently identify each object, you must give the object an ID value. For example, I identified each row in the table by using this code to give it a custom ID:
<tr id=DetailRow<%= iChapterNo%>>
The ID will be in the form: DetailRow1, DetailRow2, and so on.
When the form is posted to the server, the ASP script that processes the form needs access to the sequence number, which identifies the order of the chapters, the chapter number, and the title ID. This special ASP script will use this information to update the database. To provide the unique sequence number, I created an HTML textbox in the first cell of each row. The textbox id is set to txtSequenceNo + iChapterNo, resulting in a name like txtSequenceNo1 and so on. The value property of the textbox is set to iChapterNo. The code used to generate the correct DHTML is
<INPUT id=txtSequenceNo<%= iChapterNo%> name=txtSequenceNo
<%= iChapterNo%> value=<%= iChapterNo%> size=2></td>
The title for each chapter is also output, as Screen 1 shows. The application needs to display but not update the title text, so I didn’t need to put the title in a textbox. However, I did need to let users dynamically change the title in the client script, so I needed to make the title text addressable by name. So, I created a span element and assigned it a unique ID. A span element lets me name an area of HTML. In this application, span is useful to label the chapter description that is placed in a table TD cell. The ID value for the span has the same name as the textbox:
I also needed to add a way for users to remove a chapter from a book by clicking on a button next to the chapter title. So, I added the following button control code in the table:
<INPUT type="button" onclick="btnDeleteChapter_OnClick"
Each button has a unique number but is tied to the same click event script, btnDeleteChapter_OnClick.
The last item in each row is the ChapterID, which I inserted with another textbox:
The following code completes the loop and moves to the next chapter in the recordset:
iChapterNo = iChapterNo + 1
As I built this application, I planned to use DHTML to dynamically remove chapters from and add chapters to the table as users worked with a book. So I wrote some code to expand and collapse the table as needed. This technique worked fine until I tested the code to update the server with changes. I discovered that when the client posted data to the server by clicking the Update Now button, the browser posted the data that was in the original textboxes back to the server. For example, let’s say the page was originally generated with five chapters and the user added four new chapters. When the user clicked the Update Now button, the browser posted only the five original textboxes. The new textboxes that the script inserted did not post to the server. To solve this problem, I modified the ASP code that generates the original table to output a table that was long enough to hold all potential chapters. This solution created a table that could contain a certain number of chapters. I could show and hide items in the table by addressing them with client script. To control the length of this table, I created a server variable named MaxLinesPerGrid and set it to 30 in the first few lines of code for the page. The code in Listing 1 outputs blank lines to fill out the grid to the maximum lines:
The Client Code
Now let’s move to the client code. This page includes client script to perform many tasks. The work is done on the client to minimize trips back to the server. The first bit of client code I added was in the Window_onload event, which Listing 2 shows. This code retrieves the maximum lines per grid from the LinesInGrid textbox, which was set in the ASP script from the MaxLinesPerGrid variable. The call to the GetNoOfChapters function determines the current number of chapters in the table and sets the NoOfChapters variable.
The first problem I encountered when I worked with the client code was an Object Required error in the browser telling me that an object (oItem) was missing from the client script. At this point, I wasn’t modifying the client script; I was only testing changes to the ASP script. This message was confusing because the object (oItem) is set to a textbox (LinesInGrid) in the page. I looked at the source for the page by selecting View, Source in the browser, and saw the following message:
<p>ADODB.Fields</h3> <font face="Arial" size=2>error '800a0cc1'</h3>
<font face="Arial" size=2>ADO could not find the object in the collection
corresponding to the name or ordinal reference requested by the
This message meant that an error occurred in the ADO code on the server, stopping production of the HTML. The HTML textbox did not exist and triggered the error. The textbox did not exist because ADO couldn’t find the column requested in the recordset and generated an error. In this case, the error that the browser displayed was not the problem but a symptom of the problem. To fix the problem, I changed the SQL statement that generated the recordset to properly request all the required fields. The moral of this story: Check the source script that the browser sees any time you receive an error in the browser.
Another interesting aspect of the code is the lines in the table. The page lets users reduce the number of lines in the table, but they can set this number only once. If the user types a number in the LinesPerGrid textbox, the LinesInGrid_onblur script executes and deletes the extra rows in the table. The script gets access to the LinesInGrid textbox by setting a reference in oItem to the textbox. Then the script sets the textbox to readonly to prohibit any further changes.
DHTML is quite powerful, and when combined with SQL Server, it lets you build an application with an easy-to-use interface and good performance. You can apply these concepts to other applications, such as order-entry applications.