Friday Jan 20, 2012

Break Group Formatting in a Table - Part 1

From time to time, interesting questions come my way from the community which warrent a little investigation time and in turn capturing of that information in a posting like this. Just such a problem arrived in my in-tray yesterday. Simply put, how do you create break group style formatting in an ADF table? By break group formatting I mean the supression of repeating values in a column, something which is a very common reporting requirement although not so common in a transactional UI. However, if the data is read-only and pre-sorted, why not?

To make it clear here's an example:

Example of Break Group formatting in a table

This is one of those problems that you can address in several ways. you could of course manipulate the data set, say for example, your View Object. if using ADF BC, to have a calculated column that you pre-process to contain the grouping text or not, however, this has some drawbacks. Firstly I dislike the idea of manipulating the data model for the sake of a UI vanity, secondly you have to find the right point in time to run through the transient attribute to populate it correctly, and finally if you filter the data in any way then you'll have to re-do that calculation. 

So is there a way you can do this in the JSF layer? Well yes, that's how I created the screenshot above. I'll reveal a couple of ways to do it, but first, let's get into what the logical process needs to be. Basically when we go to render say the department name we want to look at the previous value for the same attribute in the collection and if the value matches we supress display, otherwise we print it. So that's simple enough but let's review the tools we have to play with. Within the node-stamp of a table we have access to some contextual information in the form of var (the row information we're processing) and varstatus (which gives access to the row which can be very useful in some circumstances). We could perhaps see, that if our data model backing the table was some sort of indexed List, we could use a value of (<VarStatus>.count-1) to reach into the list and get the previous value. Indeed that might be an approach worth having a play with now I come to think about it, however, lets think about another way we could look at the previously stamped value. Logically all we have to do is to store the last value we printed and not print again if this row has the same value. If this row has a different value then we do print, and also we replace the stored value with the new one, and so the process continues. Great, so how do we do it?  

Everybody likes to cheat if they (think that they) can get away with it!

In this approach I've resorted to one of the great JSF "cheats". In out of the box JSF you can't execute arbitrary method calls as part of page rendering (however, more on that in the next article where I will discuss creating a custom EL function to do this as well), but there is one way in which you can make a call that passes a value. This is when you access a Map in an expression. e.g. #{mybean['foo']}. Behind the scenes this is calling the get() method on the Map interface and passing the supplied key - "foo" in this case, as the argument. So you see, we do have a technique for calling functions in-line. This technique has been covered before by me and by others, for example my good buddy Lucas Jellema.

So the basic approach here involves creating a managed bean which supplies a fake Map, where the get() method actually proxies for the check we want to make. Here's an extract from that class. I've not shown all of the code - you do have to implement the compelete Map interface in terms of creating the empty methods, but fear not, JDeveloper's Source -> Implement Interface option will do all the work for you. The important bit is the get() method shown in the extract here:

public class UIManager {
    //Holds the DepartmentId of the last one we rendered
    Integer _breakGroupLastKey = new Integer(-1);
    
    //The fake Map
    Map _breakGroupMap = new BreakGroupFakeMap();

    //Getter for the face map. No setter required
    public Map getBreakGroupMap() {
        return _breakGroupMap;
    }

    //Implementation of the fake map as an inner class
    public class BreakGroupFakeMap implements Map{

        @Override
        public Object get(Object key) {
            Boolean retValue = false;
            if (key instanceOf Integer){
              if (((Integer)key).equals(_breakGroupLastKey)){
                  retValue = true;
              }
              else {
                  retValue = false;
                  _breakGroupLastKey = (Integer)key;
              }
            }
            return retValue;
        }
...

And then we wire that into the column that we want to break on, in this case I'm displaying the DepartmentName and using DepartmentId as the key, so DepartmentId is also defined in the underlying binding. You can see here how the current DepartmentId is passed in using the Map style expression

<af:column headerText="Department" id="c1">
  <af:outputText value="#{row.DepartmentName}" 
                 visible="#{!uiManager.breakGroupMap[row.DepartmentId]}" 
                 id="ot1"/>                            
</af:column>       

You will of course need to think about the scope in which this bean lives, ViewScope is probably the most suitable. In the next posting I'll look at the slightly more advanced option of creating an custom EL function.  

See also Part 2 in this series

About

Hawaii, Yes! Duncan has been around Oracle technology way too long but occasionally has interesting things to say. He works in the Development Tools Division at Oracle, but you guessed that right? In his spare time he contributes to the Hudson CI Server Project at Eclipse
Follow DuncanMills on Twitter

Note that comments on this blog are moderated so (1) There may be a delay before it gets published (2) I reserve the right to ignore silly questions and comment spam is not tolerated - it gets deleted so don't even bother, we all have better things to do with our lives.
However, don't be put off, I want to hear what you have to say!

Search

Archives
« April 2014
MonTueWedThuFriSatSun
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today