Friday Jan 09, 2015

Pattern For Obtaining ADF Component ID for JavaScript

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

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

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

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

Step 1

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

Step 2

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

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

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

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

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

Step 3

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

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

Step 4

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

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

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

Step 5

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

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

Additional Thoughts

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

Tuesday Nov 25, 2014

Locating JSF components by ID

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

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

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

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

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

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

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

Tuesday Feb 25, 2014

Stretching an inputText inside of a Table Column

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

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

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

Thursday Dec 05, 2013

Current Date / Time on your JSF pages

OK, it's a trivial problem but I'll answer to anyway. Is there a simple way to get the date and time in expression language inside your pages, without writing Java code?

The answer is of course yes, it's simple to do in a declarative manner.  You will need to create a basic managed bean definition in your JSF configuration.  So for example I might add the following definition for a bean called "now" to my adfc-config.xml file:

  <managed-bean>
    <managed-bean-name>now</managed-bean-name>
    <managed-bean-class>java.util.Date</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
  </managed-bean> 

That's enough to ensure that a Date object called "now" is available on the request map and is re-created (i.e. refreshed) on every roundtrip.  This can then be referenced inside of your page:

  <af:outputText value="#{now}">
    <af:convertDateTime type="both" timeZone="CET"/>
  </af:outputText> 

Notice how I've specified the timezone I want to see here as the result of the new Date() creation will be the date in UTC / GMT.

There you are, short and sweet. 

Wednesday Oct 09, 2013

Quick Reference for Popup Alignment

I don't know about you, but I always forget what what the various popup hints used by <af:showPopupBehavior> actually relate to in terms of look.  So to save myself time more than anything I've put together a simple visual reference to show how each hint positions the popup.

Tuesday Jun 11, 2013

Prioritizing your messages

I've just uploaded a small sample onto the ADF EMG Samples site which demonstrates how you can make the <af:messages> component behave differently based on the messages that are in the queue.  The use case here was within a system where we wanted to use in-line messages for those run-of-th-mill type messages that should not really interrupt the flow of the user's activity (informational messages such as "Record Saved") , however, if something goes wrong then we wanted a popup to get in the users face, so to speak. 

The mode of display within the messages tag is controlled by the inline property, so in the sample application, rather than hardcoding this instead I evaluate an EL expression that returns a boolean.  Here's the code that implements the method I call:

public boolean isInlineMessage(){
  boolean lowPriorityMessage = true;
  Iterator<FacesMessage> iter = FacesContext.getCurrentInstance().getMessages();
  while (iter.hasNext()){
    FacesMessage msg = iter.next();
    if (msg.getSeverity() == FacesMessage.SEVERITY_ERROR || 
        msg.getSeverity() == FacesMessage.SEVERITY_FATAL){
      lowPriorityMessage = false;
      break;
    }
  }
  return lowPriorityMessage;
}

In this code I simple iterate through the message stack and check to see if there are any messages at the ERROR or FATAL level. If that is the case I return false rather than true for the method result.

The only twist here is to ensure that your partialTriggers are set up correctly so that the <af:messages> tag is re-evaluated.

Anyway, the demo code is available here.  Enjoy.

Note: The demo will only run in 11.1.1.7  and 11.1.2..3 + but the message checking code part will run in earlier versions as well.  It's just that the demo screen uses the new panelGridLayout. 

Wednesday May 15, 2013

Conveyor Visualization for Tabs in ADF

If you look carefully at the online demos for panelTabbed in 11.1.1.7 you may notice that the tabs are sporting a slightly changed UI for situations where the number of tabs can't fit into the available horizontal space. 

Here's the situation with the default representation of a tab-set when it overflows. The chevron  on the right of the tab-set can be clicked to display a drop-down list of the remaining tabs. 

Basic tab visualization

In 11.1.1.7 we now have an alternative visualization where the hidden tabs are available on a conveyor belt and the user can scroll along the belt rather than having to use the pull-down list.

Conveyor Tabs

Enabling this feature

This visualization is not controlled by any property on the component but rather is switched on through the skin being used in the application. As such, all tab-sets within the application will gain the visualization if it is switched on. 

To enable it you will need to create a custom skin, if you are not familiar with  process then there are plenty of articles and documentation out there to help you, or you can download the demo that I've created to go along with this article. Basically I've created a custom skin that extends the new Skyros skin that 11.1.1.7 uses by default, and plugged that in to replace Skyros.

Within the skin file there is a single entry to make for panelTabbed (and a similar one for navigationPane):

 af|panelTabbed {
    -tr-layout-type:conveyor; 
}

And that's it, as simple as that.  As I mentioned you can download a simple sample (DRM009) which shows this in action from the ADF EMG Demos Site

Friday May 03, 2013

Slide, Charlie Brown! Slide!

Following on from an earlier article(Using inputNumberSlider with Dates) where I showed you how you could use an <af:inputNumberSlider> to display formatted date strings rather than numbers, I've been expanding into other uses for the slider components. 

Specifically the most recent use case  was to represent text labels for the number "stops" on the slider so that it could be used as a way of filtering datasets. So this is what it looks like:

