Demonstration of the ADF Faces Matrix Component

2

This article will demonstrate some of the capabilities of the ADF Faces Matrix Component I have been working on for last few weeks. It is a fairly generic component that can be configured declaratively and will be able to render a rich matrix that can be used to present complex analytical data in a compact way. The example demonstrate in this article is too simple to fully demonstrate the matrix capabilities, but it will serve to give a good impression.

 

The need for a matrix frequently arises when data from an intersection table need to be presented: the records have two master-contexts, and we want to present data in both contexts at the same time. Another situation is where we want to present detail-data for all masters at once. Let’s look at an example.....

It’s Departments and Employees again: we want to show a summary for each Department; this summary contains for example the number of employees and the total salary sum. Furthermore, for each department we also want to list some detail data: we want to see all Jobs in the department and for each job the number of staff as well as the total salary sum. After creating a new ADF application, I create two View Objects – one for the DepartmentSummary and one for the DepartmentDetailsPerJob. They are connected through a (master-detail) ViewLink. Next I create a new JSF page.

Now I can quickly create a Master Table – Detail Table page that is probably the best I can do in terms of the functional requirements using the standard ADF components and ADF Model Data Bindings:

After completing the drag & drop operation I can run the page. In no time at all, I have achieved pretty advanced functionality:

 

When I select another Department, the Staff Details are immediately (AJAX) refreshed. Pretty cool!

However, it is not good enough: I cannot easily compare staff details per job across departments. I cannot see details for multiple departments at once. So, can we do better? Enter the Matrix.

The ADF Faces Matrix component

The simplest use of the Matrix is shown below: the columns represent the first master context – the Departments. The row represent the second master context, the Job context. In the cells, we see the number of Employees in a Department in the given Job.

 

The main objective – an overview in once glance of the details of all masters – is achieved already. The steps required for achieving this matrix are rather simple:

  • create a JSPX page with a panelGroup that is bound to a managed bean
  • create three iterators in the PageDefinition: one for the first master (Departments), one for the second master (Jobs) and one for the cells (DeptJobDetails)
  • configure three managed beans in faces-config.xml: the MatrixModel, the Matrix and the MatrixProcessor; of these three, only the first one is has application specific configuration details

The key part of the JSF page looks like this:

  <af:panelGroup id="DepartmentJobMatrix" binding="#{DepartmentJobMatrix.tableContainer}"/> 

all table, column and cell elements are created programmatically by the MatrixProcessor.

The important elements in the bean configuration are for example:

  &lt;managed-bean&gt;<br />    &lt;managed-bean-name&gt;DepartmentJobMatrixModel&lt;/managed-bean-name&gt;<br />    &lt;managed-bean-class&gt;nl.amis.adffaces.matrix.MatrixModel&lt;/managed-bean-class&gt;<br />    &lt;managed-bean-scope&gt;session&lt;/managed-bean-scope&gt;<br />    &lt;managed-property&gt;&lt;property-name&gt;readOnly&lt;/property-name&gt;&lt;value&gt;true&lt;/value&gt;&lt;/managed-property&gt;<br />    &lt;managed-property&gt;&lt;property-name&gt;footer&lt;/property-name&gt;&lt;value&gt;...&lt;/value&gt;&lt;/managed-property&gt;<br />    &lt;managed-property&gt;&lt;property-name&gt;rowMetaData&lt;/property-name&gt;<br />      &lt;map-entries&gt;<br />        &lt;map-entry&gt;&lt;key&gt;type0&lt;/key&gt;&lt;value&gt;header&lt;/value&gt;&lt;/map-entry&gt;<br />        &lt;map-entry&gt;&lt;key&gt;header0&lt;/key&gt;&lt;value&gt;Job&lt;/value&gt;&lt;/map-entry&gt;<br />        &lt;map-entry&gt;&lt;key&gt;cellExpression0&lt;/key&gt;&lt;value&gt;#{'#{@Job@}'}&lt;/value&gt;&lt;/map-entry&gt;<br />      &lt;/map-entries&gt;<br />    &lt;/managed-property&gt;<br />    &lt;managed-property&gt;&lt;property-name&gt;columnMetaData&lt;/property-name&gt;<br />      &lt;map-entries&gt;<br />        &lt;map-entry&gt;&lt;key&gt;type0&lt;/key&gt;&lt;value&gt;header&lt;/value&gt;&lt;/map-entry&gt;<br />        &lt;map-entry&gt;&lt;key&gt;headerExpression0&lt;/key&gt;&lt;value&gt;#{'#{@Dname@} (#{@Deptno@})'}&lt;/value&gt;&lt;/map-entry&gt;<br />      &lt;/map-entries&gt;<br />    &lt;/managed-property&gt;<br />    &lt;managed-property&gt;&lt;property-name&gt;cellDefinitions&lt;/property-name&gt;<br />    &lt;map-entries&gt;<br />        &lt;map-entry&gt;&lt;key&gt;columnHeaderExpression0&lt;/key&gt;&lt;value&gt;#{'Num of Staff #{\'\'}'}&lt;/value&gt;&lt;/map-entry&gt;<br />        &lt;map-entry&gt;&lt;key&gt;expression0&lt;/key&gt;&lt;value&gt;#{'#{@CountEmp@}'}&lt;/value&gt;&lt;/map-entry&gt;<br />      &lt;/map-entries&gt;<br />    &lt;/managed-property&gt;<br />    &lt;managed-property&gt;<br />      &lt;property-name&gt;masterIteratorName&lt;/property-name&gt;<br />      &lt;value&gt;DepartmentSummaryViewIterator&lt;/value&gt;<br />    &lt;/managed-property&gt;<br />    &lt;managed-property&gt;<br />      &lt;property-name&gt;detailIteratorName&lt;/property-name&gt;<br />      &lt;value&gt;DeptJobDetailViewIterator&lt;/value&gt;<br />    &lt;/managed-property&gt;<br />    &lt;managed-property&gt;<br />      &lt;property-name&gt;lookupIteratorName&lt;/property-name&gt;<br />      &lt;value&gt;JobSummaryViewIterator&lt;/value&gt;<br />    &lt;/managed-property&gt;<br />  &lt;/managed-bean&gt;<br />&nbsp;

