Monday Jul 22, 2013

Setting up a standalone WebLogic 12c install for ADF Without a Database

One change that many folks have encountered with the 12c (12.1.2) release of WebLogic and ADF concerns the setup of stand-alone WebLogic instances extended with JRF for ADF Application deployment. 

The main problem comes when creating the JRF extended domain.  On page 6 of the config process screen you are confronted by the following:

Configure Database Screen

Note here that you need to provide information for the database that OPSS should use. 

Now this connection has a real purpose, the Oracle Platform security infrastructure for 12c needs to store it's information within a proper database so as to correctly (and supportably) manage itself across multiple servers in a cluster.  However, if like me, you just wanted to stand up a single unclustered ADF enabled WebLogic for testing deployments etc. This could be a bit of a pain, particularly in my case as the database version I'm currently running is not of the correct version.  So here I'm documenting the steps for avoiding this requirement on a single standalone server, although I stress, as far as I am aware, this is not a supported configuration for production usage.

The Steps to Install Without Needing a Database 

  1. First of all install WebLogic (wls_121200.jar) and the ADF runtime (fmw_infra_121200.jar) into the same 12c Oracle Home using their respective installers. At the end of each of the WebLogic install make sure you uncheck the Configuration Wizard launch option as we'll be doing that manually later. 
  2. Start a shell / command prompt in $MW_HOME/wlserver/common/bin
  3. Set the environment variable QS_TEMPLATES to point to $MW_HOME/wlserver/common/templates/wls/wls_jrf.jar
  4. Run and define the name of the domain that you want to create (e.g. add_domain) along with the usual WebLogic passwords and ports that you require. Finish the Quick Start wizard without starting the new domain. 
  5. Now run the conventional and on the first page choose "Update an Existing Domain" rather than "Create a new domain". The new domain (e.g. adf_domain) should now be listed for selection.
  6. On the second screen choose the templates that you wish to apply, e.g. Oracle JRF, Oracle Enterprise Manager etc.  and move on through the wizard. 
  7. On the Database Configuration Type screen this time you will see an extra option where Embedded Database (JavaDB) is offered and pre-selected. Select that and continue with the domain setup as usual with whatever managed servers you need.

Again to re-iterate this is only a setup that I would recommend for a development / testing environment and of course you should not take this approach if you're setting up any kind of clustered environment, even for testing, as the shared database has a real purpose in that case. Also consider that if your application uses MDS then you'll most likely need an database repository anyway, so again, in that instance don't use this technique.  

The fact that the normal install for the ADF Web Runtime does not offer this non-database option should be taken a a strong hint as to how supported you will be running with this configuration in a production environment.  Don't ask me for certification or support guidance, please contact Oracle Support for that information.  

Further Reading

The use of an external RDBMS Security Store for WebLogic (the configuration that  this article bypasses) is discussed in the WebLogic Documentation:

Read that chapter for a better understanding of why the default JRF install asks you to define a database connection for this purpose and why I talk about only using the technique that I've outlined here for development purposes. 

Wednesday Jun 26, 2013

Adaptive Connections For ADFBC

Some time ago I wrote an article on Adaptive Bindings showing how the pageDef for a an ADF UI does not have to be wedded to a fixed data control or collection / View Object. This article has proved pretty popular, so as a follow up I wanted to cover another "Adaptive" feature of your ADF applications, the ability to make multiple different connections from an Application Module, at runtime.
Now, I'm sure you'll be aware that if you define your application to use a data-source rather than a hard-coded JDBC connection string, then you have the ability to change the target of that data-source after deployment to point to a different database. So that's great, but the reality of that is that this single connection is effectively fixed within the application right?  Well no, this it turns out is a common misconception.

