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.

Friday, February 10, 2012

ADF 11g Quicky 4 : Where is my cursor ?

In my current project I encountered a usability issue. The users where unable to see where the cursor was located. It was flickering, but on a page with ,multiple input components it was very difficult to locate the field where the cursor was located. I decided to help them out and that proofed to be both very simple and in the end very helpful. I used skinning to give the active field a colored background.



First I defined a skin in my trinidad-config.xml

 <?xml version="1.0" encoding="windows-1252" ?> 
<skins xmlns="http://myfaces.apache.org/trinidad/skin">
<skin>
<id>custom.desktop</id>
<family>custom</family>
<extends>fusion.desktop</extends>
<render-kit-id>org.apache.myfaces.trinidad.desktop</render-kit-id>
<style-sheet-name>skin/custom.css</style-sheet-name>
</skin>
</skins>

Next I made sure that the application uses the skin by defining it in the trinidad-config.xml
 <?xml version="1.0" encoding="windows-1252"?>
<trinidad-config xmlns="http://myfaces.apache.org/trinidad/config">
<skin-family>custom</skin-family>
</trinidad-config>

Finally I created the corresponding style sheet and added entries for focus pseudoclass.
   
af|inputText::content:focus {background-color:rgb(214,214,255);}
af|inputDate::content:focus {background-color:rgb(214,214,255);}
af|inputListOfValues::content:focus {background-color:rgb(214,214,255);}

The result is highlighted fields whenever the cursor is in it.
For an LOV field:

For a date field:

For an inputtext field:


Imagine the effect on a page with several dozens of inpt fields....

Monday, February 06, 2012

ADF 11g : Fancy Master Detail or how to Highlight Related Detail Records

Last week I a had a rather interesting question: Is it possible to highlight related data that is in different af:table components ? Sure you can, so I decided to write a simple example application, and share the knowledge in this post.

The use Case.
After selecting a row in one table I need to highlight related rows in another table.

The implementation.
To implement this, I create a page based on ADF Business Components for Countries and Locations, both from the HR schema.
Note that, in order to make these tables behave independently, I do not use a viewlink between countries and locations.
This means that there will be no master detail relationship and that the locations table will always show all locations, not only the ones in the selected country.


The way to go here is :
a) Select 1 country in the left table
b) In the selectionListener of that country table add each and every location that is in the selected country to the selectedRowKeys of the locationsTable.

The Challenges.
The First challenge is the creation of the multiselect table component containing the Locations. Now why would that be a challenge. I allready described in one of my previous posts where I ran into an issue with mutliselection: The developers guide (22.5.1) has the solution. DO NOT check ‘enable selection’ when you want to use multiple selection in a table. And I do need mutliselection here because I want to select all related records. So create the table without selectionListener and selectedRowKeys attributes.

Next step is the creation of a custom TableSelectionHandler. The Handler is needed, because when selecting a row in the countries table, I need to do more then just selecting that row; I also need to process the selected row and match it with available Locations. This custom selectionHandler is described by Frank Nimphius in his book. Code for the Listener is in the fragment below.
1:  public static void makeCurrent(SelectionEvent selectionEvent){  
2: RichTable _table = (RichTable) selectionEvent.getSource();
3: CollectionModel _tableModel = (CollectionModel) _table.getValue();
4: JUCtrlHierBinding _adfTableBinding = (JUCtrlHierBinding) _tableModel.getWrappedData();
5: DCIteratorBinding _tableIteratorBinding = _adfTableBinding.getDCIteratorBinding();
6: Object _selectedRowData = _table.getSelectedRowData();
7: JUCtrlHierNodeBinding _nodeBinding = (JUCtrlHierNodeBinding) _selectedRowData;
8: //get the row key from the node binding and set it as the current row in the iterator
9: Key _rwKey = _nodeBinding.getRowKey();
10: _tableIteratorBinding.setCurrentRowWithKey(_rwKey.toStringFormat(true));
11: }

