Friday, November 7, 2014

ADF Tree: Searching child nodes using af:query panel

Problem Description:
We all know how to create a ADF table and provide a query panel for that table. Even we can create a query panel for ADF Tree but it only works with parent most VO. For example if we have Department/Employee tree we can provide a query panel that search only on department. At times we may want to include few employee related attributes like JOB on search panel and when user search based on JOB we just want to show those employees having same Job. By this way filtering is on child nodes and not on parent nodes.

In this blog I will be creating a tree showing department and employee. I will be creating a search panel with Department and Job field. When user search based on department, system will filter department nodes and when user search based on employee, system will filter employee nodes.

We will perform following steps
1. Create Department/Employee VO with a view link and create normal search panel
2. Add Employee Job field in search panel
3. Search Employee rowsets based on Job


1. Create Department/Employee VO with a view link and create normal search panel: This step is simple one and we all know how to create normal search panel. Here are the steps
      a. Create view object DepartmentVO (have department-id as primary key)
      b. Create view object EmployeeVO
      c. Create view link DepartmentVOToEmployeeVO. (Join based on department-id)
      d. Create a view criteria "search" in DepartmentVO and select Department-Name as view-criteria-item.
      e. Add DepartmentVO and EmployeeVO in AM using view link.
      f. Drag view-criteria from data control and drop it on page to create a search panel with adf tree.

At the end of this step you should be ready with a normal search panel on Department-Employee Tree. Search field is just department-name and when user enters department-name system filters Department (parentvo). Within department all employees are visible.

2. Add Employee Job field in search panel: Follow these steps to do it

  a. Create a transient attribute JobId in DepartmentVO (NOTE: Its DepartmentVO no EmployeeVO)

     

  b.  Include this attribute in search view-criteria

     


   Now if you run the page, you will see a new field JobId but it would not do anything at this time. We will make it working in next step.


3. Search Employee rowsets based on Job
Follow these steps

  a. Write following java method in AMImpl.java

        /**
     * This method is used to execute childrowset of employeevo based on job-id mentioned on department vo's view criteria.
     * Logic:
     *   1. Select JobId value/operator from 'search' view criteria of DepartmentVO
     *   2. Create and apply a new dynamic viewcriteria on employeeVO based on JobId value/operator
     *   3. Iterate of all department rows and find out their employeeVO row-set. Execute that rowset.
     */
    public void queryDepartment(){
        ViewObjectImpl deptVO = this.getDepartmentVO1();
        ViewCriteria vc = deptVO.getViewCriteria("search");
        ViewCriteriaRow vcr = (ViewCriteriaRow)vc.get(0);
        String jobId = (String)vcr.getAttribute("JobId");
        String operator = vcr.getOperator("JobId");

       
        if(deptVO.first() != null){
            ViewObjectImpl empVO = (ViewObjectImpl) ((RowSet)deptVO.first().getAttribute("EmployeeVO")).getViewObject();
           
           
             ViewCriteria vc1 = empVO.createViewCriteria();
             ViewCriteriaRow vcRow1 = vc1.createViewCriteriaRow();
            
             vcRow1.setAttribute("JobId", jobId);
             vcRow1.setOperator("JobId", operator);
             vc1.addRow(vcRow1);
            
             empVO.applyViewCriteria(vc1);
        }
       
        RowSetIterator rsi = null;
        try{
            rsi = deptVO.findRowSetIterator("iter");
            if(rsi == null){
                rsi = deptVO.createRowSetIterator("iter");
            }
            rsi.reset();
            while(rsi.hasNext()){
               Row row = rsi.next();
              RowSet empRs = ((RowSet)row.getAttribute("EmployeeVO"));
               empRs.executeQuery();
                             
            } 
            }finally{
            if(rsi != null){
                rsi.closeRowSetIterator();
            }
        }
       
    }


   b. Expose this method to client



   c. Create a method action binding for this exposed method in pageDef file.

   

    d. Create a bean for page and register it in backing bean scope

  

   e. Write a method in searchEmployeeAndDepartment in SearchBean.java. It should perform following tasks

  •          execute department search programmatically.
  •          call AM method queryDepartment which executes all rowsets of employee VO
  •          refresh tree ui component


 Code of  SearchBean.java should look like

