Click here to Skip to main content
Click here to Skip to main content

AJAX Was Here - Part 1: Client Side Framework

By , 2 May 2005
 

Introduction

AJAX, if you haven’t already heard, stands for Asynchronous JavaScript And XML. If you’re unfamiliar with what AJAX is, I suggest reading AJAX: A New Approach to Web Applications by Jesse James Garrett. AJAX is a relatively new name for a set of technologies that have been around for quite some time. I recently signed up for a G-Mail account and was very pleased with its responsiveness and lack of obnoxious advertising. As a developer I thought that the site's performance was due to the lean layout and minimal use of graphics or WYSIWYG text editors. It turns out the G-Mail is a prime example of AJAX at work. After uncovering this revelation I decided to jump on the AJAX banding wagon and see what all the fuss was about.

At the heart of AJAX lies the XmlHTTPRequest (or XMLHTTP) object. This little doozy allows client side developers to initiate a request to a web page. For a nice reference on the XmlHTTPRequest object, see The XmlHTTPRequest Object. With a few lines of JavaScript, we can now initiate an Asynchronous request for a web page. Asynchronous means the request is made and the user doesn’t have to wait for the page to load or stare at an hourglass waiting for something to happen. The web request means we can now have seamless interoperability between client code (ala JavaScript) and server code (ASP.NET, JSP, PHP, etc.). If you didn’t say WOW you should at least be thinking it. With a little digging, you can find a number of examples on how to use the XmlHTTPRequest object. The goal of this article is to develop a framework for using the XmlHTTPRequest object in conjunction with ASP.NET. There are two things I’m trying to achieve with this framework:

Something easy for the client side developer to use to initiate an asynchronous HTTP request (which we’ll term a “Call Back”). This is the main focus of Part 1 of this article series. Something easy for the server side developer to integrate into their code (preferably without a lot of fancy or proprietary workarounds). We will focus on this in Part 2 of the series.

The Good Stuff