Additional configuration specifies for example the link between master 1, master 2 (aka lookup) and cells.

Additional columns per master

Right now, the matrix displays only the number of employees in a specific job for each department. That is a good first step, but we would like to also see the the Salary Sum for every department, preferably in a neat column of its own. Something like:

 

To achieve this, we need a tiny little bit of additional configuration in the managed bean definition. To be exact:

        &lt;map-entry&gt;&lt;key&gt;columnHeaderExpression1&lt;/key&gt;&lt;value&gt;#{'Salary Sum #{\'\'}'}&lt;/value&gt;&lt;/map-entry&gt;<br />        &lt;map-entry&gt;&lt;key&gt;converter1&lt;/key&gt;&lt;value&gt;NumberConverter&lt;/value&gt;&lt;/map-entry&gt;<br />        &lt;map-entry&gt;&lt;key&gt;expression1&lt;/key&gt;&lt;value&gt;#{'#{@Salsum@}'}&lt;/value&gt;&lt;/map-entry&gt;<br />        &lt;map-entry&gt;&lt;key&gt;newColumn1&lt;/key&gt;&lt;value&gt;true&lt;/value&gt;&lt;/map-entry&gt;<br />&nbsp;

That’s it.

 

Adding Column Summaries 

 

A piece of information that we had in our original master-table detail-table page was the number of employees per department and the sum of their salaries. Information that in the mat
rix typically would be displayed in the col
umn footers for the Num of Staff and Salary Sum columns:

 

As has become the pattern: specifying these summaries is very straightforward. Adding the column footers is done in the column metadata properties, like this:

&lt;map-entry&gt;<br />  &lt;key&gt;columnFooterExpression0&lt;/key&gt;<br />  &lt;value&gt;#{'#{@CountEmpno@}'}&lt;/value&gt;<br />&lt;/map-entry&gt;<br />&lt;map-entry&gt;<br />  &lt;key&gt;columnFooterExpression1&lt;/key&gt;<br />  &lt;value&gt;#{'#{@SumSal@}'}&lt;/value&gt;<br />&lt;/map-entry&gt;<br />&nbsp;

The @CountEmp@ and @SumSal@ entries are references to Attributes in the Master 1 Iterator.

Adding Row Summaries 

Now that we are discussing summaries, it would be nice to not only display summaries for master 1 (column totals) but also for master 2 (row totals). The matrix can handle those row summaries as easily as it does column summaries. So it is easy to display the number of employees in each job, as well as the salary sum per job. Note that this is information we could not (easily) present in the master-table detail-table page.

The required configuration is added to the rowMetaData:

&lt;map-entry&gt;&lt;key&gt;type7&lt;/key&gt;&lt;value&gt;footer&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;header7&lt;/key&gt;&lt;value&gt;Staff Count&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;cellExpression7&lt;/key&gt;&lt;value&gt;#{'#{@CountEmp@}'}&lt;/value&gt;&lt;/map-entry&gt;<br /><br />&lt;map-entry&gt;&lt;key&gt;type8&lt;/key&gt;&lt;value&gt;footer&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;header8&lt;/key&gt;&lt;value&gt;Salary Sum&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;cellExpression8&lt;/key&gt;&lt;value&gt;#{'#{@SumSal@}'}&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;converter8&lt;/key&gt;&lt;value&gt;NumberConverter&lt;/value&gt;&lt;/map-entry&gt;<br />&nbsp;

