|
By Greg Ziebold and Marina Sum, Updated: January 25, 2007
|
|
|
A year ago, the article Asynchronous Rendering of Portlet Content With Ajax Technology demonstrated how to apply Asynchronous JavaScript and XML (Ajax) to portlets. Since then, Ajax has become increasingly popular in the software arena and many new Ajax technologies have emerged. Examples are JavaScript libraries and toolkits, such as the Dojo Toolkit, the Yahoo! UI Library, the Google Web Toolkit, Script.aculo.us, and DHTML Goodies. In addition, new standards bodies like Open Ajax and the Dojo Foundation are key players.
In light of the many developments in the past year and the host of feedback on how to use Ajax in portlets, this article describes several helpful tips and practices on how best to exploit Ajax in portlets that comply with the Java Specification Request (JSR) 168: Portlet Specification.
The article refers to an updated version of the sample, Ajax Portlet Invoice Viewer, from the original article. You can download the binary Web archive (WAR) file.
The source
is available in the Open Source Portal
Repository on java.net.
Contents
Calling Other Application Resources
A significant disadvantage of using Ajax with JSR 168 portlets is that you cannot make asynchronous calls to portlets through portlet URLs. The portlet JavaServer Pages (JSP) tag library offers simple tags for constructing portlet URLs, but the tags do not produce the desired results.
Portlet URLs enable window state changes and mode changes that require a subsequent render request to the portal or portlet container. The result? The response contains not only the content for the target portlet, but also that for the entire portal, including all the other rendered portlets. Figure 1 shows an example of a Hello World! portlet that made an asynchronous call with renderURL, causing the entire portal page to be embedded in the portlet windowobviously an undesirable scenario.
Figure 1: Incorrect Use of renderURL for Asynchronous Rendering (Click image for larger view.) |
Currently, the expert group for JSR 286, which defines Portlet Specification 2.0, is evaluating solutions to better support portlets that make asynchronous calls with Ajax.
In the meantime, solutions do exist. Here is one: Because a portlet is a Web application that can contain other resources, such as servlets and JSP pages, you can make the asynchronous requests to the resources that are bundled with the portlet. For instance, the Invoice Viewer sample includes a servlet that you can call to asynchronously render new content in the portlet.
To take maximum advantage of this flexibility, the portlet and the servlet should be able to share information between themselves. How do you make that happen? Read on.
Maintaining Portlet State
When you dynamically update portlet content with an Ajax call to a servlet, how do you maintain the portlet's state? The answer is readily at hand. Consider the following excerpt from the Portlet Specification 1.0 (PLT.15.4) in the JSR 168 specification:
A Portlet Application is also a Web Application. The Portlet Application may contain servlets and JSPs in addition to portlets. Portlets, servlets and JSPs may share information through their session.
The PortletSession must store all attributes in the HttpSession of the portlet application. A direct consequence of this is that data stored in the HttpSession by servlets or JSPs is accessible to portlets through the PortletSession in the portlet application scope. Conversely, data stored by portlets in the PortletSession in the portlet application scope is accessible to servlets and JSPs through the HttpSession.
Accordingly, while in the servlet, you can store stateful data in the HttpSession interface and later, while in the portlet, retrieve the data from the PortletSession interface.
For example, InvoiceServlet stores the invoicenumber attribute in HttpSession when doPost() is asynchronously called by the portlet, as follows.
request.getSession().setAttribute(namespace + ".invoicenumber", invoice);
|
Afterwards, the portlet's main JSP page (view.jsp) can check for the invoicenumber attribute by calling getAttribute() on PortletSession, as follows.
<%
// Check for invoice in session.
String invoice = (String)renderRequest.getPortletSession().getAttribute(namespace +
".invoicenumber", PortletSession.APPLICATION_SCOPE);
%>
|
Note: You must specify APPLICATION_SCOPE because you are sharing attributes with other resourcesthe servletin the Web application.
Figure 2 illustrates how a bundled portlet and servlet in an Ajax portlet share information with the HttpSession and PortletSession interfaces.
Figure 2: Information Sharing Between a Portlet and a Servlet in an Ajax Portlet Web Application |
Using a JavaScript Library
Recall that you can use many emerging JavaScript libraries and toolkits within a portlet's Web application. This section focuses on the Dojo Toolkit.
In June, Sun joined the Dojo Foundation. Dojo is a robust and mature open-source JavaScript library that offers numerous widgets for dynamic Web development. Most importantly for the sample, Dojo provides an intuitive wrapper for the XMLHttpRequest object. To make an asynchronous call for content to dynamically update the portlet, the sample makes the dojo.io.bind function call. See the following code segment from view.jsp.
<script type="text/javascript">
/* Use dojo.io.bind to asynchronously get invoice content. */
function <portlet:namespace/>_loadInvoice(invoice) {
/* If an invoice was selected ... */
if (invoice != null && invoice != "null") {
/* Put selected invoice value on query string. */
var querystring = new Array();
querystring['invoice'] = invoice;
/* Set up bind arguments: */
/* url: url for request */
/* method: http method */
/* content: key/value mapping of parameters sent with request */
/* handle: function to run when there is a response */
/* mimetype:mimetype of response */
var bindArgs = {
url: "<%=renderResponse.encodeURL(renderRequest.getContextPath() + "/
InvoiceServlet")%>",
method: "POST",
content: querystring,
handle: function(type, data, evt) {
if (type == "error") {
/* Swap returned error message */
data = "<p style='color:red'>" + data.message + "</p>";
}
/* Swap the returned data into the div tag. */
var div = document.getElementById("<portlet:namespace/>_content");
div.innerHTML = "";
div.innerHTML = data;
},
mimetype: "text/html"
};
dojo.io.bind(bindArgs);
}
};
|
To recap the steps in the preceding code segment:
- The function
loadInvoice checks if a value has been passed to the function.
- You load the invoice parameter into a JavaScript array, which is passed to
dojo.io.bind.
- You create the JavaScript object
bindArgs with five parameters, as follows:
url The URL of the asynchronous request you are making.
method The HTTP method for the request. The default is GET.
content The request parameters to send with the request. They are required for POST requests only.
handle The method invoked to handle the response. See also the documentation for load and error.
mimetype The Multipurpose Internet Mail Extensions (MIME) type of the response.
- You pass the
bindArgs object to dojo.io.bind. The handle method specifies how to handle the response. As provisioning for errors, set the error message returned. Otherwise, replace the data returned in the response in the portlet's innerHTML property.
To learn more about dojo.io.bind, read the Dojo Manual and, in particular, the article Introduction to dojo.io.bind in the manual.
Note: To optimize performance, standardize on one or a few JavaScript libraries in your portlets or portal. Otherwise, you must load multiple portlets, each containing different libraries, into the JavaScript evaluator on the client. Loading multiple portlets weighs down the client and negatively impacts performance.
Understanding Namespace Collisions
If multiple portlets on a portal might include similar Ajax JavaScript functions and libraries, such as Dojo, consider the possibility of namespace collisions. This section discusses two problems that pertain to namespaces.
Portlet Namespace
Be sure to uniquely name the variables and functions in multiple copies of an Ajax portlet on a portal by defining the <portlet:namespace/> portlet JSP tag. See how the sample's main JSP page (view.jsp) defines that tag's function and the <div> tag ID, as follows.
<portlet:namespace/>_loadInvoice = function (invoice) {
...
<div id="<portlet:namespace/>_content"></div>
|
Dojo Namespace
The sample bundles the Dojo JavaScript library in its WAR file and, once deployed, references that library. However, other portlets on the portal might also use Dojo, but Dojo allows only one top-level reference for its reserved namespace. To alleviate this problem, before defining the Dojo namespace, verify that it is not loaded by checking the namespace and dynamically loading the Dojo library only when it is absent. Subsequently, the script dynamically adds JavaScript on the client when the content is loaded.
For example, in the sample's main JSP page (view.jsp), this script performs those tasks.
<div id="<portlet:namespace/>_main">
<script type="text/javascript">
/* Load the Dojo library if it hasn't been loaded already. */
if (typeof dojo == "undefined") {
/* Build script tag. */
var script = document.createElement("script");
script.src = "<%=renderResponse.encodeURL(renderRequest.getContextPath() + "/js/
dojo.js") %>";
script.type= "text/javascript";
/* Insert at the top of portlet main. */
var <portlet:namespace/>_main = document.getElementById("<portlet:namespace/>_main");
<portlet:namespace/>_main.insertBefore(script, <portlet:namespace/>_main.firstChild);
}
</script>
|
The script checks whether the Dojo namespace is already defined. If the answer is no, the script inserts a new tag in the Document Object Model (DOM) at the top of the portlet's main content.
Note: In the case where you have already defined the Dojo namespace, that Dojo version applies. However, that version, which is loaded by another portlet, might differ from the one that is deployed with the portlet in question. Currently, no simple solutions exist to tackle this possible conflict. As a safeguard, deploy only one version of the Dojo library for your portlets or portal.
Deploying the Sample
The sample is a JSR 168 portlet that installs into any portlet container, such as the reference implementation Apache Pluto.
To deploy the sample, first ensure that you have a working version of Sun Java System Portal Server 7 (henceforth, Portal Server) on your system and then follow these steps:
- Download the sample.
- Unzip the files to a temporary directory.
- Type the following command line (all on one line) to deploy the WAR file to Portal Server.
% psadmin deploy-portlet -u uid -f password-filename -p portal-ID {-g|-d dn} WAR-filename
For example:
% /opt/SUNWportal/bin/psadmin deploy-portlet -u "uid=amAdmin,ou=People,dc=sun,dc=com" -f /tmp/password -p portal1 -d "o=DeveloperSample,dc=sun,dc=com" /tmp/AjaxPortlet/dist/ajaxortlet.war
For details on the command psadmin deploy-portlet, see Chapter 6 of the Sun Java System Portal Server 7 Command-Line Reference.
- Type the following command line (all on one line) to update the display profile so as to create a channel for
AjaxPortlet and add that channel to PortletSamplesTabPanelContainer.
% psadmin modify-dp -u uid -f password-filename {-g|-d dn} --combine filename
For example:
% /opt/SUNWportal/bin/psadmin modify-dp -u "uid=amAdmin,ou=People,dc=sun,dc=com" -f /tmp/password -d "o=DeveloperSample,dc=sun,dc=com" --combine /tmp/AjaxPortlet/AjaxPortlet-dp.xml
Building the Sample
The AjaxPortlet sample includes a build.xml file for building the Web application with Apache Ant.
In addition, you can load the sample source into a Java technology-based IDE, such as the NetBeans IDE.
Conclusion
Indisputably, limitations and caveats exist in programming Ajax with respect to JSR 168 portlets. But should that preclude you from taking advantage of this exciting new technology? Until JSR 286 is available, bear in mind the best practices and guidelines described in this article when applying Ajax and the associated JavaScript libraries to portlets.
References
|
Greg Ziebold, a staff engineer at Sun, has been a member of the Sun Java System Portal Server development team since July 2000. He's working on resolving issues that relate to portlet development and dynamic content. Greg's blog focuses on portal-related topics.
|
Marina Sum is a staff writer for Sun Developer Network. She has been writing for Sun since 1989, mostly in the technical arena. Marina blogs on Sun products, technologies, events, and publications.
|
|