Previous Page
Next Page

11.4. Implementing the AJAX Comment Login System Using XML

The HTML_AJAX-powered implementation of an AJAX login form was easy to implement, but it is tightly tied to the HTML_AJAX library. You could implement it in a similar fashion with another library that has the same features, but you need to take a different approach if you're using a lighter weight library. Because the HTML_AJAX approach generates JavaScript on the fly, it can be especially hard to do if your language of choice doesn't have JSON support. XML is often a good alternative if you lack JSON support, because it's easier to create by hand and more mature; therefore, it has wider language support. Any of the communications patterns that were covered at the beginning of this book are a possibility, but you'll find the ones that use a more standard page-based approach will be easiest to integrate into existing sites. RPC-based approaches can also be useful, but they introduce a different style of interaction into your Web-based applications. Thus, you'll need to think about them from the start, if you're planning on using them.

To give a better idea of what an alternative implementation would look like, I've implemented the same comment updating system using Sarissa and XML. The back end is still written in PHP, but instead of generating HTML and JavaScript, it will now generate XML code that is used by client-side JavaScript. This will increase the amount of JavaScript you need to write, but it may well be worth it, especially if you're already using XML throughout your site.

In this example, we start with the back end script XMLCommentLogin.php, because the JavaScript code doesn't make sense until you see the XML with which it is working. All the XML messages are contained within the root node of login. When a login fails, an XML message like the one shown here can be returned:

      <message>Login Failed</message>

If a login was attempted, a message should always be provided. An alternate version of this response with no message nodes can be received on the initial page load. This alternate response happens when the login status is read from the session. The other case is, of course, the XML that is shown when a user is logged in. This response contains profile data; using XML for data like this is nice because it makes it easy to extend for future purposes. An example of the XML returned by a successful login is shown here:

                 <value name="name">Joshua Eichorn</value>
                 <value name="email"></value>
                 <value name="url">

In a successful case, a result message and profile data are returned. The value tags within the profile tag store the actual profile information. In this example, the name attribute matches the ID of the field we want to update, but in most cases, this mapping wouldn't work and you would have to add code to match the XML nodes and the field IDs. In the initial page load case, the same response as a successful login is used. The code that generates these messages is shown in Listing 11-5.

Listing 11-5. XMLCommentLogin.php

1  <?php
2  if (!session_id()) {
3      session_start();
4  }
6  function profileXML($profile) {
7      $xml = "<profile>";
8      foreach($profile as $key => $val) {
9          $xml .= "<value name='$key'>$val</value>";
10      }
11      $xml .= "</profile>";
12      return $xml;
13  }

15  if (isset($_GET['ajaxLogin'])) {
16      $xml = "<login>";
17    // hard-coded login, use the application's
18    // normal login code here
19    if ($_GET['username'] === 'jeichorn' &&
20      $_GET['password'] === 'test') {
22          $_SESSION['profile'] = array(
23          'name' => 'Joshua Eichorn',
24          'email'=> '',
25          'url'  => ''
26          );
27          $xml .= "<result>success</result>";
28          $xml .= profileXML($_SESSION['profile']);
30      $_SESSION['xlogin'] = true;
31    }
32    else {
33          $xml .= "<result>fail</result>";
34          $xml .= "<message>Login Failed</message>";
35      $_SESSION['xlogin'] = false;
36    }
37      $xml .= "</login>";
38  }
39  else if (isset($_SESSION['xlogin'])
40    && $_SESSION['xlogin'] == true) {
41      $xml = '<login><result>true</result>'.
42          profileXML($_SESSION['profile'])
43          .'</login>';
44  }
45  else {
46      $xml = "<login><result>fail</result></login>";
47  }
49  if (!isset($inline)) {
50      header("Content-type: text/xml");
51  }
52  echo $xml;
53  ?>

