Thursday, September 6, 2012

Selenium WebDriver utility for determining when page has finished rendering

We are using Selenium for automated testing of our web app.   We use the new Selenium WebDriver API to create java based unit tests for interfacing with the UI.  One problem we had was determining when the page had completely finished rendering as the page may still be in the process up updating after the initial page is loaded.   There is an ADF javascript function for checking this
the helper method below can be called by the test class to determine when the page is fully synchronized with the server and the next action can be performed.



public static void waitForPageToFinishRendering(WebDriver oDriver, int timeout) {
        ExpectedCondition e = new ExpectedCondition() {
            public Boolean apply(WebDriver d) {
              JavascriptExecutor js = (JavascriptExecutor) d;
              Boolean isReady = (Boolean)js.executeScript("return AdfPage.PAGE.isSynchronizedWithServer()");
              return isReady;
            }
          };
          WebDriverWait w = new WebDriverWait(oDriver,
timeout,100);
          w.until(e);
    }


For a Good overview of using Selenium with ADF see 
http://www.oracle.com/technetwork/articles/adf/part6-094560.html
That article talks about using waitForElementPresent before performing the next action but I have found using the technique above to be more reliable.  Especially for complex pages where there is a lot of lazy loading going on.

Tuesday, April 24, 2012

The danger of using the default settings of a view object for committing data

We recently had a problem in production where the weblogic servers randomly ( once or twice per day) became extremely slow (basically unusable) for a period of time before recovering.  We discovered that the server was  running low in memory causing a perpetual garbage collection cycle to occur.   This begin occurring after loading a large amount of data (several million rows)  that was transferred from our old application.  Up until this point the system had been humming along fine.  An analysis of the thread dump showed that during this time of crisis the server was looping in the following code.


                oracle.jbo.server.QueryCollection.hasNextInResultSet(QueryCollection.java:4611)
                oracle.jbo.server.ViewObjectImpl.hasNextForCollection(ViewObjectImpl.java:6899)F
                oracle.jbo.server.QueryCollection.hasNext(QueryCollection.java:4579)
                oracle.jbo.server.QueryCollection.populateRow(QueryCollection.java:3553)
                oracle.jbo.server.QueryCollection.fetch(QueryCollection.java:3387)
                oracle.jbo.server.QueryCollection.get(QueryCollection.java:2188)
                oracle.jbo.server.ViewRowSetImpl.getRow(ViewRowSetImpl.java:5016)
                oracle.jbo.server.ViewRowSetImpl.getRow(ViewRowSetImpl.java:3242)  
                oracle.jbo.server.ViewObjectImpl.activateTransients(ViewObjectImpl.java:18328)
                oracle.jbo.server.ViewObjectImpl.activateTransients(ViewObjectImpl.java:18289)
                oracle.jbo.server.ViewObjectImpl.activateState(ViewObjectImpl.java:18512)
                oracle.jbo.server.ViewObjectImpl.activateState(ViewObjectImpl.java:18407)
                oracle.jbo.server.ViewRowSetIteratorImpl.activateIteratorState(ViewRowSetIteratorImpl.java:4025)
                oracle.jbo.server.ViewRowSetImpl.activateIteratorState(ViewRowSetImpl.java:7235)
                oracle.jbo.server.ViewObjectImpl.activateIteratorState(ViewObjectImpl.java:18742)
                oracle.jbo.server.ApplicationModuleImpl.activateVOs(ApplicationModuleImpl.java:8172)
                oracle.jbo.server.ApplicationModuleImpl.doActivateState(ApplicationModuleImpl.java:7918)
                oracle.jbo.server.ApplicationModuleImpl.doActivateAMState(ApplicationModuleImpl.java:7884)
                oracle.jbo.server.Serializer.activate(Serializer.java:296)
                oracle.jbo.server.DBSerializer.activateRootAM(DBSerializer.java:330)
                oracle.jbo.server.ApplicationModuleImpl.activateState(ApplicationModuleImpl.java:6207)



 The looping was occuring in the getRow method of ViewRowSetImpl.  The stack trace also shows that the looping is occuring as the result of a activation event to restore a previously passivated application module.  This demonstrates the importance of testing code with the jbo.ampooling=false  setting in order to test for passivation problems.   In this case however we still might not have notice this problem because it requires both a large table and a specific sequence of user actions in order to reproduce.  

