Tuesday, November 17, 2009

Referencing Bind Variables in Groovy

Thanks to Steve Muench for #143, 'Referencing UserData Map Values from View Object Bind Variables in Groovy' from Not Yet Documented ADF Sample Applications for help on figuring this one out.

I needed a ViewAccessor of my ViewObject to execute based upon a parameter calculated at run time. Generally, ViewAccessors, with required bind variables, are executed based upon an attribute in the main ViewObject, but in my case my ViewObject had no values for its attributes, because I was using it as a searchRegion (for the data binding of an af:query component) and although the I was setting the bind variable as the first step in my task-flow, the attribute on which I was trying to base the ViewAccessor's bind variable would always return null. So I created the following class that can be called via Groovy:

package com.mycompany.myproject.model.util;

import oracle.adf.model.bean.DCDataRow;

import oracle.jbo.ExprValueSupplier;
import oracle.jbo.JboReservedVarNames;
import oracle.jbo.Row;
import oracle.jbo.StructureDef;
import oracle.jbo.ViewObject;
import oracle.jbo.common.ViewCriteriaRowImpl;
import oracle.jbo.server.ViewRowImpl;

public class VOUtils {

public static Object bindValue(ViewObject vo, String bindName) {
return vo.getNamedWhereClauseParam(bindName);
}

/*
* Returns the value of the namedWhereClauseParam from the supplied VO.
* This was created so that bind variables values could be accessed via Groovy
*
* Groovy Example:
* com.synergetic.sis.model.util.VOUtils.bindValue(adf.object,"AgencyIdBind")
*
*/
public static Object bindValue(ExprValueSupplier valSupplier, String bindName) {
if (valSupplier instanceof Row) {
return bindValueFromRow((Row)valSupplier, bindName);
} else {
StructureDef sd =
(StructureDef)valSupplier.getExprVarVal(JboReservedVarNames.RESERVED_VAR_STRUCTURE_DEF);
if (sd instanceof ViewObject) {
return bindValue((ViewObject)sd, bindName);
} else if (sd instanceof Row) {
return bindValueFromRow((Row)sd, bindName);
} else {
System.out.println("Didn't handle case when valueSupplier was a " +
sd.getClass().getName());
}
}
return null;
}

public static Object bindValueFromRow(Row row, String bindName) {
ViewObject vo = null;
if (row instanceof DCDataRow) {
Object dp = ((DCDataRow)row).getDataProvider();
if (dp instanceof ViewCriteriaRowImpl) {
vo = ((ViewCriteriaRowImpl)dp).getViewCriteria().getViewObject();
}
} else {
vo = ((ViewRowImpl)row).getViewObject();
}
if (vo != null) {
return bindValue(vo, bindName);
}
return null;
}
}


This class can then be used to lookup the value of the ViewObject's bind variable using the following Groovy expression:
com.mycompany.myproject.model.util.VOUtils.bindValue(adf.object,"BindVariableName")

Wednesday, November 11, 2009

History Columns

When using the automatic audit feature of ADF's entity object, DO NOT check the Refresh After Insert or Refresh After Update checkboxes. These will prevent the columns from being updated with proper values.

Tuesday, April 21, 2009

Declarative Component, Custom Popup

I have incorporated all of the quirky requirements (for my project at least) for popups into a simple to use adf declarative component. This component is available to my entire project by means of an ADF Library JAR file (see how-to). It is included as a TagLib, which makes using it as simple as drag'n'drop. I include the lib as a JSP Tag Library whereas Frank does it a little bit different in the how-to.



You can download the whole project, AdfBitsComponent.zip or, if you want to just use the component library (as shown in the screen shot above), download just the jar AdfBitsComponentLib.jar.



    Features:
  1. Can be shown from javascript or java.

  2. Will ensure that no values are left over from the last time it was displayed (has been a major issue with our project).

  3. Has Save and Cancel buttons built in that call declared actions/actionListeners.

  4. Has optional confirmation prompt when using the cancel button (it will check to see if any of the editable values have changed, and if so will prompt the user to confirm, yes/no, that they want to cancel their edits).

  5. Will also capture the x, close-icon, and perform the Cancel button's actionListener, in case anything needs to be cleaned up once the popup is dismissed.

  6. Will click the Save button when the Enter key is pressed.

  7. Will click the Cancel button when the Escape key is pressed.

  8. If the Confirmation prompt is up, then pressing Enter will cause the current yes/no button to click.

  9. If the Confirmation prompt is up, then pressing Escape will cause the Confimration prompt to dismiss, but will allow the main popup to remain.

  10. All labels are customizable, but come with defaults set.

  11. Includes a facet to allow more buttons to be added between the Save and Cancel buttons.



If these features don't match your requirements (or if they don't work like you think they should), download the project and hack away at it.

Thursday, January 29, 2009

ERROR: Use of pinDataControl is not supported. Pinning the DataControl will result in severe performance issues.

This was discovered after implementing the code in the previous post on, "Hopping from one Train Stop to the next, causes the old stop to reexecute its bindings", although I do not believe it is directly related:


This was most likely in the console log all along, but only was noticed because of the fact that one of the executables, which was released after visiting a new tab, was being held onto and was trying to get reused. But since it had been released, it could no longer be used, and therefore threw up an exception, thus bringing the !ERROR! to our attention.

The FIX:
Reset the ChangeEventPolicy of the offending iterator back to the default, rather than "ppr".

According to the docs, ChangeEventPolicy can be used to perform automatic refresh of any UI component bound to the iterator when data is changed in the model and the docs actually recommend using it when there are a lot of components on a form that need ppr, however this was not needed in this particular case (and so far, nowhere else in our app).

Hopping from one Train Stop to the next, causes the old stop to reexecute its bindings

We have a page with several tabs within it. If you click tab 2, the bindings for both tab 1 (the starting point) and tab 2 get executed. If you then go to tab 3, the bindings for tabs 1, 2, and 3 get executed. The more tabs you visit the more crunching that goes on behind the scenes. It's obvious that we don't need tabs that we aren't currently viewing, to get executed, just the one that we have most recently clicked.

To avoid this, we have customized some of the ADF data control classes, namely DCTaskFlowBinding. What we have done, is added code to release the list of executables from a task-flow’s bindings when the task-flow is navigated away from. This avoids having its executables refreshed.

The underlying ISSUE:
As new train-stops are visited, the bindings for each of the train stops, which are task-flows in our case, are added to the main train stop task-flow's list of executables within it bindings. Each time you visit a new tab, or rather a new train stop, the bindings for the main task-flow get re-executed. This causes all of its executables, which basically are the bindings for each of the visited tabs' task-flows, to get executed as well. Which, as you may have figured, causes the entire tree of bindings to get refreshed. Since this main task-flow is a major part of our application, there are probably hundreds of iterators within this tree, especially after visiting each of the tabs. This was causing a major slow down.

The FIX:
Our solution to this problem was to simply release the old tab's executable bindings as it was navigated away from. There is a navigation listener on the main task-flow which contains the activityId of the currently selected train stop. Once a new train stop is selected, this listener gets called. The last part of the listener method calls cleanupBeforeRelease in the DCTaskFlowBinding class. What we have done is, customized the DCTaskFlowBinding class to, as a part of this method, release all of this task-flows's executable bindings. Releasing these bindings essentially removes them from the binding container itself, which prevents them from being executed when the top level task-flow navigates to a new tab. This old tab's bindings still get executed, however, it is much quicker since the costly executables have been released.

The CODE:
  1. In DataBindings.cpx
    Replaced:
    <factory nameSpace="http://xmlns.oracle.com/adf/controller/binding"
    className="com.collectamerica.aquila.common.TaskFlowBindingDefFactoryImpl"/>


    with

    <factory nameSpace="http://xmlns.oracle.com/adf/controller/binding"
    className="com.collectamerica.aquila.common.CustomTaskFlowBindingDefFactoryImpl"/>


  2. Create CustomTaskFlowBindingDefFactoryImpl.java and extend TaskFlowBindingDefFactoryImpl

    Within that class we basically replaced:
    DCTaskFlowBindingDef taskflowBindingDef = new DCTaskFlowBindingDef();


    with

    DCTaskFlowBindingDef taskflowBindingDef = new CustomDCTaskFlowBindingDef();


    to end up with:

    public class CustomTaskFlowBindingDefFactoryImpl extends TaskFlowBindingDefFactoryImpl {
    public CustomTaskFlowBindingDefFactoryImpl() {
    super();
    }

    /**
    * {@inheritDoc}
    * @param element {@inheritDoc}
    * @return {@inheritDoc}
    */
    public DCDefBase createDefinition(DefElement element)
    {
    // In our case there is only one object we can create but we still do
    // the check.
    if (element.getNodeName().equals(Constants.ENAME_TASKFLOWBINDING))
    {
    DCTaskFlowBindingDef taskflowBindingDef = new CustomDCTaskFlowBindingDef();
    return taskflowBindingDef;
    }
    return null;
    }
    }


  3. Create a class named CustomDCTaskFlowBindingDef which extends DCTaskFlowBindingDef, and basically this changes the binding container class name to point to our own custom class that we'll create next:

    public class CustomDCTaskFlowBindingDef extends DCTaskFlowBindingDef {
    public CustomDCTaskFlowBindingDef() {
    super();
    }

    @Override
    public void init(HashMap initValues) {
    super.init(initValues);

    // Overide BindingContainer class name with our special TaskFlowBinding.
    mBindingContainerClassName = CustomDCTaskFlowBinding.class.getName();
    }
    }


  4. Create a class named CustomDCTaskFlowBinding which extends DCTaskFlowBinding and override the cleanupBeforeRelease method and implement it as follows:

    public class CustomDCTaskFlowBinding extends DCTaskFlowBinding {
    public CustomDCTaskFlowBinding() {
    super();
    }

    /*
    * This method has been customized in order to release bindings which are no
    * no longer needed. You'll find that in the super's method,
    * cleanupBeforeRelease, that there is a TODO which states that exactly what
    * we're adding should be done. This will prevent these old bindings from being
    * reexecuted everytime we switch between task-flows, and in the specific case
    * this was originally done for, will prevent all previous tabs from reexectuing
    * their executable bindings when switching tabs.
    */
    @Override
    protected void cleanupBeforeRelease(String childViewPortId) {
    super.cleanupBeforeRelease(childViewPortId);

    //if this task-flow is being notified, it is because this task-flow is being
    // navigated away from, so release its executable bindings (the expensive ones)
    // which will get relisted when the page is actually displayed again
    List executables = this.getExecutableBindings();

    if(executables != null){
    for(int i=0; i<executables.size(); i++){
    Object oExec = executables.get(i);

    if(oExec instanceof JUFormBinding){
    JUFormBinding jExec = (JUFormBinding)oExec;
    jExec.release(DCDataControl.REL_ALL_REFS);
    }
    }
    }
    }
    }


Monday, January 12, 2009

An empty SelectOneChoice is not triggering the required validation error

We had a problem with a SelectOneChoice that had the required attribute set to true, but it never threw an error once the form was submitted. It turns out that LOV settings on the View Object were causing the problem. There is a checkbox to include a "No Selection" item.



Checking this box causes an empty space to be represented in the dropdown list. This means that a empty string is a selectable value. Since this is not the same as null, if it is the selected value, it considers this as fulfilling the requirement for a value to be selected. If you leave this checkbox unchecked, and do not set the value of the selectOneChoice when it is initially rendered, then you can have the selectOneChoice show up initially with no value showing/selected, and then if the user submits, they will get an error stating the a value must be selected. Also, you get the benefit of not being able to select a blank value, because as soon as the user selects a value, the blank is gone.

Thursday, December 25, 2008

Disabling buttons based on table selection