Detail Disclosure – Expand/Collapse Columns 

We have more details to display, both for master 1 the Departments as well as for master 2, the Jobs. For Departments for example we have the Average Salary per job as well as the Minimum and Maximum Hiredate per job (indications of the job experience in the department). Those are details that are interesting at times. However, by blindly adding them to the matrix, it becomes cluttered. Now we can benefit from the detail disclosure feature of our matrix; this allows us to specify cells that are in a detail disclosure. This means that they are only shown when the user explicitly expands the column detail disclosure. It looks like:

 

and after disclosing the details for the Research Department:

 

The configuration required to add these detail disclosures is:

   &lt;map-entry&gt;<br />     &lt;key&gt;columnHeaderExpression3&lt;/key&gt;<br />     &lt;value&gt;#{'Average Salary#{\'\'}'}&lt;/value&gt;<br />   &lt;/map-entry&gt;<br />   &lt;map-entry&gt;<br />     &lt;key&gt;converter3&lt;/key&gt;<br />     &lt;value&gt;NumberConverter&lt;/value&gt;<br />   &lt;/map-entry&gt;<br />   &lt;map-entry&gt;<br />     &lt;key&gt;columnFooterExpression3&lt;/key&gt;<br />     &lt;value&gt;#{'#{@AvgSal@}'}&lt;/value&gt;<br />   &lt;/map-entry&gt;<br />   &lt;map-entry&gt;<br />     &lt;key&gt;expression3&lt;/key&gt;<br />     &lt;value&gt;#{'#{@Avgsal@}'}&lt;/value&gt;<br />   &lt;/map-entry&gt;<br />   &lt;map-entry&gt;<br />     &lt;key&gt;newColumn3&lt;/key&gt;<br />     &lt;value&gt;true&lt;/value&gt;<br />   &lt;/map-entry&gt;<br />   &lt;map-entry&gt;<br />     &lt;key&gt;inDetailDisclosure3&lt;/key&gt;<br />     &lt;value&gt;true&lt;/value&gt;<br />   &lt;/map-entry&gt;<br />&nbsp;

The essence here of course is the last map-entry: the inDetailDisclosure (=true) entry. 

I had promised additional ‘detail disclosure’ columns, for earliest and latest hiredate. See below for how that looks:

The most remarkable about these additional columns is the fact that the Latest Hiredate is only displayed when the department has more than one employee in the job – if there is just one employee, the earliest and latest hiredate would be the same; displaying them both is only additional clutter. To add these two detail columns, we need:

&lt;map-entry&gt;<br />  &lt;key&gt;renderedExpression4&lt;/key&gt;<br />  &lt;value&gt;#{'#{Calculator.RESET[@CountEmp@].LONG.EQUALS &amp;gt; 1}'}&lt;/value&gt;<br />&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;columnHeaderExpression4&lt;/key&gt;&lt;value&gt;#{'Latest Hiredate#{\'\'}'}&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;Converter4&lt;/key&gt;&lt;value&gt;DateConverter&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;expression4&lt;/key&gt;&lt;value&gt;#{'#{@Maxhiredate@}'}&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;newColumn4&lt;/key&gt;&lt;value&gt;true&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;inDetailDisclosure4&lt;/key&gt;&lt;value&gt;true&lt;/value&gt;&lt;/map-entry&gt;<br /><br />&lt;map-entry&gt;&lt;key&gt;columnHeaderExpression5&lt;/key&gt;&lt;value&gt;#{'Earliest Hiredate#{\'\'}'}&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;Converter5&lt;/key&gt;&lt;value&gt;DateConverter&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;expression5&lt;/key&gt;&lt;value&gt;#{'#{@Minhiredate@}'}&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;newColumn5&lt;/key&gt;&lt;value&gt;true&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;inDetailDisclosure5&lt;/key&gt;&lt;value&gt;true&lt;/value&gt;&lt;/map-entry&gt;<br />&nbsp;

Pay special attention to the "Rendered Expression".  It defines whether or not the Maxhiredate should be rendered. The expression makes us of the Calculator bean, to turn the CountEmp attribute into a Long that the EL evaluator can use to compare with 1. See for an explanation about this Calculator bean and how to use it the article JSF EL Calculator bean – to overcome “coercion” errors and add functionality to EL Expressions.

And now for something completely different: Row Detail Disclosure

What we just did for columns can also be done for rows. It is not as neat, the ADF Faces table does not have specific features for row disclosure (at least not for a per cell disclosure), but it does the job.

The Row Disclosure I have in mind will add as detail to the Num of Staff cells the number of Juniors and the number of Seniors for the Job in that department (the definition of Junior is lazily set as ‘not part of the most experienced half of the colleagues – across the enterprise’.

 

The configuration required for cells in a row detail disclosure is very similar to the column detail disclosure. The only difference is in the properties newColumn vs. newRow:

&lt;map-entry&gt;&lt;key&gt;id1&lt;/key&gt;&lt;value&gt;maxhiredate&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;expression1&lt;/key&gt;&lt;value&gt;#{'#{@Maxhiredate@}'}&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;newColumn1&lt;/k
ey&gt;&lt;value&gt
;false&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;newRow1&lt;/key&gt;&lt;value&gt;true&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;inDetailDisclosure1&lt;/key&gt;&lt;value&gt;true&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;renderedExpression1&lt;/key&gt;&lt;value&gt;#{'#{@lookup.Job@ !=\'CLERK\' }'}&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;converter1&lt;/key&gt;&lt;value&gt;DateConverter&lt;/value&gt;&lt;/map-entry&gt;<br /><br />&lt;map-entry&gt;&lt;key&gt;id2&lt;/key&gt;&lt;value&gt;minhiredate&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;expression2&lt;/key&gt;&lt;value&gt;#{'#{@Minhiredate@}'}&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;newColumn2&lt;/key&gt;&lt;value&gt;false&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;newRow2&lt;/key&gt;&lt;value&gt;true&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;readOnlyExpression2&lt;/key&gt;&lt;value&gt;#{'#{@CountEmp &amp;gt; 1 @}'}&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;inDetailDisclosure2&lt;/key&gt;&lt;value&gt;true&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;renderedExpression2&lt;/key&gt;&lt;value&gt;#{'#{@lookup.Job@ !=\'CLERK\' }'}&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;converter2&lt;/key&gt;&lt;value&gt;DateConverter&lt;/value&gt;&lt;/map-entry&gt;<br />&nbsp;

Cells containing Calculations involving Master Attributes

The last piece of extra functionality I will introduce in this article involves cells that contain not just the value of Attributes in the Detail iterator, but that display the results of calculations that may involve multiple Detail Attributes but that can also use Master Attributes.

Let’s make it a little clearer with an example: we can add a cell that displays the percentage earned of the overall Salary Sum for a certain Job by the Employees in that Job in the Cell’s Department. Something like: the Clerks in Department 10 cost 24% of the overall Clerk costs. In the matrix, that looks like:

The cell configuration for this new cell:

&lt;map-entry&gt;<br />  &lt;key&gt;expression12&lt;/key&gt;<br />  &lt;value&gt;#{' #{Calculator.RESET[@Salsum@][@lookup.SumSal@].DIVIDE[1000].MULTIPLY.ROUND[10].DIVIDE.EQUALS}%  of total #{@lookup.Job@} costs'}&lt;/value&gt; <br />&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;newRow12&lt;/key&gt;&lt;value&gt;true&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;inDetailDisclosure12&lt;/key&gt;&lt;value&gt;true&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;renderedExpression12&lt;/key&gt;&lt;value&gt;#{'#{Calculator.RESET[@CountEmp@].LONG.EQUALS &amp;gt; 0  }'}&lt;/value&gt;&lt;/map-entry&gt;<br />&nbsp;

We can also refer to Department Attributes in Cell Expressions. That means for example that we can show for every Job/Department combination which percentage of the total Department Salary Cost is spent on that specific job.

 

The configuration used for this cell expression referring master attributes:

&lt;map-entry&gt;<br />  &lt;key&gt;expression12&lt;/key&gt;<br />  &lt;value&gt;#{' #{Calculator.RESET[@Salsum@][@master.SumSal@].DIVIDE[1000].MULTIPLY.ROUND[10].DIVIDE.EQUALS}%  of total #{@master.Dname@} costs'}&lt;/value&gt; <br />&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;newRow12&lt;/key&gt;&lt;value&gt;true&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;inDetailDisclosure12&lt;/key&gt;&lt;value&gt;true&lt;/value&gt;&lt;/map-entry&gt;<br />&lt;map-entry&gt;&lt;key&gt;renderedExpression12&lt;/key&gt;&lt;value&gt;#{'#{Calculator.RESET[@CountEmp@].LONG.EQUALS &amp;gt; 0  }'}&lt;/value&gt;&lt;/map-entry&gt;<br />&nbsp;

The interesting bit of course is @master.Dname@ – master. refers to attributes in the Master (1) iterator.

Share.

About Author

Lucas Jellema, active in IT (and with Oracle) since 1994. Oracle ACE Director for Fusion Middleware. Consultant, trainer and instructor on diverse areas including Oracle Database (SQL & PLSQL), Service Oriented Architecture, BPM, ADF, Java in various shapes and forms and many other things. Author of the Oracle Press book: Oracle SOA Suite 11g Handbook. Frequent presenter on conferences such as JavaOne, Oracle OpenWorld, ODTUG Kaleidoscope, Devoxx and OBUG. Presenter for Oracle University Celebrity specials.

2 Comments