I call this method in the selectionListener of the Countries table.
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: selectedRowKeys="#{bindings.CountriesView2.collectionModel.selectedRow}"
8: selectionListener="#{pageFlowScope.HighLightBean.onCountryTableSelect}"
9: rowSelection="single" id="t1"
10: styleClass="AFStretchWidth>
11: <af:column ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

The onCountryTableSelect method, not only calls the makeCurrent() method in my GenericTableSelectionHandler, but it also calls out to the matchEm method that will match the current Country, to all available Locations.
1:  public void onCountryTableSelect(SelectionEvent selectionEvent) {  
2: // your pre-trigger code goes here ...
3: GenericTableSelectionHandler.makeCurrent(selectionEvent);
4: //your post-trigger code goes here ...
5: DCIteratorBinding conIter = ADFUtils.findIterator("CountriesView2Iterator");
6: matchEm(conIter.getCurrentRow());
7: }

You might be tempted to use setSelectedRowKeys() on the LocationsIterator. This however doesn't work. You will need to use the table's value which is an instance of CollectionModel. This sounds complicated perhaps, but in fact it isn't.
1:  private void matchEm(Row r) {  
2: RowKeySetImpl newSelection = new RowKeySetImpl();
3: locTable.getSelectedRowKeys().clear();
4: String countryId = (String)r.getAttribute("CountryId");
5: //the CollectionModel provides access to the ADF Binding for this table
6: CollectionModel model = (CollectionModel)locTable.getValue();
7: int rowCount = model.getRowCount();
8: for (int i = 0; i &lt; rowCount; i++) {
9: model.setRowIndex(i);
10: Object rowkey = model.getRowKey();
11: JUCtrlHierNodeBinding rowdata = (JUCtrlHierNodeBinding)model.getRowData(i);
12: Row loc = rowdata.getRowAtRangeIndex(i);
13: if (loc.getAttribute("CountryId").toString().equalsIgnoreCase(countryId)) {
14: System.out.println("found a match for locations " + loc.getAttribute("City"));
15: System.out.println("adding key " + loc.getKey());
16: newSelection.add(rowkey);
17: }
18: }
19: locTable.setSelectedRowKeys(newSelection);
20: AdfFacesContext.getCurrentInstance().addPartialTarget(locTable);
21: }

Now what this code does is the following:
It gets all rows that are in the table's ColectionModel, and for each of these rows it gets the rowkey.
Whenever the CountryId in that row is the same as the selected country, the rowkey is added to the rowkeyset containing the new selection.http://www.blogger.com/img/blank.gif
Finally the, rowkeyset is used to set the selectedRowKeys of the table, and the table is added to the partialtargets using following code:

The result.
When you run the application, you will see the result. Select a country in the left table, and all related locations are selected in the right table.

Change the country, and see how selected locations are also updated.


Resources.
You can download the sample workspace here.
This post was originally published on the amis technology blog.

Wednesday, October 19, 2011

ADF 11g : Query Component with ‘dynamic’ view criteria

In my current project use a lot of re usable taskflows. In one particular situation I needed exactly the same taskflow to be re-used with one tiny small difference: The displayed query component needed to have different fields compared to page in the base taskflow. Now there are lots of possible solutions (two query components and a switcher, or two query components using the rendered property). I choose a different solution using ternary operator and EL in the page definition.

Use case

I need to display a different query component when the taskflow is started in a specific mode. For instance I can startup the taskflow a being a HR manager, or I can startup the taskflow as being an employee. In the first case I need to be able to search on items like salary and commission and hiredate. In the second case I need to be able to search on firstname. lastname and more stuff like that, and I can only see employees in my own department.

Setup:

Lets start with the creation of default business components for the Employee table. Next step is the creation of view criteria. I need two view criteria. The first one is called ManagerVC, the second on EmployeeVC.




