Tutorial #2 - Tiles
Quadriga as well as several other of the Java/Spring web applications developed in the Digital Innovation Group use Apache Tiles for managing the layout of the application. This tutorial will you walk through a very basic example of how to implement Apache Tiles with Spring.
This tutorial is based on Tutorial #1.
Configuration
First of all, let's add the Tiles dependencies to our pom.xml file. Inside the dependencies tag, add the following dependency declarations:
<dependency> <groupId>org.apache.tiles</groupId> <artifactId>tiles-template</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>org.apache.tiles</groupId> <artifactId>tiles-core</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>org.apache.tiles</groupId> <artifactId>tiles-api</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>org.apache.tiles</groupId> <artifactId>tiles-servlet</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>org.apache.tiles</groupId> <artifactId>tiles-jsp</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency>
Next, open the servlet-context.xml file in your WEB-INF > spring folder. From there, remove the InternalResourceViewResolver bean declaration and add the following code:
<beans:bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver" > <beans:property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/> </beans:bean> <beans:bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> <beans:property name="definitions"> <beans:list> <beans:value>/WEB-INF/tiles-def.xml</beans:value> </beans:list> </beans:property> </beans:bean>
Your servlet-context.xml file should now look like this:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="edu.asu.diging.tutorial.spring" /> <bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver" > <property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/> </bean> <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> <property name="definitions"> <list> <value>/WEB-INF/tiles-def.xml</value> </list> </property> </bean> </beans>
You declared two beans in this piece of XML. First, you specify that you want to use a UrlBasedViewResolver to resolve your views. As a property you give the resolver a TilesView. The second bean, the TilesConfigurer, "simply configures a TilesContainer using a set of files containing definitions, to be accessed by TilesView
instances. This is a Spring-based alternative (for usage in Spring configuration) to the Tiles-provided ServletContextListener
(e.g. CompleteAutoloadTilesListener
for usage in web.xml
.)" (Spring TilesConfigurer Javadoc)
Inside the "definitions" property, you specify where Tiles can find the definitions telling it what templates to render. In our example, the definitions are in a file called "tiles-def.xml" inside the WEB-INF folder.
Tiles Definitions
Ok, now let's create the tiles-def.xml file we just told Tiles about. Create a new file inside your WEB-INF folder, and paste the following code in it:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN" "http://tiles.apache.org/dtds/tiles-config_2_0.dtd"> <tiles-definitions> <!-- Base definition --> <definition name="base.definition" template="/WEB-INF/tiles/layouts/skeleton.jsp"> <put-attribute name="title" value="" /> <put-attribute name="currentPage" value="home" /> </definition> <!-- Home pages --> <definition name="home" extends="base.definition"> <put-attribute name="title" value="Quadriga - Login" /> <put-attribute name="content" value="/WEB-INF/views/home.jsp" /> <put-attribute name="footer" value="/WEB-INF/views/footer.jsp" /> </definition> </tiles-definitions>
For now, let's leave it with that. We'll come back to it later with more explanations.
The Skeleton
So far, we have talked about how to use Apache Tiles, however, we haven't said anything about why we would use it. Apache is a templating framework with the goal to make it easier to write and maintain webpages across an application. Remember, how in the first tutorial we wrote jsp pages like this:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page isELIgnored="false" %> <html> <body> <h2>Hello World!</h2> I am feeling: ${ mood.feeling }. </body> </html>
We had to specify a complete HTML page in each jsp page. But usually, we want all of our pages look the same. They should have the same footer, header, menubar, etc. So, if any change is required (for example adding a copyright notice in the footer of every page) after we already wrote a couple of jsp pages, we would have to go back and change every single jsp page. Not very desirable... So, Apache Tiles allows us to specify a footer jsp page, a header jsp page, a menu jsp page, etc. and then simply builds the pages out of these different components on the fly.
For Tiles to do its job, we first need to tell it, how the different components of our website should be assembled. We do this by writing a so-called skeleton page. Go to your WEB-INF folder and create a folder called "tiles", then create a folder called "layouts" in the newly created "tiles" folder. Your webapp folder should now have the following structure:
Next, create a file called "skeleton.jsp" inside the layouts folder. Paste the following code into skeleton.jsp:
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!DOCTYPE HTML> <html> <head> <title><tiles:insertAttribute name="title" /></title> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> </head> <body> <tiles:importAttribute name="currentPage" scope="request" /> <!-- Main --> <tiles:insertAttribute name="content" /> <!-- Footer --> <div> <tiles:insertAttribute name="footer" /> </div> </body> </html>
You just create the "base.definition" from line 6 of your tiles-def.xml file. The skeleton defines 4 attributes: title, currentPage, content, and footer. When Tiles renders a page, it'll will take the skeleton and replace the specified attributes with the values found in your tiles-def.xml. However, you will notice that the "base.definition" in our tiles-def.xml only defines two attributes: title and currentPage. This is due to the fact, that "base.definition" is intended to be extended by other definitions. It only defines basic default values that can be overridden by the extending definitions.
The second definition in tiles-def.xml specifies a complete page:
<definition name="home" extends="base.definition"> <put-attribute name="title" value="Home" /> <put-attribute name="content" value="/WEB-INF/views/home.jsp" /> <put-attribute name="footer" value="/WEB-INF/views/footer.jsp" /> </definition>
In line 1, you can see two attributes "name" and "extends". Name specifies the name of the definition (we will use the name later to specify what definition we want to use for a page). The extends attribute specifies that the current definition extends "base.definition". It therefore inherits all the attributes from base.definition. Line 2 overrides the title attribute from the extended definition and sets it to "Home". Line 3 and 4 specify what jsp page should be used for the content and footer section of the webpage.
Home and Footer
Now, let's create two more jsp pages to satisfy the "home" definition. Go to the folder WEB-INF > views and create two jsp pages: home.jsp and footer.jsp. Your folder structure should look like this now:
Paste the following code into home.jsp:
<h2>Hello World!</h2> I am feeling: ${ mood.feeling }. See <a href="explanation/${mood.feeling}">here</a> to find out why.
You might notice, this is the exact same code that we used in index.jsp before. Next, paste the following code into footer.jsp:
This page was generated by me.
The Controller
Now, we're all set up on the jsp side! Let's take a look at the controller. Open the HomeController in the "web" package. The home method should currently return "index" (or "index2" depending on how you ended tutorial #1). Change this to return "home". The home method should now look like this:
@RequestMapping(value = "/") public String home(ModelMap map) { map.addAttribute("mood", service.getCurrentMood()); return "home"; }
Run it!
Ok, time for a first try. Let's run the application on a server (or restart the server if you already deployed the application before) and go to http://localhost:8080/tiles (or http://localhost:8080/one if you use the same project as in tutorial #1). You should see something like this:
Whoohoo! Our page is up. However, when clicking on the "here" link will bring up an error message saying:
"javax.servlet.ServletException: Could not resolve view with name 'explanation' in servlet with name 'dispatcher'"
What's happening? Well, let's take a look at the controller that backs the second page. In the ExplanationController we can see that the method for showing the explanation, returns "explanation":
@RequestMapping(value="/explanation/{mood}") public String showExplanation(@PathVariable("mood") String mood, Model model) { model.addAttribute("mood", mood); model.addAttribute("explanation", moodService.getExplanation()); return "explanation"; }
The error message tells us exactly where the problem is: there is no definition with name "explanation", so Tiles doesn't know what page to render. In the HomeController we returned "home". Tiles used that string to lookup what definition would describe that view (the definition with name "home"). For "explanation", however, there is no definition. Let's create one!
Adding a new definition
Open the file tiles-def.xml, and add the following lines after the home definition:
<!-- Explanation page --> <definition name="explanation" extends="base.definition"> <put-attribute name="title" value="Explanation" /> <put-attribute name="content" value="/WEB-INF/views/explanation.jsp" /> <put-attribute name="footer" value="/WEB-INF/views/footer.jsp" /> </definition>
Similar to the home definition, we're overriding the title attribute with the string "Explanation", we use the same footer.jsp page for the footer attribute, but we use a different jsp page for the content attribute. In other words, the page should look exactly like the Home page, but have a different content (given that we didn't do add layout to the page, it's not difficult to create a page that looks exactly like the first one, but it's the principle that counts here!).
What's still missing is the explanation.jsp page. Or actually, if you called the explanation page in tutorial #1 "explanation.jsp", then you already have that! If so, open that jsp page. Delete everything that is not inside the "body" tag. It should look similar to this:
<h2>So you want to know why...</h2> I am feeling ${ mood } because ${explanation} <p> <a href="/tiles">Maybe my mood has changed?</a>
If you don't have a explanation.jsp file yet, go to the folder WEB-INF > views and create a file called "explanation.jsp". Then post the above content in it (of course taking into account changes necessary for you specific application).
Restart the server and try it again. You should now be able to happily switch between your two pages!
You can find the code for this tutorial here.
Now what?
This was just a very short introduction to Tiles. In this simple example, it might not seem like much, but image you have 100 jsp pages all with the same layout, and at least some using the same components (like menus or comment sections). Tiles can help you manage the different components independent of each other, avoiding repetitions in your code.
If you'd like to work a little more with Tiles, try to implement the following:
- Add a third "About" page similar to the explanation page.
- Add a menu to each page that links to the Home and About page.
- On every page, only show the links in the menu that are not the current page (e.g. show only a link to Home on the About page, but not to About).