Friday Jan 09, 2015

Pattern For Obtaining ADF Component ID for JavaScript

The following article outlines a technique that I use and has been buried in various code samples in the past.  However, in light of the explicit how-to being asked a couple of times recently, it seems sensible to pull it out into a explicit article. 

Now the scenario here is when you have a piece of JavaScript that needs to operate on a specific client side component. Now in many cases, you will be able to obtain the component that you need by simply calling the getSource() function on the event object that is passed to the function invoked by your <af:clientListener>. In other cases, given that same component reference you can walk up or down the component tree to find the component you need.  However, there are certainly circumstances where you need to grab a component which is kinda outside of the context and it would be much easier if you could just pass in the ID of the component and use the findComponent JS APIs to grab it.

The problem is of course that the ID you use for say a button (let's work with "b1" as an example) is of course not the real ID that would be used on the client to identify the component.  The full ID is going to depend on the various JSF naming containers that the component is nested within.  So our button b1 may end up being identified by something like "tmplt1:r1:b1".  So the question is, how can you easily obtain that full contextual calculated ID in a just-in-time manner so that your JavaScript can call the AdfPage.PAGE.findComponentByAbsoluteId() function to get its client side handle?

The pattern for doing this is pretty simple - you need to do just 5 things:

Step 1

On the component that  needs to be referenced from JS, remember to set clientComponent="true" You'd be amazed how often this is forgotten and leads to much hair pulling out as you just *know* that the client ID is correct but it's not being found!

Step 2

Likewise on the same tag you need to set the binding property so that you can obtain the reference to the component. So for example let's assume we create a backing bean class and set that up. e.g.

<af:button id="b1" clientComponent="true" 
           binding="#{backingBeanScope.examplePage.clientButton}">

This would then correspond to the following the backing bean class: 

  private ComponentReference clientButton;
  public void setClientButton(RichButton clientButton){
    this.clientButton = ComponentReference.newUIComponentReference(clientButton);
  }

  public RichButton getClientButton(){
    if (clientButton != null){
     return (RichButton)clientButton.getComponent();
    }
    else{
     return null;
    }
  }

Step 3

Create a new function in the same backing bean that calls the getClientId() function on the Java reference to the button.  This returns us the full calculated id, including the path defined by any templates, regions or other naming containers.

  public String getClientButtonClientId(){
    FacesContext ctx = FacesContext.getCurrentInstance();
    return this.getClientButton().getClientId(ctx);
  }

Step 4

Now we have a way of getting the correct clientId it needs to be made available to JavaScript, to do this we use an <af:clientAttribute> co-located with the <af:clientListener> that will be triggering the JS. So imagine in this case we have a second button that needs to reference our "b1" button:

<af:button text="Invoke Client Operation" id="b2">
  <af:clientListener method="doSomeClientOp" type="action"/>
  <af:clientAttribute name="b1ClientId" 
      value="#{backingBeanScope.examplePage.clientButtonClientId}"/>
</af:button> 

So we are passing the full String representation of the client ID for b1 via the attribute called "b1ClientId".

Step 5

All that remains is for the JavaScript function that is being called to get hold of this b1ClientId value that is being passed in (add error handling of course).

function doSomeClientOp(actionEvent){
  var actionComp = actionEvent.getSource();
  var b1ClientId = actionComp.getProperty("b1ClientId"); 
  var b1ComponentRef = AdfPage.PAGE.findComponentByAbsoluteId(b1ClientId);
  ...
} 

Additional Thoughts

In the steps here, I have used the pattern of utilizing the binding attribute on the component to provide the Java-side component handle.  However, there are of course other ways of getting hold of this reference (e.g. see my previous article Locating JSF components by ID).  The important thing here is the use of the getClientId() api to obtain the correct client ID encoding from the Java component and then making that available in JavaScript.

Tuesday Nov 25, 2014

Locating JSF components by ID

An occasional problem for developers using JSF is how to locate a component given just it's ID. Now if you have the complete ID, which includes the computed location, for example: Button B1 inside Region R1 inside page with template PT1 ==> PT1:R1:B1, you can hardcode that using the basic findComponent() API, or even better use the invokeOnComponent() API provided by the UIComponentBase (see the JavaDoc for more information).  However, what it you have no idea, at the time of writing the code, what the context is that the component that you want will be used in?  In that case, the only information that you might have is the basic ID of the component and you know nothing of the naming containers that contribute to the full unique ID.

 In this scenario I've seen a lot of examples where developers fall back to recursion. i.e. They grab the viewRoot from the FacesContext, get it's children, check to see if any of those children has the correct ID, if not they then get the children of each of those and check those and so on. There is, however, a much neater, and more efficient way of doing this using the JSF 2.0 visitTree API. With the visitTree API, in a similar way to the invokeOnComponent() API, you provide a callback inner class which implements a visit() method. This method is processed for each component in the component tree. You then have the ability to control how the tree is traversed based on the return value that you provide from that visit method. The valid return values are:

  • VisitResult.COMPLETE - stop the traversal all together
  • VisitResult.ACCEPT - continue with the traversal
  • VisitResult.REJECT - stop traversal for the current subtree, but continue with the rest

So here's a simple example of a helper method to invoke a ActionEvent on a commandItem such as <af:button> given just the basic id of that button. This might be the kind of thing you would want to do in a serverListener callback being invoked from a client side customEvent.

private static boolean queueActionOnCommandComponmentById(String commandId) {
  if (commandId != null && commandId.length() > 0) {
    final String candidateId = commandId;
    FacesContext ctx = FacesContext.getCurrentInstance();
    VisitContext visitContext = VisitContext.createVisitContext(ctx);
    return UIXComponent.visitTree(visitContext, ctx.getViewRoot(), new VisitCallback(){
      public VisitResult visit(VisitContext context, UIComponent component) {
        try {
          if (component instanceof UIXCommand && component.getId().equals(candidateId)) {
            ActionEvent cmdEvent = new ActionEvent(component);
            cmdEvent.setPhaseId(PhaseId.INVOKE_APPLICATION);
            cmdEvent.queue();
            return VisitResult.COMPLETE;
          }
          return VisitResult.ACCEPT;
        } catch (Exception e) {
          e.printStackTrace();
          return VisitResult.COMPLETE;
        }
      }
    });
  }
        _logger.warning("Empty or null command component ID provided, no event queued");
        return false;
} 

Of course this is a trivial example, and suffers like any of these techniques, from the risk that you might conceivably have more than one component with the same basic ID, so in this case I'd be sure that the ID assigned to the command button as already pretty unique. However, more complex uses of this traversal approach may be touching multiple components and that's where it really comes into it's own.

Anyway, I hope you get the message, never use the getChildren() recursion technique, use visitTree() instead. 

Wednesday Oct 15, 2014

Using a Translatable String as the Default Value for a Bind Variable

I've just been looking at a scenario where we needed to populate an ADFBC query bind variable using a predefined value. Now that in itself is not difficult - the bind variable definition takes a default value exactly for this, however, in this case the value supplied as the default needed to be read from the locale specific resource bundle.

I wondered here if it would be possible to convert the default value for the bind to be an expression rather than a literal and use groovy to derive the translated string?

Well it took a little bit of time to come up with the correct path, but indeed it turns out to be possible with one small constraint about the keys in the bundle that have to be used.

So to start off, here's the groovy expression I use in the default value for the bind variable: 

source.viewObject.getProperty('TRANSLATABLE_BINDVAR')

That expression refers to a translatable property called TRANSLATABLE_BINDVAR defined in the relevant View Object as a custom property (General finger tab, Custom Properties section). this, in turn, maps to a resource in the bundle with a key generated from the full name of the View Object + property name + "_VALUE". e.g. in this case:

oracle.adf.demo.model.ValueFromBIndVO.TRANSLATABLE_BINDVAR_VALUE

This is the resource that you can then translate and the value of which will be used at runtime.

An Explanation

 So what does this all mean? Unfortunately there is no way (that I've found) in groovy to reach directly into the ui hints of the bind variable itself If you start the expression with adf.object to start from the variable itself you can't really get anywhere.  So instead, we have to walk up the component hierarchy to the View Object that owns the bind variable and then get the resource from there. Just asking for the value of the custom TRANSLATABLE_BINDVAR property.  By using a translatable custom property for this, the developer has an existing UI to use that will create the resource keys for them. Furthermore, because we use the getProperty() API on the View Object  as an indirect way of getting the value, rather than using ResourceBundle APIs, we don't have to worry about finding out what locale to use - that's all handled automatically.

Thursday Oct 09, 2014

Showing Initial Selection in Your ListView

If you use a ListView or a Table component which enables selection by the user in a master detail view you may feel that there is a slight inconsistency of behaviour. When the user first enters the screen it may, by default, look something like this (in this example a ListView to the left and the "selected" department detail to the right in a form layout):

ListView showing no initial selection

Notice that the associated detail form is showing the first row in the collection as you would expect, but visually, the corresponding row in the ListView is not visually highlighted. Compare that with what happens when the user clicks on a row in the ListView to select another row:

ListView with a selection made by the user

Now the selected row in the listView is highlighted with the skin's selection colour.

So the question is, how can we show the page initially in a manner consistent with this?
e.g.

Corrected initial selection

Well in fact the binding layer already has everything you need and it's a trivial thing to fix. All we have to do here is to set the selectedRowKeys attribute of the listView tag. So, assuming we have a listView showing departments the tag would look something like this:

<af:listView value="#{bindings.DepartmentsView1.collectionModel}" 
             var="item"
             selectedRowKeys="#{bindings.DepartmentsView1.collectionModel.selectedRow}" 
             selection="single" 
             selectionListener="#{bindings.DepartmentsView1.collectionModel.makeCurrent}"
             emptyText="#{bindings.DepartmentsView1.viewable ? 'No data to display.' : 'Access Denied.'}"
             fetchSize="#{bindings.DepartmentsView1.rangeSize}" 
             id="lv1">

Doing It From Scratch

So this is an example where the binding layer does everything for you, great! However, what wanted to do this manually? I'm showing you this because it happens to illustrate a few useful techniques and code snippets.  In general I'd stick with the simple approach above though! 

Setting this up requires some simple steps and a small amount of code, so let's go.

Step 1: Defining the Selected Row Keys

The core mechanism of this solution is to pre-seed the selectedRowKeys property of the listView component with the row key of whichever row is current in the underlying collection.  So the first step is to define somewhere to hold that row key set. So we start by defining a ViewScope managed bean (called lvState in this example) and within that, we just define a variable to hold a RowKeySet reference.

So we start with the Java class:

package oracle.adf.demo;
import org.apache.myfaces.trinidad.model.RowKeySet;
public class ListViewState {
  RowKeySet _selectedRowKeys;

  public void setSelectedRowKeys(RowKeySet rks) 
  {
    this._selectedRowKeys; = rks;
  }

  public RowKeySet getSelectedRowKeys() 
  {
    return _selectedRowKeys;
  }
}

And define that as a managed bean in the relevant task flow definition:

<managed-bean>
   <managed-bean-name>lvState</managed-bean-name>
   <managed-bean-class>oracle.adf.demo.ListViewState</managed-bean-class>
   <managed-bean-scope>view</managed-bean-scope>
</managed-bean>

Once that's defined you can update your <af:listView> tag to reference it:

<af:listView value="#{bindings.DepartmentsView1.collectionModel}" 
             var="item"
             selectedRowKeys="#{viewScope.lvState.selectedRowKeys}" 
             selection="single" 
             selectionListener="#{bindings.DepartmentsView1.collectionModel.makeCurrent}"
             emptyText="#{bindings.DepartmentsView1.viewable ? 'No data to display.' : 'Access Denied.'}"
             fetchSize="#{bindings.DepartmentsView1.rangeSize}"
             id="lv1"> 

Step 2: Pre-Seeding the Row Key Set 

As defined, the row key set picked up by the listView will of course be null until the user physically makes a selection - we're no better off than before. So we now have to work out a way of taking the current selection from the model / binding layer, obtaining it's key and then setting up the row key set before the listView is rendered.

The simplest way of doing this is to utilise the capability of JSF 2 to simply define events to be executed as the page is being set up for rendering. In this case we use the postAddToView event inside of the listView to execute the setup code:

<af:listView ...>
  <f:event type="postAddToView" listener="#{lvEvents.setupListViewSelection}"/>
  <af:listItem ...>

The event tag points at a request scope managed bean called lvEvents.  This bean needs access to the lvState managed bean that we defined in the first step:

<managed-bean>
  <managed-bean-name>lvEvents</managed-bean-name>
  <managed-bean-class>oracle.adf.demo.ListViewEventManager</managed-bean-class>
  <managed-bean-scope>view</managed-bean-scope>
  <managed-property>
    <property-name>state</property-name>
    <property-class>oracle.adf.demo.ListViewState</property-class>
    <value>#{lvState}</value>
  </managed-property>
</managed-bean>

The ListViewEventManager class should therefore have a variable to hold the state with the appropriate getter and setter:

public class ListViewEventManager {
    ListViewState _state;
    public void setState(ListViewState _state) {
        this._state = _state;
    }
    public ListViewState getState() {
        return _state;
    }
    ...

Finally, the implementation of the listener defined in the <f:event> tag needs to be added to the class:

public void setupListViewSelection(ComponentSystemEvent componentSystemEvent) {
  if (getState().getRks() == null) {
     BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();
     DCIteratorBinding iterBind =
                ((DCBindingContainer)bindings).findIteratorBinding("DepartmentsView1Iterator");
            Key key = iterBind.getCurrentRow().getKey();
            RowKeySet rks = new RowKeySetImpl();
            rks.add(Collections.singletonList(key));
            getState().setRks(rks);
        }
}    

So now, when the page renders, if the selection row key set has not been setup due to user interaction we will go ahead and create it, seeding the rowkey of whatever row the model considers to be current.

Friday Aug 15, 2014

Ensuring High Availability in ADF Task Flows

Just a quick article today on ADF Controller Scopes and specifically ensuring that your application is correctly propagating state stored in PageFlow and View Scope across the cluster. This information can be found in the product doc and in Jobinesh Purushothaman's excellent book (Chapter 12 - Ensuring High Availability), however, more references means more eyes and fewer mistakes!  

Some Background

When you store state in a managed bean scope how long does it live and where does it live?  Well hopefully you already know the basic answers here, and for scopes such as Session and Request we're just dealing with very standard stuff. One thing that might be less obvious though, is how PageFlow and View Scope are handled.  Now these scopes persist (generally) for more than one request, so there is obviously the possibility that you might get a fail-over between two of those requests.  A Java EE server of whatever flavour doesn't know anything about these extra ADF memory scopes so it can't be automatically managing the propagation of their contents can it?  Well the answer is yes and no.  These "scopes" that we reference from the ADF world are ultimately stored on the Session (albeit with a managed lifetime by the framework), so you'd think that everything should be OK and no further work is going to be needed to ensure that any state in these scopes is propagated - right?  Well no, not quite, it turns out that several key tasks are often missed out. So let's look at those.

First of All -  Vanilla Session Replication

Assuming that WebLogic is all configured, this bit at least is all automatic right? Well no. In order to "know" that an object in the session needs to be replicated WebLogic relies on the HttpSession.setAttribute() API being used to put it onto the session. Now if you instanciate a managed bean in SessionScope through standard JSF mechanisms then this will be done and you're golden.  Likewise if you grab the Faces ExternalContext and grab the Session throught that (e.g. using the getSession() API), then call the setAttribute() API on HttpSession, you've correctly informed WebLogic of the new object to propagate.

You might already see though, that there is a potential problem in the case where the object stored in the session is a bean and you're changing one of its properties. Just calling an attribute setter on an object stored on the session will not be a sufficient trigger to have that updated object re-propagated, so the version of the object elsewhere will be stale.  So when you update a bean on the session in this way, and want to ensure that the change is propagated, then re-call the setAttribute() API.   
Got it? OK, on to the ADF scopes:

Five Steps to Success For the ADF Scopes 

The View and PageFlow scopes are, as I mentioned, ultimately stored on the session.  Just as in the case of any other object stored in that way,  changing an internal detail of those representaive objects would not trigger replication. So, we need some extra steps and of course we need to observe some key design principles whilst we're at it:

  1. Observe the UI Manager Pattern and only store state in View and PageFlow scope that is actually needed and is allowed (see 2)
  2. As for any replicatable Session scoped bean, any bean in View or PageFlow scope must be serializable (there are audits in JDeveloper to gently remind you of this).
  3. Only mark for storage that which cannot be re-constructed. Again a general principle; we wish to replicate as little as possible, so use the transient marker in your beans to exclude anything that you could possibly reconstruct over on the other side (so to speak). 
  4. In the setters of any attributes in these beans (that are not transient) call the ControllerContext markScopeDirty(scope) API. e.g. ControllerContext.getInstance().markScopeDirty(AdfFacesContext.getCurrentInstance().getViewScope());   This does the actual work of making sure that the server knows to refresh this state across the cluster
  5. Finally, set the HA flag for the controller scopes in the .adf/META-INF/adf-config file. This corresponds to the following section inside of the file:
<adf-controller-config xmlns="http://xmlns.oracle.com/adf/controller/config">
  <adf-scope-ha-support>true</adf-scope-ha-support>
</adf-controller-config> 
If this flag is not set, the aforementioned markScopeDirty() API will be a no-op. So this flag provides a master switch to throw when you need HA support and to avoid the cost when you do not. 

So if you've not done so already, take a moment to review your managed beans and check that you are really all doing this correctly. Even if you don't need to support HA today you might tomorrow... 

Wednesday Aug 13, 2014

Maven and ADFBC Unit Tests in 12.1.3

An issue that has come up recently has revolved around setting your Maven POM up in 12.1.3 such that you can run ADF BC JUnit Tests successfully both interactively in the IDE and headless through Maven maybe in your Hudson Jobs.  Out of the box, the default POM that you will end up with will be missing a couple of vital bits of information and need a little extra configuration.

Here are the steps you'll need to take:

Step 1: Use The Correct JUnit Dependency

Once you have created some unit tests JDeveloper will have added dependencies for JUnit from the JDeveloper JUnit extensions, something like this:

<dependency>
  <groupId>com.oracle.adf.library</groupId>
  <artifactId>JUnit-4-Runtime</artifactId>
  <version>12.1.3-0-0</version>
  <type>pom</type>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>com.oracle.adf.library</groupId>
  <artifactId>JUnit-Runtime</artifactId>
  <version>12.1.3-0-0</version>
  <type>pom</type>
  <scope>test</scope>
</dependency> 

Delete both of these entries, if they exist, and drop in a dependency to the vanilla JUnit 4.11 library instead:

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <type>jar</type>
  <scope>test</scope>
</dependency> 

Failing to make this change will result in the following error:

 java.lang.NoClassDefFoundError: org/junit/runner/notification/RunListener 

Step 2: Configure the Surefire Plugin to Select the Correct JUnit Version

This is probably not needed but it ensures that Surefire is left in no doubt about what version of JUnit it shoudl be working with (in this case a version of 4.7 or higher):

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.17</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven.surefire</groupId>
      <artifactId>surefire-junit47</artifactId>
      <version>2.17</version>
    </dependency>
  </dependencies>
</plugin>

Step 3 : Identifying Your connections.xml File 

When running outside of JDeveloper we need to set things up so that  the unit tests can actually find the connection information that defines the datasource that your Application Modules are using.  To do this, we need to add a configuration section to the  Surefire plugin to add the location into the classpath.  Add the collowing configuration into the Surefire plugin, after the <dependencies>  section:

<configuration>
  <additionalClasspathElements>
    <additionalClasspathElement>
      ${basedir}/../.adf
    </additionalClasspathElement>
  </additionalClasspathElements>
</configuration> 

This will ensure that the connection information can be found.  If you forget this step you'll get a stack trace including the message:

MDS-00013: no metadata found for metadata object "/META-INF/connections.xml" 

Step 4 - Supply the Missing JPS Library

Finally we need to supply the location of one extra required library.  This requirement will hopefully be resolved in the next release, but for now add it.  Again this is added to the Surefire plugin configuration <additionalClasspathElements> 

<additionalClasspathElement>
  ${oracleHome}/oracle_common/modules/oracle.jps_12.1.3/jps-manifest.jar
</additionalClasspathElement> 

Omitting this will result in the error:

WARNING: No credential could be loaded for Reference = Reference Class Name: oracle.jdeveloper.db.adapter.DatabaseProvider

For reference here's the complete Surefire plugin definition 

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.17</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven.surefire</groupId>
      <artifactId>surefire-junit47</artifactId>
      <version>2.17</version>
    </dependency>
  </dependencies>
  <configuration>
    <additionalClasspathElements>
      <additionalClasspathElement>
        ${basedir}/../.adf
      </additionalClasspathElement>
      <additionalClasspathElement>
        ${oracleHome}/oracle_common/modules/oracle.jps_12.1.3/jps-manifest.jar
      </additionalClasspathElement>
    </additionalClasspathElements>
  </configuration>
</plugin> 

Friday Jul 25, 2014

Per-Component Instance Skinning

A question that comes up from time to time happened to be raised twice three times this week, so I though it would be good to write the answer down.  The question is this; How can I override the visual properties of a single instance of a component? 

Now in many cases you can simply use inlineStyle or, (better) assign a styleClass to the component instance and associate a CSS file with the page using <af:resource>.  This will work in the simple case where, for example, it's a basic component such as an <af:spacer> or <af:panelGroupLayout layout="vertical">.  However, in the case of a more complex component you may not get the fidelity through inlineStyle, styleClass or even contentStyle. 

In these cases you will need to extend the skin to create a instance selector for the component type.  This will allow you to change any skinning slector, but rather than doing so for every instance of the component, you will end up with a sub-type that can be applied to just those instances that require it.

All you have to do here is to extend the skin as per normal (just create a new skin in 12.1.3 and all the metadata will be set up for you in the trinidad-skins.xml file). In the skin you define a custom style class and then append the selector that you want to change.  So for example, here's a custom version of one of the commandMenuItem selectors that sets a specific background:

.saveMenu af|commandMenuItem::menu-item-icon-style {
    background-image:url("images/save.png");
    background-repeat:no-repeat;
} 

And that's then applied to the menu item in question:

 
<af:commandmenuitem text="save" styleclass="saveMenu">

That's it, pretty simple but can be useful

Thursday Jun 26, 2014

Diagram at Last

This blog has been quiet for a while - not because I've abandonded it, but rather, because of what I've been working on.  Now that the 12.1.3 release of JDeveloper / ADF is available I can open up again, what a relief! And what is it I've been hacking on? Diagrams!. Yes, this is just the absolutely coolest (in my opinion) new feature of the 12.1.3 release of ADF - the  new DVT diagram component. It's a component that is so powerful and flexible in terms of the way that data can be represented and interacted with, that it's slightly too much to cover in a single Blog entry.  In fact, by my estimation it will take about 10 articles to cover the essentials of diagram and so that's exactly what I'll be doing.  However, those articles will not be here on the Groundside blog, but instead they'll be over on the new Data Visualization blog, but I'll publish an pointer index here as well.  I've just published the introduction to get you started and over the next week you'll have a daily injection of diagram goodness to build up your knowledge. So add the DVT blog to your RSS feed and sit back for the ride. 

Wednesday Feb 26, 2014

Customizing the Axis Labels in ADF Graphs

The various data visualization (DVT) graphs provided as part of the ADF Faces component set provide a very rich and comprehensive set of visualizations for representing your data.  One of the issues with them, that some folks struggle with however, is the fact that not all the features are controlled in an a completely declarative manner. 

In this article I want to concentrate on labeling capabilities for a graph axis, looking first of all that the declarative approaches, but then following that up with the more advanced programmatic option. 

Managing Labels Declaratively 

Control over the labels on the axis tick points is a good example of making the simple things declarative and the advanced things possible.  For basic numeric formatting you can do everything with tags - for example formatting as currency, percentage or with a certain precision.    

This is a default (bar)graph plotting employee salary against name, notice how the Y1 Axis has defaulted to a fairly sensible representation of the salary data using 0-14K:


I can change that default scaling by setting the scaling attribute in the <dvt:y1TickLabel> tag. This allows scaling at the level of none | thousand | million | billion | trillion | quadrillion (enough to show national debt then!):

<dvt:y1TickLabel id="y1TickLabel1" scaling="none"/>

Changes the graph to:


We can then further change the pattern of the numbers themselves by embedding <af:convertNumber> inside of the <dvt:y1TickLabel> tag.

e.g.

<dvt:y1TickLabel id="y1TickLabel1" scaling="none">
  <af:convertNumber type="currency" currencyCode="USD"/>
</dvt:y1TickLabel> 

Adds currency formatting:

And using the <dvt:graphFont> we can change colors and style:

<dvt:y1TickLabel id="y1TickLabel1" scaling="none">
  <dvt:graphFont name="SansSerif" size="8" color="#FF0000" bold="true" italic="true" />
  <af:convertNumber type="currency" currencyCode="USD"/>
</dvt:y1TickLabel> 

Giving:


Need More Control?  Using the TickLabelCallback...

So we can achieve quite a lot by simply using the tags.  However, what about a more complex requirement such as replacing a numerical value on an axis with a totally different string e.g. converting to a Roman Numeral (I, IV XII etc.) or maybe converting a millisecond value to a formatted date string?  To do this, ADF provides a simple callback that you can implement to map a value to whatever string you need.  Here's a simple case where I've plotted the salaries in department 100 of the standard HR EMPLOYEES demo table against the HireDate on a scatter plot.  For the sake of illustration I've actually converted the HireDate to it's time value (e.g. a long value representing the number of milliseconds since 01/01/1970) .  In a real graph I'd use the proper support that we have for representing time and date and just map a date object. Then you get to use the timeSelector and can directly set the formatting, however, bear with me because I'm just illustrating the point here.

Here's the default output with the millisecond version of the date, as you can see the millisecond value gets automatically scaled to the billions level.

To override the default representation of the millisecond value we will need to create a java class that implements the oracle.dss.graph.TickLabelCallback interface.  Here's the simple example I'll use in this case:

import java.text.SimpleDateFormat;
import oracle.dss.graph.DataTickLabelInfo;
import oracle.dss.graph.GraphConstants;
import oracle.dss.graph.TickLabelCallback;
import oracle.dss.graph.TickLabelInfo;

public class MSToDateFormatLabelCallback implements TickLabelCallback, Serializable {

  @Override
  public String getTickLabel(TickLabelInfo tickLabelInfo, int axisID) {
    String label = null;
    if (axisID == GraphConstants.X1TICKLABEL) {
      long timeInMillis = (long) ((DataTickLabelInfo) tickLabelInfo).getValue();
      SimpleDateFormat fmt = new SimpleDateFormat("MM/yy");
      label = fmt.format(timeInMillis);
    } else {
      label = "" + ((DataTickLabelInfo) tickLabelInfo).getValue();
    }
    return label;
  }
} 

As you can see the formatting  is applied only to the specified axis and we have to upcast the tickLabelInfo argument to a DataTickLabelInfo in order to gain access to the value that is being applied. Note that the callback class must also be Serializable. 

Once you have this class, then you need to apply it to the chart instance by calling the relevant set*TickLabelCallback. So for example I might set this in the backing bean for a page in the setter used by the binding attribute on the dvt graph component

public class GraphPageHandler {
    private UIGraph scatterPlot;
    public void setScatterPlot(UIGraph scatterPlot) {
        this.scatterPlot = scatterPlot;
        scatterPlot.setX1TickLabelCallback(new MSToDateFormatLabelCallback());
    } 

Now with the callback in place and the addition of:

  1. Using  the axisMinValue attribute along with axisMinAutoScaled="false" on the <dvt:x1Axis>  to reset the start date for the plot to the year 2000 rather than the default 1970
  2. Some of the currency formatting that we've already looked at to format the Y axis

Here's the result:

Tuesday Feb 25, 2014

Stretching an inputText inside of a Table Column

A quick trick to note if you have a table that includes a column containing an <af:inputText>. If the user is able to re-size the columns within the table, then it's not unreasonable to have the inputText stretch along with the column to take advantage of the full width available. 

The first thing to do is to set the simple property of the inputText to "true".  This almost does the trick, but you'll find that as you stretch the column there is a small trailing gap between the end of the input field and the column which grows as the column is stretched. So although the input is stretched as well it does not fill the cell which looks a little messy.

The second trick then is to set the contentStyle property on the inputText to "min-width:100%;" Do this and you will eliminate that gap and the input will now fill the column cell completely as you resize. 

About

Hawaii, Yes! Duncan has been around Oracle technology way too long but occasionally has interesting things to say. He works in the Development Tools Division at Oracle, but you guessed that right? In his spare time he contributes to the Hudson CI Server Project at Eclipse
Follow DuncanMills on Twitter

Note that comments on this blog are moderated so (1) There may be a delay before it gets published (2) I reserve the right to ignore silly questions and comment spam is not tolerated - it gets deleted so don't even bother, we all have better things to do with our lives.
However, don't be put off, I want to hear what you have to say!

Search

Archives
« May 2015
MonTueWedThuFriSatSun
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
       
Today