This first article in the four-part series presents a JSP-based technique for generating JavaScript code, significantly reducing the amount of code you have to write manually. The sample application shows how to generate JavaScript functions for sending Ajax requests and processing Ajax responses. You can use the simple techniques discussed here in a real application if you want to be able to change the Ajax code easily. The broader goal of this article is to demonstrate how to use JSP tag files to produce JavaScript code for any purpose, not just Ajax routines.
Using frameworks and code generators
If you are fortunate enough to find a component or framework that does exactly what you need, use it. If not, you could always develop your own solution, or you might be able to customize an existing piece of code. In each case, it is a good idea to "parameterize" the code and place it into a reusable library, instead of hard-coding the parameters into your code. Sometimes, however, trying to make things generic doesn't make sense because it can complicate development instead of simplifying it. After placing generic code into reusable components or frameworks, consider using code generators to produce specialized code more efficiently.
Avoid copy & paste during development
Let's say you need your application to request some information on cities, using Ajax. The quickest (but certainly not the best) solution is to find some free code like that shown in Listing 1, change the URL, and paste the code into your Web page. Many developers do that, a practice that can cause huge maintenance problems. If your application has hundreds of pages, you end up with lots of functions like getInfo() in Listing 1. The bad news is that every time you have to add or change something, such as implementing error handling for the Ajax requests, you'll have to manually modify all your pages and retest them. The good news is that you can easily avoid this maintenance nightmare by using libraries, frameworks, and code generators.
Listing 1. Ajax function
function getInfo(country, city) {
var request = null;
if (window.ActiveXObject)
request = new ActiveXObject("Microsoft.XMLHTTP");
else if (window.XMLHttpRequest)
request = new XMLHttpRequest();
else
return;
var url = "CityInfo.jsp?country=" + escape(country)
+ "&city=" + escape(city);
request.open("GET", url, true);
function processResponse() {
if (request.readyState == 4) {
if (request.status == 200) {
// ...
}
}
}
request.onreadystatechange = processResponse;
request.send(null);
}
|
A good development practice is to move as much code as possible into reusable routines, functions, classes, or components, which can be grouped into libraries or frameworks. In our case, you could find a generic function that creates the XMLHttpRequest instance and calls its open() and send() methods.
Let's say the function you decide to use is named xhr() and takes five parameters: the URL of the page that returns the information, two arrays containing the names and values of the request parameters, the HTTP method, and a callback to process the Ajax response. Now your application will contain much simpler functions, like getInfo2() from Listing 2, and the code becomes easier to maintain. If you want to change something in the code that sends the Ajax requests, you'll have to modify only the xhr() function.
Listing 2. Using a generic function
function xhr(url, paramNames, paramValues, method, callback) {
// send Ajax request ...
}
function getInfo2(country, city) {
function processResponse(request) {
// process Ajax response ...
}
xhr("CityInfo.jsp", ["country", "city"], [country, city],
"GET", processResponse);
}
|
Listing 2 contains the generic function named xhr() and the application-specific function named getInfo2(). The generic code should be moved into separate JavaScript files so you can import the reusable functions in any page that needs them. For the specialized code, such as the getInfo2() function, you should consider using a code generator if the application requires many functions that are based on the same pattern.
You will develop and maintain Web applications more efficiency, using code generators. For example, you could use JSP, Java code, or another language to produce JavaScript functions from templates. An XML-based syntax for specifying the generator's parameters makes the code more readable and easier to understand thanks to the attribute names. In addition, tag attributes do not have a fixed order like the parameters of a JavaScript function or Java method.
When compared, a noted advantage of using XML tags is their ability to use default values for attributes, while programming languages offer limited possibilities to omit a method parameter. Consider these implications on the extensibility of the code generator, because adding a new attribute to a tag is much simpler than changing the signature of a method without breaking the existing code. All these syntactical advantages of using XML and JSP become obvious, especially when the code generator needs lots of attributes.
JSP is a good candidate for generating JavaScript code on the server side because:
- Developers already know the JSP syntax.
- JSTL provides tags for conditional and looping constructs.
- JSP pages allow you to easily generate any type of text, including JavaScript functions.
In addition, JSP technology already has a powerful mechanism for putting executable code behind custom tags, so you don't have to implement a parser for the templates based on the JSP syntax. And finally, you don't need an external tool to regenerate the code every time you make a change.
Listing 3 contains a code snippet from the application that is presented in the next section of the article. A custom JSP tag named <da:xhr> is used here to generate the getInfo3() function, which is called in the Web browser when the user clicks a button labeled Get Info.
Listing 3. Using a JSP tag file to generate the Ajax function
<%@ taglib prefix="da" tagdir="/WEB-INF/tags/dynamic/ajax" %>
...
<script type="text/javascript">
<da:xhr function="getInfo3(country, city)" url="CityInfo.jsp" method="GET">
// process Ajax response ...
</da:xhr>
</script>
...
<button ... onclick="getInfo3(...)">Get Info</button>
|
The generated JavaScript code can be placed within a <script> element of the Web page that calls the generated function as in Listing 3. If multiple Web pages need the same JavaScript code, a separate JSP file producing the code dynamically can be imported into the Web pages of the application like any regular JavaScript file, specifying its URI within the src attribute of the <script> element (see Listing 4).
Listing 4. Importing the JavaScript generated by a JSP page
<script src="DynamicJavaScript.jsp" type="text/javascript">
</script>
|
Generating the JavaScript code for every request is not a problem during development, but you might be worried about the performance penalty in a production environment. The solution is to cache the code, possibly using JSTL to store the generated code within the JSP application scope as shown in Listing 5. Then, you can insert the cached code within your Web pages with EL constructs, such as ${applicationScope.cachedCode}.
Listing 5. Caching the generated code
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:if test="${empty cachedCode}">
<c:set var="cachedCode" scope="application">
alert("Cached Code");
</c:set>
</c:if>
${applicationScope.cachedCode}
|
Creating a simple Ajax application
This section describes the JSP pages of the sample application. The CityForm.jsp page contains a Web form whose data is sent with Ajax to the Web server. Another page named CityInfo.jsp produces the Ajax responses.
The CityForm.jsp page of the sample application uses two
custom tags named <da:xhr> and <da:innerHTML>, which are implemented as JSP tag files. The xhr.tag file generates a JavaScript function that sends Ajax requests, while innerHTML.tag generates a single line of code that inserts some content within an HTML element using the innerHTML property. The JSP code of both tag files is presented later in this article.
The JSP page (see Listing 6) declares the used tag libraries, which are JSTL Core (prefix c) and the library of tag files (prefix da). The CityForm.jsp page also imports two JavaScript files named ajax.js and encode.js whose functions are invoked from the code generated by <da:xhr> and <da:innerHTML>. These custom tags are used within a <script> element to generate the code of a JavaScript function named getInfo().
Listing 6. The CityForm.jsp example
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="da" tagdir="/WEB-INF/tags/dynamic/ajax" %>
<html>
<head>
<title>Ajax Demo</title>
<script src="ajax.js" type="text/javascript">
</script>
<script src="encode.js" type="text/javascript">
</script>
<script type="text/javascript">
<da:xhr function="getInfo(country, city)" method="GET"
url="CityInfo.jsp" sync="false" json="true" cpr="true">
<da:innerHTML id="info" value="json.info" encode="true"/>
</da:xhr>
function getInfo2(form) {
var country = form.country.options[form.country.selectedIndex].text;
var city = form.city.value;
getInfo(country, city);
}
</script>
</head>
<body>
<form name="data">
Country:
<select name="country" size="1">
<option>Canada</option>
<option>UK</option>
<option selected>USA</option>
</select>
City: <input name="city" size="20">
<button type="button" onclick="getInfo2(this.form)">Get Info</button>
</form>
<div id="info"></div>
</body>
</html>
|
The Web form of the CityForm.jsp page contains a button labeled Get Info, a list of countries, and an input field where the user is supposed to enter a city name. When the user clicks the button, the Web browser invokes the getInfo2() function whose call is coded within the onclick attribute. This function obtains the values of the country and city fields of the Web form and passes these values to the generated getInfo() function, which sends the Ajax request to the server. The Ajax response will contain the information that needs to be inserted within the <div> element placed below the Web form.
The attributes of the <da:xhr> tag allow you to specify the header of the generated JavaScript function, the HTTP method used to send the Ajax request, and the URL of the page that produces the Ajax response. Several other attributes named sync, json, and cpr let you specify various features of the generated code.
If sync is true, the information is requested synchronously, which means the user interface is blocked while retrieving data from the server. If sync is false, the request is asynchronous, meaning that users can work while the information is transmitted over the network and processed by the Web server.
If the json attribute is true,
the code generator adds a JavaScript line that evaluates the Ajax response, using eval(request.responseText). Finally, if the cpr attribute of the <da:xhr> tag is true, the xhr.tag file produces a few pieces of JavaScript code that close the previous request before sending a new one. More is coming on this feature when I discuss the details of the ajax.js and xhr.tag files.
Listing 7 shows the generated getInfo(country, city) function of the CityForm.jsp page. The used HTTP method is GET, the URL of the page producing the Ajax response is CityInfo.jsp, the sync attribute is false, and both json and cpr are true. The generated JavaScript code uses the openRequest(), sendRequest(), closeRequest() and httpError() functions of the ajax.js file, and the appendParam() and htmlEncode() functions of encode.js.
Listing 7. Generated function for an Ajax request that uses GET
var getInfoRequest = null;
function getInfo(country, city) {
if (getInfoRequest) closeRequest(getInfoRequest);
var url = "CityInfo.jsp";
url += '?';
url = appendParam(url, "country", country);
url = appendParam(url, "city", city);
var request = openRequest("GET", url, true);
getInfoRequest = request;
if (request == null) return null;
function processResponse() {
if (request.readyState == 4) {
if (request.status == 200) {
eval(request.responseText);
document.getElementById("info").innerHTML
= htmlEncode(json.info);
} else {
httpError(request);
document.location = url;
}
}
}
request.onreadystatechange = processResponse;
sendRequest(request, null);
return request;
}
|
If you change the HTTP method to POST, the generated code is modified accordingly, as shown in Listing 8. Instead of adding the request parameters to url, the getInfo() function now appends these parameters to a variable named body, which is passed later to the sendRequest() function of the ajax.js file.
Listing 8. Generated function for an Ajax request that uses POST
function getInfo(country, city) {
...
var url = "CityInfo.jsp";
var body = "";
body = appendParam(body, "country", country);
body = appendParam(body, "city", city);
var request = openRequest("POST", url, true);
...
sendRequest(request, body);
return request;
}
|
The CityInfo.jsp page (see Listing 9) generates the responses to the Ajax requests, using JavaScript Object Notation (JSON). In order to keep this page scriptless, the Java code was moved into two JSP tag files named noCache.tag and jstring.tag, which are invoked from the JSP page with <da:noCache> and <da:jstring>. Placing Java code into JSP tag files is easier than developing tag handler classes because the JSP container produces these classes for you, and automatically recompiles the Java code when changed, without having to restart the application.
Listing 9. The CityInfo.jsp example
<%@ taglib prefix="da" tagdir="/WEB-INF/tags/dynamic/ajax" %>
<da:noCache/>
json = {
country: <da:jstring value="${param.country}"/>,
city: <da:jstring value="${param.city}"/>,
info: <da:jstring>Info on ${param.city}, ${param.country}</da:jstring>
}
|
Listing 10 shows the JSON responses.
Listing 10. Generated JSON
json = {
country: "UK",
city: "London",
info: "Info on London, UK"
}
|
The noCache.tag file (see Listing 11) contains a single line of Java code that sets the Cache-Control header of the HTTP response.
Listing 11. The noCache.tag file
<% response.setHeader("Cache-Control", "no-cache"); %>
|
The jstring.tag file (shown in Listing 12) encodes a
JavaScript string literal whose value can be passed to the tag file, either as an attribute or as the content body. If the value attribute is not specified, the <jsp:doBody> action executes the JSP code included between <da:jstring> and </da:jstring>, setting the value variable in the page scope. In both cases, the Java code gets the string value with jspContext.getAttribute() and outputs the characters one by one, escaping the special and non-ASCII characters.
Listing 12. The jstring.tag file
<%@ attribute name="value" required="false" rtexprvalue="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:if test="${empty value}">
<jsp:doBody var="value"/>
</c:if>
<%
String value = (String) jspContext.getAttribute("value");
out.write('"');
int len = value.length();
for (int i = 0; i < len; i++) {
char ch = value.charAt(i);
switch (ch) {
case '\\': out.write("\\\\"); break;
case '\n': out.write("\\n"); break;
case '\r': out.write("\\r"); break;
case '\t': out.write("\\t"); break;
case '"': out.write("\\\""); break;
default: {
if (' ' <= ch && ch <= '~')
out.write(ch);
else {
out.write("\\u");
for (int j = 3; j >= 0; j--) {
int k = (((int) ch) >> (j << 2)) & 0x0f;
out.write((char) (k < 10 ? k + 48 : k + 55));
}
}
}
}
}
out.write('"');
%>
|
Developing the JavaScript functions
This section presents the ajax.js and encode.js files whose functions are called from the JavaScript code generated by the xhr.tag and innerHTML.tag files.
XMLHttpRequest-related functions
The openRequest() function of the ajax.js file (see Listing 13) takes three parameters (method, url and async) and creates a XMLHttpRequest instance. Then, it calls the open() method and returns the initialized request object. The sendRequest() function sets the Content-Type header if the body parameter isn't null and calls the send() method of the request object.
Listing 13. The ajax.js file
function openRequest(method, url, async) {
var request = null;
if (window.ActiveXObject)
request = new ActiveXObject("Microsoft.XMLHTTP");
else if (window.XMLHttpRequest)
request = new XMLHttpRequest();
if (request)
request.open(method, url, async);
return request;
}
function sendRequest(request, body) {
if (body)
request.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded");
request.send(body);
}
function closeRequest(request) {
request.onreadystatechange = function() { };
request.abort();
delete request;
}
function httpError(request) {
alert("Http Error: " + request.status);
}
|
The closeRequest() method sets the onreadystatechange property to an empty function, calls the abort() method, and then frees the memory of the request object, using the delete operator of JavaScript. This function should be called on every XMLHttpRequest instance after processing the Ajax response. Otherwise, you might end up with a memory leak in the Web browser. The last function of the ajax.js file is httpError(), which shows the request's status in an alert window.
HTML and URL encoding functions
The htmlEncode() function of the encode.js file (see Listing 14) takes a string parameter and replaces the &, < and > characters with &, < and >. The attrEncode() function does the same operation and then replaces the " characters with " so the encoded string can be used as the value of an attribute.
The escape() function of JavaScript is often used to encode the request parameters of an Ajax request. You should be aware that escape() leaves the + characters unencoded, which is a problem because any + is decoded as a space character on the server.
The issue signaled above is fixed by the urlEncode() function of the encode.js file, which uses JavaScript's escape() to perform the URL encoding and then replaces any + character with %2B so the encoded string can be decoded correctly on the server side.
Listing 14. The encode.js file
function htmlEncode(value) {
return value ? value.replace(/&/g, "&")
.replace(/</g, "<").replace(/>/g, ">") : "";
}
function attrEncode(value) {
return htmlEncode(value).replace(/"/g, """);
}
function urlEncode(str) {
return str ? escape(str).replace(/\+/g, "%2B") : "";
}
function isArray(a) {
return a.sort ? true : false;
}
function appendParam(url, name, value) {
if (isArray(value)) {
for (var i = 0; i < value.length; i++)
url = appendParam(url, name, value[i]);
} else {
if (url && url.length > 0) {
if (url.charAt(url.length-1) != '?')
url += "&";
} else
url = "";
url += urlEncode(name) + "=" + urlEncode(value);
}
return url;
}
|
The appendParam() function adds a name/value pair to the given URL. If the third parameter is an array, the JavaScript code iterates over its elements and calls appendParam() recursively so a name/value pair is added to url for each element of the array.
Using JSP tag files to generate JavaScript code
This section presents the xhr.tag and innerHTML.tag files. The former generates the JavaScript function that sends Ajax requests to the server, and the latter produces the code of the callback function that processes the Ajax responses in the Web browser.
The <da:xhr> tag accepts six attributes: the header of the generated JavaScript function, the HTTP method, the URL of the page that returns the responses to the Ajax requests, a Boolean attribute indicating whether the requests should be sent synchronously or asynchronously, another Boolean attribute that specifies if the responses use the JSON format, and a final attribute (cpr) that enables a feature, which was named "close previous request" in this article. All these attributes are declared with the <%@attribute%> directive in the xhr.tag file (see Listing 15).
Next, the tag file uses the <%@taglib%> directive to declare the used tag libraries, converts the characters of the method attribute to upper case, and defines a JSP variable named reqVarName, which is built from the name of the generated JavaScript function and the Request string. After that, the xhr.tag file starts generating the JavaScript code. If the cpr attribute is true, a JavaScript variable is declared and initialized with null. This variable is used to hold the previous request, which must be "closed" the next time the generated function is invoked. The closeRequest() function of the ajax.js file was presented in the previous section.
The <dau:appendParams> tag is used in the xhr.tag file to append the request parameters to the url variable if method is GET, or to the body variable if method is POST. The openRequest() function of the ajax.js file creates and initializes the XMLHttpRequest instance. The Ajax request is sent to the server with the sendRequest() function whose code you can find in the same ajax.js file.
Listing 15. The xhr.tag file
<%@ attribute name="function" required="true" rtexprvalue="true" %>
<%@ attribute name="method" required="false" rtexprvalue="true" %>
<%@ attribute name="url" required="true" rtexprvalue="true" %>
<%@ attribute name="sync" required="false" rtexprvalue="true"
type="java.lang.Boolean" %>
<%@ attribute name="json" required="false" rtexprvalue="true"
type="java.lang.Boolean" %>
<%@ attribute name="cpr" required="false" rtexprvalue="true"
type="java.lang.Boolean" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="dau" tagdir="/WEB-INF/tags/dynamic/ajax/util" %>
<c:set var="method" value="${empty method ? 'GET' : fn:toUpperCase(method)}"/>
<c:set var="reqVarName"
value="${fn:trim(fn:substringBefore(function, '('))}Request"/>
<c:if test="${cpr}">var ${reqVarName} = null;</c:if>
function ${function} {
<c:if test="${cpr}">if (${reqVarName}) closeRequest(${reqVarName});</c:if>
var url = "${url}";
<c:if test="${method == 'GET'}">
url += '?';
<dau:appendParams jsVarName="url" function="${function}"/>
</c:if>
<c:if test="${method == 'POST'}">
var body = "";
<dau:appendParams jsVarName="body" function="${function}"/>
</c:if>
var request = openRequest("${method}", url, ${!sync});
<c:if test="${cpr}">${reqVarName} = request;</c:if>
if (request == null) return null;
function processResponse() {
if (request.readyState == 4) {
if (request.status == 200) {
<c:if test="${json}">eval(request.responseText);</c:if>
<jsp:doBody/>
} else {
httpError(request);
<c:if test="${method == 'POST'}">url += '?' + body;</c:if>
document.location = url;
}
}
}
request.onreadystatechange = processResponse;
<c:if test="${method == 'GET'}">sendRequest(request, null);</c:if>
<c:if test="${method == 'POST'}">sendRequest(request, body);</c:if>
return request;
}
|
An inner function named processResponse() is the callback passed to the request object through the onreadystatechange property. This callback function is invoked several times during the life cycle of the Ajax request whose current state can be obtained from the readyState property. The request is completed when readyState is 4.
If the HTTP status code is 200, there is no error and the Ajax response can be processed by the JSP code placed between <da:xhr> and </da:xhr>. This code is invoked from the xhr.tag file with <jsp:doBody/>. In case of an HTTP error, the generated code calls the httpError() function of the ajax.js file and the browser is redirected to the URL that produced the Ajax response, so the developer can see the server-side error that caused the HTTP error. For example, if the HTTP error code is 500 (internal error), you should see a Java stack trace.
Listing 16 shows the appendParams.tag file, which iterates
over the parameters of the given function header and generates a JavaScript line that
calls the appendParam() function of the encode.js file. Listings 7 and 8
presented earlier in this article show the generated code.
Listing 16. The appendParams.tag file
<%@ attribute name="jsVarName" required="true" rtexprvalue="true" %>
<%@ attribute name="function" required="true" rtexprvalue="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<c:set var="paramList"
value="${fn:substringBefore(fn:substringAfter(function, '('), ')')}"/>
<c:forEach var="paramName" items="${paramList}">
<c:set var="paramName" value="${fn:trim(paramName)}"/>
${jsVarName} = appendParam(${jsVarName}, "${paramName}", ${paramName});
</c:forEach>
|
The innerHTML.tag file (shown in Listing 17) produces a line of JavaScript code that obtains the DOM object representing the HTML element with the given id, using document.getElementById(). Then, the content of this element is changed by setting the innerHTML property whose new value may be passed either via the value attribute of the <da:innerHTML> tag or within the tag's body. If the encode attribute is true, the generated code calls the htmlEncode() function of the encode.js file.
Listing 17. The innerHTML.tag file
<%@ attribute name="id" required="true" rtexprvalue="true" %>
<%@ attribute name="value" required="false" rtexprvalue="true" %>
<%@ attribute name="encode" required="false" rtexprvalue="true"
type="java.lang.Boolean" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:if test="${empty value}">
<jsp:doBody var="value"/>
</c:if>
<c:if test="${encode}">
document.getElementById("${id}").innerHTML = htmlEncode(${value});
</c:if>
<c:if test="${!encode}">
document.getElementById("${id}").innerHTML = ${value};
</c:if>
|
The <da:innerHTML> tag can be used within <da:xhr> to process the Ajax response as shown in the sample application of this article. You could build similar tag files to perform different processing operations depending on the needs of your application.
In this article, you learned how to generate Ajax routines using JSP tag files. You can use the same technique for producing other types of JavaScript functions on the server side, which allows you to add or change features more easily because the code is regenerated automatically every time you change the tag files acting as templates. Stay tuned for the next article of the series, where you'll find out how to use data-binding, page-navigation, and style conventions to minimize setup and configuration.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample application for this article | wa-aj-simplejava1.zip | 6KB | HTTP |
Information about download methods
Learn
-
The developerWorks Web development zone is packed with tools and information for Web 2.0 development.
-
The developerWorks Ajax resource center contains a growing library of Ajax content as well as useful resources to get you started developing Ajax applications today.
-
Visit developer.mozilla.org if you are looking for JavaScript tools and documentation.
-
Go to The JSP Home page for more resources on JavaServer Pages.
- Read more developerWorks articles by
Andrei Cioroianu, covering intermediate and advanced Ajax techniques you can use to enhance your application development.
-
Stay current with developerWorks
technical events and Webcasts.
-
Browse the technology
bookstore for books on these and other technical topics.
Discuss
- Participate in the discussion forum.
-
Get involved in the developerWorks community. Participate in
developerWorks blogs.
Andrei Cioroianu is the founder of Devsphere, a provider of Java EE development and Web 2.0/Ajax consulting services. He's been using Java and Web technologies since 1997 and has 10 years of professional experience in solving complex technical problems and managing the full life cycle of commercial products, custom applications, and open source frameworks. You can reach Andrei through the contact form at www.devsphere.com.
Comments (Undergoing maintenance)





