Empire-db and Struts2 » Rendering and parsing Download

Input controls part 1 – Forms and controls

For database driven Web applications one of the most common tasks is displaying a form for adding new and modifying existing records. To demonstrate how much safer, cleaner and more efficient you can work using the Empire-Struts2-Extensions we compare it to standard Struts2.

Let’s first look at the attributes that are required to define a form input control:

  1. The type of input (text, selection list, checkbox)
  2. The maximum input length for text fields
  3. The list of possible values for selection lists
  4. Whether or not the field is mandatory
  5. Whether or not the field value is editable or read only

While points 1 to 4 are usually defined through static attributes of a particular field, the field’s accessibility (point 5) is more likely to depend on the context, such as the record’s state or user credentials – and thus has more complex requirements. This point is further discussed in the section Implementing field level access logic.

The definition of a full featured input control with standard Struts2 tags looks somewhat like this:

<s:textfield key="employee.lastname" 
             size="40" 
             required="true" 
             disabled="!hasAccess('employee.lastname')" />

With Empire-Struts2-Extensions you’d simply write:

<e:control column="<%= EMPLOYEES.LASTNAME %>" />

The same applies to a field that is displayed as a selection list. With traditional Struts2 tags it may look like this:

<s:select key="employee.gender"
          list="genders" listKey="key" listValue="value"
          required="true"
          disabled="!hasAccess('employee.department')" />

Again with Empire-Struts2-Extensions the code is simply:

<e:control column="<%= EMPLOYEES.GENDER %>" />

All the information necessary to set all input control attributes is provided by the meta information supplied by the record and column objects. The following code shows the corresponding column definition for the two fields with extended view specific metadata:

public static class Employees extends DBTable
{
    public final DBTableColumn LASTNAME;
    public final DBTableColumn GENDER;
    
    public Employees(DBDatabase db)
    {
        super("EMPLOYEES", db);
        // Add columns
        LASTNAME = addColumn("LASTNAME", DataType.TEXT, 40, true);
        GENDER   = addColumn("GENDER",   DataType.TEXT,  1, false);
        // Set control types
        LASTNAME.setControlType("text");
        GENDER.setControlType("select");
        // Set gender options
        Options genders = new Options();
        genders.set("M", "Male");
        genders.set("F", "Female");
        GENDER.setOptions(genders);
    }
}

As you can see, we have added the control type and possible gender options here for our two columns. For simplicity we have put this together with the database schema definition, but you may as well separate it and add the data in a different class or method.

Form definition – the whole story

Summing up, with traditional Struts2 syntax the entire form to display an employee record may look like this:

<s:form action="employeeDetail!doSave">
    <s:textfield key="employee.employeeId" disabled="true"/>
    <s:textfield key="employee.firstname" size="40" required="true" disabled="employee.retired" />
    <s:textfield key="employee.lastname" size="40" required="true" disabled="employee.retired" />
    <s:select key="employee.gender" list="#{'M':'Male', 'F':'Female'}" disabled="employee.retired" />
    <s:textfield key="employee.dateOfBirth" size="10" disabled="employee.retired" />
    <tr>
      <td class="tdLabel">
         <s:label value="%{getText('employee.phoneNumber')}" theme="simple" />:</td>
      <td>
         <s:textfield key="employee.phoneNumber_cou" size="4" 
                      maxlength="4" disabled="employee.retired" 
                      theme="simple" />
         <s:textfield key="employee.phoneNumber_cit" size="8" 
                      maxlength="8" disabled="employee.retired" 
                      theme="simple" />-
         <s:textfield key="employee.phoneNumber_ext" size="20" 
                      maxlength="20" disabled="employee.retired" 
                      theme="simple" />
        </td>
    </tr>
    <s:select key="employee.department.departmentId" list="departments" 
              listKey="departmentId" listValue="name" required="true" 
              disabled="employee.retired" />
    <tr>
        <td class="tdLabel">
            <s:label value="%{getText('employee.retired')}" 
                     theme="simple" /> :</td>
        <td><s:checkbox key="employee.retired" theme="simple" /></td>
    </tr>
</s:form>

Whereas for exactly the same form with Empire-Struts2-Extensions the code simply looks like this:

<e:form record="<%= action.getEmployee() %>" action="!doSave">
    <e:control column="<%= EMPLOYEES.EMPLOYEE_ID %>" />
    <e:control column="<%= EMPLOYEES.FIRSTNAME     %>" />
    <e:control column="<%= EMPLOYEES.LASTNAME %>" />
    <e:control column="<%= EMPLOYEES.GENDER %>" />
    <e:control column="<%= EMPLOYEES.DATE_OF_BIRTH %>" />
    <e:control column="<%= EMPLOYEES.PHONE_NUMBER %>" />
    <e:control column="<%= EMPLOYEES.DEPARTMENT_ID %>" />
    <e:control column="<%= EMPLOYEES.RETIRED %>" />
</e:form>

Implementing field level access logic

By field level access logic we understand a function that determines whether a particular field's value is editable by the user or read only. If it's read only, the disabled attribute of the corresponding input control needs to be set to true. In the above Struts2 form example we have implemented this logic - the fact that a field should be read only if the employee is retired - in the view. This was solely for convenience since actually it does not really belong there.

Consider for example what we additionally need to check user credentials, then all the logic in the JSP becomes even more complex and hardly maintainable. But what other options do we have?

A more sensible approach is to call a function on our bean or action class that returns whether or not the field is disabled – like we have done further above by calling a function named hasAccess(). However this function needs to know which field we want to check for accessibility and what other option do we have as again providing the field property name as a string literal. And what makes this even more tedious and error prone is that if we want to do it properly we must not forget to call this function for every single input field in our entire application.

This is a good example why separation of view and logic is hard to achieve and not properly addressed by most current Web application frameworks.

With Empire-Struts2-Extensions all this is so much simpler and safer. As you can easily see, all you have to provide in your JSPs is a reference to the record and column objects. The record reference may easily be set for the entire form so for the individual controls only the column reference needs to be provided. The same field access logic that has caused us so much trouble with the traditional approach is now easily implemented where it belongs: in the model with the EmployeeRecord.

public class EmployeeRecord extends DBRecord
{
    public static final SampleDB.Employees T = 
        SampleDB.getInstance().T_EMPLOYEES;  
    
    @Override
    public boolean isFieldReadOnly(DBColumn column)
    {
        if (super.isFieldReadOnly(column))
            return true;
        // Check if Employee has retired
        if (column!=T.C_RETIRED && getBoolean(T.C_RETIRED))
            return true;
        // No, field is acessible
        return false;
    }
}

Note that this is really all the code you need. There are absolutely no changes to the JSPs necessary. And now you may easily extend this code additionally checking user credentials if required.

With this approach separation of view and logic is so much cleaner and thus simpler to maintain and to extend. This has all been achieved by utilizing Empire-db’s metadata capabilities. Without static and context specific metadata such a procedure is hardly possible and probably the reason why you have not seen anything like this with other solutions.