Section 5.1. Form Entry with Ajax


5.1. Form Entry with Ajax

Order-entry applications aren't sexy, but they're everywhereand Ajax could be one of the best things that's ever happened to them. In the world of web applications, it's all about ease of use and saving time. Google Suggest suggests a way of making the web experience much easier: using Suggest as a model, we can write web applications that tell users immediately when they have requested usernames that are already in use, that fill in a city and state automatically on the basis of a zip code, and that make it simpler to enter names (product names, customer names, etc.) that are already in the database.

That's what we'll do in this chapter, while exploring ways to make a signup page easier to use and manage. We'll start with a Customer Sign-up page, shown in Figure 5-1. This page has two fields that get special treatment: the username field and the zip code field. We'll call these fields suggestion fields. For the username field, we'll notify users immediately if they enter usernames that have already been taken, rather than waiting until the form is submitted to validate this data. We'll also utilize the value entered into the zip code field to autopopulate the city and state fields with the values that correspond to that zip code, saving users some work.

Figure 5-1. The Ajax Customer Sign-up page


5.1.1. Validating the Username

We'll begin by validating the username to ensure that it does not currently exist in the database. To accomplish this, all we have to do is register the onblur JavaScript control with the username field:

 <td>User Name:</td> <td align="left">     <input type="text"  name="ajax_username"            onblur="validateUsername( );"> </td> 

The JavaScript onblur event fires whenever the field loses focusfor example, when the user presses Tab to move the cursor to the next field or clicks anywhere outside of the input field. When onblur fires, it transfers control to the validateUsername( ) JavaScript function:

 function validateUsername( ) {     var username = document.getElementById("ajax_username");     var url = "/ajax-customer-lab5-1/username?username=" + escape(username.value);     if (window.XMLHttpRequest) {         req = new XMLHttpRequest( );     }     else if (window.ActiveXObject) {         req = new ActiveXObject("Microsoft.XMLHTTP");     }     req.open("Get",url,true);     req.onreadystatechange = callbackUsername;     req.send(null); } 

This code should look familiar. validateUsername( ) retrieves the username from the field and puts it in a URL, calling escape(username.value) to ensure that any special characters in the username are escaped properly. It then sends the URL to the server in an HTTPGetRequest. When the request returns, the browser invokes the callback function, callbackUsername( ):

 function callbackUsername( ) {     if (req.readyState==4) {         if (req.status == 200) {             usernameCheck( );         }     } } 

