Ajax Applications

In this lesson of the Ajax tutorial, you will learn...
  1. To apply the concepts covered thus far to practical applications.

Login Form

Have you ever forgotten your username or password for a website? Have you waited 10 seconds for a full page to redraw while the only thing new is a message telling you that the username and password are not recognized. Ajax can make that process much less painful for the user.

Imagine a simple login form like the one shown below, but on a page with a lot of other content:

When the user fills the form out with a bad password, an error message appears and the username is highlighted:

The rest of the page should stay the same. Ajax makes this possible without refreshing the entire page. The code for this is shown below:

Code Sample: AjaxApplications/Demos/Login.html

<html>
<head>
<title>Login Form</title>
<link href="Login.css" type="text/css" rel="stylesheet"/>
<script type="text/javascript" src="../../prototype.js"></script>
<script type="text/javascript">
 function Login(FORM)
 {
  var un = FORM.Username.value;
  var pw = FORM.Password.value;
  new Ajax.Request("Login.jsp", 
   {
    method: "get",
    parameters: "username=" + un + "&password=" + pw,
    onComplete: LoginResults
   });
 }
 
 function LoginResults(REQ) //Callback Function
 {
  if (REQ.responseText.indexOf("failed") == -1)
  {
   document.getElementById("LoggedIn").innerHTML = "Logged in as " + REQ.responseText;
   document.getElementById("LoggedIn").style.display = "block";
   document.getElementById("LoginForm").style.display = "none";
  }
  else
  {
   document.getElementById("BadLogin").style.display="block";
   document.getElementById("LoginForm").Username.select();
   document.getElementById("LoginForm").Username.className="Highlighted";
   setTimeout("document.getElementById('BadLogin').style.display='none'",3000);
  }
 }
</script>
</head>
<body>

<form id="LoginForm" onsubmit="Login(this); return false">
 <h1>Login Form</h1>
 <div class="FormRow">
  <label for="Username">Username:</label>
  <input type="text" size="15" name="Username"/>
 </div>
 <div class="FormRow">
  <label for="Password">Password:</label>
  <input type="password" size="15" name="Password"/>
 </div>
 <div class="FormRow" id="LoginButtonDiv">
  <input type="submit" value="Login"/>
 </div>
 <div id="BadLogin">
  The login information you entered does not match an account in our records.  
  Please try again.
 </div>
</form>

<h1 id="LoggedIn"></h1>

</body>
</html>

Quick Lookup Form

In some cases, you need to get quick information from a database, but you don't want to process an entire page. For example, you may have a form for requesting information about an order. The form might have multiple fields, one of which is the order id. The order id is not required, but if it's filled in it must match an existing order id in the database. Rather than waiting to check the order id until the user fills out and submits the entire form, you can use Ajax to flag a bad order id as soon as the user tabs out of that field. A simple sample interface is shown below:

When the user enters an order id that is not in the database, an error is displayed and the submit button becomes disabled:

When the user enters a valid order id, an icon is displayed indicating that the order id exists and the submit button becomes enabled:

The code is shown below.

Code Sample: AjaxApplications/Demos/Lookup.html

<html>
<head>
<title>Order Lookup Form</title>
<link href="Lookup.css" type="text/css" rel="stylesheet"/>
<script type="text/javascript" src="../../prototype.js"></script>
<script type="text/javascript">
 function Lookup(ORDER)
 {
  var orderNum = ORDER.value;
  if (orderNum.length==0) //OK to submit
  {
   document.getElementById("OrderNumError").innerHTML="";
   document.getElementById("SubmitButton").disabled=false;
   return true;
  }
  if (isNaN(orderNum)) //Error
  {
   document.getElementById("OrderNumError").innerHTML="Must be numeric.";
   ORDER.style.color="red";
   document.getElementById("SubmitButton").disabled=true;
   return true;
  }
  
  //Look up order number in database
  new Ajax.Request("Lookup.jsp", 
   {
    method: "get",
    parameters: "orderNum=" + orderNum,
    onComplete: LookupResults
   });
 }
 
 function LookupResults(REQ) //Callback function
 {
  if (REQ.responseText.indexOf("success") == -1) //Error: no match
  {
   document.getElementById("OrderNumError").innerHTML="No such order number.";
   document.getElementById("OrderNum").style.color="red";
   document.getElementById("SubmitButton").disabled=true;
  }
  else //OK to submit
  {
   document.getElementById("OrderNumError").innerHTML="<img src='check.gif'/>";
   document.getElementById("OrderNum").style.color="green";
   document.getElementById("SubmitButton").disabled=false;
  }
 }
