Putting It All Together


Now that you've seen the three constituent parts of Ajax, it's time to connect them to add some simple Ajax functionality to a Web page. To explore how JavaScript, XMLHttpRequest and the DOM interact, I'll create an application to look up contact phone numbers (see Figure 14-4). The solution uses a simple HTML page as a client (with JavaScript) and an ASP.NET HTTP handler as the server. It should be possible to convert this server-side component to any other server-side Web technology.

image from book
Figure 14-4

The server-side of the solution queries an XML file to retrieve any company or contact that matches the currently entered data, returning up to 10 items that match. Part of the XML file queried is shown in Listing 14-8.

Listing 14-8: Contact information file

image from book
      <customers>        <customer >          <company>Alfreds Futterkiste</company>          <contact>Maria Anders</contact>          <phone>030-0074321</phone>        </customer>        <customer >          <company>Ana Trujillo Emparedados y helados</company>          <contact>Ana Trujillo</contact>           <phone>(5) 555-4729</phone>        </customer>        <customer >          <company>Antonio Moreno Taquería</company>          <contact>Antonio Moreno</contact>          <phone>(5) 555-3932</phone>        </customer>        <customer >          <company>Around the Horn</company>          <contact>Thomas Hardy</contact>          <phone>(171) 555-7788</phone>        </customer>        <customer >          <company>Berglunds snabbköp</company>          <contact>Christina Berglund</contact>          <phone>0921-12 34 65</phone>        </customer>        <customer >          <company>Blauer See Delikatessen</company>          <contact>Hanna Moos</contact>          <phone>0621-08460</phone>        </customer>      ...      </customers> 
image from book

