Programmer to ProgrammerTM  
Wrox Press Ltd  
   
  Search ASPToday Living Book ASPToday Living Book
Index Full Text
 
ASPToday Home
 
 
Home HOME
Site Map SITE MAP
Index INDEX
Full-text search SEARCH
Forum FORUM
Feedback FEEDBACK
Advertise with us ADVERTISE
Subscribe SUBSCRIBE
Bullet LOG OFF
                         
      The ASPToday Article
October 27, 2000
      Previous article -
October 26, 2000
  Next article -
October 30, 2000
 
   
   
   
Client-side Data Persistence with DIVs   Michael Boone  
by Michael Boone
 
CATEGORY:  Scripting  
ARTICLE TYPE: Overview Reader Comments
   
    ABSTRACT  
 
Article Rating
 
   Useful
  
 48 responses

Well-designed ASP applications seek to maximize client–side data persistence. By doing so, they reduce the flow of data across a network and give the user the most responsive experience possible. One popular technique of doing this is by storing data in a hidden frame, but sometimes we may wish to retain the state of entire screens as the user moves throughout an application. In these cases, the hidden frame as a datastore approach can be modified in order to better achieve our goals, as Michael Boone explains.

   
                   
    Article Discussion   Rate this article   Related Links   Index Entries  
   
 
    ARTICLE

Well–designed ASP applications seek to maximize client–side data persistence. By doing so, they reduce the flow of data across a network and give the user the most responsive experience possible. One popular technique of doing this is by storing data in a hidden frame, but sometimes we may wish to retain the state of entire screens as the user moves throughout an application. In these cases, the hidden frame as a datastore approach can be modified in order to better achieve our goals.

Instead of a hidden datastore frame, we will rely upon a hidden communication frame working in conjunction with a view frame. Requests of the server will be handled by targeting the hidden communication frame, allowing the state of the view frame to be retained. The entire application interface will therefore be persistent, since individual screens will reside in overlapping positioned elements ( DIV s) within the view frame. Any of these screens (e.g. user–entered data on a form, results of a form submission) can now be immediately presented to the user by simply toggling the visibility of the DIV s encompassing each screen. The role of the separate hidden datastore frame can now be incorporated into the application view frame as well. All reusable JavaScript code and data can simply reside in the view frame along with the DIV s that constitute the application screens.

There are many benefits to this approach. The client (not the server) is maintaining the state of every screen in the application. Since we are showing and hiding DIV s, presentation of the screens to the user is instantaneous. In fact, unless we decide to scroll the DIV , the exact position in the screen the user was at previously will be retained. This screen persistence is being accomplished without the additional work of storing to and retrieving from JavaScript objects and writing out screens on the client with document.write() . Client–side code reuse is also simplified since persistent code can be placed in the same document as the DIV s. The application menu can also reside in the view frame with all the screens. This allows hierarchical style menus to be used without either redrawing the entire menu or managing it across multiple frames.

Many classes of applications can take advantage of this framework. The sample application, available for download, is an address book style application. It allows a database of organizations and contact people within those organizations to be searched via a Quick Search or an Advanced Search screen. All contact people satisfying the resulting SQL query are presented on the View Results screen. The user can then select a record for viewing on the View Detail screen. At any time the user can immediately return to a previously entered screen with its state intact. Though the JavaScript code makes use of IE–specific features, it could of course be enhanced so that it is cross–browser compatible.

The hidden communication frame and the view frame are created by a simple frameset page:

<html>
<head>
<title>Client–side persistence with DIVs</title>
</head>
<frameset rows="1,*" border="0" frameborder="0" framespacing="0">
<frame name="ScratchPad" src="About: Working">
<frame name="View" src="View.html" marginwidth="0" marginheight="0">
</frameset>
</html>

The frame named ScratchPad will serve as the hidden communication frame. There is no need to load an initial page into this frame so the About protocol is used. The frame named View will hold all the application screens and the menu bar. View.html contains the DIV s which correspond to the menu and each of the screens. It is loaded into the View frame and uses an embedded style sheet in order to absolutely position the overlapping screens below the menu bar.

#Menu {position: absolute; top: 0; left: 0; height: 100; text–align: center; _
                               visibility: hidden; background–color: #FFFFE0;}

.Screen {position: absolute; top:100; left: 0; text–align: center; padding: 10; _
                  overflow: auto; visibility: hidden; background–color: #FFFFE0;}

There is one DIV of id Menu , positioned at the top of the screen, which holds the menu bar. All application screens are assigned unique ids and are of class Screen , which causes them to be positioned one over the other immediately below the menu bar.

Initializing the application

Before the application is presented to the user, we need to size the screens based on the user's display. Since we must ensure that the entire page has been parsed by the browser, we rely upon the onload event of the document object in order to call our JavaScript Init() function.

<script language="JavaScript">
var oCurOpt=null;
var oCurDiv=null;
var iViewHeight, iViewWidth;

function Init()
{
  iViewHeight=document.body.clientHeight – 100;    
         
  document.all.QuickSrch.style.pixelHeight=iViewHeight;
  document.all.AdvSrch.style.pixelHeight=iViewHeight;
  document.all.Results.style.pixelHeight=iViewHeight;
  document.all.Detail.style.pixelHeight=iViewHeight;
 
  iViewWidth=document.body.offsetWidth;
  document.all.Menu.style.pixelWidth=iViewWidth;
  document.all.QuickSrch.style.pixelWidth=iViewWidth;
  document.all.AdvSrch.style.pixelWidth=iViewWidth;
  document.all.Results.style.pixelWidth=iViewWidth;
  document.all.Detail.style.pixelWidth=iViewWidth;

  oCurOpt=document.all.opt1;
  oCurDiv=document.all.QuickSrch;

  oCurDiv.style.visibility="visible";
  document.all.Menu.style.visibility="visible";
}

By using the clientHeight and offsetWidth properties, the DIV s holding the screens are set to the maximum size that will not cause the entire page to scroll. This is a critical aspect of using this technique. It causes the screens to appear to be residing in a frame, allowing the menu to hold in place if scrolling is necessary.

Init() performs some additional work as well. The Quick Search screen will be shown by default so a handle to the corresponding DIV element is stored in the global variable oCurDiv . Likewise, oCurOpt is assigned a handle to the corresponding menu option. Now the default Quick Search screen can be made visible along with the menu.

The menu has four options, two of which are given a disabled appearance and one which is given a selected appearance since it corresponds to the default Quick Search screen that is initially shown. The following embedded class styles are attached to table cells in order to accomplish this:

.MenuItem {font–size: 9pt; background–color: #C0C0C0; cursor: hand;}
.MenuItemDisabled { color:gray; font–size: 9pt; background–color: #C0C0C0;}
.MenuItemSelected { color:#FFFFC0; font–size: 9pt; background–color: #C0C0C0; cursor:hand;}

Since the styles will be applied to table cells and not to link objects we need to set the cursor attribute to hand for the MenuItem and MenuItemSelected classes. By using the className property of each table cell we will be able to dynamically apply the different styles to update the menu.

Changing screens without going to server

Since none of the menu options require making a request of the server, responding to a menu selection is a relatively simple matter. Each option in the menu is a cell in an HTML table. The onclick event of each table cell calls the JavaScript Show() function with two arguments: the DIV containing the screen to display and the table cell element itself (the menu option that was clicked). The onclick of the Advanced Search menu option, for example, calls Show(document.all.AdvSrch, this) .

Here is the function definition:

function Show(oDiv, oOpt)
{
  if (oCurDiv!=oDiv && oOpt.className!="MenuItemDisabled")
  {
    oCurOpt.className="MenuItem";
    oOpt.className="MenuItemSelected";
    oCurOpt=oOpt;
    oCurDiv.style.visibility="hidden";
    oDiv.style.visibility="visible";
    oCurDiv=oDiv;
  }
}

Show() takes no action if the menu option was reselected or if a disabled option was clicked upon. Otherwise, it changes the class of the previously selected option to MenuItem and sets the class of the newly selected option to MenuItemSelected . Then the previous screen is hidden and the new one displayed. The global variables oCurOpt and oCurDiv are also assigned handles to the elements containing the menu option selected and the screen being displayed.

Changing screens after going to the server

When the user submits a search from the Basic Search or Advanced Search screens, a form is submitted and the hidden frame targeted. The ASP page handling the form submission will send the data to the client in the form of a JavaScript string assigned to the innerHTML property of the appropriate DIV . A call to the Show() function, which resides in the view frame, must also be written out to the client.

In the sample application, GetResults.asp handles the form submission from both the Basic Search and the Advanced Search screens. The database is queried and a Recordset object created. It is quite common to loop through such a Recordset using Response.Write to send the HTML to the client, such as in the following:

Do While NOT oRs.EOF
  Response.Write(oRs("Last_Name") & ", " & oRs("First_Name") & "<br />")
  oRs.MoveNext
Loop

We need, however, to construct a JavaScript string containing this HTML. Instead of immediately writing to the client, we concatenate the data as we loop through the Recordset. The above code, for instance, would be changed to the following:

sData=""
Do While NOT oRs.EOF
  sData=sData & oRs("Last_Name") & ", " & "<br />"
  oRs.MoveNext
Loop

GetResults.asp uses the VBScript variable sData to construct a string in this manner. The data can then be written to the hidden frame as a JavaScript string assigned to the innerHTML property of the DIV we need to update. Since the JavaScript string is delimited by single quotes, we use the Replace function to escape the single quotes that are within the string itself. Here is the end result that is sent to the hidden frame:

sData=Replace(sData,"'","\'")

Response.Write("<script language=""JavaScript"">if(parent.View.oCurDiv)" & _
        "{parent.View.document.all.Results.innerHTML ='" & sData & _
        "';parent.View.Show(parent.View.document.all.Results," & _
        "parent.View.document.all.opt3);}</script>")

The hidden frame, referencing the View frame via its parent Window, can now update the Results Screen. It then calls our Show() function to display the screen and update the menu accordingly. The state of all the other screens in the applications will have been left intact.

Depending on the data, constructing a JavaScript string with ASP can lead to code that is difficult to maintain. It is possible, however, to transfer the contents of the hidden frame to the screen in the View frame with another method as well. Instead of constructing a JavaScript string on the server with ASP, we can simply send down the HTML as we normally would, but within a DIV . Using the onload event of the document, we can then set the innerHTML property of the DIV in the View frame to the innerHTML of the DIV in the document that was just loaded in the hidden frame. For instance:

<html>
<head>
<script>
function UpdView()
{
  oScreens=parent.View.document.all;
  oScreens.Results.innerHTML=document.all.newResults.innerHTML; 
  parent.View.Show(oScreens.Results,oScreens.opt3);
}
</script>
</head>
<body onload="if (parent.View.oCurDiv) UpdView();">
<div id="newResults">
<%
Do While NOT oRs.EOF
  Response.Write(oRs("Last_Name") & ", " & oRs("First_Name") & "<br />")
  oRs.MoveNext
Loop
%>
</div>
</body>
</html>

Going beyond the sample app

The overhead of downloading all application screens into the View frame need not be incurred. If a screen is not likely to be used, it would be best to fetch it the first time it is requested and append it to the document residing in the View frame. This can be done with the createElement() method and the appendChild() method in IE 5. The onclick of a menu selection would then check for the existence of the necessary DIV prior to calling our Show() function. If the DIV were not present, a file containing the DIV would be loaded into the hidden frame. For example, if the sample application's Advanced Query screen were not likely to be used, the onclick of the corresponding menu option could be altered to execute the following:

if (document.all.AdvSrch) 
  Show(document.all.AdvSrch, this); 
else 
  top.ScratchPad.location='GetAdvSrchScreen.asp';

The file GetAdvSrchScreen.asp would contain the Advanced Query screen data within a DIV . On the onload of the document into the hidden frame, a new DIV would be created and appended to the View.html document residing in the View frame. Then the Advanced Query screen data in the hidden frame can be transferred to this newly created DIV and our Show() function can be called. The following code does just that:

<html>
<head>
<script language="JavaScript">
function UpdView()
{
  oScreens=parent.View.document.all;
  var oDiv=parent.View.document.createElement("DIV");
  oDiv.id="AdvSrch";
  oDiv.className="Screen";
  parent.View.document.body.appendChild(oDiv);
  oDiv.style.pixelHeight=parent.View.iViewHeight;
  oDiv.style.pixelWidth=parent.View.iViewWidth;
  oScreens.AdvSrch.innerHTML=document.all.AdvSrch.innerHTML; 
  parent.View.Show(oScreens.AdvSrch,oScreens.opt2);
}
</script>
</head>
<body onload="if (parent.View.oCurDiv) UpdView();">
<div id="AdvSrch">
<!–– screen data ––>
</div>
</body>
</html>

Since we determined this screen was not likely to be used, it was not created in View.html and downloaded into the View frame. We were able to go to the server for it when needed and, by creating and appending a new DIV to View.html , handle future requests for the screen client–side.

Summary

Does this framework eliminate the need to store the state of screens in client–side JavaScript objects? In many instances, yes. Some situations, however, may lend themselves better to the use of JavaScript objects to store screen data. Retaining multiple sets of data for a single screen, for instance, could be accomplished by creating multiple DIV s for the same screen. Relying on an array of JavaScript objects to populate a single DIV is an efficient and elegant solution in this situation however. Consider both methods the next time you are designing for client–side data persistence.

 
 
   
  RATE THIS ARTICLE
  Please rate this article (1-5). Was this article...
 
 
Useful? No Yes, Very
 
Innovative? No Yes, Very
 
Informative? No Yes, Very
 
Brief Reader Comments?
Your Name:
(Optional)
 
  USEFUL LINKS
  Related Tasks:
 
 
   
  Related ASPToday Articles
   
  • Bind XML data to a Tree–Like Structure (October 5, 2000)
  • Dynamic ASP/JavaScript for Data Refresh (September 24, 1999)
  •  
           
     
     
      Related Sources
     
  • Measuring Element Dimension and Location: http://msdn.microsoft.com/workshop/author/om/measuring.asp
  •  
     
           
      Search the ASPToday Living Book   ASPToday Living Book
     
      Index Full Text Advanced 
     
     
           
      Index Entries in this Article
     
  • address book application
  •  
  • appendChild method
  •  
  • client-side data persistence
  •  
  • createElement method
  •  
  • data persistence
  •  
  • DIV element
  •  
  • DIV tag
  •  
  • DOM
  •  
  • extending
  •  
  • Init function
  •  
  • initializing
  •  
  • innerHTML property
  •  
  • JavaScript
  •  
  • persistence
  •  
  • Replace function
  •  
  • screen persistence
  •  
  • Show function
  •  
     
     
    HOME | SITE MAP | INDEX | SEARCH | REFERENCE | FEEDBACK | ADVERTISE | SUBSCRIBE
    .NET Framework Components Data Access DNA 2000 E-commerce Performance
    Security Admin Site Design Scripting XML/Data Transfer Other Technologies

     
    ASPToday is brought to you by Wrox Press (http://www.wrox.com/). Please see our terms and conditions and privacy policy.
    ASPToday is optimised for Microsoft Internet Explorer 5 browsers.
    Please report any website problems to webmaster@asptoday.com. Copyright © 2001 Wrox Press. All Rights Reserved.