Monday, November 29, 2010

LOV enabled date field throws date format error !

While working with LOV enabled attribute date fields, you might have experienced some strange date format error on UI - "The date is not in the correct format". The reason for this error is well documented in developers guide, please refer topic 5.12.5 How to Handle Date Conversion for List Type UI Components in Fusion Developer's Guide.

In simple words, the underlying run time mechanism tries to copy the selected date from the LOV pop-up to the target field using the date format specified for the attribute. This may fail to do so if the format turns out to be null/invalid. Here, you are supposed to specify date format for LOV enabled 'date' attribute of the view object(using Control Hints editor for the attribute). Later, this 'pattern' would be used by af:convertDateTime to convert string into Date.

<af:inputListOfValues id="startDateId"
    popupTitle="Search and Select: #{bindings.StartDate.hints.label}"
    .........
    shortDesc="#{bindings.StartDate.hints.tooltip}">
<f:validator binding="#{bindings.StartDate.validator}"/>
<af:convertDateTime pattern="#{bindings.StartDate.format}"/>
</af:inputListOfValues>

Thursday, November 25, 2010

What if you missed the Key definition for a ViewObject ?

When you create ViewObject WITHOUT choosing EntityObject as data source, there are chances that you may forget to define Key for the generated ViewObject. Actually the Key definition play a very vital role when ADFm(binding layer) comes in to picture. Many of the binding specific APIs make use of the 'Key' from the ViewObject to identify rows uniquely. Let me share a couple of issues that may arise if you miss out Key definition for a ViewObject

1. RichTable::getRowData() returns null.

Please see the below shown code snippet. This code is typically used to get the data for the selected rows for a table from the backing bean.

RowKeySet selectedRowKeys = someTable.getSelectedRowKeys();
  
//Store original rowKey
Object oldRowKey = someTable.getRowKey();

if (selectedRowKeys != null) {
    Iterator iter = selectedRowKeys.iterator();
    if (iter != null && iter.hasNext()) {
    Object rowKey = iter.next();
    someTable.setRowKey(rowKey); //stamp row
    JUCtrlHierNodeBinding rowData =
        (JUCtrlHierNodeBinding)someTable.getRowData();
    //Do something here
  }
}
  
//Restore the original rowKey
someTable.setRowKey(oldRowKey);
If you don't have Key defined at ViewObject level, call to someTable.getRowData() may return null. In that case, solution is to revisit your ViewObject and define a Key :)

2. Hitting the "java.lang.IllegalStateException: ADFv: Not inside a container error" when a tree(tree table) node is expanded by the user.

java.lang.IllegalStateException: ADFv: Not inside a container.
        at oracle.adfinternal.view.faces.model.binding.FacesCtrlHierBinding$FacesModel.exitContainer(FacesCtrlHierBinding.java:673)
        at oracle.adfinternal.view.faces.model.FlattenedTreeCollectionModel.getRowIndex(FlattenedTreeCollectionModel.java:111)

As a first step to debug this error, please check the underlying ViewObject to ensure the Key definition for the same.

Disclaimer
Please note that there may be multiple reason for the above listed error(s). What I'm sharing in this post is just one among many reasons. Let this be a staring point while debugging issues with similar symptoms :-)

Wednesday, November 24, 2010

Specifying defaultColor for <af:inputColor>

The <af:inputColor> creates a text field for entering colors and a button for picking colors from a palette. If you need to specify defaultColor for this component, you can use java.lang.String of hex color code as shown in the following sample. Please note that defaultColor value follows #RRGGBB syntax.
 <af:inputColor  label="Enter or select color from palette below"
  compact="false" defaultColor="#000000" id="id1"/>

Monday, November 22, 2010

An example of clickListener implementation for <dvt:barGraph>

In this post I'm sharing an interactive bar graph example built using <dvt:barGraph> and a clickListener for the graph component. The basic <dvt:barGraph> used in this example is built using the same approach as suggested by Frank in his ADF Code Corner sample #17. So, I'm not repeating those steps(for building graph using ViewObject) in this post.

Use Case

This example displays a bar graph of total salary for each department. Please see the following picture - Departments are plotted on X-axis and Salary on Y-axis


When user clicks on a 'Bar', he/she is presented with popup window to edit the data used for building the Bar. On closing the popup, graph gets refreshed to reflect the modified data


A glance at the implementation


