JavaScript Editor Free JavaScript Editor     JavaScript Debugger 




Main Page

Previous Page
Next Page

8.5. Tabular Information and Forms

With the server side taken care of, there are three ways to proceed with developing on the client side. The first is to continue developing the way that we've been developing, hand-coding every function. Although this would give us a really good understanding of how the application works, it would take forever to develop anything useful.

The second approach is to get online and find a suitable Ajax library, download it, and proceed with developing. Currently, quite a number of them are out there, such as Sarissa and JSON (pronounced "Jason"). (However, if memory serves, Jason was the leader or the Argonauts, whereas Ajax was a hero of the Trojan War.)

The third possibility is to write our own Ajax libraryor, rather, use one that I've already written. This approach is useful for several reasons, the first being that I'll (hopefully) know exactly how the library works. The second reason is that I can dissect them in a later chapter so that we'll know exactly how they work. The final reason is that it will help to pad the page counteh, I mean, to increase the depth of these examples. Table 8-2 briefly describes the classes in the library, along with their associated methods and properties.

Table 8-2. Ajax Library Classes

Name

Parent Class

Type

Description

XMLHttpRequest

Class

Constructor

action

XMLHttpRequest

Property

GET, POST, or HEAD

asynchronous

XMLHttpRequest

Property

true or false

envelope

XMLHttpRequest

Property

SOAP envelope

readyState

XMLHttpRequest

Method

Returns the document readyState

getresponseHeader

XMLHttpRequest

Method

Returns a single HTTP response header

getAllResponseHeaders

XMLHttpRequest

Method

Returns all HTTP response headers

responseText

XMLHttpRequest

Method

Returns the SOAP response as text

responseXML

XMLHttpRequest

Method

Returns the SOAP response as an XML document

stateChangeHandler

XMLHttpRequest

Method

Dummy state change handler

setRequestHeader

XMLHttpRequest

Method

Sets an HTTP response header

removeRequestHeader

XMLHttpRequest

Method

Removes a previously set HTTP response header

Send

XMLHttpRequest

Method

Sends the XMLHttpRequest

Cache

Class

Constructor

insert

Cache

Method

Inserts a name/value pair

retrieve

Cache

Method

Retrieves a value

purge

Cache

Method

Purges one or more name/value pairs

names

Cache

Method

Returns an array of names

XMLDocument

Class

Constructor

Load

XMLDocument

Method

Loads an\ XML document

serialize

XMLDocument

Method

Serializes an XML document to text

DOMDocument

XMLDocument

Method

Returns an XML document

readyState

XMLDocument

Method

Returns the document readyState

setRequestHeader

XMLDocument

Method

Sets an HTTP response header

getresponseHeader

XMLDocument

Method

Returns a single HTTP response header

getAllResponseHeaders

XMLDocument

Method

Returns all HTTP response headers

setEnvelope

XMLDocument

Method

Sets the envelope for an XMLHttpRequest

selectNodes

XMLDocument

Method

Returns an array of XML nodes

SOAPEnvelope

Class

Constructor

envelope

SOAPEnvelope

Method

SOAP envelope


Now that the foundations of the application architecture have been covered, albeit lightly, this is a good time to see what the HTML page built upon that architecture looks like. Figure 8-6 shows what it looks like in a browser, and Listing 8-19 shows the HTML and JavaScript.

Figure 8-6. Ajax page


Listing 8-19. Ajax Page

<html>
  <head>
    <title>chapter4</title>
    <link rel="stylesheet" type="text/css" href="common.css"/>
    <script language="JavaScript" src="Cache.js"></script>
    <script language="JavaScript" src="XMLHTTPRequest.js"></script>
    <script language="JavaScript" src="XMLDocument.js"></script>
    <script language="JavaScript" src="SOAPEnvelope.js"></script>
    <script language="javascript">
