Developer: Java
   DOWNLOAD
 Oracle JDeveloper 11g Technology Preview
 Sample Code
 
   TAGS
java, ajax, All
 

Working with Ajax Table Editors and Viewers

Learn how to build dynamic table components, using Ajax to connect to a server, and find out how to efficiently generate HTML and XML with JavaScript.

By Andrei Cioroianu

Published December 2007

A typical Web application shows information that is retrieved from databases and other sources such as data feeds. In many cases, one or more Web forms let the user enter new information or modify the existing data. When the user clicks a button, the form's data is sent to the server where it is validated and processed. Then, the server returns the same form or another page to the user. This application model can be optimized with the help of Ajax, minimizing the network traffic and significantly improving the user's experience.

An Ajax client responds much faster to the user's actions and can provide desktop-like features, increasing user productivity. Instead of regenerating the HTML markup for every request on the server side, the user interface (UI) is loaded once in the browser and then the Ajax client retrieves only the data that it needs, updating the user interface dynamically. In addition, many data requests are sent asynchronously, meaning that they don't block the UI.

In this article, I'll show you how to build a Web-based table editor that allows the user to insert/delete rows and undo changes without waiting for a server response and without losing the scroll position. The table component will also act as a viewer that uses Ajax to retrieve information from a data feed. You'll learn how to use JavaScript to build a table model, create the table editor/viewer, validate data with regular expressions, access the data feed, use event handlers, and monitor the data changes in the Web browser. In addition, I'll present a very interesting technique for generating XML and HTML on the client side, using a JSP-like syntax that makes the code more readable and easier to maintain.

Application Overview

The sample application of this article is a portfolio editor/viewer:

The user enters the stock information and is able to undo the data changes at any moment. The share prices are retrieved from a data feed so that the stock values and their gains/losses can be updated every second. When the user saves the data, the table component is switched to a read-only mode:

I have already presented half of the application's code in a previous OTN article titled " Enabling Data Exchange in Ajax Applications," and the Ajax utility functions (contained in the ajaxUtil.js file) are reused from another OTN article of mine titled " Developing Smart Web UIs with Ajax, JSF, and ADF Faces." The following table specifies the article that describes each source file and the directory where you can find the file:

Article

Directory

File

Description

Data exchange

src/ajaxapp/util

JSONEncoder.java

JSON encoding utilities

Data exchange

src/ajaxapp/util

XMLUtil.java

Schema, DOM, XPath utilities

Data exchange

src/ajaxapp/model

DataModel.java

Server-side data model

Data exchange

src/ajaxapp/model

StockBean.java

JavaBean used by the data model

Data exchange

src/ajaxapp/model

portfolio.xsd

XML Schema file for validation

This article

src/ajaxapp/feed

DataFeed.java

Data feed implementation

This article

src/ajaxapp/feed

ShareBean.java

JavaBean used by the data feed

This article

src/ajaxapp/builder

JSBuilder.java

JavaScript code generator

This article

public_html

index.jsp

Main page of the portfolio app

Data exchange

public_html

ajaxCtrl.jsp

Controller for the Ajax requests

Smart Web UIs

public_html

ajaxUtil.js

General Ajax utilities

Data exchange

public_html

ajaxLogic.js

App-specific Ajax utilities

This article

public_html

dataModel.js

Client-side data model

This article

public_html

dataTable.js

UI utilities and event handlers

This article

public_html

style.css

Cascading Style Sheet

This article

public_html/WEB-INF

feed.tld

Tag Library Descriptor

This article

public_html/WEB-INF

web.xml

Web Application Descriptor

This article

public_html/WEB-INF/tags

setHeader.tag

Tag that sets an HTTP header

This article

public_html/WEB-INF/tags

template.tag

Tag that invokes the JSBuilder

Data Models and Data Feeds

Like any typical Web application, the portfolio sample has a server-side data model, which was described in "Enabling Data Exchange in Ajax Applications." The DataModel class can be initialized from an XML document, and its state can be encoded as a JSON string that is used to initialize the client-side data model, which is presented in this section.

Building Table Models

The portfolio application uses a JavaScript table model that maintains the user data and the information retrieved from the data feed with Ajax. To create the JavaScript file, open JDeveloper, select an existing project or create a new one, click File / New, expand Web Tier in the New Gallery window, select the HTML category in the left panel, select JavaScript File in the right panel and click OK:

Enter the dataModel.js file name and click OK:

