ADF client-side architecture - Select All

A little while ago I got a very nice challenge: play around with the ADF client-side framework.

The customer had a table where users could multi-select via an extra column with checkboxes. Header of that column also contained a checkbox to select or deselect everything at once. For example:
example

It was implemented using autoSubmit and partialTriggers. Due to the roundtrip to the server the response time was low when having a lot of rows. To speed it up, I was asked to look if (de)selecting all rows could be done on the client.

Never having worked with that part of ADF yet, I started searching and quickly found the Oracle documentation, but actual examples to clarify some topics.. hmmm not so much.
The use case is quite specific but I thought it would still be nice to blog about it to be at least an example of some of the client-side functionality for other people in need.

The column

Let’s start with the column:

<af:table var="row" id="tab" ..>
  <af:column id="checkboxCol" width="25" align="center" displayIndex="0">
    <f:facet name="header">
	<af:selectBooleanCheckbox id="checkboxHeader" value="#{bindings.selectEmployeeValue.inputValue}">
	  <af:clientListener method="selectRows" type="click"/>
	  <af:clientAttribute name="tableLocator" value="::tab"/>
	  <af:clientAttribute name="checkboxLocator" value=":checkbox"/>
	</af:selectBooleanCheckbox>
    </f:facet>
    <af:selectBooleanCheckbox id="checkbox" value="#{row.selected}" clientComponent="true"/>
  </af:column>
  .
  .
</af:table>

When the user clicks on the checkbox in the header some javascript code will have to loop through the rows to change the checkboxes of each row.

For this I add a client listener to the header checkbox which will trigger on a click and calls some javascript method which in my case I called “selectRows”.
To make the javascript method generic I add the table and checkbox identifiers as attributes of the checkbox.
As identifiers I use the locators of the table and row checkboxes relative to the header checkbox; when we look at the javascript it will become clear why.
Last but not least, all row checkboxes get a clientComponent=”true” to make sure the client framework can access it.

The ADF client-side architecture wraps around the DOM model and can create javascript objects for faces components.
Javascript objects are created in multiple cases, among others when a component has a clientListener or the property clientComponent=”true”.
When looking for API information you can easily guess which API you need to search for. When you have an exposed RichSelectBooleanCheckbox component its javascript equivalent will be an AdfRichSelectBooleanCheckbox.

The JavaScript

Defining the client listener is comparable to other server listeners like an action listener.
When you add i.e. an action listener you will need an listener method with an ActionEvent parameter.
A client listener needs a javascript method with an event parameter as well:

  function selectRows(event) { 

    /* TODO
       retrieve the header checkbox from the event
       retrieve attributes
	   find the table
       for each row, find the checkbox to set value of
	   while(..){
		  ..
	   }
	*/
  }

The clientListener is added to the header checkbox making this checkbox the source of the event.
Via the event we can retrieve this AdfRichSelectBooleanCheckbox:

  function selectRows(event) {

    var checkbox=event.getSource(); 

    /* TODO
       retrieve attributes
	   find the table
       for each row, find the checkbox to set value of
	   while(..){
		  ..
	   }
	*/
  }

Once we have our checkbox object we can retrieve its attributes:

  function selectRows(event) {

    var checkbox=event.getSource(); 

    var checkboxLocator=checkbox.getProperty("checkboxLocator");
    var tableLocator=checkbox.getProperty("tableLocator");

    /* TODO
	   find the table
       for each row, find the checkbox to set value of
	   while(..){
		  ..
	   }
	*/
  }

The similarities in the client-side architecture are again convenient. The javascript objects have a findComponent method to search from a relative path. The root has a findComponentByAbsoluteId method to search for an object starting from the root.

The tableListener attribute contains the relative path to the table, reasoning from position of the header checkbox. This way we can do a findComponent on the header checkbox to easily get the AdfRichTable object:

  function selectRows(event) {

    var checkbox=event.getSource(); 

    var checkboxLocator=checkbox.getProperty("checkboxLocator");
    var tableLocator=checkbox.getProperty("tableLocator");

    var table=checkbox.findComponent(tableLocator);

    /* TODO
       for each row, find the checkbox to set value of
	   while(..){
		  ..
	   }
	*/
  }

So far so good, and nothing that can’t already be found when googling. But trying to reach the checkboxes of the rows was tricky.
To figure it out I started by looking at the HTML source of the ADF table with the checkbox of the first row:

<td style="width:25px" align="center" nowrap="" class="xxv">
  <span id="tab:0:checkbox" class="x1v">
    <span class="x2e">
      <span class="xgx">
         <input id="tab:0:checkbox::content" ...>
      </span>
    </span>
  </span>
</td>

The ID in the span made me do a couple of first attempts with the findComponent of the header checkbox like

checkbox.findComponent("::tab["+rowNumber+"]:checkbox");

Though this works the first time you open the page, it can break in certain situations.
Problem is that the first row not always gets 0 as a stamp etc. When you for instance have a master-form detail-table layout and navigate to the second master row, your detail row stamp continues to count from the last stamp of the first master row.
But also actions like fetching and refreshing changes the stamps.

