ADF Faces Rich Client – JavaScript Programming Nuggets
Last update: 21 - July- 2008
ADF Faces Rich Client in JDeveloper 11 is a JavaServer Faces UI component set that helps developers to build Ajax applications the smart way without having to dig too much into JavaScript client programming matters.
However, a good framework leaves you the option and so does ADF Faces Rich Client components. You can use the client side framework to actively use JavaScript to raise events or manipulate content. Its the framework itself that provides you hooks and methods for almost everything you can do on the server to do it on the client as well. Of course there are fine lines of best practices not to cross.
ADF Faces RC JavaScript client architecture
JavaScript is not Java! Surprised ? Though JavaScript has Java in its name, it has nothing to do with Java, and in fact doesn't even from the same company. For many years JavaScript had been banned from enterprise client application development because of alleged security risks.
Ajax however made it possible to bring JavaScript back into spotlight, without the security concerns being lifted. As it seems the web developer community is more feature driven than security paranoid and so they happily waved away the security concerns. I don't go into this discussion ,but in fact I agree that you can relax on this matter. Not on security in general, but on the possibility of a widened attack surfaced through the use of JavaScript in Ajax, compared to traditional web applications. Stay on your security watch towers, but don't mind Ajax.
In Ajax, JavaScript is used for what only a few new it was possible and the majority wasn't even interested: Object oriented programming! Most of know JavaScript from alerts that popup in HTML forms or dependent list boxes that work so nicely on online registration forms. However, there is more to master in Ajax than this.
[... TBC ... ]
Adding JavaScript on a page
Before JavaScript can do its little wonders, you need to get it onto the page first. For this the following markup can be added below the af:document tag
-
-
-
/* if called from a client listener */
-
function name(event) { }
-
/* ordinary JavaScript function */
-
function name1 () { }
-
...
-
]]>
Best practices is to add JavaScript into the metaContainer facet of the af:document element
-
:view> -
:document> -
:facet name="metaContainer"> -
:group> -
function name(_event) {
-
...
-
}
-
]]>
-
:group>
-
:facet>
-
...
-
:document>
-
:view>
Basic Usecases
Basic usecases explain how to access a specific information from the ADF Faces Rich client framework. JavaScript savvy developers will be able to use the published the code snippets in their own ADF Faces RC applications.
Detecting the client browser agent
ADF Faces Rich Client framework allows developers to determine the version of the client agent used with an application. The agents that can be specified are
- AdfAgent.js
- AdfSafariAgent.js
- AdfOperaAgent.js
- AdfGecko18Agent.js
- AdfGeckoAgent.js
- AdfIEAgent.js
- AdfIE6Agent.js
-
:verbatim> -
-
-
function whichAgent(event) {
-
agent = AdfAgent.getAgent();
-
if(agent.constructor == AdfGecko18Agent){
-
alert("Mozilla");
-
}
-
if(agent.constructor == AdfIEAgent){
-
alert("IE");
-
}
-
}
-
]]>
-
:verbatim>
Getting the x/y position of a mouseclick on a component
If you know how to get the x/y coordinates of a mouseclick in JavaScript then you might be surprised to see that your favorite technique fails in ADF Faces RC. The reason for this is that if the event comes from a client listener in ADF Faces, the event object is one of ADF Faces RC
-
:view> -
:document> -
:verbatim> -
-
-
function handleMouseCoordinates(event) {
-
alert("x: "+event.getPageX()+" y: "+event.getPageY());
-
}
-
-
]]>
-
:verbatim>
-
:form> -
:commandButton text="My Click Button" icon="/images/jdev_cup.gif"> -
:clientListener method="handleMouseCoordinates" type="click"/> -
:commandButton>
-
:form>
-
:document>
-
:view>
Getting the keycode of the pressed key
The key event in ADF Faces is a subclass of AdfDomAdfAdfUIInputEvent and provides a cross browser functionality to obtain the pressed key e.g in an input field.
To call a JavaScript function - e.g. whichkey - you need to add an af:clientListener in the jspx page for the input component, e.g. af:textField
-
:inputText label="Autosuggest"> -
:clientListener method="whichkey" type="keyUp"/> -
:inputText>
This invokes the following JavaScript added to or linked from the page
-
-
-
function whichkey(event) {
-
alert(event.getKeyCode());
-
}
-
-
]]>
If the executing agent is Firefox, then the event class is "AdfGeckoAdfAdfUIInputEvent", if the agent is IE then the event class is "AdfIEUIInputEvent".
Accessing the content of a selected table row
Tables in ADF Faces Rich client components are stamped for performance reasons. This means that they are built on the server and don't use a client object for the table rows, columns and cells they contain. If a developer needs to access the content of the selected table cell on the client, or the content of selected table cells in a multi select table, then there is an option to do so, which includes the declarative creation of such objects. As a note of caution, keep in mind that any client object you create for an ADF Faces application will have its impact on performance when rendering the page. In Ajax, the penalty for two many client objects is download size.
When you create the ADF Faces table - e.g. by dragging and dropping a ViewObject from the ADF data control palette onto the JSF page - then you not only decide whether or not the table should be read-only, but also, if the table rows should become selectable. The latter is what usually you want. Note that if you missed setting the select option in the ADF binding editor, you can always define this in the component properties.
Making a table selectable doesn't notify any JavaScript on the client, which is why an additional component, the af:clientListener is required. The af:clientListener component dispatches the table selection to a JavaScript method whenever a pre-defined event - selection in this case - happens.
-
:table value="#{bindings.DepartmentsView1.collectionModel}" var="row" -
rows="#{bindings.DepartmentsView1.rangeSize}"
-
first="#{bindings.DepartmentsView1.rangeStart}"
-
emptyText="#{bindings.DepartmentsView1.viewable ? 'No rows yet.' : 'Access Denied.'}"
-
fetchSize="#{bindings.DepartmentsView1.rangeSize}"
-
selectedRowKeys="#{bindings.DepartmentsView1.collectionModel.selectedRow}"
-
selectionListener="#{bindings.DepartmentsView1.collectionModel.makeCurrent}"
-
rowSelection="single">
-
:clientListener method="showDepartmentsName" type="selection"/> -
...
The client listener in the above example calls a JavaScript method "showDepartmentsName" and passes in the event object, which is of AdfSelectionEvent type. The AdfSelectionEvent class hierarchy, in case you are interested, is
-
AdfPhasedEvent
-
|
-
|-AdfRowKeySetChangeEvent
-
|
-
|- AdfSelectionEvent
The AdfPhaseEvent's getSource() method, gives us a handle to the table component - as the source of the event. The table component has a method findComponent("name",rowKey) that allows us to search for a specific client component that it contains.
The client component in a read only table is those of a outputTextField, which represents a table cell. The client components, as mentioned before, don't exist by default and are created by setting the "clientComponent" property to true.
-
:column sortProperty="DepartmentName" sortable="false"> -
utputText value="#{row.DepartmentName}"
-
clientComponent="true"
-
id="dname"/>
-
:column>
The two lines - defining clientComponent = true and id=" dname" - make it possible that the department name is rendered in a client object, which is accessible by the name "dname".
To access the content of the selected dname table cell, we can now call
var cellhandler = _theTable.findComponent("dname",firstRowKey);
The full "showDepartmentsName" JavaScript is
-
:verbatim> -
-
-
function showDepartmentsName(event) {
-
var _theTable = event.getSource();
-
rwKeySet = event.getAddedSet();
-
for(rowKey in rwKeySet){
-
firstRowKey = rowKey;
-
// we are interested in the first hit, so break out here
-
break;
-
}
-
-
var cellhandler = _theTable.findComponent("dname",firstRowKey);
-
-
if (cellhandler != null){
-
alert (cellhandler.getValue());
-
}
-
}
-
]]>
-
:verbatim>
![]() |
Stop client to server event propagation
Its not always that application developers want the client to notify the server about a client event. In this usecase its a command button that - when pressed - shouldn't propagate the event to the server but call a client side JavaScript method. If this method then calls the server to propagate an event is up to the individual function called.
All events in ADF Faces RC are subclasses from the base event class, which provides a function cancel() that, if the event is compellable, stop server propagation.
-
:view> -
:verbatim> -
-
-
function showButtonPressed(event) {
-
alert("Client alert");
-
event.cancel();
-
}
-
]]>
-
:verbatim>
-
:document> -
:form> -
:commandButton text="Press me" -
action="#{callJavaScriptButton.commandButton_action}">
-
:clientListener method="showButtonPressed" type="action"/> -
:commandButton>
-
:form>
-
:document>
-
:view>
So while the code snippet above shows a JavaScript alert, the managed bean's action method associated to the button is not called all. But what do you save by doing this ? well, you save a network round trip, which is a good thin to do if there is no need for the server to be notified if a change or event.
Note that not all ADF Faces components automatically notify the server about events. A ValueChange event for example requires a submit to happen before. Table selections for example also aren't propagated automatically and require a select listener to be attached. To check whether or not an event propagates to the server, you can use
-
if(event.propagatesToServer()){}
Calling JavaScript from a Managed Bean
A frequent request we got for ADF Faces in previous releases is for a method that allows developers to call JavaScript functions on a page from a managed bean. Trinidad implemented an API for this and because ADF Faces RC is based on Trinidad, this functionality now becomes available in ADF FAces as well
-
ExtendedRenderKitService service = Service.getRenderKitService(facesContext, ExtendedRenderKitService.class);
-
service.addScript(facesContext, "alert('foo');");
Note that calling a JavaScript method may fail if the command component uses PPR.
How to set a table column to read-only
The following code snippet sets/unsets a table column to be read only based on a double click onto the cell. Note the use of a clientListener on the table cell and its association with a JavaSCript function.
-
-
<![[CDATA
-
function mouseListener(event){
-
var comp = event.getSource();
-
var readOnly = comp.getReadOnly();
-
comp.setReadOnly(!readOnly);
-
}
-
]]>
-
>
-
:table> -
:column> -
:inputText value="text" readOnly="true"> -
:clientListener type="dblClick" method="mouseListener"/> -
:inputText>
-
:column>
-
:table>
Common client functionality usecases
Common client functionality usecases are those that explain application client tasks that use a combination of basic usecases. This section is good for JavaScript savvy developers and beginners alike and describes end-to-end usecases.
Strategies for launching a dialog from JavaScript
One of two options to launch a popup dialog is to use client side JavaScript. The other option - which should be your first choice whenever possible - is to add the af:showPopupBehavior element to the component you use to launch the dialog. However, in this blog entry, I want to cover strategies to find the popup component on the client using JavaScript.
To launch a dialog with JavaScript, you have two options: One option is to trigger the JavaScript function call that shows the popup through the use of a af:clientListener component. The other option is to have some JavaScript just calling into the page to get the dialog opened.
Option 1: using the af:clientListener
Option 1a
========
-
:commandButton text="Launch dialog" partialSubmit="true" id="launchButton"> -
:clientListener method="launchDialog" type="click"/> -
:commandButton>
Clicking the button with the mouse calls a JavaScript method launchDialog() on the page.
-
-
[CDATA[
-
-
function launchDialog(event) {
-
var popup = AdfPage.PAGE.findComponent("thisPopup");
-
var hints = {align:"before_end", alignId:"launchButton"};
-
popup.show(hints);
-
event.cancel();
-
}
-
]]>
-
The above script looks for a popup component "thisPopup" on the page and launches it. The search for the component starts from the root of the page. The hint properties are good to influence the position where the popup is displayed. In the above case, a button exists on the page that has the id set to "launchButton".
Option 1b
========
To improve the lookup performance of the popup component, you can start the search from the component that launches the popup. Usually the popup and the launching component are located close together on a page.
-
-
[CDATA[
-
-
function launchDialog(event) {
-
_button = event.getSource();
-
var popup = _button.findComponent("thisPopup");
-
var hints = {align:"before_end", alignId:"launchButton"};
-
popup.show(hints);
-
event.cancel();
-
}
-
]]>
-
The difference between this script and the previous script is that the two code lines
_button = event.getSource();
var popup = _button.findComponent("template1:thisPopup");
read the component source from the event - a button in this example - and then start the search on this component. As we will see later, pages are not pages but exist of one to many nested naming containers.
Note that in all te above examples, the event.cancel(); stops the click event from getting propergated any further.
Option 2: using some JavaScript
Using plain old JavaScript to launch a popup allows your application to respond to external events. E.g. you may have a simple XMLHttpRequest (XHR) to call out to a Web Service and upon an asynchronous callback want to launch a dialog. In this case you launch the popup as follows
-
-
[CDATA[
-
-
function launch() {
-
var popup = AdfPage.PAGE.findComponent("thisPopup");
-
var hints = {align:"before_end", alignId:"launchButton"};
-
popup.show(hints);
-
}
-
-
]]>
-
Note that the launch function does not have an event argument because there is no event passed from the ADF Faces RC framework. Still you use the ADF Faces RC framework to lookup the popup through AdfPage.PAGE.findComponent("thisPopup");
Finding a component in a naming container
Several components in ADF Faces are NamingContainer components: E.g. af:pageTemplate, af:subform, af:table, af:tree. You can look at naming containers like name spaces. So in my example page, I use a template that contains the button and the popup component
-
:pageTemplate id="template1" viewId="/simpleTemplate.jspx"> -
:facet name="left"> -
:panelBox text="PanelBox1"> -
:facet name="toolbar"/> -
:commandButton text="Launch dialog" partialSubmit="true" id="launchButton"> -
:clientListener method="launchDialog" type="click"/> -
:commandButton>
-
:panelBox>
-
:facet>
-
:facet name="right"> -
:popup id="thisPopup" clientComponent="true"> -
:commandButton text="commandButton 1" partialSubmit="true"/> -
:popup>
-
:facet>
-
:facet name="bottom"/> -
:pageTemplate>
Looking up the popup component with the code lines below just no longer works
var popup = AdfPage.PAGE.findComponent("thisPopup");
var hints = {align:"before_end", alignId:"launchButton"};
popup.show(hints);
As you see, the template has its ID property set to "template1". This gives a name to the naming container. It is worth to mention that a template doesn't have an id property set when you create a new JSF page based on it in JDeveloper. The reason for this is that an id is not required unless you want to use JavaScript code to lookup components. In this case the naming container id is a named reference that you need to mention in the component lookup code
-
-
[CDATA[
-
-
function launchDialog(event) {
-
var popup = AdfPage.PAGE.findComponent("template1:thisPopup");
-
var hints = {align:"before_end", alignId:"template1:launchButton"};
-
popup.show(hints);
-
event.cancel();
-
}
-
]]>
-
Note the use of template1:launchButton and template1:thisPopup, which indicates that the launch button and the popup both are within a template definition.
But there was another way to find and launch a popup - yes
-
-
[CDATA[
-
-
function launchDialog(event) {
-
_button = event.getSource();
-
var popup = _button.findComponent("thisPopup");
-
var hints = {align:"before_end", alignId:"template1:launchButton"};
-
popup.show(hints);
-
event.cancel();
-
}
-
]]>
-
This code still works even if a naming container is involved. However, it works for all components that are within the same naming container. Also notice that the template ID needs to be mentioned on the align property.
As a rule of thumb: Use
_button = event.getSource();
var popup = _button.findComponent("thisPopup");
whenever possible as it is a quicker search than AdfPage.PAGE.findComponent ("template1:thisPopup");
which always starts from the document root.
For plain old JavaScript calls, as mentioned by example of Ajax requesting a service, the code is
-
function launch() {
-
_button = event.getSource();
-
var popup = AdfPage.PAGE.findComponent("template1:thisPopup");
-
var hints = {align:"before_end", alignId:"template1:launchButton"};
-
popup.show(hints);
-
event.cancel();
-
}
One remaining hint
What if the popup is not part of a naming container but the launching component (button) is ? In this case
AdfPage.PAGE.findComponent("thisPopup");
works just fine to find it. However, if you prefer to start the search from the source that raised the event, then you can still use _button.findComponent("thisPopup"), but this then has to be in the form of
-
_button.findComponent("::thisPopup");
The double colon in this case indicates that the search starts at the root container
Adding a clientListener dynamically
The following code below adds a clientListener to a RichShowDetail item (a tab on a tab panel)
-
public add_listener() {
-
ClientListenerSet cls = new ClientListenerSet();
-
cls.addListener("disclosure","callMe");
-
tab1.setClientListeners(cls);
-
return null;
-
}
It is possible to add a clientListener dynamically in a running application using a managed bean. This code on a command action adds a client listener dynamically to invoke a JavaScript function "callMe" upon tab disclosure.
-
-
-
function callMe(event) {
-
alert("Hello");
-
}
-
]]>
Calling a managed bean method from JavaScript
Ajax applications don't live from the one-way communication of traditional web application, but allows bi-directional communication without requiring a full page load. As you have expected, ADF Faces RC supports this communication pattern as well. To call a managed bean method from JavaScript on the client, you use the af:serverListener
-
:serverListener type="jsServerListener" -
method="#{mangedBean.handlerMethod}"/>
The "jsServerListener" name is referenced from the JavaScript that initiates the server side communication. The managedBean.handlerMethod reference can be created using the EL editor in JDeveloper and calls a method with the following signature in the referenced managed bean
-
public void handlerMethod(ClientEvent event){
-
payload = event.getParameters().get("payload");
-
-
// add more code here
-
}
The payload is a parameter that alos you to pass data from the client to the server. The JavaScript that calls this method looks as follows
-
-
-
function callServer(event) {
-
_txtField = event.getSource();
-
AdfCustomEvent.queue(_txtField, "jsServerListener",{payload:_txtField.getSubmittedValue()}, true);
-
event.cancel();
-
}
-
]]>
The above example passes the value of a textfield as the payload to the server side method, which would be required e.g. to perform autosuggest type of functionality. To get a handle to the textfield, the callServer(event) method is called from a clientListener applied to the text field
-
:inputText id="mytxtfield" label="Send my content to the server"> -
:clientListener method="callServer" type="keyUp"/> -
:serverListener type="jsServerListener" -
method="#{mangedBean.handlerMethod}"/>
-
:inputText>
Accessing the value of a textfield
If the requirement is to read the value of a text field upon clicking on a command link then the following code will do this
-
:view> -
:document> -
:facet name="metaContainer"> -
:group> -
function readFromField(_event) {
-
var _txt = _event.getSource().findComponent('field1');
-
if(_txt != null)
-
_value = _txt.getValue();
-
alert('Text in field: '+_value);
-
event.cancel();
-
}
-
]]>
-
:group>
-
:facet>
-
:form> -
:inputText id="field1" label="Value to get" value="Hello World" -
clientComponent="true"/>
-
:commandLink text="Get It" partialSubmit="true"> -
:clientListener method="readFromField" type="action"/> -
:commandLink>
-
:form>
-
:document>
-
:view>
Worth to point out:
- The JavaScript is with in a af:group in the metaContainer facet of af:document. This is considered best practices
- The input text field must have the clientComponent property set to true. Otherwise you are not able to access the component
- the command link must have partialSubmit set to true to avoid a page re-load, which prevents JavaScript from working
Replacing highlighted text in an input text field
Though a similar usecase as the above, the caret position is not exposed on the ADF Faces RC API, which means that the browser DOM API must be used. The challenge here is to avoid too many assumption in the javaScript code about the structure of the generated HTML. The HTML that gets generated for a input text field is surrounded by a HTML table object and the JSF component client ID is assigned to this table, which means that a call to getElementById() is not an option to use.
-
:document> -
:form> -
:inputText label="Label 1" value="Hello" id="inputTxt1" clientComponent="true"/> -
:commandButton text="Add "World"" partialSubmit="true"> -
:clientListener method="completeInputText" type="action"/> -
:commandButton>
-
:form>
-
:facet name="metaContainer"> -
:group> -
-
function completeInputText(evt){
-
var txtField = AdfPage.PAGE.findComponent("inputTxt1");
-
var domElement = txtField.getPeer().getDomElement();
-
var txtFieldHtml = domElement.getElementsByTagName("input")[0];
-
-
var start = txtFieldHtml.selectionStart;
-
var end = txtFieldHtml.selectionEnd;
-
varNewString = txtFieldHtml.value.substr(0, start) + "world" + txtFieldHtml.value.substr(end, txtFieldHtml.value.length);
-
txtField.setValue(varNewString);
-
>
-
:group>
-
:facet>
-
:document>
The code snippet above uses the ADF Faces RC client framework for as long as possible to then use the DOM API just for grabbing the caret positions in the text field. Th sample above will replace any selected text in the text field with the string "world".
However, the code is customizable in that you can choose to dynamically paste the replacement value in, e.g. reading it from another field or variable. The access to the DOM element, the table, is exposed on the DhtmlBaseInput component, which is the peer component of the ADF Faces RC inputTextField. The only assumption made about the generated HTML output in ADF Faces RC is hat there is only one input field component within the surrounding table (which I think is a save bet).
Programmatically opening the calendar on an inputDate component
Not a common usecase, but there might be situations in which the developer likes to guide the user to use the calendar component instead of typing a date in manually. Of course, users could press the date icon on the right hand side of the inputComponent themselves, but it might be helpful to launch it within the program code. As with many scriots, the starting point is the clientListener added to the inputDate component.
-
:inputDate label="Label 1"> -
:clientListener method="openPopup" type="focus"/> -
:inputDate>
So whenever the inputDate field has the focus set, the calendar should be opened. In real life applications you may want to use a less obtrusive event like click, or double click ot to scare away the user.
The crux here is to find a generic piece of JavaScript that works with all inputDate components so that no context specific information needs to be maintained
-
:facet name="metaContainer"> -
:group> -
-
function handleAnnyoingPopup(evt){
-
src = evt.getSource();
-
popup = src.findComponent(""+AdfRichUIPeer.CreateSubId(src.getClientId(), AdfDhtmlInputDatePeer._POPUP_ID));
-
hints = {alignId:src.getClientId(), align:AdfRichPopup.ALIGN_END_AFTER};
-
popup.show(hints);
-
}
-
>
-
:group>
The code above gets a handle to the inputDate component from the event. It then derives the internal popup ID to then show the RichPopup component that holds the calendar. The hints are to mimic the exact same popup location that is used when the user clicks the inputDate button.