The <dvt:barGraph> supports listener interface for receiving click events on the graph components. This example is built based on this concept. The clickListener gets triggered when user clicks on the bars of the graph, and the listener method implementation for this example in turn displays the 'edit popup' window with data used for building the bar. In a nutshell, the clickListener method used in this example does the following
1. Gets the group attribute(department name) using the ClickEvent payload
2. Fires a query using the department name and populates the VO
3. Displays the data from step 2 in an edit popup window

I'm copying the relevant code snippet below for your reference.

<dvt:barGraph id="barGraph1"
value="#{bindings.DepartmentsView1.graphModel}"
subType="BAR_VERT_CLUST"
clickListener="#{TestBean.clickAction}" ...

/**
* Method to handle ClickEvent on the Graph. This method does the following
*  1. Gets the group attribute
*  2. Fire a query using the group attribute param and populates the VO 
*  3. Displays the data from step 2 in an edit popup window
* @param clickEvent
*/
public void clickAction(ClickEvent clickEvent) {
  ComponentHandle handle = clickEvent.getComponentHandle();
  String deptName = null;
  if (handle instanceof DataComponentHandle) {
    DataComponentHandle dhandle = (DataComponentHandle)handle;
    // Get the group attributes
    Attributes[] groupInfo = dhandle.getGroupAttributes();
    if (groupInfo != null) {
      for (Attributes attrs : groupInfo) {
        deptName =
          (String)attrs.getValue(Attributes.LABEL_VALUE);     
      }
    }
  }
  BindingContext bc = BindingContext.getCurrent();
  DCBindingContainer dcb =
    (DCBindingContainer)bc.getCurrentBindingsEntry();
  OperationBinding opb =
    dcb.getOperationBinding("executeDepartmentViewForName");
  opb.getParamsMap().put("deptName", deptName);
  opb.execute();
  showPopup(clickEvent);

}
Please note that this example has a dialogListener added for the above mentioned edit popup, which is responsible for re-executing the iterator and refreshing the graph component to reflect the changes done by the user.

You can download the sample workspace from here.
[Runs with Oracle JDeveloper 11g R1 PS2 + HR Schema]

Thursday, November 18, 2010

Triggering Navigation from a Contextual Event Handler Method

ADF task flows are key building blocks for a Fusion Web Application. You can render a bounded task flow in a page using ADF region. Often a parent page may need to trigger navigation within the embedded region while responding to specific user actions. This blog post discusses such a use case scenario and a possible solution for the same.

Consider a generic case where a JSF page contains a bounded task flow added as a region. Here the requirement is that when user selects specific option displayed on the parent page, the ADF region's content(taskflow view activity) should navigate to an a new view/page. Let me try to explain this scenario with a more realistic example. Please see the following diagram. You may notice that deptemp-task-flow-definition is added as region to a page and the this page is having two buttons to edit the department and employee details. On clicking these buttons on parent page, the 'embedded' deptemp-task-flow should navigate to the corresponding edit page.



The following steps outline the implementation at a high level.

1. Defining Contextual Event and associate this with an actionListener for the command button.
2. Defining Event Consumer and perform event mapping between producer and consumer.
3. Triggering the navigation based on event payload.

In this post I'm discussing only the 3rd step from the above list. Please refer 28.7 Creating Contextual Events in Fusion Developer's Guide, in case you are not familiar with Contextual Event and it's usage.

Triggering Navigation from the Contextual Event


Please note that, as of now there is no declarative way to navigate to a different view (conditionally) from a contextual event handler method. One possibility is to handle the navigation programmatically.

Solution

JSF lets you toqueue FacesEvent on UIComponent manually. The approach I'm following in this example is driven by this idea. If you have proper navigation sequence defined in the task flow, then this is going to be an easy task. In the current example, there are two CommandButtons defined on the default view for deptemp-task-flow. One for editing dept details, and the second one for emp details. As you might have imagined, these two buttons have action defined for navigating to edit view. Now the solution is pretty simple and straight forward - queue the ActionEvent on the appropriate button component (RichCommandButton) from the contextual event handler method. This may trigger the desired navigation at proper JSF life cycle phase(Invoke Application). Please see the code snippet copied below to get a feel of this approach.


/**
 * Handler(subscriber) for the Contextual Event
 */ 
public void handleEvent(String arg) {

  String compId = null;
  if (arg.equals("editDept")) {
    compId = DEPT_EDIT_BTN_ID;
  } else if (arg.equals("editEmp")) {
    compId = EMP_EDIT_BTN_ID;
  }

  if (compId != null) {
    RichCommandButton btn = getEditButtonComponent(compId);
    ActionEvent actEvent = new ActionEvent(btn);
    actEvent.queue();
  }

}