The callbackUsername( ) function is simple. It waits for the response and sends control to the usernameCheck( ) method, which checks whether the entered username currently exists in the database and, if so, brings up an alert box informing the user that the name is already in use:

 function usernameCheck( ) {     // we only want a boolean back, so no parsing is necessary     userExists = req.responseText;     var username = document.getElementById("ajax_username");     if (userExists == "true") {         alert("Choose another username, "+username.value+" exists already");         username.value="";         username.focus( );     } } 

This is not an elegant solution, but it illustrates the basic idea. You could embellish the code by displaying the text off to the side and disabling the Signup button until the user enters a unique username, or by looking ahead as the user types and enabling the Signup button only when the entered username is unique.

Note that the server sends a simple true or false value. Since we only need one value, there's no need to wrap it in XML or JSON. The simplest solution is almost always best.

5.1.2. Creating the Database

Now that we've written the client code for checking the username, we need to support it with a database. These examples will use MySQL, which is freely available from http://www.mysql.org, but you can use any database with a JDBC driver. Create a database for this example with the following command:

 mysql> create database AJAX; 

Now the application will need a table to store the users for the suggest application (not to be confused with the database user we just set up).

Here is the SQL that creates the table to store the users:

 USE AJAX; CREATE TABLE USERS(USERNAME VARCHAR(50) PRIMARY KEY,     PASSWORD VARCHAR(50),     EMAIL VARCHAR(50),     FIRST_NAME VARCHAR(50),     LAST_NAME VARCHAR(50),     ADDRESS VARCHAR(50),     ZIPCODE VARCHAR(5),     CITY VARCHAR(50),     STATE VARCHAR(2),     JOINED DATE,     LAST_LOGIN DATE); 

Now we need to create another table to store zip codes for the application. This table should store all the known U.S. zip codes. (If you are in another country, you can adapt the table, but I'll assume you want to get it working with U.S. zip codes.) Here is the SQL for creating the zip code table:

 CREATE TABLE ZIPCODES (ZIPCODE VARCHAR(5) PRIMARY KEY,     CITY VARCHAR(50),     STATE VARCHAR(2)); 

VARCHAR(5) is the minimum size for a U.S. zip code, and VARCHAR(2) is big enough for an abbreviated U.S. state name. After you get this example running, you can adapt these values to your circumstances.

MySQL table names are case-sensitive on Linux but not on Windows, so use care when creating tables. If you create a table called USERS in a Linux environment, you must access it with SELECT * from USERS. However, if you create a table called users on a Windows machine, you can access it with SELECT * from users or SELECT * from USERS. This can be misleading if you start your project on Windows and later migrate your code to Linux. To avoid confusion, I always keep my table names uppercase.


Now we need to be able to log in to the database from the web application. Use the GRANT command to create a username and a password for the web application:

 mysql> GRANT ALL ON AJAX.* to 'ajax'@'localhost' IDENTIFIED BY 'polygon'; 

This command sets up a new user with the username ajax, allows the user ajax to connect only from the local machine (localhost), and gives the user the password polygon. You will need this information when connecting to the database through JDBC.

To populate the database with zip code data, we'll use the file ZIPCODES.sql, which you can download with the source code for this book from http://www.oreilly.com/catalog/9780596101879. To load the data into your database, use the mysqlimport tool included with MySQL. You will need to be in MySQL's bin directory or have the bin directory of MySQL in your path. The import command looks like this:

 mysqlimport -d -u root -p AJAX C:\ZIPCODES.sql Enter password: ****** 

This command reads in all of the data from ZIPCODES.sql and stores it in the ZIPCODES table. The -d option deletes any rows that are already in the table. This won't have any effect the first time you import the file, but if for some reason the first attempt fails and you need to run the script again, -d will prevent duplicate rows by deleting the rows that were imported on the first run.

The -u option defines the user to use when importing the rows. In this case, the user is root. -p is the password option; you'll be prompted for the password. AJAX is the name of the database to import into, and the last parameter is the file to import: C:/ZIPCODES.sql.

Notice that the file prefix, ZIPCODES, is the same as the table name. mysqlimport requires the table name to match the filename.

If the rows are imported successfully, you will see something similar to this:

 Ajax.ZIPCODES: Records: 41905  Deleted: 0 Skipped: 0 Warnings: 41912 

5.1.3. Servicing the Ajax Request: Servlets

Now that the database is ready, we return to the coding at hand: the servlets that bridge the gap between the database and the client. A few pages ago we set up the client to check for preexisting usernames. In the servlet, we merely have to get the username from the request and query the database for a matching name. We'll keep the servlet simple by just using a JDBC connection; we won't do any connection polling, Object-Relational Mapping, or any of the other fancy tricks you'd probably see in a real application.

Example 5-1 presents the code to get the username and check it against the database to see whether it already exists.

Example 5-1. The AjaxUsernameServlet

 public class AjaxUsernameServlet extends HttpServlet {     public void doGet(HttpServletRequest req, HttpServletResponse res)             throws ServletException, IOException {         String username = req.getParameter("username");         if (username != null) {             if (existsUsername(username)) {                 res.setContentType("text/xml");                 res.setHeader("Cache-Control", "no-cache");                 res.getWriter( ).write("true");             }         } else {             // if key comes back as a null, return a message             res.setContentType("text/xml");             res.setHeader("Cache-Control", "no-cache");             res.getWriter( ).write("Username null");         }     }     private boolean existsUsername(String username) {         ResultSet result = null;         try {             Statement select = DatabaseConnector.getConnection().createStatement( );             result = select.executeQuery("SELECT USERNAME from USERS where                                          USERNAME = '" + username + "';");             if (result == null || result.next( )) {                 return true;             }         } catch (SQLException e) {             // use log4j or handle this how you want         }         return false;     } } 

The doGet( ) method intercepts the request and checks for the username parameter. The parameter is sent to the existsUsername( ) method, which checks the database and returns true if the user exists. If the user exists, doGet( ) sends the string "true" back to the user; otherwise, it sends the string "Username null". Again, we're not using any XML here, just simple strings.

The DatabaseConnector class, presented in Example 5-2, is a singleton. If there is no JDBC connection currently stored in the connection field, a new one is created, stored, and returned. If there is an existing connection, it is simply returned.

Example 5-2. The DatabaseConnector class

 public class DatabaseConnector {     private static Connection connection;     public static Connection getConnection( ) {         if (connection != null)             return connection;         Connection con = null;         String driver = "com.mysql.jdbc.Driver";         try {             Class.forName(driver).newInstance( );         } catch (Exception e) {             System.out.println("Failed to load mySQL driver.");             return null;         }         try {             con = DriverManager.getConnection(                   "jdbc:mysql:///AJAX?user=ajax&password=polygon");         } catch (Exception e) {             e.printStackTrace( );         }         connection = con;         return con;     } } 

That covers it for username validation: a full round trip is made to the database to check for username collision. Next, we'll start working on the zip codes, using the database to populate the city and state fields automatically.

5.1.4. Loading City and State by Zip Code

We've already loaded the zip codes into the database. Now we are going to hook up the client. We have to access the database again to validate the zip code and retrieve the city/state data. We'll use the same approach that we did for validating the username: we'll wait until the user has entered the whole zip code in the field and then, when the cursor leaves the field, we'll populate the city and state fields with the matching values. (Implementing this as a lookahead field wouldn't be particularly helpful; the user will either know the zip code or not.)

First, we need to add the JavaScript onblur trigger to the zip code field:

 <td>Zip Code:</td> <td align="left">     <input type="text"  name="zipcode"            onblur="retrieveCityState( )"> </td> 

There's nothing new here. The retrieveCityState( ) function is called whenever the zip code field loses focus. This function sets up the XMLHttpRequest and sends it to the AjaxZipCodesServlet:

 var req; function retrieveCityState( ) {     var zip = document.getElementById("zipcode");     var url = "/ajax-customer-lab5-1/zipcodes?zip=" + escape(zip.value);     name.value="?"+name.value;     if (window.XMLHttpRequest) {         req = new XMLHttpRequest( );     }     else if (window.ActiveXObject) {         req = new ActiveXObject("Microsoft.XMLHTTP");     }     req.open("Get",url,true);     req.onreadystatechange = callbackCityState;     req.send(null); } 

Because we are getting a good number of JavaScript functions, we'll extract the JavaScript and place it in a separate file called oreillyAJAX.js, which is loaded by index.html:

 <title>AJAX Customer Sign-up</title> <script language="JavaScript" src="/books/4/163/1/html/2/oreillyAJAX.js"></script> 

The servlet for looking up zip codes, presented in Example 5-3, is simple. The doGet( ) method extracts the zip parameter from the request; that parameter contains the zip code the user typed. The zip code is passed to the getCityState( ) method, which queries the database for the city and state. Take a look at the format used to return data to the client. Is it XML or JSON?

Example 5-3. The AjaxZipCodesServlet

 public void doGet(HttpServletRequest req, HttpServletResponse res)         throws ServletException, IOException {     String responseString = null;     String zipCode = req.getParameter("zip");     if (zipCode != null) {         HashMap location = getCityState(zipCode);         responseString = JSONUtil.buildJSON(location, "location");     }     if (responseString != null) {         res.setContentType("text/xml");         res.setHeader("Cache-Control", "no-cache");         res.getWriter( ).write(responseString);     } else {         // if key comes back as a null, return a question mark         res.setContentType("text/xml");         res.setHeader("Cache-Control", "no-cache");         res.getWriter( ).write("?");     } } private HashMap getCityState(String zipCode) {     Connection con = DatabaseConnector.getConnection( );     HashMap cityStateMap = new HashMap( );     cityStateMap.put("zip", "zipCode");     String queryString = "";     try {         queryString = "SELECT CITY, STATE FROM ZIPCODES where ZIPCODE="                 + zipCode + ";";         Statement select = con.createStatement( );         ResultSet result = select.executeQuery(queryString);         while (result.next( )) { // process results one row at a time             String city;             String state;             city = result.getString("CITY");             if (result.wasNull( )) {                 city = "";             }             cityStateMap.put("city", city);             state = result.getString("state");             if (result.wasNull( )) {                 state = "";             }             cityStateMap.put("state", state);         }     } catch (Exception e) {         System.out.println("exception caught getting city/state:"                            + queryString + " " + e.getMessage( ));     } finally {         if (con != null) {             try {                 con.close( );             } catch (SQLException e) {             }         }     }     return cityStateMap; } 

We use the JSON format to pass data back to the client because it is simpler to process and therefore less error-prone. Using the zip code, we look up the corresponding city and state in the database and store them in a HashMap. We then pass that HashMap into the buildJSON( ) method. An Iterator then goes through all of the entries in the HashMap and builds a JSON-formatted String from the key/value pairs in the HashMap:

 public static String buildJSON(HashMap map, String title) {     StringBuffer returnJSON = new StringBuffer("\r\n{\"" + title + "\":{");     String key = "";     String value = "";     // loop through all the map entries     Iterator it = map.entrySet().iterator( );     while (it.hasNext( )) {         Map.Entry e = (Map.Entry) it.next( );         value = (String) e.getValue( );         key = (String) e.getKey( );         returnJSON.append("\r\n\"" + key + "\": \"" + value + "\",");     }     // remove the last comma     int lastCharIndex = returnJSON.length( );     returnJSON.deleteCharAt(lastCharIndex - 1);     returnJSON.append("\r\n}}");     return returnJSON.toString( ); } 

This isn't production-quality code: the exceptions aren't handled very well, the database connections aren't managed well, there's no logging, and so on. In a real application, you'd want to fix these weaknesses. What we have accomplished in this example, though, is quite important. We've built an Ajax application that couldn't have been implemented entirely with client-side code. We didn't really need a trip to the server to look up the decimal value of a character, but we do need to communicate with the server to find out whether someone has requested a username that's already in use, or to find out the city and state associated with a zip code.

Now let's get more ambitious: let's build a suggestion field that helps an administrator enter a user's name.




Ajax on Java
Ajax on Java
ISBN: 0596101872
EAN: 2147483647
Year: 2007
Pages: 78

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