Thursday Feb 21, 2013

Get More From Your Messages

Within ADF Faces we take much goodness and added value for granted.  One such feature came to my attention just the other day. Had you noticed that when you have several errors outstanding on the screen the framework gives you a hyperlink to set focus to that field? See the error on commissionPct below and the hyperlink that you could click to move focus to it in the screen:

 So that's neat and I must confess that although I must have stared this one in the face hundreds of times I never really groked how cool that was. 

Anyway, as is usual, this was not just a random act of attention on my part but rather considering a couple of different user cases which both boiled down to the basic question.  Can we extend these error dialogs in some way to show more detail or carry out some other action in response to the error? So that's what I wanted to work through in this article, because the answer is (of course) yes!

How to make the Messages Interactive?

So let's take a look at how to do this. The clue is in the documentation for the tag which casually mentions that  you can include HTML into your Faces messages or message detail. and one of the examples is the embedding of an anchor tag to embed a hyperlink - something that could indeed be useful in a message.  So it occurred to me that this hyperlink provided the opportunity for  an actionable gesture that the user could make to do stuff within the actual application as well as jump off to another one. However, it's not all plain sailing, you can't just embed any old HTML markup into the messages (I did try!) So no embedding of buttons, and even for an <a> tag you can't wire in JavaScript with an onClick=, that all get stripped out.

However, with a carefully shaped href attribute you can call JavaScript. So for example I've crafted the following message detail string:

<html>Detail for errror <a href=javascript:errorDetailCallback('it1','d1');>here</a></html>

 This renders the word "here" as a link in the message and then when the user clicks on that it calls a JavaScript errorDetailCallback() with a couple of arguments.  This function is included in a script linked onto the page using a standard <af:resource> tag.

The script itself could do anything. In my sample I'm actually making it call back to the server (via an <af:serverListener> defined in the document. Here's the JavaScript function:

 function errorDetailCallback(errorContext, documentComponentId){
    
    var callbackComponent = AdfPage.PAGE.findComponentByAbsoluteId(documentComponentId);
    if (callbackComponent != null) {
        var serverEvent = new AdfCustomEvent(callbackComponent, 
                                             "errorDetailEvent",
                                             {detailKey : errorContext},
                                             true);
        serverEvent.queue(true);
    }
    else{
        AdfLogger.LOGGER.severe("errorDetailCallback: Error unable to locate document component: " 
                                + documentComponentId);
    }  
}

The custom event is wired into the server via the <af:serverListener> tag:

<af:serverListener type="errorDetailEvent" 
                   method="#{indexPageBB.errorDrilldownHandler}"/>

 The server side method (errorDrilldownHandler()) in my case gets the custom event and then shows a popup in response:

 public void errorDrilldownHandler(ClientEvent clientEvent) {
   Map params = clientEvent.getParameters();
   String detailKey = (String)params.get("detailKey");
   if (detailKey != null && _errorDetail.containsKey(detailKey)){
     _lastErrorKey = detailKey;
   }
   else{
     _lastErrorKey = "UNKNOWN";
   }
        
   //Now do what you want. In this case show a popup with a detail message
   RichPopup.PopupHints hints = new RichPopup.PopupHints();
   getDetailPopup().show(hints);
 }

Note that in the above case, the _lastErrorKey  is simply a variable which is used by the getter that populates the text in the popup, it's used as a key into a Map with some further message details in it. However, what you do in this callback routine is of course up to you. 

Thursday Dec 20, 2012

Dude, how big's my browser?

It's sometimes funny how the same question comes from several unrelated places at almost the same time. I've had just such a question from a couple of unrelated projects recently, the query being the simple one of "how big is my browser window"?

Now in many cases this is a null question. With the correct use of layout containers (and testing) you can build layouts in ADF which adapt, flow and stretch well as the user re-sizes the browser. However, there are circumstances, as was the case here, where you want more drastic changes based in the amount of space you have to play with.  For example you may choose to hide a column in a table if the screen is below a certain size.

Well this is certainly possible, but of course it comes at a price.  If you want to know when the browser window has been resized you have to tell the server, and that's a round trip. So to do this efficiently this is not totally trivial. However, to save you the trouble of thinking too hard, I've written a sample - it is Christmas after all and Christmas is all about giving. 

The sample can be downloaded from the ADF Samples site on Java.net.

 

It's all pretty well documented  so I won't explain the code line by line here, but needless to say there is a bit of JavaScript and a server side event handler to listen for events queued from that script. I use a configurable JavaScript timer to buffer the resize events and keep the number of notifications to the server to an acceptable level. 

Once you have the  sizing information from the client, of course it's up to you to decide what to do with it!