In order to retrieve both companies and contacts that match the query, the XPath combines the two queries, using the XPath function starts-with to identify desired nodes. As starts-with is case-sensitive, it is also necessary to use the translate function to perform the search in a case-insensitive way. The full XPath query for any company or customer that starts with fr is:

      customers/customer[starts-with(translate(company,              'ABCDEFGHIJKLMNOPQRSTUVWXYZ',              or starts-with(translate(contact, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',              'abcdefghijklmnopqrstuvwxyz'), 

The translate function converts a string to another string based on a mapping. The syntax looks like the following:

      translate(string-to-convert, mapping-string, result-string) 

Whenever the code encounters one of the characters in the mapping-string in the string-to-convert, it is replaced with the character at the same position in the result-string. Therefore, using the code shown previously, any alphabetic character is replaced with the lowercase equivalent. Note that this only includes basic letters. Any accented characters should be added to both the second and third parameters in the call to translate. When you do this, make certain that the lowercase character in the third parameter is in the same position as the character in the second parameter. Two starts-with clauses are needed because one searches for items matching the company name, whereas the second includes items matching on the contact name. Listing 14-9 shows the output of the preceding query when applied to the contact list. Notice how the query (fr) matches the start of both companies and contacts.

Listing 14-9: Selected contacts

image from book
      <customers>        <customer >          <company>Blondesddsl père et fils</company>          <contact>Frédérique Citeaux</contact>          <phone>88.60.15.31</phone>        </customer>        <customer >          <company>Centro comercial Moctezuma</company>          <contact>Francisco Chang</contact>          <phone>(5) 555-3392</phone>        </customer>        <customer >          <company>Frankenversand</company>          <contact>Peter Franken</contact>          <phone>089-0877310</phone>        </customer>        <customer >          <company>France restauration</company>          <contact>Carine Schmitt</contact>          <phone>40.32.21.21</phone>        </customer>        <customer >          <company>Franchi S.p.A.</company>          <contact>Paolo Accorti</contact>          <phone>011-4988260</phone>        </customer>        <customer >          <company>Lonesome Pine Restaurant</company>          <contact>Fran Wilson</contact>          <phone>(503) 555-9573</phone>        </customer>      </customers> 
image from book

Listing 14-10 shows the server-side code that performs the query on the XML and returns the resulting XML. Although it is written for ASP.NET, it could be written in PHP, JSP, ColdFusion, or other server-side code.

Listing 14-10: A server-side query

image from book
      <%@ WebHandler Language="C#"  %>      using System;      using System.Web;      using System.Web.Caching;      using System.Globalization;      using System.Xml;      using System.Xml.XPath;      using System.Text;       using System.IO;      public class CustomerLookup : IHttpHandler {          private const String dataFile = "app_data/contactList.xml";          private const String cacheKey = "contactList";          private const Int32 maxResults = 10;          private HttpContext myContext = null;          private String xQuery = @"customers/customer[starts-with(translate(company,              'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '{0}')              or starts-with(translate(contact, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',              'abcdefghijklmnopqrstuvwxyz'),          public void ProcessRequest (HttpContext context) {              myContext = context;              String result = String.Empty;              String query = context.Request["q"];              if (!String.IsNullOrEmpty(query)) {                  result = GetContactList(query, maxResults);              }              context.Response.ContentType = "text/xml";              context.Response.Write(result);          }          public bool IsReusable {              get {                  return false;              }          }         private String GetContactList(String root, Int32 count) {              StringBuilder result = new StringBuilder();              String filename = myContext.Server.MapPath(dataFile);              String data = LoadAndCache(filename);              String query = String.Empty;              XmlDocument doc = new XmlDocument();              XPathNavigator nav = null;              int i = 0;              doc.LoadXml(data);              query = String.Format(xQuery, root);              nav = doc.CreateNavigator();              XPathNodeIterator iter = nav.Select(query);              XmlWriterSettings settings = new XmlWriterSettings();              settings.Encoding = Encoding.UTF8;              settings.OmitXmlDeclaration = true;              using (XmlWriter w = XmlWriter.Create(result, settings)) {                   w.WriteStartDocument();                  w.WriteStartElement("result");                  while (iter.MoveNext()) {                     w.WriteNode(iter.Current,false);                     i++;                     if (i == count) {                         break;                     }                  }                  w.WriteEndElement();                  w.WriteEndDocument();              }              return result.ToString();          }          private String LoadAndCache(String filename) {              String result = String.Empty;              result = myContext.Cache[cacheKey] as String;              if (String.IsNullOrEmpty(result)) {                  using (StreamReader reader = File.OpenText(filename)) {                     result = reader.ReadToEnd();                  }                  myContext.Cache.Add(cacheKey, result,                     new CacheDependency(filename),                     Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration,                     CacheItemPriority.Normal, null);              }              return result;          }      } 
image from book

The bulk of the code for an ASP.NET HTTP handler is in the ProcessRequest method. This is called for each request, and the developer is responsible for writing the output. This output is generated by the GetContactList method. The GetContactList method first loads the XML file (and caches the data for performance); then it executes the query using the current data typed by the user. The resulting data is wrapped in a <result> element (see Figure 14-5).

image from book
Figure 14-5

The interface for the client application is intentionally simple and would likely be part of a more complex page. The idea is that the user can type in the search field, and the server-side code is called by JavaScript on that page. Listing 14-11 shows the code for the client page and JavaScript.

Listing 14-11: Client-side code

image from book
            <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">      <html xmlns="http://www.w3.org/1999/xhtml" >      <head>          <title>Ajax Contact Lookup</title>          <script type="text/javascript" src="/books/2/381/1/html/2/script/getReqObj.js"></script>      <script language="javascript" type="text/javascript">       <!--          var req;          function getContacts() {              var q = document.getElementById("q").value;              if("" != q) {                  req = getReqObj();                  if(null != req) {                     req.open("GET", "customerlookup.ashx?q=" + q, true);                     req.onreadystatechange = Process;                     document.getElementById("contactList").innerHTML = "Working";                     req.send(null);                  }              } else {                  document.getElementById("contactList").innerHTML = "";              }          }          function Process() {              if(4 == req.readyState) {                  if(200 == req.status) {                     //success                     var data = req.responseXML;                     var result = "";                     var node = data.documentElement.firstChild;                     while(null != node) {                         var child = node.firstChild;                         result += "" + child.firstChild.nodeValue + "&nbsp;";                         child = child.nextSibling;                         result += "(" + child.firstChild.nodeValue + "):&nbsp;";                         child = child.nextSibling;                         result += child.firstChild.nodeValue;                         result += "<br />";                         node = node.nextSibling;                     }                     document.getElementById("contactList").innerHTML = result;                  } else {                     document.getElementById("contactList").innerHTML = req.statusText;                  }              }          }      // -->      </script>       </head>      <body>      <form>          Enter the first few letters of a company or contact:          <input  type="text" onkeyup="getContacts();"/><br />          <div ></div>      </form>      </body>      </html> 
image from book

image from book
The other data format: JSON

When processing the returned XML from an XMLHttpRequest call, you are essentially left to your own devices and the DOMDocument. Although this is not really a handicap (only a bit of extra friction and a few lines of code), you should know that many developers avoid XML entirely. Instead, these developers use JSON (JavaScript Object Notation, pronounced Jason) as a data-exchange format. JSON is a serialized form of JavaScript objects. For example, the data returned from a server-side component similar to the CustomerLookup component, but returned using JSON instead of XML looks like the following:

      { "customers": { "customer": [          {"id": "DRACD",            "company": "Drachenblut Delikatessen",            "contact": "Sven Ottlieb",            "phone": "0241-039123"},          {"id": "DUMON",            "company": "Du monde entier",            "contact": "Janine Labrune",            "phone": "40.67.88.88"},          {"id": "FISSA",            "company": "FISSA Fabrica Inter. Salchichas S.A.",            "contact": "Diego Roel",            "phone": "(91) 555 94 44"},          {"id": "LACOR",            "company": "La corne d'abondance",            "contact": "Daniel Tonini",            "phone": "30.59.84.10"},          {"id": "SPECD",            "company": "Spécialités du monde",            "contact": "Dominique Perrier",            "phone": "(1) 47.55.60.10"},          {"id": "WANDK",            "company": "Die Wandernde Kuh",            "contact": "Rita Müller",            "phone": "0711-020361"}          ]      }}; 

image from book

The JavaScript file containing the cross-browser code for retrieving the XMLHttpRequest object is included, and this method is used to create the object. The first step is to identify the URL and HTTP method that will be used for the page. Notice that the true parameter is included to ensure the request is made asynchronously. If this parameter is not included, the request could cause the page to stop responding to the user while the query is being completed. Next, because this call is done asynchronously, you must set the onreadystatechange to point to the method that responds when the state of the request changes. Finally, the send method sends the request to the desired URL.

Each time the state of the request changes, the method pointed to by the onreadystatechange property is called. Therefore, you must determine if you are currently at a state where data has been retrieved (reqObj.readyState == 4) before you continue. Similarly, you should examine the status property to ensure that the request succeeded (reqObj.status == 200). At this point, you can use the various response properties (responseXML, responseText, and responseBody in Internet Explorer) to retrieve the data for further processing. Using the responseXML provides you with a DOMDocument object that can be used to process the data, using the familiar firstChild, nextSibling, and nodeValue properties. In addition, if cross-browser support is not required, you use the methods of the DOMDocument specific to your implementation (for example, the childNodes collection). The resulting data is pushed into the contactList div element (see Figure 14-6). Notice that the list of items changes as you type in the text box-without the flicker that might occur if a page round-trip were required to update the list.

image from book
Figure 14-6

As you can see, the syntax is basically name:value pairs. However, the braces ({}) create an object or hash, whereas the brackets ([]) create an array. In the preceding example, an object is created that consists of an array of customer hashtables. Each hashtable is populated with the properties of each customer.

The benefit of JSON data is that you can use the JavaScript eval method to convert it back into JavaScript hashtables and arrays. This converts the code required for dumping the returned data into the target div to the following:

      function getContacts() {          var result = "";          for(var i = 0; i < jsonObj.customer.length; i++) {              result += "" + jsonObj.customer[i].company + "&nbsp;";              result += "(" + jsonObj.customer[i].contact + "):&nbsp;";              result += jsonObj.customer[i].phone + "<br />";          }          document.getElementById("contactList").innerHTML = result;      } 

Now that you know that JSON exists and what it looks like, we can go back to ignoring it. This is a book on XML, after all.




Professional XML
Professional XML (Programmer to Programmer)
ISBN: 0471777773
EAN: 2147483647
Year: 2004
Pages: 215

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net