Thursday Jan 17, 2013

How to create cascading (depending) auto suggest behavior using BC4J

In continuation to my previous blog on "How to create multi level cascading (dependent) list of values using BC4J", i am trying to leverage a similar usecase using af:autoSuggestBehavior in this article. Though the usecase looks same, the implementation is slightly different for this.

Let us assume a usecase where we have some text fields with auto suggest feature enabled, and their suggested items could be dependent on one other. For ex : Country, State and City.

This could be modeled by having an EO and VO created based on Person table and read-only look-up VOs created based on Country, State and City tables.

model.jpg

VOs.jpg

Implementing the dependency between LOVs is pretty straight forward. However, implementing the dependency between the auto suggest items is not. In order to achieve the dependency, first we would need couple of methods returning current row's CountryId and StateId. We could add them up in the AMImpl class.

public Number getCurrentCountryId(){
return (Number) this.getPersonView1().getCurrentRow().getAttribute("CountryId");
}

public Number getCurrentStateId(){
return (Number) this.getPersonView1().getCurrentRow().getAttribute("StateId");
}

As we need to filter out the States based on the Country and the City based on the Country & State, we would need to modify the query of these two VOs to include a bind variable in the where clause.

whereClause.jpg

For having the auto suggest, we need to have a view criteria defined for all the three look-up VOs (CountryView, StateView and CityView).

CountryVC.jpg

StateVC.jpg

CityVC.jpg

Generate VOImpl classes for Country, State and City VOs (with Include bind variable accessors option checked), and then expose setBindCountryName (in Country VO), setBindStateName (in State VO) and setBindCityName (in City VO) methods as client interfaces.

And the last part on the model is to pass the current row's CountryId and StateId to the Bind Variables defined in the State and City VOs. Also, we need to get the VCs created above to be executed by default (By editing the VO instance in the AM's data model), so that the auto suggest list would be filtered as and when the users type.

CountryViewAM.jpg

StateViewAM.jpg

CityViewAM.jpg

Here, we specify groovy expression for the CountryId and StateId as adf.object.applicationModule.<methodName>. For more information about using groovy expressions, check out this : http://www.oracle.com/technetwork/developer-tools/jdev/introduction-to-groovy-128837.pdf .

With this, we are done with setting up the model layer for the auto suggest dependency.

In the View layer, we would create an ADF Form based on the Person VO, with all the navigation buttons.

PersonJSPX.jpg

In order to construct the onSuggest items, we would create Tree Bindings for Country VO, State VO and City VO, along with method action bindings for the setBindCountryName, setBindStateName and setBindCityName methods.

pagedef.jpg

Now, we could add af:autoSuggestBehavior for CountryId, StateId and CityId fields. Then, add onSuggest methods in backing bean for populating the on suggest items for each fields.

onSuggest method for Country field :

public List onCountrySuggest(String searchCountryName) {
ArrayList<SelectItem> selectItems = new ArrayList<SelectItem>();

System.out.println(searchCountryName);
//get access to the binding context and binding container at runtime
BindingContext bctx = BindingContext.getCurrent();
BindingContainer bindings = bctx.getCurrentBindingsEntry();
//set the bind variable value that is used to filter the View Object
//query of the suggest list. The View Object instance has a View
//Criteria assigned
OperationBinding setVariable = (OperationBinding) bindings.get("setBind_CountryName");
setVariable.getParamsMap().put("value", searchCountryName);
setVariable.execute();
//the data in the suggest list is queried by a tree binding.
JUCtrlHierBinding hierBinding = (JUCtrlHierBinding) bindings.get("CountryView1");


//re-query the list based on the new bind variable values
hierBinding.executeQuery();

//The rangeSet, the list of queries entries, is of type
//JUCtrlValueBndingRef.
List<JUCtrlValueBindingRef> displayDataList = hierBinding.getRangeSet();

for (JUCtrlValueBindingRef displayData : displayDataList){
Row rw = displayData.getRow();
//populate the SelectItem list
selectItems.add(new SelectItem(
(Integer)rw.getAttribute("Id"),
(String)rw.getAttribute("Name")));
}

return selectItems;
}

