Saturday Jul 14, 2012

Auto-Reduce Search Sample

For a while now I've been playing with techniques in ADF applications which will produce a user experience that is more webby (if I can use that term) and less business application like.  Some of this work can be seen if you look back on my postings on animation.

A recent challenge, in this vein, from one of the internal teams within Oracle, was to provide a search facility that would auto-reduce the results list in situe without the user having to press a button or link to trigger the search.

Now hopefully you're familiar with the component behaviour <af:autoSuggestBehavior> that will provide a drop down list below a test input that revises as you type. For example:

autosuggestBehavior in action

But that's not what I wanted here. In this case, I wanted the main search results to update as I typed - a feature you will find on certain popular search engines. As part of the process of putting this together, I found myself combining code and patterns from other prototypes that I've worked on and came to the conclusion that I could wrap all of this into a nice little demo application that actually shows several interesting techniques and patterns as well as the aforementioned auto-reduce.

Here's the screen, it's a simple search against ALL_OBJECTS in the database, and the features of it that I thought were interesting. 

Screen Shot of sample application in row view

  1. The screen uses a fixed width centered display area, a fairly popular layout pattern for a lot of sites, including this blog.
  2. The images displayed by each row use a technique called the ImageMap Pattern to derive the correct image to display. I'll be talking about two variants on this pattern, the more interesting one of which using image sprites as a way to reduce your network traffic.
  3. Typing in the search field will (after a configurable delay) cause the query to be re-executed and a revised list displayed.
  4. We have a dynamic record count which shows the records shown out of the total. 
  5. The list view here has a smart pagination bar which allows the user access to the start and end of the list without printing out every option in between.
  6. You can change how many records are displayed and hence the size of the pagination.
  7. Finally you can switch between row and icon views. This latter function is interesting because it's carried out client side to minimize the switch time.

The results of typing into the search screen would look something like this:

AutoReduce results

 As you type, not only will the list reduce, but of course the pagination bar etc. will be updated to reflect the current result set size.

The icon view mentioned in (7) looks like this:

Icon View

Over the next couple of weeks I'll be writing detailed articles on these various features, but if you can't wait to get started, you can download the sample from the ADF Samples project on Java.net: DRM004 - AutoReduce and Pagination screen

Monday Jul 09, 2012

ADF and EBS Applications

A blog entry that may be of interest to those of using building ADF apps that, in some way, need to integrate with Oracle E-Business Suite. Head over to Steven Chan's Applications Technology Blog: Building Extensions Using E-Business Suite SDK for Java

Tuesday Jul 03, 2012

New Sample Demonstrating the Traversing of Tree Bindings

A technique that I seem to use a fair amount, particularly in the construction of dynamic UIs is the use of a ADF Tree Binding to encode a multi-level master-detail relationship which is then expressed in the UI in some kind of looping form – usually a series of nested af:iterators, rather than the conventional tree or treetable. This technique exploits two features of the treebinding. First the fact that an treebinding can return both a collectionModel as well as a treeModel, this collectionModel can be used directly by an iterator. Secondly that the “rows” returned by the collectionModel themselves contain an attribute called .children. This attribute in turn gives access to a collection of all the children of that node which can also be iterated over.

Putting this together you can represent the data encoded into a tree binding in all sorts of ways.

As an example I’ve put together a very simple sample based on the HT schema and uploaded it to the ADF Sample project. It produces this UI:

Example output from this technique

The important code is shown here for a Region -> Country -> Location Hierachy:

<af:iterator id="i1" value="#{bindings.AllRegions.collectionModel}" var="rgn">
  <af:showDetailHeader text="#{rgn.RegionName}" disclosed="true" id="sdh1">
    <af:iterator id="i2" value="#{rgn.children}" var="cnty">
      <af:showDetailHeader text="#{cnty.CountryName}" disclosed="true" id="sdh2">
        <af:iterator id="i3" value="#{cnty.children}" var="loc">
          <af:panelList id="pl1">
            <af:outputText value="#{loc.City}" id="ot3"/>
          </af:panelList>
        </af:iterator>
      </af:showDetailHeader>
    </af:iterator>
  </af:showDetailHeader>
</af:iterator> 

You can download the entire sample from here:

Sunday Jun 24, 2012

Making Those PanelBoxes Behave

I have a little problem to solve earlier this week - misbehaving <af:panelBox> components... What do I mean by that? Well here's the scenario, I have a page fragment containing a set of panelBoxes arranged vertically. As it happens, they are stamped out in a loop but that does not really matter. What I want to be able to do is to provide the user with a simple UI to close and open all of the panelBoxes in concert. This could also apply to showDetailHeader and similar items with a disclosed attrubute, but in this case it's good old panelBoxes. 

Ok, so the basic solution to this should be self evident. I can set up a suitable scoped managed bean that the panelBoxes all refer to for their disclosed attribute state. Then the open all / close commandButtons in the UI can simply set the state of that bean for all the panelBoxes to pick up via EL on their disclosed attribute. Sound OK? Well that works basically without a hitch, but turns out that there is a slight problem and this is where the framework is attempting to be a little too helpful. The issue is that is the user manually discloses or hides a panelBox then that will override the value that the EL is setting. So for example.

  1. I start the page with all panelBoxes collapsed, all set by the EL state I'm storing on the session
  2. I manually disclose panelBox no 1.
  3. I press the Expand All button - all works as you would hope and all the panelBoxes are now disclosed, including of course panelBox 1 which I just expanded manually.
  4. Finally I press the Collapse All button and everything collapses except that first panelBox that I manually disclosed. 
