The Apache MyFaces Trinidad JSF implementation has a built in mechanism for AJAX style functionality, called Partial Page Rendering (or PPR). It basically entails allowing input components – such as InputText, SelectOneChoice and SelectManyCheckbox – and action components – such as buttons and action links – to initiate background, asynchronous communications with the server. This AJAX like communication is started off when either the value in the input component changes or the action component is activated (1) by posting the current contents of the data form being posted to the server (2). The normal JSF lifecycle is executed (3) on the server and the normal response is rendered to the client (4).
The response is received on the client in a hidden IFRAME, processed by Trinidad JavaScript libraries (5). Depending on which component started the PPR cycle and which components have registered themselves as ‘refreshable in case of PPR caused by that component’, some parts of the HTML page maybe refreshed in the last stage of the PPR cycle (6).
Trinidad components specify through their partialTriggers attribute when they (and their children) should be refreshed in PPR cycles: the partialTriggers attributes contains a space separated list of component id values. All components whose change or activation should result in a refresh of a component should be present in that component’s partialTriggers attribute.
This works fine, it’s a bit like having components register themselves as EventListeners with a potential EventBroadcasters. And that is easy enough. However, only Trinidad’s JSF components have the partialTriggers attribute. Does that mean non-Trinidad components, such as JSF RI or MyFaces Tomahawk of JSF components from other libraries, cannot be part of the client side refresh at the end of a PPR cycle? Fortunately,
that is not the case. All JSF components, Trinidad or non-Trinidad, can be PPR-enabled. There are basically two approaches to bring this about.
Let’s look at an example.
This extremely simple form allows the user to enter a birthdate. Immediately after the user leaves this inputDate field, we want to have the current age going with that birthdate to be displayed. The age calculation logic is in the server, in the Customer bean. What we are after is this functionality:
- changing the birthdate triggers a PPR cycle
- the recalculated age is displayed in the client
- no client side programming should be required to have the client updated with the newly derived age
The challenge here, once you have mastered the Trinidad PPR mechanism, is the fact that we have used an h:outputText element – a plain JSF RI element – to display the age. The basic JSF source for this page would be:
<tr:inputDate label="Date of Birth" chooseId="idp1"
value="#{Customer.dateOfBirth}" id="dob"
autoSubmit="true"/>
<tr:chooseDate id="idp1" shortDesc="Choose Date"/>
<tr:spacer width="10" height="20"/>
<tr:panelLabelAndMessage label="Current Age" >
<h:outputText id="ageField" value="#{Customer.age}" />
</tr:panelLabelAndMessage>
However, we need the ageField to be refreshed whenever the dob inputDate is changed and for that we not only need the autoSubmit attribute set to true on dob but also the partialTriggers attribute including dob on ageField. And h:outputText does not have such an attribute, as that is a Trinidad-only attribute.
Like I said, we have two options to have the h:outputText ageField refresh in the PPR cycle:
- have the parent or one of the other ancestors for ageField partially triggered by dob; when a component is PPRefreshed, all of this children are refreshed as well.
- have the ageField component programmatically (server side) added to the set of PPR targets
The first approach is dead simple and will do in most situations. Sometimes, especially with table of tree layouts where we do not know beforehand what the ID values are going to be for all elements rendered in the client, the second approach is required.
1. Have PPR act on the Parent (Container)
The required ‘code’ for this approach:
<tr:panelLabelAndMessage label="Current Age" partialTriggers="dob" >
<h:outputText id="ageField" value="#{Customer.age}" />
</tr:panelLabelAndMessage>
we simple add dob to the panelLabelAndMessage parent for ageField, even though that component itself has no interest in the Date of Birth. When the dob is changed, PPR is executed and the panelLabelAndMessage and all its children, including ageField, are refreshed as well.
2. Programmatically add component to list of targets for the current PPR cycle
Inside the PPR cycle – in any of the normal JSF server side steps such as converters, validators, model logic – we can access the list of PPR targets: the elements that will be refreshed in the client from the response send back at the end of the PPR cycle. We can add any component for which we forgot to set the partialTriggers attribute or for which we could not set it because it has no partialTriggers attribute. In our extremely simple example, we do two things to make this work:
- add a valueChangeEvent listener to the dob inputDate component
- implement that valueChangeEvent listener to add the ageField component to the list of partial targets
The first step:
<tr:inputDate label="Date of Birth" chooseId="idp1"
value="#{Customer.dateOfBirth}" id="dob"
valueChangeListener="#{Customer.HandleDobChangeEvent}"
autoSubmit="true"/>
Here I have added the valueChangeListener attribute, refrerring to the HandleDobChangeEvent in the Customer managed bean. This method will be invoked in any PPR cycle processing a changed dob value (not even necessarily initiated by the dob field itself by the way).
The second step is the implementation of HandleDobChangeEvent in the Customer bean:
public void HandleDobChangeEvent(ValueChangeEvent valueChangeEvent) {
UIComponent ageField =
(valueChangeEvent.getComponent()).findComponent("ageField");
RequestContext.getCurrentInstance ().addPartialTarget(ageField);
}
And this is where it really happens: the ageField component is located in the server side View Tree, using the findComponent method on the component triggering the ValueChange event. Then, the addPartialTarget() method on the current instance of the RequestContext (a Trinidad specific implementation of FacesContext) is invoked, passing the ageField component to have it added to the list of partial targets, the components whose client side renditions should be refreshed at the end of the current PPR cycle.
I’m seeing this a couple months after it was published, but the primary benefits of switching to XMLHttpRequest from iframes are:
1. Better error handling (IFrames do a very poor job of letting you know when a request fails)
2. Far better (and simpler) handling of Javascript embedded in PPR requests
3. Finally, a public API for submitting PPR requests
4. A statusIndicator component for showing busy state
5. Much better opportunities for JS plug points (the statusIndicator uses one of these)
6. Simpler (and therefore more robust) code overall
7. Proper queueing of requests (instead of a fairly ad-hoc solution before)
It probably is a bit lighterweight, but that wasn’t the main goal, and I haven’t attempted to measure this. In fact, we do actually still use iframes when PPR needs to include a file upload (XMLHttp does not support file upload payloads), but it switches transparently.
All this is available in Trinidad 1.0.2, which will be released shortly, but has been available as nightly bulds for awhile.
I’ve also finally gotten off my rear and written documentation for Trinidad PPR: see http://myfaces.apache.org/trinidad/devguide/ppr.html
Seb, You are right and an XmlHttpRequest Object based implementation can hopefully be made leaner and little bit more intelligent as to what to render to the browser in a PPR request – not the whole page but just the updated component (‘s attribute values) ideally. Lucas
Hi,
Indeed, interesting things to know about PPR becoming XmlHttpRequest…
Ric, is there any plan for integrating XmlHttpRequest in ADF Faces before the release of the rich components that come with JDeveloper 11g ?
Lucas, one thing I think is really different is the fact that AJAX with XmlHttpRequest can be true asynchronous. It seems that the current implementation of PPR in ADF Faces leads to some weird behaviors … Am I wrong ?
Apart this detail, I agree with you, I am happy the way PPR works right now.
Seb.
Ric,
Thanks for your reaction. Interesting to see that development., When will it be available in the regular Trinidad builds?
One remark though: why do you only consider frameworks using the XmlHtppRequest Object to be “true AJAX’? As far as I am concerned what matters most is the effect we achieve: interactive, responsive, appealing, productive applications. And Asynchronous communication between client and server is an important means to that end. And the XmlHtppRequest object is just one way to achieve that asynchronous background communication, just as the IFRAME is a mechanism. I have never considered PPR in either ADF Faces (or UIX) or Trinidad to be inferior to other AJAX solutions because of the fact that it does not use the XmlHttpRequest object.
If this new implementation is more light weight (faster, less network or server load) or easier to use (don’t think that will be the case though) I am happy. If it is the same with a different underlying implementation, I am not so much thrilled. I have never really understood the “true or proper AJAX” discussion.
Lucas
Lucas,
The architecture you describe is no longer invalid with the next release of Trinidad. Adam Winer just put the final changes intro Trinidad that removes the iFrame dependency from all components except for file upload. Trinidad now relies on the XmlHttpRequest object. Thus, Trinidad is now a true Ajax framework.
Regards,
Ric Smith
Principal Product Manager
Oracle JDeveloper & ADF