Wednesday, February 29, 2012

ADF 11g : Even Fancier ! Multi Master Multi Detail and how to highlight related detail records

A few weeks ago I wrote about how to highlight related detail records. That solution worked very well, but turned out to be not as fancy as expected. I needed to be able to implement multiple selection in the master as well. That turned out to be not very simple but I managed to end up with having a multi-master / multi-detail implementation.



In this post I show you how I did that.

First I had to enable multiple selection on the master table (Countries). To achieve this I had to remove the selectionListener and the selectedRowKeys. I also set rowSelection="multiple". The JSF code for the table now is :

1:  <af:table rows="#{bindings.CountriesView2.rangeSize}"  
2: fetchSize="#{bindings.CountriesView2.rangeSize}"
3: emptyText="#{bindings.CountriesView2.viewable ? 'No data to display.' : 'Access Denied.'}"
4: var="row"
5: value="#{bindings.CountriesView2.collectionModel}"
6: rowBandingInterval="0"
7: rowSelection="multiple"
8: id="t1"
9: styleClass="AFStretchWidth">


After this change, the second table (Locations) is no longer refreshed. That makes sense because I removed the selectionListener from the Countries table. To solve this problem I decided to use a clientListener and a ServerListener on the Countries table. The table component supports a clientListener of type 'selection', that fires accordingly.

1:    <af:clientListener method="rowSelected" type="selection"/>            
2: <af:serverListener type="invokeMatcher"
3: method="#{pageFlowScope.HighLightBean.matchEmFromJavaScript}"/>


The corresponding javascript function queues an event to invoke a serverside Java method. Notice line 4 and 5 where I get a handle to the table component, and I pass this table component to the managed bean.
1:     <f:facet name="metaContainer">  
2: <af:resource type="javascript">
3: function rowSelected(evt) {
4: var table = evt.getSource();
5: AdfCustomEvent.queue(table, "invokeMatcher", {}, true);
6: evt.cancel();
7: }
8: </af:resource>
9: </f:facet>


In the managed bean I have e method called "matchEmFromJavaScript". The code for this method is below. After doing some initialization, I get a handle to the table (line 5) and for each and every selected row (line 9) I get the rowdata and pass the corresponding row to the matchEm() method (line 12). This matchEm()method is almost the same as the one that I used in my previous post. There are however some minor changes because I have a Multi Select master now. The most important change is that I had to move code one level up because this method is now being called in a loop.
Otherwise only the last match is finally added to the selected rowkeys instead of all matching rows.

1:   public void matchEmFromJavaScript(ClientEvent ce) {  
2: // init
3: newSelection.clear();
4: String country = "";
5: RichTable countryTable = (RichTable)ce.getComponent();
6: // process selected rowkeys
7: RowKeySet rowKeySet = (RowKeySet)countryTable.getSelectedRowKeys();
8: CollectionModel cm = (CollectionModel)countryTable.getValue();
9: for (Object countryRowKey : rowKeySet) {
10: cm.setRowKey(countryRowKey);
11: JUCtrlHierNodeBinding rowData = (JUCtrlHierNodeBinding)cm.getRowData();
12: matchEm(rowData.getRow());
13: }
14: locTable.setSelectedRowKeys(newSelection);
15: AdfFacesContext.getCurrentInstance().addPartialTarget(locTable);
16: AdfFacesContext.getCurrentInstance().addPartialTarget(countryTable);
17: }


Now the MultiSelect Master and MultiSelect Detail are ready for use. However, as you can see below, the result is not yet satisfactory. I do have mutliple master records and multiple detail records selected, but I am not able to see how they are related.



So here is the question : Is there a way to add colors to the rows so it becomes clear how they are related ?

The answer is yes, however there are some tricky steps here.

First this requires a custom skin. In this skin I can define colors so that the the selected rows in the locations table one to have the same color as their selected parent row. I decided to create 5 predefined styles, style-0 ---> style-4, staring in lines 13, 21, 29, 37 and 45. What is very important here is the use of !important, because whithout that predicate it doesn't seem to work.

