Showing posts with label Custom. Show all posts
Showing posts with label Custom. Show all posts

29 Oct 2017

Checking ADF BC Transaction Status

There is a very common use case when we need to programmatically check if the current transaction is dirty and notify a user about that. The most common approach is to get an instance of the current data control frame or a data control and check their isTransactionDirty() and isTransactionModified() methods.

For example, like this:

    private boolean isTransactionDirty() {
        BindingContext context = BindingContext.getCurrent();
        DataControlFrame dcFrame = context.dataControlFrame();               
        return dcFrame.isTransactionDirty();
    }

Or like this:

    private boolean isTransactionDirty() {
        BindingContext context = BindingContext.getCurrent();
        DCBindingContainer binding = (DCBindingContainer) context.getCurrentBindingsEntry();
        DCDataControl dc = binding.getDataControl();      

        return dc.isTransactionDirty();       

        //or       

        return dc.isTransactionModified();       

        //or       

        return dc.isTransactionDirty() || dc.isTransactionModified();       
    }

Note, that in the second example both isTransactionDirty() and isTransactionModified() methods are used. In the good old days, when people worked with 11g, the isTransactionDirty() method checked the underlying model if it was dirty (basically if ADF BC transaction was dirty). The isTransactionModified() has never done that, it's been always checking its internal flag only which is useful when it comes to a non-transactional data control (e.g. been data control). Having those both methods was nice as it gave some flexibility and you could use either of them (or both) depending on what you were actually checking.

Nowadays (12cisTransactionDirty() is combined with isTransactionModified(), so it checks the internal flag (which is set up whenever any data bound value is changed) and the underlying model transaction and returns true if either of them is true. Having said that, you are not able anymore to use isTransactionDirty() to check if ADF BC transaction is dirty.

Let's say there is a transient view object and you use it on your screen for some temporary values (e.g. implementing custom filtering or a form with input values for a business service method). Since those values are data bound (even though they have nothing to do with ADF BC entity cache) the framework will mark the internal data control flag as dirty whenever the values are changed. So, isTransactionDirty() method in 12c is going to return true. The user didn't do anything bad yet, and we are scaring them with the notification about dirty transaction.

The solution is to override the method in the data control. You can see how to tell the framework to use a custom data control here. So, in our custom data control we are going to override isTransactionDirty() method:

    //We consider transaction as dirty only if BC transaction is dirty
    //all manipulations with transient VOs/attributes should not matter
    @Override
    public boolean isTransactionDirty()  {
       ApplicationModule am = getApplicationModule();
       return (am != null
                  && am.getTransaction() != null
                  && am.getTransaction().isDirty());
    }


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! 

28 Apr 2013

ADF BC. Working with custom data control.

It is highly recommended to create our custom set of subclasses of the framework BC base classes and to build our BC model on the top of them. This gives us a certain flexibility and ability to control what's going on. But sometimes it would be cool to have our own extension at the middle layer, I mean the data control layer. A wide range of tricks can be done having a custom data control. I've already blogged about some and definitely will blog about many more. A lot of ADF developers are a bit scared of this technique and consider it as something from the dark side. But actually it's a very easy technique despite the power it gives to developers.

So, you have to make the following steps:

Create custom data control class:
It is recommended to use JUApplication as a base class for your custom data control:
public class AgileDataControl extends JUApplication
{
...


Override the desired methods:
  @Override
  protected void applySortCriteria(DCIteratorBinding iter,
                                   SortCriteria[] sortBy) {
     //Some job

    }



Create custom data control factory returning the class name of your custom data control:
public class AgileDataControlFactory extends DataControlFactoryImpl
{
  @Override
  protected String getDataControlClassName() {
          return AgileDataControl.class.getName();
      } 
}


Specify custom data control factory in the DataBindings.cpx file:
<BC4JDataControl id="AgileDataModelServiceDataControl"
                     Package="agiledatamodel"
                    FactoryClass="agiledatamodel.datacontrol.AgileDataControlFactory"
                     ...


Enjoy!
 
That's it!