Tuesday 12 April 2011

A special event for ADF EMG members - world wide and virtual


I'm happy to announce that three key participants of the ADF EMG will be presenting alongside Oracle staff at the upcoming "OTN Virtual Developer Day - Rich Enterprise Applications" .... a virtual conference on all things "ADF" in early May, open to the general public.

Jean-Marc Desvaux, John Stegeman and Sten Vesterli will be presenting under the "Best Practices" stream on the following topics:

* ADF Security in a Project-Centric Environment - An ADF Case Study (Jean-Marc)
* The bit every one forgets: Unit testing your ADF applications with JUnit (John)
* Starting an Enterprise ADF project (Sten)

I need not remind members that these three gentlemen are well known in the ADF community for their volunteer efforts in writing blogs and presenting, writing OTN forum papers, answering OTN forum questions, and even an upcoming ADF book!

Overall what I like about this event is there's a good opportunity to pick and choose across 5 streams. As such depending on your experience and background, you can dip your toe into all sorts of different ADF topics, from basics to best practices, all from the comfort of you're own desk.

The online conference will be repeated in several time zones to ensure everyone gets a chance to attend:

N. America - Tuesday, May 3rd, 2011
08:00 a.m. – 12:30 p.m. US PT

Europe / Russia - Tuesday, May 10th, 2011
08:00 a.m. – 12:30 p.m. U.K. Time
9:00 a.m. – 1:30 p.m. CET

Central / South America - Tuesday, May 17th, 2011
08:00 a.m. – 12:30 p.m. US Eastern Time
09:00 a.m. – 1:30 p.m. Brazil time

Asia Pacific - Tuesday, May 24th, 2011
08:00 a.m. – 12:30 p.m. India Standard Time
10:30 a.m. – 02:30 p.m. Beijing Time

If you're interested in attending please follow this link to register.

Good luck to Jean-Marc, John and Sten, I look forward to hearing them present, all 3 always have valuable information to share.

Monday 4 April 2011

af:table – Restoring the basic browser copy functionality

One of the first things any computing user learns is how to select text with the mouse or keyboard, then copy & paste the selected text. This simple functionality is supported in most applications including browsers, making it a very familiar facility to all users.

There are specific scenarios under certain browsers with the af:table component in ADF Faces RC 11.1.1.4.0 (also verified in 11.1.1.2.0) where the ability to select and copy text is absent.

This post identifies the specific scenarios, as well as a potential workaround for Internet Explorer (only).

The Issue

The following picture from Internet Explorer 7 (also replicated in IE8) shows a simple af:table without its selectionListener and rowSelection properties set.

If you look closely you can see with my mouse I've selected and highlighted the "Hong" in "HongKong".

While I can't demonstrate it in a screenshot, if you were to right click on the selected text you would see IE's standard right mouse menu with options such as Copy.

Here we can see similar functionality in Firefox 3.6, with the "Kingdom" in "United Kingdom" selected:

And finally Chrome 10, the "Can" in "Canada" selected:

(Unfortunately I don't have access to Safari to test its behaviour)

In all of these browsers it would be a reasonable requirement that the user would want to be able to highlight text and copy it to some other application running on their desktop.

Let's revisit the same af:table within each browser, this time with the selectionListener and rowSelection properties set.

The following picture shows IE7 (also confirmed in IE8) where you can see the effects of the selectionListener and rowSelection properties, highlighting the selected row. But what you can't do with your mouse is select any text. This disables the user from copying any text from the table:

Yet oddly in Firefox 3.6 the user can do both:

And in Chrome 10 they can too:

The behaviour or lack of it in Internet Explorer is certainly undesirable, even more so as most corporate environments will choose IE as their preferred browser. However via bug 9830307 Oracle has noted that this is in fact by design, and Oracle is planning to remove the functionality from the other browsers too.

Solution

The following technique demonstrates a solution for Internet Explorer displaying an af:table with its selectionListener and rowSelector properties set.

The technique requires us to create our own Copy contextMenu option for the table, and then via a combination of JavaScript, af:clientListener and af:clientAttribute tags, we'll copy the user selected table cell's data to the clipboard.

Starting out this is the code for the page containing the af:table before any modifications:

xmlns:h="http://java.sun.com/jsf/html" xmlns:af="http://xmlns.oracle.com/adf/faces/rich">





rows="#{bindings.CountriesView1.rangeSize}"
emptyText="#{bindings.CountriesView1.viewable ? 'No data to display.' : 'Access Denied.'}"
fetchSize="#{bindings.CountriesView1.rangeSize}" rowBandingInterval="0"
selectedRowKeys="#{bindings.CountriesView1.collectionModel.selectedRow}"
selectionListener="#{bindings.CountriesView1.collectionModel.makeCurrent}" rowSelection="single"
id="t1" columnStretching="last">
headerText="#{bindings.CountriesView1.hints.CountryId.label}" id="c1">


headerText="#{bindings.CountriesView1.hints.CountryName.label}" id="c2">






We then extend the af:table with a contextMenu facet, which includes an af:clientListener:








For each column we wish to provide the copy functionality, we extend each by including an af:clientListener tag and af:clientAttribute tag as follows:
                     headerText="#{bindings.CountriesView1.hints.CountryId.label}" id="c1">




....note the use of the af:clientListener and af:clientAttribute tags *within* the af:outputText. Also note how the af:clientAttribute's value EL expression has been changed to match that of the parent af:outputText value.

From the code above, each af:clientListener makes a call to separate JavaScript functions captureTableFieldName() and copyMenu. We provide these in JavaScript attached to the page :

var globalLastVisitedField = null;

/*
* Given the user clicking on a field in a table, captures the field name to be later used by the copyMenu
* function
*/
function captureTableFieldName() {
return function (evt) {
evt.cancel();
globalLastVisitedField = evt.getSource();
}
}
/*
* Function referenced from the clientListener on the copy menu option
*/
function copyMenu(evt) {
// Copy the last visited field to the clipboard
if (globalLastVisitedField == null) {
alert("copyMenu() Error: No field could be identified to be in focus");
}
else if (navigator.appName != "Microsoft Internet Explorer") {
alert("Copy function is only supported in Microsoft Internet Explorer");
}
else {
var txt = globalLastVisitedField.getProperty("ItemValue");
window.clipboardData.setData('Text', "" + txt);
}
evt.cancel();
}
The complete code for the page is as follows:

xmlns:h="http://java.sun.com/jsf/html" xmlns:af="http://xmlns.oracle.com/adf/faces/rich">




var globalLastVisitedField = null;

/*
* Given the user clicking on a field in a table, captures the field name to be later used by the copyMenu
* function
*/
function captureTableFieldName() {
return function (evt) {
evt.cancel();
globalLastVisitedField = evt.getSource();
}
}
/*
* Function referenced from the clientListener on the copy menu option
*/
function copyMenu(evt) {
// Copy the last visited field to the clipboard
if (globalLastVisitedField == null) {
alert("copyMenu() Error: No field could be identified to be in focus");
}
else if (navigator.appName != "Microsoft Internet Explorer") {
alert("Copy function is only supported in Microsoft Internet Explorer");
}
else {
var txt = globalLastVisitedField.getProperty("ItemValue");
window.clipboardData.setData('Text', "" + txt);
}
evt.cancel();
}



rows="#{bindings.CountriesView1.rangeSize}"
emptyText="#{bindings.CountriesView1.viewable ? 'No data to display.' : 'Access Denied.'}"
fetchSize="#{bindings.CountriesView1.rangeSize}" rowBandingInterval="0"
selectedRowKeys="#{bindings.CountriesView1.collectionModel.selectedRow}"
selectionListener="#{bindings.CountriesView1.collectionModel.makeCurrent}" rowSelection="single"
id="t1" columnStretching="last">









headerText="#{bindings.CountriesView1.hints.CountryId.label}" id="c1">





headerText="#{bindings.CountriesView1.hints.CountryName.label}" id="c2">









How The Solution Works

When the user wants to invoke the Copy function, there's effectively two actions. First they right click the individual af:outputText rendered in a cell of the table. Second after the contextMenu appears as a result of the right click, the user then left clicks on the Copy af:commandMenuItem. The action of selecting the af:commandMenuItem obscures the table cell clicked. As such the two distinct operations require we handle them separately:

1) On the first right click we capture the name of the field. This is what the af:clientListener within the af:outputText field does, by calling captureTableFieldName() storing the field name as a JavaScript global to be retrieved later.

2) On the left click on the Copy menu option that appears in the contextMenu, the af:clientListener within the af:commandMenuItem calls the copyMenu JavaScript function.

The copyMenu function armed with the field name captured and stored in the JavaScript global from step 1, retrieves the relating "ItemValue" property which is the value from the af:clientAttribute tag within the af:outputText. It then copies the relating value to the Browser's clipboard.

Note the call to window.clipboardData.setData. This is the limiting code that makes this solution only useful in Internet Explorer. From research there isn't a simple solution available in other browsers. As such in this example we simply alert the user the functionality is only supported in IE.

Limitations

From a design time point of view, having to add the af:clientListener and af:clientAttribute to each column is certainly a pain. If anybody can think of a simpler solution your comments would be appreciated.

From a runtime point of view, as already explained the workaround is specific to Internet Explorer.

Though we haven't undertaken significant amounts of testing we've also found limitations in applying this to check boxes and date fields. In the case of check boxes we can't seem to derive the underlying value, and with the date field we sometimes get the date and time in the wrong format. There's probably solves for both of these issues but beyond the general technique described here.

As usual developers adopting this code should be careful to test in their own environments as it hasn't been rigorously tested in a production environment.

Credit

Thanks to Penny Cookson from SAGE Computing Services for the original solution.