UIX Developer's Guide
Go to Table of Contents
Contents
Go to previous page
Previous
Go to next page
Next

13. Partial Page Rendering

This chapter contains the following sections:

Adding Interactivity to UIX Pages

Web pages typically support a variety of actions, such as entering and submitting form data and navigating to different pages. Many web pages also support another type of action, which is to allow the user to make modifications to the contents of the web page itself without actually navigating to a different page. Some examples of such actions include:

All of these actions are similar in that they result in the same page being re-rendered in a slightly different state. Ideally, these changes should be implemented as seemlessly as possible, so that the user does not experience a loss of context which could distract from the task at hand.

Developers that want to add such behaviors to their web pages are often faced with a difficult decision. All of these actions can be implemented using a very simple solution: by refreshing the entire page in response to the user interaction. However easy, this solution is not always desirable. The full page refresh can be slow, giving the user the impression that the application is unresponsive. Another option is to implement such actions using JavaScript (or other client-side scripting technologies). This results in faster response times, at the expense of more complex, less portable code. JavaScript may be a good choice for simple actions, such as updating an image. However, for more complicated actions, such as scrolling through data in a table, writing custom JavaScript code can be a very challenging undertaking.

UIX provides another solution which avoids some of the drawbacks of the full page refresh and custom JavaScript solutions: partial page rendering. The UIX partial page rendering functionality provides the ability to re-render a limited portion of a page. As in the full page render solution, partial page rendering sends a request back to the application on the middle-tier to fetch the new contents. However, when partial page rendering is used to update the page, only the modified contents are sent back to the browser. UIX automatically merges the new contents back into the web page. The end result is that the page is updated without custom JavaScript code, and without the loss of context that typically occurs with a full page refresh.

How Partial Page Rendering Works

The partial page rendering process breaks down into three main areas: the partial page event, the partial page rendering pass, and the partial page replacement.

The partial page event is the request that the browser sends to the application to request new contents. Partial page events are very similar to their full page event equivalents. For example, when the user navigates to a new record set in a table bean, a goto event with a value event parameter is sent to the application regardless of whether the event is a partial or full page event. There are two important differences between partial and full page events. First, partial page events specify partial page rendering-specific event parameters which are not present on the full page event equivalents. For example, partial page events may include an event parameter which identifies the set of partial targets, or nodes that should be re-rendered. The second difference between partial page events an full page events is how the events are sent.

Unlike full page events, partial page events must be sent in a way which does not force the browser to reload the current page. To implement this capability, UIX partial page rendering uses a hidden inline frame (iframe) as a communication channel between the browser and the web application running on the middle-tier. Partial page events are sent by forcing a navigation in the hidden iframe, which in turns causes a request to be sent to the application on the middle-tier. Since the iframe is hidden, the process of sending a partial page event and rendering partial page contents can take place behind the scenes, without discarding the contents of the current page.

When the partial page event is received by the application, the application responds by determining the set of partial targets to render and performing the partial page rendering pass. The partial page rendering pass is similar to a full page rendering pass. In both cases, the UINode tree is traversed by calling render() on each node in the tree. However, in the partial page rendering case, only the contents generated by the partial targets are actually sent back to the browser. All other contents are dropped. So, although the scope of a partial page rendering pass and full page rendering pass are similar in the number of UINodes that are rendered, the partial page response is generally much smaller, since only the modified contents are sent back to the browser.

The final part of the partial page rendering process is the partial page replacement. When the browser receives the partial page response, the new contents for each partial target node are copied from the hidden iframe into the main browser window, replacing the existing contents for each target node. So, for example, in the table navigation case, rather than replacing the entire page, just the contents of the table itself are replaced. The browser reflows in response to the modifications, displaying the new contents to the user without fully re-rendering the entire page.

To recap, the sequence of steps which occur during a partial page render are:

  1. The initial page is rendered.
  2. The user performs some action which triggers a partial page render, for example clicking on a link or button.
  3. JavaScript event handlers provided by UIX force a navigation in a hidden iframe.
  4. The partial page event is sent to the application.
  5. The application determines whether the request is a partial page event and which partial target nodes to re-render.
  6. The partial page rendering pass is performed.
  7. The contents of the partial target nodes are sent back to the iframe in the browser.
  8. The partial page replacement is performed, at which point the new contents are copied from the iframe into the main browser window.
  9. The browser re-flows and displays the new content to the end user.