</script>
</head>
<body>

<form id="LookupForm" onsubmit="alert('Form would submit.'); return false;">
 <h1>Lookup Form</h1>
 <p>Enter an order number.</p>
 <div class="FormRow">
  <label for="Year">Order Number:</label>
  <input type="text" size="10" id="OrderNum" name="OrderNum" onblur="Lookup(this)"/>
  <span id="OrderNumError"></span>
 </div>
 <div class="FormRow">
  <label for="Year">Comments:</label><br />
  <textarea name="Comments" cols="40" rows="4"></textarea>
 </div>
 <div class="FormRow">
  <input type="submit" id="SubmitButton" value="Submit">
 </div>
</form>

</body>
</html>

Code Sample: AjaxApplications/Demos/Lookup.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" import="java.sql.*"%>
<%
 Connection conn = null;
 PreparedStatement stmt = null;
 ResultSet rs = null;
 try
 {
  Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
  conn = DriverManager.getConnection("jdbc:odbc:Northwind");
  
  String sql = "SELECT OrderID From Orders WHERE OrderID=?";
  stmt = conn.prepareStatement(sql);
  stmt.setString(1, request.getParameter("orderNum"));
  
  rs = stmt.executeQuery();
  if (rs.next())
  {
   out.write("success");
  }
  else
  {
   out.write("failed");
  }
 }
 catch(Exception e)
 {
  out.write("failed: " + e.toString());
 }
 finally 
 {
  if (rs != null) rs.close();
  if (stmt != null) stmt.close();
  if (conn != null) conn.close();
 }
%>
Code Explanation

The server-side script simply return "success" if the order number is found or "failed" if it is not.

Preloaded Data

Google Maps (http://maps.google.com) was one of the applications that brought so much attention to Ajax. One of the cool things about it is that it allows the user to drag maps around the screen seamlessly loading new sections. It does this by preloading the sections around the map that the user is likely to drag on to the screen. This same concept can be applied to other applications, such as slideshows and navigable tables.

Ajax Slideshow

Let's first take a look at the slideshow shown below:

When the user clicks on the Previous or Next buttons, the page makes an XMLHttpRequest to the server, which returns XML as shown below:

The callback function creates the next slide from this XML. The code is shown below.

Code Sample: AjaxApplications/Demos/SlideShow.html

<html>
<head>
<title>Slide Show</title>
<link href="SlideShow.css" type="text/css" rel="stylesheet"/>
<script type="text/javascript" src="../../prototype.js"></script>
<script type="text/javascript">

var CurSlide=1; //Keeps track of current slide
var NumSlides = 10;

function init()
{
 document.getElementById("PrevButton").onclick=PrevSlide;
 document.getElementById("NextButton").onclick=NextSlide;
 document.getElementById("TotalSlideNum").innerHTML = NumSlides;
 GetSlide();
}

function PrevSlide()
{
 if (CurSlide > 1)
 {
  CurSlide--;
  GetSlide();
 }
 else
 {
  document.getElementById("SlideMessage").innerHTML = "Already showing first slide.";
  setTimeout("document.getElementById('SlideMessage').innerHTML=''",2000);
 }
}

function NextSlide()
{
 if (CurSlide < NumSlides)
 {
  CurSlide++;
  GetSlide();
 }
 else
 {
  document.getElementById("SlideMessage").innerHTML = "Already showing last slide.";
  setTimeout("document.getElementById('SlideMessage').innerHTML=''",2000);
 }
}

function GetSlide()
{
 document.getElementById("CurSlideNum").innerHTML=CurSlide;
 
 new Ajax.Request("SlideShow.jsp", 
  {
   method: "get",
   parameters: "Slide=" + CurSlide,
   onComplete: ChangeSlide
  });
}

function ChangeSlide(REQ) //Callback function creates slide
{
 var docElem = REQ.responseXML.documentElement;
 Element.cleanWhitespace(docElem);
 document.getElementById("SlideText").innerHTML = docElem.firstChild.firstChild.nodeValue + "<br/>";
 document.getElementById("SlideText").innerHTML += docElem.childNodes[1].firstChild.nodeValue;
 document.getElementById("SlideImage").src = "Slides/" + docElem.childNodes[2].firstChild.nodeValue;
}

window.onload = init;
</script>
</head>

<body>
<h1>First 10 Presidents</h1>
<div id="Slide">
 <img id="SlideImage"/>
 <div id="SlideText"></div>
 <form action="SlideShow.html" onsubmit="return false" id="SlideForm">
  <input type="button" value="Previous" id="PrevButton"/>
  Slide <span id="CurSlideNum">1</span> of <span id="TotalSlideNum"></span>
  <input type="button" value="Next" id="NextButton"/>
 </form>
 <div id="SlideMessage"></div>
</div>
</body>
</html>

Although this is pretty cool in and of itself, it can be made cooler by preloading the preceding and following images, so the user experiences no delay when navigating from slide to slide. In this case, the server-side script needs to return more data. Our script is shown below.

Code Sample: AjaxApplications/Demos/SlideShow-preloaded.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" import="java.sql.*"%>
<%
 Connection conn = null;
 PreparedStatement stmt = null;
 ResultSet rs = null;
 try
 {
  Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
  conn = DriverManager.getConnection("jdbc:odbc:Presidents");
  
  String sql = "SELECT FirstName, LastName, StartYear, EndYear, ImagePath FROM Presidents WHERE PresidentID BETWEEN ?-1 AND ?+1";
  stmt = conn.prepareStatement(sql);
  stmt.setString(1, request.getParameter("Slide"));
  stmt.setString(2, request.getParameter("Slide"));
  
  rs = stmt.executeQuery();
  
  response.setContentType("text/xml");
 
  out.write("<Presidents>");
  while (rs.next()) 
  {
   out.write("<President>");
   out.write("<Name>" + rs.getString("FirstName") + " " + rs.getString("LastName") + "</Name>");
   out.write("<Years>" + rs.getString("StartYear") + "-" + rs.getString("EndYear") + "</Years>");
   out.write("<Image>" + rs.getString("ImagePath") + "</Image>");
   out.write("</President>");
  }
  out.write("</Presidents>");
 }
 catch(Exception e)
 {
  out.write("failed: " + e.toString());
 }
 finally 
 {
  if (rs != null) rs.close();
  if (stmt != null) stmt.close();
  if (conn != null) conn.close();
 }
%>
Code Explanation

Notice that the SQL query will return records for the chosen president, the preceding president, and the following president. The resulting XML will look something like this:

Now we need to change the callback function to handle the preloaded slides and change the HTML to have hidden locations for the preloaded data. The code below shows how this is done.

Code Sample: AjaxApplications/Demos/SlideShow-preloaded.html

<html>
<head>
<title>Slide Show</title>
<link href="SlideShow.css" type="text/css" rel="stylesheet"/>
<script type="text/javascript" src="../../prototype.js"></script>
<script type="text/javascript">

var CurSlide=1;
var NumSlides = 10;
var PrevSlideReady = false;
var NextSlideReady = false;
var CurSlideLoaded = false;

function init()
{
 document.getElementById("PrevButton").onclick=PrevSlide;
 document.getElementById("NextButton").onclick=NextSlide;
 document.getElementById("TotalSlideNum").innerHTML = NumSlides;
 GetSlide();
}

function PrevSlide()
{
 if (CurSlide > 1)
 {
  CurSlide--;
  if (PrevSlideReady) //use data from PrevSlide placeholder
  {
   var slideText = document.getElementById("SlideText");
   var prevSlideText = document.getElementById("PrevSlideText");
   var slideImage = document.getElementById("SlideImage");
   var prevSlideImage = document.getElementById("PrevSlideImage");
   slideText.innerHTML = prevSlideText.innerHTML;
   slideImage.src = prevSlideImage.src;
   //document.getElementById("SlideMessage").innerHTML = "Loaded from PrevSlide.";
   PrevSlideReady=false;
   CurSlideLoaded=true;
  }
  GetSlide();
 }
 else
 {
  document.getElementById("SlideMessage").innerHTML = "Already showing first slide.";
  setTimeout("document.getElementById('SlideMessage').innerHTML=''",2000);
 }
}

function NextSlide()
{
 if (CurSlide < NumSlides)
 {
  CurSlide++;
  if (NextSlideReady) //Use data from NextSlide placeholder
  {
   var slideText = document.getElementById("SlideText");
   var nextSlideText = document.getElementById("NextSlideText");
   var slideImage = document.getElementById("SlideImage");
   var nextSlideImage = document.getElementById("NextSlideImage");
   slideText.innerHTML = nextSlideText.innerHTML;
   slideImage.src = nextSlideImage.src;
   //document.getElementById("SlideMessage").innerHTML = "Loaded from NextSlide.";
   NextSlideReady=false;
   CurSlideLoaded=true;
  }
  GetSlide();
 }
 else
 {
  document.getElementById("SlideMessage").innerHTML = "Already showing last slide.";
  setTimeout("document.getElementById('SlideMessage').innerHTML=''",2000);
 }
}

function GetSlide()
{
 document.getElementById("CurSlideNum").innerHTML=CurSlide;

 new Ajax.Request("SlideShow-preloaded.jsp", 
  {
   method: "get",
   parameters: "Slide=" + CurSlide,
   onComplete: ChangeSlide
  });
}

function ChangeSlide(REQ)
{
 var docElem = REQ.responseXML.documentElement;
 var PrevSlideNode, CurSlideNode, NextSlideNode;
 if (CurSlide == 1) //First slide
 {
  PrevSlideNode = null;
  CurSlideNode = docElem.childNodes[0];
  NextSlideNode = docElem.childNodes[1];
  Element.cleanWhitespace(CurSlideNode);
  Element.cleanWhitespace(NextSlideNode);
  PrevSlideReady=false;
  NextSlideReady=true;
 }
 else if (CurSlide == NumSlides) //Last slide
 {
  PrevSlideNode = docElem.childNodes[0];
  CurSlideNode = docElem.childNodes[1];
  Element.cleanWhitespace(PrevSlideNode);
  Element.cleanWhitespace(CurSlideNode);
  NextSlideNode = null;
  PrevSlideReady=true;
  NextSlideReady=false;
 }
 else
 {
  PrevSlideNode = docElem.childNodes[0];
  CurSlideNode = docElem.childNodes[1];
  NextSlideNode = docElem.childNodes[2];
  Element.cleanWhitespace(PrevSlideNode);
  Element.cleanWhitespace(CurSlideNode);
  Element.cleanWhitespace(NextSlideNode);
  PrevSlideReady=true;
  NextSlideReady=true;
 }
 
 if (!CurSlideLoaded) //Use data returned from server
 {
  document.getElementById("SlideText").innerHTML = CurSlideNode.childNodes[0].firstChild.nodeValue + "<br/>";
  document.getElementById("SlideText").innerHTML += CurSlideNode.childNodes[1].firstChild.nodeValue;
  document.getElementById("SlideImage").src = "Slides/" + CurSlideNode.childNodes[2].firstChild.nodeValue;
 }
 
 if (PrevSlideNode)
 {
  document.getElementById("PrevSlideText").innerHTML = PrevSlideNode.childNodes[0].firstChild.nodeValue + "<br/>";
  document.getElementById("PrevSlideText").innerHTML += PrevSlideNode.childNodes[1].firstChild.nodeValue;
  document.getElementById("PrevSlideImage").src = "Slides/" + PrevSlideNode.childNodes[2].firstChild.nodeValue;
 }
 
 if (NextSlideNode)
 {
  document.getElementById("NextSlideText").innerHTML = NextSlideNode.childNodes[0].firstChild.nodeValue + "<br/>";
  document.getElementById("NextSlideText").innerHTML += NextSlideNode.childNodes[1].firstChild.nodeValue;
  document.getElementById("NextSlideImage").src = "Slides/" + NextSlideNode.childNodes[2].firstChild.nodeValue;
 }
 CurSlideLoaded=false;
 
}

window.onload = init;
</script>
</head>

<body>
<h1>First 10 Presidents</h1>
<div id="PrevSlide">
 <img id="PrevSlideImage"/>
 <div id="PrevSlideText"></div>
</div>
<div id="Slide">
 <img id="SlideImage"/>
 <div id="SlideText"></div>
 <form action="SlideShow.html" onsubmit="return false" id="SlideForm">
  <input type="button" value="Previous" id="PrevButton"/>
  Slide <span id="CurSlideNum">1</span> of <span id="TotalSlideNum"></span>
  <input type="button" value="Next" id="NextButton"/>
 </form>
 <div id="SlideMessage"></div>
</div>
<div id="NextSlide">
 <img id="NextSlideImage"/>
 <div id="NextSlideText"></div>
</div>
</body>
</html>
Code Explanation

Notice these two divs in the HTML body:

<div id="PrevSlide">
 <img id="PrevSlideImage"/>
 <div id="PrevSlideText"></div>
</div>

<div id="NextSlide">
 <img id="NextSlideImage"/>
 <div id="NextSlideText"></div>
</div>

These divs are simply there to hold the incoming data. Their display property of these divs is set to "none" in SlideShow.css, but if you were to comment this out, the page would show up as follows:

The images in the upper corners are preloaded so that new slides load seamlessly.

In the JavaScript code, we have to keep track of which slide we are on.

  • If we're on the first slide, then only the following slide's data has been preloaded.
    if (CurSlide == 1)
    {
     PrevSlideNode = null;
     CurSlideNode = docElem.childNodes[0];
     NextSlideNode = docElem.childNodes[1];
     PrevSlideReady=false;
     NextSlideReady=true;
    }
    Notice that the global variable NextSlideReady is set to true indicating that the next slide should be loaded from the preloaded placeholder. This is checked in the NextSlide() function:
    function NextSlide()
    {
     if (CurSlide < NumSlides)
     {
      CurSlide++;
      if (NextSlideReady) //Use data from NextSlide placeholder
      {
       document.getElementById("SlideText").innerHTML = 
        document.getElementById("NextSlideText").innerHTML;
       document.getElementById("SlideImage").src = 
        document.getElementById("NextSlideImage").src;
       NextSlideReady=false;
       CurSlideLoaded=true;
      }
      GetSlide();
     }
     else
     {
      document.getElementById("SlideMessage").innerHTML = "Already showing last slide.";
      setTimeout("document.getElementById('SlideMessage').innerHTML=''", 2000);
     }
    }
  • If we're on the last slide, then only the previous slide's data is preloaded.
    else if (CurSlide == NumSlides)
    {
     PrevSlideNode = docElem.childNodes[0];
     CurSlideNode = docElem.childNodes[1];
     NextSlideNode = null;
     PrevSlideReady=true;
     NextSlideReady=false;
    }
    The global variable PrevSlideReady is set to true indicating that the previous slide should be loaded from the preloaded placeholder. This is checked in the PrevSlide() function:
    function PrevSlide()
    {
     if (CurSlide > 1)
     {
      CurSlide--;
      if (PrevSlideReady) //Use data from PrevSlide placeholder
      {
       document.getElementById("SlideText").innerHTML = 
        document.getElementById("PrevSlideText").innerHTML;
       document.getElementById("SlideImage").src = 
        document.getElementById("PrevSlideImage").src;
       PrevSlideReady=false;
       CurSlideLoaded=true;
      }
      GetSlide();
     }
     else
     {
      document.getElementById("SlideMessage").innerHTML = "Already showing first slide.";
      setTimeout("document.getElementById('SlideMessage').innerHTML=''",2000);
     }
    }
  • For all other slides, both the previous and following slides' data is preloaded.
    else
    {
     PrevSlideNode = docElem.childNodes[0];
     CurSlideNode = docElem.childNodes[1];
     NextSlideNode = docElem.childNodes[2];
     PrevSlideReady=true;
     NextSlideReady=true;
    }
    Both NextSlideReady and PrevSlideReady are set to true.

Note that in the NextSlide() and PrevSlide() functions, CurSlideLoaded is set to true after a slide is loaded from preloaded data. This variable is checked in the ChangeSlide() function to decide whether or not to load the current slide from the newly downloaded data:

if (!CurSlideLoaded) //Use the data returned from the server
 {
  document.getElementById("SlideText").innerHTML = CurSlideNode.childNodes[0].firstChild.nodeValue + "<br/>";
  document.getElementById("SlideText").innerHTML += CurSlideNode.childNodes[1].firstChild.nodeValue;
  document.getElementById("SlideImage").src = "Slides/" + CurSlideNode.childNodes[2].firstChild.nodeValue;
 }

Navigable Tables

The same techniques can be used to create navigable tables like the one shown below:

This screenshot shows the preloaded rows with a gray background. In practice, these rows would be hidden. Open AjaxApplications/Solutions/TableRows.html in your browser to try it out. To hide the preloaded rows, change the following lines in TablesRows.html:

<tbody id="PrevRows" style="background-color:gray;"></tbody>
<tbody id="CurRows"></tbody>
<tbody id="NextRows" style="background-color:gray"></tbody>

...to...

<tbody id="PrevRows" style="display:none;"></tbody>
<tbody id="CurRows"></tbody>
<tbody id="NextRows" style="display:none"></tbody>

Ajax Applications Conclusion

In this lesson of the Ajax tutorial, you have learned to apply some of the Ajax techniques you have learned.

To continue to learn Ajax go to the top of this page and click on the next lesson in this Ajax Tutorial's Table of Contents.

Use of this website implies agreement to the following:

Copyright Information

All pages and graphics on this Web site are the property of Webucator, Inc. unless otherwise specified.

None of the content on this website may be redistributed or reproduced in any way, shape, or form without written permission from Webucator, Inc.

No Printing or saving of web pages

This content may not be printed or saved. It is for online use only.


Linking to this website

You may link to any of the pages on this website; however, you may not include the content in a frame or iframe without written permission from Webucator, Inc.


Warranties

This website is provided without warranty of any kind. There are no guarantees that use of the site will not be subject to interruptions. All direct or indirect risk related to use of the site is borne entirely by the user. All code and explanations provided on this site are provided without warranties to correctness, performance, fitness, merchantability, and/or any other warranty (whether expressed or implied).

For individual private use only

You agree not to use this online manual to deliver or receive training. If you are delivering or attending a class that is making use of this online manual, you are in violation of our terms of service. Please report any abuse to courseware@webucator.com. If you would like to deliver or receive training using this manual, please fill out the form at http://www.webucator.com/Contact.cfm.