A coworker asked me how to write Plumtree portlet pagination (i.e. showing records in a UI and allowing the user to move from page to page, n records at a time) the other day and the ensuing discussion made me rethink how I've always done this and consider some new options. In this post, I attempt to shed some light on a very simple concept that turns out to be quite interesting in terms of implementation.
First, let's consider some of the ways developers typically add pagination to standard web applications, forgetting about portals and portlets for a moment. Let's call n
your page size, i
your page number and t
the total number of records. On the first page, you might see n
records laid out with alternating row colors and a 1-n of t
marker to show you were you are, e.g. Now Showing Records 1-5 of 45. There's probably also a next button and a back button (grayed out for now), a first page button (also grayed out), a last page button and maybe even a "fast-forward" button to move forward several pages at a time.
A very easy way to implement this in a standard (non-portal) MVC Java/J2EE Web application would be to carry some state, say i
(the page index), on the querystring. For example, say you have a record viewer servlet called "RecordView" running on a Java-enabled Web application container such as Tomcat. You could have something like http://bdg-plumtree:8080/bdg-plumtree-context/RecordView as your URL. Your servlet code snippet might look something like this:
import javax.servlet.*;
import javax.servlet.http.*;...
private Model model;
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException {//if you didn’t specify a page argument, display the
//first page (pageIndex = 0)
int pageIndex = request.getParameter("i") == null ? 0 :
Integer.parseInt((String)request.getParameter("i"));//make a call into the data model to get the i-th page
//and put the results in the request
request.setAttribute("results", model.getResults(pageIndex));//forward to your view (JSP)
request.getRequestDispatcher("view.jsp").forward(request, response);
}
In the view, you would simply print out the results, line-by-line, perhaps alternating row colors to make it easier to read. Then you need to display the little marker that tells you what page you're on and the buttons to get next/back/to the end/etc. The logic to figure out what buttons to display and which buttons to gray out is a little involved, but it's mundane enough that I don't think I need to cover it here. The important part for this discussion is what's in the links that actually take you forward and (soon) back. The answer there is simple enough -- just create links that append the appropriate querystring and you’re all set. Here’s an example:
<a href="http://bdg-plumtree:8080/bdg-plumtree-context/RecordView?i=<%=i + n%>">Next</a>
So far so good.
The problem is that when you try to do this in an ALUI portlet, you don’t have direct access to the querystring, so you can't use this approach. You need to store the variable i using some kind of state mechanism. Here are your options:
- The HTTP session
- A portlet setting
- A session setting (G6 and up only)
There are tradeoffs between #1 and #2 but #3 offers a good compromise. Let me explain.
If you use the HTTP session, your users' page setting (i
), will only persist for the life of the session, which is probably desirable. But, if you're using the Plumtree caching model for portlets (such as expires or last-modified/etag caching), you can't cache this portlet at all, which is definitely not desirable. The reason is that every page, regardless of the value of i
, will have the same cache key.
To implement session-based pagination, you only need to change two lines of code:
becomes
request.getParameter("i")request.getSession().getAttribute("i")
and your anchors that control the moving from page to page now need to point to a different controller servlet. (Remember, you don't have control over the query string any more in portletland).
<a href="http://bdg-plumtree:8080/bdg-plumtree-context/ChangePage?i=<%=i%>">Next</a>
The ChangePage servlet simply sets the session attribute and then calls return to portal as shown here:
request.getSession().setAttribute("i",
request.getParameter("i"));
PortletContextFactory.createPortletContext(request,
response).getResponse().returnToPortal();
The only way to unique-ify the cache key, therefore caching your portlet appropriately, is to go with approach #2, the portlet setting. Now, when users advance i
, they will be creating a new cache entry for each value of i
(since settings are always part of the cache key). The drawback is that the users' page settings (i
) will persist longer than the life of the session. In other words, they could be browsing page 5, then they could leave the portal for several days, come back, and still be on page 5!
For your view:
PortletContextFactory.createPortletContext(request,
response).getRequest().getSettingValue(SettingType.Portlet, "i");
And for your ChangePage servlet:
PortletContextFactory.createPortletContext(request,
response).getResponse().setSettingValue(SettingType.Portlet, "i", request.getParameter("i"));
PortletContextFactory.createPortletContext(request, response).getResponse().returnToPortal();
G6 offers a nice compromise: the session setting. (If I had to guess, I would say the session setting was designed expressly for pagination.) With a session setting, you get the best of both worlds: a page setting that lasts only for the duration of the session but also a unique cache key so that you can effectively cache your portlet.
For your view:
PortletContextFactory.createPortletContext(request,
response).getRequest().getSettingValue(SettingType.Session, "i");
And finally, for your ChangePage servlet:
PortletContextFactory.createPortletContext(request,
response).getResponse().setSettingValue(SettingType.Session, "i", request.getParameter("i"));
PortletContextFactory.createPortletContext(request,response).getResponse().returnToPortal();
All of these methods have one drawback -- they refresh the entire page on every portlet pagination click. So . . . stay tuned for an upcoming post on AJAX-based portlet pagination.
Comments
dev2dev comments are listed in date ascending order (oldest first)
- This is a good start for everyone on Java. If you are like me and are a .NET developer at heart, you will be glad to know the .NET Web Control Consumer supports the use of a MS data grid control. All that is needed is for the developer to drag one of these objects onto the form, hook it up to a data source, and presto, you get pagination, storability, edit, and any other thing you always wanted from a table.
Andrew Morris - andrew.morris@bdg-online.com
- It's true -- in many ways, .NET is way ahead of Java. I have only a little experience with the .NET Web Controls and the Control Consumer, but from what I've seen, there's a lot of power and flexibility there. My post was making the "I want to roll my own pagination in Java" assumption. :)