onSuggest method for State field :


public List onStateSuggest(String searchStateName) {
ArrayList<SelectItem> selectItems = new ArrayList<SelectItem>();

System.out.println(searchStateName);
//get access to the binding context and binding container at runtime
BindingContext bctx = BindingContext.getCurrent();
BindingContainer bindings = bctx.getCurrentBindingsEntry();
//set the bind variable value that is used to filter the View Object
//query of the suggest list. The View Object instance has a View
//Criteria assigned
OperationBinding setVariable = (OperationBinding) bindings.get("setBind_StateName");
setVariable.getParamsMap().put("value", searchStateName);
setVariable.execute();
//the data in the suggest list is queried by a tree binding.
JUCtrlHierBinding hierBinding = (JUCtrlHierBinding) bindings.get("StateView1");


//re-query the list based on the new bind variable values
hierBinding.executeQuery();

//The rangeSet, the list of queries entries, is of type
//JUCtrlValueBndingRef.
List<JUCtrlValueBindingRef> displayDataList = hierBinding.getRangeSet();

for (JUCtrlValueBindingRef displayData : displayDataList){
Row rw = displayData.getRow();
//populate the SelectItem list
selectItems.add(new SelectItem(
(Integer)rw.getAttribute("Id"),
(String)rw.getAttribute("Name")));
}

return selectItems;
}

onSuggest method for City field :

public List onCitySuggest(String searchCityName) {
ArrayList<SelectItem> selectItems = new ArrayList<SelectItem>();

System.out.println(searchCityName);
//get access to the binding context and binding container at runtime
BindingContext bctx = BindingContext.getCurrent();
BindingContainer bindings = bctx.getCurrentBindingsEntry();
//set the bind variable value that is used to filter the View Object
//query of the suggest list. The View Object instance has a View
//Criteria assigned
OperationBinding setVariable = (OperationBinding) bindings.get("setBind_CityName");
setVariable.getParamsMap().put("value", searchCityName);
setVariable.execute();
//the data in the suggest list is queried by a tree binding.
JUCtrlHierBinding hierBinding = (JUCtrlHierBinding) bindings.get("CityView1");


//re-query the list based on the new bind variable values
hierBinding.executeQuery();

//The rangeSet, the list of queries entries, is of type
//JUCtrlValueBndingRef.
List<JUCtrlValueBindingRef> displayDataList = hierBinding.getRangeSet();

for (JUCtrlValueBindingRef displayData : displayDataList){
Row rw = displayData.getRow();
//populate the SelectItem list
selectItems.add(new SelectItem(
(Integer)rw.getAttribute("Id"),
(String)rw.getAttribute("Name")));
}

return selectItems;
}

Once after this, we could bind this to the af:autoSuggestBehavior's suggestedItems property

<af:inputText value="#{bindings.CountryId.inputValue}" label="#{bindings.CountryId.hints.label}"
columns="#{bindings.CountryId.hints.displayWidth}"
autoSubmit="true"
shortDesc="#{bindings.CountryId.hints.tooltip}" id="it3">
<af:autoSuggestBehavior suggestedItems="#{viewScope.AutoSuggestBean.onCountrySuggest}"/>
</af:inputText>
<af:inputText value="#{bindings.StateId.inputValue}" label="#{bindings.StateId.hints.label}"
columns="#{bindings.StateId.hints.displayWidth}"
autoSubmit="true" partialTriggers="it3"
shortDesc="#{bindings.StateId.hints.tooltip}" id="it4">
<af:autoSuggestBehavior suggestedItems="#{viewScope.AutoSuggestBean.onStateSuggest}"/>
</af:inputText>
<af:inputText value="#{bindings.CityId.inputValue}" label="#{bindings.CityId.hints.label}"
columns="#{bindings.CityId.hints.displayWidth}"
autoSubmit="true" partialTriggers="it4"
shortDesc="#{bindings.CityId.hints.tooltip}" id="it5">
<af:autoSuggestBehavior suggestedItems="#{viewScope.AutoSuggestBean.onCitySuggest}"/>
</af:inputText>

Finally, our output would be

output1.jpg

output2.jpg

output3.jpg

Monday Jan 14, 2013

Display Holiday Name in af:calendar

When using af:calendar component in the realtime applications, there is a frequent requirement to display the holdays in it. In this article, we will see how to achieve that.

After setting the environment, we would be expecting the output as shown in the below image.

finalOutput.jpg

Assuming we already have a page with calendar, the individual date level customizations can be done by using DateCustomizer.

For this, we would create a custom class that extends DateCustomizer class.

public class MyDateCustomizer extends DateCustomizer{
public String format(Date date, String key, Locale locale, TimeZone tz)
{
// For illustrative purpose
// Hashmap holding the holiday list
HashMap holidays = new HashMap();
holidays.put(new Date("25-Dec-2012"), "Christmas");
holidays.put(new Date("01-Jan-2013"), "New Year");

if ("af|calendar::month-grid-cell-header-misc".equals(key))
{
return holidays.get(date)!=null?holidays.get(date).toString():null;

}

return null;

}

}

As per the tag doc, following keys are passed to the format method.

  • "af|calendar::day-header-row"
  • "af|calendar::list-day-of-month-link"Year's Day".
  • "af|calendar::list-day-of-week-column"
  • "af|calendar::month-grid-cell-header-day-link"
  • "af|calendar::month-grid-cell-header-misc"
  • "af|calendar::week-header-day-link"

So, in the above code, we return the actual holiday name for the "af|calendar::month-grid-cell-header-misc" key.

In order to use the new DateCustomizer in our calendar, we create an instance of it in the backing bean and bind it to the calendar's dateCustomizer property.

Bean Code :

public class CalendarBean {
private MyDateCustomizer holidays = new MyDateCustomizer(); ;

public CalendarBean() {
}


public void setHolidays(MyDateCustomizer holidays) {
this.holidays = holidays;
}

public MyDateCustomizer getHolidays() {
return holidays;
}
}


Page Source :

<af:calendar id="c1" dateCustomizer="#{viewScope.CalendarBean.holidays}"/>

If needed, the text can be styled to be displayed in different color (as shown in the image at the top). For this, we can have a styleclass in the css

af|calendar::month-grid-cell-header-misc
{
background-color: Yellow;
}

Tuesday Jul 17, 2012

Interpret af:query's queryEvent and display popup to end user using QueryListener

Found an interesting question on OTN. Based on the question, wired a usecase to try out.

Usecase : Show a warning to user when they try to search the records (af:query component), without specifying a criteria / a wild card "%". I.e, when the user tries to query the entire table, show a warning that querying all the records would take some time.

There are three phases in implementing this usecase.

1. Interpret the query event and get the query criteria.
2. Show the popup.
3. Process the interpreted query based on the outcome of the popup.

Before proceeding with the implementation, we'll create a page for assumption.
a. Page contains a af:query component with a resultant table / read-only table.
b. Has a popup to be shown to the end user.
c. Bound to a bean.

We'll now implement it phase by phase.

First of all, we'll create couple of attributes in the bean and generate accessors to them.
    private RichTable empTable;
    private boolean warnUser=true;
   // Set the default queryListener property value of the af:query component
   // for mexpr.
   private String mexpr = "#{bindings.ImplicitViewCriteriaQuery.processQuery}"; 
    private QueryEvent qEvt;

    public void setEmpTable(RichTable empTable) {
        this.empTable = empTable;
    }

    public RichTable getEmpTable() {
        return empTable;
    }



