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. 

Friday Mar 01, 2013

I Did It Because I Can

Somewhat inspired by Chris Muir's Record breaking (i.e. he claims to be the first) on getting ADF running on a Raspberry Pi,  I just had to do it.. Yes Husdon Continuous Integration Server running on a Model B using Java 7 and  Jetty as the application server. Imagine it now... - a supercluster of $35 computers doing your continuous builds (reallllllly sloooowly). But the fact remains, it ran, and I claim that record:

Now I just have to smuggle the machine back to my son's room before he gets back from school... 

Thursday Feb 21, 2013

Get More From Your Messages

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

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

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

How to make the Messages Interactive?

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

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

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

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

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

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

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

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

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

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

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

Wednesday Feb 20, 2013

Using JDeveloper 11.1.1.6 & 11.1.2.3 with Subversion 1.7

This issue has come up a couple of times this week and has also be raised on the OTN JDeveloper Forum, so it seemed to be worth a quick public-service announcement.  

Subversion 1.7 introduces changes (AKA complete re-write) into the way that working copies are stored, the implication of this is that a 1.6 client and a 1.7 client cannot operate again the same working copy on a developer's workstation. 

Current versions of JDeveloper's SVN support contain 1.6 client code, so if you are mixing and matching JDeveloper and SVN / Tortoise command line operations you will run into trouble. 

e.g. The following sequence of operations will fail:

  1. Checkout working copy from the command line with the svn 1.7 client
  2. Make updates and attempt to checkin from JDeveloper 

However, the following should work:

  1. Checkout working copy from JDeveloper 
  2. Make updates and checkin from JDeveloper 

As will:

  1. Checkout working copy from the command line with the svn 1.7 client
  2. Disable the JDeveloper SVN plugin - (Versioning -> Configure -> Uncheck - Versioning Support for Subversion )
  3. Make updates  to existing files only from JDeveloper
  4. Commit / Add / Delete operations from Command line svn 1.7 client. 

Obviously, for convenience and because some operations such as refactoring need to issue SVN commands I'd recommend that, if using a 1.7 repository, you carry out all operations including the initial checkout from within the IDE.  

As a general guidance if you are going to upgrade your repository (and my further advice would be not to upgrade at the moment if your primary client is JDeveloper) I'd recommend that you clean up any outstanding transactions before the upgrade, and then, once the server is up to 1.7, do a clean checkout from JDeveloper and stick to working within that environment.

Monday Feb 18, 2013

MySQL & ADF Business Components - Enum types

A quick guide to effectively mapping and representing MySQL enumeration types through ADF Business Components [Read More]

Friday Jan 18, 2013

Refresh Problems using Adaptive Bindings

In a previous article (Towards Ultra-Reusability for ADF - Adaptive Bindings)  I discussed how ADF Data Binding can be a lot more flexible that you would think due to the neat trick of being able to use expression language within the PageDef file to create bind sources that  can be switched at runtime.

As it happens my good buddy Frank Nimphius picked up the technique to use on a particular  project and hit a slight problem. If you change the results of the EL used in the binding - for example, you switch the base VO from Departments to Employees, things don't get refreshed correctly.  In fact what you see is that any attribute names that happen to match between the old and the new source will be correctly populated with the new data but the actual list of attributes will not be refreshed. The effect is that if you were using one of these bindings to populate a table, the "shape" of the table, in terms of its columns, would not change. 

No worries though, given that Frank is a clever chap he worked out the correct way to address this which is to simply call clearForRecreate() on the iterator binding.

 BindingContext bctx = BindingContext.getCurrent();
 BindingContainer bindings = bctx.getCurrentBindingsEntry();
 DCIteratorBinding iter = (DCIteratorBinding)
 bindings.get("TableSourceIterator");
 iter.clearForRecreate();

Thanks Frank! 

Thursday Dec 20, 2012

Dude, how big's my browser?

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

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

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

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

 

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

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

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

Friday Dec 07, 2012

Selective Suppression of Log Messages

Those of you who regularly read this blog will probably have noticed that I have a strange predilection for logging related topics, so why break this habit I ask? 

