Level: Introductory Michael Wanderski, IBM Pervasive Computing Development, IBM
26 Mar 2003 Portals provide a consolidated interface for applications that are customizable through preferences, tailored with unique layouts, and personalized through selectable content. Portlets are Web-based applications which interact within the portal structure and collaborate with other portlets. The next step in portal evolution is extending the reach to pervasive devices, which are not always connected to the internet and may roam in and out of connectivity. This article discusses how WebSphere Everyplace Access provides wireless portal access for the enterprise, as well as a solution for the occasionally connected portal user through access to offline portlets.
Introduction to WebSphere Everyplace Access and offline technology
Portals have become a very popular user interface and provide an important channel for a large variety of Web content. They provide a consolidated interface for applications that are customizable through preferences, tailored with unique layouts, and personalized through selectable content. Example uses of a portal include access to stock quotes, subscription-based news feeds and access to productivity applications.
Portal adoption by companies provide highly personalized access to corporate news and applications. For example, a marketing employee might need access to competitive news feeds and search engines. A developer might make use of code control applications and technology-based news feeds. All employees of a company, regardless of job responsibility, would need access to human resource applications. Thus, portals provide the framework for managing all these applications, aggregating content for employees based on departmental needs, and allowing employees to individually tailor their user experience based on their access authority.
IBM WebSphere® Portal is a first-class portal product that allows construction of highly customized business-to-customer, business-to-employee and business-to-business portals. This product provides a framework for developing Web-based applications, called
portlets
, which interact within the portal structure and collaborate with other portlets. The portal provides enhanced services for portlets such as standardized styling, single sign-on, event handling, and many, many others.
The next step in portal evolution is extending the reach to a pervasive device such as a PalmPilotTM, Pocket PC, or SmartPhone. Each of these devices employs a different technique for rendering markup on a particular device. For example, the Pocket PC uses a slimmed down version of HTML, and a SmartPhone uses a completely different markup called WML (Wireless Markup Language), which is targeted for a limited display device. Additionally, devices are not always connected to the internet and may roam in and out of connectivity, especially with the advent of wireless access points. Any portal-based solution that is intended for use on a pervasive device must provide infrastructure and solutions for these situations.
With these considerations in mind, meet IBM WebSphere EveryplaceTM Access (Version 4.2). WebSphere Everyplace Access is a WebSphere Portal addition that provides wireless portal access for the enterprise. This first-rate product provides the technology needed to give mobile employees access to productivity data and enterprise applications from virtually any device, such as a PDA or a SmartPhone. The platform helps move business-critical information throughout the organization efficiently and without boundaries. For example, field professionals can have access to sales inventory information and personal information management applications, such as email, all through the portal interface. Additionally, WebSphere Everyplace Access provides a solution for the occasionally-connected portal user through access to offline portlets.
Introduction to offline portlet development
The good news for portlet developers is that developing offline content with IBM WebSphere Everyplace Access is very similar to traditional portlet development. Offline portlets can be thought of as regular portlets with some additional constraints, as well as some added capabilities. Additionally, just as in any targeted application, there are some considerations to keep in mind while developing your offline portlets. This article builds on the fundamental portlet development concepts you are already familiar with, and provides a simple example for you to expand upon.
Portlets in WebSphere Everyplace Access currently only operate in offline mode on a Pocket PC device. They do this by extending the base support offered on the Pocket PC platform. Portlets are identified as supporting the Pocket PC device by adding a value to their portlet descriptor file (
portlet.xml
). The XML code fragment in Listing 1 needs to be inserted in the
<supports>
tag within the
portlet.xml
file before being packaged and deployed within the portal server.
Listing 1
¦
<supports>
<markup name="html">
<view output="fragment">
</markup>
<markup name="pda">
<view output="fragment">
</markup>
</supports>
¦
|
Once this markup is inserted into
portlet.xml
, the portal server will recognize that a request is coming from a Pocket PC device by using the HTTP User-Agent field, and map that request to the registered controller and JSP packaged in the portlet. Knowledge of multiple markup support in WebSphere Portal and WebSphere Everyplace Access is assumed, but for a refresher or additional information, please review the recommended related documents and articles (see
Resources
).
Once a "pda" portlet is deployed into the portal server, it can be added as a portlet to a special page available to every WebSphere Everyplace Access user. This page is called the Offline page within the WEA Home place (Figure 1) and looks similar to any other portal page. A user can add any portlet to this page. Portlets on this page that support the "pda" markup are designated as offline portlets and will be synchronized as such when initiated by the client software, the WebSphere Everyplace Client, running on the Pocket PC device.
Figure 1: Offline page in WEA Home place
So what exactly is an offline portlet? There are basically two technologies that together define what an offline portlet is. The first is termed
offline browsing
. Simply put, all the pages associated with a portlet are made available on the Pocket PC in the state they were captured in during a synchronization request. Basically, the portlet pages are crawled on the server, and then downloaded and cached locally on the device for viewing when the device is offline. On the Pocket PC, the pages are inserted into the Pocket Internet Explorer (IE) cache and viewable directly from the browser.
The second technology is a little more interesting and introduces an interactive nature to offline portlets. This technology is termed
mobile forms
and allows for form-based content to be cached
locally on the device. When a user fills in a form
while offline and submits the input, the form data is
captured locally, queued up in the client and is
available to be "synchronized" to the
portal server, pending an offline synchronization
request. It's important to note, however, that this
process is not truly synchronization, because there is
no conflict resolution involved. Basically, forms are
processed in the order they were submitted just as if
they were processed in a connected environment. You can
think of the offline form submission as following the
same procedure that would have been taken had the form
been submitted online, except that the interaction is
delayed until the synchronization request is made.
Thus, it is important to note that in order for this to
work correctly at this delayed time, the offline server
must first connect and log into the portal on behalf of
the client to establish credentials. It then must crawl
to the specified form-based page and submit the form
data. Thus, potential hazardous side affects surface
when a change to the portlet content occurs during this
request, compared to the last synchronization request.
This will be discussed in more detail in subsequent
sections.
Before jumping into an example, here are a couple screen captures of the WebSphere Everyplace Client. Complete use and interaction with the client is not fully covered in this article, but information can be found in the WebSphere Everyplace Access InfoCenter and in recommended related articles (see
Resources
). The first screen capture is of the Everyplace Client start screen (Figure 2). Here you can create profiles and initiate a synchronization request with the WebSphere Everyplace Access server by clicking
Refresh now
.
Figure 2. Everyplace Client start screen
By initiating this request, you instruct the client to submit any pending offline form data and resynchronize, or "capture", all portlet content on the offline page. Once completed, you can view your offline content from the Pocket IE browser by selecting
Tools => View offline page
. Figure 3 shows an example with two offline portlets, World Clock and Reminders, represented by the provided icons.
Figure 3. Offline portlets
By clicking on one of the icons, you can access the portlet content. For example, Figure 4 shows the content associated with the Reminder portlet. Like connected browsing, you can browse the portlet pages and submit forms within the portlet while offline.
Figure 4. Portlet content
Let's start the with a simple portlet example that will illustrate some of the guidelines and techniques of offline portlet development. This portlet interacts with messages on a backend message store, and provides three main functions:
- view all messages in the store
- add a new message to the store
- delete a message from the store.
The user interface for the portlet will be constructed as shown in Figures 5 and 6:
Figure 5. User interface
By clicking the "Add Message" link (Figure 5), the user is presented with the input option JSP (Figure 6).
Figure 6. User interface input option
To implement this portlet, first define an interface to abstract the implementation details of the message store (Listing 2).
Listing 2
package com.ibm.wps.portlets.messages;
import java.util.*;
/**
* Simple and generic interface for a message store.
*/
public interface MessageStore {
/**
* Enumerate through all the message IDs available.
* @return All the message IDs.
*/
public Enumeration getMessageIDs();
/**
* Save a message to the store with the specified message ID.
* @param message The message to store.
* @return The message ID.
*/
public Integer saveMessage(String message);
/**
* Load a message from the store with the specified message ID.
* @param messageID The message ID.
*/
public String loadMessage(Integer messageID);
/**
* Delete a message from the store with the specified message ID.
* @param messageID The message ID.
*/
public void deleteMessage(Integer messageID);
} |
For this example, this store is implemented with a simple, ephemeral hash table and is provided with a factory for generating the implementation class (Listings 3 and 4). Please note, though, that for a production portlet, you should not take the shortcuts (e.g. non-persistence) that have been used in this example for the sake of simplicity.
Listing 3
package com.ibm.wps.portlets.messages;
import java.util.*;
public class MessageStoreImpl implements MessageStore {
private int ids = 0;
private Hashtable store = new Hashtable();
public Enumeration getMessageIDs() {
return store.keys();
}
public Integer saveMessage(String message) {
Integer id = new Integer(ids++);
store.put(id, message);
return id;
}
public String loadMessage(Integer messageID) {
return (String)store.get(messageID);
}
public void deleteMessage(Integer messageID) {
store.remove(messageID);
}
}
|
Listing 4
package com.ibm.wps.portlets.messages;
public class MessageStoreFactory {
public static MessageStore createInstance() {
return new MessageStoreImpl();
}
|
Next, the controller class is created to handle requests to the portlet and to direct those requests to the correct JSP (Listing 5). The controller class also owns the responsibility of providing the JSP with the MessageStore via a request attribute, and handling any posted requests from the JSP forms (e.g. a message add and delete). For related information on MVC design patterns in portlets and pervasive portlet development, please see
Resources
. Be sure to note the warning below about not using class level variables. This is a bad practice in portlet development, but has been done here to keep the example simple and focused on the point of offline techniques only.
Listing 5
package com.ibm.wps.portlets.messages;
import java.io.*;
import java.util.*
import com.ibm.wps.portlets.*
import org.apache.jetspeed.portlet.*
import org.apache.jetspeed.portlets.*
import org.apache.jetspeed.portlet.event.*
public class MessagesPortletController extends PortletAdapter
implements ActionListener
protected static final String JSP_Directory = "/WEB-INF/MessagesPortlet/"
// WARNING: Simplifying example by creating one store that is not
// persistent and will be shared among users. Not recommended for
// a production portlet.
private MessageStore store = MessageStoreFactory.createInstance();
public void doView(PortletRequest request, PortletResponse response)
throws PortletException, IOException {
String view = request.getParameter("VIEW");
if (view != null && view.equals("add"))
getPortletConfig().getContext().include(
JSP_Directory + "MessagesPortletAdd.jsp", request, response);
}
else {
request.setAttribute("messageStore", store);
getPortletConfig().getContext().include(
JSP_Directory + "MessagesPortletView.jsp", request, response);
}
}
public void actionPerformed(ActionEvent anEvent) throws PortletException {
DefaultPortletAction action = (DefaultPortletAction)anEvent.getAction();
PortletRequest request = anEvent.getRequest();
if (action.getName().equals("save")) {
doActionSave(request.getParameter("message"));
}
else if (action.getName().equals("delete")) {
doActionDelete(request);
}
}
private void doActionSave(String message) {
store.saveMessage(message);
}
private void doActionDelete(PortletRequest request) {
for (Enumeration en = request.getParameterNames();
en.hasMoreElements(); ) {
String name = (String)en.nextElement();
if (name.startsWith("ID")) {
int ind = name.indexOf(".");
store.deleteMessage(
new Integer(ind == -1 ? name.substring(2) :
name.substring(2, ind)));
}}
}
}
|
The controller has two main methods, actionPerformed and doView. The actionPerformed method is called before the doView method of the portlet, and is intended to interact with the model (in the MVC paradigm) of the portlet. For example, it can be used to add or delete entries from a database. In this example, the actionPerformed method receives two types of actions, either a save or a delete request. The save request simply defers to the doActionSave method, which in turn invokes the saveMessage method on the MessageStore. The delete action defers to the doActionDelete method, and is responsible for deleting the selected message from the MessageStore based on its message ID. To accomplish this, we need to iterate through the portlet request parameters to find any parameter that starts with the string
ID
. As you will see below, the JSP posts a parameter starting with this value and appends the message ID number (e.g.
ID123
) when selected on the delete form. Since clicking an icon is used to initiate the request, most browsers return a parameter of the form
ID123.Y
, where ".Y" is used to indicate that the value of this parameter is the Y position of where the icon was clicked. Thus, in order to obtain the message ID, we take the internal ID string, in this case
123
.
The first JSP (Listing 6) is responsible for rendering the page and displaying the messages in a tabular format. It inserts a delete icon in front of each message so that, by clicking it, the message will be removed. The user can also click the
Add Message
link to proceed to the next JSP for adding a message.
Listing 6
<%@ page language="java" contentType="text/html;charset=utf-8" %>
<%@ page import="java.util.*" %>
<%@ page import="org.apache.jetspeed.portlet.*" %>
<%@ page import="org.apache.jetspeed.portlets.*" %>
<%@ page import="org.apache.jetspeed.portlet.event.*" %>
<%@ taglib uri="/WEB-INF/tld/engine.tld" prefix="wps" %>
<%@ taglib uri="/WEB-INF/tld/portlet.tld" prefix="portletAPI" %>
<portletAPI:init/>
<jsp:useBean id="messageStore"
class="com.ibm.wps.portlets.messages.MessageStore"
scope="request"/>
<% PortletURI addURI = portletResponse.createURI(); %>
<% addURI.addParameter("VIEW", "add"); %>
<a href="<%= addURI %>">Add Message</a>
<% PortletURI delURI = portletResponse.createURI(); %>
<% delURI.addAction(new DefaultPortletAction("delete")); %>
<form name="<portletAPI:encodeNamespace value="MessageForm"/>"
action="<%= delURI %>" method="POST">
<table border="0" cellspacing="1" cellpadding="1" width="100%">
<tr>
<td bgcolor="#808080">
<b><font color="#FFFFFF">Messages</font></b>
</td>
</tr>
<% boolean flag = false; %>
<% for (Enumeration en = messageStore.getMessageIDs(); en.hasMoreElements();
flag = !flag) { %>
<% Integer id = (Integer)en.nextElement(); %>
<tr>
<td colspan="2" valign="middle" <%= flag ? "bgcolor=\"#DEDEDE\"" : "" %>>
<% String del = "ID" + id; %>
<input type="image" name="<portletAPI:encodeNamespace value="<%= del %>"/>"
src="<portletAPI:encodeURI path='<%= "images/delete.gif" %>'/>"
align="absmiddle" width="16" height="16" alt="Delete Message"
border="0"/>
<%= messageStore.loadMessage(id) %>
</td>
</tr>
<% } %>
</table>
</form>
|
The second JSP (Listing 7) handles adding a new message to the store by providing an input box, and posting the value back to the controller through a PortletAction for processing.
Listing 7
<%@ page language="java" contentType="text/html;charset=utf-8" %>
<%@ page import="java.util.*" %>
<%@ page import="org.apache.jetspeed.portlet.*" %>
<%@ page import="org.apache.jetspeed.portlets.*" %>
<%@ page import="org.apache.jetspeed.portlet.event.*" %>
<%@ taglib uri="/WEB-INF/tld/engine.tld" prefix="wps" %>
<%@ taglib uri="/WEB-INF/tld/portlet.tld" prefix="portletAPI" %>
<portletAPI:init/>
<% PortletURI saveURI = portletResponse.createURI(); %>
<% saveURI.addAction(new DefaultPortletAction("save")); %>
<form name="<portletAPI:encodeNamespace value="AddForm"/>"
action="<%= saveURI %>" method="POST">
Enter a new message:
<input type="text" name="<portletAPI:encodeNamespace value="message"/>"/>
<input type="submit" name="<portletAPI:encodeNamespace value="submit"/>" value=
"Save"/>
</form>
|
Offline portlet guidelines
Using this example, we will now discuss guidelines associated with offline portlet development, first by pointing out the portions of code that were done correctly and explaining why. Second, we'll look at some intentional mistakes that were made to point out common pitfalls and demonstrate, through code modifications, how to work around these issues.
Well-formedness
Portlet JSPs must strictly adhere to XML "well-formedness" so that they are interpreted correctly by the WebSphere Everyplace Access offline server. This is necessary because the server must crawl portlet pages as well as modify portions of pages during offline processing. So for example, tags like
<input>
or
<img>
must have a closing bracket as shown in Listing 8, taken from the above JSP.
Listing 8
<input type="text" name="<portletAPI:encodeNamespace value=
"message"/>"/> />
|
Additionally, all tag attributes must have a start and an end quote, and tags must not become entangled, as happens so often in HTML development. By following the rules of strict XML well-formedness, you will avoid any problems related to this issue.
Form POST support
Only HTTP POST is supported from a form, as shown in the JSP snippet below (Listing 9). Thus, you cannot use the form GET method if you want your content to be synchronized properly.
Listing 9
<form name="<portletAPI:encodeNamespace
value="MessageForm"/>" action="<%= delURI %>" method="POST"> |
Avoid or plan for dynamic content
Since the offline server must crawl portlet links to perform an offline synchronization, dynamic content can cause problems. For example, if a portlet contains an unordered list of links that is dynamically generated, and a user has traversed to a link below that cluster of dynamic links to submit a form, there is no guarantee that the form will be positioned in the same relative location as before. Thus, the offline server can become "lost" as it tries to traverse the portlet to submit the form. Dynamic or unordered lists are not strictly prohibited; instead, care needs to be exercised with their use. For example, in our portlet, the Add Message link has been intentionally placed above the dynamic list of messages to avoid this problem. If it had been placed after the list of messages, and a user had deleted a message while offline, the offline server would become lost during synchronization because the list size would have changed.
Use caution with potentially harmful action buttons
This is the first mistake presented in our example. Again, during synchronization, the offline server traverses every link up until the configured link depth is reached. Because of this, some action buttons can cause problems. In the list of messages, for example, a delete button was placed next to each entry. Every time the offline server processes the portlet page, all links are traversed, and thus all messages are deleted. An alternative approach is to put the items into a form with checkboxes next to each message so that there is no link to crawl that would invoke that action. The necessary changes to the view JSP have been highlighted below (Listing 10) to circumvent this problem.
Listing 10
¦
<% PortletURI addURI = portletResponse.createURI(); %>
<% addURI.addParameter("VIEW", "add"); %>
<a href="<%= addURI %>">Add Message</a>
<% PortletURI delURI = portletResponse.createURI(); %>
<% delURI.addAction(new DefaultPortletAction("delete")); %>
<form name="<portletAPI:encodeNamespace value="MessageForm"/>"
action="<%= delURI %>" method="POST">
<table border="0" cellspacing="1" cellpadding="1" width="100%">
<tr>
<td>
<input type="submit" name="<portletAPI:encodeNamespace value="submit"/>"
value="Delete"/>
</td>
</tr>
<tr>
<td bgcolor="#808080">
<b><font color="#FFFFFF">Messages</font></b>
</td>
</tr>
<% boolean flag = false; %>
<% for (Enumeration en = messageStore.getMessageIDs(); en.hasMoreElements();
flag = !flag) { %>
<% Integer id = (Integer)en.nextElement(); %>
<tr>
<td colspan="2" valign="middle" <%= flag ? "bgcolor=\"#DEDEDE\"" : "" %>>
<% String del = "ID" + id; %>
<!-- This section of code should be deleted:
<input type="image" name="<portletAPI:encodeNamespace value="<%= del %>"/>"
src="<portletAPI:encodeURI path='<%= "images/delete.gif" %>'/>"
align="absmiddle" width="16" height="16" alt="Delete Message"
border="0"/> -->
<input type="checkbox"
name="<portletAPI:encodeNamespace value="<%= del %>"/>" />
<%= messageStore.loadMessage(id) %>
</td>
</tr>
<% } %>
</table>
</form>
|
The portlet controller also needs to be modified to accept the new input, as shown in Listing 11 (the doActionDelete method of the MessagesPortletController class).
Listing 11
¦
private void doActionDelete(PortletRequest request) {
for (Enumeration en = request.getParameterNames(); en.hasMoreElements(); ) {
String name = (String)en.nextElement();
if (name.startsWith("ID")) {
<!-- This section of code should be deleted:
int ind = name.indexOf(".");
store.deleteMessage(
new Integer(ind == -1 ? name.substring(2) :
name.substring(2, ind))); -->
store.deleteMessage(new Integer(name.substring(2)));
}}
}
¦
|
The resulting JSP user interface now looks similar to the Figure 7. Again, the delete button was intentionally inserted above the dynamic list to prevent the offline server from becoming confused on a synchronization request.
Figure 7. User interface
PortletActions not supported
PortletActions are implemented in WebSphere Portal using server-side processing. When pages utilizing these actions are browsed offline, the normal server-side processing cannot occur in real-time, and thus the resulting links may not be valid. This is a point of confusion for many portlet developers because PortletActions are recommended in many portlet coding guides. Luckily, future versions of WebSphere Portal will likely deprecate server-side processing. But until this occurs, alternatives are necessary to side-step this problem. The simplest solution is to replace all PortletActions with request parameters, and perform the logic typically carried out in the actionPerformed method at the beginning of the doView method instead. In Listing 12, the code is highlighted where the change has been made to the view JSP.
Listing 12
¦
<% PortletURI addURI = portletResponse.createURI(); %>
<% addURI.addParameter("VIEW", "add"); %>
<a href="<%= addURI %>">Add Message</a>
<% PortletURI delURI = portletResponse.createURI(); %>
<!-- This line of code should be deleted:
<% delURI.addAction(new DefaultPortletAction("delete")); %> -->
<% delURI.addParameter("ACTION", "delete"); %>
<form name="<portletAPI:encodeNamespace value="MessageForm"/>"
action="<%= delURI %>" method="POST">
<table border="0" cellspacing="1" cellpadding="1" width="100%">
<tr>
<td>
<input type="submit" name="<portletAPI:encodeNamespace value="submit"/>"
value="Delete"/>
</td>
</tr>
<tr>
<td bgcolor="#808080">
<b><font color="#FFFFFF">Messages</font></b>
</td>
</tr>
<% boolean flag = false; %>
<% for (Enumeration en = messageStore.getMessageIDs(); en.hasMoreElements();
flag = !flag) { %>
<% Integer id = (Integer)en.nextElement(); %>
<tr>
<td colspan="2" valign="middle" <%= flag ? "bgcolor=\"#DEDEDE\"" : "" %>>
<% String del = "ID" + id; %>
<input type="checkbox"
name="<portletAPI:encodeNamespace value="<%= del %>"/>" />
<%= messageStore.loadMessage(id) %>
</td>
</tr>
<% } %>
</table>
</form>
|
The modification to the add JSP is similar and is shown highlighted in Listing 13:
Listing 13
¦
<% PortletURI saveURI = portletResponse.createURI(); %>
<!-- This line of code should be deleted:
<% saveURI.addAction(new DefaultPortletAction("save")); %> -->
<% saveURI.addParameter("ACTION", "save"); %>
<form name="<portletAPI:encodeNamespace value="AddForm"/>"
action="<%= saveURI %>" method="POST">
Enter a new message:
<input type="text" name="<portletAPI:encodeNamespace value="message"/>"/>
<input type="submit" name="<portletAPI:encodeNamespace value="submit"/>" value=
"Save"/>
</form>
|
Changes to the controller are also needed to discontinue use of the actionPerformed method and to insert similar code at the top of the doView method, as shown in Listing 14:
Listing 14
¦
public class MessagesPortletController extends PortletAdapter {
<!-- This line of code should be deleted:
implements ActionListener { -->
protected static final String JSP_Directory = "/WEB-INF/MessagesPortlet/";
// WARNING: Simplifying example by creating one store that is not
// persistent and will be shared among users. Not recommended for
// a production portlet.
private MessageStore store = MessageStoreFactory.createInstance();
public void doView(PortletRequest request, PortletResponse response)
throws PortletException, IOException {
String action = request.getParameter("ACTION");
if (action != null && action.equals("save")) {
doActionSave(request.getParameter("message"));
}
else if (action != null && action.equals("delete")) {
doActionDelete(request);
}
String view = request.getParameter("VIEW");
if (view != null && view.equals("add")) {
getPortletConfig().getContext().include(
JSP_Directory + "MessagesPortletAdd.jsp", request, response);
}
else {
request.setAttribute("messageStore", store);
getPortletConfig().getContext().include(
JSP_Directory + "MessagesPortletView.jsp", request, response);
}
}
<!-- This section of code should be deleted:
public void actionPerformed(ActionEvent anEvent) throws PortletException {
DefaultPortletAction action = (DefaultPortletAction)anEvent.getAction();
PortletRequest request = anEvent.getRequest();
if (action.getName().equals("save")) {
doActionSave(request.getParameter("message"));
}
else if (action.getName().equals("delete")) {
doActionDelete(request);
}
} -->
private void doActionSave(String message) {
store.saveMessage(message);
}
private void doActionDelete(PortletRequest request) {
for (Enumeration en = request.getParameterNames();
en.hasMoreElements(); ) {
String name = (String)en.nextElement();
if (name.startsWith("ID")) {
store.deleteMessage(new Integer(name.substring(2)));
}}
}
}
|
Avoiding embedded forms
Since offline forms are posted and queued on the client while submitted offline, there is no way to chain offline forms because the cascaded form content depends on the previous post. Embedded forms should be avoided for all offline portlets.
Deployment of offline portlets
The last step in developing your offline portlet is packaging and deployment. The packaging of offline portlets is almost identical to traditional portlet development, so we won't go into detail on this topic here. Listing 15 shows the layout structure of the portlet WAR file and internal JAR file. Note that the JSPs are at the highest possible directory structure because the same JSPs are used for the rendering of both the "pda" and "html" markups. If there were separate JSPs, developers could create
html
and
pda
directories under the
WEB-INF\MessagesPortlet
directory and put their device-specific JSPs there for automatic detection by the WebSphere Portal server.
Listing 15
\WEB-INF\portlet.xml
\WEB-INF\web.xml
\WEB-INF\lib\MessagePortlet.jar
\com\ibm\wps\portlets\messages\MessagesPortletController.class
\com\ibm\wps\portlets\messages\MessageStore.class
\com\ibm\wps\portlets\messages\MessageStoreFactory.class
\com\ibm\wps\portlets\messages\MessageStoreImpl.class
\WEB-INF\lib\wpsportlets.jar
\WEB-INF\MessagesPortlet\MessagesPortletAdd.jsp
\WEB-INF\MessagesPortlet\MessagesPortletView.jsp
|
Both the
portlet.xml
and
web.xml
files are similar to the files packaged with a traditional portlet. The contents of the
web.xml
file are shown in Listing 16:
Listing 16
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app id="WebApp_1_MessagesPortlet">
<display-name>MessagesPortlet</display-name>
<servlet id="Servlet_1_MessagesPortlet">
<servlet-name>MessagesPortlet</servlet-name>
<servlet-class>
com.ibm.wps.portlets.messages.MessagesPortletController
</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping id="ServletMapping_1_MessagesPortlet">
<servlet-name>MessagesPortlet</servlet-name>
<url-pattern>/MessagesPortlet/*</url-pattern>
</servlet-mapping>
</web-app>
|
The contents of the
portlet.xml
are shown in Listing 17, but there are two distinct points of interest that are highlighted and explained below.
Listing 17
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE portlet-app-def PUBLIC
"-//IBM//DTD Portlet Application 1.1//EN" "portlet_1.1.dtd">
<portlet-app-def>
<portlet-app uid="MessagesPortlet.portletapp" major-version="1"
minor-version="0">
<portlet-app-name>MessagesPortlet Portlet</portlet-app-name>
<portlet id="Portlet_1_MessagesPortlet"
href="WEB-INF/web.xml#Servlet_1_MessagesPortlet">
<portlet-name>MessagesPortlet Portlet</portlet-name>
<cache>
<expires>0</expires>
<shared>YES</shared>
</cache>
<allows>
<maximized/>
<minimized/>
</allows>
<supports>
<markup name="html">
<view output="fragment"/>
</markup>
<markup name="pda">
<view output="fragment"/>
</markup>
</supports>
</portlet>
</portlet-app>
<concrete-portlet-app uid="MessagesPortlet.concreteportletapp">
<portlet-app-name>MessagesPortlet Portlet</portlet-app-name>
<context-param>
<param-name>Webmaster</param-name>
<param-value>webmaster@yourco.com</param-value>
</context-param>
<concrete-portlet href="#Portlet_1_MessagesPortlet">
<portlet-name>MessagesPortlet Portlet</portlet-name>
<default-locale>en</default-locale>
<language locale="en">
<title>MessagesPortlet</title>
<title-short>MessagesPortlet</title-short>
<description>MessagesPortlet</description>
<keywords>MessagesPortlet</keywords>
</language>
<config-param>
<param-name>pda-icon</param-name>
<param-value>/images/pda/messaging.gif</param-value>
</config-param>
</concrete-portlet>
</concrete-portlet-app>
</portlet-app-def>
|
As mentioned previously, offline portlets must be designated as supporting the "pda" markup. Also, offline portlets have an icon displayed on the initial offline page on the client. By default, a generic icon is provided if none is specified. You can change this icon by specifying a configuration parameter named "pda-icon" and pointing it to an image under the document root of the portal server. The directory to insert your custom icon is the following path, under the install path of the portal server (Listing 18).
Listing 18
%WPS_DIRECTORY%\app\wps.ear\wps.war\images\pda
|
Once packaged into a WAR file, the portlet is deployed in the same manner as any other portlet using the WebSphere Portal administration portlets. The last step is adding the portlet to the WebSphere Everyplace Access Offline page located under the WEA Home place (Figure 8). Also, be sure to configure the link depth for your portlets on the
Configure
tab to an appropriate depth.
Figure 8. Offline page in WEA Home place
Once synchronized using the WebSphere Everyplace Client, the portlet is fully functional in both online and offline environments, as shown in the screen captures below. Again, this article does not show the WebSphere Everyplace Client user interface in full detail, but you can find more information in the WebSphere Everyplace InfoCenter (see
Resources
).
Once synchronized, the first offline page lists the available offline portlets, in this case only the MessagesPortlet (Figure 9).
Figure 9. Available offline portlets
By clicking on the portlet icon, the portlet content is displayed, as shown in Figure 10. As you will notice, the user interface looks very similar to the interface of the online portal.
Figure 10. User interface
By clicking on the
Take car to shop
and
Talk to boss
check boxes and then clicking
Delete
, the form request is queued up on the client and the following screen is displayed:
Figure 11. Request deferred message
Using the WebSphere Everyplace Client, you can manage your offline form submissions (Figure 12). For example, you can delete them or request that they be synchronized.
Figure 12. Manage offline form submissions
Once the offline form submission request is complete and the portlet content is refreshed, Figure 13 shows how the two list items were deleted correctly from the portlet.
Figure 13. Updated user interface
You can also go the
Add Message
link while offline and submit a new message to be added, as shown in Figure 14.
Figure 14. Add message
Again, the request is deferred until you request a refresh. When this occurs, the same process is invoked, and the portlet is updated as expected. The results are shown in Figure 15.
Figure 15. Updated user interface
Conclusion
This article began with a sample portlet traditionally used in a connected environment, and discussed the changes and considerations necessary to enable this portlet for offline use. Considering how important offline access has become for the increasingly mobile worker, it's a safe bet more and more enterprises will require offline applications for their own business portals. This article has explained many of the basic concepts necessary for creating your own offline portlet applications, detailed the ease of making portlets offline-ready, and provided you with a starting point for being successful in extending your own portlet application reach for offline access to meet the increasing demands of the mobile market. Good luck!
Resources
About the author  | |  | Michael Wanderski is an advisory software engineer at the IBM Research Triangle Park Lab working in IBM's Pervasive Computing Division. Currently Michael is working on WebSphere Portal Server and related products. You can reach Mike at mwanders@us.ibm.com. |
Rate this page
|