The sample is written in 11.1.1.6 and will work for 11.1.1.n and 11.1.2.n versions. 

Wednesday Nov 14, 2012

Controlling the Sizing of the af:messages Dialog

Over the last day or so a small change in behaviour between 11.1.2.n releases of ADF and earlier versions has come to my attention. This has concerned the default sizing of the dialog that the framework automatically generates to handle the display of JSF messages being handled by the <af:messages> component. Unlike a normal popup, you don't have a physical <af:dialog> or <af:window> to set the sizing on in your page definition, so you're at the mercy of what the framework provides. In this case the framework now defines a fixed 250x250 pixel content area dialog for these messages, which can look a bit weird if the message is either very short, or very long. Unfortunately this is not something that you can control through the skin, instead you have to be a little more creative.

Here's the solution I've come up with.  Unfortunately, I've not found a supportable way to reset the dialog so as to say  just size yourself based on your contents, it is actually possible to do this by tweaking the correct DOM objects, but I wanted to start with a mostly supportable solution that only uses the best practice of working through the ADF client side APIs.

The Technique

The basic approach I've taken is really very simple.  The af:messages dialog is just a normal richDialog object, it just happens to be one that is pre-defined for you with a particular known name "msgDlg" (which hopefully won't change). Knowing this, you can call the accepted APIs to control the content width and height of that dialog, as our meerkat friends would say, "simples" 1

The JavaScript

For this example I've defined three JavaScript functions.  

  1. The first does all the hard work and is designed to be called from server side Java or from a page load event to set the default.
  2. The second is a utility function used by the first to validate the values you're about to use for height and width.
  3. The final function is one that can be called from the page load event to set an initial default sizing if that's all you need to do.

Function resizeDefaultMessageDialog()

/**
   * Function that actually resets the default message dialog sizing.
   * Note that the width and height supplied define the content area
   * So the actual physical dialog size will be larger to account for
   * the chrome containing the header / footer etc.
   * @param docId Faces component id of the document
   * @param contentWidth - new content width you need 
   * @param contentHeight - new content height
   */
  function resizeDefaultMessageDialog(docId, contentWidth, contentHeight) {
    // Warning this value may change from release to release
    var defMDName = "::msgDlg";
 
    //Find the default messages dialog
    msgDialogComponent = 
      AdfPage.PAGE.findComponentByAbsoluteId(docId + defMDName); 

    // In your version add a check here to ensure we've found the right object!

    // Check the new width is supplied and is a positive number, if so apply it.
    if (dimensionIsValid(contentWidth)){
        msgDialogComponent.setContentWidth(contentWidth);
    }
 
    // Check the new height is supplied and is a positive number, if so apply it.
    if (dimensionIsValid(contentHeight)){
        msgDialogComponent.setContentHeight(contentHeight);
    }
  }

 Function dimensionIsValid()

 /**
 * Simple function to check that sensible numeric values are 
 * being proposed for a dimension
 * @param sampleDimension 
 * @return booolean
 */
function dimensionIsValid(sampleDimension){
    return (!isNaN(sampleDimension) && sampleDimension > 0);
}

Function  initializeDefaultMessageDialogSize()

 /**
 * This function will re-define the default sizing applied by the framework 
 * in 11.1.2.n versions
 * It is designed to be called with the document onLoad event
 */
function initializeDefaultMessageDialogSize(loadEvent){
  //get the configuration information
  var documentId = loadEvent.getSource().getProperty('documentId');
  var newWidth = loadEvent.getSource().getProperty('defaultMessageDialogContentWidth');
  var newHeight = loadEvent.getSource().getProperty('defaultMessageDialogContentHeight');
  resizeDefaultMessageDialog(documentId, newWidth, newHeight);
}

Wiring in the Functions

As usual, the first thing we need to do when using JavaScript with ADF is to define an af:resource  in the document metaContainer facet

<af:document>

   ....  

   <f:facet name="metaContainer">
     <af:resource type="javascript" source="/resources/js/hackMessagedDialog.js"/>
   </f:facet>
</af:document>

This makes the script functions available to call. 

Next if you want to use the option of defining an initial default size for the dialog you use a combination of <af:clientListener> and <af:clientAttribute> tags like this.

<af:document title="MyApp" id="doc1">
   <af:clientListener method="initializeDefaultMessageDialogSize" type="load"/>
   <af:clientAttribute name="documentId" value="doc1"/>
   <af:clientAttribute name="defaultMessageDialogContentWidth" value="400"/>
   <af:clientAttribute name="defaultMessageDialogContentHeight" value="150"/> 
... 

 Just in Time Dialog Sizing 

