Life Without Refresh

 

Dmitri Khanine and Phil Carrillo

July 2005

Summary: Looks at creating more dynamic user interfaces using JavaScript RPC (Remote Procedure Call) to communicate with the server and update without requiring a browser refresh. (11 printed pages)

Download the sample code associated with this article, MSDNJSRPC.msi.

Contents

Google Suggest
Introducing JavaScript RPC
XmlHttpRequest
Data Binding and More
Web Server Support for JavaScript RPC
The Value of JavaScript RPC: From Mainframe to Client-Server
About the Authors

Internet Explorer and other major browsers have long provided the power and flexibility to support dynamic Web applications that match and surpass their desktop counterparts; however, that power still remains relatively unknown and underutilized. In this article we introduce a JavaScript RPC technique that allows a Web page to communicate with the server without refreshing itself and forcing the user to wait for the next page to load. We will also demonstrate how JavaScript RPC, combined with XML, can simplify your server-side code and make your application more robust and versatile.

Google Suggest

In December 2004 Google launched its famous Google Suggest, where it offers keyword suggestions in real time as you type. Except for providing an excellent search tool, Google Suggest attracted a great deal of attention to JavaScript RPC technique that the tool is based upon. As you type, JavaScript submits HTTP requests to the server and updates the suggestions drop-down box.

Introducing JavaScript RPC

