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>