31 Oct 2017

Implementing Dynamic Dialog Handler with Functional programming

In my previous post I mentioned a common use case when we need to programmatically check if the current transaction is dirty and notify a user about that before doing something. Like "You have unsaved changes that will be lost, do you want to continue?".
Suppose that we need to notify the user about dirty transaction in many places across the application, when navigating from one view to another, when clicking Search button, when invoking a business service method, etc. So, in every single scenario we need to do different things after the user confirms that they want to proceed. It means that our dialog listener should know somehow what it was all about and what to do next.

The solution could be to add a custom attribute to the af:dialog component pointing to a function which is going to be invoked when the user clicks "Yes" on the dialog:

<af:popup id="pDirtyTransaction" contentDelivery="lazyUncached">
  <af:dialog title="Warning" type="yesNo" closeIconVisible="false"
             id="dDirtyTransaction"
    dialogListener="#{theBean.dirtyTransactionDialogListener}">
     <af:outputText value="You have unsaved changes, do you want to continue?"
                    id="ot1"/>

     <f:attribute name="dialogHandler" value=""/>                   

  </af:dialog>
</af:popup>


In that case the dialog listener may look like this:

public void dirtyTransactionDialogListener(DialogEvent dialogEvent) {       
  Map attrs = dialogEvent.getComponent().getAttributes();
  Consumer<Boolean> dialogHandler = (Consumer) attrs.get("dialogHandler");
  if (dialogHandler != null) {
      dialogHandler.accept(dialogEvent.getOutcome() == DialogEvent.Outcome.yes);
      attrs.put("dialogHandler",null);
  }                   
}

We expect here that dialogHandler attribute points to an object implementing Consumer functional interface.

There is a method in our utils showing the popup with the dialog:

public static void showDirtyTransactionPopup(Consumer dialogHandler) {
  if (dialogHandler != null) {
      JSFUtil.findComponent("dDirtyTransaction").getAttributes().
              put("dialogHandler",dialogHandler);
  }

  RichPopup popup =
      (RichPopup) JSFUtil.findComponent("pDirtyTransaction");
  popup.show(new RichPopup.PopupHints());
}


Let's use this approach in a simple scenario. There are two view activities in our task flow View1 and View2. The user clicks a button to navigate from one view to another. While navigating we need to check if the current transaction is dirty and if it is ask the user if they want to proceed. We can leverage the power of Java 8 Lambda expressions and implement the button action listener  like this:

public void buttonActionListener(ActionEvent actionEvent) {

  if (Utils.isTransactionDirty()) {       

       Utils.showDirtyTransactionPopup((yesOutcome) -> {          

           //the code below will be invoked by the dialog listener
           //when the user clicks a button on the dialog                                                                     
           if ((Boolean) yesOutcome) {
               //the user has agreed to proceed,
               //so let's rollback the current transaction
               Utils.getCurrentRootDataControl().rollbackTransaction();            

               //and queue an action event for this button again
               new ActionEvent(actionEvent.getComponent()).queue();
           } });

   } else
       //just navigate to View2
       Utils.handleNavigation("goView2");
}

Basing on this technique we could implement a declarative component serving as a dialog with a dynamic content and a dynamic handler.

That's it!






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!