Recently a question on the OTN JDeveloper & ADF Space caught my interest. The question was how to initialize an af:quickQuery component with a parameter passed to a task flow on load of a page. At first I thought that this would be a simple case of setting a property InitialQueryOverwritten=true as mentioned by Frank Nimphius in his article How-to query af:quickQuery on page load ?, but after a short test it turned out, that this setting only executes the query but can’t be used to initialize the criteria.

This post is about a solution to this problem. The question can be divided into two smaller problems to solve. The first is to pass a parameter to a bounded task flow and use the passed parameter in the bounded task flow. The second problem is to initialize a default query attribute of a af:quickQuery component and execute the query.

Let’s have a look at a sample application.

Start Page

Start Page

On the start page the user can enter a parameter, which is used as input parameter in the second page, which holds a region as bounded task flow with the quick query component. Clicking on the ‚Go Query‘ button passes the entered parameter to a pageFlowScope variable and navigates to the second page.

Start Page Page with initialized af:quickQuery

Start Page with initialized af:quickQuery

As we see, the passed parameter is visible in the quick query component and the table shows the corresponding data in the table.

The first problem mentioned isn’t really one as the solution is well documented. So passing a parameter from an af:inputText to a bounded task flow will only be shown briefly here. The button on the start page uses an af:setPropertyListener to set the parameter to a pageFlowScope variable. On the second page the parameter is passed as input parameter to the bounded task flow which assembles the af:quickQuery.

The images above showing the navigation between the two pages and the region (QuickQuery.jsf) which holds the af:quickQuery.

First Try
The first method I tried to initialize the af:quickQuery was to overwrite the QueryListener of the af:quickQuery component to set the parameter to the default search attribute. The already mentioned property InitialQueryOverwritten=true would then execute the query with the parameter set. This should show the right result in the table. As it turned out, if the property InitialQueryOverwritten is set to true, the QueryListener is not called on load of the page. No change to set the parameter which is passed to the bounded task flow.

Second Try
The next try I used, was a method activity in the bounded task flow and were I tried to set the parameter from this method. This will not work as the component is not present when the method is called as default activity in the task flow. You can set the parameter to the view object and filter the data after it, however, the overwritten property InitialQueryOverwritten then executed the default query again, this time without the parameter. If you set the property to false, you see the data, but the parameter is not set in the af:inputText component.

Final Try: Working solution
The working solution uses a trick which is kind of lazy initializing the component. For this we bind a property of the component to a bean and overwrite the getter method for the property. In the getter we check a private variable of the bean if the component has been called already or not. In case the getter has already been called we just return the value for the property. In case the getter method is called the first time we initialize the component before returning the value of the property.

Let’s look at the af:quickQuery in the region:

value="#{bindings.ImplicitViewCriteriaQuery.quickQueryDescriptor}"
model="#{bindings.ImplicitViewCriteriaQuery.queryModel}"
queryListener="#{bindings.ImplicitViewCriteriaQuery.processQuery}" binding="#{viewScope.QuickQueryBean.quickQuery}"

Two things to note are
1. the component is bound to the viewScope bean QuickQueryBean
2. the searchDesc property is bound to the same QuickQueryBean bean
The component is bound to the bean as a convenience to get the query descriptor easily in the initialization method. To make this save we use a ComponentReference to store the component.

private ComponentReference quickQuery;
/**
* setter for component to ComponentReference
* @param quickQuery the component
*/
public void setQuickQuery(RichQuickQuery quickQuery) {
    this.quickQuery = ComponentReference.newUIComponentReference(quickQuery);
}

/**
* getter for the component from the component reference
* @return
*/
public RichQuickQuery getQuickQuery() {
    if (quickQuery != null) {
        return (RichQuickQuery) quickQuery.getComponent();
    }
    return null;
}

For more information about this technique see Rules and Best Practices for JSF Component Binding in ADF

The lazy initialization is done by binding the searchDesc property to the QuickQueryBean. The trick is that the component has to call the getter for this property to get its value. In the getter in the bean we check a local variable needInit which is set to true when the bean is created each time the page gets loaded.

/**
* getter for a string value names dummy in EL
* @return value of the dummy property
*/
public String getDummy() {
    if (needInit) {
        needInit = false;
        initQuickQuery();
    }
    return "Search";
}

As the bean is in view scope it guarantees that the bean is created each time the page is loaded and stays active until the page is visible. The real work is done in the initQuickQuery() method:

/**
* Initialize the quickQuery component if a parameter tpCityName is found in the pageFlowScope. Once this is done, the pageFlowScope
* variable tpCityName is set to null or removed.
*/
public void initQuickQuery() {
    // get the PageFlowScope Params
    AdfFacesContext adfFacesCtx = AdfFacesContext.getCurrentInstance();
    Map<String, Object> scopePageFlowScopeVar = adfFacesCtx.getPageFlowScope();
    String paramCity = (String) scopePageFlowScopeVar.get("tpCityName");
    if (paramCity != null && !paramCity.isEmpty()) {
        // get query descriptor (the components value property)
        FilterableQueryDescriptor queryDescriptor = (FilterableQueryDescriptor) getQuickQuery().getValue();
        // get the current selected criterion (which should set in the ImplicitViewCriteriaQuery in hte pageDef
        AttributeCriterion attributeCriterion = queryDescriptor.getCurrentCriterion();
        // get the attribute name and check if it is 'City'
        AttributeDescriptor attribute = attributeCriterion.getAttribute();
        String name = attribute.getName();
        // only set parameter if hte attribute matches the parameter
        if ("City".equalsIgnoreCase(name)) {
            attributeCriterion.setValue(paramCity);
            // remove value to allow new one in component
            scopePageFlowScopeVar.put("tpCityName", null);
            // set the parameter to the attributeCriterion
            QueryModel model = getQuickQuery().getModel();
            model.setCurrentDescriptor(queryDescriptor);
            // create a queryEvent and invoke it
            QueryEvent qe = new QueryEvent(getQuickQuery(), queryDescriptor);
            invokeMethodExpression("#{bindings.ImplicitViewCriteriaQuery.processQuery}", Object.class, QueryEvent.class, qe);
        }
    }
}

In this method we check if a parameter named tpCityName is present in the pageFlowScope (lines 8-10). If yes the next check is if the current selected criterion the for the selected parameter, in this case the City (lines 11-19). Only if this test is positive the value from the parameter is set to the criterion (line 20), the pageFlowScope variable tpCityName is removed and the new criterion is set back to the query model (lines 21-25). Finally to execute the af:quickQuery we create a new QueryEvent and invoke it via an expression language (lines 26 -28). The solution does not need to set the InitialQueryOverwritten property to true to run. The query is fired after setting the attribute via the QueryEvent. Here is an image of the af:quickQuery binding

Definition of the ImpliciteViewCriteriaQuery

Definition of the ImpliciteViewCriteriaQuery

The sample needs the HR DB schema.

Wir bleiben für Sie da!

In unserem neusten Blog-Beitrag sprechen wir mit zwei neuen Mitarbeitern, die Mitten in der Corona-Krise bei virtual7 angefangen haben.

Welche Eigenheiten und Herausforderungen einen Jobwechsel in dieser Zeit bedeuten und wie ihr erster Arbeitstag im Home-Office ausgesehen hat, erfahren Sie in diesem Beitrag.