Foundation
Project Documentation

Overview

AJAX (Asynchronous-Javascript-And-XMLHttp) is a widespread technique in web applications today. Trinidad makes AJAX easy by building it into its components and giving you many ways to request AJAX updates of non-AJAX components. Trinidad calls this "Partial Page Rendering", or PPR for short.

partialSubmit and partialTriggers

The simplest example of PPR in Trinidad is the partialSubmit property on the Trinidad commandLink and commandButton components. When set to true, the ActionEvents delivered by these components will happen by an AJAX event.

  <tr:commandButton text="Do Something"
                       partialSubmit="true"
                       actionListener="#{myBean.doSomething}"/>

This will send a notification to the server, but will not change any content in the page. To do that, you have to tell Trinidad what needs to be redrawn in response to this. The easiest way to do this is with the partialTriggers property, which connects components that must be repainted with the component that is the reason for them to repaint.

This is a two-step process. First, make sure the source (here, a commandButton) has an ID. Second, set the partialTriggers attribute on each component that has to be repainted to that ID:

  <tr:commandButton text="Do Something"
                       id="myButton"
                       partialSubmit="true"
                       actionListener="#{myBean.doSomething}"/>

  <-- repaint the outputText any time 'myButton' has an event -->
  <tr:outputText value="#{myBean.textValue}"
                    partialTriggers="myButton"/>


public void doSomething(ActionEvent event)
{
  // Change the text value
  this.textValue = "A new value";
}

We might also want to disable myButton in response to its own click. For that, just mark it as its own partialTrigger:

  <tr:commandButton text="Do Something"
                       id="myButton"
                       partialSubmit="true"
                       partialTriggers="myButton"
                       actionListener="#{myBean.doSomething}"/>

  <-- repaint the outputText any time 'myButton' has an event -->
  <tr:outputText value="#{myBean.textValue}"
                    partialTriggers="myButton"/>


public void doSomething(ActionEvent event)
{
  ((CoreCommandButton) event.getSource()).setDisabled(true);
  // Change the text value
  this.textValue = "A new value";
}

All these examples are with action events, but PPR is not restricted to action events by any means. partialTriggers will be fired if any server-side event is queued for the triggering component - ValueChangeEvent, SortEvent, anything. partialSubmit itself only happens to exist for ActionEvent components, though. (See below for other component events.)

partialTriggers: more details

It's possible to have a component that is affected by multiple triggers (hence, the attribute name "partialTriggers", not just "partialTrigger"). For this, just separate each ID with a space:

  <tr:outputText value="#{myBean.textValue}"
                    partialTriggers="myButton1 myLink2"/>

