Contents


Refreshing individual portlets and preferences using Single Portlet Refresh in WebSphere Portal V6.0.1

Comments

Over the past few years, Web 2.0 and Ajax (especially) have influenced the development of interactive Web applications. You can use Ajax to send asynchronous requests to a server without refreshing the full page. The result is very appealing to the end user because the Web application seems to react immediately, and the user's experience is similar to what he or she sees when using a desktop application.

The JSR 168 Portlet API is a server side API which you can use to write portlets for portal servers such as IBM® WebSphere® Portal. Because portlets are components embedded into a page, and multiple portlets are aggregated onto one page, each portlet has limited real estate on the page for its content. Each portlet is responsible for its own content only, and it is unaware of any other portlets on the page.

In a portal environment, using Ajax to refresh the page is more problematic than in a simple Web application because of the fact that multiple portlets are on a page. You must also handle issues such as navigational state in the portal environment.

The Single Portlet Refresh feature, introduced in WebSphere Portal Version 6.0.1, enables you to use Ajax inside of portlets to refresh portlet content. In particular, it provides a method which you can use to create URLs that address a single portlet window only. Submitting such a "single portlet refresh URL" will cause the portal server to only invoke the rendering of the addressed portlet window (as implied by the name "Single Portlet Refresh"). Consequently, the response will only contain the markup produced by the portlet; that is, it will not contain the markup generated by the theme or skin or other artifacts.

You can use the Single Portlet Refresh in use cases such as:

  • Refreshing only a specific part of the portlet’s markup using an asynchronous render request.
  • Updating portlet preferences using an asynchronous (action) request.

Previously, you could use Ajax in portlets only to access servers (for example, using servlets) to retrieve data.

This article introduces you to the new feature Single Portlet Refresh, tells you when and how to use it, and discusses its current limitations. In the next version of WebSphere Portal more powerful Web 2.0 features are offered and these will address the use cases discussed in this article. You can get a first glance at these new features in the WebSphere Portal Version 6.1 Open Beta (see Related topics for a link).

Existing Ajax portlet programming model

The current Ajax portlet programming model is based on servlets which are packaged with portlets. In this model, the portlet drives the main content and navigation, and the servlet acts as a proxy which retrieves additional data from any backend to display. For example, the backend could be a SAP system connector or a news feed such as one provided by CNN.

Figure 1. Existing Ajax portlet programming model based on servlets
Figure 1. Existing Ajax portlet programming model based on servlets
Figure 1. Existing Ajax portlet programming model based on servlets

Figure 1 shows the servlet-based Ajax programming model. The first bootstrap request from the browser goes through the Portal WAR, which addresses all portlets on the page and includes them into the markup. The gray box (Portlet) depicts one portlet on the page. Within the first bootstrap request the portlet accesses the HTTP Session and stores required data into it so that the servlet can use it at a later time. For example, this data could be the details about the connection to a SAP backend.

When the user interacts with the portlet on the client, an Ajax request is created; this request is depicted in Figure 1 as the green box. The Ajax request directly addresses the servlet and fetches either data or markup which is displayed in the browser. The servlet in the backend can now access the same HTTP Session as the portlet and read the shared data, such as connection details to a SAP backend.

When to use Single Portlet Refresh

Single Portlet Refresh enables additional use cases. While you can use the servlet-based Ajax programming model for most use cases, it lacks the ability to directly connect to the portlet to use certain capabilities such as portlet preferences. Use cases that include updating portlet preferences are perfect candidates for using Single Portlet Refresh.

You can also use the Single Portlet Refresh to update portlet markup; however, in this case, you need to use it with care. Because state changes of the portlet are no longer propagated to the portal environment (such as changing render parameters), unexpected back button behavior can result. (See the Limitations section).

Single Portlet Refresh is not part of the JSR 168 Portlet API (see Java Portlet Specification Version 1.0 in Related topics) standard; it is an extension of WebSphere Portal. It enables you to create URLs which address one specific portlet window, using only the Navigational State SPI (see Leveraging WebSphere Portal V6 Programming Model, Part 2: Advanced URL Generation in themes and portlets in Related topics).