Here is the getRow method from ViewRowSetImpl.   The section in red was where the looping was occuring


   public Row getRow(Key key)
   {
      Row row;

      if (key != null)
      {
         Object keyHandle = key.getRowHandle();

         if (keyHandle != null && mQRef.getSignature() == key.getSignature())
         {
            row = getRowFromHandle(keyHandle);
            if (row != null && key.equals(row.getKey()))
            {
               return row;
            }
         }
      }

      //if there are rows in the collection, then match those before calling execute.
      if (getFetchedRowCount() > 0)
      {
         for (int j = 0; (row = getRow(j)) != null; j++)
         {
            if (key == null || row.getKey().equals(key))
            {
               return row;
            }
         }
      }

      //make sure that the query is executed before calling getRow(int);
      if (!isExecuted())
      {
         synchronized(getSyncLock())
         {
            execute(false /*force*/, false /*notify*/); //no need for eventing here.
         }
      }

     for (int j = 0; (row = getRow(j)) != null; j++)
      {
         if (key == null || row.getKey().equals(key))
         {
            return row;
         }
      }

      return null;
   }

Here is what I  did to duplicate the problem on my local jdeveloper instance.

  • We have an entity based view object to a table that contains a large (2+ million) row set.  The view object was unbounded (no bind variables)
  • The range size was set to 15. 
  •  There is an iterator binding on the page bound to this view that is bound to a popup form for adding or inserting  one row of data. 
  •  When the popup displays the unbounded query executes because the iterator executes when first used (the default setting).  This may not be good for database performance (which was taking ~3 seconds) but it only retrieves the 1st range of 15 rows from the database into memory and this did not normally cause a problem so was not noticed.
  •  The data was entered on the form and a button “save and add another” is clicked which commits the current row and advances the form to a new row ready for input. 
  •  At this point we have a view object that contains 1 range of rows (15) that were returned by the orignal query plus the newly inserted row that we just committed (16 rows in total).
  •  Now to duplicate the problem we force a passivation of this users app module.  We did this by setting the pool size to 1 and opening up the same page in another browser window.
  • We then cancel out of the form popup.  Before the form closes it presents an “Are you sure?” dialog and  it must re-activate the app module it is bound to.   During activation the listed getRow() method in ViewRowSetImpl is called in order to find and sync up all of the 16 rows that were in the view when it was passivated.  Since there was a newly inserted row it may be located near the end of the result set that would be returned by the unbounded query. (there was no sort order)   The loop in red is the problem.   The ADF framework was searching for this row by retrieving one row at a time from the database.  It basically had to go through all 2 million rows and pull them into memory before it would find it.  On my local debug machine this was happening extremely slow.  (About 50 per second) and I would run out of memory before it finished (and there was only 390,000 rows in our dev database)
This  could have been instantaneous if ADF would run a query with the ID to retrieve it, instead of searching for it row by row.  I really think this is a serious design flaw but the feedback from Oracle was that this was working as designed.   I don't understand why the activation process event needs to find the rows from the database.   It's purpose is to restore the state of the view object to what it was prior to passivation so why not just read the passivation record and be done with it?   This problem could occur on any large table that is used for both scrolling through the data and inserting rows using the same view object. 

Bottom Line and Lessons Learned.
  1. If you have a view object that is only used for inserting rows, make sure that you set the Tuning parameter to Retrieve from the Database "No Rows" or "At Most One Row"  
  2. If you do need to display a table that can filter and scroll over a very large result set and also insert new rows, use a read-only view object for displaying the data and a separate Entity based view object for inserting new rows.  
  3. If the view object had had a sort order that caused newly inserted rows to appear at the beginning of the result set, we wouldn't have noticed this problem. 
  4. This shows how critical it is to set the tuning parameters appropriately when dealing with large tables.   In our case this was a hidden time bomb waiting to happen.




Tuesday, January 24, 2012

Executing iterator binding in deferred Mode when there are required bind variable parameters