When I think about OOP and JavaScript the word “hack” comes to mind…but, I have created what I call the CallBackObject which we will go over in the remainder of this article (see Listing 1). The CallBackObject is simply a wrapper for the XmlHTTPRequest object. The CallBackObject allows the client side developer to initiate an asynchronous HTTP request (CallBackObject.DoCallBack) as well as provides a number of events for the developer to use to respond to changes in the state of the XmlHTTPRequest object. Let’s take it one method at a time.

  • Function CallBackObject()

    This is the JavaScript constructor that is executed when a new CallBackObject is created like so:

      var cbo = new CallBackObject();

    The constructor creates a new XmlHTTPRequest object with a call to GetHttpObject() and assigns it to the member variable XmlHttp. I wish it were more exciting than that.

  • Function GetHttpObject()

    The code for creating an XmlHTTPRequest object can vary depending on which browser is being used. Jim Lay posted the code that makes up the GetHttpObject method back in April 2002 (see, AJAX isn’t new). Essentially, IE browsers attempt to create an instance of the XMLHTTP ActiveX control, and Mozilla browsers create an XmlHTTPRequest object. If the browser doesn’t support XmlHTTPRequest, then the code does nothing.

  • Function DoCallBack(eventTarget, eventArgument)

    This is where the magic happens. Remember, one of the goals of the CallBackObject is to make asynchronous HTTP requests that can be easily integrated into ASP.NET. ASP.NET coders should recognize the general format of this function, as it closely mimics the .NET doPostBack() JavaScript function that initiates server side events in ASP.NET. Let’s take it slow.

      var theData = '';
      var theform = document.forms[0];
      var thePage = window.location.pathname + window.location.search;  
      var eName = '';

    Here we are declaring some variables to hold all of the form data theData, grabbing a reference to the current form theform, and obtaining the name of the current page.

      theData  = '__EVENTTARGET=' + 
             escape(eventTarget.split("$").join(":")) + '&';

    This is identical to doPostBack(), we are essentially telling ASP.NET which control is responsible for the Call Back. The escape is necessary to URLEncode any data posted to the server, the split and join return ASP.NET control ID’s to their proper form. It isn’t critical to understand why we are doing this to understand the JavaScript, I’ll cover this in more detail in Part 2 of this article.

      theData += '__VIEWSTATE=' + 
           escape(theform.__VIEWSTATE.value).replace(new 
                             RegExp('\\+', 'g'), '%2b') + '&';

    The ViewState is that magical hunk of Base64 encoded text that makes web programmers’ jobs a lot easier. Unfortunately, the JavaScript escape function doesn’t handle the ‘+’ sign, so we have to manually encode it by substituting ‘%2b’ in its place.

      theData += 'IsCallBack=true&';

    This line lets the server side code know that the current request is a CallBack, initiated by the client side code. Otherwise, the server would assume it was a normal web request and might handle things differently.

    for( var i=0; i< i++ i++)
    {
      eName = theform.elements[i].name;
    
      if( eName && eName != '')
      {
        if( eName == '__EVENTTARGET' || eName == '__EVENTARGUMENT' 
                                       || eName == '__VIEWSTATE' )
        {
          // Do Nothing
        }
        else
        {
          theData = theData + escape(eName.split("$").join(":")) + '=' + 
                                              theform.elements[i].value;
          if( i != theform.elements.length - 1 )
            theData = theData + '&';
        }
      }
    }

    Finally, we loop through the rest of the form elements (input boxes, check boxes, drop down lists, etc.) and append their names and values to our theData variable.

    Now you might be wondering why we did all this. After all that processing theData now contains exactly the same information that is sent to the server whenever the “Submit” button is clicked on a form. We’re ready to make our asynchronous request to the server.

    if( this.XmlHttp )
    { 
      if( this.XmlHttp.readyState == 4 || this.XmlHttp.readyState == 0 )
      {
        var oThis = this;
        this.XmlHttp.open('POST', thePage, true);
        this.XmlHttp.onreadystatechange = function()
                                     { oThis.ReadyStateChange(); };
        this.XmlHttp.setRequestHeader('Content-Type', 
                                    'application/x-www-form-urlencoded');
        this.XmlHttp.send(theData);
      }
    }

    First, we make sure we have a valid XmlHTTPRequest object. Then, we check to make sure that the XmlHTTPRequest object is ready to make a new request.

    Then, save a reference to the current object (CallBackObject).

      var oThis = this;

    Open an asynchronous connection to the current page using the POST method.

      this.XmlHttp.open('POST', thePage, true);

    As the state of our XmlHTTPRequest object changes, we want to be able to take various actions. We tell the object to call CallBackObject.ReadyStateChange() any time the state changes.

      this.XmlHttp.onreadystatechange = function()
                     { oThis.ReadyStateChange(); };

    Finally, send all the form data along with the request.

      this.XmlHttp.send(theData);

    That’s it!!! We have successfully made an asynchronous web request using JavaScript… now what? Well, in most cases, you will be expecting some sort of response from the server after making your request. When the response comes back, you can process any data returned from the server. How will you know when the response comes back? That is where the ReadyStateChange() method comes in handy.

  • Event Handler ReadyStateChange()

    The XmlHTTPRequest object has four main states/state codes, Loading-1, Loaded-2, Interactive-3, and Complete-4. To allow the client side developer to respond to each of those states, ReadyStateChange() receives the new state from the XmlHTTPRequest object, then raises the proper event.

    CallBackObject.prototype.ReadyStateChange = function()
    {
      if( this.XmlHttp.readyState == 1 )
      {
        this.OnLoading();
      }
      else if( this.XmlHttp.readyState == 2 )
      {
        this.OnLoaded();
      }
      else if( this.XmlHttp.readyState == 3 )
      {
        this.OnInteractive();
      }
      else if( this.XmlHttp.readyState == 4 )
      {
        if( this.XmlHttp.status == 0 )
          this.OnAbort();
        else if( this.XmlHttp.status == 200 && 
                         this.XmlHttp.statusText == "OK")
          this.OnComplete(this.XmlHttp.responseText, 
                         this.XmlHttp.responseXML);
        else
          this.OnError(this.XmlHttp.status, 
                         this.XmlHttp.statusText,
            this.XmlHttp.responseText);   
      }
    }

    A state code of 4 means complete but may not mean that things went according to plan. If a request was aborted then the state is changed to Completed-4, but the status is Unkown-0, so we raise an event for that as well. A successful HTTP request should yield a response of status 200-OK, anything else is considered an error and the OnError event is raised.

  • Event OnComplete(responseText, responseXML)

    The OnComplete event makes the responseText and responseXML available to the client side developer. These values will depend on the data returned from the server side response.

      CallBackObject.prototype.OnComplete = 
                 function(responseText, responseXml)
  • Event OnError(status, statusText, responseText)

    The OnError event provides some feedback on what may have gone wrong with the request. In this event you may re-initiate a request or indicate to the user that something went wrong.