So  what happens if you have a variety of messages that you might add and in some cases you need a small dialog and an other cases a large one? Well in that case you can re-size these dialogs just before you submit the message. Here's some example Java code:

FacesContext ctx = FacesContext.getCurrentInstance();
        
//reset the default dialog size for this message
ExtendedRenderKitService service = 
             Service.getRenderKitService(ctx, ExtendedRenderKitService.class);
service.addScript(ctx, "resizeDefaultMessageDialog('doc1',100,50);");
        
FacesMessage msg = new FacesMessage("Short message");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
ctx.addMessage(null, msg); 

So there you have it. This technique should, at least, allow you to control the dialog sizing just enough to stop really objectionable whitespace or scrollbars.

1 Don't worry if you don't get the reference, lest's just say my kids watch too many adverts.

Thursday Oct 25, 2012

Centered Content using panelGridLayout

A classic layout conundrum,  which I think pretty much every ADF developer may have faced at some time or other, is that of truly centered (centred) layout. Typically this requirement comes up in relation to say displaying a login type screen or similar.

Superficially the  problem seems easy, but as my buddy Eduardo explained when discussing this subject a couple of years ago it's actually a little more complex than you might have thought. If fact, even the "solution" provided in that posting is not perfect and suffers from a several issues (not Eduardo's fault, just limitations of panelStretch!)

  1. The top, bottom, end and start facets all need something in them
  2. The percentages you apply to the topHeight, startWidth etc. are calculated as part of the whole width.  This means that you have to guestimate the correct percentage based on your typical screen size and the sizing of the centered content. So, at best, you will in fact only get approximate centering, and the more you tune that centering for a particular browser size the more it will fail if the user resizes.
  3. You can't attach styles to the panelStretchLayout facets so to provide things like background color or fixed sizing you need to embed another container that you can apply styles to, typically a panelgroupLayout

 

For reference here's the code to print a simple 100px x 100px red centered square  using the panelStretchLayout solution, approximately tuned to a 1980 x 1080 maximized browser (IDs omitted for brevity):

<af:panelStretchLayout startWidth="45%" endWidth="45%" 
                       topHeight="45%"  bottomHeight="45%" >
  <f:facet name="center">
    <af:panelGroupLayout inlineStyle="height:100px;width:100px;background-color:red;" 
                         layout="vertical"/>
  </f:facet>
  <f:facet name="top">
    <af:spacer height="1" width="1"/>
  </f:facet>
  <f:facet name="bottom">
    <af:spacer height="1" width="1"/>
  </f:facet>
  <f:facet name="start">
    <af:spacer height="1" width="1"/>
  </f:facet>
  <f:facet name="end">
    <af:spacer height="1" width="1"/>
   </f:facet>
</af:panelStretchLayout> 

And so to panelGridLayout 

So here's the  good news, panelGridLayout makes this really easy and it works without the caveats above.  The key point is that percentages used in the grid definition are evaluated after the fixed sizes are taken into account, so rather than having to guestimate what percentage will "more, or less", center the content you can just say "allocate half of what's left" to the flexible content and you're done.

Here's the same example using panelGridLayout:

<af:panelGridLayout>
  <af:gridRow height="50%"/>
  <af:gridRow height="100px">
    <af:gridCell width="50%" />
    <af:gridCell width="100px" halign="stretch" valign="stretch" 
                 inlineStyle="background-color:red;">
      <af:spacer width="1" height="1"/>
    </af:gridCell>
    <af:gridCell width="50%" />
  </af:gridRow>
  <af:gridRow height="50%"/>
</af:panelGridLayout>

 So you can see that the amount of markup is somewhat smaller (as is, I should mention, the generated DOM structure in the browser), mainly because we don't need to introduce artificial components to ensure that facets are actually observed in the final result.  But the key thing here is that the centering is no longer approximate and it will work as expected as the user resizes the browser screen.  By far this is a more satisfactory solution and although it's only a simple example, it will hopefully open your eyes to the potential of panelGridLayout as your number one, go-to layout container.

Just a reminder though, right now, panelGridLayout is only available in 11.1.2.2 and above.

Thursday Sep 27, 2012

PanelGridLayout - A Layout Revolution

With the most recent 11.1.2 patchset (11.1.2.3) there has been a lot of excitement around ADF Essentials (and rightly so), however, in all the fuss I didn't want an even more significant change to get missed - yes you read that correctly, a more significant change! I'm talking about the new panelGridLayout component, I can confidently say that this one of the most revolutionary components that we've introduced in 11g, even though it sounds rather boring. To be totally accurate, panelGrid was introduced in 11.1.2.2 but without any presence in the component palette or other design time support, so it was largely missed unless you read the release notes. However in this latest patchset it's finally front and center. Its time to explore - we (really) need to talk about layout. 