JavaScript RPC technique has been available to the Web scripting community since the introduction of Internet Explorer 3.0 in 1996 (see https://www.microsoft.com/windows/WinHistoryIE.mspx). In essence, the technique allows a Web page to avoid reloading and interrupting user activity when it needs to do a trip to the server for a server-side validation, dynamic lookup, or similar action.

Let's look at an investor profile management screen where an account supervisor may update an investor's details and assign her a financial analyst who is authorized to deal with her portfolio size. Let's ignore the rest of the form and only focus on the dynamic lookup piece, where we will do a background HTTP request to retrieve a list of people who are authorized to handle the portfolio at hand. Remember, we don't want to interrupt an account supervisor who may continue to update the investor's profile while we retrieve analyst data. To illustrate this concept we left a text area comments field that will retain its content without any effort on our end while the financial analyst drop-down updates.

ms972956.lifewithoutrefresh01(en-us,MSDN.10).gif

Figure 1. The sample application

One of the ways this can be accomplished is by posting the form to a hidden frame when the portfolio size selection changes. Here is how our sample form's main page looks:

<html>
   <head>
      <title>Dynamic data acquisition</title>
   </head>
   <frameset cols="100%,0%" frameborder="0">
      <frame name="frameA" id="frameA" src="frame_a.htm">
      <frame name="frameB" id="frameB">
   </frameset>
</html>

Notice that the second frame takes up 0% of the screen, which is what makes it hidden. It also has no document initially loaded, as it lacks the src attribute. Let's now look at our form page frame_a.htm:

<html>
  <head>
    <script language="javascript">
      function HandlerOnChange(){
       var mySelect = document.forms[0].portfolio;
       if (mySelect.selectedIndex>-1){
         var value = mySelect.options[mySelect.selectedIndex].text;
         // Send the request
         top.frameB.document.location.href = "frame_b.htm?value=" + value;
       }
     }
      function buildOptions(optionsArray){
        var mySelect = document.forms[0].analyst;
        if (optionsArray != null){
          mySelect.innerHTML = "";
          for (var i=0; i < optionsArray.length; i++){
            var o = new Option();
            o.text = optionsArray[i];
            mySelect.add(o);
          }
         }
        }
    </script>
  </head>
   <body>
     <form id="form1" name="form1">
       <table>
         <tr>
           <td>Portfolio size:</td>
           <td>
              <select id="portfolio" name="portfolio" 
                 onchange="HandlerOnChange();">
                 <option></option>
                 <option>0 .. 10,000</option>
                 <option>10,000 to 100,000</option>
                 <option>Over 100,000</option>
              </select>
            </td>
          </tr>
          <tr>
            <td>Authorized financial analyst:</td>
            <td>
              <select id="analyst" name="analyst">
              </select>
            </td>
           </tr>
           <tr>
             <td>Comments</td>
             <td>
               <textarea name="comments" rows="3" cols="20"></textarea>
          </td>
        </tr>
      </table>
    </form>
  </body>
</html>

As you can see, on line 10 we are building a request URL based on the index of the currently selected portfolio size. We then submit the request by changing the URL of the hidden frame. After the response comes back, we update the content of our analyst drop-down. Here is what we expect back:

<html>
  <head>
    <script language="javascript">
      var optionsArray = ["John Doe", "Jason Smith", "Michael Roberts"];
      top.frameA.buildOptions(optionsArray);
    </script>
  </head>
  <body>
  </body>
</html>

The data returned by the server are in form of a JavaScript array on line 4. It is being passed to our visible frameA on line 5, where it is used by the buildOptions function to populate our analyst drop-down. We leave the task of producing the above HTML output to an ASP.NET Web form or plain old ASP page as an exercise for the reader.

The hidden frame JavaScript RPC code above will run with cosmetic modifications on Internet Explorer 3.0 and later and all of today's major browsers. In fact, this is the technique that Google Suggest uses as a fall-back when a browser doesn't support the more advanced features that we are now going to explore.

XmlHttpRequest

As you probably noticed, hidden frame RPC, while being portable across most almost all existing browsers, doesn't add much clarity to your code. It introduces a frameset and an extra document to manage, so if you would like to apply it to another page you will most likely have to duplicate that code. It also doesn't provide any specific XML support.

For quite some time, Internet Explorer 5.0 and later and Mozilla-based browsers have included an XmlHttpRequest object that enables us to implement an asynchronous HTTP request in just a few lines of code and provides intrinsic XML support. Let's rewrite our analyst lookup form fragment using XmlHttpRequest. This time will have our server produce a simple XML document instead. Later in this article we shall see how producing XML on the server lets us simplify our server side, making it more versatile and improving performance.

Here is what we expect from the server for a portfolio size range of "10,000 to 100,000":

<employees>
  <employee id="1">John Doe</employee>
  <employee id="2">Jason Smith</employee>
  <employee id="3">Michael Roberts</employee>
  </employees>

Notice how much clarity we gained by trading HTML output for XML. While its size is much smaller, it includes a lot more information in its element names and attributes. Now we can even reuse our sever-side script. For example, to have a standalone employee search by authorized portfolio size, all we need to do is create an XSL stylesheet.

Below is our familiar analyst look-up piece, this time implemented using XmlHttpRequest:

<html>
<head>
  <script language="javascript">
    var xhttp;

    function onSelectionChange(){
      var mySelect = document.forms[0].portfolio;
      if (mySelect.selectedIndex>-1){
        //instantiate XmlHttpRequest

          // Checking if IE-specific document.all collection exists 
          // to see if we are running in IE 
          if (document.all) { 
            xhttp = new ActiveXObject("Msxml2.XMLHTTP"); 
           } else { 
          // Mozilla - based browser 
            xhttp = new XMLHttpRequest(); 
          }
          //hook the event handler
          xhttp.onreadystatechange = HandlerOnReadyStateChange;
          //prepare the call, http method=GET, true=asynchronous call
          xhttp.open("GET", 
            "http://www.myserver.com/getModels?portfolio=" + 
            mySelect.selectedIndex, true);
          //finally send the call
          xhttp.send();          
        }
      }
      function HandlerOnReadyStateChange(){
        var mySelect = document.forms[0].analyst;
        
        // This handler is called 4 times for each 
        // state change of xmlhttp
        // States are: 0 uninitialized
        //      1 loading
        //      2 loaded
        //      3 interactive
        //      4 complete
        if (xhttp.readyState==4){
          mySelect.innerHTML = "";
          
          //responseXML contains an XMLDOM object
          var nodes = xhttp.responseXML.selectNodes("//employee");
          for (var i=0; i<nodes.length; i++){
            var o = new Option();
              o.text = nodes(i).text;
            mySelect.add(o);
          }
        }
      }
    </script>
  </head>
  <body>
    <form id="form1" name="form1">
      <table>
        <tr>
          <td>Portfolio size:</td>
          <td>
            <select id="portfolio" name="portfolio" 
             onchange="onSelectionChange();">
              <option></option>
              <option>0 .. 10,000</option>
              <option>10,000 to 100,000</option>
              <option>Over 100,000</option>
            </select>
          </td>
        </tr>
        <tr>
          <td>Authorized financial analyst:</td>
          <td>
            <select id="analyst" name="analyst"> 
            </select>
          </td>
        </tr>
        <tr>
          <td>Comments</td>
          <td>
            <textarea name="comments" rows="3" cols="20"
              ID=Textarea1></textarea>
          </td>
        </tr>
      </table>
    </form>
  </body>
</html>

On lines 13 through 17 we instantiate an XmlHTTP control if we are running in Internet Explorer, or an XmlHttpRequest object if FireFox or another Mozilla-based browser runs our script. Both have almost identical interfaces. We then assign a handler for an onReadyStateChange event that will tell us when a response is ready to process. On lines 22 and 25 we open a connection and submit the request. Our event handler verifies whether we have a response to process (Line 37) and if so uses the XML document conveniently created for us by XmlHttpRequest to populate the analyst drop-down.

Data Binding and More

Let's face it, with Internet Explorer being by far the dominant browser, it's time to seriously consider its unique features, especially when they make our lives a lot easier. Once you get comfortable using XmlHttpRequest you may consider looking at XML data islands and data binding (see Fun with Data Binding). Data-bound XML islands ease development by automatically updating their content when form data changes, thus eliminating the need to code individual bindings. Direct updates to data-bound XML islands also reflect immediately on the form display, making it bi-directional. Whenever an update or refresh of the data is required, XmlHttpRequest can do this cleanly and efficiently, making it a Swiss army knife in your drawer of tools.

Web Server Support for JavaScript RPC

Even though it is entirely possible to successfully use JavaScript RPC within existing applications, it offers an enormous productivity, performance, and scalability boost on the server side if you consider the following changes:

  • Produce XML on the server instead of HTML. Consume it with JavaScript on the client or transform it with XSL.

    Switching HTML response to XML makes your application versatile and capable of supporting multiple presentation layers. A single application can serve to a cell phone WAP browser, Internet Explorer 6.0 client, and B2B partner applications. It also keeps server responses small, thus reducing traffic and bandwidth requirements.

  • Keep your server-side logic to data manipulation only. It should have no presentation logic in it.

    Designs with a clean separation of business and presentation logic, such as in the Model-View-Controller (MVC) pattern, make applications easier to develop and maintain.

  • There is no real need for state management when you use JavaScript RPC.

    With JavaScript RPC you are not forced to refresh every time you need to communicate with the server. Your presentation layer becomes stateful and doesn't need to rely on the Session object to maintain its state. This makes the infamous resource and scalability issues go away; see Tip 6: Using the Session Object Wisely.

Let's see how the ASP.NET HttpHandler can be used to implement a fast and light Web tier to support our browser JavaScript RPC application. Below is a bare-bones HttpHandler that produces the following XML using the System.Xml.XmlTextWriter class:

<employees>
  <employee id="5">John Doe</employee> 
      </employees>

It also checks whether an XML document has been submitted to it via the POST method and if one is found, loads it into the DOM document. Notice that you have to implement the IHttpHandler interface and its methods IsReusable (to specify whether your class can be reused for subsequent requests) and ProcessRequest. The latter allows you to do everything you need to produce XML response as its HttpContext parameter exposes all the objects you'd expect from an ASP.NET page, such as Request, Response, and Application.

using System;
using System.Web;
using System.Xml;
namespace Test {
  public class MyHandler : IHttpHandler {
    public bool IsReusable {
      get {
        return true;
      }
    }
    
    public void ProcessRequest(HttpContext ctx) {
      XmlDocument xmlDoc = null;

      // If XML document was submitted via POST method
      //   then load it in the DOM document:
      if (ctx.Request.InputStream.Length > 0 && 
        ctx.Request.ContentType.ToLower().IndexOf("/xml") > 0) {
        xmlDoc = new XmlDocument();
        xmlDoc.Load( ctx.Request.InputStream );
      }

      // Produce XML response
      ctx.Response.ContentType = "application/xml";
      XmlTextWriter xw = new XmlTextWriter(ctx.Response.OutputStream, 
        new System.Text.UTF8Encoding());

      xw.WriteStartElement("employees");
        xw.WriteStartElement("employee");
          xw.WriteAttributeString("id", "5");
          xw.WriteString("John Doe");
        xw.WriteEndElement(); // employee
      xw.WriteEndElement(); // employees

      xw.Close();
    }
  }
}

There are several options you can use to invoke your HttpHandler, but the simplest one is to use an HttpHandler .ashx file. An HttpHandler file is just like a regular ASP.NET .aspx file, and in this sample we use it to specify a class we want to invoke.

<% @ webhandler class="Test.MyHandler" %>

If we save this line to sample.ashx into our local Web root folder and put the compiled assembly with the Test.MyHandler class that implements IHttpHandler interface into the bin directory, we can use https://localhost/sample.ashx to invoke the handler.

What About AJAX?

In his essay Ajax: A New Approach to Web Applications Jesse James Garrett of Adaptive Path coined the term Ajax (Asynchronous JavaScript and XML) to describe a "new" way of developing dynamic Web applications. Since then Ajax became a buzzword all over the Web development community. But Garrett's term misses the key and required ingredient of the technique—the actual HTTP request.

The Value of JavaScript RPC: From Mainframe to Client-Server

In the mid-1970s IBM introduced 3270 terminals that were smart enough to present the user with custom markup-driven forms, collect the input, and send it back to the mainframe (see http://www.columbia.edu/acis/history/3270.html). Despite their graphic nature and ubiquity, most conventional Web applications are still based on that model, where the user either browses the site content or fills in simple forms and waits for results to be processed. In this model, the browser's role is limited to graphic effects and simple form validation.

Since the advent of more powerful and popular desktop computers, more and more processing has been offloaded to the client and the client-server paradigm has emerged. Well-written client-server applications behaved just like their desktop counterparts that are always available and responsive. Most of the server communication has been done in the background without interrupting the current user activity. So when do we expect our Web applications to behave like that? How about today? If my desktop e-mail client doesn't stop me from typing while it checks for new mail, why should the Web-based one? With techniques you've just learned, you are now fully equipped to provide your users with the seamless and dynamic user interface they've long been waiting for.

 

About the Authors

Dmitri Khanine is an enterprise Web developer who is excited about the great potential of existing technologies. Dmitri focuses on making developers more productive while delivering better applications. Dmitri's blog is at http://dmitrikhanine.blogspot.com.

Phil Carrillo is a self-taught software developer. He has 6 years of development experience with Web application design, specializing in user interfaces. Phil is currently a senior software engineer at Royal Bank of Canada. He has a great deal of experience architecting and implementing e-commerce solutions, reusable components, and common application frameworks. You can contact Phil at Felipe.Carrillo@rbccm.com.

© Microsoft Corporation. All rights reserved.