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
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
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"Web application configuration
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>
At first we need to configure our web app in web.xml:
<!DOCTYPE web-app PUBLICSpringjsdemo 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.
"-//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>
We also have to setup application context - springjsdemo-servlet.xml:
<?xml version="1.0" encoding="UTF-8"?>We configured Spring MVC to work with Apache Tiles. Spring MVC controller will be found and initialized by component scanning.
<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>
Pages layout
Tiles layouts are configured in pages.xml:
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.<?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>
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" %>JS files needed for Spring Javascript have been included.
<%@ 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>
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" %>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.
<%@ include file="include.jsp" %>
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()%>
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" %>And page2.jsp:
<%@ include file="include.jsp" %>
<div id="body">
This is page 1. Rendering time: <%=new java.util.Date()%>
</div>
<%@ page pageEncoding="UTF-8" %>We need also some less important jsp pages to make our application running.
<%@ include file="include.jsp" %>
<div id="body">
This is page 2. Rendering time: <%=new java.util.Date()%>
</div>
We use include.jsp just to include some TLDs:
<%@ page pageEncoding="UTF-8" %>index.jsp is used only to redirect us from root view to our welcome page:
<%@ 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" %>
<%@ include file="/WEB-INF/jsp/include.jsp" %>Spring MVC controller
<c:redirect url="welcome.htm"/>
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
package org.maciekm.springjsdemo.controller;Running and testing
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";
}
}
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:)