Apache My Faces Trinidad: dynamically refreshing Non Trinidad components in a Partial Page Refresh ("AJAX")

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).

Apache My Faces Trinidad: dynamically refreshing Non Trinidad components in a Partial Page Refresh ("AJAX") trinidadppr1 

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.

 Apache My Faces Trinidad: dynamically refreshing Non Trinidad components in a Partial Page Refresh ("AJAX") trinidadppr2

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:

  1. 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.
  2. 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.

5 Comments

  1. Adam Winer August 13, 2007
  2. Lucas Jellema June 18, 2007
  3. sebnoumea June 18, 2007
  4. Lucas Jellema June 17, 2007
  5. Ric Smith June 17, 2007