Initialize an execute af:quickQuery default criteria on page load

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 blog 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 the running 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 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 the well documented. So passing a parameter from an af:inputText to a bounded task flow will only showed briefly here. The button on the start page uses a 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
For the next try I used a method activity in the bounded task flow and 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:

                        <af:quickQuery label="Search" searchDesc="#{viewScope.QuickQueryBean.dummy}" id="qryId1"
                                       value="#{bindings.ImplicitViewCriteriaQuery.quickQueryDescriptor}"
                                       model="#{bindings.ImplicitViewCriteriaQuery.queryModel}"
                                       queryListener="#{bindings.ImplicitViewCriteriaQuery.processQuery}" binding="#{viewScope.QuickQueryBean.quickQuery}">
                            <f:facet name="end">
                                <af:commandLink text="Advanced" rendered="false" id="cl1"/>
                            </f:facet>
                        </af: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 it’s value. In the getter in the bean

    /**
     * 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";
    }

we check a local variable ‘needInit’ which is set to true when the bean is created each time the page gets loaded. As the bean is in viewScope 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'S '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 EL (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. You can download the code for the sample, which was build using JDeveloper 12.1.3, from GitHub. Please note that if you run the sample in your environment, that you have to change the DB connection to the HR DB schema according to your environment.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.