So, no problem you say, the previous article shows pretty much how to do that - why a new one?   Well the twist here is in the form of tables.

What if I want to use the slider in a table, and I want to have different slider labels for different rows, like this:

Well that's a bit more of a problem.  Why you ask?  It's because of the way that tables work.  When you define a table, each row is stamped out from the same set of components.  So If I have one converter that say converts numbers to  Domain, Kingdom, Phylum etc. associated with the slider then that's the converter that I'm going to use for every row as it's stamped out. I can't use a different converter per row. (Well OK I could use EL to specify a different converter value for each but then I'd need to define as many converters as I have value lists and plus I would have to know in advance what all the possible lists of filter values are.  In this case, the reality is that the values shown in the sliders come from user configuration rather than being baked into the application anyway, so a hardcoded solution is out) . 

So my aim here was to build a converter that could be used generically with as many different lists as required. You can download the resulting code from here.

If you take a look at the demo you will see that the converter is defined by name in the faces-config.xml  and then associated with the inputSlider

<af:inputNumberSlider value="..." minimum="0" maximum="3" minorIncrement="1" 
                      converter="ismsConverter">
    <f:attribute name="imsConverterMeasureElements" value="John,Paul,Ringo,George"/>
</af:inputNumberSlider> 

The actual list of elements which will be displayed as labels for each slider stop are then passed as a comma delimited string using the <f:attribute> tag.

This version of the converter always assumes a zero indexed list and the labels are assigned in order from zero. So selecting George in the slider above will translate into a value of 3. It would be perfectly possible to adapt the code to allow specific mapping between a number and the label, e.g. if you wanted a 1 based list you might pass a definition string like this:

"1:John,2:Paul,3:Ringo,4:George"

You'd just need to do a little more parsing in the converter itself to make this work though.

Even with no change you can use the converter out of the box with <af:inputRangeSlider>. Here's a version with Roman numerals:

 Finally on the matter of datatypes there is a slight twist when using a model attribute of type oracle.job.domain.Number.  The ismsConverter will handle the basic Java types, float, int, long double etc. and then there us a second converter called ismsJboConverter that handles the JBO Number type.  All of this is shown in the demo.

Tuesday Apr 30, 2013

New Table Sorting Capability in Patchset 6

With every new release you can trawl through the release notes and fine some handy new features to play with, however, sometimes there is even more to discover hidden away.

One such feature, which I think is actually pretty exciting, has crept into the 11.1.1.7.0 release of JDeveloper / ADF.  This is a new feature which adds case insensitive sorting to the table.  Previously we have had to resort to tricks such as defining transient attributes where all attributes are converted to a particular case in order to create this transient attribute. It would then be used as the sortProperty on the <af:column> that houses the column that you wanted to sort in a case insensitive way.  Not too difficult to do, but it means adding stuff to your model to satisfy a UI need. Now there is a cleaner way.

So to replace this technique, we now have a new property on <af:column> called sortStrength. This new property takes one of four values which allow you some degree of control as to how the sorting takes place. The resulting sorts are locale specific.  This is probably best illustrated with a table:

Identical (default)  This is the default value and reflects the traditional behavior where the sort is very sensitive. i.e AA != aa and aa != áá
Primary  Only the primary difference is observed i.e. the underlying letter. So a != b of course but AA == aa == áá == Äæ  for the purposes of sort. You can think of this as the most promiscuous sort   
Secondary Secondary is still case insensitive. A secondary difference is something like an accented version of a character. So in this case AA == aa, however, aa != áá.  
Tertiary

 Returns us to the world of case sensitivity, so AA != Aa != áá != ÁÁ.  There are some subtle locale specific differences between Identical and Tertiary but for most purposes they will have the same effect

 So you can see that for day-to-day case insensitive table sorting you should use the Primary or Secondary values depending on how you want to treat accented characters.  If you are wondering where the weird Primary, Secondary, Tertiary thing came from, head over to the JavaDoc for the java.text.Collator class which is the mechanism underlying all of this. 

 

 

Monday Apr 15, 2013

Building the revealPanel - Part 2

In the first part of this series I described the basic physical makeup of the revealPanel and described a simple server side approach to the display and hide of the information panel and indicator triangle.  

In this posting I want to look at an alternative approach that uses the same physical components in the UI, but rather than handling the reveal with a roundtrip to the server, does it all in the browser itself using JavaScript.

The sample that you can download shows both approaches. 

Changing the Configuration for JavaScript Use

The JavaScript version of the component works in essentially the same way as the server side version that I talked about last time, that is, it simply sets the visible attribute of the panelGroupLayouts that contain the reveal information and the triangle. The good thing is at this is a property that we can set through the ADF Faces client component JavaScript API and we never really have to tell the server side what the state of the panels is as we'll always revert to the all-closed layout if anything like navigation away and back takes place. 

So the definition of say the triangle PGL simply has the visible attribute set to a hardcoded value of false, with no need to refer to a managed bean. Of course we need to hold the information about which panels are expanded somewhere,  so that we can close one set as another is opened, or close a set if it is currently open. To do this I actually create a new JavaScript object at the page level called revealPanelGroupMap (if you want to read along open the revealPanel.js file in the demo). 

This Object does not directly hold the information about the panel, it's a slightly more complex structure:

  1. revealPanelGroupMap is a Map (thinking in Java terms) of revealGroup Objects 
  2. Each RevealGroup contains an array of Topics and the currently selected Topic in the array
  3. Each Topic contains component references to the triangle PGL and the revealPanel PGL as well as the topic number
The fact that revealPanelGroupMap can hold multiple revealGroups allows us to host multiple revealPanels on the same page as long as each is identified by a unique string which is used to key that revealPanelGroupMap.

How are the Data Structures Created? 

So this revealPanelGroupMap does not appear out of thin air, but neither in fact is it created in advance in any way. Because, for an individual page, you may have dynamic regions and suchlike, I wanted the revealPanels to be self registering and created on demand rather than pre-defined in some way.  The way that I ended up achieving this was to add a client attribute to each of the three important panelGroupLayouts (the topic, the triangle and the revealPanel). This locator is just a string consisting of 3 parts delimited by ":".

  1. The panelGroupId - e.g. "REVEAL1"
  2. The TopicID - zero indexed
  3. The SegmentID - each topic has three segments 0-2 where 0 is the topic, 1 is the triangle and 2 is the revealPanel

So the client attribute definition in the page looks like this:

<af:clientAttribute name="revealPanelLocator" value="REVEAL1:0:0"/>

This indicates that this panel is the topic panel for the first topic in the REVEAL1 group. 

When the first of the topics is clicked within a revealPanel, the click handler code grabs this locator and parses out the segments.  From the first segment it can tell if this group has been seen before, if it has then great, it can start to set visibility etc. If, however, the group is not present in the main map then we do a little self discovery to find out how many Topics there are in the revealPanel group as a whole and store the relevant component references.

Walking the Tree

So how to we self discover the group?  Well the first bit is easy.  Using the getParent() API  from the component that raised the click event we can get a reference to the hosting panelGridLayout.  

I then use the visitChildren() api on the grid to visit each of its top level children. Each of these will actually be one of those segment panelGroupLayouts. So in a panelGrid with 9 children that will actually define 3 topics each consisting of three panels (topic, triangle, reveal) .  As each of those panels has an associated clientAttribute with it's locator we can add it to the correct place in the revealGroup control structure. 

The visitChildren API actually defines a callback function that the framework will call for you, passing any context that you've defined and the child component it has found. 

Note, it would be perfectly possible to alter the sample code to do away with the clientAttribute all together. I actually wrote it this way to allow for more than one revealPanel to be hosted within the same physical panelGrid. However, of that's not something you need then the page definition could be condensed down to simply the clientListener in the Topic panel.  Left as an exercise for the reader... 

Acting on the Click

Once the data structure is build for a particular revealPanel it will be caching the component references to the triangle and to the reveal PGL so you can simply call setVisible(true|false) to manage the hide and display.

You've Noticed the Fatal Flaw?

Well I hope so... If I'm storing the component references to the various panelGroupLayouts will they stay valid? Well certainly if I navigate off to another page no.  But that's OK, because if I navigate away then the document object in the DOM will have been destroyed, taking my data structures with it. So when I re-enter the page they just get re-built.  So no problems there.   However, if my page contains a region and one of the fragments on the region has a revealPanel then there is a problem.  Changing the view in the region will not effect the overall document so the control structures contain references to components that have been destroyed - if I go away and come back to a revealGroup on a fragment then the references will be bad.  However,  there is a simple solution to this.  The client side components have an isDead() API call which we can use to check to see if the reference held in the cache is still valid.  If it is then great. If not we assume that we need to rebuild the entire structure for that revealPanel.

Adding Some Bling with Animations

I promised some animation as part of this version and indeed it's in there.  So in fact, rather than simply setting the visible attribute when the topic is clicked I do that, but I also apply some CSS styles as well.  

I'm essentially re-using some of the things I've discussed before in my postings on animations, using in this case Scale transforms for the main reveal panel and a color transform for the triangle.

Here's the logical flow:

Revealing a Topic

  1. Set the triangle to visible - it's initial style will define it as transparent
  2. Set the reveal panel as visible  - its initial style will define a scale of (1,0) with full width but no height
  3. Set the reveal panel style to revealPanelAnimated. This resets the scale to (1,1) - full size over about 200ms
  4. Register a listener on the animation
  5. When the animation is finished then reset the style on the triangle to restore its color.

Note that I've used a scale transform here rather than actually trying to move the PGL <div> around the screen. Trying the latter could put you into conflict with the existing layout management and will certainly cause some problems with z-order in the DOM so it's better to avoid the issue by scaling in place.  It gives a pleasing effect.  

The actual code and styles involved in all of this is a little too complex to reproduce here in the blog, so do be sure to check out the demo.  I've added plenty of comments to help you along.  

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
« September 2015
MonTueWedThuFriSatSun
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today