When it comes to web application development, reusability and maintenance are two important factors that need to be considered. Apache Tiles is another popular open source framework that encourages reusable template-based web application development.
In this chapter, you are going to see how to incorporate the Apache Tiles framework within a Spring MVC application, so that we can obtain maximum reusability of frontend templates with the help of Apache Tiles. Apache Tiles are mostly used to reduce redundant code in the frontend by leveraging frontend templates. After finishing this chapter, you will have a basic idea about decomposing pages using reusable Apache Tile templates.
In the past, we developed a series of web pages (Views) as part of our webstore
application, such as a page to show products, another page to add products, and so on. Although every View has served a different purpose, all of them share a common visual pattern; each page has a header, a content area, and so on. We hardcoded and repeated those common elements in every JSP View page. But this is not a good idea because, in future, if we want to change the look and feel of any of these common elements, we have to change every page in order to maintain a consistent look and feel across all the web pages.
To address this problem, modern web applications use template mechanisms; Apache Tiles is one such template composition framework. Tiles allow developers to define reusable page fragments (tiles), which can be assembled into a complete web page at runtime. These fragments can have parameters to allow dynamic content. This increases the reusability of templates and reduces code duplication.
Okay, enough introduction, let's dive into Apache Tiles by defining a common layout for our web application and let the pages extend the layout:
pom.xml
. You can find pom.xml
under the project root directory itself.pom.xml
; select the Dependencies tab and click on the Add button of the Dependencies section.org.apache.tiles
as Group Id, tiles-extras
as Artifact Id, 3.0.5
as Version, and select Scope as compile. Then click on the OK button and save pom.xml
.layouts/definitions/
under the src/main/webapp/WEB-INF/
directory and create an XML file called tiles.xml
. Add the following content to it and save it:<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" "http://tiles.apache.org/dtds/tiles-config_3_0.dtd"> <tiles-definitions> <definition name="baseLayout" template="/WEB- INF/layouts/template/baseLayout.jsp"> <put-attribute name="title" value="Sample Title" /> <put-attribute name="heading" value="" /> <put-attribute name="tagline" value="" /> <put-attribute name="navigation" value="/WEB- INF/layouts/template/navigation.jsp" /> <put-attribute name="content" value="" /> <put-attribute name="footer" value="/WEB- INF/layouts/template/footer.jsp" /> </definition> <definition name="welcome" extends="baseLayout"> <put-attribute name="title" value="Products" /> <put-attribute name="heading" value="Products" /> <put-attribute name="tagline" value="All the available products in our store" /> <put-attribute name="content" value="/WEB- INF/views/products.jsp" /> </definition> <definition name="products" extends="baseLayout"> <put-attribute name="title" value="Products" /> <put-attribute name="heading" value="Products" /> <put-attribute name="tagline" value="All the available products in our store" /> <put-attribute name="content" value="/WEB- INF/views/products.jsp" /> </definition> <definition name="product" extends="baseLayout"> <put-attribute name="title" value="Product" /> <put-attribute name="heading" value="Product" /> <put-attribute name="tagline" value="Details" /> <put-attribute name="content" value="/WEB- INF/views/product.jsp" /> </definition> <definition name="addProduct" extends="baseLayout"> <put-attribute name="title" value="Products" /> <put-attribute name="heading" value="Products" /> <put-attribute name="tagline" value="Add Product" /> <put-attribute name="content" value="/WEB- INF/views/addProduct.jsp" /> </definition> <definition name="login" extends="baseLayout"> <put-attribute name="title" value="Login" /> <put-attribute name="heading" value="Welcome to Web Store!" /> <put-attribute name="tagline" value="The one and only amazing web store" /> <put-attribute name="content" value="/WEB- INF/views/login.jsp" /> </definition> <definition name="cart" extends="baseLayout"> <put-attribute name="title" value="Shopping Cart" /> <put-attribute name="heading" value="Cart" /> <put-attribute name="tagline" value="All the selected products in your cart" /> <put-attribute name="content" value="/WEB- INF/views/cart.jsp" /> </definition> </tiles-definitions>
template
under the src/main/webapp/WEB-INF/layouts/
directory and create a JSP file called baseLayout.jsp
. Add the following content to it and save it:<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles"%> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><tiles:insertAttribute name="title" /></title> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css /bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs /angularjs/1.5.1/angular.min.js"></script> <script src="/webstore/resources/js/controllers.js"></script> </head> <body> <section class="container"> <div class="pull-right" style="padding-right: 50px"> <a href="?language=en">English</a>|<a href="?language=nl">Dutch</a> <a href="<c:url value="/logout" />">Logout</a> </div> </section> <div class="container"> <div class="jumbotron"> <div class="header"> <ul class="nav nav-pills pull-right"> <tiles:insertAttribute name="navigation" /> </ul> <h3 class="text-muted">Web Store</h3> </div> <h1> <tiles:insertAttribute name="heading" /> </h1> <p> <tiles:insertAttribute name="tagline" /> </p> </div> <div class="row"> <tiles:insertAttribute name="content" /> </div> <div class="footer"> <tiles:insertAttribute name="footer" /> </div> </div> </body> </html>
template
), create another template JSP file called navigation.jsp
and add the following content to it:<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <li><a href="<spring:url value="/market/products"/>">Home</a></li> <li><a href="<spring:url value="/market/products/"/>">Products</a></li> <li><a href="<spring:url value="/market/products/add"/>">Add Product</a></li> <li><a href="<spring:url value="/cart/"/>">Cart</a></li>
footer.jsp
and add the following content to it:<p>© Company 2016</p>
jumbotron
section and others, from our products.jsp
file, and keep only the container section, it would look as follows:<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <section class="container"> <div class="row"> <c:forEach items="${products}" var="product"> <div class="col-sm-6 col-md-3" style="padding-bottom: 15px"> <div class="thumbnail"> <img src="<c:url value="/img/${product.productId}.png"></c:url>" alt="image" style="width: 100%" /> <div class="caption"> <h3>${product.name}</h3> <p>${product.description}</p> <p>$${product.unitPrice}</p> <p>Available ${product.unitsInStock} units in stock</p> <p> <a href=" <spring:url value="/market/product?id=${product.productId}" /> " class="btn btn-primary"> <span class="glyphicon-info-sign glyphicon" /></span> Details </a> </p> </div> </div> </div> </c:forEach> </div> </section>
/src/main/webapp/WEB-INF/views
directory; do not remove the taglib
references.TilesConfig
under the com.packt.webstore.config
package in the src/main/java
source folder, and add the following code to it:package com.packt.webstore.config; import org.springframework.context.annotation.Bean; import org.springframework.context .annotation.Configuration; import org.springframework.web.servlet .view.UrlBasedViewResolver; import org.springframework.web.servlet .view.tiles3.TilesConfigurer; import org.springframework.web.servlet .view.tiles3.TilesView; @Configuration public class TilesConfig { @Bean public UrlBasedViewResolver viewResolver() { UrlBasedViewResolver viewResolver = new UrlBasedViewResolver(); viewResolver.setViewClass (TilesView.class); viewResolver.setOrder(-2); return viewResolver; } @Bean public TilesConfigurer tilesConfigurer() { TilesConfigurer tilesConfigurer = new TilesConfigurer(); tilesConfigurer.setDefinitions("/WEB- INF/layouts/definitions/tiles.xml"); tilesConfigurer.setCheckRefresh(true); return tilesConfigurer; } }
http://localhost:8080/webstore/market/products
. You will be able to see our regular products page with an extra navigation bar at the top and a footer at the bottom. You can add a product by clicking on the Add Product link:To work with Apache Tiles, we need Apache Tiles related JARs, so from steps 1 to 3 we added those JARs via Maven dependencies. Step 4 is very important because we created our tiles definition file (tiles.xml
) in that step. Understanding the tiles definition file is crucial to developing Apache Tiles-based applications, so you need to understand our tile definition file.
A tile definition file is a collection of definitions, where each definition can be associated with a template via the template
attribute for the layout:
<definition name="baseLayout" template="/WEB-INF/layouts/template/baseLayout.jsp"> <put-attribute name="title" value="Sample Title" /> <put-attribute name="heading" value="Sample Heading" /> <put-attribute name="tagline" value="Sample Tagline" /> <put-attribute name="navigation" value="/WEB-INF/layouts/template/navigation.jsp" /> <put-attribute name="content" value="" /> <put-attribute name="footer" value="/WEB-INF/layouts/template/footer.jsp" /> </definition>
Within each definition, we can define many attributes. These attributes can be a simple text value or a full-blown markup file. These attributes would be available in the template file via the <tiles:insertAttribute>
tag. For example, if you open the base layout (baseLayout.jsp
) template, you can see the following snippet under the jumbotron
<div>
tag:
<h1> <tiles:insertAttribute name="heading" /> </h1> <p> <tiles:insertAttribute name="tagline" /> </p>
So at runtime, Apache Tiles would replace the <tiles:insertAttribute name="heading" />
tag with the value Sample Heading
and similarly the <tiles:insertAttribute name="tagline" />
tag with the value Sample Tagline
.
So the baseLayout
definition is associated with the template /WEB-INF/layouts/template/baseLayout.jsp
, and we can insert the defined attributes such as tile
, heading
, tagline
, and more in the template using the <tiles:insertAttribute>
tag.
Apache Tiles allows us to extend a definition just like how we extend a Java class, so that the defined attributes would be available for the derived definition, and we can even override those attributes values if we want. For example, look at the following definition from tile-definition.xml
:
<definition name="products" extends="baseLayout"> <put-attribute name="title" value="Products" /> <put-attribute name="heading" value="Products" /> <put-attribute name="tagline" value=" All the available products in our store" /> <put-attribute name="content" value="/WEB-INF/views/products.jsp" /> </definition>
This definition is an extension of the baseLayout
definition. We have only overridden the title
, heading
, tagline
, and content
attributes, and since we have not defined any template for this definition, it uses the same template that we configured for the baseLayout
definition.
Similarly, we defined the tile definition for every possible logical View name that can be returned from our Controllers. Note that each definition name (except the baseLayout
definition) is a Spring MVC logical View name.
From steps 5 to 7, we just created the templates that can be used in the tile definition. First, we created the base layout template (baseLayout.jsp
), then the navigation template (navigation.jsp
), and finally the footer template (footer.jsp
).
Steps 8 and 9 explained how to remove the existing redundant content such as the jumbotron
<div>
tag from every JSP View page. Note you have to be careful while doing this—don't accidentally remove the taglib
references.
In step 10, we defined our UrlBasedViewResolver
for TilesView
in order to resolve logical View names into the tiles View and also configured the TilesConfigurer
to locate the tiles definition files by the Apache Tiles framework.
That's it; if you run our application and enter the URL http://localhost:8080/webstore/products
, you will be able to see our regular products page with an extra navigation bar at the top and a footer at the bottom, as mentioned in step 11. You can add a product page by clicking on the Add Product link. Previously, every time a logical View name was returned by the Controller method, the InternalResourceViewResolver
comes into action and finds the corresponding jsp
View for the given logical View name. Now for every logical View name, the UrlBasedViewResolver
will come into action and compose the corresponding View based on the template definition.
Which of the following statements are true according to Apache Tiles?
<definition>
tag name.The <tiles:insertAttribute>
tag acts as a placeholder in the template.<definition>
tag can extend another <definition>
tag.