Showing posts with label ViewCriteria. Show all posts
Showing posts with label ViewCriteria. Show all posts

22 Nov 2015

Dynamic View Criteria UI Hints

In this post I am going to show how we can manipulate view criteria UI hints (such as Show in List, Search Region Mode, Show Operators,  etc.) dynamically on-the-fly. Let's consider a use case when we need to show or hide a view criteria in the drop down list of af:query component depending on user permissions.

Actually, it's very easy. We have to override ViewObjectImpl method getViewCriteria(String name) like this:

@Override
public ViewCriteria getViewCriteria(String name)
{
  ViewCriteria vc = super.getViewCriteria(name);
  if (isCriteriaPermitted(name))
  {
    vc.setProperty(ViewCriteriaHints.CRITERIA_SHOW_IN_LIST,
                   ViewCriteriaHints.CRITERIA_HINT_TRUE);     
  }
  else
  {
    vc.setProperty(ViewCriteriaHints.CRITERIA_SHOW_IN_LIST,
                   ViewCriteriaHints.CRITERIA_HINT_FALSE);
  }
  return vc;
}

isCriteriaPermitted in this code snippet is a custom method returning whether the view criteria is available for the user on af:query component. The getViewCriteria method can be overridden in your base ViewObjectImpl class in order to provide a generic solution for this use case across the application.

That's it! 


20 Apr 2015

Altering LOV View Criteria on-the-fly

In this post I am going to show how we can programmatically modify a view criteria which is applied to the view accessor view object at the LOV's popup window.

Let's consider a simple example. There is a page with Employee LOV on it:


Besides Employee LOV there is Min Salary field on the page. This field stores its value in a managed bean property. Users use this field in order to force the Employee LOV to show only employees whose salary is not less than required. So, basically, if Min Salary is not empty, the LOV dialog should look like this:



The LOV's view object (VEmployees) has a corresponding view criteria VEmployeesCriteria:


What we're going to do is to set up at run-time the minimum salary value in the LOV's launchPopupListener:

 <af:inputListOfValues id="ilov1"
   launchPopupListener="#{viewScope.TheBean.lovPopupListener}"

And a corresponding managed bean method is going to look like this:

public void lovPopupListener(LaunchPopupEvent launchPopupEvent) {
    UIXInputPopup lovComponent = (UIXInputPopup) launchPopupEvent.getSource();

    //Get LOV's View Criteria
    ListOfValuesModelImpl model = (ListOfValuesModelImpl) lovComponent.getModel();
    ViewCriteria vc = model.getCriteria();

    //Get first View Criteria Row
    ViewCriteriaRow vcr = (ViewCriteriaRow) vc.getRows().get(0);

    //Set up View Criteria Item
    vcr.setAttribute("Salary", getMinSalary())
}

The sample application for this post can be downloaded here. It requires JDeveloper 12.1.3.

That's it!



17 Mar 2013

Association Consistency and View Criterias

There is very powerful and useful feature in ADF BC - "association consistency". It guaranties that when we create a new entity row or update an entity attribute, this change is going to be replicated to the view objects based on this entity.
Let's say, for example, we have two view objects VListDept and VRecordDebt based on the same entity Departments. We use the VListDept view object on a browse view activity with a table component, and VRecordDebt view object is used on create/update view activities. So, when we create a new record via the VRecordDebt view object, we expect it to appear in the VListDept view object, because it is based on the same entity. This is the default framework behavior, we rely on it and design our applications in accordance with this feature.
But we should be aware of some hidden rock connected to VO's view criterias. When we apply any view critirias to a view object at run time, the framework checks whether there is any applied view criteria with the database query execution mode. And in such case the framework can not guaranty the consistency and it's going to switch the association consistency mode off at the view object level.
If we want to keep the mode working anyway, we have to set it up manually in the executeQueryForCollection VO's method:

protected void executeQueryForCollection(Object qc, Object[] params, 
         int noUserParams) {
  super.executeQueryForCollection(qc, params, noUserParams);
  setAssociationConsistent(true);
}

That's it!

16 Dec 2012

Building custom where clauses for view criterias

In this post I'm going to show how we can generate custom where clauses for VO's view criterias. Let's consider some example. We have a simple view object:

The ViewObject has a couple of view criterias - Today and Yesterday. We're going to use them in an effort to filter orders submitted today and yesterday correspondingly. Note, that view criterias are absolutely empty:


And we are going to generate where clauses for them manually. The ViewObjectImpl class has a special extension point for cases like this one. We're going to override getCriteriaAdapter() method. The method should return some implementation of CriteriaAdapter interface. By default, the method returns null and the framework takes care of building where clauses in a standard way.
So, our implementation of CriteriaAdapter interface is going to look like this:

public class CustomCriteriaAdapter implements CriteriaAdapter
{

  private static String TODAY_CRITERIA = "Today";
  private static String YESTERDAY_CRITERIA = "Yesterday";


  @Override
  public String getCriteriaClause(ViewCriteria criteria)
  {
    if (criteria.getName().equals(TODAY_CRITERIA)) 
      return "Orderdate = trunc(sysdate)";
    else
      if (criteria.getName().equals(YESTERDAY_CRITERIA))
        return "Orderdate = trunc(sysdate-1)";
       else
         return null;     
  }



And let's return this adapter in getCriteriaAdapter() method of our ViewObjectImpl class:

  private static CustomCriteriaAdapter CUSTOM_CRITERIA_ADAPTER 
    = new CustomCriteriaAdapter();