Note that the partial page rendering process requires no custom JavaScript code.

Supported Platforms

The partial page rendering implementation requires the ability to modify arbitrary content via the browser's DOM APIs. At the moment, partial page rendering is only supported on the following browsers:

On all other platforms, UIX automatically reverts to full page rendering. In the future, partial page rendering should be supported on a wider range of browsers, including IE 5.0. However, due to Netscape 4.x's limited DOM support, partial page rendering will not be supported on Netscape 4.x.

One other note: Since the client-side DOM modifications used by partial page rendering may cause problems for assitive technologies such as screen readers, partial page rendering is only used when the UIX accessibility mode is explicitly set to AccessibilityMode.INACCESSIBLE_MODE. It may be the case that partial page rendering could actually be beneficial to users that read UIX pages with screen readers, since these users also suffer from the loss of context that comes with a full page refresh. Further research is needed to determine whether partial page rendering can be used in a way which is compatible with assistive technologies. In the meantime, full page rendering is used for all accessibility modes other than the inaccessible mode. For more information on how applications can specify the accessibility mode for a particular user, see the section on setting the accessibility mode.

Enabling Partial Page Rendering

Set the Partial Render Mode

Four UIX components have been enhanced to leverage partial page rendering: the table, hideShow and the hideShowHeader, subTabLayout. Each of these components supports two new attributes: partialRenderMode and partialTargets. The partialRenderMode attribute controls whether or not partial page rendering is enabled for a particular component. This attribute can be set to one of three values: none, self or multiple. The default value is none, which indicates that partial page rendering is disabled. Setting the partialRenderMode attribute to self indicates that the component itself should be updated using partial page rendering. This is the most common case. The multiple mode is used in conjunction with the partialTargets attribute to specify that multiple partial targets should be re-rendered in response to each partial page event.

So, for example, to indicate that a table should refresh itself using partial page rendering, the partialRenderMode can be set as follows:


  <table id="table-id" partialRenderMode="self">

To indicate that several dependent fields need to be updated any time the table is updated, use the multiple mode with the partialTargets attribute:


  <table id="table-id"
         partialRenderMode="multiple"
         partialTargets="dependent-id1 dependent-id2 dependent-id3">

Note that when specifying multiple partial targets, the ID of the component which fires the partial page events is implicitly included in the list of partial targets. Thus, in the above example, the ID of the table ("table-id") should not be included in the partialTargets value, since this ID is automatically considered to be a partial target.

In the case of the table component, setting the partialRenderMode to self or multiple turns on partial page rendering for the following actions:

For the hideShow and hideShowHeader components, partial page rendering is used to optimize both the hide and show actions. The subTabLayout component uses partial page rendering to update its contents when switching between tabs.

In addition to setting the partialRenderMode attribute, there are several other requirements that must be met in order to enable partial page rendering. These requirements are described in the following sections.

Use a Body Bean

A body bean must be present in the UINode hierarchy in order to enable partial page rendering. Any UIX page which leverages partial page rendering should have a structure similar to the following:


<page  xmlns="http://xmlns.oracle.com/uix/controller">
<content>
  <document xmlns="http://xmlns.oracle.com/uix/ui">
  <contents>

    <!-- The body bean is required -->
    <body>
    <contents>

      <!-- Components which fire partial page events can go here -->

    </contents>
    </body>

  </contents>
  </document>
</content>
</page>

If no body is present, partial page rendering will fail. For most uiXML pages, body will be automatically added for you - only pages that use document need an explicit body element. Java and UIX JSP developers, however, must explicitly use BodyBean and <uix:body> respectively.

Define IDs

Any node which might be re-rendered via partial page rendering must specify a value for the ID attribute. For example, the following code is not sufficient to enable partial page rendering for the hideShow bean:


  <hideShow partialRenderMode="self">

The ID attribute must also be specified as follows:


  <hideShow id="some-unique-id" partialRenderMode="self">

The ID serves several important purposes during the partial page rendering process. First, the partial page event identifies the set of partial targets to re-render by ID. During the partial page rendering traversal, the partial target nodes are located in the UINode tree by examining the ID of each node. Finally, when the partial page contents are sent back to the browser, the ID is used to locate the target node in the browser's DOM tree for the partial page replacement.

Set the Accessibility Mode

