Thursday, June 19, 2008

JSF - Adding Input Rows dynamically to DataTable using JavaScript



This article is about adding a new input row(s) to html table generated by "h:dataTable". This article assumes a working knowledge of JSF and does not cover anything related to configuration. For the complete set of source files mail to kalai.selvant@gmail.com.

There is couple of solutions available for the above problem:

  • Maintaining the list (bound to the “h:dataTable”) in the session. To add a new row to the table, simple add a new element to the list and re-render the “h:dataTable” using AJAX or something equivalent. The drawback of this approach is we need to maintain the entire list in the session scope, which might make session a bit heavy. And also a strategy might be needed to remove the list from the session once it is not in use.

  • Creating a list having size equal to the number of input rows in the html table rendered by “h:dataTable” tag during the APPLY_REQUEST_VALUES phase.


This article is mainly focused on the 2nd approach.

Software Requirements:

Sample JSF Page (Refer the above image):
The sample JSF page has two “h:dataTable” tag – First one accepts input from the user and the 2nd one displays the values entered by user using AJAX. The "Add A Row" button adds a new input row using JavaScript and the "Process" button displays the values (entered in the input fields) in the "Values From Dynamically Added Fields" section. In the above image rows circled in red color are the input rows added using JavaScript.

Key source files: (It's not possible to copy-paste all the files, hence only the important source files are given below. For other files like faces-config.xml, NameBean.java etc. send me a mail at kalai.selvant@gmail.com)

  • dynamic-inputfld-without-session.xhtml -
    <?xml version='1.0' encoding='UTF-8' ?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:a4j="http://richfaces.org/a4j">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Dynamic Input fields Sample</title>
    <script language="JavaScript">
    function addARow() {
    var cell;
    var table = document.getElementById("x:dl");
    var rowObj = table.tBodies[0].lastChild.cloneNode(true);
    table.tBodies[0].appendChild(rowObj);
    rowObj.cells[0].getElementsByTagName("INPUT")[0].id = "x:dl:" + (table.rows.length - 2) + ":firstName";
    rowObj.cells[0].getElementsByTagName("INPUT")[0].name =
    rowObj.cells[1].getElementsByTagName("INPUT")[0].id = "x:dl:" + (table.rows.length - 2) + ":lastName";
    rowObj.cells[1].getElementsByTagName("INPUT")[0].name = rowObj.cells[1].getElementsByTagName("INPUT")[0].id;
    return false;
    }

    function updateRowCount() {
    document.getElementById("x:rowCount").value = document.getElementById("x:dl").rows.length - 1;
    }
    </script>
    </head>
    <body>
    <f:view>
    <h:form id="x">
    <b>Dynamic Input Field Example Without Session:</b> <br />
    Dynamic Input Fields:
    <h:dataTable id="dl" border="1" value="#{dynamicInputFieldController.nameList}" var="obj">
    <h:column>
    <f:facet name="header">
    <h:outputText value="First Name" />
    </f:facet>
    <h:inputText id="firstName" value="#{obj.firstName}" />
    </h:column>
    <h:column>
    <f:facet name="header">
    <h:outputText value="Last Name" />
    </f:facet>
    <h:inputText id="lastName" value="#{obj.lastName}" />
    </h:column>
    </h:dataTable><br />

    <h:commandButton value="Add A Row" onclick="addARow();return false" /><br /><br />
    Values From Dynamically Added Fields: <br />
    <h:dataTable border="1" value="#{dynamicInputFieldController.nameList}" var="name">
    <h:column>
    <f:facet name="header">
    <h:outputText value="First Name" />
    </f:facet>
    <h:outputText value="#{name.firstName}" />
    </h:column>
    <h:column>
    <f:facet name="header">
    <h:outputText value="Last Name" />
    </f:facet>
    <h:outputText value="#{name.lastName}" />
    </h:column>
    </h:dataTable><br />
    <h:inputHidden size="3" id="rowCount" value="#{dynamicInputFieldController.rowCount}" /><br />
    <h:commandButton value="Process" onclick="updateRowCount();"
    action="#{dynamicInputFieldController.process}" />
    </h:form>
    </f:view>
    </body>
    </html>

  • DynamicInputFieldController.java -
    package controller;

    import java.util.ArrayList;
    import java.util.List;

    import javax.faces.context.FacesContext;

    public class DynamicInputFieldController {

    private List nameList;

    private int rowCount;

    /**
    * @return the nameList
    */
    public List getNameList() {

    if (nameList == null) {
    String num = FacesContext.getCurrentInstance().getExternalContext()
    .getRequestParameterMap().get("x:rowCount");
    int len = 1;
    if (num != null && !num.trim().equals("")) {
    len = Integer.parseInt(num);
    }
    nameList = new ArrayList();
    for (int i = 0; i <> nameList) {
    this.nameList = nameList;
    }

    public String process() {
    System.out.println("Have your business logic here...");
    return null;
    }

    /**
    * @return the rowCount
    */
    public int getRowCount() {
    return rowCount;
    }

    /**
    * @param rowCount the rowCount to set
    */
    public void setRowCount(int rowCount) {
    this.rowCount = rowCount;
    }
    }

  • NameBean.java -
    package beans;

    import java.io.Serializable;

    public class NameBean implements Serializable {

    /**
    *
    */
    private static final long serialVersionUID = 908185190638335125L;

    private String firstName;
    private String middleName;
    private String lastName;

    public NameBean(String firstName, String middleName, String lastName) {
    super();
    this.firstName = firstName;
    this.middleName = middleName;
    this.lastName = lastName;
    }

    public NameBean() {
    super();
    }

    /**
    * @return the firstName
    */
    public String getFirstName() {
    return firstName;
    }

    /**
    * @return the middleName
    */
    public String getMiddleName() {
    return middleName;
    }

    /**
    * @return the lastName
    */
    public String getLastName() {
    return lastName;
    }

    /**
    * @param firstName the firstName to set
    */
    public void setFirstName(String firstName) {
    this.firstName = firstName;
    }

    /**
    * @param middleName the middleName to set
    */
    public void setMiddleName(String middleName) {
    this.middleName = middleName;
    }

    /**
    * @param lastName the lastName to set
    */
    public void setLastName(String lastName) {
    this.lastName = lastName;
    }
    }


Code Walkthrough:

  • dynamic-inputfld-without-session.xhtml – The key thing to note in this file is the “addARow()” function and the “rowCount” hidden field:

    • addARow()” –This function adds a new input row using JavaScript and changes the index part of the id/name of the input elements. The pattern of id/name will be "<id of the form>:<id of the datatable>:<index of the list>:<input field id>". If you are using tag other than the "h:dataTable", check for the id/name pattern and change accordingly.

    • rowCount” – This hidden field is used to store the number of input rows in the input table. And this number will be used during the APPLY_REQUEST_VALUES phase to create a list having that many dummy elements.

  • DynamicInputFieldController.java – Only interesting method is the “getNameList”, this method gets invoked during the APPLY_REQUEST_VALUES phase to set the values from the input fields into the elements of the list. When this method gets invoked first time, we can initialize the list to have number of elements equal to the value of “rowCount”, rest will be taken care by JSF framework itself.

  • NameBean.java - A simple java bean to encapsulate first, middle and last name.



There are other approaches like adding the input elements using JavaScript and accessing their value from request map or using "UIViewRoot" is available. But the above one seems more appropriate, Let me know in case of any difference in opinion as I am quite new to JSF.

2 comments:

Unknown said...

It seems that the getNameList() method has something wrong. After new a list, I think the for loop is incorrect syntax. Please take a look.

Pluto said...

Hi! How would you to in case you want to pre-pone the line?
So not add at the end but at the beginning?
Thank you very much!