AJAX in a JSR-168 Portlet

This page is out of date. uPortal supports the JSR-286 Portlet 2.0 spec and there are better strategies for doing Ajax using ResourceRequests. This page is retained for historic reasons.

TBD: Create or find page that describes using ResourceRequests for Ajax interactions. In the mean time, look at the Apereo CalendarPortlet or Apereo EmailPreviewPortlet for examples of using Ajax in a portlet.

Recommended solutions for using AJAX in a portlet

Most of the solutions described on this page require Tomcat to be configured with emptySessionPath set to true.  For more information on this configuration setting, please refer to the uPortal Manual.  The appropriate section for uPortal 3.1 is found here.

AJAX Portlet Support Problem Overview

An approach to perform AJAX callbacks to a portlet completely within the JSR-168 specification. An example portlet and generic support library for this approach are also linked to below.

AJAX in Portlet Hurdles

  • URL structure, including parameter name format, is dictated by the portal. This results in restrictions in how the JavaScript performs the callbacks.
  • Portlets render just a fragment of content that is surrounded by portal content, this means Portlet.render is not an option for responding to AJAX callbacks as the entire portal-page renders.

AJAX in Portlet Solutions

  • There are no limitations to the parameter names in a form POSTed to a Portlet ActionUrl. This allows AJAX code to perform requests via a POST to an ActionUrl rendered by the portlet.
  • During a Portlet.processAction the portlet can send a HTTP 302 redirect to a Servlet packaged in the same WAR. This allows the Servlet to handle rendering the response to the request.
    • To pass data from the Portlet processAction to the Servlet object data is placed in the PortletSession at the APPLICATION_SCOPE using a random key which is then passed on the Servlet URL.

AJAX in Portlet Support

An add-on library has been developed to provide the common base services around doing AJAX in a Portlet following the above formula. The support library can be used by depending on the following Maven artifact:

<dependency>
    <groupId>org.jasig</groupId>
    <artifactId>AjaxPortletSupport</artifactId>
    <version>1.0.9</version>
</dependency>

The code for the support library is available in SVN at: https://source.jasig.org/sandbox/AjaxPortletSupport/tags/rel-1.0.9/
An example portlet using this library is available in SVN at: https://source.jasig.org/sandbox/AjaxNotepadPortlet/trunk/

Setup

Application Setup

In applicationContext.xml or some other context file that's loaded by web.xml, add the following:

applicationContext.xml
<bean id="ajaxResponseController" class="org.jasig.web.servlet.mvc.AjaxResponseController"/>

    <bean class="org.springframework.web.servlet.view.XmlViewResolver"
    	p:order="0"/>

By default XmlViewResolver looks for WEB-INF/views.xml
jsonView automatically digests your model objects and represents them as JSON. You can probably tweak this by manipulating JsonConfig in the jsonView bean.

views.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="jsonView" class="net.sf.json.spring.web.servlet.view.JsonView">
        <property name="contentType" value="application/json" />
    </bean>
</beans>

Set the order parameter for your existing view resolvers to something greater than 0.

Freemarker View Resolver example
<bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"
		p:order="1"
		p:cache="true"
		p:prefix=""
		p:suffix=".ftl"/>

AJAX Servlet

The AJAX Servlet listens for AJAX requests. Add the following sections to your web.xml file in the appropriate locations.

web.xml excerpt
<servlet>
        <servlet-name>AjaxResponseServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/ajaxResponseServletContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

<servlet-mapping>
        <servlet-name>AjaxResponseServlet</servlet-name>
        <url-pattern>/ajaxResponse</url-pattern>
    </servlet-mapping>

Create this file in the location specified above.

ajaxResponseServletContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="order" value="0"/>
        <property name="alwaysUseFullPath" value="true"/>
        <property name="defaultHandler" ref="ajaxResponseController" />
        <property name="urlMap">
            <map>
                <entry key="/ajaxResponse" value-ref="ajaxResponseController"/>
            </map>
        </property>
    </bean>

    <bean id="requiredAnnotationBeanPostProcessor" class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor" />
</beans>

AjaxPortletSupport Usage

There are two approaches for using the support library.

Option 1 - Inheritance based Spring Controllers

Extend org.jasig.web.portlet.mvc.AbstractAjaxController and implement the Map<Object, Object> handleAjaxRequestInternal(ActionRequest, ActionResponse) method.

protected Map<String, Object> handleAjaxRequestInternal(ActionRequest request, ActionResponse response) throws Exception {
    final Map<String, Object> model = new HashMap<String, Object>();
    final Data d = this.applicationApi.getData(request);
    model.put("data", d);
    return model;
}

Configure your implementation of AbstractAjaxController as you would any other controller.

Configure org.jasig.web.servlet.mvc.AjaxResponseController as a Servlet controller. The default path that AbstractAjaxController expects for the controller is ajaxResponse but that can be configured via the ajaxServletName property. The AjaxResponseController renders with the view named jsonView by default, the view name can be configured via the viewName property.

Your application code then should POST via a portlet ActionURL to your AbstractAjaxController implementation. Once the data is returned from handleAjaxRequestInternal it will be stored in the user's session with a random key and then a redirect is sent to the rendering servlet with the key. The rendering servlet gets the data out of the session and returns it as part of the ModelAndView to render.

Option 2 - Using SessionKeyGenerator and ModelPasser Directly

If your application is using a different MVC framework or the Spring annotation based MVC support you'll need to use the ajax support service API directly.

The org.jasig.web.service.AjaxPortletSupport interface defines methods for passing a data model to the rendering serlvet and for getting the data model in the rendering servlet. The default implementation org.jasig.web.service.AjaxPortletSupportService should be sufficient for most uses.

In the code that handles the portlet ActionRequest the following logic flow is used:

this.ajaxPortletSupportService.redirectAjaxResponse(this.ajaxServletName, request, response, new ModelGenerator() {
    public Map<Object, Object> generate(ActionRequest request, ActionResponse response) throws Exception {
        final Map<Object, Object> model = new HashMap<Object, Object>();
        final Data d = applicationApi.getData(request);
        model.put("data", d);
        return model;
    }
});

With an annotated controller method, this looks something like this. (Note: This uses an alternate way of passing the model)

Annotated Controller Method Example
@RequestMapping(params="action=ajaxTest")
public void ajaxTest(ActionRequest request,ActionResponse response) throws Exception {
   final Map<String, Object> model = new HashMap<String, Object>();
   model.put("testData", "foo");
   this.ajaxPortletSupportService.redirectAjaxResponse(this.ajaxServletName, model, request, response);
}

In the servlet that renders the response to the AJAX request the following logic flow is used: (You don't need to implement this yourself)

final Map<Object, Object> model = this.ajaxPortletSupportService.getAjaxModel(request, response);
return new ModelAndView(this.viewName, model);

AjaxPortletSupportService.getAjaxModel will set a return code of 404 if no model is found in the session for the key, null is returned by the call in this case. If an error generating the model was signaled by the AjaxPortletSupportService.redirectAjaxResponse a 500 return code is set and the model which should contain the causing exception is returned by the call in this case.

AJAX Portlet Support Request Diagram

In the code that handles the processAction request the following logic is needed: