Inline Cell Editing for the TableListJS HTML5 Component


In earlier posts, A Scrollable, Selectable HTML Table with JavaScript and CSS, and Adding Searching to the TableListJS HTML5 Component, I introduced an attractive HTML5 list component that uses CSS and JavaScript to create a customized, scrollable and selectable multi-column list (see Figure 1 below for an overview). In the second article, I added searching and filtering to the list to help find entries that match text entered—something particularly useful when scrolling through a list with many entries.

Figure 1 – The searchable, scrollable multi-column TableListJS HTML5 component

In this installment, I’ll add inline editing so you can edit the contents of the list by double-clicking on a row, or hitting the Enter key. You can also navigate the list with the keyboard, using the up and down arrow keys, or the tab key when editing text in a row with multiple columns. Let’s dive in.

Improved List Creation

Previously, the sample HTML pages that illustrated how to use the TableListJS component contained embedded JavaScript code to add rows to the list. It was a little tedious, in that it assumed (and hard-coded) how many columns existed in the table, and the code was mixed with the HTML. To make this more elegant and extensible, I created a function called addRow() that accepts an array of strings. Each string is placed in a matching column of the table. It also uses each column’s matching header entry to set the column width to match.

For example, the table HTML defined in Listing 1 defines a table with a header that has three columns, each with a width attribute.

Column 1 Column 2 Column 3

Listing 1 – HTML to add a table with three header columns, with widths specified

The addRow() function introspects the columns and creates a row with matching column widths automatically. The result is a nicely formatted table that matches the widths you specify once in the table header. The JavaScript code that handles this is shown in Listing 2, below.

