Skip to main content

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.

Comments

Unknown 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 ?
Unknown said…
It was my mistake, the application wasn't depployed after the changes.

Regards,
Frederico.
Unknown 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.
Unknown 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.
Unknown 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.

Popular posts from this blog

ADF 12.1.3 : Implementing Default Table Filter Values

In one of my projects I ran into a requirement where the end user needs to be presented with default values in the table filters. This sounds like it is a common requirement, which is easy to implement. However it proved to be not so common, as it is not in the documentation nor are there any Blogpost to be found that talk about this feature. In this blogpost I describe how to implement this. The Use Case Explained Users of the application would typically enter today's date in a table filter in order to get all data that is valid for today. They do this each and every time. In order to facilitate them I want to have the table filter pre-filled with today's date (at the moment of writing July 31st 2015). So whenever the page is displayed, it should display 'today' in the table filter and execute the query accordingly. The problem is to get the value in the filter without the user typing it. Lets first take a look at how the ADF Search and Filters are implemented by

How to: Adding Speech to Oracle Digital Assistant; Talk to me Goose

At Oracle Code One in October, and also on DOAG in Nurnberg Germany in November I presented on how to go beyond your regular chatbot. This presentation contained a part on exposing your Oracle Digital Assistant over Alexa and also a part on face recognition. I finally found the time to blog about it. In this blogpost I will share details of the Alexa implementation in this solution. Typically there are 3 area's of interest which I will explain. Webhook Code to enable communication between Alexa and Oracle Digital Assistant Alexa Digital Assistant (DA) Explaining the Webhook Code The overall setup contains of Alexa, a NodeJS webhook and an Oracle Digital Assistant. The webhook code will be responsible for receiving and transforming the JSON payload from the Alexa request. The transformed will be sent to a webhook configured on Oracle DA. The DA will send its response back to the webhook, which will transform into a format that can be used by an Alexa device. To code

ADF 11g Quicky 3 : Adding Error, Info and Warning messages

How can we add a message programatically ? Last week I got this question for the second time in a months time. I decided to write a short blogpost on how this works. Adding messages is very easy, you just need to know how it works. You can add a message to your faces context by creating a new FacesMessage. Set the severity (ERROR, WARNING, INFO or FATAL ), set the message text, and if nessecary a message detail. The fragment below shows the code for an ERROR message. 1: public void setMessagesErr(ActionEvent actionEvent) { 2: String msg = "This is a message"; 3: AdfFacesContext adfFacesContext = null; 4: adfFacesContext = AdfFacesContext.getCurrentInstance(); 5: FacesContext ctx = FacesContext.getCurrentInstance(); 6: FacesMessage fm = 7: new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, ""); 8: ctx.addMessage(null, fm); 9: } I created a simple page with a couple of buttons to show the result of setting the message. When the but