Let's face it,with ADF Faces rich client, layout is a rather arcane pursuit, once you are a layout master, all bow before you, but it's more of an art than a science, and it is often, in fact, way too difficult to achieve what should (apparently) be a pretty simple.

Here's a great example, it's a homework assignment I set for folks I'm teaching this stuff to: 

Sample Layout

The requirements for this layout are:

  1. The header is 80px high, the footer is 30px. These are both fixed. 
  2. The first section of the header containing the logo is 180px wide
  3. The logo is centered within the top left hand corner of the header 
  4. The title text is start aligned in the center zone of the header and will wrap if the browser window is narrowed. It should be aligned in the center of the vertical space 
  5. The about link is anchored to the right hand side of the browser with a 20px gap and again is center aligned vertically. It will move as the browser window is reduced in width.
  6. The footer has a right aligned copyright statement, again middle aligned within a 30px high footer region and with a 20px buffer to the right hand edge. It will move as the browser window is reduced in width.
  7. All remaining space is given to a central zone, which, in this case contains a panelSplitter.
  8. Expect that at some point in time you'll need a separate messages line in the center of the footer. 

In the homework assigment I set I also stipulate that no inlineStyles can be used to control alignment or margins and no use of other taglibs (e.g. JSF HTML or Trinidad HTML). 
So, if we take this purist approach, that basic page layout (in my stock solution) requires 3 panelStretchLayouts, 5 panelGroupLayouts and 4 spacers - not including the spacer I use for the logo and the contents of the central zone splitter - phew! The point is that even a seemingly simple layout needs a bit of thinking about, particulatly when you consider strechting and browser re-size behavior. In fact, this little sample actually teaches you much of what you need to know to become vaguely competant at layouts in the framework. The underlying result of "the way things are" is that most of us reach for panelStretchLayout before even finishing the first sip of coffee as we embark on a new page design. In fact most pages you will see in any moderately complex ADF page will basically be nested panelStretchLayouts and panelGroupLayouts, sometimes many, many levels deep. 
So this is a problem, we've known this for some time and now we have a good solution. (I should point out that the oft-used Trinidad trh tags are not a particularly good solution as you're tie-ing yourself to an HTML table based layout in that case with a host of attendent issues in resize and bi-di behavior, but I digress.)


So, tadaaa, I give to you panelGridLayout. PanelGrid, as the name suggests takes a grid like (dare I say slightly gridbag-like) approach to layout, dividing your layout into rows and colums with margins, sizing, stretch behaviour, colspans and rowspans all rolled in, all without the use of inlineStyle. As such, it provides for a much more powerful and consise way of defining a layout such as the one above that is actually simpler and much more logical to design. The basic building blocks are the panelGridLayout itself, gridRow and gridCell. Your content sits inside the cells inside the rows, all helpfully allowing both streching, valign and halign definitions without the need to nest further panelGroupLayouts. So much simpler! 


If I break down the homework example above my nested comglomorate of 12 containers and spacers can be condensed down into a single panelGrid with 3 rows and 5 cell definitions (39 lines of source reduced to 24 in the case of the sample). What's more, the actual runtime representation in the browser DOM is much, much simpler, and clean, with basically one DIV per cell (Note that just because the panelGridLayout semantics looks like an HTML table does not mean that it's rendered that way!) .


Another hidden benefit is the runtime cost. Because we can use a single layout to achieve much more complex geometries the client side layout code inside the browser is having to work a lot less. This will be a real benefit if your application needs to run on lower powered clients such as netbooks or tablets.


So, it's time, if you're on 11.1.2.2 or above, to smile warmly at your panelStretchLayouts, wrap the blanket around it's knees and wheel it off to the Sunset Retirement Home for a well deserved rest. There's a new kid on the block and it wants to be your friend. 

Update: panelGridLayout is also available in the 11.1.1.7 release as well as the 11.1.2.n series. 

Friday Jul 20, 2012

The ImageMap Pattern

In my last article, I alluded to the fact that the associated sample combined a bunch of existing patterns and techniques, and that I would progressively write those up.  In this article I'm going to talk about the first of these, the ImageMap pattern.

What's the Use Case?

 This pattern is all about solving a pretty common problem, I have, in my service model, some kind of codified value (e.g. a Type or a Status) which I want to reflect visually in my UI using an image. Now if you only have a couple of options to represent then you can use a simple ternary expression in the page, switching based on the value you're getting from the service.  However, once you get over three or four options the EL starts to get hard to read and maintain, and face it, sometimes you might not even notice that it's evaluating to the wrong thing if the expression is wrong.  So what are the options for approaching this? 

  1. Do the translation in the service layer and provide an attribute containing the correct image to display. 
  2. Manage some simple lookup into a shared UI level resource

