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. 

 

 

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" 
          RangeSize="25" 
          DataControl="HRServiceAMDataControl" 
          id="EmployeesView1Iterator"
          ChangeEventPolicy="ppr"/>

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>");  
iterBind.refresh(DCIteratorBinding.RANGESIZE_UNLIMITED);  

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.  

Friday Apr 12, 2013

Building the revealPanel - Part 1

Working with one of our internal teams the other day, a couple of interesting ideas came up for presenting information. One of these was a sort of twist on panelAccordion and it seems to be a nice usecase to work through to show how you can get creative with your UI without having to ask for and wait for new components. 

The Brief

As I said, the requirement here was to produce something like panelAccordion. However, rather than restricting the selection bar to just a single line of text an maybe an icon,  what if you wanted larger images or multiple lines?  Here's a mockup:



Then like a panelAccordion, with discloseMany="false" and discloseNone="true" you want to be able to expand the details of up to one and only one of the selection panels (I'll call them "Topics" from now on).



So if I click anywhere in the first topic bar in the above image then that topic will collapse, if I click on a different topic bar then the first will collapse and the new selection will expand.
You'll notice the modernist conceit of the little triangle linking the revealed panel with it's topic. It's a small thing but that also helps to polish the look and make it feel current.

The UI Elements

So how would we build this? Well the answer is that the UI itself is surprisingly easy. That's all thanks to our new friend, the panelGridLayout  which hands us everything we need on a plate. 
We can break down the mockup above into three sets of repeating units, remember I called them topics.  Each topic is identical, so let's just look at one.
Logically the topic is composed of three vertically arranged areas:

(1) The topic header itself
        |
(2) The little indicator triangle
        |
(3) The reveal panel area with the additional content 

Area 1, the header is always displayed and areas 2 and 3 we need to reveal or hide dynamically when the user clicks anywhere on area 1.
In panelGridLayout terms we can simply translate this into three grid rows, one for each area.  The key point here is that if you set the height attribute of a <af:gridRow> to the value "auto" the grid will automatically size that row to fit its children, so guess what: if we set visible="false" on the contents in areas 2 and 3 then the grid shrinks for free. I just love that this is so easy.
So here's the outline component model for a revealPanel (Yes there is a full demo you can download - read on to the end)

<panelGridLayout>
<!-- For Each Topic -->
  <gridRow height="50px">
    <gridCell columnSpan="3" halign="stretch">
      <panelGroupLayout layout="vertical" styleClass="revealTopicPanel">
      <!-- Topic header content will go here --> 
      <panelGroupLayout>
    </gridCell>
  </gridRow>
  <gridRow height="auto">
    <gridCell width="80px"/>
    <gridCell width="10px" valign="bottom" >
      <panelGroupLayout layout="vertical" 
                        styleClass="triangleMarker"/>
    </gridCell>
    <gridCell width="100%"/>
  </gridRow>
  <gridRow height="auto">
    <gridCell columnSpan="3" halign="stretch">
      <panelGroupLayout styleClass="revealPanel"
      <!-- Revealed content will go here --> 
    </gridCell>
  </gridRow>
</panelGridLayout>

Let's look at the key points
  1. The first grid row representing the topic header can be fixed in size to make sure that the unexpanded list is regular.
  2. The first grid row just contains a single grid cell that streches across three logical cells using the columnSpan attribute. The reason for doing this will become clear when we look at the second area of the topic 
  3. We use halign="stretch" on that first area gridCell to fill it with its content. This is important to make the included component (e.g. panelGroupLayout in this case) fill the cell with its styling.
  4. Moving onto the second area of the topic than contains the little indicator triangle, here the row as it's height as auto so it will collapse when we hide the triangle.
  5. The triangle area row is divided into three cells. The center cell contains the actual triangle (see below) and the other two cells are used to position the triangle cell to a particular horizontal position. In this case I'm indenting the triangle by a fixed 80 pixels, however, if I adjusted the first and last cells to use width="50%" then the triangle would be centered to the width of the panel.
  6. The final area is again a grid row with height set to auto containing a single cell with columnSpan set to 3 and stretching it's content. 

The Triangle

The little triangle indicator could be done using an image file, however, here I'm just using a CSS style which is simpler and enables you to change the color as required. Here's the definition:

 .triangleMarker {
  width:0px;
  height:0px;
  border-left:8px solid transparent;
  border-right:8px solid transparent;
  border-bottom:8px solid rgb(247,255,214);  
}
This style is applied to the vertical panelGridLayout in the center area.  Vertical panelGrids become simple html <div> elements.

Managing the Topic - The Server Side Version

I'll cover a more advanced version of the component in my next posting which uses JavaScript to manage the hide and reveal of the panels, but in this post, let's look at the simple version. 
As discussed, the paneLGridLayout actually does all of the UI resizing for us, so all we really need to do to manage the set of topics is two things:
  1. Introduce a management class that will tell the reveal panel and triangle panel if they should be visible or not
  2. A small amount of event code to translate a click anywhere on the topic panel into a change in the above management class. 

The Management Class

For the sake of the demo I've developed a very simple management class that does not attempt to do anything fancy such as handling multiple sets of revealPanels (that task is left for the JavaScript implementation). Here's the class:

package oracle.demo.view;
import java.util.ArrayList;
import java.util.List;
public class RevealPanelManager {
    private int _panelCount = 10;
    private int _toggleTarget = -1;
    private List<Boolean> _revealedList;

    /**
     * Switches the state of the currently selected panel 
     */
    public void toggleState() {
        if (_toggleTarget >= 0 && _panelCount > 0) {
            boolean currentState = false;
            if (_revealedList != null){
                currentState = _revealedList.get(_toggleTarget);
            }
            resetStates();
            if (!currentState) {
                _revealedList.set(_toggleTarget, true);
            }
            _toggleTarget = -1;
        }
    }
    /**
     * Used to inject a panelCount into the management structure. If not called then an array upper limit of 
     * 10 will be used
     * @param panelCount
     */
    public void setPanelCount(Long panelCount) {
        int candidateCount = panelCount.intValue();
        if (candidateCount > 0) {
            //reset the list & re-create in the new size
            _revealedList = null;
            _panelCount = candidateCount;
            resetStates();    
        }
    }
    /**
     * Invoked, probably from a setPropertyListener / setActionListener to set the id of the 
     * panel to act on.  This may disclose or hide depending on the current state of the selected
     * panel
     * @param toggleNo - index number of the panel
     */
    public void setToggleTarget(int toggleNo) {
        this._toggleTarget = toggleNo;
    }
    /**
     *Called by the panel to see if it should be visible or not
     * @return List of Booleans indexed by panel number
     */
    public List<Boolean> getRevealed() {
        return _revealedList;
    }
    /**
     * Either creates or reinitializes the array to close all the 
     * panels
     */
    private void resetStates() {
        if (_revealedList == null) {
            _revealedList = new ArrayList<Boolean>(_panelCount);
            for (int i=0; i < _panelCount; i++) {
              _revealedList.add(i,false);
            }
        }
        else{
            for (int i = 0; i < _panelCount; i++) {
                _revealedList.set(i, false);
            }            
        }
    }    
}

As I said this class only manages a single set of topics in a single grid and it defined as a managed bean in your page flow definition for the relevant view something like this:

  <managed-bean>
    <managed-bean-name>revealManager</managed-bean-name>
    <managed-bean-class>oracle.demo.view.RevealPanelManager</managed-bean-class>
    <managed-bean-scope>view</managed-bean-scope>
    <managed-property>
      <property-name>panelCount</property-name>
      <property-class>java.lang.Long</property-class>
      <value>3</value>
    </managed-property>
  </managed-bean>
Notice how the expected panel count (3) in this case  is injected into this bean.

Once this management bean is defined then the content that we want to hide and display dynamically (e.g. the triangle panelGrouplayout and the revealPanel panelGrouplayout) and both use expression language to determine if they should be visible. e.g. 

<af:panelGroupLayout layout="vertical" 
                     styleClass="triangleMarker"
                     visible="#{viewScope.revealManager.revealed[0]}"/>
Where the zero based index number is passed into the list evaluation. 

The Click Event Handler

The final thing we need to do is to find a way to call the toggleState() method in the revealManager. The only twist here is that we don't want to have a commandButton or link in the panel to click. We want to be able to click anywhere on the panel.
This is a well established technique so you can look at the demo for the details of the code, but basically we define a small JavaScript function which is registered with the client side click. When the user clicks, this reads the numerical ID of the panel that was selected and makes a call back to the server to the handlePanelToggle() server listener method. That then talks to the revealManager, setting the id of the panel to toggle and invoking the toggle, finishing off with a partialUpdate to get the whole grid to re-draw. Have a look at the RevealRowComponentHandler.java class in the demo for the details.  

And the Result

Well here's the ADF implementation of the revealPanel. First closed:


And now opened:



The Demo

I've uploaded a demo that shows the basic version of revealPanel discussed here and a more advanced version which is discussed in the next article.  You can download the sample from the ADF Code Samples Repository

This demo is not foolproof and at the moment the animations are restricted to Chrome and Safari (WebKit browsers) It also needs to check for older browser versions that do not support the animations and switch them off

Note: The sample is written in 11.1.1.7 it will not work in 11.1.1.6 or older.

Further Note: There seems to have been a slight change in the js API in 11.1.2.n which are breaking the code at the moment - working on that one

Tuesday Apr 02, 2013

panelGridLayout - now we are complete

Good news, with the arrival of 11.1.1.7 (Patchset 6) the immensely useful <af:panelGridLayout> component has made it into the 11.1.1.n code-line.

If you're not familiar with panelGridLayout then check out my article on the subject from earlier and then go and check out the demo page which will link you off to the documentation etc. 

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 2013 »
MonTueWedThuFriSatSun
1
3
4
5
6
7
8
9
10
11
13
14
16
17
18
20
21
22
23
24
25
26
27
28
29
     
       
Today