Like the other login pages, Listing 11-5 is included directly in the main content page to produce the loading of default values. Unlike the other pages, it needs a specific flag set to know this is happening. It needs this flag because it sends a content-type header on line 50, and we want to do that only in the stand-alone case. The page starts by initializing the PHP session; like all the login examples, the actual status of the login is stored on the server. Just as in normal Web development, storing the login status on the client is a big security problem. Next (lines 613), we set up a helper function, profileXML, which loops over profile data and generates XML for it. It's in a helper function because we need to be able to access it in two places: once for a successful login (line 28), and once for the already logged-in case (line 42).

Lines 1547 handle the actual login processing. We're using GET requests in this case because they are easier to accomplish with Sarissa, but this could cause some future problems. GET requests can be cached, unlike POST requests, which could make it hard to use this same code as a method to reload profile data. On line 15, we check whether we have a login attempt; if so, we start building our output xml string (line 16) and then do a login check (lines 1920). If the login succeeds, we set the session flag (line 30) and load the user's profile into the session (lines 2226). The successful login process is completed by outputting the needed XML. The successful login code first outputs a result tag (line 27) and then the profile data (line 28) by calling the profileXML helper function.

If the login fails, we output a result tag with a value of fail (line 33) and add a message tag whose value will be displayed on the login form (line 34). The failed login process is finished by setting the SESSION flag to false (line 35). Lines 3947 contain the two default loading cases. If the user is logged in, we generate the profile XML (lines 4143); if the user isn't logged in, we output the failure XML with no messages (line 46). The file finishes by outputting an XML Content-type header (line 50), which is needed because PHP generates HTML files by default, and then we display the xml (line 52). This example is used by a comment page, shown in Listing 11-6, which has been updated to use the Sarissa library to consume the XML.

Listing 11-6. XMLComment.php

1  <?php
2  session_start();
3  $inline = true;
4  ?>
5  <html>
6  <head>
7  <title>A sample Comment Page</title>
8  <link rel="stylesheet" type="text/css"
9    href="Comment.css" />
10  <script type="text/javascript"
11      src="sarissa/sarissa.js"></script>
12  <script type="text/javascript"
13      src="sarissa/sarissa_ieemu_xpath.js"></script>
14  <script type="text/javascript">
15  var loginData =
16     "<?php include 'XMLCommentLogin.php'; ?>";
18  function processForm(form) {
19      var remote = Sarissa.getDomDocument();
20      remote.onreadystatechange = function() {
21          if(remote.readyState == 4) {
22              var result =
23              remote.selectSingleNode('//result');
25          if (result.firstChild.nodeValue
26              == 'success') {
27              loadProfile(remote);
28              }
29              else {
30                  var message =
31                  remote.selectSingleNode('//message');
32                  var el =
33                  document.getElementById('message');
34                  el.innerHTML =
35                  message.firstChild.nodeValue;
36            }
37          }
38       }
40       var url = "XMLCommentLogin.php?ajaxLogin=1";
41       url += "&username="+
42           escape(form.elements.username.value);
43       url += "&password="+
44           escape(form.elements.password.value);
46      remote.load(url);
48      return false;
49  }
51  function loadInline() {
52      var parser = new DOMParser();
53      var loginXML = parser.parseFromString(
54          loginData, "text/xml");
55      loadProfile(loginXML);
56  }
58  function loadProfile(doc) {
59      var nodes = doc.selectNodes('//profile/value');
60      if (nodes.length > 0) {
61          document.getElementById('loginForm').style.display = 'none';
62      }
63      for(var i = 0; i < nodes.length; i++) {

64          var name = nodes[i].getAttribute('name');
65          document.getElementById(name).value =
66              nodes[i].firstChild.nodeValue;
67      }
68  }
70  </script>