import java.util.Map;
import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.application.Application;
import javax.faces.context.FacesContext;
import oracle.adf.model.binding.DCBindingContainer;
import oracle.adf.view.rich.component.rich.data.RichTree;
import oracle.adf.view.rich.component.rich.data.RichTreeTable;
import oracle.adf.view.rich.context.AdfFacesContext;
import oracle.adf.view.rich.event.QueryEvent;
import oracle.binding.BindingContainer;
import oracle.binding.OperationBinding;

public class SearchBean {

    private RichTree deptTree;

    public SearchBean() {
        super();
    }


    public void searchEmployeeAndDepartment(QueryEvent queryEvent) {
        // Add event code here...
        FacesContext fctx = FacesContext.getCurrentInstance();
                ELContext elctx = fctx.getELContext();
                Application app = fctx.getApplication();
                ExpressionFactory exprFactory = app.getExpressionFactory();
              
                MethodExpression methodExpr = exprFactory.createMethodExpression(elctx, "#{bindings.searchQuery.processQuery}", Object.class, new Class[] {QueryEvent.class});
         methodExpr.invoke(elctx, new Object[] {queryEvent});
        
         this.executeOperationBinding("queryDepartment", null);
        
        AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();
        adfFacesContext.addPartialTarget(deptTree);
    }
   
    public static Object executeOperationBinding(String methodAction,
                                                               Map param) {
       
            OperationBinding ob =  getDCBindingContainer().getOperationBinding(methodAction);

            if (param != null) {
                Map paramOps = ob.getParamsMap();
                paramOps.putAll(param);
            }
            Object result = ob.execute();
            return result;
        }
   
    /**
        * Return the Binding Container as a DCBindingContainer.
        * @return current binding container as a DCBindingContainer
        */
       public static DCBindingContainer getDCBindingContainer() {
           return (DCBindingContainer)getBindingContainer();
       }
   
    /**
        * Return the current page's binding container.
        * @return the current page's binding container
        */
       public static BindingContainer getBindingContainer() {
           return (BindingContainer)evaluateEL("#{bindings}");
       }
   
    /**
    * Programmatic evaluation of EL.
    *
    * @param el EL to evaluate
    * @return Result of the evaluation
    */
    public static Object evaluateEL(String el) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ELContext elContext = facesContext.getELContext();
        ExpressionFactory expressionFactory =
        facesContext.getApplication().getExpressionFactory();
        ValueExpression exp =
        expressionFactory.createValueExpression(elContext, el,
        Object.class);
   
        return exp.getValue(elContext);
    }


    public void setDeptTree(RichTree deptTree) {
        this.deptTree = deptTree;
    }

    public RichTree getDeptTree() {
        return deptTree;
    }
}






   f. Bind tree with bean's deptTree property

      

   g. Change af:query's querylistener and point it to searchEmployeeAndDepartment method of bean.
   


That's it. You can run your page now. You should see following result



The logic is
1. On search button click execute a bean method
2. Bean method will excute department search as normal
3. Bean method will also call AM method which will execute each employee rowsets based on Job-id
4. Bean method will refresh tree component.


Few things that we can try
a. Do not execute all row-set of employee vos but only those which are already open. Yes set criteria in all row-sets so that if user opens other, criteria is applied.

b. If we only specify JobId and there are departments which does not have employee with that job, we should not be showing those departments. We can use Exist operator (view-link) in DepartmentVO for that but we need to change AM code to refer job-id from nested row. Planning to implement it in next blog.

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

7 comments:

  1. Nice blog sanjeev, Good work :)
    I have also posted about this topic
    http://www.awasthiashish.com/2014/07/search-on-filtering-child-nodes-of.html

    ReplyDelete
  2. Really helpful. If we have view criteria in master and add child attributes to it. I could able to do search properly. But the only problem is with reordering query fields in my case. This help to solve that issue.

    ReplyDelete
    Replies
    1. Good to know that it helped.

      Thanks
      Sanjeev

      Delete
  3. source code please

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Did the next blog get created, which shows how to handle the case where the JobId is provided, but not the DepartmentName?

    ReplyDelete
  6. Hi
    i need demo for panel accordin tree parent child relationship
    e.g panel accordin parent and their child also shows as panel accordin....

    ReplyDelete