The problem is that the component remembers this manual disclosure and that overrides the value provided by the expression. If I change the viewId (navigate away and back) then the panelBox will start to behave again, until of course I touch it again! Now, the more astute amoungst you would think (as I did) Ah, sound like the MDS personalizaton stuff is getting in the way and the solution should simply be to set the dontPersist attribute to disclosed | ALL. Alas this does not fix the issue. 

After a little noodling on the best way to approach this I came up with a solution that works well, although if you think of an alternative way do let me know. The principle is simple. In the disclosureListener for the panelBox I take a note of the clientID of the panelBox component that has been touched by the user along with the state. This all gets stored in a Map of Booleans in ViewScope which is keyed by clientID and stores the current disclosed state in the Boolean value. 

The listener looks like this (it's held in a request scope backing bean for the page):

public void handlePBDisclosureEvent(DisclosureEvent disclosureEvent) {
  String clientId = disclosureEvent.getComponent().getClientId(FacesContext.getCurrentInstance());
  boolean state = disclosureEvent.isExpanded();
  pbState.addTouchedPanelBox(clientId, state);
}

The pbState variable referenced here is a reference to the bean which will hold the state of the panelBoxes that lives in viewScope (recall that everything is re-set when the viewid is changed so keeping this in viewScope is just fine and cleans things up automatically). The addTouchedPanelBox() method looks like this:

public void addTouchedPanelBox(String clientId, boolean state) {
  //create the cache if needed this is just a Map<String,Boolean>
  if (_touchedPanelBoxState == null) {
    _touchedPanelBoxState = new HashMap<String, Boolean>();
  }
  // Simply put / replace
  _touchedPanelBoxState.put(clientId, state);
}

So that's the first part, we now have a record of every panelBox that the user has touched. So what do we do when the Collapse All or Expand All buttons are pressed? Here we do some JavaScript magic. Basically for each clientID that we have stored away, we issue a client side disclosure event from JavaScript - just as if the user had gone back and changed it manually.

So here's the Collapse All button action:

public String CloseAllAction() {
  submitDiscloseOverride(pbState.getTouchedClientIds(true), false);
  _uiManager.closeAllBoxes();
  return null;
} 

The _uiManager.closeAllBoxes() method is just manipulating the master-state that all of the panelBoxes are bound to using EL. The interesting bit though is the line: 

submitDiscloseOverride(pbState.getTouchedClientIds(true), false);

To break that down, the first part is a call to that viewScoped state holder to ask for a list of clientIDs that need to be "tweaked":

public String getTouchedClientIds(boolean targetState) {
  StringBuilder sb = new StringBuilder();
  if (_touchedPanelBoxState != null && _touchedPanelBoxState.size() > 0) {
    for (Map.Entry<String, Boolean> entry : _touchedPanelBoxState.entrySet()) {
      if (entry.getValue() == targetState) {
        if (sb.length() > 0) {
          sb.append(',');
        }
        sb.append(entry.getKey());
      }
    }
  }
  return sb.toString();
}

You'll notice that this method only processes those panelBoxes that will be in the wrong state and returns those as a comma separated list.

This is then processed by the submitDiscloseOverride() method:

private void submitDiscloseOverride(String clientIdList, boolean targetDisclosureState) {
  if (clientIdList != null && clientIdList.length() > 0) {
    FacesContext fctx = FacesContext.getCurrentInstance();
    StringBuilder script = new StringBuilder();
    script.append("overrideDiscloseHandler('");
    script.append(clientIdList);
    script.append("',");
    script.append(targetDisclosureState);
    script.append(");");
    Service.getRenderKitService(fctx, ExtendedRenderKitService.class).addScript(fctx, script.toString());
  }
}

This method constructs a JavaScript command to call a routine called overrideDiscloseHandler() in a script attached to the page (using the standard <af:resource> tag). That method parses out the list of clientIDs and sends the correct message to each one:

function overrideDiscloseHandler(clientIdList, newState) {
  AdfLogger.LOGGER.logMessage(AdfLogger.INFO, "Disclosure Hander newState " + newState + " Called with: " + clientIdList);
  //Parse out the list of clientIds
  var clientIdArray = clientIdList.split(',');
  for (var i = 0; i < clientIdArray.length; i++){
    var panelBox = flipPanel = AdfPage.PAGE.findComponentByAbsoluteId(clientIdArray[i]);
    if (panelBox.getComponentType() == "oracle.adf.RichPanelBox"){
      panelBox.broadcast(new AdfDisclosureEvent(panelBox, newState));
    }
  } 
} 

So there you go. You can see how, with a few tweaks the same code could be used for other components with disclosure that might suffer from the same problem, although I'd point out that the behavior I'm working around here us usually desirable.

You can download the running example (11.1.2.2) from here

Thursday May 17, 2012

Setting Up Embedded WLS for MySQL

For a while, on and off,  I've been playing with MySQL in various applications, to the extent that it made sense to work out how to specifically configure the domain so that the MySQL driver would always be available. The advice in the great googleblogosphere seems to be to drop the jar file in the DefaultDomain/lib directory and all will be well. But although you can then see the jar being loaded as WLS starts up, it certainly wasn't working for some of my pre-loading services within the container which could still not find the driver. (Although I'm not saying that this technique would not be OK for an ADF application that you deploy that uses MySQL)  

In the end I thought that the simplest thing was to work out what we do with the Oracle driver and emulate that. Sure enough it's there in the /DefaultDomain/bin/setDomainEnv script (.cmd or .sh).  You'll find it in there in the PRE_CLASSPATH section. So my solution was to simply add the driver reference there (code below reformatted for clarity):

if NOT "%PRE_CLASSPATH%"=="" (
  set PRE_CLASSPATH=%COMMON_COMPONENTS_HOME%\modules\oracle.jdbc_11.1.1\ojdbc6dms.jar;
                    d:\shared\mysql\mysql-connector-java-5.1.18-bin.jar;%PRE_CLASSPATH%
) else (
  set PRE_CLASSPATH=%COMMON_COMPONENTS_HOME%\modules\oracle.jdbc_11.1.1\ojdbc6dms.jar;
                    d:\shared\mysql\mysql-connector-java-5.1.18-bin.jar
)

And that's certainly done the trick for me.  So I think I can guarantee that at least one of the two methods here will work for you... At some point I'll put together a proper MySQL Extension for JDev that creates a library, sets this up, and registers a custom type-map for ADFBC, but alas time is always at a premium... 

Wednesday May 16, 2012

Placeholder Watermarks with ADF 11.1.2

I'm sure you're all familiar with the concept of watermarking an input field to provide the user with a greyed out hint as to what to do with it.  This is often used in search boxes or UIs where space is at a premium and you really don't have room for a label. Here's a small screenshot that shows what I mean:

Image of watermark in use

As you can see,  both the filter field and multi-line field have such text.  As soon as the user starts to enter values in these fields the watermark will disappear only to reappear if the user clears out the field.  In the past, there have been various JavaScript based solutions to this, but the HTML5 spec introduces a common way of doing this using an attribute on the <input> element called placeholder. Alas, only Chrome and FireFox have implemented this in their later versions, although it's on the list for IE 10. 

Now I probably won't be giving too much away if I let slip that placeholder support might possibly be standard in a future version of ADF Faces, but for now, I'm working in 11.1.2.2, so here's a simple implementation in the form of a custom JSF clientBehavior that will do it. 

For this, I actually took inspiration from a recent blog posting from Donatas Valys: Set Initial Focus component for Oracle ADF Faces.  He hit upon the smart idea of using a client behavior to mark a particular component as having initial focus, I've used essentially the same technique here, although extended a little bit because of the nature of the job I'm doing.

Create your Tag Library

So the first step will to create the tag (it will be called <afg:setWatermarkBehavior>) in your project.  Just select New > Web Tier > JSF/Facelets > Facelets Tag Library.  On the first page of the creation wizard, choose Deployable or Project Based, the choice is yours. For convenience I chose Project Based, then on Step 2 provide a file name and a namespace. I used adfglitz.taglib.xml and http://xmlns.oracle.com/adf/faces/glitz respectively, choose suitable values for your implementation. This step will do everything to register the Tag Library with the project (you'll see an entry is added to web.xml) 

Define the Behavior Definition 

Now you can edit the tag file and add the new behavior (or as I would have it "behaviour"). The taglib editor provides an overview view to make this simple:

Tag Lib editor

The important things to note here are:

  • The namespace (http://xmlns.oracle.com/adf/faces/glitz), we'll need that when adding this tag to the page. 
  • The name of the behavior tag - setWatermarkBehavior
  • The ID of the behavior tag - oracle.demo.adfglitz.watermark - I'll use this to associate an implementation class with the tag
  • The attributes.  Note that I've defined one attribute for the tag called value. I'll use this to pass the actual text that needs to be displayed in the placeholder.

You can switch to the source view of the editor and provide more information such as a friendly description of the taglib and tag, but you don't actually need to. 

Implement the Behavior

 Next we need to actually create a class that provides the implementation of the behavior. This needs to extend javax.faces.component.behavior.ClientBehaviorBase and implement javax.faces.component.StateHolder. The latter interface implementation is to ensure that the String passed in as the value of the tag is persisted and will survive re-builds of the component tree. 

The basic class definition therefore looks like this: 

@FacesBehavior("oracle.demo.adfglitz.watermark")
public class SetWatermarkBehavior extends ClientBehaviorBase implements StateHolder {...}

Note that I use the FacesBehavior annotation  to do the wiring between this implementation class and the taglib using the ID attribute defined above.

The other thing we need to implement the tag contract is the "value" that must be passed in as a  required attribute for the tag.  This is done with a simple member variable (String value;) and an associated public getter and setter pair on the class - getValue(), setValue() - standard stuff. We also have to manage the storage of this value by implementing the StateHolder interface. The code for that is not very interesting so I'll not reproduce it here, you can see it in the complete java class though.

The core method within the behavoir class is the getScript() method. This is called to encode the JavaScript  to send down with the enclosing component, however, we're going to subvert it a little - let me explain why.  If you have a clientBehavior associated with an inputItem then any script that you return from the getScript() method will be associated with a value change listener on the component.  In this case, that's not what we want, rather than applying the watermark when the value of the input changes, we want to apply it at the point in time the component is rendered. So to achieve this we actually just use and abuse the getScript() call and use the Trinidad ExtendedRenderKitService to queue up execute the JavaScript we need in a just in timely way.

Here's the implementation of the method:

//Constants used by getScript() defined at class level
private static final String SCRIPT_PREFIX = "addWatermarkBehavior(\"";
private static final String SCRIPT_ARG_SEPARATOR = "\",\""; 
private static final String SCRIPT_SUFFIX = "\");"; 
 
@Override
public String getScript(ClientBehaviorContext ctx) {
  UIComponent component = ctx.getComponent();
  //only contiune if the component is currently rendered
  if (component.isRendered()) {
    String componentType = deduceDOMElementType(component);
    //Only continue if the component is a valid type
    if (!componentType.equals(UNSUPPORTED_ELEMENT)) {
      String wmText = (getValue() == null) ? EMPTY_DEFAULT : getValue();
      StringBuilder script = new StringBuilder(SCRIPT_PREFIX);
      script.append(ctx.getSourceId());
      script.append(SCRIPT_ARG_SEPARATOR);
      script.append(componentType);
      script.append(SCRIPT_ARG_SEPARATOR);
      script.append(wmText);
      script.append(SCRIPT_SUFFIX);

      //We don't have an init event, just valueChange so push the code this way
      FacesContext fctx = ctx.getFacesContext();
      ExtendedRenderKitService extendedRenderKitService =
               Service.getRenderKitService(fctx, ExtendedRenderKitService.class);
      extendedRenderKitService.addScript(fctx, script.toString());
    }
  }
  //And return nothing as we don't need a valuechangeListener
  return "";
}

Things to note here.  We could of course encode the entire JavaScript  function within the script string generated above. However, given that in my case, I have several uses of the behavior in the app it makes sense to shove the detail of that code into a common .js file. I already have this available on the page and call a simple function - addWatermarkBehavior(), passing the relevant component ID, type and placeholder value.  That JavaScript can be seen below.

Another point is that getScript() uses the convenience method deduceDOMElementType() which, from the component and its attributes works out: First of all if it's a valid component on which to do anything, and secondly if the placeholder will need to be set on an html <input> element or an <textarea>.

//Constants used by deduceDOMElementType() defined at class level
private static final String UNSUPPORTED_ELEMENT = "unsupported";
private static final String TEXTAREA_ELEMENT = "textarea";
private static final String INPUT_ELEMENT = "input";

private String deduceDOMElementType(UIComponent component) {
  String componentType = UNSUPPORTED_ELEMENT;
  //work out the correct component type
  if (component instanceof RichInputText) {
    //In this case we may have a multi-line item but assume intially that this is not the case
    componentType = INPUT_ELEMENT;
    //Now check for the rows attribute to see if this is multi-line
    Map<String, Object> compAttr = component.getAttributes();
    if (compAttr.containsKey("rows")) {
      int rows = (Integer)compAttr.get("rows");
      if (rows > 1) {
         componentType = TEXTAREA_ELEMENT;
      }
    }
  } else if (component instanceof RichInputDate || 
             component instanceof RichInputListOfValues ||
             component instanceof RichInputComboboxListOfValues) {
    //These all resolve to inputs at the DOM level
    componentType = INPUT_ELEMENT;
  }
  return componentType;
}

The JavaScript

 As I mentioned above, rather than stream down reams of script for each component I have a standard JavaScript file attached to my page using the <af:resource> tag and in that I've implemented the simple function to locate the correct component on the page and apply the correct placeholder text. You could also use this method as a place to add a script based solution to browsers that don't support placeholder. Here's the  function:

function addWatermarkBehavior(componentId, type, text){
    var sourceInput = AdfPage.PAGE.findComponent(componentId);
    var domElemement = AdfAgent.AGENT.getElementById(sourceInput.getClientId());
    var targetArray = domElemement.getElementsByTagName(type);
    targetArray[0].placeholder = text;
}

As you can see, pretty short and sweet, you could of course add various validations to check you have a real element  etc. but let's keep it simple.

Using the Behavior

So that's about it. The final point is, of course how to use this. Well all you need to do is register the namespace in your page or fragment as I've done here using the afg: prefix:

<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets" 
                xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
                xmlns:afg="http://xmlns.oracle.com/adf/faces/glitz"
                xmlns:f="http://java.sun.com/jsf/core"> 

And then use the tag thus:

<af:inputText ...>
  <afg:setWatermarkBehaviour value="Filter Items"/> 
</af:inputText> 

 Enjoy.

Tuesday Apr 24, 2012

Using inputNumberSlider for Dates

I'm currently working on a prototype User Interface for an internal project that surfaced a requirement for allowing date selection using a slider control.  ADF Faces itself only supports two forms of slider control (<af:inputNumberSlider> and <af:inputRangeSlider>) , so what to do? Well putting aside for one moment the aesthetic and usability of using a slider for date selection ( not something I wholly buy into myself), can it be done? 

The simple answer is (of course and hence the article) yes.

Is it a Date? Is it a Number? 

Fortunately it's both. Java dates are stored internally as longs so there is no fundamental issue with using the inputNumberSlider to select one, providing that we get the precision right - milliseconds are probably not that useful as an increment. However, if we try and base a inputNumberSlider on the millisecond value of a date, the main problem is going to be the labels - in fact here's what it might look like:

Time slider with default labelling

So how do we  use this component but convert the labels to something sensible such as dates in the format "MM/dd", ending up with this:

Tome slider with corrected labelling

Well to achieve that we need a custom converter which can be assigned to the converter property of the component thus:

<af:inputNumberSlider label="Pick a day" id="ins1"
    minimum="#{uiManager.dateRangeSliderStartDate}"
    maximum="#{uiManager.dateRangeSliderEndDate}"
    majorIncrement="#{uiManager.dateRangeSlider30DayIncrement}"
    minorIncrement="#{uiManager.dateRangeSlider1DayIncrement}"
    value="#{uiManager.pickedDateAsLong}"
    converter="SliderDateConverter" 
    contentStyle="width:50em;"/> 

Defining The Converter

 Before I proceed here, credit has to go to my good buddy Matthias Wessendorf who's code from this article I have freely adapted here.

To define the converter, there are three steps:

  1. Write a server side converter in Java. 
  2. Write a client-side converter in JavaScript
  3. Register the converter with Faces 

The Server Side Converter Class

The server side converter is called by the framework as it initially renders the component on the page. It will call into this class several times to generate the major tick labels and of course the label for the slider value.  The converter class needs to implement two interfaces; org.apache.myfaces.trinidad.convert.ClientConverter and javax.faces.convert.Converter. In this case I've only had to implement four methods, two of which relate to the wiring up of the client JavaScript to the converter and the others manage the conversion itself. Let's look at those latter two first. 

Converters in JSF handle the basic problem of taking a value Object  and converting it into a String form that can be sent down to the browser in HTML and then the reverse of that cam task of taking the String value that gets sent up on the request and converting that back into the Object value form. 

So in this case we're attempting to convert a Long object (for convenience I'm actually storing the value as a long and then providing a typed getter to provide the actual date value when it's asked for). The conversion will be something like this:

1335135600000 --> "04/24"

So this paired conversion is handled by two methods called getAsString() and getAsObject() and the implementations are pretty simple - just a bit of string parsing and date arithmetic / formatting. I'm using the org.apache.commons.lang.time.DateFormatUtils helper class as well:

public String getAsString(FacesContext facesContext, UIComponent uIComponent, Object valueAsObject) {
  long selectedValue =  ((Double)valueAsObject).longValue();
  return DateFormatUtils.format(selectedValue, "MM/dd");
}
public Object getAsObject(FacesContext facesContext, UIComponent uIComponent, String valueAsString) {
  Calendar cal = new GregorianCalendar();
  int currentMonth = cal.get(Calendar.MONTH); /* Zero based */
  int currentYear = cal.get(Calendar.YEAR);
        
  //Parse the supplied String assuming the format MM/dd in this case
  String[] dateBits = valueAsString.split("[/]");
  int month = Integer.parseInt(dateBits[0]) - 1;
  int day = Integer.parseInt(dateBits[1]);
  int year = currentYear;
  //Handle the situation where the span crosses a year boundary
  //In my specific use case the dates all work backwards from today
  if (month > currentMonth){
    year--;
  }
        
  //Reconstruct the actual date
  Calendar selectedAsDateCal = new GregorianCalendar(year,month,day);
  return selectedAsDateCal.getTimeInMillis();
}

So the only complexity in this case is in the case where the selected String value is something like "11/30" which, because the range of my slider extends into the past from the current date, has to be interpreted as 30th November 2011 not 30th November 2012. Of course if you are trying to create a slider that extends across multiple years you'll have to encode the year into the string as well - month and day alone will not give you enough information.

The second two methods I need to implement wire up the JavaScript. The function getClientLibrarySource() tells the framework what .js file the client converter is in and  getClientConversion() defines the name of the converter function:

public String getClientLibrarySource(FacesContext facesContext) {
  return facesContext.getExternalContext().getRequestContextPath() + 
                          "/resources/js/sliderDateConverter.js";   
}

public String getClientConversion(FacesContext facesContext, UIComponent uIComponent) {
  return ("new SliderDateReformatter()");
} 

The Client Side Converter 

 As specified above, the client converter is defined in a file called sliderDateConverter.js in my PUBLIC_HTML/resources/js directory. This converter is called as the use moves the slider around, so unlike the server side code which is used to format the labels as well, this is really just used to format the label on the selector and it's value tooltip.  The underlying logic is essentially identical to the Java version just converted to JavaScript. Again it's just a matter of methods to convert from Object to String and back. You'll note here as well, that the prototype of the SliderDateReformatter  is set to TrConverter, this is the equivalent, to implementing the Converter interface in Java terms.

function SliderDateReformatter()
{   
}

SliderDateReformatter.prototype = new TrConverter();

SliderDateReformatter.prototype.getFormatHint = function()
{
	return null;
}

SliderDateReformatter.prototype.getAsString = function(dateMillis,label) {
	var asDate  = new Date(dateMillis);
        var month = asDate.getMonth()+1; /* Again zero based */
        var day = asDate.getDate();
	return month + "/" + day;
}

SliderDateReformatter.prototype.getAsObject = function(dateString,label){
        var dateNow = new Date();
        var currentYear = dateNow.getFullYear();
        var currentMonth = dateNow.getMonth();
        var dateBits = dateString.split("/");
        var selectedMonth = (dateBits[0]) - 1;
        var selectedDay = dateBits[1];
        var selectedYear = currentYear;
        
        if (selectedMonth > currentMonth){
            selectedYear--;
        }
        var representedDate = new Date(selectedYear,selectedMonth,selectedDay);
	return representedDate.getTime();
} 

Register the Converter

 The final step is to register the converter by name in the faces-config.xml file. This allows the framework to match the reference converter="SliderDateConverter" made by the component with the actual converter class. Just edit the faces-config and set this in the Overview editor Converters page, or add it directly to the XML, thus:

<faces-config version="2.1" xmlns="http://java.sun.com/xml/ns/javaee">
  <application>
    <default-render-kit-id>oracle.adf.rich</default-render-kit-id>
  </application>
  ...
  <converter>
    <converter-id>SliderDateConverter</converter-id>
    <converter-class>oracle.demo.view.slider.SliderDateConverter</converter-class>
  </converter>
</faces-config> 

Wrap Up

So as we've seen it's not too difficult to use the inputNumberSlider to represent data that, at first glance, is not numerical.  The same technique can be used to control the tick labelling of the component, even when you are dealing with "real" numbers, for example you might want to define a slider that allows the user to pick a percentage from the range 1%-100% and map that onto an underlying value of 0.01 to 1. You'd use exactly the same technique to do so if you were writing things from scratch, however, that one's already handled for you! Just embed a <af:convertNumber type="percent"/> as a child of the component.

We can also use the same technique for <af:inputRangeSlider> as well.

Friday Apr 13, 2012

Skinning af:selectOneChoice

A question came in today about how to skin the selection button (Image of a drop down list)  of an <af:selectOneChoice>. If you have a delve in the ADF Skinning editor, you'll find that there are selectors for the selectOneChoice when in compact mode (af|selectOneChoice::compact-dropdown-icon-style), however, there is not a selector for the icon in the "normal" mode. I had a quick delve into the skinning source files that you can find in the adf-richclient-impl-11.jar and likewise there seemed to be no association there. However, a quick sample page and a peek with Chrome developer tools revealed the problem.  The af:selectOneChoice gets rendered in the browser as a good old <select> element (reasonable enough!). Herein lies the problem, and the reason why there is no skin selector. The <select> HTML element does not have a standard way of replacing the image used for the dropdown button.  If you have a search around with your favorite search engine, you can find various workarounds and solutions for this.  For example, using Chrome and Safari you can define the following for the select element:

select {
  -webkit-appearance: listbox;
  background-image: url(blob.png); 
  background-position: center right;
  background-repeat: no-repeat;
  }

Which gives a very exciting select box: Image of updated selectOneChoice .

Wednesday Apr 11, 2012

More Animation - Self Dismissing Dialogs

In my earlier articles on animation, I discussed various slide, grow and  flip transitions for items and containers.  In this article I want to discuss a fade animation and specifically the use of fades and auto-dismissal for informational dialogs.  If you use a Mac, you may be familiar with Growl as a notification system, and the nice way that messages that are informational just fade out after a few seconds. So in this blog entry I wanted to discuss how we could make an ADF popup behave in the same way. This can be an effective way of communicating information to the user without "getting in the way" with modal alerts. This of course, has been done before, but everything I've seen previously requires something like JQuery to be in the mix when we don't really need it to be. 

The solution I've put together is nice and generic and will work with either <af:panelWindow> or <af:dialog> as a the child of the popup. In terms of usage it's pretty simple to use we  just need to ensure that the popup itself has clientComponent is set to true and includes the animation JavaScript (animateFadingPopup) on a popupOpened event:

<af:popup id="pop1" clientComponent="true">
  <af:panelWindow title="A Fading Message...">
   ...
 </af:panelWindow>
  <af:clientListener method="animateFadingPopup" type="popupOpened"/>
</af:popup> 

 The popup can be invoked in the normal way using showPopupBehavior or JavaScript, no special code is required there.

As a further twist you can include an additional clientAttribute called preFadeDelay to define a delay before the fade itself starts (the default is 5 seconds) . To set the delay to just 2 seconds for example:

<af:popup ...>
  ...
  <af:clientAttribute name="preFadeDelay" value="2"/>
  <af:clientListener method="animateFadingPopup" type="popupOpened"/> 
</af:popup>

The Animation Styles 

As before, we have a couple of CSS Styles which define the animation, I've put these into the skin in my case, and, as in the other articles, I've only defined the transitions for WebKit browsers (Chrome, Safari) at the moment. In this case, the fade is timed at 5 seconds in duration.

.popupFadeReset {
  opacity: 1;
}

.popupFadeAnimate {
  opacity: 0;
  -webkit-transition: opacity 5s ease-in-out;
}

As you can see here, we are achieving the fade by simply setting the CSS opacity property.

The JavaScript

The final part of the puzzle is, of course, the JavaScript, there are four functions, these are generic (apart from the Style names which, if you've changed above, you'll need to reflect here):

  1. The initial function invoked from the popupOpened event,  animateFadingPopup which starts a timer and provides the initial delay before we start to fade the popup.
  2. The function that applies the fade animation to the popup - initiatePopupFade.
  3. The callback function - closeFadedPopup used to reset the style class and correctly hide the popup so that it can be invoked again and again.  
  4. A utility function - findFadeContainer, which is responsible for locating the correct child component of the popup to actually apply the style to.

Function - animateFadingPopup

This function, as stated is the one hooked up to the popupOpened event via a clientListener. Because of when the code is called it does not actually matter how you launch the popup, or if the popup is re-used from multiple places. All usages will get the fade behavior.

/**
 * Client listener which will kick off the animation to fade the dialog and register
 * a callback to correctly reset the popup once the animation is complete
 * @param event
 */
function animateFadingPopup(event) {
  var fadePopup = event.getSource();
  var fadeCandidate = false;
  //Ensure that the popup is initially Opaque
  //This handles the situation where the user has dismissed
  //the popup whilst it was in the process of fading
  var fadeContainer = findFadeContainer(fadePopup);
  if (fadeContainer != null) {
    fadeCandidate = true;
    fadeContainer.setStyleClass("popupFadeReset");
  }
  //Only continue if we can actually fade this popup
  if (fadeCandidate) {
    //See if a delay has been specified
    var waitTimeSeconds = event.getSource().getProperty('preFadeDelay');
    //Default to 5 seconds if not supplied
    if (waitTimeSeconds == undefined) {
      waitTimeSeconds = 5;
    }
    // Now call the fade after the specified time
    var fadeFunction = function () {
      initiatePopupFade(fadePopup);
    };
    var fadeDelayTimer = setTimeout(fadeFunction, (waitTimeSeconds * 1000));
  }
}

The things to note about this function is the initial check that we have to do to ensure that the container is currently visible and reset it's style to ensure that it is.  This is to handle the situation where the popup has begun the fade, and yet the user has still explicitly dismissed the popup before it's complete and in doing so has prevented the callback function (described later) from executing. In this particular situation the initial display of the dialog will be (apparently) missing it's normal animation but at least it becomes visible to the user (and most users will probably not notice this difference in any case).

You'll notice that the style that we apply to reset the  opacity - popupFadeReset, is not applied to the popup component itself but rather the dialog or panelWindow within it. More about that in the description of the next function findFadeContainer().

Finally, assuming that we have a suitable candidate for fading, a JavaScript  timer is started using the specified preFadeDelay wait time (or 5 seconds if that was not supplied). When this timer expires then the main animation styleclass will be applied using the initiatePopupFade() function

Function - findFadeContainer

As a component, the <af:popup> does not support styleClass attribute, so we can't apply the animation style directly.  Instead we have to look for the container within the popup which defines the window object that can have a style attached.  This is achieved by the following code:

/**
 * The thing we actually fade will be the only child
 * of the popup assuming that this is a dialog or window
 * @param popup
 * @return the component, or null if this is not valid for fading
 */
function findFadeContainer(popup) {
  var children = popup.getDescendantComponents();
  var fadeContainer = children[0];
  if (fadeContainer != undefined) {
    var compType = fadeContainer.getComponentType();
    if (compType == "oracle.adf.RichPanelWindow" 
     || compType == "oracle.adf.RichDialog") {
      return fadeContainer;
    }
  }
  return null;
}

 So what we do here is to grab the first child component of the popup and check its type. Here I decided to limit the fade behaviour to only <af:dialog> and <af:panelWindow>. This was deliberate.  If  we apply the fade to say an <af:noteWindow> you would see the text inside the balloon fade, but the balloon itself would hang around until the fade animation was over and then hide.  It would of course be possible to make the code smarter to walk up the DOM tree to find the correct <div> to apply the style to in order to hide the whole balloon, however, that means that this JavaScript would then need to have knowledge of the generated DOM structure, something which may change from release to release, and certainly something to avoid. So, all in all, I think that this is an OK restriction and frankly it's windows and dialogs that I wanted to fade anyway, not balloons and menus. You could of course extend this technique and handle the other types should you really want to.

One thing to note here is the selection of the first (children[0]) child of the popup. It does not matter if there are non-visible children such as clientListener before the <af:dialog> or <af:panelWindow> within the popup, they are not included in this array, so picking the first element in this way seems to be fine, no matter what the underlying ordering is within the JSF source.  If you wanted a super-robust version of the code you might want to iterate through the children array of the popup to check for the right type, again it's up to you. 

Function -  initiatePopupFade

 On to the actual fading. This is actually very simple and at it's heart, just the application of the popupFadeAnimate style to the correct component and then registering a callback to execute once the fade is done.

/**
 * Function which will kick off the animation to fade the dialog and register
 * a callback to correctly reset the popup once the animation is complete
 * @param popup the popup we are animating
 */
function initiatePopupFade(popup) {
  //Only continue if the popup has not already been dismissed 
  if (popup.isPopupVisible()) {
    //The skin styles that define the animation 
    var fadeoutAnimationStyle = "popupFadeAnimate";
    var fadeAnimationResetStyle = "popupFadeReset";

    var fadeContainer = findFadeContainer(popup);
    if (fadeContainer != null) {
      var fadeContainerReal = AdfAgent.AGENT.getElementById(fadeContainer.getClientId());
      //Define the callback this will correctly reset the popup once it's disappeared
      var fadeCallbackFunction = function (event) {
        closeFadedPopup(popup, fadeContainer, fadeAnimationResetStyle);
        event.target.removeEventListener("webkitTransitionEnd", fadeCallbackFunction);
      };
      //Initiate the fade
      fadeContainer.setStyleClass(fadeoutAnimationStyle);
      //Register the callback to execute once fade is done
      fadeContainerReal.addEventListener("webkitTransitionEnd", fadeCallbackFunction, false);
    }
  }
}

I've added some extra checks here though. First of all we only start the whole process if the popup is still visible. It may be that the user has closed the popup before the delay timer has finished so there is no need to start animating in that case. Again we use the findFadeContainer() function to locate the correct component to apply the style to, and additionally we grab the DOM id that represents that container.  This physical ID is required for the registration of the callback function. The closeFadedPopup() call is then registered on the callback so as to correctly close the now transparent (but still there) popup.

Function -  closeFadedPopup

The final function just cleans things up:

/**
 * Callback function to correctly cancel and reset the style in the popup
 * @param popup id of the popup so we can close it properly
 * @param contatiner the window / dialog within the popup to actually style
 * @param resetStyle the syle that sets the opacity back to solid
 */
function closeFadedPopup(popup, container, resetStyle) {
  container.setStyleClass(resetStyle);
  popup.cancel();
} 

First of all we reset the style to make the popup contents opaque again and then we cancel the popup.  This will ensure that any of your user code that is waiting for a popup cancelled event will actually get the event, additionally if you have done this as a modal window / dialog it will ensure that the glasspane is dismissed and you can interact with the UI again. 

What's Next?

There are several ways in which this technique could be used, I've been working on a popup here, but you could apply the same approach to in-line messages. As this code (in the popup case) is generic it will make s pretty nice declarative component and maybe, if I get time, I'll look at constructing a formal Growl component using a combination of this technique, and active data push. Also, I'm sure the above code can be improved a little too.  Specifically things like registering a popup cancelled listener to handle the style reset so that we don't loose the subtle animation that takes place when the popup is opened in that situation where the user has closed the in-fade dialog.

Thursday Apr 05, 2012

The UIManager Pattern

One of the most common mistakes that I see when reviewing ADF application code, is the sin of storing UI component references, most commonly things like table or tree components in Session or PageFlow scope. The reasons why this is bad are simple; firstly, these UI object references are not serializable so would not survive a session migration between servers and secondly there is no guarantee that the framework will re-use the same component tree from request to request, although in practice it generally does do so.

So there danger here is, that at best you end up with an NPE after you session has migrated, and at worse, you end up pinning old generations of the component tree happily eating up your precious memory. So that's clear, we should never. ever, be storing references to components anywhere other than request scope (or maybe backing bean scope). So double check the scope of those binding attributes that map component references into a managed bean in your applications. 

Why is it Such a Common Mistake? 

At this point I want to examine why there is this urge to hold onto these references anyway? After all, JSF will obligingly populate your backing beans with the fresh and correct reference when needed.  

In most cases, it seems that the rational is down to a lack of distinction within the application between what is data and what is presentation. I think perhaps, a cause of this is the logical separation between business data behind the ADF data binding (#{bindings}) façade and the UI components themselves. Developers tend to think, OK this is my data layer behind the bindings object and everything else is just UI.  Of course that's not the case.  The UI layer itself will have state which is intrinsically linked to the UI presentation rather than the business model, but at the same time should not be tighly bound to a specific instance of any single UI component. So here's the problem.  I think developers try and use the UI components as state-holders for this kind of data, rather than using them to represent that state. An example of this might be something like the selection state of a tabset (panelTabbed), you might be interested in knowing what the currently disclosed tab is. The temptation that leads to the component reference sin is to go and ask the tabset what the selection is.  That of course is fine in context - e.g. a handler within the same request scoped bean that's got the binding to the tabset. However, it leads to problems when you subsequently want the same information outside of the immediate scope.  The simple solution seems to be to chuck that component reference into session scope and then you can simply re-check in the same way, leading of course to this mistake.

Turn it on its Head 

So the correct solution to this is to turn the problem on its head. If you are going to be interested in the value or state of some component outside of the immediate request context then it becomes persistent state (persistent in the sense that it extends beyond the lifespan of a single request). So you need to externalize that state outside of the component and have the component reference and manipulate that state as needed rather than owning it. This is what I call the UIManager pattern. 

Defining the Pattern

The  UIManager pattern really is very simple. The premise is that every application should define a session scoped managed bean, appropriately named UIManger, which is specifically responsible for holding this persistent UI component related state.  The actual makeup of the UIManger class varies depending on a needs of the application and the amount of state that needs to be stored. Generally I'll start off with a Map in which individual flags can be created as required, although you could opt for a more formal set of typed member variables with getters and setters, or indeed a mix. This UIManager class is defined as a session scoped managed bean (#{uiManager}) in the faces-config.xml. 

The pattern is to then inject this instance of the class into any other managed bean (usually request scope) that needs it using a managed property.  So typically you'll have something like this:

  <managed-bean>
    <managed-bean-name>uiManager</managed-bean-name>
    <managed-bean-class>oracle.demo.view.state.UIManager</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean> 

When is then injected into any backing bean that needs it: 

   <managed-bean>
    <managed-bean-name>mainPageBB</managed-bean-name>
    <managed-bean-class>oracle.demo.view.MainBacking</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <managed-property>
      <property-name>uiManager</property-name>
      <property-class>oracle.demo.view.state.UIManager</property-class>
      <value>#{uiManager}</value>
    </managed-property>
  </managed-bean>

In this case the backing bean in question needs a member variable to hold and reference the UIManager:

private UIManager _uiManager; 

Which should be exposed via a getter and setter pair with names that match the managed property name (e.g. setUiManager(UIManager _uiManager), getUiManager()). 

This will then give your code within the backing bean full access to the UI state.

UI components in the page can, of course, directly reference the uiManager bean in their properties, for example, going back to the tab-set example you might have something like this:

<af:paneltabbed>
  <af:showDetailItem text="First"
                     disclosed="#{uiManager.settings['MAIN_TABSET_STATE'].['FIRST']}">
    ...
  </af:showDetailItem>
  <af:showDetailItem text="Second"
                     disclosed="#{uiManager.settings['MAIN_TABSET_STATE'].['SECOND']}">
    ...
  </af:showDetailItem>
  ...
</af:panelTabbed>

Where in this case the settings member within the UI Manger is a Map which contains a Map of Booleans for each tab under the MAIN_TABSET_STATE key. (Just an example you could choose to store just an identifier for the selected tab or whatever, how you choose to store the state within UI Manger is up to you.)

Get into the Habit

So we can see that the UIManager pattern is not great strain to implement for an application and can even be retrofitted to an existing application with ease. The point is, however, that you should always take this approach rather than committing the sin of persistent component references which will bite you in the future or shotgun scattered UI flags on the session which are hard to maintain.  If you take the approach of always accessing all UI state via the uiManager, or perhaps a pageScope focused variant of it, you'll find your applications much easier to understand and maintain. Do it today!

More Information

Another interesting article relating to this topic has been written by Steven Davelaar  subsequent to the original publication of this post. This article is well worth checking out more more information on this area and some best practice around component references.

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
« April 2014
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
    
       
Today