1:   af|table::data-row:highlighted af|column::data-cell,  
2: af|table::data-row:highlighted af|column::banded-data-cell{
3: background-color: rgb(184,223,253) !important;
4: }
5: af|table::data-row:selected:focused af|column::data-cell,
6: af|table::data-row:selected:focused af|column::banded-data-cell,
7: af|table::data-row:selected:inactive af|column::data-cell,
8: af|table::data-row:selected:inactive af|column::banded-data-cell,
9: af|table::data-row:selected af|column::data-cell,
10: af|table::data-row:selected af|column::banded-data-cell{
11: background-color:transparent !important;
12: }
13: af|table::data-row:selected:focused af|column::data-cell.style-0,
14: af|table::data-row:selected:focused af|column::banded-data-cell.style-0,
15: af|table::data-row:selected:inactive af|column::data-cell.style-0,
16: af|table::data-row:selected:inactive af|column::banded-data-cell.style-0,
17: af|table::data-row:selected af|column::data-cell.style-0,
18: af|table::data-row:selected af|column::banded-data-cell.style-0{
19: background-color:Aqua !important;
20: }
21: af|table::data-row:selected:focused af|column::data-cell.style-1,
22: af|table::data-row:selected:focused af|column::banded-data-cell.style-1,
23: af|table::data-row:selected:inactive af|column::data-cell.style-1,
24: af|table::data-row:selected:inactive af|column::banded-data-cell.style-1,
25: af|table::data-row:selected af|column::data-cell.style-1,
26: af|table::data-row:selected af|column::banded-data-cell.style-1{
27: background-color:Fuchsia !important;
28: }
29: af|table::data-row:selected:focused af|column::data-cell.style-2,
30: af|table::data-row:selected:focused af|column::banded-data-cell.style-2,
31: af|table::data-row:selected:inactive af|column::data-cell.style-2,
32: af|table::data-row:selected:inactive af|column::banded-data-cell.style-2,
33: af|table::data-row:selected af|column::data-cell.style-2,
34: af|table::data-row:selected af|column::banded-data-cell.style-2{
35: background-color:Lime !important;
36: }
37: af|table::data-row:selected:focused af|column::data-cell.style-3,
38: af|table::data-row:selected:focused af|column::banded-data-cell.style-3,
39: af|table::data-row:selected:inactive af|column::data-cell.style-3,
40: af|table::data-row:selected:inactive af|column::banded-data-cell.style-3,
41: af|table::data-row:selected af|column::data-cell.style-3,
42: af|table::data-row:selected af|column::banded-data-cell.style-3{
43: background-color:Orange !important;
44: }
45: af|table::data-row:selected:focused af|column::data-cell.style-4,
46: af|table::data-row:selected:focused af|column::banded-data-cell.style-4,
47: af|table::data-row:selected:inactive af|column::data-cell.style-4,
48: af|table::data-row:selected:inactive af|column::banded-data-cell.style-4,
49: af|table::data-row:selected af|column::data-cell.style-4,
50: af|table::data-row:selected af|column::banded-data-cell.style-4{
51: background-color:Yellow !important;
52: }


This skin defines the styles, but now I need to make sure that selected rows take on that style.

First I created a List with available and unAvailable styles in my backing bean. I found this to be necessary in order to retain the same color for a country over several selection events.
1:  .........................  
2: private ArrayList availableStyles = new ArrayList();
3: private ArrayList unavailableStyles = new ArrayList();
4: public HighLightBean() {
5: init();
6: }
7: public void init(){
8: availableStyles.add("style-0");
9: availableStyles.add("style-1");
10: availableStyles.add("style-2");
11: availableStyles.add("style-3");
12: availableStyles.add("style-4");
13: unavailableStyles.clear();
14: }


As you can see I add the 5 predefined styles to the List with available colors.
Next step is to set the a style whenever a row is selected. This also means moving that style form available to unavailable. I use two additional maps to keep track of selected countries, the selCoMap that holds the newly selected countries, and the oldSelCoMap to hold the previously selected ones.

In the matchEmFromJavaScript() method I can now do all processing that I need. For instance I check if the selected row was already selected. If not, I add it to the map with selected rows, and apply the first available style to it (lines 15-18). If it was already selected, I add it to the map with selected rows keeping its previously defined style (lines 21,22). Finally (lines 28,29) all entries that remained in the oldSelCoMap are no longer selected rows anymore, so I make the corresponding styles available for the next run.