Anyway here's an issue which came up recently that I thought was a good one to mention in a brief post.  The scenario really applies to production applications where you are seeing entries in the log files which are harmless, you know why they are there and are happy to ignore them, but at the same time you either can't or don't want to risk changing the deployed code to "fix" it to remove the underlying cause. (I'm not judging here). The good news is that the logging mechanism provides a filtering capability which can be applied to a particular logger to selectively "let a message through" or suppress it. This is the technique outlined below.

First Create Your Filter 

You create a logging filter by implementing the java.util.logging.Filter interface. This is a very simple interface and basically defines one method isLoggable() which simply has to return a boolean value. A return of false will suppress that particular log message and not pass it onto the handler.

The method is passed the log record of type java.util.logging.LogRecord which provides you with access to everything you need to decide if you want to let this log message pass through or not, for example  getLoggerName(), getMessage() and so on.

So an example implementation might look like this if we wanted to filter out all the log messages that start with the string "DEBUG" when the logging level is not set to FINEST:

 public class MyLoggingFilter implements Filter {
    public boolean isLoggable(LogRecord record) {
        if (  !record.getLevel().equals(Level.FINEST) 
            && record.getMessage().startsWith("DEBUG")){
         return false;   
        }
        return true;
    }
}

Deploying  

This code needs to be put into a JAR and added to your WebLogic classpath.  It's too late to load it as part of an application, so instead you need to put the JAR file into the WebLogic classpath using a mechanism such as the PRE_CLASSPATH setting in your domain setDomainEnv script. Then restart WLS of course.

Using

The final piece if to actually assign the filter.  The simplest way to do this is to add the filter attribute to the logger definition in the logging.xml file. For example, you may choose to define a logger for a specific class that is raising these messages and only apply the filter in that case. 

<logger name="some.vendor.adf.ClassICantChange"
        filter="oracle.demo.MyLoggingFilter"/>

You can also apply the filter using WLST if you want a more script-y solution.

Saturday Nov 17, 2012

Towards Ultra-Reusability for ADF - Adaptive Bindings

The task flow mechanism embodies one of the key value propositions of the ADF Framework, it's primary contribution being the componentization of your applications and implicitly the introduction of a re-use culture, particularly in large applications.

However, what if we could do more? How could we make task flows even more re-usable than they are today? Well one great technique is to take advantage of a feature that is already present in the framework, a feature which I will call, for want of a better name, "adaptive bindings".

What's an adaptive binding? well consider a simple use case.  I have several screens within my application which display tabular data which are all essentially identical, the only difference is that they happen to be based on different data collections (View Objects, Bean collections, whatever) , and have a different set of columns. Apart from that, however, they happen to be identical; same toolbar, same key functions and so on. So wouldn't it be nice if I could have a single parametrized task flow to represent that type of UI and reuse it?

Hold on you say, great idea, however, to do that we'd run into problems. Each different collection that I want to display needs different entries in the pageDef file and:

  1. I want to continue to use the ADF Bindings mechanism rather than dropping back to passing the whole collection into the taskflow  
  2. If I do use bindings, there is no way I want to have to declare iterators and tree bindings for every possible collection that I might want the flow to handle

 Ah, joy! I reply, no need to panic, you can just use adaptive bindings.

Defining an Adaptive Binding 

It's easiest to explain with a simple before and after use case.  Here's a basic pageDef definition for our familiar Departments table. 

<executables>
  <iterator Binds="DepartmentsView1" 
            DataControl="HRAppModuleDataControl"
            RangeSize="25" 
            id="DepartmentsView1Iterator"/>
</executables>
<bindings>
  <tree IterBinding="DepartmentsView1Iterator" id="DepartmentsView1">
    <nodeDefinition DefName="oracle.demo.model.vo.DepartmentsView" Name="DepartmentsView10">
      <AttrNames>
        <Item Value="DepartmentId"/>
        <Item Value="DepartmentName"/>
        <Item Value="ManagerId"/>
        <Item Value="LocationId"/>
      </AttrNames>
    </nodeDefinition>
  </tree>