Option (1) just feels wrong because you'd be letting client side information, the names of image files in this case,  leak into the wrong layer, so the shared resource approach really looks like the way to go. 

Implementation

The approach that we take in this pattern is to exploit the ability of Expression Language to be able to refer to both arrays and maps using the "[<value>]" syntax. For example the expression mybean.image[1] can actually mean several things depending on what "image" is in this case. If the getImage() method in the underlying mybean returns a List then this would translate to pull out index 1 from that list. If on the other hand getImage() returns a Map then the get() method will be called on the map with a key of (in this case) "1".


We can exploit this behavior, and particularly the understanding of the Map expressions to define a mapping between a piece of data from the model, such as a status code, and a particular image to use to represent that. To illustrate this let's take a simple example where we have some possible string status values in the datamodel and want to map that into different images, thus:

Code from the Service Image To Use
 TABLE  /images/table.png
 VIEW  /images/view.png
FUNCTION  /images/plsql_func.png
 ...  ..

Notice that the names of the images are not quite the same as the codification from the service layer so we can't get away with the simplier solution of:

<af:image source="/images/#{bindings.ObjectType}.png" .../> 

So instead, we have to work via an abstraction using a lookup map for the images 

Step 1: Define your image map

We can define the lookup map to work from either as an explicit managed bean, or even more easily as a stand-alone bean definition in the adfc-config.xml file. Conventionally we will store this bean on the application scope so that all users on the system share the same copy:

<managed-bean>
   <managed-bean-name>typeImages</managed-bean-name>
   <managed-bean-class>java.util.HashMap</managed-bean-class>
   <managed-bean-scope>application</managed-bean-scope>
   <map-entries>
     <map-entry>
       <key>TABLE</key>
       <value>/images/table.png</value>
     </map-entry>
     <map-entry>
       <key>VIEW</key>
       <value>/images/view.png</value>
     </map-entry>
     <map-entry>
       <key>FUNCTION</key>
       <value>/images/pls_func.png</value>
     </map-entry>
     ... 
   </map-entries>
</managed-bean>  

Alternatively if the thing we wanted to key off was a simple numerical list then we could have a definition that used a simple array rather than a map; like this:

<managed-bean>
  <managed-bean-name>lifecycleImages</managed-bean-name>
  <managed-bean-class>java.util.ArrayList</managed-bean-class>
  <managed-bean-scope>application</managed-bean-scope>
  <list-entries>
    <value-class>java.lang.String</value-class>
    <value>/images/new.png</value>
    <value>/images/updated.png</value>
    <value>/images/sclosed.png</value>
  </list-entries>
</managed-bean>  

In this latter case index lifecycleImages[0] would map to /images/new.png, lifecycleImages[1] to /images/updated.png etc.

Step 2: using this bean in EL

Now that the list or map has been defined we can use it thus:

<af:image source="#{typeImages[bindings.ObjectType]}"

Where the bindings.ObjectType attribute binding can be expected to turn one of the valid keys in the map (TABLE,VIEW etc.)

 You can see an example of this version of the pattern inside the tile iterator in the home.jspx page in the sample: DRM004 - AutoReduce and Pagination screen

Variations on the Theme

 I'm always thinking about how to optimize performance, and one useful approach when it comes to images is to use the technique of using image sprites. This is where, instead of having lots of discrete images, you have a single image file, a little like a film-strip, containing all of the images. In this case, CSS is used to assign the filmstrip image as a background to a page element and then the CSS background-position is set to select a particular icon on the strip. This has the advantage of only needing a single round trip to grab all the images at once, having a positive effect on your page load time. 

Using this idea we can take the Image Map Pattern approach, but rather than having the map entry value be the name of a discrete image, it can point to the name of the CSS style. This style can be applied in the styleClass attribute of an element to pull the required image from the film-strip. To save you having to create a bunch of extra styles to encode the positions, you could also just hold an offset in the film-strip as the lookup value. 

This latter approach can also be seen in the  DRM004 - AutoReduce and Pagination screen sample, have a look at the managed bean definition for typeImageOffsets. This information is then used to define the images in the list view of the home.jspx page, thus:

<af:spacer styleClass="iconStripBase" inlineStyle="background-position:#{typeImageOffsets[row.ObjectType]}px;"/> 

 Have a look at the sample to see it all in action. 

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

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

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.

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