Now, we need to trap the query event of the af:query component to perform the desired task. We can add a queryListener and bind it to the af:query component

  public void processQuery(QueryEvent queryEvent) {
        // store the queryEvent in a bean attribute, to be used in another method.
        qEvt = queryEvent;
        // Reset the flag. This flag would be used to check if the system has to 
        //raise the popup or not
        warnUser=false;
        DCBindingContainer bc =   
        (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();   
         // Get the view criteria that would be applied.
         // findExecutableBinding method takes two parameters.
         // id of the searchRegion executable binding
         // criteria for the searchRegion executable binding
         // Ex : <searchRegion Criteria="__ImplicitViewCriteria__"      
         // Customizer="oracle.jbo.uicli.binding.JUSearchBindingCustomizer"
         //         Binds="EmpView1Iterator" id="ImplicitViewCriteriaQuery"/>
       


 ViewCriteria vc = 
JUSearchBindingCustomizer.getViewCriteria((DCBindingContainer)bc.findExecutableBinding("ImplicitViewCriteriaQuery"),"__ImplicitViewCriteria__");


        ViewCriteriaRow vcr = (ViewCriteriaRow)vc.get(0);
        // Some logic to set the flag. Here, checking if the Ename attribute has
        // no value specified / used a wildcard expression ("%").
        for(int i=0;i<vcr.getAttributeNames().length;i++) {
           
 if(vcr.getAttributeNames()[i] == "Ename" &&  
("%".equals(vcr.getAttributeValues()[i]) || 
vcr.getAttributeValues()[i]==null)) 
                warnUser=true;
        }
 
        if(warnUser) 
            showPopup();
        else 
            executeQuery();
 
    }



showPopup and executeQuery are custom methods to show the popup and to process the query respectively.

       public void showPopup(){
        UIViewRoot root = FacesContext.getCurrentInstance().getViewRoot();
        RichPopup popup = (RichPopup) root.findComponent("p1");
        RichPopup.PopupHints hints = new RichPopup.PopupHints();
        popup.show(hints);
    }

  // This method invokes the method expression used by af:query component programatically
    public void executeQuery(){
        processMethodExpression(mexpr, new Object[] {qEvt}, new Class[] {QueryEvent.class}); 
        AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance(); 
        adfFacesContext.addPartialTarget(empTable); 
 
    }

    private Object processMethodExpression(String methodExpression, Object[] parameters, Class[] expectedParamTypes) { 
        FacesContext fctx = FacesContext.getCurrentInstance(); 
        ELContext elctx = fctx.getELContext(); 
        Application app = fctx.getApplication(); 
        ExpressionFactory exprFactory = app.getExpressionFactory(); 
       
 MethodExpression methodExpr = exprFactory.createMethodExpression(elctx,
 methodExpression, Object.class, expectedParamTypes); 
        return methodExpr.invoke(elctx, parameters); 
        }


Now, we need to bind the custom querListener created above to the af:query component

 <af:query id="qryId2" headerText="Search" disclosed="true"
   value="#{bindings.ImplicitViewCriteriaQuery.queryDescriptor}"
   model="#{bindings.ImplicitViewCriteriaQuery.queryModel}"
   queryListener="#{viewScope.QueryBean.processQuery}".....



We are almost there. Now, when we run the page and query for the records by keeping Ename as null (in the query panel), we would get the popup.

Final step is to handle the user action on the popup and then proceed executing the query / to stop it.

For this, we'll create a dialog listener and bind it to popup.

 public void onDialog(DialogEvent dialogEvent) {
        Outcome o = dialogEvent.getOutcome();
        if(o == Outcome.yes) 
            executeQuery();
 
    }

                <af:popup childCreation="deferred" autoCancel="disabled" id="p1">
                    <af:dialog id="d2" type="yesNo" title="Are you sure?"
                               dialogListener="#{viewScope.QueryBean.onDialog}">
                       
 <af:outputText value="It would be time consuming to query for all 
records. Are you sure you want to continue?" id="ot9"/>
                        <f:facet name="buttonBar"/>
                    </af:dialog>
                </af:popup>



Here is how the runtime would be.

step1.jpg



Enter % for Ename and hit the Search button

" class="mt-enclosure mt-enclosure-image">step2.jpg



Popup with a warning message displayed

step3.jpg


Clicking on Yes / No on the popup performs respective task (perform query / cancel query).

step4.jpg



Monday Nov 14, 2011

Combo LOV - How to display Description for Selected Value instead of ID

Problem

The common problem that any one working with Combo LOV in a faces page faces is that the list of values component shows Id for selected value inspite of the dropdown showing Description for Options.
Unfortunately, this is a known issue with ADF faces for which there is already an enhancement request which we would expect to see in future releases.

Solution

In this article, I discuss a way to overcome this and display Description rather than Id.
In simple words, The workaround is to define LOV on a transient attribute instead of a View Attribute(Probably a Foreign Key attribute) and set the selected value back to View attribute on change of the LOV and make use of this transient attribute instead of View attribute wherever Combo LOV is needed.

Usecase

I take a simple usecase with well known Emp and Dept tables to display an Emp form with Deptno Combo box List of Values with selected value of LOV being the Department name instead of Deptno
Combo_LOV.png

Here are the detailed steps for the same:

- Add a transient attribute to EmpView with the name "Dname"
Transient_Dname_Attr.png

- Define Combo LOV matching Dname and Deptno both (Deptno matching helps in setting value back to Deptno view attribute on change of Dname LOV value)
- Keep a note of List Data Source name(i.e, DeptViewAccessor in screenshot)
Dname_Combo_LOV.png

- Finally, Set the Department name of respective Deptno as default value to transient attribute(To show current department as selected) using below groovy expression:
Dname_Groovy.png



Now, when you run the page you should be able to see the combo lov showing Department name for the selected value and change in LOV automatically updates Deptno attribute.

Incase, anyone don't like to have groovy, they can overwrite getDname method in RowImpl and add this code there.

Wednesday Sep 14, 2011

Paritally restricting user entry in the ADF input text - using Java Script

Recently came across a forum post in which the OP wanted to let the users edit the content of the text field partially.

https://forums.oracle.com/forums/thread.jspa?forumID=83&threadID=2259832

Here is an example. Let us assume the input text contains the following text. "You can edit the content inside { this }". In this, users should be able to edit only the content inside { }. I.e Only "this" should be editable.

To achieve this, we can use a java script method, that tracks the cursor position and ignore the user edits if the cursor position is not between the curly braces. After which, the method would be used in the client listener for the input text.

Example code snippet.



<af:inputText label="Partial Editable Text Item" 
id="it1" value="You can edit the content inside { this }" clientComponent="true" >
<af:clientListener  type="keyPress" method="validateValue"  /> 
           
</af:inputText>
<af:resource type="javascript">
               function validateValue(evt){
                       var inputTxt=document.getElementById('it1::content');
                       var startPos = inputTxt.value.indexOf("{");
                       var endPos = inputTxt.value.indexOf("}");
                       var cursorPos = inputTxt.selectionStart;
                          if (cursorPos &lt; startPos+2 || cursorPos > endPos-1) { 
                                   alert("Cannot Edit");
                                   evt.cancel();
                          }
 }
</af:resource>

More complex example by Frank Nimphius http://blogs.oracle.com/jdevotnharvest/entry/get_social_security_numbers_right
Note : Tested the above snippet successfully in Mozilla Firefox and IE 9.

Thursday Jun 30, 2011

Highlighting new rows in ADF Table

About

This article explains how to hightlight newly inserted rows in an ADF Table without writing any extra java/javascript code.

Introduction

Sometimes we may wish to give more clarification to the end user by differentiating between newly inserted rows and the existing rows(i.e the rows from DB) in a table by highlighting new rows in different color as in the figure shown below.

HIGHLIGHT_ROW_COLOR.JPG
Solution

We can achieve the same by giving following EL to inlineStyle property of every column inside af:table:

#{row.row.entities[0].entityState == 0?'background-color:#307D7E;':''}
Explanation

Here is the explanation for row.row.entities[0].entityState given inside EL which returns the state of the row(i.e, New, Modified, Unmodified, Initialized etc.)

row - Refers to a tree node binding(instance of FacesCtrlHierNodeBinding) at runtime
row.row - Refers to an instance of row that the tree node is based on
row.row.entities[0] - Gets the Entity row at zeroth index. In most of the cases, the table will be based on single entity. If your table is based on multiple entities then the index needs to be given accordingly.
row.row.entities[0].entityState - Gets Entity Object's current Entity-state in the transaction.
(0 - New, Modified - 2, Unmodified - 1, Initialized - -1,  etc.,)

Tuesday Mar 15, 2011

Merge Records in Session bean by using ADF Drag/Drop

[Read More]

Tuesday Dec 14, 2010

Getting all selected rows in ADF Table with multiple rows selection enabled

When we build a web application which contains an ADF Table (with multiselect option), in many cases, we require to get all the selected rows to process through backing bean. This example will illustrate how to achieve that.

Assuming that we already have an application, that contains an ADF Table with multi-selection enabled (i.e the web page look something like the image shown below).

empTableRT-thumb-301x302-20887.jpg

img empTableRT : Table based on Emp

To access the table in the backing bean (through command button click, for ex.), we add an attribute with accessors in the backing bean and bind it to ADF Table

    RichTable empTable;
    public void setEmpTable(RichTable empTable) {
        this.empTable = empTable;
    }

public RichTable getEmpTable() { return empTable; }

code snippet of the af:table in jspx page.

<af:table value="#{bindings.EmpView1.collectionModel}" var="row"
                  rows="#{bindings.EmpView1.rangeSize}"
                  emptyText="#{bindings.EmpView1.viewable ? 'No data to display.' : 'Access Denied.'}"
                  fetchSize="#{bindings.EmpView1.rangeSize}"
                  rowBandingInterval="0"
                  selectedRowKeys="#{bindings.EmpView1.collectionModel.selectedRow}"
                  selectionListener="#{bindings.EmpView1.collectionModel.makeCurrent}"
                  rowSelection="multiple" id="t1"
                  binding="#{backingBeanScope.EmpBean.empTable}">

Let us add a command button in the jspx page to print down the "Ename"s of selected rows.

<af:commandButton text="Print selected Emps" id="cb1" 
      action="#{backingBeanScope.EmpBean.printSelectedEmpNames}"/>

The method printSelectedEmpNames in the backing bean gets the selected row keys from the table, gets the corresponding Enames from the iterator (on which the table is based on) and prints them.
public String printSelectedEmpNames() {
        RowKeySet selectedEmps = getEmpTable().getSelectedRowKeys();    
        Iterator selectedEmpIter = selectedEmps.iterator();
        DCBindingContainer bindings =
                          (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
        DCIteratorBinding empIter = bindings.findIteratorBinding("EmpView1Iterator");
        RowSetIterator empRSIter = empIter.getRowSetIterator();
         while(selectedEmpIter.hasNext()){
           Key key = (Key)((List)selectedEmpIter.next()).get(0);
           Row currentRow = empRSIter.getRow(key);
           System.out.println(currentRow.getAttribute("Ename"));
         }
         return null;
}

We run the page and click button to check the selected rows.

empTableMultiSelect-thumb-321x325-20890.jpg

As we've selected 7 rows, clicking on the "Print selected Emps" button is expected to print 7 Enames.

But..... It just prints only one Ename.

CLARK

Well, this is because of the selectedRowKeys property of the af:table. Let us try removing that and see if we get the expected output.

<af:table value="#{bindings.EmpView1.collectionModel}" var="row"
                  rows="#{bindings.EmpView1.rangeSize}"
                  emptyText="#{bindings.EmpView1.viewable ? 'No data to display.' : 'Access Denied.'}"
                  fetchSize="#{bindings.EmpView1.rangeSize}"
                  rowBandingInterval="0"
selectionListener="#{bindings.EmpView1.collectionModel.makeCurrent}" rowSelection="multiple" id="t1" binding="#{backingBeanScope.EmpBean.empTable}">

Now, when we run the page, select 7 rows and click on the print button, it prints out

JONES
ALLEN
CLARK
WARD
SMITH
MARTIN
BLAKE

That is what we expected. Now, the question is why this happens?. It is because, since value of this selectedRowKeys property is #{bindings.EmpView1.collectionModel.selectedRow}, the selectedRowKeys will contain only the "row which is selected last". By unsetting this attribute, we let the table to push all the selected rows to selectedRowKeys, which will help us during

getEmpTable().getSelectedRowKeys();

in the backing bean. JDeveloper automatically adds this property to the ADF Table when it is created by dragging from the data control. So, removing this property from the af:table would get us desired result.

[Read More]
About

Tips and Tricks from Oracle's JDeveloper & ADF QA

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
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