The dataModel.js file of the sample application creates an object whose methods are similar to those defined by Swing's TableModel interface: getRowCount(), getColumnCount(), getColumnName(), getValueAt(), and setValueAt(). Here is the code that creates the data model object:
var dataModel = {

    columns: [ "Symbol", "Shares", "Paid Price",
        "Last Price", "Stock Value", "Gain/Loss" ],

    stocks: [ ],

    getRowCount: function() {
        return this.stocks.length;
    },

    getColumnCount: function() {
        return this.columns.length;
    },

    getColumnName: function(index) {
        return this.columns[index];
    },

    getValueAt: function(row, column) {
        ...
    },
    
    setValueAt: function(value, row, column) {
        ...
    },

    ...
}

The data model object has additional methods that return a stock's value and gain, calculate the portfolio's value and gain, get/set all stocks, insert a new stock, and delete an existing stock. You can find the code of these simple methods in the dataModel.js file.

The JavaScript editor of JDeveloper has many useful features such as syntax highlighting and verification, completion and parameter insight, refactoring, usage finding, brace matching, and code folding. In addition, you can use the Structure Navigator to locate declarations and functions:

The JDeveloper release used here (Oracle JDeveloper 11g Technology Preview 2) also includes a JavaScript debugger for Firefox. To view or change the debugger settings, click Tools / Project Properties, select Run/Debug/Profile in the left panel, choose the Default profile in the right panel, and click Edit:

In the Edit Run Configuration window, select Launch Settings / JavaScript in the left panel so that you can setup the debugger:

Implementing Data Feeds

A data feed usually provides a Web service or an HTTP-based protocol so that it can be accessed over a network. An Ajax client, however, cannot directly connect to third-party Web services due to security restrictions imposed by the Web browser. Therefore, you need an Ajax controller running on the same server that hosts the Ajax application. This controller should connect to the third-party Web service to retrieve the data for the Ajax client. In order to keep things simple and focus on the Ajax topics, the sample application simulates the data feed, generating random share prices in the ShareBean class:

package ajaxapp.feed;

public class ShareBean implements java.io.Serializable {
    private String symbol;
    private int trend;
    private double lastPrice;
        
    public ShareBean(String symbol) {
        this.symbol = symbol;
        trend = 1;
        lastPrice = Math.random() * 100;
    }
        
    public String getSymbol() {
        return symbol;
    }

    public int getTrend() {
        return trend;
    }
        
    public double getLastPrice() {
        lastPrice += trend * Math.random() * 0.1;
        if (Math.random() < 0.2)
            trend = -trend;
        return lastPrice;
    }

}

The DataFeed class provides a getData() method that takes an array of symbols and returns the share information encoded in a JSON string:

package ajaxapp.feed;

import ajaxapp.util.JSONEncoder;

import java.util.*;

public class DataFeed implements java.io.Serializable {
    private HashMap<String, ShareBean> shareMap;
    
    public DataFeed() {
        shareMap = new HashMap<String, ShareBean>();
    }
    
    public synchronized String getData(String symbols[]) {
        JSONEncoder json = new JSONEncoder();
        json.startArray();
        for (int i = 0; i < symbols.length; i++) {
            String symbol = symbols[i];
            ShareBean share = shareMap.get(symbol);
            if (share == null) {
                share = new ShareBean(symbol);
                shareMap.put(symbol, share);
            }
            json.startObject();
            json.property("symbol", share.getSymbol());
            json.property("trend", share.getTrend());
            json.property("lastPrice", share.getLastPrice());
            json.endObject();
        }
        json.endArray();
        return json.toString();
    }
    
    public static String getData(
            DataFeed feed, String symbols[]) {
        return feed.getData(symbols);
    }
    
}

The static getData() method is useful for calling the instance method from a JSP page, using an expression language (EL) function that is defined in feed.tld. To create the TLD file, click File / New, select the JSP category from Web Tier in the left panel, select the JSP Tag Library item and click OK:

Skip the Welcome screen of the wizard and select Project Based:

Provide the feed short name, enter the /ajaxapp/feed URI and click Finish:

Then, paste the <function> element shown below:
<?xml version="1.0" encoding="UTF-8" ?>
<taglib ...>

    <tlib-version>1.0</tlib-version>
    <short-name>feed</short-name>
    <uri>/ajaxapp/feed</uri>

    <function>
        <name>getData</name>
        <function-class>ajaxapp.feed.DataFeed</function-class>
        <function-signature>
            java.lang.String getData(
                ajaxapp.feed.DataFeed, java.lang.String[])
        </function-signature>
    </function>

</taglib>

The application's Ajax controller (called ajaxCtrl.jsp) uses the getData() EL function to obtain the share prices for the Ajax client. A DataFeed instance is used as an application bean in the ajaxCtrl.jsp file:

<%@ taglib prefix="tags" tagdir="/WEB-INF/tags/" %>
<%@ taglib prefix="feed" uri="/WEB-INF/feed.tld" %>

<tags:setHeader name="Cache-Control" value="no-cache"/>

<jsp:useBean id="dataFeed" scope="application"
        class="ajaxapp.feed.DataFeed" />
...
${feed:getData(dataFeed, paramValues.symbol)}

It is important to set the no-cache header in order to disable the Web browser's cache. Here is the code of the setHeader.tag file:

<%@ attribute name="name" required="true" %>
<%@ attribute name="value" required="true" %>

<%
    String name = (String) jspContext.getAttribute("name");
    String value = (String) jspContext.getAttribute("value");
    response.setHeader(name, value);
%>

Now that we have implemented a data feed on the server side, let's see how to access it from the Ajax client.

Accessing Data Feeds

The Ajax client sends an HTTP request to the controller, which calls the getData() method and returns the JSON string. The HTTP request must contain the parameters, which are the share symbols in the case of this example. The requestFeedInfo() function (whose code can be found in dataTable.js) gets the symbols from the data model and makes sure that each symbol is included only once, because the portfolio could contain multiple stocks with the same symbol:

function requestFeedInfo() {
    var symbolSet = new Array();
    for (var i = 0; i < dataModel.stocks.length; i++) {
        var symbol = dataModel.stocks[i].symbol;
        if (isValidSymbol(symbol)) {
            for (var j = 0; j < symbolSet.length; j++)
                if (symbol == symbolSet[j].value)
                    symbol = null;
            if (symbol)
                symbolSet[symbolSet.length] = symbol;
        }
    }
    if (symbolSet.length > 0)
        sendInfoRequest(symbolSet, feedInfoCallback);
}

The array of symbols is passed to sendInfoRequest() along with feedInfoCallback, which will be invoked when the data is received from the server. The sendInfoRequest() function aborts the previous HTTP request if it hasn't been completed, and then it builds the array of HTTP parameters that is passed to sendHttpRequest(), which builds and sends the request to the server, using the Ajax API. The sendHttpRequest() function can be found in the ajaxUtil.js file whose code was discussed in a previous OTN article entitled " Developing Smart Web UIs with Ajax, JSF, and ADF Faces." Here is the code of the sendInfoRequest() function from ajaxLogic.js:

var ctrlURL = "ajaxCtrl.jsp";
var feedRequest = null;

function sendInfoRequest(symbols, callback) {
    if (feedRequest)
        abortRequest(feedRequest);
    var params = new Array();
    for (var i = 0; i < symbols.length; i++)
        params[i] = {
            name: "symbol",
            value: symbols[i]
        };
    feedRequest = sendHttpRequest(
        "GET", ctrlURL, params, callback);
}

The Ajax callback can be found in the dataTable.js file. The feedInfoCallback() function evaluates the JSON response with eval(request.responseText), obtaining an object tree that contains the information retrieved from the data feed. Then, it stores the latest share prices into the data model and calls updateDynamicCells() for updating the UI:

function feedInfoCallback(request) {
    var feedInfo = eval(request.responseText);
    for (var i = 0; i < dataModel.stocks.length; i++) {
        var symbol = dataModel.stocks[i].symbol;
        dataModel.stocks[i].lastPrice = 0;
        for (var j = 0; j < feedInfo.length; j++)
            if (symbol == feedInfo[j].symbol) {
                var value = feedInfo[j].lastPrice;
                dataModel.stocks[i].lastPrice = value;
            }
    }
    updateDynamicCells();
}

Before discussing the UI code, let's see how you can use JavaScript to generate XML and HTML in the Web browser.

Generating XML and HTML with JavaScript

Before JSP, servlets were used to dynamically generate HTML content on the server side. It was difficult to create and maintain the servlet code, because the developer had to manually encode HTML strings within the Java code and each line of markup had to be placed in a println() statement. It was difficult to view the page structure, and it wasn't possible to use visual tools for designing the pages that were coded as servlets.

Today, servlets are doing very well as the foundation of the JSP technology, which provides a much better way for generating markup on the server side. On the client side, however, JavaScript has no JSP equivalent. Many developers use servlets-like coding techniques to dynamically create HTML content, or they use the DOM API. None of these solutions is easy to use when you have to generate HTML or XML with JavaScript.

Using a JSP-like Syntax with JavaScript

It would be much easier to use something like this

<tag attr="[%= value %]"> [%= data %] </tag>

instead of

"<tag attr=\"" + escapeXML(value) + "\">" + escapeXML(data) + "</tag>"