The easiest way to do this is to simply put an EL expression in the disabled attribute of the button that checks the size of the table's selectedRowKeys. You would also need to set a partialTrigger on the button with the ID of the table. The only problem with this is that it requires a trip to the server to evaluate the expression. Our application is going to be used by customers that are going to have low bandwidth and therefore need as few trips to the server as possible. So, we had to come up with a way to disable our toolbar buttons on the client (i.e. through javascript). We knew that a panelCollection did this very thing, so we just needed to tap into the panelCollection's functionality in order to duplicate this behavior.

We also had a need to arrange the buttons on the toolbar differently than what the panelCollection allows for. So, using a template (to replace all of our current implementations of panelCollections) we overhauled the panelCollection to suit our needs.

We tapped into the AdfDhtmlPanelCollectionPeer and found two important methods, updateStandardMenuToolbarItems and updateRowSelectionContext. The first is called when a column is selected, and the second is called when a row is selected.

Our template basically hid the original toolbar and added one on top that we could customize as much as we wanted, one that gets updated by chaining our own custom methods onto the two mentioned above. I am sure there is a proper design pattern named for what we're doing; it's something that is done with the onload function in many apps. Anyway, here is the code that we used to get our client code called every time a row or column is selected on a table that is within our template (which of course includes a panelCollection):

/* Allows the 'extra' toolbar to function as if it is part of the
* panelcollection. */
function initGridScript() {
if (typeof AdfDhtmlPanelCollectionPeer.prototype.updateStandardMenuToolbarItemsORIG != 'function') {
AdfDhtmlPanelCollectionPeer.prototype.updateStandardMenuToolbarItemsORIG = AdfDhtmlPanelCollectionPeer.prototype.updateStandardMenuToolbarItems;

AdfDhtmlPanelCollectionPeer.prototype.updateStandardMenuToolbarItemsCustom = updateMenuButtons;

//Update the 'extra' toolbar's buttons when the normal toolbar is updated
AdfDhtmlPanelCollectionPeer.prototype.updateStandardMenuToolbarItems = function () {
this.updateStandardMenuToolbarItemsORIG();

if(typeof this.getDomElement().className != 'undefined'){
if (this.getDomElement().className.search('CustomGridWithToolbar') > -1) {
this.updateStandardMenuToolbarItemsCustom();
}
}
}

AdfDhtmlPanelCollectionPeer.prototype.updateRowSelectionContextORIG = AdfDhtmlPanelCollectionPeer.prototype.updateRowSelectionContext;

AdfDhtmlPanelCollectionPeer.prototype.updateRowSelectionContextCustom = updateTableButtons;

AdfDhtmlPanelCollectionPeer.prototype.updateRowSelectionContext = function () {
this.updateRowSelectionContextORIG();

if(typeof this.getDomElement().className != 'undefined'){
if (this.getDomElement().className.search('CustomGridWithToolbar') > -1) {
this.updateRowSelectionContextCustom();
}
}
}
}
}


The AdfDhtmlPanelCollectionPeer.prototype.updateStandardMenuToolbarItemsORIG = AdfDhtmlPanelCollectionPeer.prototype.updateStandardMenuToolbarItems; saves off the original functionality so that we can invoke it later.

The typeof AdfDhtmlPanelCollectionPeer.prototype.updateStandardMenuToolbarItemsORIG != 'function' ensures that we are not appending our own functions more than once.

The this.updateStandardMenuToolbarItemsORIG(); ensures the original functionality is invoked.

The check for 'CustomGridWithToolbar' ensures that we are only calling our custom code when the panelCollection is the one from our template.

This function, initGridScript(), is called at the bottom of the javascript file on which it is declared, and the javascript file itself included as part of the template. So, it gets called whenever the template is written out to the response.

The updateStandardMenuToolbarItemsCustom method disables/enables menu items based column selection. In updateRowSelectionContextCustom, the code checks the RichTable client component's getSelectedRowKeys() to disable and enable the buttons in the toolbar accordingly.