To be clear, yes a single instance of an ADF Application Module is associated with a single connection but there is nothing to stop you from creating multiple instances of the same Application Module within the application, all pointing at different connections.  If fact this has been possible for a long time using a custom extension point with code that which extends oracle.jbo.http.HttpSessionCookieFactory. This approach, however, involves writing code and no-one likes to write any more code than they need to, so, is there an easier way? Yes indeed.  It is in fact  a little publicized feature that's available in all versions of 11g, the ELEnvInfoProvider.

What Does it Do? 

The ELEnvInfoProvider  is  a pre-existing class (the full path is  oracle.jbo.client.ELEnvInfoProvider) which you can plug into your ApplicationModule configuration using the jbo.envinfoprovider property. Visuallty you can set this in the editor, or you can also set it directly in the bc4j.xcfg (see below for an example) .
Configuration Editor
Once you have plugged in this envinfoprovider, here's the fun bit, rather than defining the hard-coded name of a datasource instead you can plug in a EL expression for the connection to use.  So what's the benefit of that? Well it allows you to defer the selection of a connection until the point in time that you instantiate the AM.
To define the expression itself you'll need to do a couple of things:
  1. First of all you'll need a managed bean of some sort – e.g. a sessionScoped bean defined in your ViewController project. This will need a getter method that returns the name of the connection. Now this connection itself needs to be defined in your Application Server, and can be managed through Enterprise Manager, WLST or through MBeans. (You may need to read the documentation [] here on how to configure connections at runtime if you're not familiar with this) 
  2.  The EL expression (e.g. ${connectionManager.connection} is then defined in the configuration by editing the bc4j.xcfg file (there is a hyperlink directly to this file on the configuration editing screen in the Application Module editor). You simply replace the hardcoded JDBCName value with the expression.  So your cfg file would end up looking something like this (notice the reference to the ELEnvInfoProvider that I talked about earlier)
<BC4JConfig version="11.1" xmlns="">
  <AppModuleConfigBag ApplicationName="oracle.demo.model.TargetAppModule">
    <AppModuleConfig DeployPlatform="LOCAL" 
      <AM-Pooling jbo.doconnectionpooling="true"/>
      <Database jbo.locking.mode="optimistic">
      <Security AppModuleJndiName="oracle.demo.model.TargetAppModule"/>
      <Custom jbo.envinfoprovider="oracle.jbo.client.ELEnvInfoProvider"/>

Still Don't Quite Get It?

So far you might be thinking, well that's fine but what difference does it make if the connection is resolved "just in time" rather than up front and changed as required through Enterprise Manager?
Well a trivial example would be where you have a single application deployed to your application server, but for different users you want to connect to different databases. Because, the evaluation of the connection is deferred until you first reference the AM you have a decision point that can take the user identity into account.

However, think about it for a second.  Under what circumstances does a new AM get instantiated? Well at the first reference of the AM within the application yes, but also whenever a Task Flow is entered -  if the data control scope for the Task Flow is ISOLATED.  So the reality is, that on a single screen you can embed multiple Task Flows, all of which are pointing at different database connections concurrently.

Hopefully you'll find this feature useful, let me know... 

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 =;
    if (msg.getSeverity() == FacesMessage.SEVERITY_ERROR || 
        msg.getSeverity() == FacesMessage.SEVERITY_FATAL){
      lowPriorityMessage = false;
  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  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 22, 2013

UKOUG ADF Mobile Demo

Yesterday I participated in a Special Interest Group meeting organised by the UK Oracle User Group on ADF Mobile. 

As part of my session I concentrated on building an ADF Mobile application from the ground up based on grabbing JSON data from Hudson. This demo shows the core techniques for dealing with URL based data and parsing the same. Several folks at the event had asked for the demo code so I've packaged and uploaded onto the ADF EMG Code Samples Repository:


Friday May 17, 2013

Launching JConsole as an External Tool From JDeveloper

I find JConsole to be a useful tool, particularly for MBean browsing, but frankly getting the correct command line arguments to get it to connect correctly to my WebLogic instances is a bit of a pain. I always have to go and look it up. - generally from here. However, setting all those paths is tedious. Given that I'm usually working in an environment where my JDeveloper, and the WebLogic servers I'm using JConsole on, are matched from a version perspective I can get JDeveloper's external tools capability to do the lifting.

Very simple this. Just choose Tools > External Tools from the menu and press New.  Then run through the wizard:

  1. Type is External Program
  2. Program Options > Program Executable = jconsole 
  3. Program Options > Arguments =  (All on one line of course, reformatted here for readability)

And that's it.  JDeveloper helpfully substitutes both the Java location and the WebLogic location for us saving all that hunting around.  


 Thanks to Torsten Kleiber who discovered and followed up on the twist of ${java.path} not expanding correctly (or rather expanding to null). Before running the tool command with the macro make sure that you have a project open and selected as the actual java path is obtained in the context of the JRE version used by the selected project. (Project Properties > Libraries and Classpath > Java SE Version)

Wednesday May 15, 2013

Conveyor Visualization for Tabs in ADF

If you look carefully at the online demos for panelTabbed in 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 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 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 {

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" 
    <f:attribute name="imsConverterMeasureElements" value="John,Paul,Ringo,George"/>

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:


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 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 != áá.  

 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. 



Friday Apr 19, 2013

Lions and Tigers and RangeSize, Oh My!

One of the most common mistakes I see made in ADF Business Components based applications is a failure to tune the View Objects, and specifically to tune the in Batches of parameter in the VO tuning section. This setting defaults to 1, which does not always meet the needs of the consuming UI or service interface and should generally be changed.  This is a topic that I and others have covered before.

Now certainly the batch fetch size is a crucial tuning parameter as it controls how many times the framework has to hit the database to get the rows needed to service a particular function.  However, in this posting I wanted to turn my attention to another factor which can also have a significant effect - the iterator RangeSize.

The background to this was some recent detective work on an application where the time taken to display one particular screen was suspiciously long. 
The page in question had a tabular display of data, but an inspection of the VO tuning parameters showed that a reasonable Batch size of 51 was being used. What's more the As Needed switch rather than the All at Once option in the VO tuning was being used. So the developer had done totally the right things there.
Running a SQL Trace on the page revealed an interesting thing though.  Because the batch size was pretty high we'd expect that the framework would have to only do one or at most two fetches from the cursor to satisfy the needs of that table.  However The TKProf output showed that in fact over 150 fetches took place retrieving over 8000 rows!

My thought processes in diagnosing this were to look in the following places:
  1. Are there alternative VO Instances defined on the AM where the tuning parameters are different (e.g. ALL_ROWS was specified)? We know the base definition is OK but it could be overridden.  
  2. Any programmatic calls to change the Batch Size or fetch node in the VO?
  3. Any programatic calls to last() on the rowset or iterations through the rowset? 
  4. Check for a RangeSize of -1 on the iterator definition in the pageDef files.

All of these drew a blank.  The last one in particular felt like the problem but a search for the value of -1 in the pageDefs of the UI project only turned up legitimate usages of the -1 value. 

Hold on I don't Understand This RangeSize?

Maybe I should take a quick step back and explain the iterator RangeSize.  So, as we've seen, the tuning options in the view Object will control how often the VO has to go back to the database to get a specific number of rows. The iterator rangeSize is defined in the pageDef file for a particular page, fragment or method activity and it defines how many rows the UI layer should ask the service layer (in this case the VO) for. 
Here's a typical definition that you'll see in the pageDef:
<iterator Binds="EmployeesView1" 

You'll see that the rangeSize here is set to 25 which just happens to be the value that is defaulted in when you drag and drop a binding into the page.  However, it turns out that 25 is not the default value, something which has a bearing later in this investigation as we'll see.
So in this default case when the iterator is asked for data, it in turn will ask the VO for 25 rows, and if the VO does not already have that many rows in the cache it will have to go back to the database as many times, as determined by the batch-size, as it needs to get enough data.

Back to the Problem page 

As it happens the pageDef for the table displaying the problem VO was indeed the defacto default of 25, so, sad to say, it was not the obvious suspect at fault, more investigation was needed.

At this stage the investigation splits into a couple of parallel efforts, manual code inspection, and tracing using the ADF Logging capability to try and work out what interactions were happening with the problem VO.

Welcome to the Internet, Please Tread with Care

What can we trust? Well in the world of ADF Blogs in the wild there are some great bloggers, but that does not mean that you can just copy without thinking.  It turned out that one of the problems with this application was to fall foul of copy-without-thinking syndrome.
The code in question seems innocent enough, it's published out there on the internet as a way of refreshing an iterator:

//Please, Please Don't do this! (My comment)
DCIteratorBinding iterBind= (DCIteratorBinding)dc.get("<your iterator name>");  

Two facts to discuss in relation to this code snippet:
  1. Read the JavaDoc for DCIteratorBinding - the refresh() method is very clearly marked as Internal only, applications should not use. That's what we call "a hint" in the trade, you can choose to ignore it but don't come crying...
  2. Look at that parameter being passed to the refresh method DCIteratorBinding.RANGESIZE_UNLIMITED - can you guess what that does? Yes it does the same as setting the RangeSize to -1 and will cause the VO to be asked for all of the data for that query. You can see how bad that could be if the VO has the potential to return a lot of rows.
So something to put right already. 

But Wait, There's more!

Although the call to refresh was a great catch and the application will be better without it, it turned out not to be the cause - darn. 
However, the parellel effort to run some more tracing found the smoking gun.  The ADF Log trace showed a double execute on the iterator for the VO in question, or to be more precise, executes on two different iterators bound to the same VO from different regions on the page. 
A useful diagnostic here was then to set a breakpoint in the setRangeSize() of oracle.adf.model.binding.DCIteratorBinding.  Doing this we could see that the first iterator execution was actually responsible for setting the RangeSize to -1 and the second to the value we where expecting for that table based on the pageDef. 
All credit to the development team I was working with here who ferreted out the actual problem, it was finally down to one of omission. 

Recall I made the statement earlier about 25 being the defacto default for the RangeSize? Very true, when you create a binding, that's what the IDE puts in for you.  But what's the actual default? Well that turns out to be -1.  So if you omit the RangeSize from the iterator definition by intent, or mistake, you're going to have a potential side effect you may not have expected!  That was exactly the problem in this case  - no RangeSize. 

Specifically the problem was caused by a method binding in one of the bounded task flows in the page. The pageDef for this method activity included and iterator defined for the problem VO but without a RangeSize defined.  

Lessons to Learn

  1. For each peice of UI that is bound to data understand how the user is going to use that screen - will they view just one record, will they scroll through several or a lot, will they only scroll downwards and process one record at a time and so on. You can't effectivly tune a page until you understand how it will be used.
  2. Always tune your VO definitions or instances to the requirements of each endpoint.  If this means having multiple VO definitions or multiple VO instances with different tuning parameters then do it.
  3. If you have consciously set your Batch size in the VO tuning parameters to a small number because that's all you see on the page at once then also tune the pageDef iterator RangeSize. Otherwise that defacto 25 RangeSize could generate a bunch of unnecessary calls back to the database. Think about it, if you have a single record form and you've not tuned the VO Batch size and you've not tuned the iterator RangeSize, you could be doing 25 roundtrips to the database just to see one record.  Great way to please your DBA!
  4. Use a RangeSize of -1 with caution.  It has its place for iterators that serve short lists used for lookups and menus but anything else is an exception.
  5. Don't blindly copy everything you see in the blog-o-sphere. If you don't recognize an API call then look it up.  If something says it's for internal use only, then guess what, don't use it.
  6. Never ever, define an iterator in your pageDef without an explicit rangeSize. If you need to see all rows, say so with the -1 value, otherwise use a positive integer.

We all go home older and wiser...

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.  


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!


« October 2015