You wouldn't have to escape " with \ anymore, and expressions included within the [%= %] constructs would be automatically escaped, meaning that &, <, >, and " would be replaced with &amp;, &lt;, &gt;, and &quot;, respectively. Sometimes you already have a piece of markup and you don't want to escape &, <, >, and ". In this case, you could use [%# %] instead of [%= %].

So you can easily view the markup's structure, the blocks of JavaScript code can be wrapped between [% and %] as in the following example:

<table class="tableClass"> [% 
    for (var i = 0; i < rowCount; i++) { %]
        <tr class="[%= getRowClass(i) %]"> [% 
            for (var j = 0; j < columnCount; j++) { %]
                <td class="[%= getCellClass(i, j) %]"> 
                    [%# buildCell(i, j) %]
                </td> [% 
            } %]
        </tr> [% 
    } %]
</table>

The above template must be translated into valid JavaScript code on the server side. Here is how this should look:

var content = "";
content += "<table class=\"tableClass\">";
for (var i = 0; i < rowCount; i++) { 
    content += "<tr class=\"";
    content += escapeXML(getRowClass(i));
    content += "\">";
    for (var j = 0; j < columnCount; j++) { 
        content += "<td class=\"";
        content += escapeXML(getCellClass(i, j));
        content += "\">";
        content += buildCell(i, j);
        content += "</td>";
    }
    content += "</tr>";
} 
content += "</table>";

As you can see, the code is much easier to read and maintain when you use the JSP-like syntax. Let's see how to generate the JavaScript code from the template.

Creating the Code Generator

The JSBuilder class (of the ajaxapp.builder package) parses the template and generates the JavaScript code. In addition, the JSONEncoder class (of the ajaxapp.util package) provides the means for encoding the JavaScript strings with the character() and string() methods:

package ajaxapp.util;

public class JSONEncoder {
    private StringBuilder buf;
    
    public JSONEncoder() {
        buf = new StringBuilder();
    }
    
    public void character(char ch) {
        switch (ch) {
            case '\'':
            case '\"':
            case '\\':
                buf.append('\\'); 
                buf.append(ch); 
                break;
            ...
            default:
                if (ch >= 32 && ch < 128)
                    buf.append(ch);
                else {
                    ...
                }
        }
    }
    
    public void string(String str) {
        int length = str.length();
        for (int i = 0; i < length; i++)
            character(str.charAt(i));
    }

    ...

    public String toString() {
        ...
        return buf.toString();
    }

    public void clear() {
        buf.setLength(0);
    }

}

The character() and string() methods of the JSONEncoder class need some modifications so they can be used by the code generator. Therefore the createEncoder() method of JSBuilder uses an inner class that overrides character() to output a JavaScript line when it meets the '\n' character. The string() method will output the last line, which might not end with the new line character.

package ajaxapp.builder;

import ajaxapp.util.JSONEncoder;

public class JSBuilder {
    ...
    private JSONEncoder json;
    private String varName;
    private boolean isHTML;
    private PrintWriter out;

    public JSBuilder() {
        json = createEncoder();
    }
    
    private JSONEncoder createEncoder() {
        return new JSONEncoder() {
            private void outputLine() {
                String line = toString();
                out.println(varName + " += \"" + line + "\";");
                clear();
            }

            public void character(char ch) {
                super.character(ch);
                if (ch == '\n')
                    outputLine();
            }
            
            public void string(String str) {
                super.string(str);
                if (toString().length() > 0)
                    outputLine();
            }
        };
    }
    ...
}

The printContent() method uses the customized encoder to generate the JavaScript code that reconstructs a piece of HTML or XML content on the client side:

public class JSBuilder {
    ...
    private void printContent(String content) {
        json.clear();
        json.string(content);
    }
    ...
}

The printExpr() method generates the code that escapes and appends an expression:

public class JSBuilder {
    ...
    private void printExpr(String expr, boolean escapeXML) {
        if (escapeXML)
            expr = "escapeXML(" + expr + ", " + isHTML + ")";
        out.println(varName + " += " + expr + ";");
    }
    ...
}

The escapeXML() JavaScript function is included in the ajaxUtil.js file.

The build() method parses the template and generates the JavaScript code:

public class JSBuilder {
    public static final String CODE_STARTER = "[%";
    public static final String CODE_ENDER = "%]";
    public static final char EXPR_STARTER = '=';
    public static final char INCL_STARTER = '#';
    ...
    public void build(String template, String varName,
            Writer writer, boolean isHTML) {
        this.varName = varName;
        this.isHTML = isHTML;
        out = new PrintWriter(writer, true);
        out.println("var " + varName + " = \"\";");
        int contentStart = 0;
        int contentEnd = template.indexOf(CODE_STARTER);
        while (contentEnd != -1) {
            String content = template.substring(
                    contentStart, contentEnd);
            printContent(content);
            int codeStart = contentEnd + 2;
            int codeEnd = template.indexOf(CODE_ENDER, codeStart);
            if (codeEnd == -1) {
                codeEnd = template.length();
                template += CODE_ENDER;
            }
            char starter = template.charAt(codeStart);
            if (starter == EXPR_STARTER || starter == INCL_STARTER) {
                codeStart++;
                String expr = template.substring(codeStart, codeEnd);
                printExpr(expr, starter == EXPR_STARTER);
            } else {
                String code = template.substring(codeStart, codeEnd);
                out.println(code);
            }
            contentStart = codeEnd + 2;
            contentEnd = template.indexOf(CODE_STARTER, contentStart);
        }
        String content = template.substring(contentStart);
        printContent(content);
    }

}

With fewer than 100 lines of code, we've built a template parser / code generator, which significantly improves the readability and maintainability of the JavaScript code that dynamically produces HTML or XML on the client-side.

Using the Code Generator in JSP Pages

In order to keep the JSP pages scriptless, the JSBuilder's build() method can be invoked from a custom tag file (named template.tag), which will generate a JavaScript function that returns the generated content. The function's header can be provided via an attribute, and the template can be placed between the start tag and the end tag. Here is the syntax of the custom tag:

<tags:template function="myTemplate(...)">
     ... template ...
</tags:template>
To create the tag file, click File / New, expand Web Tier in the New Gallery window, select the JSP category in the left panel, select JSP Tag File in the right panel, and click OK:

Enter the template.tag file name and click OK:

Then, paste the following source code. The <jsp:doBody> action is used in the tag file to obtain the template�s body, which is stored in a JSP variable. Then, the template is passed to the build() method, which generates the JavaScript code that will produce the content on the client side. Here is the code of the template.tag file:

<%@ attribute name="function" required="true" %>
<%@ attribute name="isHTML" required="false"
    type="java.lang.Boolean" %>
<%@ tag import="ajaxapp.builder.JSBuilder" %>

<script language="javascript">

    function ${function} {
        <jsp:doBody var="templateBody"/>
        <%
            Boolean isHTML
                = (Boolean) jspContext.getAttribute("isHTML");
            if (isHTML == null)
                isHTML = new Boolean(false);
            String templateBody
                = (String) jspContext.getAttribute("templateBody");
            new JSBuilder().build(templateBody, "content",
                jspContext.getOut(), isHTML.booleanValue());
        %>
        return content;
    }
    
</script>

The index.jsp page of the sample application uses the template tag to generate JavaScript functions that produce dynamic HTML content as well as an XML document containing the portfolio�s data. You'll see later in the article how the HTML pieces are inserted into the Web page with innerHTML. The XML document is sent to the server with XMLHttpRequest.

You can use Oracle JDeveloper to insert the tag into the JSP page. Select Local Tag Files: /WEB-INF/tags/ in the Component Palette. Then, drag Template from the palette and drop it on the head tag, which is shown in the Structure Navigator. JDeveloper starts a wizard that lets you enter the required function attribute in the Common Properties tab and the optional isHTML attribute in the Advanced Properties tab. Enter the buildPortfolioDoc() function header, and click Finish:

JDeveloper will insert the <tags:template> tag within the <head> element of the JSP page. After that, you may enter the template�s body. Here is the code that generates JavaScript function, which returns the portfolio XML document:
<tags:template function="buildPortfolioDoc()">
    <portfolio> [%
        var stocks = dataModel.stocks;
        for (var i = 0; i < stocks.length; i++) {
            var stock = stocks[i]; %]
            <stock symbol="[%= stock.symbol %]"
                shares="[%= stock.shares %]"
                paidPrice="[%= stock.paidPrice %]"/> [%
        } %]
    </portfolio>
</tags:template>

The next section shows how to use the custom tag for building the table editor/viewer.

Table Editors and Viewers

There are many cases when a table component must change dynamically in response to the user's actions. For example, a row could be inserted or deleted when the user clicks a button. If the table's content is recreated on the server, the user must wait while the table is reloaded and the scroll position is lost. If the user has just inserted an empty row, he/she must scroll down to find the new line where data must be entered. This is a time-consuming operation that can be avoided, using Ajax and DHTML.

Initializing the User Interface

The application's main page (index.jsp) declares the used tag libraries, JavaScript files, and style sheet:

<%@ taglib prefix="tags" tagdir="/WEB-INF/tags/" %>

<html>
<head>

<link rel="stylesheet" href="style.css" type="text/css">

<script src="ajaxUtil.js"></script>
<script src="ajaxLogic.js"></script>
<script src="dataModel.js"></script>
<script src="dataTable.js"></script>

...
</head>
...
</html>
You can use JDeveloper's editors and wizards to build JSP pages. To insert a JSP directive or a standard action, select JSP in the Component Palette, and then click or drag the desired element:

Click Taglib Directive and select a JSP library that you want to use in the Web page:

You can also use JDeveloper to insert HTML tags in a Web page. Select HTML in the Component Palette, select the Common category, and click Script. Then select the JavaScript file whose functions you want to call in the Web page:

The body of the index.jsp page contains two elements (<span> and <div>) whose content will be set later with the help of innerHTML. The onLoad attribute indicates the function that the browser must call after loading the page:

<body onLoad="init()">

<h1 align="center"><span id="title"></span></h1>

<div id="dataTable">
</div>

</body>

The init() function tries to load the portfolio, using the sendLoadRequest() utility from the ajaxLogic.js file. If the loading succeeds, stocks are stored into the client-side data model and the editing flag is set to false. Otherwise, init() adds three empty rows and sets the editing mode. The requestFeedInfo() function is scheduled with setInterval() to be called every second:

function init() {
    var stocks = sendLoadRequest();
    if (stocks && stocks.length > 0) {
        dataModel.setStocks(stocks);
        addDataChange();
        setEditing(false);
    } else {
        for (var i = 0; i < 3; i++)
            dataModel.insertStock(i);
        addDataChange();
        setEditing(true);
    }
    setInterval("requestFeedInfo()", 1000);
}

Using Cascading Style Sheets

The sample application uses the following CSS file:

BODY { background: #FFFFFF; color: #000000; }
TH { font-weight: bold;
     background-color: #EEF8FF;
     border-top: 1px solid #CCCCCC;
     border-right: 1px solid #CCCCCC;
     border-bottom: 1px solid #CCCCCC; }
TD { font-weight: normal;
     background-color: #FFFFFF;
     border-right: 1px solid #CCCCCC;
     border-bottom: 1px solid #CCCCCC; }
TH.symbol, TD.symbol { text-align: left;
     border-left: 1px solid #CCCCCC; }
TH.number, TD.number { text-align: right; }
TD.button { text-align: center; }
TD.leftButton { text-align: center;
     border-left: 1px solid #CCCCCC; }
BUTTON { background-color: #EEEEEE; }
INPUT.valid { background-color: #FFFFFF; }
INPUT.error { background-color: #FFEEEE; }
SPAN.gain { color: #008000; }
SPAN.loss { color: #FF0000; }
A:link, A:visited { color: #000000;
     text-decoration: underline; }
A:hover, A:active { color: #FF0000;
     text-decoration: underline; }

You can use Oracle JDeveloper to enter the values of the CSS properties:

Creating Tables with JavaScript

The JavaScript code that builds the sample application's table is generated in the index.jsp file with the custom template tag presented in the previous section. Here is the code for building a single cell of the table:

<tags:template function="buildCell(row, column)" isHTML="true"> [%
    var value = "";
    if (!isDynamic(row, column))
        value = dataModel.getValueAt(row, column);
    if (isEditable(row, column)) {
        var id = getInputId(row, column); %]
        <input id="[%= id %]" name="[%= id %]" value="[%= value %]"
            type="text" size="[%= column == 2 ? 6 : 4 %]"
            onKeyUp="validateCellData([%= row %], [%= column %])"
            onChange="dataChanged([%= row %], [%= column %])"> [%
    } else {
        var id = getSpanId(row, column); %]
        <span id="[%= id %]">[%= value %]</span> [%
    } %]
</tags:template>

The dataTable.js file contains the JavaScript functions that are called from the template code. The isDynamic() function returns true if the cell's value is computed using data that is retrieved from the feed:

function isDynamic(row, column) {
    return column >= 3;
}

The isEditable() function returns true if the user can modify the cell's value:

function isEditable(row, column) {
    return isEditing() && column < 3;
}

The getInputId() and getSpanId() functions return the IDs of the <input> and <span> elements of the cells:

function getInputId(row, column) {
    return "input_" + row + "_" + column;
}

function getSpanId(row, column) {
    return "span_" + row + "_" + column;
}

The buttons and links are generated with another template. The buildButton() function accepts three parameters: the button's label, the event handler that must be called when the button (or the link) is clicked, and an optional parameter for the handler function:

<tags:template function="buildButton(label, handler, param)"
         isHTML="true"> [%
    if (!param) param = ""; %]
    <c:if test="${initParam.useButtons}">
        <button onClick="[%# handler %]([%# param %])" type="button">
            [%= label %]
        </button>
    </c:if>
    <c:if test="${!initParam.useButtons}">
        <a href="javascript:[%# handler %]([%# param %])">
            [%= label %]
        </a>
    </c:if>
</tags:template>

The useButtons parameter, which is configured in the web.xml file, determines whether buildButton() generates a button or a link:

<web-app ...>

    <context-param>
        <param-name>useButtons</param-name>
        <param-value>true</param-value>
    </context-param>
    ...
</web-app>

The table's template starts with the header that contains the column names. The buildCell() and buildButton() functions are used to generate the content of the cells. The last row contains additional buttons and the totals:

<tags:template function="buildTable()" isHTML="true">
    <table border=0 cellpadding=5 cellspacing=0 align="center">
        <tr> [%
            var columnCount = dataModel.getColumnCount();
            for (var j = 0; j < columnCount; j++) { %]
                <th class="[%= j == 0 ? "symbol" : "number" %]">
                    [%= dataModel.getColumnName(j) %]
                </th> [%
            }
            if (isEditing()) { %]
                <th class="header" colspan="2"> </th> [%
            } %]
        </tr> [%
        var rowCount = dataModel.getRowCount();
        for (var i = 0; i < rowCount; i++) { %]
            <tr> [%
                for (var j = 0; j < columnCount; j++) { %]
                    <td class="[%= j == 0 ? "symbol" : "number" %]">
                        [%# buildCell(i, j) %]
                    </td> [%
                }
                if (isEditing()) { %]
                    <td class="button">
                        [%# buildButton("Insert", "insertAction", i) %]
                    </td>
                    <td class="button">
                        [%# buildButton("Delete", "deleteAction", i) %]
                    </td> [%
                } %]
            </tr> [%
        } %]
        <tr>
            <td class="leftButton" colspan="3"> [%
                if (isEditing()) { %]
                    [%# buildButton("Save", "saveAction") %] [%
                } else { %]
                    [%# buildButton("Edit", "editAction") %] [%
                } %]
            </td>
            <td class="number">Total:</td>
            <td class="number"><span id="totalValue"></span></td>
            <td class="number"><span id="totalGain"></span></td> [%
            if (isEditing()) { %]
                <td class="button">
                    [%# buildButton("Add", "insertAction", rowCount) %]
                </td>
                <td class="button">
                    [%# buildButton("Undo", "undoAction") %]
                </td> [%
            } %]
        </tr>
    </table>
</tags:template>

The generated buildTable() function is called from updateTableComponent(), whose code can be found in the dataTable.js file:

function updateTableComponent() {
    getElementById("dataTable").innerHTML = buildTable();
    updateDynamicCells();
    requestFeedInfo();
    if (isEditing())
        validateTableData();
}

The getElementById() function calls the document's getElementById(), which returns the DOM object representing the HTML element that has the given ID:

function getElementById(id) {
    return document.getElementById(id);
}

The isEditing() function returns the value of the editing flag, which indicates whether the table component is in editing or viewing mode:

var editing = false;
...
function isEditing() {
    return editing;
}

In addition to setting the editing flag, the setEditing() function modifies the page's title and updates the table component:

function setEditing(value) {
    editing = value;
    var title = "AJAX-based Portfolio "
        + (editing ? "Editor" : "Viewer");
    getElementById("title").innerHTML = title;
    updateTableComponent();
}

Validating Table Editor's Data

Regular expressions provide an easy way to validate data with JavaScript. For example, a share symbol can be validated with the following function, whose code can be found in dataTable.js:

function isValidSymbol(symbol) {
    return symbol && (symbolRE.exec(symbol) == symbol);
}

The sample application uses three regular expressions for validating the user input:

var symbolRE = /[A-Za-z][A-Za-z][A-Za-z][A-Za-z]?/;
var numberRE = /\d+/;
var priceRE = /\d+\.?\d*/;

The validateCellData() function validates the value of a single cell:

function validateCellData(row, column) {
    if (!isEditable(row, column))
        return true;
    var valid = false;
    var inputElem = getElementById(getInputId(row, column));
    var value = inputElem.value;
    if (value) {
        var regexp = null;
        switch (column) {
            case 0: regexp = symbolRE; break;
            case 1: regexp = numberRE; break;
            case 2: regexp = priceRE; break;
        }
        if (regexp != null)
            valid = regexp.exec(value) == value;
    }
    inputElem.className = valid ? "valid" : "error";
    return valid;
}

User errors are signaled with the pink color of the error style:

The validateTableData() function verifies the data of the whole table:

function validateTableData() {
    var allValid = true;
    for (var i = 0; i < dataModel.getRowCount(); i++)
        for (var j = 0; j < dataModel.getColumnCount(); j++) {
            var valid = validateCellData(i, j);
            allValid = allValid && valid;
        }
    return allValid;
}

Data should be revalidated on the server for security reasons. The sample application ( download) uses the portfolio.xsd schema for this operation.

Refreshing Table Viewer's Data

In a JavaScript application, data can be formatted using custom utilities, such as the formatPrice() function, which returns a string representation of a numeric price:

function formatPrice(value) {
    var n = new Number(value);
    if (isNaN(n) || n == 0)
        return " ";
    return n.toFixed(2);
}

The updateDynamicCells() function updates all dynamic cells of the table. After locating the <span> element of each dynamic cell, the content is changed, using the innerHTML property of the <span> element. If the cell represents a gain or a loss, its style can be changed too, using the className property of the same <span> element:

function updateDynamicCells() {
    for (var i = 0; i < dataModel.getRowCount(); i++) {
        for (var j = 0; j < dataModel.getColumnCount(); j++) {
            if (isDynamic(i, j)) {
                var value = dataModel.getValueAt(i, j);
                var spanElem = getElementById(getSpanId(i, j));
                spanElem.innerHTML = formatPrice(value);
                if (j == 5)
                    spanElem.className = value >= 0 ? "gain" : "loss";
            }
        }
    }
    var totalValue = dataModel.getTotalValue();
    var totalValueElem = getElementById("totalValue")
    totalValueElem.innerHTML = formatPrice(totalValue);
    var totalGain = dataModel.getTotalGain();
    var totalGainElem = getElementById("totalGain")
    totalGainElem.innerHTML = formatPrice(totalGain);
    totalGainElem.className = totalGain >= 0 ? "gain" : "loss";
}

Using Event Handlers

The dataTable.js file contains the code that handles the UI events. Every time the user changes the value of a cell, the browser calls the dataChanged() function, whose call is coded in value of the onChange attribute of each <input> element. The dataChanged() function validates the cell's data, stores the new value in the data model, updates the dynamic cells, and requests the feed information just in case the user entered a new symbol:

function dataChanged(row, column) {
    validateCellData(row, column);
    var inputElem = getElementById(getInputId(row, column));
    dataModel.setValueAt(inputElem.value, row, column);
    addDataChange();
    updateDynamicCells();
    requestFeedInfo();
}

The addDataChange() function saves the stocks into the dataChanges array:

var dataChanges = new Array();
...    
function addDataChange() {
    dataChanges[dataChanges.length]
        = dataModel.getStocks();
}

The undoAction() function is called each time the user clicks the Undo button. The previous stocks are retrieved from the dataChanges array. Then, the data model and the table are updated:

function undoAction() {
    if (dataChanges.length >= 2) {
        var stocks = dataChanges[dataChanges.length-2];
        dataChanges.length--;
        dataModel.setStocks(stocks);
        updateTableComponent();
    }
}

The insertAction() function inserts a new row:

function insertAction(row) {
    dataModel.insertStock(row);
    addDataChange();
    updateTableComponent();
}

The deleteAction() function deletes a row:

function deleteAction(row) {
    dataModel.deleteStock(row);
    addDataChange();
    updateTableComponent();
}

The saveAction() function sends the portfolio's data to the server, using the buildPortfolioDoc() function that is generated in index.jsp and the sendSaveRequest() function whose code can be found in ajaxLogic.js. The portfolio is saved only if the entire table's data is valid:

function saveAction() {
    if (validateTableData()) {
        sendSaveRequest(buildPortfolioDoc());
        setEditing(false);
    } else {
        alert("Please provide valid values for the pink fields.");
    }
}

The editAction() function just calls setEditing(true):

function editAction() {
    setEditing(true);
}

Summary

In this article, you've learned how to build Swing-like table models and implement data feeds that are accessed with the help of Ajax. Then, you've seen how to use a JSP-like syntax for generating HTML and XML with JavaScript. Finally, you've found out how to build table components, validate and format their data with JavaScript, define their appearance with CSS, handle events, and undo the data changes.


Andrei Cioroianu (devtools@devsphere.com) is the founder of Devsphere (www.devsphere.com), a provider of development, integration, and consulting services. Cioroianu has written many Java articles published by Oracle Technology Network, ONJava (www.onjava.com), JavaWorld (www.javaworld.com), and Java Developer's Journal. He also coauthored the books Java XML Programmer's Reference and Professional Java XML (both from Wrox Press).