| [ Team LiB ] |
|
17.4 Mixing Client-Side and Server-Side CodeI touched on the difference between server-side code and client-side code in Chapter 3. JSP is a server-side technology, so all JSP elements, such as actions and scriptlets, execute on the server before the resulting page is sent to the browser. A page can also contain client-side code, such as JavaScript code or Java applets, to provide a more interactive user interface. This code is executed by the browser itself. A JSP page can generate JavaScript code dynamically the same way it generates HTML, WML, or any other type of text contents. Therefore, you can add client-side scripting code to your JSP pages. The important thing to keep in mind here is that even though you can include JavaScript code in your JSP page, the container doesn't see it as code at all. It treats it as template text and just sends it to the browser together with the rest of the response. Also remember that the only way a browser can invoke a JSP page is to send an HTTP request; there is no way that a JavaScript event handler such as onClick or onChange can directly invoke a JSP element such as an action, a scriptlet, or a Java method declared with a JSP declaration in a JSP page. A client-side script can ask the browser to make a request for a complete page, but there is no way that the script can process the response and use it to do something such as populate a selection list with the data. Applets can make your pages more interesting and provide an easier-to-use interface than what's possible with pure HTML. As you will see, JSP includes a standard action for generating the HTML needed for embedding applets in a page in a browser-independent way. 17.4.1 Generating JavaScript CodeExample 17-6 shows a modified version of the User Info page used in the examples in Chapter 10. Example 17-6. Input form with client-side validation code (clientscript.jsp)<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>User Info Entry Form</title>
<script language="JavaScript">
<!-- Hide from browsers without JavaScript support
function isValidForm(theForm) {
if (isEmpty(theForm.userName.value)) {
theForm.userName.focus( );
return false;
}
if (!isValidDate(theForm.birthDate.value)) {
theForm.birthDate.focus( );
return false;
}
if (!isValidEmailAddr(theForm.emailAddr.value)) {
theForm.emailAddr.focus( );
return false;
}
if (!isValidNumber(theForm.luckyNumber.value, 1, 100)) {
theForm.luckyNumber.focus( );
return false;
}
return true;
}
function isEmpty(aStr) {
if (aStr.length == 0) {
alert("Mandatory field is empty");
return true;
}
return false;
}
function isValidDate(dateStr) {
var matchArray = dateStr.match(/^[0-9]+-[0-1][0-9]-[0-3][0-9]$/)
if (matchArray == null) {
alert("Invalid date: " + dateStr);
return false;
}
return true;
}
function isValidEmailAddr(emailStr) {
var matchArray = emailStr.match(/^(.+)@(.+)\.(.+)$/)
if (matchArray == null) {
alert("Invalid email address: " + emailStr);
return false;
}
return true;
}
function isValidNumber(numbStr, start, stop) {
var matchArray = numbStr.match(/^[0-9]+$/)
if (matchArray == null) {
alert("Invalid number: " + numbStr);
return false;
}
if (numbStr < start || numbStr > stop) {
alert("Number not within range (" + start + "-" +
stop + "): " + numbStr);
return false;
}
return true;
}
-->
</script>
</head>
<body bgcolor="white">
<jsp:useBean id="userInfo"
scope="request"
class="com.ora.jsp.beans.userinfo.UserInfoBean"
/>
<form action="userinfovalidate.jsp" method="post"
onSubmit="return isValidForm(this)">
<input type="hidden" name="submitted" value="true">
<table>
<c:if
test="${param.submitted and userInfo.userNameValid == false}">
<tr><td></td>
<td colspan="2"><font color="red">
Please enter your Name
</font></td></tr>
</c:if>
<tr>
<td>Name:</td>
<td>
<input type="text" name="userName"
value="${fn:escapeXml(userInfo.userName)}">
</td>
</tr>
<c:if test="${param.submitted and not userInfo.birthDateValid}">
<tr><td></td>
<td colspan="2"><font color="red">
Please enter a valid Birth Date
</font></td></tr>
</c:if>
<tr>
<td>Birth Date:</td>
<td>
<input type="text" name="birthDate"
value="${fn:escapeXml(userInfo.birthDate)}">
</td>
<td>(Use format yyyy-mm-dd)</td>
</tr>
<c:if test="${param.submitted and not userInfo.emailAddrValid}">
<tr><td></td>
<td colspan="2"><font color="red">
Please enter a valid Email Address
</font></td></tr>
</c:if>
<tr>
<td>Email Address:</td>
<td>
<input type="text" name="emailAddr"
value="${fn:escapeXml(userInfo.emailAddr)}">
</td>
<td>(Use format name@company.com)</td>
</tr>
<c:if test="${param.submitted and not userInfo.genderValid}">
<tr><td></td>
<td colspan="2"><font color="red">
Please select a valid Gender
</font></td></tr>
</c:if>
<tr>
<td>Gender:</td>
<td>
<c:choose>
<c:when test="${userInfo.gender == 'f'}">
<input type="radio" name="gender" value="m">
Male<br>
<input type="radio" name="gender" value="f" checked>
Female
</c:when>
<c:otherwise>
<input type="radio" name="gender" value="m" checked>
Male<br>
<input type="radio" name="gender" value="f">
Female
</c:otherwise>
</c:choose>
</td>
</tr>
<c:if test="${param.submitted and not userInfo.luckyNumberValid}">
<tr><td></td>
<td colspan="2"><font color="red">
Please enter a Lucky Number between 1 and 100
</font></td></tr>
</c:if>
<tr>
<td>Lucky number:</td>
<td>
<input type="text" name="luckyNumber"
value="${fn:escapeXml(userInfo.luckyNumber)}">
</td>
<td>(A number between 1 and 100)</td>
</tr>
<c:if test="${param.submitted and not userInfo.foodValid}">
<tr><td></td>
<td colspan="2"><font color="red">
Please select only valid Favorite Foods
</font></td></tr>
</c:if>
<tr>
<td>Favorite Foods:</td>
<td>
<input type="checkbox" name="food" value="z"
${userInfo.pizzaSelected ? 'checked' : ''}>Pizza<br>
<input type="checkbox" name="food" value="p"
${userInfo.pastaSelected ? 'checked' : ''}>Pasta<br>
<input type="checkbox" name="food" value="c"
${fn:escapeXml(userInfo.chineseSelected}) ? 'checked' : ''}>Chinese
</td>
</tr>
<tr>
<td colspan=2>
<input type="submit" value="Send Data">
</td>
</tr>
</table>
</form>
</body>
</html>
The only differences are that a client-side validation function is defined in a <script> element and that the method is invoked by the onSubmit JavaScript event handler added to the <form> element. When the user submits the form, the browser first executes the isValidForm( ) JavaScript function to validate all input field values. Only if all values pass the test is the form actually submitted to the userinfovalidate.jsp page specified as the form's action URL. This means that the user is alerted to mistakes much faster, and the server is relieved from processing invalid requests. However, the server also performs the validation when the form is finally submitted, in exactly the same way as described in Chapter 8. This is important, because you don't know if the user's browser supports JavaScript or if scripting has been disabled in the browser. Please note that the JavaScript validation code shown in Example 17-6 is far from perfect. It's really intended only as an example. You can find much better validation code on sites such as the JavaScript Source (http://javascript.internet.com/). You may also want to put large amounts of JavaScript code such as this in a separate file to make the JSP page easier to read and maintain. Most browsers that support scripting allow you to specify an external source for the scripting code with the src attribute: <html>
<head>
<title>User Info Entry Form</title>
<script language="JavaScript" src="validate.js"></script>
</head>
<body bgcolor="white">
...
17.4.1.1 Using server-side data in JavaScript codeIn Example 17-6, all JavaScript code is written as static template text. However, nothing prevents you from generating parts of the JavaScript code dynamically, for instance a JavaScript array with values retrieved from a database by the JSP page. Example 17-7 shows a page that uses JavaScript code for setting the value of a selection list based on the selection made in another list. To run this example, you need a database with two tables named Sizes and Toppings, each with two columns named Name (of type CHAR) and Id (of type INT). Example 17-7. Dynamic selection setting (selections.jsp)<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Online Pizza</title>
<script language="JavaScript" src="dynamicscript.jsp"></script>
</head>
<body bgcolor="white"
onLoad="setList(document.pizza.sels, values[0]);">
<form name="pizza">
Please make your pizza order selections below:
<br>
<select name="categories"
onChange="setList(this.form.sels, values[this.selectedIndex]);">
<option value="0">Size
<option value="1">Toppings
</select>
<br>
<select name="sels" size="6">
<option>
<c:forEach begin="1" end="25"> </c:forEach>
</option>
</select>
</form>
</body>
</html>
The form in Example 17-7 contains two selections lists named categories and sels. When the user selects a category from the first list, the JavaScript onChange handler calls a JavaScript function named setList( ) to set the options in the second list. The setList( ) function takes two arguments: a reference to the selection list that should be updated and an array with the choice values. The JavaScript values array contains nested arrays: one array for each selection category, containing another set of arrays for the choices within each category. Each choice array contains two elements: the name of the choice (e.g., "Pepperoni") and the value to use for the <option> element's value attribute (i.e., a unique ID for each choice). To set the initial values, the onLoad event handler for the <body> element calls the setList( ) function with the subarray that contains the choices for the first category. The dynamicscript.jsp file specified as the source for the <script> element generates the JavaScript setList( ) function and values array. Example 17-8 shows how this JSP page fills the values array with values retrieved from two database tables. Example 17-8. Dynamically generated JavaScript code (dynamicscript.jsp)<%@ page contentType="application/x-javascript"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <sql:setDataSource var="pizza" driver="org.gjt.mm.mysql.Driver" url="jdbc:mysql:///test" /> <sql:query var="sizes" dataSource="${pizza}"> SELECT * FROM Sizes </sql:query> <sql:query var="toppings" dataSource="${pizza}"> SELECT * FROM Toppings </sql:query> values = new Array( new Array( <c:forEach items="${sizes.rows}" var="size" varStatus="s"> new Array("${fn:escapeXml(size.Name)}", "${size.Id}") <c:if test="${not s.last}">,</c:if> </c:forEach> ), new Array( <c:forEach items="${toppings.rows}" var="topping" varStatus="s"> new Array("${fn:escapeXml(topping.Name)}", "${topping.Id}") <c:if test="${not s.last}">,</c:if> </c:forEach> ) ); function setList(selectCtrl, itemArray) { // Remove current items for (i = selectCtrl.options.length; i >= 0; i--) { selectCtrl.options[i] = null; } for (i = 0; i < itemArray.length; i++) { selectCtrl.options[i] = new Option(itemArray[i][0]); selectCtrl.options[i].value = itemArray[i][1]; } } When the browser requests a JavaScript file it expects the response to be of type application/x-javascript. The page directive's contentType attribute in Example 17-8 takes care of that. The JSTL database actions described in Chapter 12 are used to get the data from the two tables. In this example, I use the <sql:setDataSource> action to create a DataSource instead of using the default (configured in the deployment descriptor) I used in Chapter 12 and Chapter 13. I tell the <sql:query> to use this DataSource by specifying the dataSource attribute. This way I can test this page with a different database from the one used for the other examples. In a production environment, I'd remove the <sql:setDataSource> action and make a real DataSource available in one of the ways described in Chapter 24. The rest of the page consists of static JavaScript code (highlighted) and JSTL actions that generate JavaScript array creation code. For each category ("Sizes" and "Toppings"), a <c:forEach> action loops through the corresponding database query result and generates a JavaScript subarray with strings for the choice name and value. Here's a sample of the resulting JavaScript code sent to the browser: values = new Array(
new Array(
new Array("Small",
"0")
,
new Array("Large",
"1")
,
new Array("X-Large",
"2")
),
...
To decide whether to add a comma after the JavaScript subarray, I use the status bean optionally exposed by the <c:forEach> action. The name of the variable to hold the bean is specified by the varStatus attribute. Within the loop, I test the value of its last property. It's set to true by <c:forEach> when it processes the last element in the collection, so I add a comma as long as it's false. When you mix code for the client and server like this, just remember which code executes where and when. To the code in the JSP page executing on the server, the JavaScript code it generates is just plain text; it doesn't even try to understand it. It's only when the page that contains the dynamically generated JavaScript code reaches the browser that it becomes meaningful and can be executed. The browser, on the other hand, couldn't care less that the JavaScript code was created by a JSP page; it has no idea how the code was created. It should be clear then, that JavaScript code can't call Java code in the JSP page and vice versa. 17.4.2 Using Java AppletsA Java applet is a Java class that is identified by a special element in an HTML page. The browser loads the class and executes it. An applet can provide a nice user interface on a web page. The problem is that the browsers don't keep up with the Java release cycles for the native Java support. Many users still have browsers that support only JDK 1.0, and more current browsers have so many limitations and bugs in their implementations that you're still limited to JDK 1.0 features to make the applet work. To address this issue, Sun provides a Java runtime environment that can be integrated in a browser using the browser's native plug-in API. The product is appropriately named the Java Plugin, and as of this writing the JDK 1.4 version is available for Netscape Navigator and Internet Explorer on Windows, Linux, and Solaris. For an up-to-date list of supported platforms, visit Sun's Java Plugin page at http://java.sun.com/products/plugin/. With the Java Plugin, you can use the latest Java features in your applets, such as the Swing GUI classes, collection classes, enhanced security, and more. But there's one more hurdle you have to jump. The HTML element you need in a page to get the Java Plugin (or any plug-in component) installed and loaded by the browser differs between Internet Explorer and Netscape Navigator. For Netscape, you need to use the <embed> element, while Internet Explorer requires the <object> element. Fortunately, JSP provides an easy solution to this problem, namely the <jsp:plugin> action. The <jsp:plugin> action looks at the User-Agent request header to figure out which type of browser is requesting the page and inserts the appropriate HTML element for using the Java Plugin to run the applet. Example 17-9 shows an example borrowed from the Tomcat JSP examples. Example 17-9. Embedding an applet in a JSP page (applet.jsp)<%@ page contentType="text/html" %>
<html>
<head>
<title>Embedding an applet</title>
</head>
<body bgcolor="white">
<h1>Embedding an applet</h1>
<jsp:plugin type="applet" code="Clock2.class"
codebase="applet"
jreversion="1.2" width="160" height="150" >
<jsp:params>
<jsp:param name="bgcolor" value="ccddff" />
</jsp:params>
<jsp:fallback>
Plugin tag OBJECT or EMBED not supported by browser.
</jsp:fallback>
</jsp:plugin>
</body>
</html>
The <jsp:plugin> action has three mandatory attributes: type, code, and codebase. The type attribute must be set to either applet or bean (to include a JavaBeans object), code is used to specify the class name, and codebase is the absolute or relative URL for the directory or archive file that contains the class. Note that the applet class must be stored in a directory that can be accessed by the web browser, that is, part of the public web page structure for the application (such as in webapps/myapp/myapplet.class). While class files for beans and custom actions are typically stored in the webapps/myapp/WEB-INF lib and classes subdirectories, you can't store applet classes in these directories, because they are accessible only to the container. The different locations makes sense when you think about where the code is executed: the applet is loaded and executed by the browser; beans and custom action classes are loaded and executed by the container. In Example 17-9, the applet class file is stored in an applet subdirectory of the directory that holds the JSP page. The <jsp:plugin> action also has a number of optional attributes, such as the width, height, and jreversion attributes used here. Appendix Acontains a description of all attributes. The body of the action element can contain nested elements. The <jsp:params> element, which in turn contains one or more <jsp:param> elements, provides parameter values to the applet. In Example 17-9, the applet's bgcolor parameter is set to the hexadecimal RGB value for light blue. The <jsp:fallback> element can optionally specify text that should be displayed instead of the applet in a browser that doesn't support the HTML <object> or <embed> element. Figure 17-2 shows what the page in Example 17-9 looks like in a browser. Figure 17-2. A page with an applet using the Java Plugin![]() An applet can communicate with the server in many different ways, but how it's done is off-topic for this book. If you would like to learn how to develop applets that communicate with a server, I suggest that you read Jason Hunter and William Crawford's Java Servlet Programming (O'Reilly). It includes a chapter about different applet-server communication options. |
| [ Team LiB ] |
|