An iterator binding can be set to execute in "deferred" mode.  This is the default behavior if you don't change it.   What this means is that the query will not execute unless the page needs to display it's data.   This is great for use on a page that doesn't initially display the data to the user, like in a popup or tab that is not initially disclosed.  If the popup is displayed or the tab is disclosed, then and only then will the query be executed.  Also for tables that are set to lazy load (retrieve it's data after the page initially loads) this prevents the query from executing until the page is first displayed and the "Fetching data..."  message is displayed over the table.  A problem arises when the view object requires named bind variables.  The bind variables do not get set unless you add an ExecuteWithParams action binding to the task flow.   Calling an ExecuteWithParams will execute the query but that defeats the value of setting the iterator binding to "deferred"   So how do I set the bind variables without executing the query?

Here is one technique:
  • Edit the View object, select the Java panel and check the "Generate View Object Class" and "Include bind_variable accessors.
  • Edit the Client Interface and add the setMyValueBind()  method to the interface.
  • Select the Data Control panel and click the refresh icon to refresh the data controls.
  • Find your view object in the data controls panel and expand it.  You should see the setMyValueBind method under the list of attributes.
  • Drag the method call over onto your task flow and add an EL expression to set the bind variables value.
  • insert the method call into the chain of operations that execute prior to displaying the page.
That is all that is needed.  The first time the data is displayed, the iterator binding will execute the query using the set bind parameters and if the user never displays the popup or tab that displays the data, the query will not need to be executed.

Friday, January 13, 2012

How to cancel a long running query from the UI (part 1)

I've long wondered if there was any way to cancel a query that is taking too long to complete from the page where the query is being run, similar to hitting the cancel button in SQL Developer.   I noticed that the view object class has a cancelQuery() method on it but how do you call that when the thread is busy running the query.   The javadoc on the cancelQuery function says it can be called from another thread and this is what happens when you set a timeout value on the view object.   ADF spawns a monitor thread that will cancel the query after the timeout elapses.  I did some experimenting and found that if I overrode the executeQuery method in the view and saved the view object on the session I could then use that to cancel the query from another task flow running in another tab or browser window.
   Here is the code
    @Override
    public void executeQuery() {

        Map sessionScope = 
            ADFContext.getCurrent().getSessionScope();
        sessionScope.put("MyQuery",this);
        try{
        super.executeQuery();
        } finally{
          // only keep on the Session 
          //for the duration of the query  
          sessionScope.remove("MyQuery");
       }

On a different page put a button for canceling the query that calls this action method
  
public void cancelQueries(ActionEvent actionEvent) {
    Map sessionScope = ADFContext.getCurrent().getSessionScope();
    ViewObjectImpl vo = (ViewObjectImpl)sessionScope.get("MyQuery");
    if (vo!=null){
        if (vo.isDead()==false){
          vo.cancelQuery();        

        }
      sessionScope.remove("MyQuery");
    }

}
          
This will cause the query to throw a "JBO-27126 Long running query has been canceled" Exception.
In order to prevent the page from becoming broken you must catch this exception in the executeQueryForCollection method and re-throw a new JboException which will be displayed to the user.   See this blog for an example of that.


This seems to work fine, at least on 11G database.  According to this blog there my be problems making it work with the 10G drivers.  The next question is how would I put a button on the same browser tab as the one running the query.   I haven't implemented that but I can think of a couple of ways that might be done.  One would be to have the button call a javascript method that makes a custom AJAX call back to a plain old servlet that retrieves the users session and calls the cancelQuery method.  Another might be to add an inline frame that has a URL to a different task flow and page containing only the single button.  ADF doesn't let you submit a button on the same page until the page is no longer busy but using an inline frame is the same as running the task flow on another tab so concurrent requests could be made. 



Thursday, September 22, 2011

How to Ignore Time Component of Date column when filtering on Date.

When you create a table that supports column filtering,  the default behavior for filtering on dates is to compare the date directly.  This means that if the date in the database has a time component the row will not match and no results will be found.    If you don't need to display the time along with the date in the table then you can just put a trunc(...) around the date in the original query.   If you do need to display the time then you can add another view attribute to your query that outputs the trunc'd version of the date.   The new column will not be displayed but will be used for sorting and filtering.   The af:column component has a sortProperty attribute that usually contains the column attribute name for the column displayed below it.   This does not have to be the case however.  The sortProperty can be set to any column in the view object so that when you put a value in the filter field it filters and sorts on a different column then the one displayed below it.  Set the sortProperty for the date/time column to the date only column and you will get the desired behavior.  When you filter on a date it will return all rows with that date regardless of the time portion of the date.

There is another way to accomplish this that will make every Date filter within your application time agnostic so that developers do not need to add this extra column every time they want to filter on the date portion of a date/time column.To do this you need to create a custom SQL builder class that extends OracleSQLBuilderImpl and override the getFormattedLHSCompareFragment method (Left Hand Side Compare Fragment)  This is called whenever filtering is performed.   In this method add the trunc(...) around the value.  Sample code is shown below.


This class is activated by adding a JVM parameter on startup (jbo.SQLBuilder)


  -Djbo.SQLBuilder=com.yourCompany.model.custom.MySQLBuilderImpl

/**
 * Override method in OracleSQLBuilderImpl in order to trunc (remove time component)
 * when filtering on Dates in tables (Query By Example) QBE
 * This class is activated by adding a JVM parameter
 *  -Djbo.SQLBuilder=com.yourCompany.model.custom.MySQLBuilderImpl
 */
public class MySQLBuilderImpl extends OracleSQLBuilderImpl {
    
    private static final Log log = LogFactory.getLog(MySQLBuilderImpl.class);
    private static SQLBuilder mSQLBuilderInterface=null;  
    
    public MySQLBuilderImpl() {
    }


  /**
   * Gets the singleton instance of this class.
   * @return a SQLBuilder object.
   */
  synchronized public static SQLBuilder getInterface()
  {
     if (mSQLBuilderInterface == null)
     {
        if (Diagnostic.isOn())
        {
           Diagnostic.println("OracleSQLBuilder reached getInterface");
        }

        mSQLBuilderInterface = (SQLBuilder)(new MySQLBuilderImpl());

        if (Diagnostic.isOn())
        {
           Diagnostic.println(mSQLBuilderInterface.getVersion());
        }
     }
     return mSQLBuilderInterface;
  }
 

  @Override
  public String getFormattedLHSCompareFragment(ViewObject vo, ViewCriteria vc,
                                               AttributeDef attrDef,
                                               int sqltype, String lhs,
                                               Object rhs) {
   String value =   super.getFormattedLHSCompareFragment(vo, vc, attrDef, sqltype, lhs, rhs);
   
    if (sqltype==Types.DATE || sqltype==Types.TIMESTAMP) {
      
      // only apply trunc to database columns, not view alias names used for in-memory filtering
      // ADF does not handle a trunc for a in-memory filter and will get an error
      Integer attr = vo.getAttributeIndexOf(value); // 
      if (attr == -1){  // not found, meaning it's a database column name, not an attribute name.
      
       value = "TRUNC("+value+")";
        if (log.isDebugEnabled()) {
            log.debug("MySQLBuilderImpl truncating date value:"+value);
        }
      }
    }
    return value;
  }
}

Tuesday, January 18, 2011

Commiting an ADF view that contains an outer join

Here is my problem

  • I have a ADF writable View of table1 that that contains a left outer join to table2.  
  • Table2 has a foreign key to table1. 
  • Both Table1 and Table2 contain editable fields displayed in the UI.
  • Table1 contains an existing row to be edited and will always be commited but I don't want to create a row in table2 unless the user enters data in one of table2's fields
My problem was that if I tried to commit, I would get an "attribute x is required" message because the foreign key was missing.  To solve this I opened the view in jdeveloper, clicked on the Java tab and selected the "Generate View Row Class" so that a ViewRowImpl.java class is created.

 I then opened this generated class and updated the setter for each attribute that can be edited in the UI. Below is an example. The red text was what I added.   KeyAttribute is the primary key of table1 KeyAttribute2 is the foreign key in table2.  They need to be set to the same value before table2 can be commited. If there is currently no matching row in table2, KeyAttribute2 will be null.  KeyAttribute1 is a DBSequence and KeyAttribute2 is a Number, hence the call to getSequenceNumber to convert it to a Number.  With this code, now I don't get the error any more and also it won't commit a row in table2 if no data for it has been entered.

    public void setMyDate(Date value) {
        if (value!=null && getKeyAttribute2() == null){
          setKeyAttribute2(getKeyAttribute().getSequenceNumber());
        }
        setAttributeInternal(MYDATE, value);
    }

This works great when the row you are editing in table1 already exists.  If you want to create a new row in table1 and commit it at the same time as a new row in table2 it is a bit more challenging as you will need to generate a DBSequence to be added to both the Primary and Foreign key fields but only to the foreign key field if other data exists for table2, otherwise you will create a empty row in table2 with only the foreign key.

Monday, December 13, 2010

Accessing ADF components using client side script

Occasionally there is a need to access a client side component using javascript.   One technique is to send script  down to the page from the backing bean that looks up the component using the ADF javascript method
AdfPage.PAGE.findComponent('clientIdOfComponent');
That technique works well but requires that you bind the component to the backing bean or look up the component using the full naming container path.  The technique I am going to describe allows you to declaratively access a UI component in script without having to add java code in the backing bean.  Because the getClientId(FacesContext)   method of the UIComponent requires a parameter it cannot be called directly within a EL expression on the page. The only parameter that can be passed in a EL Expression is the key to a Map object.  Because of this I created a Application scope helper bean  (ClientIdMap) that implements the Map interface.   It's not a real map and it's sole purpose is to evaluate the Key as a EL expression and return the Client ID of the component it finds. Just add this as an Application scope bean to the adfc-config.xml file.  (unused methods are not shown but must be implemented). 

public class ClientIdMap implements Map {
    /**
     * @param key el expression where UIComponent is stored
     * @return client side component ID
     */
    public Object get(Object key) {
       
        String returnValue = "";
        Object val = JSFUtils.resolveExpression("#{"+key+"}");
        if(val instanceof UIComponent){
            returnValue = ((UIComponent)val).getClientId(FacesContext.getCurrentInstance());
        }
           
        return returnValue;
    }

}

Then add this line to the top of your page which sets the client ID into a javascript variable that can then be used by your script.  Don't ask how the double nested single quotes work but they do.

<trh:script text="var myPopup = '#{ClientIdMap['requestScope.myComponent']}';"/>

Notice that for this to work, an EL expression (without the surrounding #{}) must be passed that is the same as the binding for the component.  Note that you can bind a component to a backing bean or directly to requestScope as shown. 

<af:popup binding="#{requestScope.myComponent}" id="myPopup">


Binding to requestScope is easiest because it can be added at runtime without restarting the server.

Then to retrieve the UIComponent do something like this (shows the popup without making an ajax call to the server)


var popup = AdfPage.PAGE.findComponent(myPopup);
var hints = {autodismissNever:true};
popup.show(hints);

The advantage of retrieving the ClientId from the binding instead of adding the fully qualified path in your javascript is that ADF may add indexes to your client ID that may change.  (see this link) Also, if someone adds a naming container around your page, region or component the Client ID will change so it's never a good idea to hard code the client ID.

Removing rows from a Query Collection

Here's a link to a blog that describes how to remove Rows from a query Collection without deleting the row from the database.  This works for entity based views but I had a need to remove rows from a Query Collection of a non-entity based read only view.     The way I did this was to add a transient attribute "Displayed" to the view.  I then create a view criteria (filterCriteria) that filters out any row where Displayed='False'.  In the "Edit View Criteria" dialog I set the Query Execution Mode to "In Memory"   After executing the query in my backing bean I loop through the rows and set the Displayed attribute to 'False" for any row I don't want to display.   Then I apply an in-memory filter by executing the following

        setApplyViewCriteriaName(filterCriteria);
        setQueryMode(QUERY_MODE_SCAN_VIEW_ROWS);                                                           
        super.executeQuery();




Normally you would just filter the original results using SQL but in my case the rules for determining which rows to remove did not translated easily to SQL so this technique was employed.  This might be useful if you were filtering out rows based on the users permissions and those permissions were stored in LDAP and couldn't be made part of the query.

Wednesday, June 30, 2010

Case Insensitive Search

We had an interesting problem when migrating to the PS2 version of jdeveloper. All of our  QBE table filters that previously were case insensitive became case sensitive.


ADF supports case-insensitive queries but it relies on use of the UPPER() function to wrap the criteria and column selection.  We made design decisions a long time ago, that (A) the business data would not be converted to all upper case, and (B) the application search screens would not convert user criteria to upper case. To make all searches case-insensitive, the application use Oracle’s case-insensitive string comparison feature NLS_COMP=LINGUISTIC and NLS_SORT=BINARY_CI (where CI means case-insensitive).  Furthermore, to support indexed case-insensitive searches, the data modeler added linguist-sort indexes on all indexed character strings. That works fine for the “=” operator but we found out it does not work for the LIKE operator. This has been fixed in the 11g database but if using a 10g database,  filtering using the like operator will invalidate the indexes and produce a full table scan.  Furthermore, the use of the UPPER() function on the table column will invalidate the linguistic sort index so we can't use the ADF way of making filters be case insensitive.

The reason filters on tables became case sensitive in PS2 was that even though the database was case insensitive by default, ADF would perform an in-memory filter immediately after executing the query and the in-memory filter, being case sensitive would remove all of the non-matching rows from the result set.  The fix for this was a little tricky.   All of our view objects inherit from a custom base class we created.  In this class we have overridden the getViewCriteraClause(boolean forQuery) method.   This method is called whenever ADF is about to apply view criteria (i.e. filter)  for either a database or a in-memory query.   It conveniently lets you know which type (forQuery) it is requesting.  So now all we need to do is return null when forQuery==false and no criteria will be applied to in-memory filters.   But what if we actually want to perform in-memory filtering?   Well, we can check the query mode to determine if the query is a "database only" query  and no in-memory filtering should be performed.   The problem caused in PS2 was because ADF was performing this in-memory filter even though the query mode had not been set to do so. Therefore we can detect this condition and only allow in-memory filtering when the view's query mode is set to do so.

In the example code below, I'm allowing the in-memory filter to occur but simply calling vc.setUpperColumns(true); to put the UPPER() clause around the in-memory filter making it case insensitive. I want to do just the opposite when the query is bound for the database. There is one section of code added there to support date filters where we skip the in-memory filter all together by returning null. I'll talk about that in a later blog.




   public String getViewCriteriaClause(boolean forQuery) { 

    if (forQuery == false){
         // clause is for cache (in-memory)
    
    
  // WARNING - It is possible to set the criteria mode to cache and the query mode
  // to database.  That will query for a larger result set, then immediately filter it down in-memory
  // queryModeDoesNotUseCache will be true in that case even though it performs a
  // in-memory filter after the fact.  Doing so is not recommended as it wastes memory unless you
  

            boolean queryModeDoesNotUseCache =
                ((getQueryMode() & ~QUERY_MODE_SCAN_DATABASE_TABLES) == 0);


            ViewCriteria[] vcs =
                getApplyViewCriterias(ViewCriteria.CRITERIA_MODE_CACHE);
            if (vcs != null && vcs.length > 0) {

                boolean criteriaForCacheModeOnlyFound = false; //
                for (ViewCriteria vc : vcs) {

                    criteriaForCacheModeOnlyFound |=
                            (vc.getCriteriaMode() == ViewCriteria.CRITERIA_MODE_CACHE);

                    if (vc.isUpperColumns() == false) {
                        vc.setUpperColumns(true); // puts UPPER(..) around the values for filtering in-memory
                    }
                }
                if (queryModeDoesNotUseCache &&
                    !criteriaForCacheModeOnlyFound) {
                    // if query mode does not apply to cache and criteria mode
                    // is not "cache only" mode (i.e not for both cache & database)
                    // don't apply any in-memory filter.
                    // This is needed in PS2 to prevent in-memory filtering of dates which causes
                    // no results because the time component is compared as well.
                    // see CustomSQLBuilderImpl which has a PS2 workaround to trunc the date
                    // so the date filters work (without comparing time component)
                    // trunc will fail if applied to a in-memory filter on PS2
                    return null;
                }


            }
    } else{
      
      ViewCriteria[] vcs =
          getApplyViewCriterias(ViewCriteria.CRITERIA_MODE_QUERY);
      if (vcs != null && vcs.length > 0) {
          for (ViewCriteria vc : vcs) {

              if (vc.isUpperColumns()) {
                  vc.setUpperColumns(false); // remove UPPER(..) around the values for database queryies
              }
          }
      
      
      }
    }
        String clause = super.getViewCriteriaClause(forQuery);

        return clause;

    }

Tuesday, June 29, 2010

Creating Page level accelerator (hot) keys

ADF allows you to set access keys on buttons.  For example
<af:commandtoolbarbutton accesskey="o" 
            action="OpenDetails" text="Open" />

The accessKey must be a single character and in firefox, in order to use the access key you have to hit Alt+Shift+o.

What if you want to use an accelerator key for other types of keystrokes? Menu Items are the only component that supports accelerator keys so for example to add a hot key for CTRL+0  you would need to add a menu item somewhere on the page 
<af:commandMenuItem action="OpenDetails" 
                       accelerator="ctrl 0" />

A menu item needs to hang inside a af:menu and af:menu needs to hang inside a menuBar so if you only want to use the accelerator feature without displaying a menu you will need to hide the menu container (menuBar).

using inlineStyle="display:none".   Now have the menu item perform the same action as the button and you have just created an accelerator key stroke for your button.

 Oracle Documentation on Accelerator Keys

Tuesday, June 15, 2010

Displaying newly added row in a table

With the new release  of jdeveloper (PS2) There was a table that I thought wasn’t refreshing after adding a new row to it. It actually was refreshing but when the table refreshed, the scroll bar moved slightly but not noticeably down so that that the added row was scrolled off the top of the screen so that the table looked like it hadn’t changed. I discovered that there is a property of the table “DisplayRow” that can contain "default", "first", "last" or selected. The dropdown shows that selecting “default” should be the same as selecting “first” but I found this not to be the case. When I changed it from “default” to “first” it fixed the problem and now the added row is displayed at the top of the table after refreshing without moving the scrollbar.

Friday, June 4, 2010

Preventing long numbers from displaying in exponential notation when exporting to excel

The af:exportCollectionActionListener behavior in ADF Faces Rich client provides a simple way of exporting table content to excel spreadsheets. There is one issue with that though in that long numbers will display in exponential notation. This is the fault of Excel not the export. Opening the spreadsheet in "Open Office" doesn't exhibit that behavior. There was a work-around for this problem as described in a blog by Duncan Mills but that work-around no longer works in the PS2 release of Jdeveloper, however I was able to modify that solution and make it work. The work around was to put hidden text around the data in the column that put a =TEXT(...) or =TRIM(...) tag around the long data value. This tag would get exported and inform excel that the value was to be displayed as text. This didn't work in PS2 because elements with the visible='false' attribute no longer get exported. My work-around was to set the element to be visible but add a inline style to the text component of "display:none". Now it gets exported to excel but doesn't show up in the UI. Problem solved. 

example


<af:panelGroupLayout layout="horizontal" >
<af:outputText value="=TRIM(" visible="true" inlineStyle="display:none;"/>
<af:outputText value="121212121212121212121" />
<af:outputText value=")" visible="true" inlineStyle="display:none;"/>
</af:panelGroupLayout>

Tuesday, May 5, 2009

How to force a table to refresh itself

Sometimes you will need to refresh a table after some event takes place in your backing bean.  You can refresh the table component using a partial trigger or refresh it from the backing bean using   getAdfFacesContext().addPartialTarget(uicomponent);  but that pulls the current values from the model through the page binding and doesn't necessarily pull fresh data from the database when the data has not been inserted from the ADF application.  I was having a problem in that when data was modified externally to our application (for example by an external web service call), that refreshing the table itself was not re-executing the query to bring in the new data.

This technique is simple and works across region boundaries instead of using contextual events.  Contextual events are Oracles' recommended practice for inter-region communication and I use them in many places but I have found them to carry a performance penalty, at least at the time of this writing and they are more difficult to configure.

When you have a table somewhere on your page (any region) that needs refreshing, perform the following steps


  • In a backing bean method, set a Boolean.TRUE into a requestScope variable. (see code in red below).
    ADFContext.getCurrent.getRequestMap()
    .put("refreshNeeded", Boolean.TRUE);
    This can be done from the backing bean of any region (task flow) running on the page.
  •  In the executable bindings for your table, add a invokeAction executable (named refreshIfNeeded) and bind it to the Execute or ExecuteWithParams action binding for your table’s view (on the left hand side).
  • Set the refreshIfNeeded binding to refresh “Always”
  • Add a refresh condition expression that references your request scope variable.






In your backing bean you could  retrieve the "Execute" Action from the bindings and executed it yourself but that only works if your backing bean is in the same region as the table you want to refresh.  Also, this technique assures the query is only executed once during the ADF lifecycle.   If the variable is set on requestScope more than once by different regions, no harm done.  You will still need to refresh the table component itself either through a partial trigger or from the backing bean as mentioned but this technique will force the query to execute as well.