30 Jun 2013

Resizing of Search and Select dialog of LOV Components

Playing with skin selectors of ADF Faces LOV components I found a new selector announced in release notes for 11.1.1.7.0 and 11.1.2.4.0 versions. The -tr-stretch-search-dialog selector enables the feature of resizing LOV's search and select dialog, allowing users to interact with this dialog in more convenient way.   

By default the feature is disabled and in order to enable it the source code in the css file for the inputListOfValues component should look like this:

af|inputListOfValues{
             -tr-stretch-search-dialog: true;
           } 


And for the inputComboboxListOfValues it should look like this:

af|inputComboboxListOfValues{
             -tr-stretch-search-dialog: true;
           } 



The cool thing is that when end user is resizing the dialog, it is going to stretch its content as well:




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

That's it!


22 Jun 2013

Building Custom LOV with searchContent Facet

List of Values UI components, such as inputListOfValues and inputComboboxListOfValues have a special facet searchContent. The facet is supposed to be used as an extension point which allows us to build our custom LOV's. The content of the facet is going to be rendered in the LOV's search dialog window. This feature looks pretty attractive, since there are lots of use cases when we would like to modify the search dialog content and functionality. And what is important, we basically don't want to throw away the existing LOV's search engine and build it ourselves from scratch. Our goal is to enhance a little bit the search dialog in terms of look and feel and to inherit the existing functionality.

In order to transform a LOV into the custom one, we've got to implement a couple of listeners and design the content of the searchContent facet. So, there is a LOV:

<af:inputComboboxListOfValues id="deptId"
    popupTitle="Search and Select: #{bindings.DepartmentId.hints.label}"
    value="#{bindings.DepartmentId.inputValue}"
    label="#{bindings.DepartmentId.hints.label}"
    model="#{bindings.DepartmentId.listOfValuesModel}"
    required="#{bindings.DepartmentId.hints.mandatory}"
    columns="#{bindings.DepartmentId.hints.displayWidth}"
    shortDesc="#{bindings.DepartmentId.hints.tooltip}"
    autoSubmit="true"
    
    returnPopupListener="#{LOVUtilBean.lovSearchListener}"
    launchPopupListener="#{LOVUtilBean.lovPopupListener}"
    >    


Attributes returnPopupListener and launchPopupListener refer to some managed bean methods. And the searchContent facet:
 <f:facet name="searchContent">
  <af:panelGroupLayout layout="vertical" id="pgl1">
    <af:query headerText="Search" disclosed="true"
      value="#{bindings.DepartmentId.listOfValuesModel.queryDescriptor}"
      model="#{bindings.DepartmentId.listOfValuesModel.queryModel}"
      queryListener="#{LOVUtilBean.queryListener.processQuery}"
      resultComponentId="::t1"
      saveQueryMode="hidden"
      modeChangeVisible="false" maxColumns="4"
      rows="1" id="q1"/>
             
    <af:table value="#{bindings.DepartmentId.listOfValuesModel.
                       tableModel.collectionModel}"
              var="row" rowBandingInterval="1"                        
              rowSelection="single" columnStretching="last"
              width="700px" id="t1">
      <af:column headerText="#{bindings.DepartmentId.listOfValuesModel.
                               itemDescriptors[0].name}"
                 id="c1" sortable="true"
                 sortProperty="#{bindings.DepartmentId.listOfValuesModel.
                                 itemDescriptors[0].name}"
                 align="left" width="80">
        <af:outputText value="#{row.DepartmentId}" id="ot1"></af:outputText>
      </af:column>
     
      <af:column headerText="#{bindings.DepartmentId.listOfValuesModel.
                               itemDescriptors[1].name}"
                 id="c2">
        <af:outputText value="#{row.DepartmentName}" id="ot2"/>
      </af:column>
    </af:table>
  </af:panelGroupLayout>
</f:facet>


The facet contains the standard Query+Table form, which is based on the LOV's model. Actually, we can fill free designing the content of the facet. The only requirement is that we should have one query component and one table component within the facet. But, even this restriction is based on the listener's implementation provided in this post. If you want to avoid the requirement, you can rewrite the listeners as you wish.

So, let's have a look at the listeners.
We're going to use the internal framework listener as the query listener of the Query component:
public QueryListener getQueryListener() 
{
  return queryListener;
}
private QueryListener queryListener = new InternalLOVQueryListener();
Handling the returnPopupEvent after selecting the value in the search dialog:  
public void lovSearchListener(ReturnPopupEvent returnPopupEvent)
{
  UIXInputPopup lovComponent =
    (UIXInputPopup) returnPopupEvent.getSource();

  //Looking for the table component within 
  //seacrhContent facet
  RichTable table = findTable(lovComponent);

  RowKeySet keySet = table.getSelectedRowKeys();


  if (keySet != null && keySet.size() > 0)
  {

    ListOfValuesModel model = lovComponent.getModel();
    Object newVal = model.getValueFromSelection(keySet);
    Object oldVal = lovComponent.getValue();

    //Have we selected anything new?
    //If yes, set it as a value of the LOV and update the model
    if (!ObjectUtils.equal(oldVal, newVal))
    {
      lovComponent.setValue(newVal);
      lovComponent.processUpdates(FacesContext.getCurrentInstance());
    }

  }
}

Handling the launchPopupEvent just before the search dialog is going to be rendered:
public void lovPopupListener(LaunchPopupEvent launchPopupEvent)
{
  UIXInputPopup lovComponent =
    (UIXInputPopup) launchPopupEvent.getSource();
  ListOfValuesModel model = lovComponent.getModel();

  if (model != null)
  {
    //Resetting the query component. 
    //So each time whenever the dialog is rendered the query component 
                  //is in its initial state
    QueryModel queryModel = model.getQueryModel();
    QueryDescriptor queryDesc = model.getQueryDescriptor();
    if ((queryModel != null) && (queryDesc != null))
    {
      queryModel.reset(queryDesc);
      //Looking for the query component within 
      //seacrhContent facet
      RichQuery query = findQuery(lovComponent);
      if (query != null)
        query.refresh(FacesContext.getCurrentInstance());
    }


    //If the LOV component has the searchFacet, then the framework fires this
    //event even in case of leaving the LOV component by TAB pressing. 
    //So, we have to check whether the dialog is really need to be launched.
    //And in case of exact match, we don't need any search dialog.
    Object oldVal = lovComponent.getValue();
    if (!ObjectUtils.equal(String.valueOf(oldVal),
                           launchPopupEvent.getSubmittedValue()))
    {
      List<Object> autoComplete =
        model.autoCompleteValue(launchPopupEvent.getSubmittedValue());
      
      //Do we have an exact match?
      if (autoComplete != null && autoComplete.size() == 1)
      {
        Object autoCompletedValue = autoComplete.get(0);

        Object newVal = model.getValueFromSelection(autoCompletedValue);
        lovComponent.setValue(newVal);
        lovComponent.processUpdates(FacesContext.getCurrentInstance());
        
        //We don't need to launch the dialog anymore
        launchPopupEvent.setLaunchPopup(false);
      }

    }

  }
}


The important thing is that these listeners are generic, they don't depend on any particular use-case and they don't depend on any particular LOV component. These methods can be gathered in some utility bean and used across the entire application.
The result of our work looks like this:
 
The sample application for this post can be downloaded here. It requires JDeveloper R2.

That's it!