After some more searching I found out that finding components by locator should solve the stamp issue.
This allows me to use [0] to address the first row and ADF will look up to which actual stamp this translates at that moment.
It resulted in some next attempts like

checkbox.findComponentByLocator("::tab["+rowNumber+"]:checkbox");

It turned out the table part of the expression was not correct yet.
Some more digging in the APIs to see how I could figure out the locator of the table made me stumble upon AdfUIComponent.getAbsoluteLocator() and a note in AdfUIComponent.findComponentByLocator that there was a method called AdfPage.findComponentByAbsoluteLocator().

Tying those last knots together finally gave a successful result:

  function selectRows(event) {

    var checkbox=event.getSource(); 

    var checkboxLocator=checkbox.getProperty("checkboxLocator");
    var tableLocator=checkbox.getProperty("tableLocator");

    var table=checkbox.findComponent(tableLocator);

    var nbRows=parseInt(table.getRows());
    var tableAbsLocator=table.getAbsoluteLocator();

    while(nbRows--){
      var rowCheckbox=AdfPage.PAGE.findComponentByAbsoluteLocator(tableAbsLocator+"["+nbRows+"]"+checkboxLocator);
      if(checkbox.getValue()!=rowCheckbox.getValue()){
        rowCheckbox.setValue(checkbox.getValue());
      }
    }
  }

I have created a quick little project for this.
I immediately admit it is quite ugly as I put the boolean for the checkboxes in my model, but it is just to show the client side selection.

Note

This solution only checks or unchecks all fetched rows. As non fetched rows are not in the HTML tree yet it is not possible to check them.
In my use case the query results were maxed to a certain amount which was also used as fetch size to solve this issue.

Resources

This blogpost on the AMIS technology blog – ADF client-side architecture Select All

Oracle white paper – ADF Design Fundamentals Using JavaScript in ADF Faces Rich Client Application

Oracle Web User Interface Developer’s Guide (11.1.1.5) – H3 Using ADF Faces Architecture

Oracle Web User Interface Developer’s Guide (11.1.1.5) – H5 Handling Events

Oracle JavaScript API Reference (11.1.1) - http://docs.oracle.com/cd/E21043_01/apirefs.1111/e12046/overview-summary.html

Little example project - ClientSideMultiSelect.zip

ADF application not working on Linux

When you find your self staring at the code of a JDeveloper 11/ ADF application wondering why o why the application worked on Windows, but does not on Linux, think about case-sensitivity.

It might be common knowledge to most of us that Linux is case-sensitive and Windows is not, but actively remembering this while developing and specifically naming/ refactoring package names is something different.

Example:
I had a custom data control based on a webservice called DataListService.
I generated a web service proxy in a package called “dataList.proxy” and placed custom entity classes in “dataList.entities”. On a custom proxy client with method getCompany() I generated a data control to use on a page in a selectOnChoice.

- com
 - client
   - app
     - model
       - dataList
         + proxy
         - entities
             Company.java
             Company.xml
           DLDataControl.java
           DLDataControl.xml

In the page definition I had a method iterator

<methodIterator Binds="Company.result" DataControl="DLDataControl"
                    BeanClass="com.client.app.model.datalist.entities.Company"
                    id="CompanyIterator"/>

It involved a lot of staring at every part of the application before realizing why the selectOnChoice did not get filled with values on the Linux VM.

Even if you always use lowercase package names (like a true java developer), remember this:
JDeveloper generates page definitions automatically in a package called pageDef. If the reference to a pagedefinition in the DataBindings.cpx is done with “pagedef. PageDef.xml” you have the same problem:

  <pageDefinitionUsages>
    <page id="com_client_app_view_testPageDef"
          path="com.client.app.view.pagedefs.testPageDef"/>
    <page id="com_client_app_view_secondtestPageDef"
          path="com.client.app.view.pageDefs.secondtestPageDef"/>

Weblogic deployment error

When trying to run/debug an application in JDeveloper and getting the error

The domain edit lock is owned by another session in exclusive mode

go to

http://127.0.0.1:7101/console

This is the administration console of your local Weblogic server you’re running when running/debugging an application in JDeveloper.

username and password are usually

weblogic/weblogic

First time you’ve tried to start the weblogic server with JDeveloper you’ve entered this information. If you really don’t know no more you can always remove the \jdeveloper\system\system11.1.1.0.31.51.88 folder of your JDeveloper installation.
This removes ALL settings, not just of the weblogic server.

When the administration console is started there is a block in the left upper corner called ‘Change Center’. Click on the button to activate changes and the lock will be gone.

sql*plus without tnsnames

just enter the following:

sqlplus username/password@//hostname:1521/sidname

 
 
(It can be placed in field “User Name” if working with the SQL*plus interface.)

invalid password in a combination of oracle 10g and 11g products

When it is the second time you have a “HUH am I crazy or what?” moment when dealing with an environment with a oracle 10g product talking to an 11g product, and you’re really glad you finally remember the reason of the first ‘HUH’ time, it’s time to write it down.

In the latest situation we had an oracle 10g database and an oracle 10 application server with a webservice deployed on it using a data source pointing to that 10g DB. No problem in this configuration yet.
The 10g DB was then migrated to 11g without any problems and only step left was switching the data source on the AS.
We were notified that the password was uppercase now as 11g has case-sensitive passwords.