Before winding up this discussion, let me answer one query that may arise in your minds :)
Why don't you use javax.faces.application.NavigationHandler
.handleNavigation(....
) here?

Well, this works...but this may have it's own side effects. The call to NavigationHandler.handleNavigation(...) bypasses JSF life cycle phases and forcefully triggers the navigation. So you may see some unexpected results in certain scenarios.

You can download the sample workspace from here.
[Runs with Oracle JDeveloper 11g R1 PS2 + HR Schema]

A glance at the implementation

The entry point for this sample application is main.jspx. This page contains two buttons('Edit Dept' and 'Edit Emp' ) on top, and a task flow(deptemp-task-flow-definition) displaying department employee details.

The deptemp-task-flow-definition at glance:

1. This task flow has 'deptEmp' view as the default activity.
2. The 'deptEmp' view contains two CommandButtons namely 'Dept Details' and 'Emp Details'. Clicking on these button would take you to corresponding edit screens. Please note that we would be using these buttons for queuing the ActionEvent to trigger the navigation from contextual event handler method.

Please run the main.jspx. When you click on 'Edit Dept' or 'Edit Emp' buttons displayed on top, the action in turn triggers contextual event('editEvent') defined on the main page. The 'deptEmp' view on the deptemp-task-flow has been registered as a subscriber for this event. As we discussed earlier, this subscriber method queues an Action Event on the CommandButton defined on the deptEmp view('Dept Details' / 'Emp Details' buttons). This result in the navigation to the editDept/editEmp view at proper phase of JSF life cycle.

Friday, November 12, 2010

Layout Tips - Organizing page content using <trh:tableLayout>

ADF Faces is packaged along with rich set of layout components which can be used to arrange other components on a page. Though these components are smart enough to meet most of your requirement, sometimes you may need to look around for other layout components to realize specific UI features. This discussion is based on such a scenario where I'm using <trh:tableLayout> for building certain part of the UI. btw, this post is in (logical) continuation of my previous post on Layout Tips - Stretching components....

In this post, I'm discussing a UI centric use case implementation using ADF Faces layout components along with MyFaces Trinidad <trh:tableLayout>. Though mixing of the components from these two families are not considered as good practice, sometimes you may need to move out of the box to make your application working :)

A Case Study

Requirement is to build a web page as shown in following screen shot.


In my experience, the painful phase of UI design is picking up the right layout component which works for a specific case. Let me share simple tip to make your life easier. To begin with, let us draw outlines over the screen elements as shown in the following diagram. You can follow this approach irrespective of the technology you are using for building the UI. I was using this approach for building Java Swing UI when I started my career ;)

Now the picture is more clear, the dark clouds are moving out and the silver lining is becoming visible :) The requirement here is that table component is expected to stretch to fit the available space in the browser window and the 'parameter form' displayed below should occupy fixed height and width.

Cool...now we may need to identify the right set of layout components to organize the page contents. One obvious candidate for top level container is panelStretchLayout component. This component stretches the child in the center facet to fill all of available space. So let us add table to center facet of the panelStretchLayout, and specify fixed height for bottom facet where the parameter from goes. The jsf page may look like as follows.

 <af:panelStretchLayout id="psl1" startWidth="50px"
                                   bottomHeight="100px">
  <f:facet name="center">
     <af:table value="#{bindings.EmployeesView1.collectionModel}"
  </f:facet>
  <f:facet name="bottom">
  <!--  parameter form goes here -->
  </f:facet>
  <f:facet name="center"/>
  <f:facet name="start"/>
  <f:facet name="end"/>
  <f:facet name="top"/>
</af:panelStretchLayout>
Next step is to identify the layout for second part(parameter form) of the screen. Th challenge here is the uneven distribution of the UI elements across rows. Though panelFormLayout comes close to the requirement, unfortunately this doesn't give the desired out put. Time to look around for a smart component which acts more like wrapper over HTML <table> element. Search ends in Trinidad libraries where we can see our friend - <trh:tableLayout>. Please note that Trinidad components doesn't have any client peer components as we do have for ADF Faces. So you may need to be measured while choosing this guy.

Adding the Trinidad tag library

First step is to add the the required Trinidad tag libraries to your web project's class path. Select the web project, right click and select project properties. Using the Project properties window add the 'Trinidad Components' from the list.

Organizing the content using <trh:tableLayout>