If the portal receives a single portlet refresh request it calls the action and render phase of the addressed portlet. The response that it returns to the client contains the markup of the addressed portlet only; that is, it contains neither markup generated by the theme nor markup of other portlets on the same page. On the client, the portlet can use JavaScript to inject the received markup into the DOM that resides in the browser.

Important: In order to stay within a Single Portlet Refresh model, use the Navigational State SPI only to create single portlet refresh URLs during the render phase. If you use the JSR 168 Portlet API to create a URL instead, it addresses the full portal page and then the response would contain the markup for the entire page.

Limitations

While Single Portlet Refresh lets you significantly spice up the user's experience with your portlets, it has some limitations and drawbacks that you should be aware of when using it. These limitations are explained in the following subsections.

Single Portlet Refresh has a negative effect on the navigational state handling of portal and on all capabilities relying on navigational state, such as back button support and bookmarkability. Supporting these capabilities in a portal environment requires that the navigational state of the entire portal page be encoded into every URL on that page. In this context, navigational state includes portal-specific state such as the currently selected portal page plus the navigational state of every portlet, for example, portlet mode, window state, and render parameters.

Using a Single Portlet Refresh request results in a partial page response, and all the URLs that are not part of the refreshed markup fragment might become unaligned because they no longer address the correct navigational state. For example, if a submitted Single Portlet Refresh URL addresses a specific render parameter, this render parameter is not reflected in the URLs that already reside in the unchanged part of the DOM.

The implied limitations are:

  • In a full page refresh (either caused by a Browser refresh or when the user clicks into any other portlet on the page which does not make use of Single Portlet Refresh), the navigational state changes that occurred through interacting with the Single Portlet Refresh enabled portlet are lost.
  • The back/forward button behavior will be inconsistent with the user expectation because asynchronous requests are not considered in the browser history by default.

Inter-portlet communication

If you use Single Portlet Refresh to update only specific parts of the portlet markup, make sure that your portlet does not communicate or share data with other portlets on the page (for example, using the Property Broker infrastructure or a shared portlet session) in a way that would require a refresh of these other portlets. Otherwise, inconsistencies in the user interface would occur because the affected portlets are not refreshed until a full page refresh occurs.

WSRP

Because of the use of the Navigational State SPI, portlets that use Single Portlet Refresh can not be consumed through Web Services for Remote Portlets (WSRP).

IBM Portlet API

For legacy portlets implemented against the IBM Portlet API, consider the following limitations:

  • You must use simple portlet actions.
  • Do not use beginPage/endPage.

Session Suspend/Resume

WebSphere Portal does not consider Single Portlet Refresh requests for Session Suspend/Resume. All applied navigational state changes are not persisted when a session times out or is explicitly finished. Therefore, these navigational state changes cannot be restored, even if the user wants to resume to the navigational state of his or her previous session.

How to use Single Portlet Refresh

Now you see how to programmatically generate Single Portlet Refresh URLs using the Navigational State SPI and Portal Model SPI to implement use cases such as updating preferences or performing a partial portlet rendering.

For more information about the Navigational State SPI, see Leveraging WebSphere Portal V6 Programming Model, Part 2: Advanced URL Generation in themes and portlets in Related topics.

For details on the Model SPI, see the section "Model SPI Overview" in the in WebSphere Portal Information Center (also listed in Related topics).

Before discussing how to implement these use cases you need to know how to get access to the required SPIs.

Getting access to the required portlet services

To create Single Portlet Refresh URLs you need to use two portlet services:

  • PortletStateManagerService provides access to the Navigational State SPI from within a portlet. You can access it through JNDI using the JNDI name portletservice/com.ibm.portal.state.service.PortletStateManagerService

    Important: Portlets using this service can not be consumed using WSRP.

  • PortletModelProvider provides access to the portlet model from within a portlet. It is part of the Portal Model SPI. The JNDI name of this portlet service is portletservice/com.ibm.portal.portlet.service.model.PortletModelProvider. You need to use this service to access the portlet window of your portlet.