So we changed the connection string of the data source, entered the password uppercase and applied changes. Testing the connection we got a ‘invalid password’ error though.
Probably we made a typo, so we retype the password. Still the error and while getting a bit unsure of our own typing skills we make sure caps lock is off and retype. Still..
To be sure, we restart the container and then test. Still.. We start and stop the container but still.. Totally confused we go to pl/sql developer, double-check tnsnames and try to connect: no problems.

I remembered then a situation of a couple of months a go where I had been going bonkers over a database link I had to make from a 10g to 11g DB.
I kept entering an uppercase password in the ‘edit database link’ screen of pl/sql developer checking different stuff and kept getting an invalid password when testing the altered db link.
As it turned out pl/sql developer actually creates this statement

ALTER DATABASE LINK databaselinkto11g
CONNECT TO schema11g IDENTIFIED BY SCHEMA11G;

But because this was executed on a 10g database the password got saved in lower case, but obviously you can’t see or check that..
Manually altering with quotes around the password did the trick in this situation.

Following this reasoning I expected the 10g AS to also use the lower case version of my upper case entered password.
Lower casing the password of the user on the 11g DB indeed solved the problem.

 
 
 
(Getting the AS or webservice to actually go to the changed connection in the data source also gave unexpected problematic behavior but that’s a different story..)

force ddl procedure to execute as calling user

Say you have a user DBAUSER with DBA rights. As DBAUSER you need to create a trigger in a different schema.
 
 
As DBAUSER you simply enter

CREATE OR REPLACE TRIGGER otherschema.my_trigger ..

without any problems.
 
 
You need to create the same trigger in a couple of different schema’s though so you decide to make a procedure to make the triggers without duplicating code

CREATE OR REPLACE PROCEDURE DBAUSER.create_trigger( schema IN VARCHAR2 )
IS
BEGIN
  EXECUTE IMMEDIATE 'CREATE OR REPLACE TRIGGER ' || schema || '.my_trigger............'

 
 
Looking at it, executing the following code as DBAUSER

BEGIN
  DBAUSER.create_trigger(schema => 'otherschema')
END;
/

shouldn’t give any problems either, right?
 
 
This is actually a situation a colleage handed me asking me to help him figure out why o why the last statement DID give an error being:

ORA-01031: insufficient privileges

He had been staring at it all afternoon trying to understand and I just stared some more.
 
 
Apparently Oracle didn’t get that the caller of the procedure also needed to be the executer of the command inside the procedure.
Solution:

CREATE OR REPLACE PROCEDURE DBAUSER.create_trigger( schema IN VARCHAR2 )
AUTHID CURRENT_USER
IS
BEGIN
  EXECUTE IMMEDIATE 'CREATE OR REPLACE TRIGGER ' || schema || '.my_trigger............'

 
This forces the DDL to be excuted ‘in’ the schema calling it.
So say you have a procedure in schema X to execute a DDL statement like creating a table (without schema prefix in the DDL command), you grant execute to public and execute it as schema Y. Without AUTHID the table is created in schema X, with AUTHID it is created in schema Y.
 
 
 
 
With thanks to my colleage T. Aalsma

%found after a bulk collect

Today I was reminded of a mistake I so often make:

OPEN  c_somecursor;
FETCH c_somecursor BULK COLLECT INTO t_somecursor;
l_found := c_somecursor%FOUND;
CLOSE c_somecursor;

IF l_found
THEN
 dbms_output.put_line('Jup, found something');

The title of this post should give anough hints where this goes wrong. Even if there are records fetched %FOUND does not give back TRUE.
This does work:

OPEN  c_somecursor;
FETCH c_somecursor BULK COLLECT INTO t_somecursor;
CLOSE c_somecursor;

IF t_somecursor.count > 0
THEN
 dbms_output.put_line('Jup, found something');

Thanks to a colleague of mine, Erik:
%FOUND only works with a BULK COLLECT if this BULK COLLECT is limited:

OPEN  c_somecursor;
FETCH c_somecursor BULK COLLECT INTO t_somecursor LIMIT 2;
l_found := c_somecursor%FOUND;
CLOSE c_somecursor;

IF l_found
THEN
 dbms_output.put_line('Jup, found something');

refreshing caller window

Probably not shocking to experienced Apex developers but this is a nice piece of code to discover:

onUnload="javascript:window.opener.doSubmit();"

in the On Load of a pop-up page will refresh the window that opened the pop-up.

Take a good look at the branches of the window that called the pop-up though to make sure the submit does what you want.
To make a specific branch to do something when the pop-up requests a submit of the caller window use

onUnload="javascript:window.opener.doSubmit('<SUBMITSTRING>');"

In the caller window make a branch with a condition ‘Request = Expression 1′ with <SUBMITSTRING> as Expression 1.

PPR in Apex using javascript

Thanks to a colleague, a very interesting article on Partial Page Rendering using javascript at
http://www.oracle-and-apex.com/question-how-to-refresh-an-apex-sql-report-from-javascript/: