Pages

Tuesday, November 30, 2010

ADF Faces Rich Client Development

In this blogpost I will try to give you a jumpstart in javascript development. For ADF Faces RC development you need to have JDeveloper 11g together with Mozilla FireFox & the FireBug plugin.
This blogpost is inspired by the great work of Frank Nimphius ( Lot's of demos and a very good chapter in his book ) and a very nice blogpost of Chris how you can use hotkeys, to switch Tabs in UIShell.

Before you can start we need to add some context parameters to the web.xml
<context-param>
    <param-name>oracle.adf.view.rich.profiler.ENABLED</param-name>
    <param-value>true</param-value>
  </context-param>
  <context-param>
    <param-name>oracle.adf.view.rich.LOGGER_LEVEL</param-name>
    <param-value>ERROR</param-value>
  </context-param>
  <context-param>
    <param-name>oracle.adf.view.rich.LOG_WRITERS</param-name>
    <param-value>AdfConsoleLogWriter</param-value>
  </context-param>
  <context-param>
    <param-name>org.apache.myfaces.trinidad.DEBUG_JAVASCRIPT</param-name>
    <param-value>true</param-value>
  </context-param>
I set the oracle.adf.view.rich.LOGGER_LEVEL to ERROR because the RC frameworks generates a lot of INFO messages, so you won't see your own log messages.
The oracle.adf.view.rich.LOG_WRITERS parameter enables the Javascript Logger popup window, when the firebug console is enabled then this popup won't launch and the messages will be seen in the firebug console window.
The org.apache.myfaces.trinidad.DEBUG_JAVASCRIPT will allow to debug a javascript file in the firebug debug console.

The next step is to create a javascript file and put this in a new folder in the Web Content folder of your web application.

To load the javascript files in your ADF Web Application you can use af:resource
<af:resource type="javascript" source="js/keys.js"/>
When you do this in the JSPX page then every page / region or fragment can call these javascript methods.
<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
  <jsp:directive.page contentType="text/html;charset=UTF-8"/>
  <f:view beforePhase="#{viewScope.HandleKeys.registerKeyboardMapping}">
    <af:document id="document" title="Client Side">
      <af:resource type="javascript" source="js/keys.js"/>
      <af:resource type="javascript" source="js/utils.js"/>
      <af:serverListener type="keyboardToServerNotify"
                         method="#{viewScope.HandleKeys.handleKeyboardEvent}"/>
      <af:form id="f1">
        <af:pageTemplate viewId="/oracle/ui/pattern/dynamicShell/dynamicTabShell.jspx"
                         value="#{bindings.pageTemplateBinding}" id="pt1">

To call a javascript method from your JSF page you can use af:clientListener where you need to provide the method name and how it is activated.
<af:clientListener method="numbersOnly" type="keyDown"/>

Here an example of a javascript method
function numbersOnly(evt){
     var _keyCode = evt.getKeyCode();
     var _filterField = evt.getCurrentTarget();
     var _oldValue = _filterField.getValue();
     
     AdfLogger.LOGGER.logMessage(AdfLogger.ERROR, "key: "+_keyCode);
     //check for characters
     if (_keyCode > 64 && _keyCode < 91){  
       _filterField.setValue(_oldValue);
       evt.cancel(); 
     }
  }
In this code the AdfLogger.LOGGER.logMessage will log a message in the firebug console or logging popup.

ADF 11g has a very large Javascript API which you can use in your own Web Application, to see the API, click here.

You can also pass on data to the javascript method. You can use the af:clientAttribute for this with as value a text or an EL expression.
<af:panelHeader text="Tab Order" id="ph4">
     <af:panelFormLayout id="pform" clientComponent="true">
      <af:inputText id="it3" label="Tab 1">
        <af:clientAttribute name="tabindex" value="1"/>
        <af:clientListener method="setTabIndex('1','4')" type="keyPress"/>
      </af:inputText>
      <af:inputText id="it4" label="Tab 3">
        <af:clientAttribute name="tabindex" value="3"/>
        <af:clientListener method="setTabIndex('1','4')" type="keyPress"/>
      </af:inputText>
      <af:inputText id="it5" label="Tab 4">
        <af:clientAttribute name="tabindex" value="4"/>
        <af:clientListener method="setTabIndex('1','4')" type="keyPress"/>
      </af:inputText>
      <af:inputText id="it6" label="Tab 2">
        <af:clientAttribute name="tabindex" value="2"/>
        <af:clientListener method="setTabIndex('1','4')" type="keyPress"/>
      </af:inputText>
     </af:panelFormLayout>
    </af:panelHeader>
To get the clientAttribute value in javscript you can use this in javascript , component.getProperty("tabindex") Or you can do a Javascript callback
function setTabIndex( min, max) {
     return function (evt) {
       var minTabIndex = min;
       var maxTabIndex = max;
       
       var keyCodePressed = evt.getKeyCode();
       var keyModifiers = evt.getKeyModifiers();
       var component = evt.getSource();
       var panelForm = component.getParent();

       if ( keyCodePressed == AdfKeyStroke.TAB_KEY ) {
         evt.cancel();
         var tabIndex = parseInt(component.getProperty("tabindex"));
       }
    }
  }

Next question will be, how can you invoke a method in a managed bean (serverside) from the clientside. For this we can use af:serverListener, but you can not invoke this managed bean method directly. You need to fire a client ADF Event from the clientListener method. like this AdfCustomEvent.queue( source, "clickOnRow", {}, false);
<af:table value="#{bindings.allDepartments.collectionModel}" var="row"
               rows="#{bindings.allDepartments.rangeSize}"
               emptyText="#{bindings.allDepartments.viewable ? 'No data to display.' : 'Access Denied.'}"
               fetchSize="#{bindings.allDepartments.rangeSize}"
               rowBandingInterval="0" rowSelection="single" id="departmentTable"
               width="400"
               selectedRowKeys="#{bindings.allDepartments.collectionModel.selectedRow}">
      <af:clientListener method="clickRow" type="click"/>
      <af:serverListener type="clickOnRow" method="#{Client.goCurrentRow}"/>
      <af:column sortProperty="departmentId" sortable="false"
                 headerText="#{bindings.allDepartments.hints.departmentId.label}"
                 id="c1">
       <af:outputText value="#{row.departmentId}" id="ot3">
        <af:clientAttribute name="departmentId" value="#{row.departmentId}"/>
        <af:clientListener method="showPopup('::deptPopup','dialog1','departmentId')"
                           type="click"/>
       </af:outputText>
      </af:column>
      <af:column sortProperty="departmentName" sortable="false"
                 headerText="#{bindings.allDepartments.hints.departmentName.label}"
                 id="c2" width="200">
       <af:outputText value="#{row.departmentName}" id="ot1"/>
      </af:column>
     </af:table>
Here is the javascript for the ADF Client Event.
function clickRow(evt) {
     var source = evt.getSource();
     AdfLogger.LOGGER.logMessage(AdfLogger.ERROR, "clickOnRow");
     AdfCustomEvent.queue( source, "clickOnRow", {}, false); 
  } 
And the managed bean method.
public void goCurrentRow(ClientEvent clientEvent) {
       // Determine which row is currently selected in the RichTable
       RichTable deptTable = (RichTable)clientEvent.getComponent();
       Iterator keys = deptTable.getSelectedRowKeys().iterator();
       while (keys.hasNext()) {
           Key key = (Key)((List)keys.next()).get(0);
           System.out.println(key.getKeyValues()[0]);
       }
   }

The only thing still to do is to debug the javascript methods. Start you Web application in FireFox and open the firebug application.  Click on the script Tab.

 Select the javascript file which you want to debug.

Set the breakpoint and debug through your application.

Here is my demo application with the following client examples ( thanks to Frank ).

  • Hotkeys for Tab switching
  • Tab Order
  • Clear validation messages
  • Type only numbers in a inputtext.
  • Click on a record and invoke a managed bean method
  • Click on a inputtext and display the value in a popup


Here you can download the demo workspace

4 comments:

  1. Hi Biemond. Nice article!

    I was wondering how can I click an ADF command button from inside a .js file included in an html inside an af:inlineFrame: all this to integrate Oracle Map with ADF: I want that my "click" on a javascript object propagates to an adf button.
    ...
    To be clearer, my page structure is:
    document
    panelStretchLayout
    facet:center
    inlineFrame (this contains the map, an HTML with javascripts)

    facet:end
    commandButton

    is this possible? Can you suggest me something ?

    ReplyDelete
  2. Hi,

    I think this is simple, just lookup you adf button id and call the click on this button.

    http://www.java2s.com/Tutorial/JavaScript/0200__Form/Buttonclick.htm

    thanks

    ReplyDelete
  3. Hi Edwin,

    Regarding clicking a button programatically from java script. I am having the above code:
    var eventSource = actionEvent.getSource();
    var button = eventSource.findComponent("cmi2");
    where cmi2 is a menu button.

    on button I can't use click() function since it is not found on this object.
    How can I force in ADF a click on a button from javascript?

    Regards Corneliu

    ReplyDelete
  4. Hi again,

    Finaly I succeded to call click button programaticaly from js.
    I used:
    var partialSubmit = true;
    AdfActionEvent.queue(button, partialSubmit);

    Regards Corneliu

    ReplyDelete