Saturday, May 17, 2014

ADF Mobile Dependent (Cacading) LOV

This blog is to show how we can create depending (or cascading) LOVs using ADF Mobile

Steps are
1. Create client classes to store data that needs to appear in LOVs
2. Create datacontrol class to hold LOV data in a list (array) and provide mechanism to refresh LOV data
3. Create select-one-choice on page using datacontrol
4. Set valuechange listner of LOV1 to refresh dataprovider of LOV2.



In this example I will create a Country/State LOVs

Country LOV will have India, US values. and State LOV will show states of India or US.

==============================

Step 1. Create client classes to store data that needs to appear in LOVs

To represent country and states we need two classes Country, State

Country.java 
package com.san.mobile.client;

public class Country {
    private String countryName;
  
    public Country( String countryName) {
        super();
        this.countryName = countryName;
    }


    public void setCountryName(String countryName) {
        this.countryName = countryName;
    }

    public String getCountryName() {
        return countryName;
    }

}




State.java
package com.san.mobile.client;

public class State {
    private String stateName;
    public State(String stateName) {
        super();
        this.stateName = stateName;
    }

    public void setStateName(String stateName) {
        this.stateName = stateName;
    }

    public String getStateName() {
        return stateName;
    }
}




========================================================

Step 2. Create datacontrol class to hold LOV data in a list (array) and provide mechanism to refresh LOV data 

package com.san.mobile.dc;

import com.san.mobile.client.Country;
import com.san.mobile.client.State;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import oracle.adfmf.framework.api.AdfmfJavaUtilities;
import oracle.adfmf.java.beans.ProviderChangeListener;
import oracle.adfmf.java.beans.ProviderChangeSupport;

public class DependentLOVDC {
    private List countries = new ArrayList();
    private List states = new ArrayList();
  
    protected transient ProviderChangeSupport providerChangeSupport = new ProviderChangeSupport(this);
    public DependentLOVDC() {
        super();
      
        //NOTE: You can call a webservice here to get actual country data.
      
        countries.add(new Country("India"));
        countries.add(new Country("US"));
    }
  
    public void addProviderChangeListener(ProviderChangeListener l) { 
         providerChangeSupport.addProviderChangeListener(l);
       }
       public void removeProviderChangeListener(ProviderChangeListener l) { 
         providerChangeSupport.removeProviderChangeListener(l);
       }
     
     
    public Country[] getCountries(){      
        return (Country[])countries.toArray(new Country[countries.size()]);      
    }
  
  public State[] getStates(){
       
        states = new ArrayList();
            Map pageFlow = (Map)AdfmfJavaUtilities.evaluateELExpression("#{pageFlowScope}");
            String countryName = (String)pageFlow.get("pCountryName");
                      
           
            if(countryName == null || "India".equals(countryName)){
                //NOTE: You can call a webservice here to get actual country data.
                states.add(new State("Delhi"));
                states.add(new State("Karnataka"));
            }
            else  if("US".equals(countryName)){
                //NOTE: You can call a webservice here to get actual country data.
               states.add(new State("California"));
                states.add(new State("Georgia"));
            }
           
           
            return (State[])states.toArray(new State[states.size()]);
           
        }
  
  
        public void refresh(){
            providerChangeSupport.fireProviderRefresh("states");
        }
      
    }


In above class I mainly have two getters, which returns list of countries and states. State getter is returning based on pageFlow variable pCountryName. It means if on selection of country we can set pCountryName in pageFlow. Which is easy.
I have also added provider change support on this class by adding 

protected transient ProviderChangeSupport providerChangeSupport = new ProviderChangeSupport(this); 

and 


  public void addProviderChangeListener(ProviderChangeListener l) { 
         providerChangeSupport.addProviderChangeListener(l);
       }
       public void removeProviderChangeListener(ProviderChangeListener l) { 
         providerChangeSupport.removeProviderChangeListener(l);
       }  



There are two big problems we need to resolve
1. How would we ask framework to execute getStates once we have selected a project.

2. How would we ask framework to refresh UI LOV if getStates returns new set of data. 

Both of this can be done using providerChangeSuuport. You just need to add a refresh method on this class as 


public void refresh(){
            providerChangeSupport.fireProviderRefresh("states");
        }


Expose this class as a datacontrol. For this right click on
DependentLOVDC and select 'Create data control'.
You will wee following structure in Data Controls. 



 











===================================================================
Step 3. Create select-one-choice on page using datacontrol

 Drag and drop countries/states on your page and create select-one-choice as shown below.




















Select OK on next popup.

You will get following code in your amx

    <amx:selectOneChoice value="#{bindings.countries.inputValue}" label="#{bindings.countries.label}" id="soc1">
      <amx:selectItems value="#{bindings.countries.items}" id="si1"/>
    </amx:selectOneChoice>
    <amx:selectOneChoice value="#{bindings.states.inputValue}" label="#{bindings.states.label}" id="soc2">
      <amx:selectItems value="#{bindings.states.items}" id="si2"/>
    </amx:selectOneChoice>


AS we know in ADF when you select a choice list value and try to use valueChangeEvent.getNewValue(), it returns index but not the actual value. To avoid using valueChangeEvent.getNewValue() we can create an attribute binding in pageDef as 



================================================================
4. Set valuechange listner of LOV1 to refresh dataprovider of LOV2.


Create method action binding for refresh method of DependentLOVDC in pageDef.























Create a bean and register it as usual. Write valuechangelistener on country LOV as

    public void handleCountrySelection(ValueChangeEvent valueChangeEvent) {
        // Add event code here...
       
        Map pageFlow = (Map)AdfmfJavaUtilities.evaluateELExpression("#{pageFlowScope}");
        String countryName = (String)valueChangeEvent.getNewValue();
       
        pageFlow.put("pCountryName", countryName);
       
        MethodExpression me = AdfmfJavaUtilities.getMethodExpression("#{bindings.refresh.execute}", Object.class, new Class[] {});
        me.invoke(AdfmfJavaUtilities.getAdfELContext(), new Object[]{});
    }


======================================================

Thats it.

Code flow is like

1. System create both lovs using datacontrol data provider
2. On selection of country, valuechange listener runs. Listener executes a bean method, which sets pageFlow variable pCountryName and calls refresh method of binding. Binding refresh points to refresh method of datacontrol.
3. Refresh method asks providerChangeSupport to refresh "states" dataprovider, which is nothing but getStates method of DependentLOVDC. Once data is changed, providerchangesupport will refresh on UI components showing data from that dataprovider.

Disclaimer: Any views or opinions presented in this blog are solely those of the author and do not necessarily represent those of the company.