  public CriteriaAdapter getCriteriaAdapter()
  {
     return CUSTOM_CRITERIA_ADAPTER;
  }


Actually, that's it. For the use-case above this is enough. But what about using Today and Yesterday criterias as nested ones? For example, we have a view criteria containing the Today VC:


In this case our approach will not work. The method getCriteriaClause(ViewCriteria criteria) is going to be invoked by the framework only once per root view criteria. So, in our case, it's going to be invoked for TodayCriteria only.
Let's extend standard CriteriaAdapterImpl class and override getCriteriaClause(AttributeDef[] attrDefs, ViewCriteria criteria) method. This method is going to be invoked recursively for each nested view criteria and view criteria usage. And our implementation of this method is going to look like this:

public class CustomCriteriaAdapter extends CriteriaAdapterImpl 
implements CriteriaAdapter
{

  private static String TODAY_CRITERIA = "Today";
  private static String YESTERDAY_CRITERIA = "Yesterday";

  @Override
  protected String getCriteriaClause(AttributeDef[] attrDefs, 
                                     ViewCriteria criteria) 
  {
    String whereClause = null;
    if (criteria.getName().equals(TODAY_CRITERIA)) 
      whereClause = "Orderdate = trunc(sysdate)";
    else
      if (criteria.getName().equals(YESTERDAY_CRITERIA))
        whereClause = "Orderdate = trunc(sysdate-1)";
       else
         //Let the framework to do the job in all other cases
         whereClause = super.getCriteriaClause(attrDefs, criteria);    
    return whereClause;
  }


That's it!




23 Sept 2012

Dynamic view criterias from where clauses

Let's consider a use-case when a system administrator or a business administrator or even a user can define some user-filters. These user filters are going to be stored in some storage, for example a database, and at run-time an end user can apply these filters to their data in UI. These filters contain just simple Where SQL clauses. For example, we have in the application some entity "Boats" and it has some predefined user filters:

It would be cool to dynamically create and add View Criterias to the VO definition and to work with them in a usual way. For example, we could use standard af:query component, where a user can select a criteria from the list and apply it to the data in a table. But the question is: how to create View Criterias having only simple where clause strings?
A View Criteria consists of view criteria rows, and a view criteria row consists of view criteria items, and a view criteria item consists of a view object attribute, an operation type like equal, less, like or whatever and an operand, literal or bind variable. The framework at run-time generates appropriate where clauses from these criteria items and applies them to the SQL query. But we don’t have all these cool things – an attribute, an operation type and an operand, we just have a where clause string and nothing else.
The trick is to put this string into the value property of the view criteria item and override a view object method getCriteriaItemClause responsible for generating a where clause from a view criteria item:

   @Override
  public String getCriteriaItemClause(ViewCriteriaItem crieriaItem) {
      return (String) crieriaItem.getValue(); 
   }  

The method is just going to return back a criteria item’s value. And we can add a method to the custom ViewDefImpl class:

    private void createViewCriterias(RowIterator userFilters){
      while (userFilters.hasNext()) {
        VAgileEntityFilterRowImpl userFilter = (VAgileEntityFilterRowImpl) userFilters.next();
        //Create a View Criteria
        ViewCriteriaImpl viewCriteria =  (ViewCriteriaImpl) createViewCriteria();
        viewCriteria.setName(userFilter.getName());
        
        //Create a View Criteria Row
        ViewCriteriaRow vcr = viewCriteria.createViewCriteriaRow();
        
        //Get the first attribute from the VO's attribute list
        String firstAttributeName = getAttributeDef(0).getName();
        //And create a View Criteria Item for this attribute
        ViewCriteriaItem vci = new ViewCriteriaItem(firstAttributeName, vcr);
        
        //Set the Where clause string as a value of the 
        //View Criteria Item instance
        vci.setValue(userFilter.getWhereclause());
        
        //Add the View Criteria Item instance to the View Criteria Row
        vcr.addCriteriaItem(firstAttributeName, vci);        
        
        //Add the View Criteria Row instance to the View Criteria
        viewCriteria.add(vcr);
            
        //Add the View Criteria instance to the View Definition
        putViewCriteria(viewCriteria.getName(), viewCriteria);
        
      }
    }
 
A View Criteria Item can be created for some VO's attribute only. But we’ve overridden generating of a where clause from a view criteria item. So, it doesn't matter for which particular attribute it's going to be created.  Therefore, we can just take the first attribute from the list and pass it to the ViewCriteriaItem constructor. Actually, that's it. And now we can work with these View Criterias in a usual way.

23 Jun 2012

Dependent LOV in a Search Form

Let's say we have a ViewObject with a LOV enabled attribute.

 The ViewAccessor's ViewObject has a bind variable.


In our use case we have a method in the ViewObjectImpl calculating value for this bind variable.

    public Integer getDealTypeID() {

        return someCalculatedValue;

    }


So, we can put a groovy expression "viewObject.dealTypeID" to the ViewAccessor's definition:


And it works fine everywhere except Search Form. If our LOV enabled attribute is used in a View Criteria and triggered from a Search form we will get something like this: JBO-25077: Name dealTypeID not found in the given object: oracle.adf.model.bean.DCDataVO. The object for the groovy script in case of a SearchForm is DCDataRow that is built internally by the af:query mechanism.
If we change the groovy expression to this "dataProvider.viewCriteria.viewObject.dealTypeID",
the Search Form will start to work, but the normal form will be broken. So, we have to understand somehow where the groovy expression is triggered from. The "adf.isCriteriaRow" expression can help us and our groovy script will look like this:

if (adf.isCriteriaRow)  
  return dataProvider.viewCriteria.viewObject.dealTypeID; 
else   
  return viewObject.dealTypeID;


This script will work in both cases!
But... That's all fine for R1 only . In R2 the expression "adf.isCriteriaRow" is not working in this case and the groovy script for R2 will look a bit ugly:


if (object.getClass().getName().endsWith('DCCriteriaValueRowImpl'))     
   return viewCriteria.viewObject.dealTypeID;  
else   
   return viewObject.dealTypeID;


That's it!.



22 Jan 2012

ViewObject. Working with multiple RowSets.

Let's say we have a ViewObject with quite "heavy" SQL query from performance point of view. We need to show ViewObject's rows in two different tables on the same page and rows in the tables should be filtered in different ways. For example, we have a query from Employees table and we need to show separately clerks in one table and managers in another one. And let's suppose that the query is very heavy, so it's preferable to be executed once only. It would be nice to retrieve records from database once and after that filter them in memory and show in appropriate tables as many times as we need.
In most cases we work with ViewObjects containing one "Default" RowSet only. ViewObject creates it internally and implementing RowSet interface delegates it's methods to the "Default" RowSet. But ViewObject can support more than one RowSet. Except internally created "Default" RowSet we can create "secondary" RowSets. This feature can be very useful for our usecase. We're going to use "Default" RowSet to retrieve all rows from database and create two "secondary" RowSets (clerks and managers) with filtered in memory rows. These "secondary" RowSets we will show in our tables on the page.

In the model of our sample application we have very simple (just for example) entity based ViewObject EmployeesView with two ViewCiterias - EmployeesViewCriteriaClerk and EmployeesViewCriteriaMAN:




In EmployeesViewImpl we have a method that creates new "secondary" RowSet as a result of filtering in memory rows from main "Default" rowset. It uses one of our ViewCriterias to filter the rows:

    private RowSet getRowSet(String rowSetName, String criteriaName) {
        //Find created secondary rowset by name
        ViewRowSetImpl rs = (ViewRowSetImpl)findRowSet(rowSetName);

        //if not found
        if (rs == null) {
            //Create new rowset as a result of filtering in memory rows
            //from DefaultRowSet using ViewCriteria criteriaName
            rs =
               (ViewRowSetImpl)findByViewCriteria(getViewCriteria(criteriaName), -1,
                                     ViewObject.QUERY_MODE_SCAN_VIEW_ROWS);

            //RowSet is created with autogenerated name like "EmployeesView1_...
            //Let's remove the rowset from VO's rowsets table.           
            removeRowSet(rs);
            //Change rowset's name. 
            rs.setName(rowSetName);
            //And put it back. Next time we'll be able to find it by name. 
            addRowSet(rs);

        }

        return rs;

    }


And two methods-clients of the getRowSet method:

    private static String CLERK_RS = "ClerkRowSet";
    private static String MAN_RS = "ManRowSet";
    private static String CLERK_CRITERIA = "EmployeesViewCriteriaClerk";
    private static String MAN_CRITERIA = "EmployeesViewCriteriaMAN";
    
    //Get RowSet for clerks
    public RowSet getClerkRowSet () {
      return getRowSet(CLERK_RS, CLERK_CRITERIA);
    
    }
    
    //Get RowSet for managers 
    public RowSet getManRowSet () {
      return getRowSet(MAN_RS, MAN_CRITERIA);  
    } 


These two methods should be exposed via client interface to be accessible from the binding layer.

In our View we have the following TaskFlow:


The first activity is a method call, executing query of EmployeesView instance. So, on this step we're going to retrieve rows from the database. In order to retrieve all rows, be sure, that Fetch Mode attribute of the ViewObject is set to FETCH_ALL.


View activity MainView contains two tables for clerks and managers. Let's have a look at its pageDef:
  <executables>
    <variableIterator id="variables"/>

    <methodIterator Binds="getClerkRowSet.result"
                    DataControl="AppModuleDataControl" RangeSize="25"
                    id="getClerkRowsIterator"/>
    
    <methodIterator Binds="getManRowSet.result"
                    DataControl="AppModuleDataControl" RangeSize="25"
                    id="getManRowSetIterator"/>
  </executables>
  <bindings>
   
  
    <tree IterBinding="getClerkRowsIterator" id="EmployeesView11">
      <nodeDefinition DefName="com.cs.blog.testfilterVO.model.EmployeesView">
        <AttrNames>
          <Item Value="EmployeeId"/>
          <Item Value="FirstName"/>
          <Item Value="LastName"/>
          <Item Value="Email"/>
          <Item Value="PhoneNumber"/>
          <Item Value="HireDate"/>
          <Item Value="JobId"/>
          <Item Value="Salary"/>
          <Item Value="CommissionPct"/>
          <Item Value="ManagerId"/>
          <Item Value="DepartmentId"/>
          <Item Value="CreatedBy"/>
          <Item Value="CreatedDate"/>
          <Item Value="ModifiedBy"/>
          <Item Value="ModifiedDate"/>
          <Item Value="ActionComment"/>
        </AttrNames>
      </nodeDefinition>
    </tree>
    <tree IterBinding="getManRowSetIterator" id="EmployeesView12">
      <nodeDefinition DefName="com.cs.blog.testfilterVO.model.EmployeesView">
        <AttrNames>
          <Item Value="EmployeeId"/>
          <Item Value="FirstName"/>
          <Item Value="LastName"/>
          <Item Value="Email"/>
          <Item Value="PhoneNumber"/>
          <Item Value="HireDate"/>
          <Item Value="JobId"/>
          <Item Value="Salary"/>
          <Item Value="CommissionPct"/>
          <Item Value="ManagerId"/>
          <Item Value="DepartmentId"/>
          <Item Value="CreatedBy"/>
          <Item Value="CreatedDate"/>
          <Item Value="ModifiedBy"/>
          <Item Value="ModifiedDate"/>
          <Item Value="ActionComment"/>
        </AttrNames>
      </nodeDefinition>
    </tree>
    <methodAction id="getManRowSet" RequiresUpdateModel="true"
                  Action="invokeMethod" MethodName="getManRowSet"
                  IsViewObjectMethod="true" DataControl="AppModuleDataControl"
                  InstanceName="AppModuleDataControl.EmployeesView1"
                  ReturnName="AppModuleDataControl.methodResults.getManRowSet_AppModuleDataControl_EmployeesView1_getManRowSet_result"/>
    <methodAction id="getClerkRowSet" RequiresUpdateModel="true"
                  Action="invokeMethod" MethodName="getClerkRowSet"
                  IsViewObjectMethod="true" DataControl="AppModuleDataControl"
                  InstanceName="AppModuleDataControl.EmployeesView1"
                  ReturnName="AppModuleDataControl.methodResults.getClerkRowSet_AppModuleDataControl_EmployeesView1_getClerkRowSet_result"/>

  </bindings>

Note, that Instead of Iterators we use methodIterators representing results of methodActions getManRowSet and getClerkRowSet and these methodIterators are set in IterBinding attributes of tree bindings EmployeesView11 and EmployeesView12 that are  used by our tables on the page.

The result of our work looks like this:


You can download sample application for this post. It was designed for JDev 11.1.1.2.0.
That's it!



21 Aug 2011

How to apply View Criteria programmatically

Sometimes it could be handy to apply view criteria dynamically at run time.  Let's say I have a simple VO EmployeesView representing data from Emloyees table in HR schema. The VO has view criteria SalaryCriteria:


The criteria filters records with some salary value using bind variable "salary".  On my jspx page I have a table , inputText for salary value and a button:



By default the VO doesn't have any view criteria applied, but when the button is pressed, SallaryCriteria is to be applied with submitted value for salary bind variable. The button's action listener actually calls very simple method in EmployeesViewImpl class:

    public void applySalaryCriteria(Number salary) {
      setsalary(salary);
      setApplyViewCriteriaName("SalaryCriteria");
      executeQuery();
    }


In this use-case I don't have any af:query component on my jspx page and all the job of applying view criteria is going to be done in the model layer.
But there is another use-case when I have af:query on a page and I need to change selected criteria in af:query's component programmatically at run time. In my managed bean I have the following piece of code:

    private RichQuery queryPnl; //Bounded af:query

    //Looking for query descriptor in a list with specified query name
    private QueryDescriptor getQueryDescriptor(List<QueryDescriptor> list, String queryname) {
        QueryDescriptor result = null;
        for (Object qd: list.toArray()) 
            if (((QueryDescriptor) qd).getName().equals(queryname)) result = (QueryDescriptor) qd;
        return result;           
    }
    
    //Selecting criteria with specified name
    private void  selectCriteria(String criteria) {
        QueryModel model = queryPnl.getModel();

        //Looking for needed query decriptor  
        QueryDescriptor qd = getQueryDescriptor(model.getSystemQueries(),criteria);

        //Setting needed query descriptor as current one
        queryPnl.setValue(qd);        
        model.setCurrentDescriptor(qd);
    }


So, when I need to change selected criteria I call selectCriteria method  with specified criteria name. And if I need to apply the criteria and execute the query I call applyCriteria method:

    private void applyCriteria(QueryDescriptor qd) {       
        //Creating new QueryEvent
        QueryEvent queryevent = new QueryEvent(queryPnl, qd);
        
        //And processing this event
        //EmpCriteriaQuery is ID attribute of the searchRegion tag in the pageDef
        invokeMethodExpression("#{bindings.EmpCriteriaQuery.processQuery}",
            Object.class, new Class[]{QueryEvent.class}, new Object[]{queryevent});
    }
    
    //Just utility method to invoke EL expressions 
    public Object invokeMethodExpression(String expr, Class returnType, Class[]  argClasses, Object[] arguments){   
      FacesContext fc = FacesContext.getCurrentInstance(); 
      ELContext elc = fc.getELContext();
      ExpressionFactory exprFactory = fc.getApplication().getExpressionFactory(); 
      MethodExpression methodExpr = exprFactory.createMethodExpression(elc,expr,returnType,argClasses);    
      return methodExpr.invoke(elc,arguments); 
     }

That's all!