Creating an ADF Faces Dynamic Table with multi-faceted Head-to-Head comparison between records

This article shows how to create a widget in an ADF Faces application that looks like this:

Image

This table shows a comparison between records – Departments in this case – on a number of different aspects. The table compares four departments on characteristics like their name and location, the number of staff they contain and the Average Salary (in $) and Experience (in Days). Some of these comparisons are visualized by bars – with the length of the bar depicting the value at stake.

The comparison table could be further improved by introducing colors, nicer looking labels, highlighting exceptions, indicating thresholds, using Gauges etc. That’s for another time.

In this article, the topics covered are:

The source code is downloadable at the end of this article. Note: the article is fairly sparse- as the code is fairly self-explanatory.

Create ViewObject to gather the data

A simple SQL query is used to retrieve the data required for this department comparison:

select dname
,      loc
,      d.deptno
,      count(empno) empcount
,      max(sal)     maxsal
,      avg(sal)     avgsal
,      avg(sysdate - hiredate) avgexp
from   dept d
       left outer join
       emp e
       on (d.deptno = e.deptno)
group
by     dname, loc, d.deptno
ORDER BY deptno

Using this query, a read only ViewObject is created and added to some application module. This DataControl’s collection is the result:

Image

Create Data Bound Table in new JSF Web Page

The managed bean will read data from the DepartmentSummaryView iterator and convert it to a structure that is more suitable to the creation of a dynamic table. In order to get hold of the data control’s iterator in the context of the current page, it is convenient to drag the DepartmentSummaryView1 collection from the Data Controls palette to the page, and drop it as a ADF Table (optionally read only and possibly dynamic)

Image

To get a preview of what we already have achieved, we can run the page:

Image

The data is there already. However, it is presented in a completely wrong orientation. That is where the dynamic table and the managed bean will have to come in.

Create a Managed Bean that constructs the data model

The managed bean is accessed from the page. Associated with the page is the PageDefinition that contains the DepartmentSummaryView1Iterator iterator binding that was created when the Data Bound Table was added to the page. We can access this iterator binding from the managed bean to get hold of the data, just like the table does when it is rendered.

Getting hold of the data in the bean means: first get hold of the BindingContext, then access the current binding container (the run time instantiation of the page definition) and then retrieve the iterator binding:

BindingContext bcxt = BindingContext.getCurrent();
DCBindingContainer bc = null;
bc = (DCBindingContainer)bcxt.getCurrentBindingsEntry();
DCIteratorBinding iter = bc.findIteratorBinding("DepartmentSummaryView1Iterator");

Using this iterator binding, all Department Rows are at our disposal. A column will be created for each department.

However, we first need to define the rows that the table needs to display. There will be a row for each characteristic that we want to compare, for example name, location, number of staff and average salary. For each row, a Map is used that contains the specification of the characteristic, the attribute that contains the value that is represents and some properties that describe how the cells in the row should be painted. A typical row description in the bean looks like this:

row = new HashMap<String,Object>();
row.put(“header”, “Average Salary”);
row.put(“attribute”, “Avgsal”);
row.put(“type”, “bar”);
row.put(“range”, 3500);
row.put(“base”, 0);
rows.add(row);

With the definition of all rows complete, we will loop over the departments to create the columns:

Row[] depts = iter.getAllRowsInRange();
for (Row dept:depts) {
column = new HashMap&lt;String,String&gt;();
column.put("label", ((Integer)dept.getAttribute("Deptno")).toString());
column.put("name", ((Integer)dept.getAttribute("Deptno")).toString());

columns.add(column);
for (Map&lt;String,Object&gt; trow :rows) {
trow.put(column.get("name"), dept.getAttribute((String)trow.get("attribute")));
}

}

Both rows and columns objects are published through getter methods.

Create a dynamic table component that paints the table

The dynamic table is a simple component: it is a table that contains a forEach component to iterate over all column definitions. Each column is defined through a column component. Nothing special really:

Image

The interesting fact here is that the columns are defined dynamically (one for each Department) and the rows are created at run-time as well – one for each characteristic.

Image

The cell is a little more interesting because of the fact that for rows of type bar the value is displayed using a little horizontal bar:

Image

This trick was described in the article https://technology.amis.nl/blog/12739/suggestion-for-new-type-of-adf-dvt-data-visualization-the-delta-graph-to-visualize-relative-changes-integrated-in-a-table-layout .

The MyInvokableMap bean has been slightly extended, to deal with the new base property that specifies the value that indicates the start of horizontal bar. For example: the experience (in days since the hiredate) ranges from 10600 to 10900. Without the base property, every bar would start at 0. Differences between the departments (less than 400) would not stand out using a base of 0 and a full range of 11000 for the entire bar. Using 10500 as the base for this bar makes the difference more noticeable:

Image

Here is the relevant section from the MyInvokableMap bean:

Image

With these definitions done, we can run the page and see the comparison.

With this starting point, it turns out to be very easy to add a characteric. The steps are:

  • extend the query to produce an extra attribute with the required value
  • add the attribute in the Tree(Table) Binding
  • add the definition of the new row in the DepartmentJudge class
        row = new HashMap<String,Object>();
        row.put("header", "Highest Salary");
        row.put("attribute", "Maxsal");
        row.put("type", "bar");
        row.put("range", 6000);
        row.put("base", 0);
        rows.add(row);

These steps performed for the attribute Maxsal and rerunning the page results in:

Image

Resources

Download the JDeveloper 11gR2 application with the sources for this article: HeadToHead.