Each JNDI lookup returns a generic PortletServiceHome interface which offers a getPortletService(Class) method to get the concrete service. The retrieved service instance is valid for the lifetime of the portal. Therefore, perform the service retrieval in the init() method of the portlet and store the obtained service instances in portlet instance variables as shown in Listing 1.

Listing 1: Getting access to the needed SPIs
public class MyPortlet extends GenericPortlet {

  /** JNDI name needed to lookup the state manager service */
  private static final String JNDI_STATE_SERVICE =
    "portletservice/com.ibm.portal.state.service.PortletStateManagerService";

  /** JNDI name needed to lookup the portlet model provider */
  private static final String JNDI_MODEL_PROVIDER =
    "portletservice/com.ibm.portal.portlet.service.model.PortletModelProvider";

  /** portlet state manager service */
  protected PortletStateManagerService stateService;

  /** portlet model provider */
  protected PortletModelProvider modelProvider;

  /**
   * @see javax.portlet.GenericPortlet#init()
   */
  public void init() throws PortletException {
    super.init();
    try {
      final Context ctx = new InitialContext();
      // lookup the portlet state manager service
      PortletServiceHome psh = (PortletServiceHome) ctx.lookup(JNDI_STATE_SERVICE);
      stateService = (PortletStateManagerService) 
        psh.getPortletService(PortletStateManagerService.class);
      // lookup the portlet model provider
      psh = (PortletServiceHome) ctx.lookup(JNDI_MODEL_PROVIDER);
      modelProvider = (PortletModelProvider) 
        psh.getPortletService(PortletModelProvider.class);
    } catch (NamingException e) {
      throw new PortletException(e);
    }
  }

Creating Single Portlet Refresh URLs

The PortletStateManagerService provides a URLFactory which lets you create EngineURLs. An EngineURL represents a URL that contains navigational state.

A Single Portlet Refresh URL is a special EngineURL which contains a selection mapping that maps the selected page to the ObjectID or unique name of the portlet window that should be addressed. To set such a selection mapping, use the SelectionAccessorController. The provided ObjectID (or unique name respectively) must refer to the portlet window that can be obtained by the PortletModelProvider. The selection mapping tells the portal server to render the addressed portlet window only, instead of running the page rendering process.

Listing 2 shows how you can set such a selection mapping. The proposed pattern forms the basis for the concrete samples described in the following sections.

Listing 2: Creating a base URL addressing a single portlet window
/**
 * @see javax.portlet.GenericPortlet#doView(RenderRequest,RenderResponse)
 */
protected void doView(final RenderRequest request,
        final RenderResponse response) throws PortletException, IOException {
    response.setContentType(request.getResponseContentType());
    final PrintWriter writer = response.getWriter();
    try {
        // get the request-specific portlet state manager
        final PortletStateManager stateManager = 
            stateService.getPortletStateManager(request, response);
        // get the portlet window
        final PortletWindow portletWindow = 
            modelProvider.getCurrentPortletWindow(request);
        // get the URL factory
        final URLFactory urlFactory = stateManager.getURLFactory();
        final EngineURL url = urlFactory.newURL(null);
        // get the URL-specific state
        final StateHolderController state = url.getState();
        // make the URL a single portlet refresh URL
        setSelectionMapping(stateManager, portletWindow, state);
        // if needed add other aspects to the URL
        // ...
        // write the URL to the markup
        writer.print("<a href=\"");
        url.writeDispose(writer);
        writer.print("\">Single Portlet Refresh link</a><p/>");
        // dispose the objects that are no longer needed
        urlFactory.dispose();
        stateManager.dispose();
    } catch (StateException e) {
        throw new PortletException(e);
    } catch (ModelException e) {
        throw new PortletException(e);
    }

}
    
/**
 * Helper method which maps the currently selected page to the given portlet window.
 * Typically this method will operate on a URL-specific state holder. 
 */
protected void setSelectionMapping(final PortletStateManager stateManager,
    final PortletWindow window, final StateHolderController urlState)
    throws StateException {
    // get the selection accessor controller to set the selection mapping
    final SelectionAccessorFactory factory = (SelectionAccessorFactory) stateManager
            .getAccessorFactory(SelectionAccessorFactory.class);
    final SelectionAccessorController selCtrl = factory
            .getSelectionAccessorController(urlState);
    try {
        // set the mapping which maps the current state to our portlet window
        selCtrl.setSelectionMapping(selCtrl.getSelection(), window.getObjectID());
    } finally {
        // release the selection controller
        selCtrl.dispose();
    }
}

Render URLs

To refresh portlet markup fragments (using asynchronous HTTP GET requests for example), use render URLs. To identify the particular markup fragment you would like to address, use render parameters. Use the PortletAccessorController to set render parameters. If needed, you can also set a portlet mode or window state.

Listing 3 shows a helper method called setState() which you can use to set these aspects to the state of the created EngineURL. The passed parameter map must contain (String, String[])-mappings.

Listing 3: Method to set portlet mode, window state, and render parameters
/**
 * Helper method which sets an action into the given URL-specific state. 
 */
protected void setState(final PortletStateManager stateManager,
    final PortletWindow window, final StateHolderController urlState,
    final PortletMode mode, final WindowState winState, final Map params)
    throws StateException {
    // get the portlet accessor controller
    final PortletAccessorController portletCtrl = 
        stateManager.getPortletAccessorController(urlState);
    try {
        // set portlet mode (if any)
        if (mode != null) {
            portletCtrl.setPortletMode(mode);
        }
        // set window state (if any)
        if (winState != null) {
            portletCtrl.setWindowState(winState);
        }
        // set render /action parameters (if any)
        if (params != null && !params.isEmpty()) {
            portletCtrl.getParameters().putAll(params);
        }
    } finally {
        // release portlet accessor controller
        portletCtrl.dispose();
    }
}

Listing 4 illustrates the overall process of creating a Single Portlet Refresh render URL. It summarizes the key points made in this section. Use the writeDispose() or writeCopy() method of the EngineURL to embed the URL into the portlet markup (for example, into JavaScript).

Listing 4: Method to create a Single Portlet Refresh render URL
/**
 * Helper method which creates a new Single Portlet Refresh render URL.
 */
protected EngineURL createRenderURL(final PortletStateManager stateManager,
    final PortletWindow window, final Map renderParams, final PortletMode mode, 
    final WindowState winState) throws StateException {
    // get the URL factory
    final URLFactory urlFactory = stateManager.getURLFactory();
    // get a new EngineURL object from the factory
    final EngineURL url = urlFactory.newURL(null);
    // get the URL-specific state
    final StateHolderController state = url.getState();
    // make the URL a single portlet refresh URL
    setSelectionMapping(stateManager, window, state);
    // set the render state
    setState(stateManager, window, state, mode, winState, renderParams);
    // release the URL factory
    urlFactory.dispose();
    // return the URL
    return url;
}

Action URLs

To update portlet preferences, using an asychronous HTTP POST request for example, you need to make sure that your Single Portlet Refresh URL triggers the portlet action phase. Server-side state, such as portlet preferences, can only be changed in the portlet action phase. In other words, you must create an action URL, using the so-called PortletTargetAccessorController to add this aspect to your URL (see Listing 5).

Listing 5: Method to set a portlet action
/**
 * Helper method which sets an action into the given URL-specific state. 
 */
protected void setAction(final PortletStateManager stateManager,
    final PortletWindow window, final StateHolderController urlState)
    throws StateException {
    // get the target accessor controller to set the action
    final PortletTargetAccessorController targetCtrl = stateManager
            .getPortletTargetAccessorController(urlState);
    try {
        // set the action to our portlet window
        targetCtrl.setActionTarget(window.getObjectID());
    } finally {
        // release the target controller
        targetCtrl.dispose();
    }
}

If your portlet action requires any action parameters, use the PortletAccessorController to add the parameters to the state. You can reuse the helper method setState() from the previous section introduced for the partial portlet refresh. In the case of an action URL, the parameters passed to the setState()method are interpreted as portlet action parameters.

Listing 6 illustrates the overall process of creating a Single Portlet Refresh action URL to summarize the key points in this section. Use the writeDispose() or writeCopy()method of the EngineURL to embed the URL into the portlet markup (for example, into JavaScript).

On the client, you could use the created action URL to submit an asychronous request (for example, using XMLHttpRequest), which will cause the portal server to invoke the processAction() method of your portlet which, in turn, performs the preference update. The portal server returns the markup of the addressed portlet window. You can use it to dynamically update your DOM residing in the browser, using JavaScript. Alternatively, you could perform the entire markup update in the browser, using JavaScript, and simply ignore the markup delivered with the response.

Listing 6: Method to create a Single Portlet Refresh action URL
/**
 * Helper method which creates a new Single Portlet Refresh action URL.
 */
protected EngineURL createActionURL(final PortletStateManager stateManager,
    final PortletWindow window, final Map actionParams) throws StateException {
    // get the URL factory
    final URLFactory urlFactory = stateManager.getURLFactory();
    // get a new EngineURL object from the factory
    final EngineURL url = urlFactory.newURL(null);
    // get the URL-specific state
    final StateHolderController state = url.getState();
    // make the URL a single portlet refresh URL
    setSelectionMapping(stateManager, window, state);
    // set the portlet action
    setAction(stateManager, window, state);
    // set the action parameters
    setState(stateManager, window, state, null, null, actionParams);
    // release the URL factory
    urlFactory.dispose();
    // return the URL
    return url;
}

Refreshing portlet markup fragments

Single Portlet Refresh URLs are meant to be used in combination with JavaScript. Typically, you would use XMLHttpRequest, provided by almost every browser today, to send requests to the portal server using these URLs. Subsequently, the portlet markup returned by the server can be injected into the portlet markup residing in the browser.

The following code listings show how to transmit portlet requests to the portal server using JavaScript. The functionality is encapsulated into an JavaScript object called ibmsample.PortletRequest (see listing 7). The PortletRequest object has been attached to a global variable ibmsample to avoid name clashes with other JavaScript code running in the page. The constructor lets you pass in the following arguments:

  • url: The URL to which the portlet request should be sent. Pass in Single Portlet Refresh URLs generated in your Java code.
  • onload: Callback function that is called if the portlet content has been loaded successfully.
  • onerror: Optional callback function that is called in case of an error.
  • method: Optional argument to specify the HTTP method the request should use. Defaults to "GET" but should be set to "POST" if a portlet action URL is passed in.
  • params: Optional set of request parameters that should be sent to the portlet.
  • contentType: Optional argument specifying the content type.
Listing 7: PortletRequest JavaScript object to send requests to the portlet
var ibmsample = new Object();

if (typeof(ibmsample.PortletRequest) == "undefined") {

  ibmsample.PortletRequest = function(url, onload, onerror, method, params, contentType) {
    this.request = null;
    this.url = url;
    this.onload = onload;
    if (onerror != null) {
      this.onerror = onerror;
    } else {
      this.onerror = this.defaultError;
    }
    this.loadContent(url, method, params, contentType);
  }

  ibmsample.PortletRequest.prototype = {
    loadContent: function(url, method, params, contentType) {
      this.request = this.newXMLHttpRequest();
      if (!method) {
        method = "GET";
      }
      if (!contentType && method=="POST") {
        contentType = "application/x-www-form-urlencoded";
      }
      if (this.request) {
        try {
          var me = this;
          this.request.onreadystatechange = function() {
            var req = me.request;
            var readyState = req.readyState;
            // check for complete state
            if (readyState == 4) {
              var returnCode = req.status;
              if (returnCode == 0 || returnCode == 200) {
                me.onload.call(me);
              } else {
                me.onerror.call(me);
              }
            }
          }
          this.request.open(method, url, true);
          if (contentType) {
            this.request.setRequestHeader("Content-Type", contentType);
          }
          this.request.send(params);
        } catch (error) {
          this.onerror.call(this);
        }
      }		
    },
    defaultError: function() {
      alert("An error occured!");
    },
    newXMLHttpRequest: function() {
      if (window.XMLHttpRequest) {
        return new XMLHttpRequest();
      } else {
        return new ActiveXObject("Microsoft.XMLHTTP");
      }
    }
  }
}

When adding the JavaScript code to your portlet, remember that your portlet might be aggregated on a portal page with other portlets or that your portlet might appear multiple times on a page. Therefore, offload the PortletRequest JavaScript code into a separate JavaScript file and include that file in your portlet JSPs. To send a portlet request simply instantiate a PortletRequest object.

Listing 8 depicts a JSP snippet which contains a script that declares two JavaScript functions: one for sending a portlet request, and another one for processing the response. These functions need to be namespaced because the functions are not offladed to a JavaScript file. The function <portlet:namespace/>loadFragment(url)might be used to create a render request (in which case, a render URL should be passed in). In this example, it has been connected to the <portlet:namespace/>refreshDOM() callback handler which takes the received markup to refresh the portlet DOM residing in the browser. The callback handler can access the XMLHttpRequest object using the keyword this. Note that all PortletRequest properties can be accessed in this way to simplify the implementation of such callback functions.

Listing 8: JSP fragment defining two functions
<%@ page session="false" contentType="text/html" import="java.util.*,javax.portlet.*" %>
<%@taglib uri="http://java.sun.com/portlet" prefix="portlet" %>
<portlet:defineObjects/>

<script type="text/javascript" src="<%=request.getContextPath()%>/js/PortletRequest.js">
</script>

<script type="text/javascript">
  function <portlet:namespace/>loadFragment(url) {
    new ibmsample.PortletRequest(url, <portlet:namespace/>refreshDOM);
  }
  function <portlet:namespace/>refreshDOM() {
    var markup = this.request.responseText;
    document.getElementById("<portlet:namespace/>fragment").innerHTML = markup;
  }
</script>

...

Before finishing this section, the following enumeration summarizes the most important recommendations for using JavaScript in portlets:

  1. Namespace page DOM elements as well as global JavaScript identifiers.

    Use either the <portletAPI:namespace/> tag or the getNamespace()method defined on the RenderResponse interface. For details, see the Java Portlet Specification Version 1.0 in Related topics.

  2. Offload JavaScript code into separate files instead of including it into the page.

    Using separate JavaScript files has several advantages. It lets you reduce the page size and benefit from the browser cache, and it significantly simplifies namespacing.

  3. Avoid double-initialization of JavaScript libraries.

    To save resources, include a simple typeofcheck in your JavaScript code to make sure that your library is not initialized multiple times.

Conclusion

This article provided an overview of the Ajax programming model and discusses how portlets can leverage either servlets or portlets as a backend. The existing Ajax programming model using servlets for asynchronous requests was presented. You saw how this model differs from a more generic Ajax programming model, which uses the Single Portlet Refresh feature of WebSphere Portal Version 6.0.1 to create requests to individual portlet windows.

In addition, you learned in detail when and how to use Single Portlet Refresh to implement use cases such as updating preferences using asynchronous action requests or refreshing single markup fragments using render requests. Single Portlet Refresh has some limitations that need to be considered when using this new feature. In particular refreshing portlet markup fragments is problematic with respect to the navigational state handling of portal; however, you can use it in scenarios where the navigational state handling is not an issue.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=273102
ArticleTitle=Refreshing individual portlets and preferences using Single Portlet Refresh in WebSphere Portal V6.0.1
publish-date=12052007