<trh:tableLayout> is supposed to have <trh:rowLayout> for each row and <trh:rowLayout> in turn can take <trh:cellFormat> to hold the UI components. In this example, we may need to have 3 rows. Each <trh:rowLayout> can take different number of <trh:cellFormat> based on number of UI elements to be displayed in a row. Please note that you may need to keep label separate from the <af:inputText> for better result. To achieve this, set the attribute 'simple' as true for inputText component, and add a separate <af:outputLabel> for labeling each inputText field.


The last step in this exercise is to specify the columnSpan and rowSpan properties for <trh:cellFormat>. This decides whether cells can span across more than one column or row. This is an easy task if you have an 'overlay grid' drawn on top of the screen elements as we discussed at the beginning.


Finally, the high level mapping between the UI elements and layout components may look like as shown in the following diagram


You can download the sample workspace from here.
Please try running main.jspx to get a feel of the <trh:tableLayout> component.
[Runs with Oracle JDeveloper 11g R1 PS2 + HR Schema]


Learn More ...

There are a lot more points like this. If  you are curious to learn the internals of the ADF Business Components and ADF Binding Layer,  the following book is for you - Oracle ADF Real World Developer’s Guide.
More details about this book can be found in this post- http://jobinesh.blogspot.in/2012/07/pre-order-your-copy-of-oracle-adf-real.html

Thursday, November 4, 2010

Time moves so fast !

I just realized that in the last month I finished my 2 years with Oracle( well don't rise your eyebrows..this is not a short time span if you see my job history he..he..)
Yes, time moves so fast :D

Let me plan something new for coming year(s). At this stage of my journey with ADF, I'm renaming this blog as 'Decompiling ADF Binaries' with your permission. The motive behind this move is to give more focus on areas where we don't have enough documentation or samples. On a related note, If I can be of any help on ADF/JavaEE technology stack, please let me know. Feel free to write your questions/suggestions to jobinesh@gmail.com, I'll be happy to listen to you.

What you may need to know about source URI for an <af:inlineFrame>

The <af:inlineFrame> component creates an inline frame tag. The content of the inline frame is loaded through the source attribute.
e.g: <af:inlineFrame id="if1" source="/faces/someView.jspx"/>

Please note that, <af:inlineFrame> let you to define the 'relative' source URLs in two ways.

1. Relative to the current web application

<af:inlineFrame id="if1" source="/faces/someView.jspx"/>
In this case the source is looked up relative to the current web application's context. This approach is useful if the page resides in the same 'war' file

2. Relative to the server root

<af:inlineFrame id="if1" source="//<app_context>/faces/someView.jspx"/>
Please note that URL starts with double forward slash in this case. Here the source is looked up relative to the server root. This is very useful if the you need to load a page from another 'war' file within the same server root. Apparently this feature frees you from writing down the custom EL to extract the server root and using the same to construct the URIs.

Wednesday, November 3, 2010

Converting user input to uppercase

Converting the user input to upper case is a very common requirement for a typical web application. In this post, I'm sharing a simple solution for this use case using Java Script. Idea is to trigger the client side Java Script method using <af:clientListener> for 'keyUp' event. The below given code snippet may give you better idea on the implementation part.
<af:resource type="javascript">
  function toUpper(event) {
      var inputComp = event.getCurrentTarget();
      inputComp.setValue(inputComp.getSubmittedValue().toUpperCase());
  }
</af:resource>

<af:inputText value="#{bindings.CountryId.inputValue}"
    label="#{bindings.CountryId.hints.label}"
    required="#{bindings.CountryId.hints.mandatory}"
    columns="#{bindings.CountryId.hints.displayWidth}"
    maximumLength="#{bindings.CountryId.hints.precision}"
    shortDesc="#{bindings.CountryId.hints.tooltip}" id="it2"
    clientComponent="true">
 <f:validator binding="#{bindings.CountryId.validator}"/>
 <af:clientListener type="keyUp" method="toUpper"/>
</af:inputText>

You can download the sample workspace from here.
[Runs with Oracle JDeveloper 11g R1 PS2 + HR Schema]

A glance at the implementation

This example contains test.jspx page to capture 'Country' master data. The attribute 'CountryId' is supposed to be in upper case always. The upper case conversion is achieved by triggering a custom Java Script method 'toUpper(...)' for keyUp event using <af:clientListener> tag.
To test this functionality, run the test.jspx and click on CreateInsert button which may let you to define new 'Country'. When you try key in the CountryId, you may notice that the keyed in letters are getting converted to upper case.