XMLComment.php provides the same comment form as the Comment.php example. Because it's using the Sarissa library, the code quickly diverges because it defines several JavaScript functions to handle the XML. The page starts with some basic PHP setup, starting the session (line 2) and setting $inline to TRue so that the XML-generating page knows not to send Content-type headers. The page then includes the same CSS (lines 89) and the Sarissa library (lines 1011), including its XPath support (lines 1213). Then, we move into the JavaScript. The first item we define is the loginData variable, which will contain the XML generated by XMLCommentLogin.php. This variable allows us to load the profile data at page load if the user is already logged in.

After that, we define a function that will handle submitting the login form (lines 1849). It starts by getting a Sarissa DOM Document (line 19); it then sets up its onreadystatechange handler (lines 2038). When the page is loaded (readyState == 4), this handler will use an XPath query to grab the result node (lines 2223) and then check to see whether its value is successful (lines 2526). If the value is success, the profile information will be loaded using the loadProfile function. If the value isn't success, an XPath query will be used to load the message node (lines 3031), and then the content of this message will be added to the paragraph element with an ID of message (lines 3235).

Once the handler is set up, the URL to be used in the request is created (lines 4044). Because we're making a GET request, the data is appended to the query string. The values are read from the form using the elements.elementName syntax. Each value read from the form is escaped; this escaping makes sure that we won't get a broken URL. Once we have a URL, we use the DOM document's load method to make the request (line 46) and then return false (line 48) so that the form won't do a normal submission. Lines 5156 define the loadInline function; this function is called at page load, and it uses a DOMParser to load the XML from loginData and then calls loadProfile on it.

The JavaScript code finishes up by defining the loadProfile function; this function takes a login result DOM document and loads the profile data into the comment form. It does this by making an XPath query that loads all the value nodes into an array (line 59). If that query succeeds, it first hides the login form (line 61) and then loops over the array (lines 6367), grabbing the name of the value from its name attribute, grabbing the HTML element with an ID that matches that name, and then setting that element's value to the value of the tag. If the names do not match, you can loop over the nodes, creating a hash with the name as the key, and then write out each update by hand.

Listing 11-7. XMLComment.php Continued

71  </head>
72  <body onload="loadInline()">
73  <h3>Leave a reply</h3>
74  <form action="Comment.php" method="post">
76  <div id="inputFields">
77  <p><input name="author" id="name"
78    size="22" type="text">
79  <label for="author"><small>Name
80    (required)</small></label></p>
82  <p><input name="email" id="email"
83    size="22" type="text">
84  <label for="email"><small>Mail (will not be
85    published) (required)</small></label></p>
87  <p><input name="url" id="url"
88    size="22" type="text">
89  <label for="url"><small>Web site
90    </small></label></p>
91  </div>
92  <br style="clear:both" />
94  <p><textarea name="comment" id="comment"
95    cols="100" rows="10"></textarea></p>
97  <p><input name="submit" id="submit"
98    value="Submit Comment" type="submit">
99  </p>
101  </form>
102  <div id="loginForm">
103  <form onsubmit="return processForm(this)">
104  <h4>Login</h4>
105  <p id="message"></p>
106  <p>
107  <input name="username">
108  <label><small>Username</label>

109  </p>
111  <p>
112  <input name="password" type="password">
113  <label>Password</label>
114  </p>
116  <input type="hidden" name="ajaxLogin" value="1">
117  <p><input type="submit" value="Login"></p>
118  </form>
119  </div>
120  </body>
121  </html>

The rest of the page builds the user interface. It has a few changes from Comment.php. The big one is that the login form is defined directly on this page instead of being included. On line 72, we set an onload handler. This handler called loadInline loads any current profile data. After that, we have just the standard comment form until line 102, where we define the login form. The only difference in this form is that it is now calling the processForm function in its onsubmit handler. This event handler will start the AJAX submit process when the user clicks the Submit button. The data flow of the user filling out a comment, logging in, and then submitting his or her comment is shown in Figure 11-6; this data flow is identical in both the HTML_AJAX and Sarissa XML cases.

Figure 11-6. Workflow of the AJAX login process using Sarissa

Previous Page
Next Page