function addRow(colText) {
    var tableHeader = document.getElementsByTagName('thead').item(0);
    var headerRow = tableHeader.childNodes[1];

    // Create a new row
    var tbl = document.getElementById('entries');
    var row = tbl.insertRow(tbl.rows.length); = document.getElementById("entries").rows.length - 1;

    // Go through the array of column text fields and add them to the new row
    for ( c = 0; c < colText.length; c++ ) {
        textNode = document.createTextNode(colText[c]);
        // It's assumed a column header exists for each entry in the array
        cell = document.createElement("td");
        // Set the new cell width equal the matching header column width
        cell.width = headerRow.cells[c].width;



Listing 2 - The addRow() function inserts a new row in the HTML table, and sets column widths.
The DOM is traversed to locate the table header row, and the child nodes that make up that row (the

elements, or columns) are stored in the variable headerRow. Next, a call to insertRow() adds a new row to the table, and the row is given a unique ID for manipulation later. Each text element in the given array is then added to the new row, each as a new column referred to as the variable cell. Each column’s width is set to match that of its matching header row column.

Making Each Row Editable

Making a row editable, triggered either by a mouse double-click or by selecting the row and hitting the Enter key, is accomplished by replacing each row’s column text elements with HTML input elements, as shown in Listing 3.

function toggleRowEditMode(row) {
  // For each column, replace the text with an input element
  for ( c = 0; c < row.childNodes.length; c++ ) {
    cell = row.childNodes[c];
    text = cell.innerText;

    // Create an input node, set its text to the cell's text,
    // and set its width to the column width
    var input = document.createElement("input");
    input.setAttribute('type', 'text');
    input.setAttribute('style', 'width:100%');
    input.setAttribute('tabindex', ''+ (c+1) );
    input.setAttribute('id', 'cell'+c);
    input.setAttribute('value', text);
    input.onkeydown = function(event) {
        // Capture the Enter key and clear this row's edit mode
        // as though the user clicked somewhere else
        if ( event.code === 'Enter' && row === editRow) {

            // The key event will be handled by the document also,
            // so flag it to be ignored 
            ignoreKeyEvent = true;

    // Replace the cell's text node with the input node
    if ( cell.childNodes.length > 0 ) {

    // Set the focus to the first column's input node
    if ( c === 0 ) {
  Return true;

Listing 3 - Replacing a row’s text elements with HTML input elements to accept human input.

First, a loop traverses each of the columns within the selected row. The text for each column is extracted, an input node is created, and the node is seeded with the existing text. Additional attributes are set as well, including the width, tab index (for keyboard navigation), an ID, and a key event handler. This event handler captures the Enter key from within each cell. When Enter is pressed, the handler places the row back into “normal” mode, where it replaces each of the input elements with text elements to restore the row to normal display. Finally, keyboard focus is set for the input element within the first column in the row (shown in Listing 4).

function clearRowEditMode(currentRow) {
    if ( editRow !== null && editRow !== currentRow ) {
        // Change the row cells' text to what's in the edit boxes
        for ( c = 0; c < editRow.childNodes.length; c++ ) {
            cell = editRow.childNodes[c];
            text = cell.childNodes[0].value;
            cell.innerHTML = text;
        editRow = null;

Listing 4 - To revert from edit mode to normal mode, clearRowEditMode() is called

In summary, to revert back to normal mode, each column’s text is extracted and simply set as the column’s innerHTML entry. Only one row may be edited at a time, and is tracked with the editRow variable. Figure 2 shows the list with a row in edit mode.

Figure 2 - A TableListJS list with a row that’s in edit mode.

Keyboard and Mouse Handling

The next steps involve enabling navigation and editing of rows with the mouse. Keyboard handlers are added at the DOM Document level, and look for arrow keys for navigation up and down (changing the selected row as the user keys through), and the Enter key to toggle the selected row’s edit state. The document.body.onkeydown function handler is set to call the function, moveSelectedRow(), see Listing 5.

function moveSelectedRow(event) {
    // In order to move the selection, there needs to be a selected row
    if ( selected === null ) { return; }

    var rows = document.getElementById("entries").rows;
    var r =;

    if ( event.code === 'ArrowDown' ) {
        // Make sure we're not at the bottom of the list
        if ( >= rows.length-1 ) { return; }
    else if ( event.code === 'ArrowUp' ) {
        // Make sure we're not at the top of the list
        if ( <= 1 ) { return; }
    else {
        return; // Not an arrow key

    // Select the next or prev row, and make sure it's in view
    rowToSelect = rows[r];
    var tableBody = document.getElementsByTagName("tbody").item(0);
    var vis = isElementInViewport(tableBody, rowToSelect);
    if ( vis ) {
        // Eat the key by returning false, otherwise allow the 
        // key to scroll the list
        return false;

Listing 5 - The document.body.onkeydown handler to navigate via arrow keys.

First, some basic checks are done to ensure there’s already a selected row, one of the two arrow keys was pressed, and that the user cannot scroll past the top or bottom of the list. Otherwise the currently selected row’s ID is extracted and then incremented or decremented depending on the arrow key pressed, and the new row is retrieved by ID. Since the ID is the row’s index in the array of rows in the table, the newly selected row is easy to retrieve.

Next, a check is made that the row is visible. (Remember that the table is scrollable and might contain more rows than can be displayed.) This is done in the function isElementInViewport(), where the child row’s rectangle coordinates are compared to the parent table’s bounding rectangle coordinates. If the newly selected row is not currently visible, which will occur when the user navigates beyond the first or last row displayed, the function will return from the onkeydown handler without returning a value. This will cause the normal keyboard handling to occur, which scrolls the list by default, and selects it (due to CSS-defined handling of rows marked as selected—see previous article here). If the row is already visible, the function returns false, effectively intercepting normal keyboard handling, and the newly selected row will just be highlighted.

A row’s edit state can be toggled by either selecting a row and pressing the Enter key, or double-clicking the mouse on a row not already in edit mode. A row.ondoubleclick handler is added for each row in the addRow() function (shown earlier in Listing 3). This function is also triggered if the user hits the Enter key when a row is already selected. All of the keyboard and mouse handlers, which are defined within the addRow() function, are shown in Listing 6.

function addRow(colText) {

    document.body.onkeydown = function(event) {
        // look for arrow keys to move selection
        return moveSelectedRow(event);
    document.body.onkeyup = function(event) {
        // look for enter key to toggle row edit mode
        return handleToggleKey(event);

    row.ondblclick = function() {
        return toggleRowEditMode(row);

    // Clicking on another row when this row is in edit mode clears edit mode
    row.onclick = function() {

Listing 6 - The keyboard and mouse handlers in the addRow() function.

Next, let’s take a look at how to use a database to load the list, and then persist updates after a user edits at least one row.

Using JSP and JavaDB to Populate the Table List

With the TableListJS HTML5 component able to handle user selection and edit activity, we’ll combine the JavaScript code explored so far with some JavaServer Page (JSP) code to communicate with a database. In this case, we’ll use JavaDB since it comes with the JDK, with a sample database included with it, and it’s easy to use. Additionally, the sample code and NetBeans project (available here) uses Glassfish, which comes with the Java EE SDK.

First, the HTML file that we’ve been using to demonstrate the TableListJS component—index.html—is converted to index.jsp, and some Java imports are added to the top of the file (see Listing 7).

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page language="java" import="java.lang.*,java.util.*,java.sql.*"%>


        JSP Page

Listing 7 - The JavaServer Page header information

Global data objects are declared with special tags towards the top of the file as well, as shown in Listing 8. These variables are available to all of the code within the JSP file at any time.

    class Customer {
        int id;
        String name;
        String address;
        String state;
        String city;
        String phone;
        String email;
    Vector customers = null;
    Vector edits = null;
    String driver = "org.apache.derby.jdbc.EmbeddedDriver";
    String url = "jdbc:derby://localhost:1527/sample";
    String usr = "app";
    String pwd = "app";
    Connection con = null;
    final Object lock = new Object(); 

Listing 8 - Global Java variables declared within a JSP file.

Java code that executes as part of the HTML/JSP page is contained within <% %> tags, and executed snippets used to fill in parts of the HTML dynamically are placed within <%= %> tags. For example, take the HTML below (as part of a JSP):

The customer’s name is "<%= %>"

...will be dynamically updated to include the customer’s name:

The customer’s name is Fixate

There are other ways to code JavaServer Pages, such as with custom tag libraries, but I often prefer this old-school approach where all the code is inline with HTML as part of the JSP.

In this example, when the page is first loaded (and optionally when specific request parameters are included as part of the URL), the database connection code loads the sample data in preparation for loading the TableListJS HTML5 table (see Listing 9).

    String action = request.getParameter("action");
    if ( action == null || 
         action.equalsIgnoreCase("null") ||
         action.equalsIgnoreCase("updaterow") ) {
        try {
            if ( con == null ) {
                System.setProperty("derby.database.forceDatabaseLock", "true");
                Class.forName( driver ).newInstance();
                con = DriverManager.getConnection( url, usr, pwd );
        catch ( Exception e ) {
            // Database connect failed because it probably doesn't exist

        if ( con != null ) {
            System.out.println("Database connected");
            if ( customers == null ) {
                customers = new Vector();
                // Get the database rows from the CUSTOMER table 
                // sorted by name and add each one as a row in
                // the TableListJS HTML5 list
                String req = "SELECT * FROM APP.CUSTOMER ORDER BY NAME"; 

                PreparedStatement ps = con.prepareStatement(req);
                ResultSet rs = ps.executeQuery();
                while ( ) {
                    Customer customer = new Customer();
           = rs.getInt("CUSTOMER_ID");
           = rs.getString("NAME");
                    customer.address = rs.getString("ADDRESSLINE1");
           = rs.getString("CITY");
                    customer.state = rs.getString("STATE");
           = rs.getString("PHONE");
           = rs.getString("EMAIL");
        else {
            System.out.println("Database NOT connected");

Listing 9 - Database code as part of the JavaServer Page to load sample data

The TableListJS component is loaded the same as in previous examples, except this time we use the data loaded from the database, as shown in Listing 10.

<% if ( customers != null ) { for ( int i = 0; i < customers.size(); i++ ) { Customer customer = customers.elementAt(i); %> <% } } %>

Listing 10 - A mix of JSP and JavaScript code to load the TableListJS component

An array is created per database row, which in turn becomes a row in the TableListJS component. The result is an interactive, attractive list loaded with real data from a database, as shown in Figure 2 below.

Figure 3 - The TableListJS component loaded with real data from a database.

The code in Listing 10 is interesting because it mixes three paradigms: HTML, JavaScript, and Java as part of a JSP. But it’s important to remember where each set of code gets executed. For example, JavaScript and HTML get executed at the client, whereas the JSP code shown inline with it will be executed at the server, prior to the page being delivered to and loaded by the browser.

This can be confusing at times, but to help enable the interaction, I find it useful to use a mix of session variables and URL request parameters. Be careful to take security into consideration since these parameters are human readable as part of the URL. Just as an example, when a table row is edited, I’ve added a JavaScript event handler so that the page can trigger a database update. Since the notification is executed in the browser, and the database is updated with code on the server, the data is extracted at the client and the browser is redirected to a URL containing this data, as shown in Listing 11.

Listing 11 - When the JavaScript notification is executed, JSP code is triggered via a browser update with URL request parameters.

Note that there certainly are more robust and secure mechanisms that can be used here, but they’re out of scope for this article.

As each row is edited, the updated Customer records are placed in a collection to be applied to the database once the user submits them via the Next button. Once clicked, the JSP code in Listing 12 applies the changes.

    // Check for edits and update the database

    // First, get the selected row and just assume it was edited
    String index = request.getParameter("rowEntries");
    if ( index != null ) {
        Customer customer = null;
        if ( customers != null ) {
            customer = customers.elementAt(Integer.valueOf(index));
        // Add the selected entry and add it to the list of edited entries
        if ( edits == null ) {
            edits = new Vector<>();
    // Go through all of the edited entries and update the database
    if ( con != null ) {
        String req = "UPDATE APP.CUSTOMER SET" +
                     " NAME=?, " +
                     " ADDRESSLINE1=?, " +
                     " CITY=?, " +
                     " STATE=?, " +
                     " PHONE=?, " +
                     " EMAIL=?" +
                     " WHERE CUSTOMER_ID=?";

        Iterator iter = edits.iterator();
        while ( iter.hasNext() ) {
            Customer custEdit =;
            PreparedStatement ps = con.prepareStatement(req);
            ps.setString(2, custEdit.address);
            ps.setString(4, custEdit.state);

Listing 12 - Updating the database with the edited Customer data.

Although the code to load the table was shown in Listing 10 above, there was one important snippet that was left out. In order to make the table a submittable component as part of an HTML form tage,a hidden HTML select component is included (see Listing 13).

Searchable TableListJS By Eric Bruno


Listing 13 - The hidden

Customer Address City State Phone Email

Additional JavaScript (shown in Listing 14) is added to the toggleRowSelection() function to take clicks within the table and echo them in the component visible, you can see this in action. As a result, when you select a row in the TableListJS component and submit it as part of the form (via the Next button in this example), the replicated selection in the