<!-- <![CDATA[
try {var x = new DOMParser(); var _IE = false; } catch(e)
{ var _IE = true; };
var xml = new XMLDocument();
var soap = new SOAPEnvelope();
var pageName = 'Items';
var itemsXHTMLStart = '<table width="960px" border="1" cellpadding="2"
cellspacing="2"><tr class="rowHeader">
<th width="10%">Guild</th><th width="70%">Item Name</th><th>
Item Price</th></tr>';
var itemsXHTMLEnd = '</table>';
var itemsInnerXHTML = '<tr class="rowData" id="data">
<td align="center"><a href="javascript:pageLoad(\'Items\',@guild)"
xmldi="xmlDI" xmlnode="guild_name"></a></td><td align="left">
<a href="javascript:pageLoad(\'Detail\',@item)"><div id="value"
xmldi="xmlDI" xmlnode="item_name"></div></a></td>
<td class="numeric">$<span xmldi="xmlDI"
xmlnode="item_price"></span></td></tr>';
var detailXHTML = '<div><div class="rowHeader" style="position: absolute;
left: 50px; right: auto%; bottom: auto; width: 200px; top: 75px"> Guild
Name:</div><div class="rowHeader" style="position: absolute; left: 50px;
right: auto%; bottom: auto; width: 200px; top: 92px"> Item Name:</div><div
class="rowHeader" style="position: absolute; left: 50px; right: auto%;
bottom: auto; width: 200px; top: 110px"> Description:</div><div
class="rowHeader" style="position: absolute; left: 50px; right: auto%;
bottom: auto; width: 200px; top: 127px"> Price:</div><div
class="rowHeader" style="position: absolute; left: 50px; right: auto%;
bottom: auto; width: 200px; top: 144px"> Quantity:</div><div
class="rowData" style="position: absolute; left: 255px; right: auto;
bottom: auto; width: 600px; top: 75px" xmldi="xmlDI"
xmlnode="guild_name"></div><div class="rowData" style="position: absolute;
left: 255px; right: auto; bottom: auto; width: 600px; top: 92px"
xmldi="xmlDI" xmlnode="item_name"></div>
<div class="rowData" style="position: absolute; left: 255px; right: auto;
bottom: auto; width: 600px; top: 110px" xmldi="xmlDI"
xmlnode="item_description"></div><div class="rowData" style="position:
absolute; left: 255px; right: auto; bottom: auto; width: 600px; top:
127px">$<span xmldi="xmlDI" xmlnode="item_price"></span></div><input
type="text" id="quantity" name="quantity" value=""
onkeyup="restrict(this,\'[0-9]\',\'gi\')" class="rowData" style="position:
absolute; left: 255px; right: auto; bottom: auto; width: 600px; top:
144px; text-align: right"></div>';

function setEvents() {
  pageLoad();
}

function pageLoad(name,parm) {
  switch(true) {
    case(arguments.length == 0):
      soap.content = '<guild_item_id/><guild_id/>';
    case(name == 'Items'):
      if(arguments.length != 0)
        soap.content =
        '<guild_item_id/><guild_id>' + parm + '</guild_id>';

      soap.operator = 'getItems';
      xml.setEnvelope(soap.envelope());
      xml.setRequestHeader('SOAPAction','http://tempuri.org/getItems');
      xml.setRequestHeader('Content-Type','text/xml');
      xml.load('http://localhost/AJAX4/chapter4.asmx');
      window.setTimeout('pageWait()',10);

      pageName = 'Items';

      break;
    case(name == 'Detail'):
      soap.content =
      '<guild_item_id>' + parm + '</guild_item_id><guild_id/>';

      soap.operator = 'getItems';
      xml.setEnvelope(soap.envelope());
      xml.setRequestHeader('SOAPAction','http://tempuri.org/getItems');
      xml.setRequestHeader('Content-Type','text/xml');
      xml.load('http://localhost/AJAX4/chapter4.asmx');

      window.setTimeout('pageWait()',10);

      pageName = name;

      break;
    default:
      alert(name);
  }
}

function pageWait() {
  if(xml.readyState() == 4) {
    var xhtml = itemsXHTMLStart;
    var input =
document.getElementById('buttons').getElementsByTagName('input');

    if(_IE)

document.getElementById('xmlDI').XMLDocument.loadXML(xml.selectSingleNode(
'//NewDataSet').serialize());
    else
      document.getElementById('xmlDI').innerHTML =
xml.selectSingleNode('//NewDataSet').serialize();

    switch(pageName) {
      case('Items'):
        for(var i=0;i < xml.selectNodes('//Table').length;i++) {
          var reGuild = new RegExp('@guild','i');
          var reItem = new RegExp('@item','i');
          var guild =
xml.selectNodes('//guild_id')[i].serialize().replace(new
RegExp('<[^<]{0,}>','g'),'');
          var item =
xml.selectNodes('//guild_item_id')[i].serialize().replace(new
RegExp('<[^<]{0,}>','g'),'');

          xhtml +=
itemsInnerXHTML.replace(reGuild,guild).replace(reItem,item);
        }

        document.getElementById('formBody').innerHTML = xhtml +
itemsXHTMLEnd;

        break;
      case('Detail'):
        document.getElementById('formBody').innerHTML = detailXHTML;

        break;
    }

    window.setTimeout('_bind()',10);
  } else
    window.setTimeout('pageWait()',10);
}

function _bind() {
  if(arguments.length == 0) {
    doBind(document.body.getElementsByTagName('a'));
    doBind(document.body.getElementsByTagName('div'));
    doBind(document.body.getElementsByTagName('input'));
    doBind(document.body.getElementsByTagName('select'));
    doBind(document.body.getElementsByTagName('span'));
    doBind(document.body.getElementsByTagName('textarea'));
  } else {
    applyChange(arguments[0],arguments[1]);
    _bind();                                // Re-bind
  }

  /*
        Function:   doBind
        Programmer: Edmond Woychowsky
        Purpose:    To handle data-binds for specific nodes based
                    upon HTML element type and browser type.
  */
  function doBind(objects) {
    var strTag;                    // HTML tag
    var strDI;                     // XML data island id
    var strNode;                   // XML node name
    var strValue;                  // XML node value
    var index = new Object();      // Object to store information

    for(var i=0;i < objects.length;i++) {
      strTag = objects[i].tagName;
      strDI = objects[i].getAttribute('xmldi');
      strNode = objects[i].getAttribute('xmlnode');

      if(strDI != null && strNode != null) {
        if(typeof(index[strNode]) == 'undefined')
          index[strNode] = -1;
        ++index[strNode];

        if(_IE) {
            strValue =
document.getElementById(strDI).XMLDocument.selectNodes('//' +
strNode).item(index[strNode]).text;
        } else {
            strValue =
document.getElementById(strDI).getElementsByTagName(strNode)[index[strNode
]].innerHTML;
        }

        switch(strTag) {
            case('A'):
            case('DIV'):
            case('SPAN'):
                objects[i].innerHTML = strValue;

                break;
            case('INPUT'):
                switch(objects[i].type) {
                    case('text'):
                    case('hidden'):
                    case('password'):
                        objects[i].value = strValue;
                        objects[i].onchange = new Function("_bind(this," +
i.toString() + ")");
                        break;
                    case('checkbox'):
                        if(objects[i].value == strValue)
                            objects[i].checked = true;
                        else
                            objects[i].checked = false;

                        objects[i].onclick = new Function("_bind(this," +
i.toString() + ")");
                        break;
                    case('radio'):
                        if(_IE)
                            strValue =
document.getElementById(strDI).XMLDocument.selectNodes('//' +
strNode).item(0).text;
                        else
                            strValue =
document.getElementById(strDI).getElementsByTagName(strNode)[0].innerHTML;

                        if(objects[i].value == strValue)
                            objects[i].checked = true;
                        else
                            objects[i].checked = false;
                            objects[i].onclick = new
    Function("_bind(this,0)");

                            break;
                    }

                    break;
                case('SELECT'):
                case('TEXTAREA'):
                    objects[i].value = strValue;
                    objects[i].onchange = new Function("_bind(this," +
    i.toString() + ")");

                    break;
            }
          }
        }
      }
    }

    /*
      Function:  restrict
      Programmer:  Edmond Woychowsky
      Purpose:  Restrict keyboard input for the provided object
                using the passed regular expression and option.
    */
    function restrict(obj,rex,opt) {
      var re = new RegExp(rex,opt);
      var chr = obj.value.substr(obj.value.length - 1);

      if(!re.test(chr)) {
        var reChr = new RegExp(chr,opt);

        obj.value = obj.value.replace(reChr,'');
      }
    }

    /*
      Function:   add2Cart
      Programmer: Edmond Woychowsky
      Purpose:    To add an item/quantity pair to an XML Data
                  Island that represents a shopping cart.
    */
    function add2Cart() {
      var item =
    xml.selectSingleNode('//guild_item_id').serialize().replace(new
    RegExp('<[^<]{0,}>','g'),'');
      var quantity = document.getElementById('quantity').value;
      var re = new RegExp('<item><id>' + item +
    '</id><quantity>[^<]{1,}</quantity></item>','g');

      if(re.test(document.getElementById('cart').innerHTML))
    document.getElementById('cart').innerHTML =
document.getElementById('cart').innerHTML.replace(re,'');

  document.getElementById('cart').innerHTML += '<item><id>' + item +
'</id><quantity>' + quantity + '</quantity></item>';

  alert('Item added to cart.');
}
// ]]> >
    </script>
  </head>
  <body onload="setEvents()">
    <table border="0" height="60px" width="975px" cellpadding="0"
cellspacing="0" ID="Table1">
      <tr class="pageHeader" height="40px">
        <td width="5%"> </td>
        <th id="systemName" class="pageCell" width="45%" align="left">My
System</th>
        <th id="pageName" class="pageCell" width="45%" align="right">My
Page</th>
        <td width="5%"> </td>
      </tr>
      <tr>
        <td> </td>
        <td> </td>
        <td> </td>
        <td> </td>
      </tr>
    </table>
  <xml id="cart"></xml>
  <xml id="xmlDI"></xml>
    <div id="formBody" style="color: #000000; background-color: F0F8FF;
font-family: tahoma; font-size: 12px; border: solid 1px gray; height:
400px; width: 980px; overflow: scroll"></div>
    <p />
    <div id="buttons">
      <input id="show_all" type="button" value="Show All"
onclick="javascript:pageLoad()" style="height: 22px; width: 110px" />
      <input id="add_to_cart" type="button" value="Add to cart"
onclick="add2Cart()" style="height: 22px; width: 110px" />
      <input id="view_cart" type="button" value="View cart"
onclick="javascript:pageLoad('displayCart')" style="height: 22px; width:
110px" />
      <input id="place_order" type="button" value="Place order" onclick=""
style="height: 22px; width: 110px" />
    </div>
  </body>
</html>

Just as in the earlier HTML examples, Listing 8-19 has bound XML data islands and an asynchronous XMLHTTP request. The biggest differences are that the XML comes from a web service and that the request is made using SOAP. This means that although all the code that you see here is custom for this book, there is absolutely no reason why an Ajax front end cannot be written for existing web services. It's like General Patten said: "Never pay twice for the same real estate."

Please take note of the HTML DIV tag with the id attribute; there is something special about it. As you've probably deduced from the style attribute, both its height and its width are static. This is to keep the buttons along the bottom from moving around. In addition, it provides someplace to display the information returned from the server, without having to worry about the buttons. An alternative would be to put the buttons on the top of the page, but scrolling up to find the buttons would get old really quickly. With the underlying architecture around 90 percent complete, let's revisit the page that displays the items available for purchase on our site.

8.5.1. Read Only

Again, the purpose of the read-only page is to display our wares to visitors. On the surface, it is just rows and rows of items that are available for sale. Behind the scenes, however, is a different story. This is a web service delivering a SOAP response to a request for informationin this instance, the information relating to the items for sale.

Upon receiving the request, the web service obtains the necessary information from the database, which is the same MySQL database from the previous chapters. When it has the information, it programmatically builds the XHTML required to fill the scrollable div. Updates are not permitted on this page, so only the XHTML is being sent to the client. Hey, conserve bandwidth wherever you can.

Unfortunately, there is more to it than that. For instance, the page's onload event handler needs to send the SOAP request so that the previous method is invoked. In addition, buttons need to be activated or deactivated, clicks need to be handled, and, in short, there is more work to do.

Starting with the handler for the page onload event, we need to build a SOAP request, send the request to the web service, and activate the appropriate buttons. In addition, eventually the web service will get back to the page with its response, which will have to be dealt with. Sound like enough? Let's break it down into a little more detail.

1.
Create a global instance of XMLDocument().

2.
Build a SOAP request describing the URI of the web service, the method, the namespace, and the parameters being sent.

3.
Send the SOAP request using the XMLHttpRequest that is incorporated into the XMLDocument class.

4.
Wait for the SOAP response from the web service.

5.
Active the appropriate buttons.

6.
Populate the page.

Sound pretty easy? Well, it is easy, after the first time. The first time, however, it is kind of difficult to figure out what is what and what goes where. The first time that I did this, I stumbled a bit on steps 2 and 4. The problem that I had with step 2 was simply a matter of what goes where; a look at the code will explain everything. Dealing with step 4 is merely a matter of using window.setTimeout in JavaScript to repeatedly call a function after a suitable number of milliseconds to check the readyState of the XMLHttpRequest. If the readyState is 4, it is complete. Table 8-3 shows the possible readyState values and their meanings.

Table 8-3. readyState Values

readyState

Description

0

Uninitialized

1

Loading

2

Loaded

3

Interactive

4

Complete


Probably the hardest thing to get used to with Ajax is the ratio of client-side JavaScript to HTML. With traditional web development, the number of lines of HTML far exceeds the number of lines of JavaScript. With Ajax development, it is the other way around, with more JavaScript than HTML. Fortunately, with a halfway decent library of objects and functions, Ajax development doesn't usually need a lot of custom code. For example, Listing 8-20 shows the custom JavaScript for our page listing the items available, and Figure 8-7 shows what it looks like in the browser.

Figure 8-7. Items available


Listing 8-20. Items Available

soap.content =

'<guild_item_id>' + parm + '</guild_item_id><guild_id/>';

soap.operator = 'getItems';
xml.setEnvelope(soap.envelope());
xml.setRequestHeader('SOAPAction','http://tempuri.org/getItems');
xml.setRequestHeader('Content-Type','text/xml');
xml.load('http://localhost/AJAX4/chapter4.asmx');

window.setTimeout('pageWait()',10);

pageName = name;

function pageWait() {
  if(xml.readyState() == 4) {
    var xhtml = itemsXHTMLStart;
    var input =
document.getElementById('buttons').getElementsByTagName('input');

    if(_IE)
document.getElementById('xmlDI').XMLDocument.loadXML(xml.selectSingleNode(
'//NewDataSet').serialize());
    else
      document.getElementById('xmlDI').innerHTML =
xml.selectSingleNode('//NewDataSet').serialize();

    switch(pageName) {
      case('Items'):
        for(var i=0;i < xml.selectNodes('//Table').length;i++) {
          var reGuild = new RegExp('@guild','i');
          var reItem = new RegExp('@item','i');
          var guild =
xml.selectNodes('//guild_id')[i].serialize().replace(new
RegExp('<[^<]{0,}>','g'),'');
          var item =
xml.selectNodes('//guild_item_id')[i].serialize().replace(new
RegExp('<[^<]{0,}>','g'),'');

          xhtml +=
itemsInnerXHTML.replace(reGuild,guild).replace(reItem,item);
        }

        document.getElementById('formBody').innerHTML = xhtml +
itemsXHTMLEnd;

        break;
      case('Detail'):
        document.getElementById('formBody').innerHTML =
        detailXHTML;

        break;
    }

    window.setTimeout('_bind()',10);
  } else
    window.setTimeout('pageWait()',10);
}

The pageWait() function shown here might seem somewhat formidable, but its sole purpose is to dynamically build the HTML necessary for the bound table in the page. This is a somewhat slick trick, but really nothing that hasn't been done for the last five years, although usually for different reasons.

8.5.2. Updateable

Because we've worked out the underlying architecture, an updateable page is merely a variant of the read-only page shown in the previous example. There are essentially two differences, the first being that, instead of using SPAN or DIV tags, the bound tags are things such as INPUT and SELECT. The second difference is that eventually it will be necessary to send an entire XML data island to the server. The interesting thing about this is that it doesn't have to be the XML Data Island that is bound to the HTML, although it could be.

Remember the shopping cart from earlier in the book? Well, instead of using the funky item id-dash-quantity in a text box, now the shopping is itself an XML Data Island. Unfortunately, this means that I can't be lazy and recycle the function from Chapter 5. Alas, it was necessary to write the function shown in Listing 8-21. It's not anything fancy; in fact, it treats the XML as text. Not only is that a valid option, but it also works in a cross-browser environment.

Listing 8-21. Add to Shopping Cart Function

/*
   To add an item/quantity pair to an XML Data Island that
   represents a shopping cart.
*/
function add2Cart() {
  var item =
xml.selectSingleNode('//guild_item_id').serialize().replace(new
RegExp('<[^<]{0,}>','g'),'');
  var quantity = document.getElementById('quantity').value;
  var re =
new RegExp('<item><id>' + item +
'</id><quantity>[^<]{1,}</quantity></item>','g');

  if(re.test(document.getElementById('cart').innerHTML))
    document.getElementById('cart').innerHTML =
document.getElementById('cart').innerHTML.replace(re,'');

  document.getElementById('cart').innerHTML += '<item><id>' + item +
'</id><quantity>' + quantity + '</quantity></item>';

  alert('Item added to cart.');
}

The end result of this is the page that was shown in Listing 8-21 and Figures 8-7 and 8-8. It works roughly the same as the pageWait() function from Listing 8-20. The difference is that, instead of adding elements to the HTML document based upon an XML document, elements are added to the embedded XML document based upon the actions of the visitor. The page shown in Figure 8-7 lists the items available for purchase, and Figure 8-8 handles the add to the shopping cart.

Figure 8-8. Item added to the shopping cart



Previous Page
Next Page




JavaScript Editor Free JavaScript Editor     JavaScript Debugger


©