Dashboard > RIFE > ... > Tips and Tricks > Sortable table example
RIFE Log In | Sign Up   View a printable version of the current page.
Sortable table example


Added by Frederic Daoud, last edited by Geert Bevin on Jul 31, 2006  (view change)
Labels: 
(None)

Using an embedded element to implement a sortable table

Frederic Daoud

Note

Running the example

The complete source code for this example is included in the attachments. To reduce the file size, the RIFE jar is not included. You must add a RIFE-1.5 for JDK 1.5 jar in the lib/ directory. Then, run ant build to build the war file. After deploying, use the path /tablesort/welcome (relative to your base server URL) to view the example.

Overview

The following is an example of defining a RIFE embedded element to implement an HTML table that has links in its header cells that allow the user to sort by the selected column, either in ascending or descending order.

Here is a screenshot of the initial page:

Note that there are two tables, initially unsorted.

After clicking on the header Last name in the first table:

Now the first table is sorted by last name, in ascending order. Thus the [^] symbol.

Now, after clicking the URL header twice, in the second table:

Now the second table is sorted by URL, in descending order ([V]). Note that the first table did not lose its previous sorting; it is still sorted by last name.

The Model

Representing the table data

We can represent the table data in any way we wish; for this simple example, I created a TableData class which uses a List of String s for the headers and a List of List of String s for the rows and columns of data.

Using a Comparator to sort the data

TableRowComparator is a simple Comparator that sorts the table data according to the index of the column on which to sort, and the boolean flag indicating whether to sort in ascending order or descending order. Here is an example:

List<List<String>> rows = tableData.getRowData();
Collections.sort(rowData, new TableRowComparator(1, true));

This would sort the rows according to the column at index 1 (the second column), in ascending order.

Defining the properties of the embedded element

To implement such a table as an embedded element, we start with a regular Element with properties for the index of the column on which to sort, and a boolean flag that indicates whether to sort in ascending order (true) or descending order (false):

TableElement.java
// BaseElement contains a property to get and set the Template
public class TableElement extends BaseElement
{
    private int m_sortColumn = -1;

    public int getSortColumn() { return m_sortColumn; }
    public void setSortColumn(int p_sortColumn) { m_sortColumn = p_sortColumn; }

    private boolean m_sortAscending = true;

    public boolean isSortAscending() { return m_sortAscending; }
    public void setSortAscending(boolean p_sortAscending) { m_sortAscending = p_sortAscending; }
    //....
}

Configuring the embedded element

To configure TableElement as a stateful embedded element, we must define the above properties as inputs and outputs, and wire them together. This is called creating reflexive datalinks.

Here is the configuration:

site.xml
<site>
  <!-- embedded table element -->
  <element id="Table" implementation="info.javelot.rife.components.Table">
    <property name="template">
      <template>table</template>
    </property>

    <input  name="sortColumn"/>
    <output name="sortColumn"/>

    <input  name="sortAscending"/>
    <output name="sortAscending"/>

    <!-- reflexive datalinks -->
    <datalink srcoutput="sortColumn"    destinput="sortColumn"    destid="Table"/>
    <datalink srcoutput="sortAscending" destinput="sortAscending" destid="Table"/>

    <!-- submission -->
    <submission name="sort">
      <param name="submitSortColumn"/>
      <param name="submitSortAscending"/>
    </submission>
  </element>
</site>

First, the template is injected. Then, the two properties of the element, the sort column and ascending flag, are defined both as inputs and outputs, and are wired as reflexive datalinks. Finally, a Submission is declared, to contain the submitted sort column and ascending flag. When these values are submitted, they will replace the previous values. When these values are not submitted, the last used values are kept by the reflexive datalinks.

The result is a stateful embedded element, as represented below:

The parent element template

Let's continue by defining the parent element, here called WelcomeElement, which will use the embedded table element to display two tables.

The template for the welcome element includes tags for two instances of our embedded table element:

welcome.html
<p>
Here is a table:
</p>
<r:v name="ELEMENT:+Table:1"/>

<p>
Here is another table:
</p>
<r:v name="ELEMENT:+Table:2"/>

First, the name has the ELEMENT: prefix. Then, notice the '+' sign, which indicates to render the embedded element only when the template is printed, as opposed to as soon as the template is instantiated. This is followed by the name of the element, Table, which must match the id of the element in the configuration file. The suffix is another colon and a differentiator, to distinguish between the two different instances of the same element. Here these differentiators are simply 1 and 2.

Providing data from the parent to the embedded element

The implementation of the welcome element uses the processEmbeddedElement method to supply data to the embedded element:

WelcomeElement.java
// BaseElement contains getter and setter for the template
public class WelcomeElement extends BaseElement
{
    //....
    public void processElement()
    {
        Template template = getTemplate();

        processEmbeddedElement(template, "Table", "1", m_tableData1);
        processEmbeddedElement(template, "Table", "2", m_tableData2);

        print(template);
    }
}

The parameters of the processEmbeddedElement method that is used here are:

  • template : the template that contains the embedded element; here this is welcome.html
  • id : the id of the embedded element
  • differentiator : to target the specific instance of embedded element
  • data : the data to be provided to the embedded element

Here, m_tableData1 and m_tableData2 are member variables that refer to instances of TableData, which contain the data to be displayed in the two tables.

It is interesting to note that the reference to TableElement is made by id, not by implementation. This decouples the parent element from the implementation of the embedded element, rightfully delegating those details to the configuration file.

Retrieving the parent's provided data in the embedded element

Now, in the embedded element implementation (the TableElement class), the data that was provided by the parent element can be obtained using the getEmbedData method. This method returns an Object, so we must cast it to our desired data type:

TableElement.java
public class TableElement extends BaseElement
{
    //....
    public TableData getTableData()
    {
        return (TableData) getEmbedData();
    }
    //....
}

Rendering the embedded element

To render the HTML table, the template is straightforward and contains nothing that is specific to embedded elements:

table.html
<table class="data">
  <r:v name="header"/>
  <r:v name="rows"/>
</table>

<r:b name="headerRow">
  <tr class="header">
    <r:v name="headerCells"/>
  </tr>
</r:b>

<r:b name="headerCell">
  <th class="data">
    <a href="${v SUBMISSION:QUERY:sort/}"><r:v name="headerData"/></a>
  </th>
</r:b>

<r:b name="dataRow">
  <tr class="${v rowClass/}">
    <r:v name="dataCells"/>
  </tr>
</r:b>

<r:b name="dataCell">
  <td class="data"><r:v name="data"/></td>
</r:b>

The interesting part is the rendering of a header cell. This contains a hyperlink for the sort submission. Recall from the configuration file that this submission contains two parameters: submitSortColumn and submitSortAscending. These two parameters, when submitted, replace the previous values of the embedded element. If the values are not submitted, the previous values are kept.

Handling the submission

To achieve this, the doSort method, which is automatically invoked when the sort submission is sent, pulls the parameters and sets the corresponding fields of the embedded element:

TableElement.java
/**
 * Handle the sort submission.
 */
public void doSort()
{
    setSortColumn(getParameterInt("submitSortColumn"));
    setSortAscending(getParameterBoolean("submitSortAscending"));
    processElement();
}

If the submission is not sent, the previous values are kept because the embedded element's fields are reflexively linked. Recall from the configuration file:

site.xml
<input  name="sortColumn"/>
<output name="sortColumn"/>

<input  name="sortAscending"/>
<output name="sortAscending"/>

<datalink srcoutput="sortColumn"    destinput="sortColumn"    destid="Table"/>
<datalink srcoutput="sortAscending" destinput="sortAscending" destid="Table"/>

Finally, the processElement method, which is called from the doSort method when there is a submission, and automatically by the framework when there is no submission, uses the value of the fields to sort the table data and renders the table using the template:

TableElement.java
public void processElement()
{
    TableData td = getTableData();
    if (td == null)
    {
        return;
    }

    Template template = getTemplate();

    List<String> headers = td.getHeaders();
    List<List<String>> rowData = td.getRowData();

    // Render the headers
    int index = 0;
    for (String headerData : headers)
    {
        // Determine if the current column is the one being sorted.
        boolean currentSortColumn = (index == m_sortColumn);

        // Next time, reverse the sort ascending for the column being sorted; for
        // other columns, always sort ascending the first time.
        boolean nextAscending = currentSortColumn ? !m_sortAscending : true;

        // Set the parameters for the submission
        String[] parameters = new String[]
        {
            "submitSortColumn", String.valueOf(index),
            "submitSortAscending", String.valueOf(nextAscending),
        };
        setSubmissionQuery(template, "sort", parameters);

        // For the currently sorted column, decorate the header with a symbol.
        if (currentSortColumn)
        {
            headerData += " [" + (m_sortAscending ? "^" : "V") + "]";
        }

        // Set the values in the template
        template.setValue("headerData", headerData);
        template.appendBlock("headerCells", "headerCell");

        index++;
    }
    template.setBlock("header", "headerRow");

    // Sort the rows
    Collections.sort(
        rowData, new TableRowComparator(m_sortColumn, m_sortAscending)
    );

    // Render the rows, alternating CSS classes for even and odd rows
    boolean even = false;
    for (List<String> row : rowData)
    {
        template.blankValue("dataCells");

        for (String data : row)
        {
            template.setValue("data", data);
            template.appendBlock("dataCells", "dataCell");
        }
        String rowClass = even ? "even" : "odd";
        template.setValue("rowClass", rowClass);

        template.appendBlock("rows", "dataRow");

        even = !even;
    }
    print(template);
}

All of the logic is implemented in Java code, where it should be. The amount of functionality to feature in your embedded element is up to you.



Are you enjoying Confluence? Please consider purchasing it today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.2.1a Build:#515 May 19, 2006) - Bug/feature request - Contact Administrators