</bindings> 

Here's the adaptive version:

<executables>
  <iterator Binds="${pageFlowScope.voName}" 
            DataControl="HRAppModuleDataControl"
            RangeSize="25" 
            id="TableSourceIterator"/>
</executables>
<bindings>
  <tree IterBinding="TableSourceIterator" id="GenericView">
      <nodeDefinition Name="GenericViewNode"/>
  </tree>
</bindings> 

You'll notice three changes here.  

  1. Most importantly, you'll see that the hard-coded View Object name  that formally populated the iterator Binds attribute is gone and has been replaced by an expression (${pageFlowScope.voName}). This of course, is key, you can see that we can pass a parameter to the task flow, telling it exactly what VO to instantiate to populate this table!
  2. I've changed the IDs of the iterator and the tree binding, simply to reflect that they are now re-usable
  3. The tree binding itself has simplified and the node definition is now empty.  Now what this effectively means is that the #{node} map exposed through the tree binding will expose every attribute of the underlying iterator's collection - neat! (kudos to Eugene Fedorenko at this point who reminded me that this was even possible in his excellent "deep dive" session at OpenWorld  this year)

Using the adaptive binding in the UI

Now we have a parametrized  binding we have to make changes in the UI as well, first of all to reflect the new ID that we've assigned to the binding (of course) but also to change the column list from being a fixed known list to being a generic metadata driven set:

<af:table value="#{bindings.GenericView.collectionModel}"
          rows="#{bindings.GenericView.rangeSize}"
          fetchSize="#{bindings.GenericView.rangeSize}"
          emptyText="#{bindings.GenericView.viewable ? 'No data to display.' : 'Access Denied.'}"
          var="row" rowBandingInterval="0" 
          selectedRowKeys="#{bindings.GenericView.collectionModel.selectedRow}"
          selectionListener="#{bindings.GenericView.collectionModel.makeCurrent}"
          rowSelection="single" id="t1">
  <af:forEach items="#{bindings.GenericView.attributeDefs}" var="def">
    <af:column headerText="#{bindings.GenericView.labels[def.name]}" sortable="true"
               sortProperty="#{def.name}" id="c1">
      <af:outputText value="#{row.bindings[def.name].inputValue}" id="ot1"/>
    </af:column>
  </af:forEach>
</af:table>

Of course you are not constrained to a simple read only table here.  It's a normal tree binding and iterator that you are using behind the scenes so you can do all the usual things, but you can see the value of using ADFBC as the back end model as you have the rich pantheon of UI hints to use to derive things like labels (and validators and converters...) 

One Final Twist

 To finish on a high note I wanted to point out that you can take this even further and achieve the ultra-reusability I promised. Here's the new version of the pageDef iterator, see if you can notice the subtle change?

<iterator Binds="{pageFlowScope.voName}" 
          DataControl="${pageFlowScope.dataControlName}"
          RangeSize="25" 
          id="TableSourceIterator"/> 

Yes, as well as parametrizing the collection (VO) name, we can also parametrize the name of the data control. So your task flow can graduate from being re-usable within an application to being truly generic. So if you have some really common patterns within your app you can wrap them up and reuse then across multiple developments without having to dictate data control names, or connection names. This also demonstrates the importance of interacting with data only via the binding layer APIs. If you keep any code in the task flow generic in that way you can deal with data from multiple types of data controls, not just one flavour. Enjoy!

Update

Read this post as well on overcoming possible refresh problems when changing the source on a single page. 

Further update

Check out this article from Luc Bors on using similar ideas with Query Components / View Criteria.  

Wednesday Nov 14, 2012

Controlling the Sizing of the af:messages Dialog

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

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

The Technique

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

The JavaScript

For this example I've defined three JavaScript functions.  

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

Function resizeDefaultMessageDialog()

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

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

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

 Function dimensionIsValid()

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

Function  initializeDefaultMessageDialogSize()

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

Wiring in the Functions

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

<af:document>

   ....  

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

This makes the script functions available to call. 

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

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

 Just in Time Dialog Sizing 

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

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

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

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

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
« May 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
31
       
Today