Partial page rendering is only enabled when it is known that the end user does not require accessibile content. This information is conveyed by the AccessibilityMode, which is specified via the Configuration.ACCESSIBILITY_MODE property. This property can be set with a single method call:


  ConfigurationImpl configImpl = ...
  configImpl.putProperty(Configuration.ACCESSIBILITY_MODE,
                         AccessibilityMode.INACCESSIBLE_MODE);

However, UIX Controller clients can specify this information declaratively via the uix-config.xml configuration file. Simply add a new configuration element as follows:


  <configuration name="noADA">
    <accessibility-mode>inaccessible</accessibility-mode>
  </configuration>

An override of UIXPageBroker.getConfigurationName() can then specify which configuration to use on a per-request basis:


  protected String getConfigurationName(
    BajaContext context,
    Page page)
  {
    if (_useInaccessibleConfiguration(context))
      return "noADA";
    return super.getConfigurationName(context, page);
  }

In this example, the _useInaccessibleConfiguration() method must determine whether or not to render inaccessible content for the particular request. The implementation of such a method would be application-specific. For example, an application which has a persistent store for user preferences could retrieve the user's preferred accessibility mode from a repository. Applications which do not store user preferences in a repository could use a cookie or an HttpSession value to track whether or not the user requires accessible content.

For more information on working with the Configuration API, see the Configuration chapter.

Identify Scripts which Generate Content

During partial page replacement, the newly generated contents are copied from the hidden iframe into the parent window of the iframe for display. If the new contents include any scripts, the scripts are explicitly executed in the parent window as well. This is necessary so that any functions or variables that are defined by the scripts will be available from the parent window. However, executing scripts in the parent window can cause problems if the scripts themselves generate content. Scripts which call document.write() or document.writeln() must not be executed in the parent window, as these calls will cause the contents of the parent window to be completely replaced.

Since UIX can not automatically determine whether or not a particular script generates content (that is, calls document.write() or document.writeln()), clients must explicitly identify such scripts. This can be done via the script bean's generatesContent boolean attribute. The generatesContent attribute defaults to false, which indicates that the script does not generate content. Clients that use partial page rendering should be sure to set the attribute to true for any scripts which call document.write() or document.writeln(), for example:


  <!-- Explicitly specify generatesContent="true", since 
          this script calls document.write() -->
  <script generatesContent="true">
    document.write("Hello, World!");
  </script>

Failure to identify scripts which generate content in this way can result in a partial page request replacing the entire page with just the contents generated by the script.

Responding to Partial Page Events

Once partial page rendering is enabled, components use the hidden iframe to send partial page events to the application. The UIX Controller automatically processes these events by rendering any partial target nodes that are specified by the event. Thus, UIX Controller clients are not required to make any changes in order to handle partial page events. Existing UIX Controller event handlers can be reused across both partial page and full page events. However, applications which use custom controllers must be enhanced to detect partial page events, determine the set of partial targets to render, and render the requested contents.

Creating the PartialPageContext

Applications which leverage partial page rendering must be able to distinguish between partial page and full page events. This responsibility belongs to the application's controller. Controllers can distinguish between partial page and full page events by calling the UIX PartialPageEventUtils.createPartialPageContext() method for each request (specifying either the HttpServletRequest object or a oracle.cabo.share.data.ServletRequestParameters object). This method examines the incoming request to determine whether or not the request is a partial page event. If the request is a partial page event, a PartialPageContext object is created and returned. The PartialPageContext object encapsulates any state associated with the partial page render. For example, the PartialPageContext defines the set of partial targets to render.

In the case of a full page event, createPartialPageContext() returns null. Thus, controllers can distinguish between full page and partial page events simply by checking the value returned by this method. In the full page event case, the controller can proceed with its normal processing. In the partial page case, the controller needs to perform additional work to ensure that only the desired partial targets are rendered.

Modifying the Set of Partial Targets

Each partial page event may specify one or more partial targets that need to be re-rendered. createPartialPageContext() automatically initializes the PartialPageContext with the set of requested targets. However, there are cases where the application may want to modify the set of partial targets to render when processing a particular event. For example, a page which contains both a table and a graph image may need to re-render both the table and the graph any time the table is updated. In this case, even though the partial page event may only specify that the table should be re-rendered, the application can intervene and explicitly modify the set of partial targets so that the graph is rendered as well.