1:   public void matchEmFromJavaScript(ClientEvent ce) {  
2: // initialize and store previously selected values
3: oldSelCoMap.putAll(selCoMap);
4: selCoMap.clear();
5: newSelection.clear();
6: String country = "";
7: RichTable countryTable = (RichTable)ce.getComponent();
8: // process selected rowkeys
9: RowKeySet rowKeySet = (RowKeySet)countryTable.getSelectedRowKeys();
10: CollectionModel cm = (CollectionModel)countryTable.getValue();
11: for (Object countryRowKey : rowKeySet) {
12: cm.setRowKey(countryRowKey);
13: JUCtrlHierNodeBinding rowData = (JUCtrlHierNodeBinding)cm.getRowData();
14: country = (String)rowData.getRow().getAttribute("CountryId");
15: if (!oldSelCoMap.containsKey(country)){
16: selCoMap.put(country, availableStyles.get(0));
17: unavailableStyles.add(availableStyles.get(0));
18: availableStyles.remove(0);
19: }
20: else {
21: selCoMap.put(country, oldSelCoMap.get(country));
22: oldSelCoMap.remove(country);
23: }
24: matchEm(rowData.getRow());
25: }
26: // all entries that remained in the oldSelCoMap are no longer selected rows anymore.
27: // make the corresponding styles available for the next run.
28: availableStyles.addAll(oldSelCoMap.values());
29: unavailableStyles.removeAll(oldSelCoMap.values());
30: // clear the previously selected values
31: oldSelCoMap.clear();
32: // System.out.println("check avialable " + availableStyles.toString());
33: // System.out.println("check unavialable " + unavailableStyles.toString());
34: // set the selectedRowKeys for the locations table
35: locTable.setSelectedRowKeys(newSelection);
36: // refresh both tables
37: AdfFacesContext.getCurrentInstance().addPartialTarget(locTable);
38: AdfFacesContext.getCurrentInstance().addPartialTarget(countryTable);
39: }


With this in place I 'only' need to tell the table components what style to apply to the rows. I used EL to achieve this. On all columns of the Countries table and the Locations table I added the following expression:
1:  styleClass="#{(empty pageFlowScope.HighLightBean.selCoMap[row.CountryId])  ? '' : pageFlowScope.HighLightBean.selCoMap[row.CountryId]}"  

This expression takes the styleClass from the selCoMap Map that I defined in the backing bean. If there is no match the expression evaluates to "" and else sets the defined styleClass.
http://www.blogger.com/img/blank.gifhttp://www.blogger.com/img/blank.gif
I think the result is pretty amazing.



Code can be for this post can be downloaded here.

7 comments:

Frederico Barracha said...

Hey Luc,

I've downloaded your project and i'm trying to make some changes but i think its highly complicated.
For example, a simple change like adding a style, gives me error on executing the page and i don't understand why..

I added the skin in the CSS, the code line in the init and it doesn't work. Am i doing something wrong?

Luc Bors said...

What error do you get ?

Frederico Barracha said...

It was my mistake, the application wasn't depployed after the changes.

Regards,
Frederico.

Frederico Barracha said...

Hey Luc, i just wanted to say thanks because this is what i need for my project.

It's a really nice work and i'm trying to use it.
Thank you for your post.

Just a question: is it possible to order in your location table by the selected elements?
For example, you select US and the locations are ordered by what you selected (even with multiple selection).

Regards,
Frederico.

Frederico Barracha said...

Hey Luc, i just wanted to say thanks because this is what i need for my project.

It's a really nice work and i'm trying to use it.
Thank you for your post.

Just a question: is it possible to order in your location table by the selected elements?
For example, you select US and the locations are ordered by what you selected (even with multiple selection).

Regards,
Frederico.

Frederico Barracha said...

Hey Luc, how are you?

I have a question regarding your project.

I've tried to do it myself (and it works) but whenever i make a selection, it colours it by the default adf color and not by the colour i defined on the css file. Why is that? Do i have to define the css file in a specific place?

Regards,
Frederico.

Ps - I have this in the column:
styleClass="#{(empty BeanParaPintar.selCoMap[row.DepartmentId]) ? '' : BeanParaPintar.selCoMap[row.DepartmentId]}"

Anonymous said...

Hello Luc, how are you m8?

I've downloaded your code and i've tried to run it but i don't get the colours like you do, i only get the default colour for selected rows. Do you know why or how do i change this?

It's a really nice work and i would like to make it working here.