With this in place I create a simple taskflow with a pagefragment. The taskflow has an inputParameter called 'startupMode'. The pagefragment contains a table with a query component. This is easy to create by dragging the Employees collection from the datacontrol onto the pagefragment and pick "query panel with table".



Make sure to have single row selection enabled.



I also create a testpage in order to run my taskflow. The testpage is a rather simple page containing two buttons, and a bean to hold the value of the button that is pushed.
 <af:toolbar id="t1">  
<af:commandToolbarButton text="Start as Manager" partialSubmit="true" id="ctb1">
<af:setPropertyListener from="mgr" to="#{TesterBean.buttonPushed}" type="action"/>
</af:commandToolbarButton>
<af:commandToolbarButton text="Start as Employee" id="ctb2"partialSubmit="true">
<af:setPropertyListener from="emp" to="#{TesterBean.buttonPushed}" type="action"/>
</af:commandToolbarButton>
</af:toolbar>

I drop my taskflow on the page as a region. I link the inputparameter of the taskflow to the buttonPushed property. Don't forget to set the refresh option to 'ifNeeded', so the region will refresh whenever the input paramaters change.



When I run the page you will see the querycomponent as expected.



The trick:
Now take a look what happened when the query component was created. The page definition states contains an executable binding for the search region.
 <searchRegion Criteria="CriteriaForManager"  
Customizer="oracle.jbo.uicli.binding.JUSearchBindingCustomizer"
Binds="EmployeesView1Iterator"
id="CriteriaForManagerQuery"/>

This is exactly the place where I will use an EL expression (yes you can do that) to dynamically set the viewcriteria to be used by the query component. Lets change the "criteria" attribute. Whenever the taskflow is started by a manager I want the CriteriaForManager viewcriteria to be used by the querycomponent, and in other cases the CriteriaForEmployee.
 <searchRegion Criteria="#{pageFlowScope.startupMode eq 'mgr' ? 'CriteriaForManager' : 'CriteriaForEmployee'}"  
Customizer="oracle.jbo.uicli.binding.JUSearchBindingCustomizer"
Binds="EmployeesView1Iterator"
id="CriteriaForManagerQuery"/>

Now this is really all I changed. No changes to the page whatsoever. When I run the page after this minor change, depending on the startupmode, a different querycomponent is rendered. Or actually, the querycomponent stays the same, only the viewcriteria used are different.



Resources:
This post was initially posted by me on the AMIS technology blog.
The workspace can be downloaded here.

Monday, October 03, 2011

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 button is pressed the message is shown.
1:  <?xml version='1.0' encoding='UTF-8'?>  
2: <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
3: xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
4: xmlns:f="http://java.sun.com/jsf/core">
5: <af:inputText label="Label 1" id="it1"></af:inputText>
6: <af:inputText label="Label 2" id="it2"
7: binding="#{pageFlowScope.msgsBean.showMessage}"></af:inputText>
8: <af:inputText label="Label 3" id="it3"></af:inputText>
9: <af:commandButton text="error" id="cb1"
10: actionListener="#{pageFlowScope.msgsBean.setMessagesErr}"/>
11: <af:commandButton text="info" id="cb2"
12: actionListener="#{pageFlowScope.msgsBean.setMessagesInf}"/>
13: <af:commandButton text="warn" id="cb3"
actionListener="#{pageFlowScope.msgsBean.setMessagesWrn}"/>
14: 15: actionListener="#{pageFlowScope.msgsBean.setMessagesFat}"/>
16: <af:commandButton text="warn for comp" id="cb4"
17: actionListener="#{pageFlowScope.msgsBean.setMessagesWrnForComp}"/>
18: </jsp:root>



A special case is the message that is directly related to a component. For that I use the button that calls the "setMessageForComp" method combined with an inputtext field that is bound to a bean : binding="#{pageFlowScope.msgsBean.showMessage}.
The code behind this button is slightly different from the one show in the java fragment earlier. The important part is that the message is specifically added to the component : getShowMessage().getClientId(ctx).

1:   public void setMessagesWrnForComp(ActionEvent actionEvent) {  
2: // Add event code here...c
3: String msg = "This is a message";
4: AdfFacesContext adfFacesContext = null;
5: adfFacesContext = AdfFacesContext.getCurrentInstance();
6: FacesContext ctx = FacesContext.getCurrentInstance();
7: FacesMessage fm =
8: new FacesMessage(FacesMessage.SEVERITY_WARN, msg, "");
9: ctx.addMessage(getShowMessage().getClientId(ctx), fm);
10: }
11: public void setShowMessage(RichInputText showMessage) {
12: this.showMessage = showMessage;
13: }
14: public RichInputText getShowMessage() {
15: return showMessage;
16: }
17: public void setMessagesWrnForComp(ActionEvent actionEvent) {
18: // Add event code here...c
19: String msg = "This is a message";
20: AdfFacesContext adfFacesContext = null;
21: adfFacesContext = AdfFacesContext.getCurrentInstance();
22: FacesContext ctx = FacesContext.getCurrentInstance();
23: FacesMessage fm =
24: new FacesMessage(FacesMessage.SEVERITY_WARN, msg, "");
25: ctx.addMessage(getShowMessage().getClientId(ctx), fm);
26: }
27: public void setShowMessage(RichInputText showMessage) {
28: this.showMessage = showMessage;
29: }
30: public RichInputText getShowMessage() {
31: return showMessage;
32: }





The code for this quicky can be downloaded here.

Sunday, September 18, 2011

Announcement – Free ADF Webinar for ODTUG

This Tuesday I will present a webinar for ODTUG. In this webinar I will talk about how to provide management information from within your ADF application. Topics : ADF DVT's, Google Maps, MS Excel, and Reporting Solutions.

Tuesday, September 20, 2011, 3:00 PM - 4:00 PM EST
Reaching out from ADF: Management Information using ADF-DVT, Google Maps, MS Excel, and Oracle Reports. Luc Bors, AMIS Services

Usually applications serve purposes such as data entry and process support. Another purpose is to provide management information.In your ADF 11g application you can use Oracle ADF Data Visualization Tools (ADF-DVT) but there are also other options that are not necessarily part of the ADF Framework. This session gives an overview of your possibilities and will answer the following questions:
  • How to integrate ADF-DVT graphs in your application and how to make them interactive for your business users?
  • How to use ADF-DVT maps in your application and how to interact with Google maps?
  • How to interact with Excel?
  • How to add reporting to your application and can you protect your investment in Oracle Reports?
You will learn how to use ADF to provide management information and how to interact with third party tools to add extra functionality to your ADF Application.

You can still register for this webinar. Registration Web Link

Wednesday, August 31, 2011

ADF 11g : Using the ActiveRowKey property

In ADF 11g Release 2, the ADF Table component has a property called 'ActiveRowKey'. According to documentation, this represents the row that is currently active on the client. In click-to-edit mode, the active row will be made editable and is brought into view (if not already visible). Upon initial display, the click-to-edit component defaults the active row to the first visible row.

In this post I will show you how to use the activeRowKey programmatic.

Use Case.

Imagine the following use case: I have a 'click-to-edit' table and I added a 'filter' field with a button in the surrounding panel collection. User enters (in this case) a last name and pushes the button. The table scrolls to the first row that matches the criteria, and the row is active to edit.

Implementation.

I create an ADF table based on ADF business components for the Employees table.
It's a clickToEdit table with singleSelection. After creating it I bind it to a bean. I'll show later why I need to do that. The table code looks like this:
1:        <af:table value="#{bindings.Employees1.collectionModel}" var="row"  

2: rows="#{bindings.Employees1.rangeSize}"
3: emptyText="#{bindings.Employees1.viewable ? 'No data to display.' : 'Access Denied.'}"
4: fetchSize="#{bindings.Employees1.rangeSize}"
5: rowBandingInterval="0"
6: selectionListener="#{bindings.Employees1.collectionModel.makeCurrent}"
7: rowSelection="single" id="t1" editingMode="clickToEdit"
8: selectedRowKeys="#{bindings.Employees1.collectionModel.selectedRow}"
9: binding="#{pageFlowScope.empsBean.empTable}">
10: <af:column ............................


Next I surround it with a panel collection and I turn off a lot of features (also new in R2) of the panel collection. In the toolbar facet I add an inputText component and a commanfbutton. The inputtext will hold the search value, and the button invokes logic to scroll the table and make to row active.

1:       <af:panelCollection id="pc1" featuresOff="viewMenu detach" >  

2: <f:facet name="menus"/>
3: <f:facet name="toolbar">
4: <af:toolbar id="t2">
5: <af:group id="g1">
6: <af:inputText label="enter last name" value="#{pageFlowScope.empsBean.searchValue}" id="it11"/>
7: <af:commandToolbarButton text="goto employee" id="ctb1"
8: actionListener="#{pageFlowScope.empsBean.gotoPressed}"/>
9: </af:group>
10: </af:toolbar>
11: </f:facet>


In the actionListener I first get a hold of the RowSetItetator. For each and every row in the rowSetIterator, I check if it matches the entered search string. I do that in a separate method called matchFound() (see line 8).

1:   public void gotoPressed(ActionEvent actionEvent) {  

2: // Add event code here...
3: DCIteratorBinding it = ADFUtils.findIterator("Employees1Iterator");
4: RowSetIterator rsi = it.getRowSetIterator();
5: RowKeySet oldSelection = empTable.getSelectedRowKeys();
6: if (rsi.first() != null) {
7: Row r = rsi.first();
8: while (rsi.hasNext() && getKey() == null && (!matchFound(r,oldSelection))) {
9: r = rsi.next();
10: }
11: }
12: }


In the matchFound() method I compare the value of the LastName attribute with the value entered by the user (see line 7). If it is a match there are two more things I need to do. First I add the key of the row found to be the activeRowKey (see line 11). Finally I need to make the row current. If I dont do this, it will still work, but by making the row current, it just looks a lot nicer. The trick I use here (see line 13) is a method (line 18) that I borrowed from Frank Nimphius' blog.

1:   private boolean matchFound (Row r,RowKeySet oldSelection){  

2: setKey(null);
3: ArrayList lst = new ArrayList(1);
4: RowKeySetImpl newSelection = new RowKeySetImpl();
5: Key key = null;
6: String rowValue = (String)r.getAttribute("LastName");
7: if (rowValue.toString().contains(searchValue)) {
8: System.out.println("now setting key to " + key);
9: key = r.getKey();
10: lst.add(key);
11: empTable.setActiveRowKey(lst);
12: newSelection.add(lst);
13: makeCurrent(empTable, newSelection, oldSelection);
14: return true;
15: }
16: return false;
17: }
18: private void makeCurrent(RichTable empTable, RowKeySet newCurrentRow, RowKeySet oldCurrentRow) {
19: //To make a row current, we need to create a SelectionEvent which
20: //expects the following arguments: component, unselected_keys,
21: //selected_keys. In our example, we don't have unselected keys and
22: //therefore create an empty RowSet for this
23: SelectionEvent selectionEvent =
24: new SelectionEvent(oldCurrentRow, newCurrentRow, empTable);
25: selectionEvent.queue();
26: AdfFacesContext.getCurrentInstance().addPartialTarget(empTable);
27: }


The works.

Start the application, type some searchText, push the button and of you go.


The row is found, and immediate available for edit.


Resources.

ADF Code Corner Oracle JDeveloper OTN Harvest

A copy of the workspace for this post can be downloaded here.