The PartialPageContext includes methods for adding and removing partial targets: addPartialTarget() and removePartialTarget(). These methods can be called prior to rendering to modify the set of partial targets to render. For UIX Controller clients, the most obvious place where clients might want to call these methods is from a UIX Controller event handler. Clients can obtain the PartialPageContext from a UIX event handler by calling the PartialPageEventUtils.getPartialPageContext() method.

Performing the Partial Page Render

Once the set of partial targets has been determined and any application logic has been executed, it is finally time to respond to the partial page event by rendering the needed contents. Performing a partial page render is very similar to a full page render. The only difference is that the PartialPageContext must be set on the RenderingContext prior to rendering. This can be accomplished using the PartialPageUtils.setPartialPageContext() method. The UIX Controller automatically calls this method when processing partial page events, so UIX Controller clients do not need to perform any special work.

Another option for applications with custom controllers is to call the PartialPageUtils.renderPartialPage() convenience method, which both sets up the PartialPageContext on the RenderingContext and performs the render.

So, instead of:


  node.render(renderingContext);

The application should call:


  PartialPageUtils.renderPartialPage(renderingContext,
                                     node,
                                     partialPageContext);

Note that in both the partial page case and the full page case, the same root node of the UINode tree must be rendered. That is, even though the partial page rendering pass may only generate content for the limited set of partial target nodes, the entire UINode tree must be traversed. Also, as a convenience renderPartialPage() can be called with a null PartialPageContext, in which case a normal full page render will be performed.

Handling Errors

If errors occur while processing a partial page event, it may not be sufficient to simply render the requested partial target nodes. Applications may need to provide additional information about the error to the end user. Depending on when the error is detected, one of two options can be used to provide more information. If the error occurs before rendering has commenced, the application can display a new error page. For errors which occur during rendering, navigating to an error page is not an option. In these cases, it is possible to provide additional information by enhancing the set of partial targets to include nodes that were not specifically requested in the partial page event.

In order to render an error page in response to a partial page event, it is not sufficient to simply call UINode.render() on the UINode tree for the error page. Remember that partial page events are sent via a hidden inline frame. Any contents that are sent back to the browser are first displayed in the inline frame and must be explicitly copied into the parent document window in order to be made visible to the end user. So, calling UINode.render() on a new UINode tree may generate the desired error page contents, but the contents are not made visible. Instead, applications must call the PartialPageUtils.renderFullPage() method to render a new full page of content. The renderFullPage() method ensures that the entire contents of the UINode tree are sent back to the browser and are copied from the hidden iframe into the parent window for display.

UIX Controller-based applications can achieve the same result simply by returning a different Page from the EventHandler which handles the partial page event. In this case, the UIX Controller automatically calls renderFullPage() on behalf of the application.

Once rendering has started, it is too late to navigate to a new page to display error information. However, when errors are detected during the partial page rendering pass, it is possible to include error information in the partial page response by adding new partial targets. For example, if an error occurs while querying data to populate a partial target, the application can display an error message in a MessageBox bean even if the id of the MessageBox bean was not explicitly included in the set of requested partial targets. The PartialPageContext.addPartialTarget() method can be called to indicate that a new UINode, specified by its ID attribute value, should be added to the set of nodes that are rendered during the partial page rendering pass.

There are some requirements that must be met in order for the newly added partial targets to be successfully rendered and displayed. The most obvious requirement is that the ID attribute must be specified on any UINode that is a potential partial target. Also, the new partial target must be rendered after addPartialTarget() is called. In general this means that UINodes that are used to display error information should be placed strategically beneath any potentially problematic partial targets in the UINode tree. The final requirement is that the new partial target must have been rendered in some form on the previous full render of the current page. At a minimum, there must be some node in the client-side DOM tree with the ID of the error node, even if this node does not display any content. If a node with the required ID can not be found in the client-side DOM tree, the error message contents are not copied into the document.

Future Directions

So far we have seen how to enable partial page rendering for table, hideShow, hideShowHeader and subTabLayout components, but what about the other proposed uses that were described earlier in this chapter? The partial page rendering architecture is designed to support the ability to fire partial page events in response to arbitrary user actions, such as clicking on the link or button, or selecting an item from a choice box. However, as of yet UIX does not expose public APIs for firing such events. Future versions of UIX will provide such APIs, which should greatly expand the number of cases where partial page rendering can be used to optimize user interaction. In the meantime, we encourage UIX clients to take advantage of partial page rendering for the table and hideShow components.