Also, if you've got JSF NamingContainers (e.g., f:subview) between the trigger and its target, you'll need to incorporate that into the partialTriggers definition. The syntax is:

  • If you need to go down through a naming container to get to the trigger, include the naming container's ID with a colon (e.g., partialTriggers="theSubform:theLink"
  • If you need to start at the root of the page to get the trigger component, start with a single colon (e.g., partialTriggers=":someRootComponent"
  • If you need to go up and out of a naming container to get the trigger component, start with two colons. (e.g., partialTriggers="::someComponentOutsideNamingContainer") This includes the current component if the component is a NamingContainer. (e.g., tr:table partialTriggers="::mySiblingComponent" pops out of the table to get to the sibling)
  • If you need to go up and out of two naming containers to get the trigger component, start with three colons. To pop out of three naming containers, start with four colons, and so on. (e.g., partialTriggers=":::someOtherComponent" pops out of two naming containers)
Here are some examples:


  <f:view>
    <tr:document title="Relative partialTriggers Test">
      <tr:form>
        <f:subview id="wrapperAroundEverything">
          <tr:panelGroupLayout layout="vertical">
            <!-- Button 1 is the component that all of the other buttons have partialTriggers
                 for. -->
            <f:subview id="button1WrapperA">
              <f:subview id="button1WrapperB">
                <tr:commandButton
                  partialSubmit="true"
                  id="button1"
                  text="button 1 (click me then view errors in server console)"/>
              </f:subview>
            </f:subview>

            <!-- Button 2's partialTrigger starts with ':::'. 
            Two colons to pop it out of button2WrapperB, another colon to pop it out of buttons2WrapperA.
            commandButton is not a NamingContainer -->
            <f:subview id="button2WrapperA">
              <f:subview id="button2WrapperB">
                <tr:commandButton
                  partialTriggers=":::button1WrapperA:button1WrapperB:button1"
                  partialSubmit="true"
                  id="button2"
                  text="button 2"/>
              </f:subview>
            </f:subview>
        ....
            <!-- table is a NamingContainer. Two colons pop out of the table.
            Two more to pop it out of the two subviews.
            -->
            <f:subview id="table7WrapperA">
              <f:subview id="table7WrapperB">
                <tr:panelGroupLayout layout="vertical">
                  <tr:table
                    value="#{facesContext.externalContext.requestMap.cookies}"
                    var="row"
                    partialTriggers="::::button1WrapperA:button1WrapperB:button1"
                    id="table7">
                    <tr:column headerText="Column">
                      <tr:outputText value="#{row.name}"/>
                    </tr:column>
                  </tr:table>
                </tr:panelGroupLayout>
              </f:subview>
            </f:subview>
            

Finally, a cool thing about partialTriggers when you're inside a table row. If an event in one column needs to repaint a component in a different column, partialTriggers works great, no code required:

  <tr:column>
    <tr:outputText value="#{row.textValue}"
                      partialTriggers="myLink"/>
  </tr:column>

  <tr:column>
    <tr:commandLink text="Increment Value"
                       id="myLink"
                       partialSubmit="true"
                       actionListener="#{myBean.incrementRowVal}"/>
  </tr:column>
This works in h:dataTable, tr:table, tr:tree or tr:treeTable, the Facelets ui:repeat, etc. However, if you need to redraw a component in a different row, then you'll need to write some Java code (see RequestContext.addPartialTarget(), below).

PPR and 'rendered'

There's one important limitation of PPR that needs to be mentioned up front. You can use PPR to modify just about any property on a component. However, you can't use PPR to directly modify the "rendered" property of a component. If you need to do this, you either have to:

  • Put partialTriggers on the parent of the component where "rendered" is changing - which might require that you wrap the component in something simple like a panelGroupLayout.
  • Use an alternative CSS technique like adding "display:none" to the inlineStyle
See the Implementation Notes section for the reason for this limitation.

autoSubmit

The next most common use of PPR is the autoSubmit attribute. This is supported by all Trinidad input components, and will result in an AJAX request any time the value of the field changes. To minimize AJAX chattiness, autoSubmit on inputText will only fire when you've changed the value and tabbed out of the field. In the following example, a button will automatically become enabled when the field's quantity is greater than zero.

  <tr:inputText value="#{myBean.quantity}" autoSubmit="true"
                   id="quantity"/>

  <tr:commandButton text="Put One Back"
                       disabled="#{myBean.quantity le 0}"
                       partialTriggers="quantity"
                       actionListener="#{myBean.putOneBack}"/>

autoSubmit is also supported on the table and treeTable components. For these, it identifies whether changes in the table selection should fire an AJAX request (and notify the selectionListener, if you have one.)

Built-in PPR

Many components in Trinidad automatically use PPR without any configuration required. As of Trinidad 1.0.2, this list is:

  • table/treeTable/selectRangeChoicebar: paging through rows
  • table/treeTable: sorting
  • showDetail/showDetailHeader/table/tree/treeTable: expanding or collapsing content
  • panelTabbed/panelAccordion: showing tabs or panes
  • poll: polling
  • chooseDate: paging through months
  • Dialog framework: all return events from dialogs

Polling

The poll component provides a trivial way to set up a heartbeat or request regular updates on a page as needed. Poll events can be set to be delivered at any required interval, and multiple poll components with different intervals can be specified.

tr:statusIndicator

The statusIndicator component shows when an AJAX request is busy. It can be placed anywhere on a page - or in multiple locations, if you really need it. You can use skinning to replace the built-in icons - override both of the "af|statusIndicator::busy-icon" and "af|statusIndicator::ready-icon" keys in your skin.

Using RequestContext

At times, you may not want to or be able to use partialTriggers to identify components. For example:

  • You may not know which component needs to be updated until you process on the server.
  • You may only want to update a component in some cases, not on all updates to the value. For example, in the "Quantity" example above, the button will be repainted every time the quantity changes, not just if the state really changed.
  • Updating a row of a table other than the one where the event happened cannot be done with partialTriggers

For these cases (and any others you come up with), you can always programatically force a component to be repainted with RequestContext.addPartialTarget():

  if (_needToRepaint())
  {
    // Repaint another component programatically
    RequestContext rc = RequestContext.getCurrentInstance();
    rc.addPartialTarget(anotherComponent);
  }
  if (_needToRepaintAParticularRow())
  {
    // Repaint a component on a row programatically
    RequestContext rc = RequestContext.getCurrentInstance();
    Object oldRowKey = table.getRowKey();
    table.setRowIndex(rowToRepaint);
    rc.addPartialTarget(componentWithinRow);
    
    // Restore the row key
    table.setRowKey(oldRowKey);
  }

If you call this method outside of a partial-page request, no harm done - it will just be ignored, since the whole page is being repainted anyway. Speaking of that: if you want to find out if this is a partial-page request, call isPartialRequest(facesContext) on the RequestContext.

Finally, if you're implementing a custom component, you aren't extending the Trinidad UIXComponentBase base class or one of its subclasses, and you want to support partialTriggers, you'll need to use addPartialTriggerListeners(). If you deliver events, and you want to support being the target of partialTriggers, you'll need to use partialUpdateNotify().

  // This override is needed to support a partialTriggers attribute
  public void decode(FacesContext context)
  {
    // Find all the partialTriggers and save on the request context
    Object triggers = getAttributes().get("partialTriggers");
    if (triggers instanceof String[])
    {
      RequestContext rc = RequestContext.getCurrentInstance();
      if (rc != null)
        rc.addPartialTriggerListeners(this, (String[]) triggers);
    }

    super.decode(context);
  }

  // This override is needed if you want to be the trigger 
  // for another component's partialTriggers
  public void broadcast(FacesEvent event)
    throws AbortProcessingException
  {
    super.broadcast(event);
    RequestContext rc = RequestContext.getCurrentInstance();
    if (rc != null)
      rc.partialUpdateNotify(this);
  }

Javascript APIs for PPR

If you're writing a custom component, you may want to be able to send a PPR request from the client. Or, as a page author, you may want to add custom PPR behavior. Trinidad provides some hooks for this:

Sending a PPR request

Send a PPR request with the following invocation:

/**
 * Post the form for partial postback.  Supports both standard AJAX
 * posts and, for multipart/form posts, IFRAME-based transmission.
 * @param actionForm{FormElement} the HTML form to post
 * @param params{Object} additional parameters to send
 * @param headerParams{Object} HTTP headers to include (ignored if 
 *   the request must be a multipart/form post)
 */
  TrPage.getInstance().sendPartialFormPost(
     theForm,
     {param1: "value1", param2: "value2"},
     {header1: "headerValue1", header2: "headerValue2"}); 
This will automatically gather up all form fields on the page and include their values with the postback. In addition, it will pass any parameters along with this postback. The headers are completely optional, and can be omitted. The parameters are also optional, but usually you'll want at least one. If the content of the form happens to include a non-empty file upload component (input type="file"), the request will automatically switch to an alternative IFRAME-based method that supports file upload, without any required change in your code on the server or client.

Sending an AJAX request

If you wish to send an AJAX request on your own, without posting back to the server or going through the JSF lifecycle, use the RequestQueue object off of the Page object:

  // Get the shared request queue
  var queue = TrPage.getInstance().getRequestQueue();

/**
* Performs Asynchronous XML HTTP Request with the Server
* @param context    any object that is sent back to the callback when the request 
*  is complete. This object can be null.
* @param method   Javascript method
* @param actionURL   the url to send the request to
* @param headerParams  Option HTTP header parameters to attach to the request
* @param content     the content of the Asynchronous XML HTTP Post
*/
  // Send an AJAX request to example.org with one query parameter,
  // and no headers or content.  Call myCompletionMethod with "this"
  // set up correctly.
  queue.sendRequest(this, this.myCompletionMethod,
             "http://example.org?foo=bar");
The completion method will be called with an instance of TrXMLRequestEvent, which supports the following methods:
  • getStatus(): returns one of TrXMLRequestEvent.STATUS_QUEUED, .STATUS_SEND_BEFORE, .STATUS_SEND_AFTER, and .STATUS_COMPLETE
  • getResponseXML(): returns the XML document
  • getResponseText(): returns the text of the reply
  • getResponseStatusCode(): returns the HTTP status code
  • getResponseHeader(): returns an HTTP header
  • getResponseContentType(): returns the content type of the response

Monitoring AJAX requests

If you want to be notified when the client is busy waiting for an AJAX response, add a "state change listener" to the request queue:

    TrPage.getInstance().getRequestQueue().addStateChangeListener(myCallback);
Your callback will be passed a single parameter, which will be either TrRequestQueue.STATE_READY or TrRequestQueue.STATE_BUSY, depending on the current state of the request queue. The statusIndicator component uses exactly this mechanism to know when to start and stop spinning its indicator. You can optionally pass a second parameter to addStateChangeListener() for the "this" you want active when your callback is notified.

Trinidad also lets you get notified when DOM is replaced after an AJAX response by adding a "DOM replace listener" on the page:

    function myCallback(oldDom, newDom) { ... }

    TrPage.getInstance().addDomReplaceListener(myCallback);
The callback is passed two parameters: the old DOM element and the new DOM element that is replacing it. This callback is invoked after the replacement has happened. As with addStateChangeListener(), a second argument can be passed if you need a "this" to be active when your callback is notified.

Coming Features

The following features are planned enhancements:

  • Optimized postback lifecycle (at least in JSF 1.2)
  • Support for explicitly requesting PPR of component from client

Implementation Notes

Trinidad uses a ResponseWriter decorator to catch sections of the page that needs to be replaced. Specifically, it looks for calls to ReponseWriter.startElement() to extract those components that need to be redrawn. Then, on the client, IDs are used to match up new content with DOM already in the page. As a result, there are the following requirements and limitations:

  • Renderers must pass their UIComponent to ResponseWriter.startElement(), at least for any root elements in their content.
  • Renderers must write out a unique ID on any root element.
  • We cannot directly toggle "rendered" via PPR. When rendered="false", no DOM content is available. So, for instance, when going from "false" to "true", there's no DOM in the page to know where to insert the new content. Nor is there any content to replace the existing content when toggling from "true" to "false".
Scripts that are rendered during PPR - both libraries and inline scripts - will automatically be executed once the content is loaded. This includes scripts added using the ExtendedRenderKitService's addScript() method. Script libraries will only be loaded once per page - either from the initial page's contents, or on the first PPR request that loads that library.