Dynamic table in ADF 12c empty dynamic table

Dynamic table in ADF 12c

A while ago I decided to create an ADF page based on a dynamic table, and I found that, although there are quite some articles about the subject online, I still faced some challenges while implementing such a solution in ADF 12c. These challenges, however, can be solved easily when you know some basic principles, and I would like to share those.

First, let me define what I mean with dynamic table in this post. It is an ADF table, specified in the JSF file with the af:table tag, with an unknown number of columns and rows. Moreover, the number of columns is still unknown when you run the page, as opposed to solutions where a table is created at runtime, based on a query.
We can add to that another feature: this table is not based on a database table nor a query, but will be built up at runtime based an a set of data that we will retrieve at the user’s request.

Use case: importing data with a time dimension

Why would we need such a table? Let me give a practical example: we want to let the user import a file, which is a CSV file export of a spreadsheet, in which the columns represent the time dimension. For instance, each column represents a day. Since we don’t know on forehand for how many days we will import data, we cannot build a view or table which has a column for each day.
On the other hand, when we will store the data in a database table, this table will have a date column (or attribute) in which the date column value from the CSV file will be stored.
So the database data model is very different from the data model we can read from the CSV file.
And the reason we want to show this data in the ADF table is simple: we want to show to the user that the CSV file has been read by the program successfully and that the columns as shown in our ADF page correspond to the columns in the spreadsheet the data has come from.

Demo: creating rows and columns at the user’s request

The demo application that I provide with this post, is meant to illustrate how a dynamic ADF table can be created, and how data can be read from and written to that table. As I want to keep the demo application as simple as possible, I concentrate on the ADF table, and will leave out the functionality of the storing data in the database or reading it from a file.
What I do include in the demo, is that the table is editable, so we can see that it is working properly.
For this purpose, the demo contains three buttons: two for adding columns and rows, and one to dump the contents of the entire table to the console.
On starting the application up, the following screen appears: a table with zero columns and zero rows.

empty_dynamic_table

After clicking the ‘Add column’ button three times and the ‘Add row’ button four times, the screen will look like this:

Dynamic table with three columns and four rows.

Dynamic table with three columns and four rows.

The newly created table cells are filled with sample data, for illustrative purposes.

If we now fill in the table with some data, like below…

dynamic_table_with_data

… and we press the button “Dump content to console”, we will see in the console log the following text:

dump_dynamic_table_to_console

So this proves that the table is working, because the data has been read and printed out in the console.
The rest of the post will explain how this has been made.

Creating the dynamic table

The demo application basically consists of two components: a JSF file which contains the table definition and a managed bean which will contain the table data. These two files can be easily ported to previous versions of ADF, but this post concentrates on the latest version: 12c. The demo has been tested with versions 12.1.3 and 12.2.1.

Here is the part of the JSF file which contains the table:

                <af:table id="t1" value="#{viewScope.dynamicTableBean.tableModel}" var="tRow"
                          partialTriggers="::b1 ::b2">
                    <af:forEach items="#{viewScope.dynamicTableBean.columns}" var="col">
                        <af:column id="#{col}" headerText="#{col}">
                            <af:inputText id="it#{col}" value="#{tRow[col]}"/>
                        </af:column>
                    </af:forEach>
                </af:table>

In line 1 you see a reference to the managed bean ‘dynamicTableBean’ which contains the data structure, or model, for the table. The table will be refreshed after the buttons to add rows and columns are pressed, hence the partial triggers in line 2.
It gets interesting at the forEach statement in line 3. This tag references the columns data structure in the managed bean, and is used to iterate over the available columns. The variable that will be used to specify each column is named “col”.
Then the column and input component are specified. As of version 12 of ADF, it is imperative to use an id attribute for every component. Moreover, the value for this id needs to be unique across the columns.
Special attention needs to be given to the value of the inputText component: #{tRow[col]}. To understand this, we need to know that each row of the table, denoted here as tRow, consists of a Java HashMap, with keys corresponding to the “col” variable value, and the values to the contents of the table cell.

So let us see how the managed bean code look like:

    private ArrayList tableValues;
    private ArrayList<String> columns;
    private SortableModel tableModel;
    ...
    public void addColumn(ActionEvent event) {
        columns.add("col" + columns.size());
    }
    ...
    public void addRow(ActionEvent actionEvent) {
        HashMap<String, String> tableRow = new HashMap<String, String>();
        for (String column: columns) {
            tableRow.put(column, "row" + tableValues.size() + ", " + column);
        }
        tableValues.add(tableRow);
        tableModel = new SortableModel(tableValues);
    }
    ...
    public void dumpContent(ActionEvent actionEvent) {
        for (int i = 0; i < tableValues.size(); i++) {
            Map<String, String> tableRow = (Map<String, String>) tableValues.get(i);    
            System.out.println("Processing row: " + i);
            for (int j = 0; j < tableRow.size(); j++) {
                System.out.print("value: " + tableRow.get("col" + j) + "; ");
            }
            System.out.println();
        }
    }

The data in our table will be contained in a data structure that consists of an ArrayList, in which each element represents a row. Each element of this ArrayList consists of a HashMap, in which the key contains the column name, and the value contains the table cell value. This is the ‘tableValues’ variable.
The column names will be hold in a separate bean property, named ‘columns’, that has been typed as an ArrayList of Strings. This gives us the possibility to iterate over the columns in the JSF page.
The third important variable is tableModel, which is of type SortableModel, to be precise: org.apache.myfaces.trinidad.model.SortableModel. This class contains a constructor that does the magic: it transforms our ArrayList into a CollectionModel, that the ADF table can use as its value attribute. The SortableModel class does not have methods for adding rows or columns, so we need to call the constructor each time a row or column has been added. Don’t worry about performance, I’ve tested it with hundreds of columns and rows and it works pretty fast.

The addColumn method simply adds a new column name, which is ‘col<number>’, to the ArrayList of columns, and in the addRow method you can see how a new row is being constructed: we iterate over the column list and initialize the table cell values with “row <number>, <column name>”. Then we add this ‘row’ to the ArrayList and we create a new instance of SortableModel, based on this data structure.

The ‘dumpContent’ method probably needs some explanation too: it shows how data can be read from the table. For this, we just need to use the ‘tableValues’ variable, that is: the ArrayList we constructed. In this method, we iterate over the ArrayList, which will give us the rows. Each row is an instance of the HashMap, which is by definition unordered, so if we want to read the columns in a specific order, we need te keep them indexed. In this example this is done by giving each column name a number.
So the second ‘for-loop’ iterates over the available columns and prints out their values to the console.

You can download the demo application here.

6 Comments

  1. Dhorrairaajj July 13, 2017
  2. chandra prakash May 25, 2017
    • Lucas Jellema June 3, 2017
  3. incubeidea February 16, 2017
  4. Jatin May 18, 2016
  5. Lucas Jellema December 13, 2015