Sunday, 6 September 2009

Partial rendering with Spring MVC and Spring Javascript

Introduction
Almost all modern web pages uses many AJAX techniques. One of them is partial rendering - it allows user to rerender only part of a page instead of its full contents.

Main benefits are:
  • less response size sent via network
  • less time needed to show new page contents
  • less application server load
  • better user experience
Recently I was using partial rendering in JSF application with Ajax4JSF framework. To have wider perspecitve I was going to try another out of the box solution from lightweight stack like Spring MVC, Spring Javascript and Apache Tiles.

Application goals
Test application has a page layout configured with Tiles. Pages consist of menu on the left side and main contents on the right. Navigation links in menu are used to choose page contents for right side - it will be rerendered (Ajax) after each click on menu item.

Main page (rendering time is just to show ajax behaviour):


Page after ajax rerendering triggered by click in menu:


To prepare my sample application I used:
  • Maven 2.0.9
  • Java 1.6.0_12
  • Spring 2.5.6
  • Spring Javascript 2.0.5
  • Apache Tiles 2.1.0
Project structure
I used typical maven structure for the project:


























All project dependencies are configured in maven's pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.maciekm</groupId>
<artifactId>spring-js-demo</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>spring-js-demo Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>org.springframework.js</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-core</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>spring-js-demo</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Web application configuration
At first we need to configure our web app in web.xml:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<servlet>
<servlet-name>springjsdemo</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>Resource Servlet</servlet-name>
<servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
<init-param>
<param-name>allowedResourcePaths</param-name>
<param-value>/**/*.css,/**/*.gif,/**/*.ico,/**/*.jpeg,/**/*.jpg,/**/*.js,/**/*.png,META-INF/**/*.css,META-INF/**/*.gif,META-INF/**/*.ico,META-INF/**/*.jpeg,META-INF/**/*.jpg,META-INF/**/*.js,META-INF/**/*.png,META-INF/**/dojo/resources/blank.html,/dojo/resources/blank.html</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springjsdemo</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Resource Servlet</servlet-name>
<url-pattern>/resources/*</url-pattern>
</servlet-mapping>
</web-app>
Springjsdemo servlet is used to start spring web application context and as a front controller in our application. Resources Servlet is necessary for Spring Javascript. characterEncodingFilter is important when you want to use national characters on pages fragments rerendered by Ajax.

We also have to setup application context - springjsdemo-servlet.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<context:component-scan base-package="org.maciekm.springjsdemo"/>
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/tiles/pages.xml</value>
</list>
</property>
</bean>
<bean id="tilesViewResolver" class="org.springframework.js.ajax.AjaxUrlBasedViewResolver">
<property name="viewClass" value="org.springframework.js.ajax.tiles2.AjaxTilesView"/>
</bean>
</beans>
We configured Spring MVC to work with Apache Tiles. Spring MVC controller will be found and initialized by component scanning.

Pages layout
Tiles layouts are configured in pages.xml:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN"
"http://tiles.apache.org/dtds/tiles-config_2_1.dtd">
<tiles-definitions>
<definition name="welcome" template="/WEB-INF/jsp/template.jsp">
<put-attribute name="menu" value="/WEB-INF/jsp/menu.jsp"/>
<put-attribute name="body" value="/WEB-INF/jsp/welcome.jsp"/>
</definition>
<definition name="page1" extends="welcome">
<put-attribute name="body" value="/WEB-INF/jsp/page1.jsp"/>
</definition>
<definition name="page2" extends="welcome">
<put-attribute name="body" value="/WEB-INF/jsp/page2.jsp"/>
</definition>
</tiles-definitions>
Idea of using Tiles is not just to define page layouts. It helps us to define logical structure of pages so we will be able to rerender its parts.

Our layout template is in template.jsp and we configured 3 views based on it: welcome, page1 and page2. Each view consist of menu and body attributes.

Here we have template.jsp source:
<%@ page pageEncoding="UTF-8" %>
<%@ include file="include.jsp" %>
<html>
<head>
<title>Spring JS Demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Expires" CONTENT="-1">
<meta http-equiv="Content-Language" content="pl">
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate, Post-Check=0, Pre-Check=0">
<script type="text/javascript" src="<c:url value="/resources/dojo/dojo.js" />"> </script>
<script type="text/javascript" src="<c:url value="/resources/spring/Spring.js" />"> </script>
<script type="text/javascript" src="<c:url value="/resources/spring/Spring-Dojo.js" />"> </script>
</head>
<body>
<table cellpadding="5" cellspacing="5" border="1" width="100%">
<tr>
<td id="left" width="150px"><tiles:insertAttribute name="menu" /></td>
<td id="right"><tiles:insertAttribute name="body" /></td>
</tr>
</table>
</body>
</html>
JS files needed for Spring Javascript have been included.

Web pages
The core point of our application is menu. It contains links that will allow you to choose body of the web page and rerender it using Ajax.
<%@ page pageEncoding="UTF-8" %>
<%@ include file="include.jsp" %>
&nbsp;&nbsp;&nbsp;&nbsp;Menu:
<br/>
<br/>
<ul>
<li>
<form action="<c:url value='/page1.htm'/>" method="POST" id="page1Form" style="padding: 0px;margin: 0px;"></form>
<a id="page1" href="#">Page 1</a>
</li>
<li>
<form action="<c:url value='/page2.htm'/>" method="POST" id="page2Form" style="padding: 0px;margin: 0px;"></form>
<a id="page2" href="#">Page 2</a>
</li>
</ul>
<script type="text/javascript">
Spring.addDecoration(new Spring.AjaxEventDecoration({
elementId: "page1",
formId: "page1Form",
event: "onclick",
params: {fragments: "body"}
}));
Spring.addDecoration(new Spring.AjaxEventDecoration({
elementId: "page2",
formId: "page2Form",
event: "onclick",
params: {fragments: "body"}
}));
</script>
<br/><br/>
Rendering time:<br/><%=new java.util.Date()%>
Each link is "decorated" with special Spring Javascript code. AjaxEventDecoration will make Ajax request to server to ask for the page configured in form definition. However, it will ask only for specified page fragment - body.
Name of this fragment must be the same as name of Tiles attribute in layout of requested page.
On both current page as well as loaded fragment we need to have a div element with ID same as fragment name - body. Spring Javascript will be able to replace its old contents with new that just has been loaded from server.

Note: I used POST forms to define links becouse I had some problems with GET requests and cacheing in IE8. However it is not a recommended solution in a serious project. You should consider using GET with random parameters added just to avoid caching problem.

Default page body is defined in welcome.jsp as a part of welcome tiles view:
<%@ page pageEncoding="UTF-8" %>
<%@ include file="include.jsp" %>
<div id="body">
Main area
</div>
As I said, it contains body div as an container for ajax rerendering with its initial value.
Below we have alternative pages that can be selected from menu.

page1.jsp:
<%@ page pageEncoding="UTF-8" %>
<%@ include file="include.jsp" %>
<div id="body">
This is page 1. Rendering time: <%=new java.util.Date()%>
</div>
And page2.jsp:
<%@ page pageEncoding="UTF-8" %>
<%@ include file="include.jsp" %>
<div id="body">
This is page 2. Rendering time: <%=new java.util.Date()%>
</div>
We need also some less important jsp pages to make our application running.
We use include.jsp just to include some TLDs:
<%@ page pageEncoding="UTF-8" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
index.jsp is used only to redirect us from root view to our welcome page:
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<c:redirect url="welcome.htm"/>
Spring MVC controller
Of course we need MVC controller to map our views to urls. It our simple case it does not contain any business logic.

We map:
  • /welcome.htm -> welcome view
  • /page1.htm -> page1 view
  • /page2.html -> page2 view
Source code:
package org.maciekm.springjsdemo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MainController {

@RequestMapping("/welcome.htm")
public String welcomePage() {
return "welcome";
}

@RequestMapping("/page1.htm")
public String page1() {
return "page1";
}

@RequestMapping("/page2.htm")
public String page2() {
return "page2";
}
}
Running and testing
To run application type mvn jetty:run from console in main project directory. It should start Jetty server and deploy our webapp to http://localhost:8080/spring-js-demo. Point your browser to this url and test links in menu.

Hopefully our Ajax partial rendering works:)

9 comments:

  1. Maciej, thanks for this tutorial! It just what I was looking for!

    -
    http://fritzfs.blogspot.com

    ReplyDelete
  2. Hi there,

    I'm wondering if you can help me?

    I have managed to work through your tutorial code and got the example work.

    I am now trying to implement the functionality in my own application but something is lacking in my understanding?

    I have posted on the Spring forums

    http://forum.springsource.org/showthread.php?t=83573&highlight=partial+rendering

    If you can be of any help it would be very much appreciated?

    Thanks

    Eggsy
    http://eggsylife.co.uk

    ReplyDelete
  3. Hi, thank you for your comment.
    I tried to analyze your problem. Please look at my post at your spring forum topic.

    Maciej

    ReplyDelete
  4. Great examples. After spending an entire morning I finally get my test app working. A few points to note though:
    If you use tiles version higher than 2.1.0, you won't get tileConfigurer working. And I think it also depends on slf4j jar which maven didn't get it for me automatically. Anyway, anyone following this excellent tutorial better follow EXACTLY what's been listed here:)

    Maciej, I know I prolly should ask this in spring forum. But would you kindly share some more experience with spring js validation? I've tried it with my app and it successfully add the decoration on visually. But when I submit the form, values entered in those fancy looking widgets are not passed to server. And if a server side validation failed it, the page gets redisplayed has all widgets gone.

    ReplyDelete
  5. Hi Patrick,
    I am happy that my article helped you. Thanks for corrections.
    I was using spring js validation, but only in JSF application with Spring Faces tags like:

    <sf:clientTextValidator required="true">
    <h:inputText id="name" value="#{authorModel.name}" />
    </sf:clientTextValidator>

    It was working properly. But I guess it is not the case you are looking for.

    ReplyDelete
  6. Thanks for this it is really nice and very good one for the new guy or even for experienced

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. how to avoid double click event in, double click event is firing two ajax request at the same time.

    Spring.addDecoration(new Spring.AjaxEventDecoration({
    elementId : "mypageid",
    event : "onclick",
    params : {
    fragments : "body"
    }
    }));

    ReplyDelete
  9. Hi Maciej, you could upload the project?
    Thank you.

    ReplyDelete