But What Does it All Mean??

Well, that’s a lot to digest. AJAX requires an understanding of both client side and server side technologies, so it’s difficult to provide an example without delving into the server side aspect of things. For an explanation of the server side of things, read AJAX was Here Part 2 – ASP.NET Integration. See Listing 2 for the complete client side portion of the CallBackObject example. I’ll be covering it in more detail in Part 2, as well as showing you where the server side code fits in.

Conclusion

We’ve taken care of the bulk of the client side portion of things with the CallBackObject. Client developers can now make asynchronous requests without having to worry about too much of the details, but we still have some demystifying to do.

Listing 1 - CallBackObject.js

function CallBackObject()
{
  this.XmlHttp = this.GetHttpObject();
}
 
CallBackObject.prototype.GetHttpObject = function()
{ 
  var xmlhttp;
  /*@cc_on
  @if (@_jscript_version >= 5)
    try {
      xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      try {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (E) {
        xmlhttp = false;
      }
    }
  @else
  xmlhttp = false;
  @end @*/
  if (!xmlhttp && typeof XMLHttpRequest != 'undefined'){
    try {
      xmlhttp = new XMLHttpRequest();
    } catch (e) {
      xmlhttp = false;
    }
  }
  return xmlhttp;
}
 
CallBackObject.prototype.DoCallBack = 
                     function(eventTarget, eventArgument)
{
  var theData = '';
  var theform = document.forms[0];
  var thePage = window.location.pathname + window.location.search;
  var eName = '';
 
  theData  = '__EVENTTARGET='  + 
              escape(eventTarget.split("$").join(":")) + '&';
  theData += '__EVENTARGUMENT=' + eventArgument + '&';
  theData += '__VIEWSTATE=' + 
              escape(theform.__VIEWSTATE.value).replace(new 
                              RegExp('\\+', 'g'), '%2b') + '&';
  theData += 'IsCallBack=true&';
  for( var i=0; i<theform.elements.length; i++ )
  {
    eName = theform.elements[i].name;
    if( eName && eName != '')
    {
      if( eName == '__EVENTTARGET' || eName == '__EVENTARGUMENT' 
                                       || eName == '__VIEWSTATE')
      {
        // Do Nothing
      }
      else
      {
        theData = theData + escape(eName.split("$").join(":")) + '=' + 
                                            theform.elements[i].value;
        if( i != theform.elements.length - 1 )
          theData = theData + '&';
      }
    }
  }
 
  if( this.XmlHttp )
  {
    if( this.XmlHttp.readyState == 4 || this.XmlHttp.readyState == 0 )
    {
      var oThis = this;
      this.XmlHttp.open('POST', thePage, true);
      this.XmlHttp.onreadystatechange = function()
                                        { oThis.ReadyStateChange(); };
      this.XmlHttp.setRequestHeader('Content-Type', 
                                 'application/x-www-form-urlencoded');
      this.XmlHttp.send(theData);
    }
  }
}
 
CallBackObject.prototype.AbortCallBack = function()
{
  if( this.XmlHttp )
    this.XmlHttp.abort();
}
 
CallBackObject.prototype.OnLoading = function()
{
  // Loading
}
 
CallBackObject.prototype.OnLoaded = function()
{
  // Loaded
}
 
CallBackObject.prototype.OnInteractive = function()
{
  // Interactive
}
 
CallBackObject.prototype.OnComplete = 
                   function(responseText, responseXml)
{
  // Complete
}
 
CallBackObject.prototype.OnAbort = function()
{
  // Abort
}
 
CallBackObject.prototype.OnError = 
                         function(status, statusText)
{
  // Error
}
 
CallBackObject.prototype.ReadyStateChange = function()
{
  if( this.XmlHttp.readyState == 1 )
  {
    this.OnLoading();
  }
  else if( this.XmlHttp.readyState == 2 )
  {
    this.OnLoaded();
  }
  else if( this.XmlHttp.readyState == 3 )
  {
    this.OnInteractive();
  }
  else if( this.XmlHttp.readyState == 4 )
  {
    if( this.XmlHttp.status == 0 )
      this.OnAbort();
    else if( this.XmlHttp.status == 200 && 
                            this.XmlHttp.statusText == "OK" )
      this.OnComplete(this.XmlHttp.responseText, 
                            this.XmlHttp.responseXML);
    else
      this.OnError(this.XmlHttp.status, 
           this.XmlHttp.statusText, this.XmlHttp.responseText);   
  }
}

Listing 2 - Article.aspx

<%@ Page language="c#" Codebehind="Article.aspx.cs" 
      AutoEventWireup="false" Inherits="AJAX.Article" %>


HTML
  <head>
    
    <script type="text/javascript" src="CallBackObject.js">
    </script>
  </head>
  BODY
    <script type="text/javascript">
      var Cbo = new CallBackObject();
      Cbo.OnComplete = Cbo_Complete;
      Cbo.OnError    = Cbo_Error;
 
      function CheckUsername(Username)
      {
        var msg = document.getElementById('lblMessage');
        if( Username.length > 0 )
        {
          Cbo.DoCallBack('txtUsername', '', true);
        }
        else
        {
          Cbo.AbortCallBack();
          msg.innerHTML = '';
        }
      }
 
      function Cbo_Complete(responseText, responseXML)
      {
        var msg = document.getElementById('lblMessage');
        if( responseText == 'True' )
        {
          msg.innerHTML = 'Username Available!';
          msg.style.color = 'green';
        }
        else
        {
          msg.innerHTML = 'Username Unavailable!';
          msg.style.color = 'red';
        }
      }
 
      function Cbo_Error(status, statusText, responseText)
      {
        alert(responseText);
      }
    </script>
    <form id="frmAjax" method="post" runat="server">
      <TABLE>
        <TR>
          <TD>Username:</TD>
          <TD>
            <asp:TextBox Runat="server" ID="txtUsername" 
              onkeyup="CheckUsername(this.value);" 
              OnTextChanged="txtUsername_TextChanged" />
          </TD>
          <TD><asp:Label Runat="server" ID="lblMessage" /></TD>
        </TR>
        <TR>
          <TD align=left colSpan=3>
            <asp:Button Runat="server" ID="btnCheckUsername" 
              OnClick="btnCheckUsername_Click" 
              Text="Check Username Availability" />
          </TD>
        </TR>
      </TABLE>
    </form>
  </body>
</HTML>

History

  • 2005-04-21
    • Added links to Part 2.
    • Updated source code download.
    • Removed abortCurrent argument from DoCallBack.
    • Learned that XmlHTTPRequest.open automatically aborts any current requests.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Bill Pierce
Software Developer (Senior)
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionRunning the code mentioned in AJAXWasHere-Part1.asp does not work for DOT Net version 2.0memberHemant Israni8 Aug '07 - 4:46 
AnswerRe: Running the code mentioned in AJAXWasHere-Part1.asp does not work for DOT Net version 2.0mvpJohn Simmons / outlaw programmer2 May '08 - 2:46 
QuestionNot able to access value of the control.memberimtiyaz_alamshah23 Jul '07 - 21:29 
GeneralAre you using Prototypememberdhmason19 Feb '07 - 10:47 
GeneralUploading filememberdjrocks29 Aug '06 - 0:05 
Questionfor( var i=0; i< i++ i++)??memberKlom Dark29 Jun '06 - 8:20 
AnswerRe: for( var i=0; i< i++ i++)??memberSteff Henrique15 Aug '12 - 8:06 
Questionhow many simultaneous requests?membersstoyan15 Feb '06 - 22:09 
AnswerRe: how many simultaneous requests?memberKlom Dark29 Jun '06 - 8:27 
GeneralAjax ProjectsmemberHazem Torab4 Jan '06 - 10:53 
Questionany functional example?memberTibor Blazko3 Jan '06 - 1:04 
NewszumiPage for ASP.NETmemberAmir L.21 Dec '05 - 13:12 
GeneralThread abort exceptionmemberRonan Dodworth25 Oct '05 - 5:00 
GeneralRe: Thread abort exceptionmemberchorpeac26 Sep '06 - 10:19 
QuestionCustom Custom Control (Callback)memberruven.naidoo@gmail.com13 Oct '05 - 22:41 
AnswerRe: Custom Custom Control (Callback)memberBill Pierce14 Oct '05 - 5:05 
GeneralOpera compabilitymemberWhyer24 Sep '05 - 4:08 
GeneralRe: Opera compabilitymemberBill Pierce24 Sep '05 - 16:08 
GeneralComfortASPsussAnonymous20 Sep '05 - 10:34 
GeneralThanks for the excellent seriesmemberArgiris Kirtzidis15 Sep '05 - 21:09 
GeneralExcellent Articlememberswolfman2 Sep '05 - 5:40 
QuestionSupport for Checkboxes?memberjhyatt_228 Jun '05 - 10:07 
AnswerRe: Support for Checkboxes?memberamitchell30 Jun '05 - 23:18 
GeneralRe: Support for Checkboxes?memberjhyatt_24 Jul '05 - 6:35 
GeneralError on txtUsername_TextChangesussAnonymous27 May '05 - 3:47 
GeneralRe: Error on txtUsername_TextChangememberBill Pierce27 May '05 - 3:54 
GeneralRe: Error on txtUsername_TextChangesussAnonymous27 May '05 - 4:52 
Generalwebservices.htcmemberi.j.russell16 May '05 - 2:10 
GeneralRe: webservices.htcmemberBill Pierce16 May '05 - 4:13 
GeneralGreat StuffmemberGautam Hotti13 May '05 - 4:57 
QuestionWhat I was looking formemberscubaduba5 May '05 - 20:53 
GeneralVery good stuffmemberThiago Rafael5 May '05 - 17:01 
GeneralExcellentmemberTim Kohler2 May '05 - 9:10 
GeneralJavascript 'hack'susskillerKiwinz2719 Apr '05 - 13:02 
GeneralRe: Javascript 'hack'memberBill Pierce19 Apr '05 - 14:12 
GeneralRe: Javascript 'hack'sussAnonymous27 Apr '05 - 11:03 
GeneralRe: Javascript 'hack'memberrball3 May '05 - 4:47 
GeneralRe: Javascript 'hack'sussAnonymous27 Apr '05 - 19:42 
GeneralRe: Javascript 'hack'memberJames Curran3 May '05 - 6:29 
GeneralRe: Javascript 'hack'sussAnonymous4 May '05 - 4:25 
GeneralRe: Javascript 'hack'sussAnonymous4 May '05 - 19:02 
GeneralRe: Javascript 'hack'memberMJH1 Nov '05 - 13:40 
GeneralRe: Javascript 'hack'memberjustice-23 May '05 - 7:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 2 May 2005
Article Copyright 2005 by Bill Pierce
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid