![]() |
One of the areas in which Spring excels is in the separation of view technologies from the rest of the MVC framework. For example, deciding to use Velocity or XSLT in place of an existing JSP is primarily a matter of configuration. This chapter covers the major view technologies that work with Spring and touches briefly on how to add new ones. This chapter assumes you are already familiar with Section 16.5, “Resolving views” which covers the basics of how views in general are coupled to the MVC framework. Spring provides a couple of out-of-the-box solutions for JSP and JSTL views. Using JSP
or JSTL is done using a normal view resolver defined in the
Just as with any other view technology you’re integrating with Spring, for JSPs you’ll
need a view resolver that will resolve your views. The most commonly used view resolvers
when developing with JSPs are the <!-- the ResourceBundleViewResolver --> <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename" value="views"/> </bean> # And a sample properties file is uses (views.properties in WEB-INF/classes): welcome.(class)=org.springframework.web.servlet.view.JstlView welcome.url=/WEB-INF/jsp/welcome.jsp productList.(class)=org.springframework.web.servlet.view.JstlView productList.url=/WEB-INF/jsp/productlist.jsp As you can see, the <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> The When using the Java Standard Tag Library you must use a special view class, the
Spring provides data binding of request parameters to command objects as described in earlier chapters. To facilitate the development of JSP pages in combination with those data binding features, Spring provides a few tags that make things even easier. All Spring tags haveHTML escaping features to enable or disable escaping of characters. The tag library descriptor (TLD) is included in the As of version 2.0, Spring provides a comprehensive set of data binding-aware tags for handling form elements when using JSP and Spring Web MVC. Each tag provides support for the set of attributes of its corresponding HTML tag counterpart, making the tags familiar and intuitive to use. The tag-generated HTML is HTML 4.01/XHTML 1.0 compliant. Unlike other form/input tag libraries, Spring’s form tag library is integrated with Spring Web MVC, giving the tags access to the command object and reference data your controller deals with. As you will see in the following examples, the form tags make JSPs easier to develop, read and maintain. Let’s go through the form tags and look at an example of how each tag is used. We have included generated HTML snippets where certain tags require further commentary. The form tag library comes bundled in To use the tags from this library, add the following directive to the top of your JSP page: <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> where This tag renders an HTML form tag and exposes a binding path to inner tags for
binding. It puts the command object in the Let’s assume we have a domain object called <form:form> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form> The The generated HTML looks like a standard form: <form method="POST"> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value="Harry"/></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value="Potter"/></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form> The preceding JSP assumes that the variable name of the form backing object is
<form:form commandName="user"> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form> This tag renders an HTML input tag using the bound value and type=text by default. For an example of this tag, see the section called “The form tag”. Starting with Spring 3.1 you can use other types such HTML5-specific types like email, tel, date, and others. This tag renders an HTML input tag with type checkbox. Let’s assume our public class Preferences { private boolean receiveNewsletter; private String[] interests; private String favouriteWord; public boolean isReceiveNewsletter() { return receiveNewsletter; } public void setReceiveNewsletter(boolean receiveNewsletter) { this.receiveNewsletter = receiveNewsletter; } public String[] getInterests() { return interests; } public void setInterests(String[] interests) { this.interests = interests; } public String getFavouriteWord() { return favouriteWord; } public void setFavouriteWord(String favouriteWord) { this.favouriteWord = favouriteWord; } } The <form:form> <table> <tr> <td>Subscribe to newsletter?:</td> <%-- Approach 1: Property is of type java.lang.Boolean --%> <td><form:checkbox path="preferences.receiveNewsletter"/></td> </tr> <tr> <td>Interests:</td> <%-- Approach 2: Property is of an array or of type java.util.Collection --%> <td> Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/> Herbology: <form:checkbox path="preferences.interests" value="Herbology"/> Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/> </td> </tr> <tr> <td>Favourite Word:</td> <%-- Approach 3: Property is of type java.lang.Object --%> <td> Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/> </td> </tr> </table> </form:form> There are 3 approaches to the
Note that regardless of the approach, the same HTML structure is generated. Below is an HTML snippet of some checkboxes: <tr> <td>Interests:</td> <td> Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/> <input type="hidden" value="1" name="_preferences.interests"/> Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/> <input type="hidden" value="1" name="_preferences.interests"/> Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/> <input type="hidden" value="1" name="_preferences.interests"/> </td> </tr> What you might not expect to see is the additional hidden field after each checkbox.
When a checkbox in an HTML page is not checked, its value will not be sent to the
server as part of the HTTP request parameters once the form is submitted, so we need a
workaround for this quirk in HTML in order for Spring form data binding to work. The
This tag renders multiple HTML input tags with type checkbox. Building on the example from the previous <form:form> <table> <tr> <td>Interests:</td> <td> <%-- Property is of an array or of type java.util.Collection --%> <form:checkboxes path="preferences.interests" items="${interestList}"/> </td> </tr> </table> </form:form> This example assumes that the "interestList" is a This tag renders an HTML input tag with type radio. A typical usage pattern will involve multiple tag instances bound to the same property but with different values. <tr> <td>Sex:</td> <td> Male: <form:radiobutton path="sex" value="M"/> <br/> Female: <form:radiobutton path="sex" value="F"/> </td> </tr> This tag renders multiple HTML input tags with type radio. Just like the <tr> <td>Sex:</td> <td><form:radiobuttons path="sex" items="${sexOptions}"/></td> </tr> This tag renders an HTML input tag with type password using the bound value. <tr> <td>Password:</td> <td> <form:password path="password" /> </td> </tr> Please note that by default, the password value is not shown. If you do want the
password value to be shown, then set the value of the <tr> <td>Password:</td> <td> <form:password path="password" value="^76525bvHGq" showPassword="true" /> </td> </tr> This tag renders an HTML select element. It supports data binding to the selected
option as well as the use of nested Let’s assume a <tr> <td>Skills:</td> <td><form:select path="skills" items="${skills}"/></td> </tr> If the <tr> <td>Skills:</td> <td> <select name="skills" multiple="true"> <option value="Potions">Potions</option> <option value="Herbology" selected="selected">Herbology</option> <option value="Quidditch">Quidditch</option> </select> </td> </tr> This tag renders an HTML option. It sets selected as appropriate based on the bound value. <tr> <td>House:</td> <td> <form:select path="house"> <form:option value="Gryffindor"/> <form:option value="Hufflepuff"/> <form:option value="Ravenclaw"/> <form:option value="Slytherin"/> </form:select> </td> </tr> If the <tr> <td>House:</td> <td> <select name="house"> <option value="Gryffindor" selected="selected">Gryffindor</option> <option value="Hufflepuff">Hufflepuff</option> <option value="Ravenclaw">Ravenclaw</option> <option value="Slytherin">Slytherin</option> </select> </td> </tr> This tag renders a list of HTML option tags. It sets the selected attribute as appropriate based on the bound value. <tr> <td>Country:</td> <td> <form:select path="country"> <form:option value="-" label="--Please Select"/> <form:options items="${countryList}" itemValue="code" itemLabel="name"/> </form:select> </td> </tr> If the <tr> <td>Country:</td> <td> <select name="country"> <option value="-">--Please Select</option> <option value="AT">Austria</option> <option value="UK" selected="selected">United Kingdom</option> <option value="US">United States</option> </select> </td> </tr> As the example shows, the combined usage of an The This tag renders an HTML textarea. <tr> <td>Notes:</td> <td><form:textarea path="notes" rows="3" cols="20" /></td> <td><form:errors path="notes" /></td> </tr> This tag renders an HTML input tag with type hidden using the bound value. To submit
an unbound hidden value, use the HTML <form:hidden path="house" /> If we choose to submit the house value as a hidden one, the HTML would look like: <input name="house" type="hidden" value="Gryffindor"/> This tag renders field errors in an HTML span tag. It provides access to the errors created in your controller or those that were created by any validators associated with your controller. Let’s assume we want to display all error messages for the public class UserValidator implements Validator { public boolean supports(Class candidate) { return User.class.isAssignableFrom(candidate); } public void validate(Object obj, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required."); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required."); } } The <form:form> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> <%-- Show errors for firstName field --%> <td><form:errors path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> <%-- Show errors for lastName field --%> <td><form:errors path="lastName" /></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form> If we submit a form with empty values in the <form method="POST"> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value=""/></td> <%-- Associated errors to firstName field displayed --%> <td><span name="firstName.errors">Field is required.</span></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value=""/></td> <%-- Associated errors to lastName field displayed --%> <td><span name="lastName.errors">Field is required.</span></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form> What if we want to display the entire list of errors for a given page? The example below
shows that the
The example below will display a list of errors at the top of the page, followed by field-specific errors next to the fields: <form:form> <form:errors path="*" cssClass="errorBox" /> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> <td><form:errors path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> <td><form:errors path="lastName" /></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form> The HTML would look like: <form method="POST"> <span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value=""/></td> <td><span name="firstName.errors">Field is required.</span></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value=""/></td> <td><span name="lastName.errors">Field is required.</span></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </form> A key principle of REST is the use of the Uniform Interface. This means that all
resources (URLs) can be manipulated using the same four HTTP methods: GET, PUT, POST,
and DELETE. For each method, the HTTP specification defines the exact semantics. For
instance, a GET should always be a safe operation, meaning that is has no side effects,
and a PUT or DELETE should be idempotent, meaning that you can repeat these operations
over and over again, but the end result should be the same. While HTTP defines these
four methods, HTML only supports two: GET and POST. Fortunately, there are two possible
workarounds: you can either use JavaScript to do your PUT or DELETE, or simply do a POST
with the real method as an additional parameter (modeled as a hidden input field in an
HTML form). This latter trick is what Spring’s To support HTTP method conversion the Spring MVC form tag was updated to support setting the HTTP method. For example, the following snippet taken from the updated Petclinic sample <form:form method="delete"> <p class="submit"><input type="submit" value="Delete Pet"/></p> </form:form> This will actually perform an HTTP POST, with the real DELETE method hidden behind a
request parameter, to be picked up by the <filter> <filter-name>httpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>httpMethodFilter</filter-name> <servlet-name>petclinic</servlet-name> </filter-mapping> The corresponding @RequestMapping(method = RequestMethod.DELETE) public String deletePet(@PathVariable int ownerId, @PathVariable int petId) { this.clinic.deletePet(petId); return "redirect:/owners/" + ownerId; } Starting with Spring 3, the Spring form tag library allows entering dynamic attributes, which means you can enter any HTML5 specific attributes. In Spring 3.1, the form input tag supports entering a type attribute other than text. This is intended to allow rendering new HTML5 specific input types such as email, date, range, and others. Note that entering type=text is not required since text is the default type. It is possible to integrate Tiles - just as any other view technology - in web applications using Spring. The following describes in a broad way how to do this.
To be able to use Tiles, you have to add a dependency on Tiles version 3.0.1 or higher and its transitive dependencies to your project. To be able to use Tiles, you have to configure it using files containing definitions
(for basic information on definitions and other Tiles concepts, please have a look at
http://tiles.apache.org). In Spring this is done using the <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> <property name="definitions"> <list> <value>/WEB-INF/defs/general.xml</value> <value>/WEB-INF/defs/widgets.xml</value> <value>/WEB-INF/defs/administrator.xml</value> <value>/WEB-INF/defs/customer.xml</value> <value>/WEB-INF/defs/templates.xml</value> </list> </property> </bean> As you can see, there are five files containing definitions, which are all located in
the You can specify locale specific Tiles definitions by adding an underscore and then the locale. For example: <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> <property name="definitions"> <list> <value>/WEB-INF/defs/tiles.xml</value> <value>/WEB-INF/defs/tiles_fr_FR.xml</value> </list> </property> </bean> With this configuration,
The <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/> </bean> The <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename" value="views"/> </bean> ... welcomeView.(class)=org.springframework.web.servlet.view.tiles3.TilesView welcomeView.url=welcome (this is the name of a Tiles definition) vetsView.(class)=org.springframework.web.servlet.view.tiles3.TilesView vetsView.url=vetsView (again, this is the name of a Tiles definition) findOwnersForm.(class)=org.springframework.web.servlet.view.JstlView findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp ... As you can see, when using the Note that the As an advanced feature, Spring also supports two special Tiles Specify Specify <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> <property name="definitions"> <list> <value>/WEB-INF/defs/general.xml</value> <value>/WEB-INF/defs/widgets.xml</value> <value>/WEB-INF/defs/administrator.xml</value> <value>/WEB-INF/defs/customer.xml</value> <value>/WEB-INF/defs/templates.xml</value> </list> </property> <!-- resolving preparer names as Spring bean definition names --> <property name="preparerFactoryClass" value="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory"/> </bean> Velocity and FreeMarker are two templating languages that can be used as view technologies within Spring MVC applications. The languages are quite similar and serve similar needs and so are considered together in this section. For semantic and syntactic differences between the two languages, see the FreeMarker web site. Your web application will need to include A suitable configuration is initialized by adding the relevant configurer bean
definition to your <!-- This bean sets up the Velocity environment for us based on a root path for templates. Optionally, a properties file can be specified for more control over the Velocity environment, but the defaults are pretty sane for file based template loading. --> <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="resourceLoaderPath" value="/WEB-INF/velocity/"/> </bean> <!-- View resolvers can also be configured with ResourceBundles or XML files. If you need different view resolving based on Locale, you have to use the resource bundle resolver. --> <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver"> <property name="cache" value="true"/> <property name="prefix" value=""/> <property name="suffix" value=".vm"/> </bean> <!-- freemarker config --> <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/> </bean> <!-- View resolvers can also be configured with ResourceBundles or XML files. If you need different view resolving based on Locale, you have to use the resource bundle resolver. --> <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <property name="cache" value="true"/> <property name="prefix" value=""/> <property name="suffix" value=".ftl"/> </bean>
Your templates need to be stored in the directory specified by the The basic configurations highlighted above will be suitable for most application requirements, however additional configuration options are available for when unusual or advanced requirements dictate. This file is completely optional, but if specified, contains the values that are passed
to the Velocity runtime in order to configure velocity itself. Only required for
advanced configurations, if you need this file, specify its location on the
<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="configLocation" value="/WEB-INF/velocity.properties"/> </bean> Alternatively, you can specify velocity properties directly in the bean definition for the Velocity config bean by replacing the "configLocation" property with the following inline properties. <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="velocityProperties"> <props> <prop key="resource.loader">file</prop> <prop key="file.resource.loader.class"> org.apache.velocity.runtime.resource.loader.FileResourceLoader </prop> <prop key="file.resource.loader.path">${webapp.root}/WEB-INF/velocity</prop> <prop key="file.resource.loader.cache">false</prop> </props> </property> </bean> Refer to the
API
documentation for Spring configuration of Velocity, or the Velocity documentation for
examples and definitions of the FreeMarker Settings and SharedVariables can be passed directly to the FreeMarker
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/> <property name="freemarkerVariables"> <map> <entry key="xml_escape" value-ref="fmXmlEscape"/> </map> </property> </bean> <bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/> See the FreeMarker documentation for details of settings and variables as they apply to
the Spring provides a tag library for use in JSP’s that contains (amongst other things) a
A standard set of macros are maintained within the Some of the macros defined in the Spring libraries are considered internal (private) but
no such scoping exists in the macro definitions making all macros visible to calling
code and user templates. The following sections concentrate only on the macros you need
to be directly calling from within your templates. If you wish to view the macro code
directly, the files are called spring.vm / spring.ftl and are in the packages
In your html forms (vm / ftl templates) that act as the formView for a Spring form
controller, you can use code similar to the following to bind to field values and
display error messages for each input field in similar fashion to the JSP equivalent.
Note that the name of the command object is "command" by default, but can be overridden
in your MVC configuration by setting the commandName bean property on your form
controller. Example code is shown below for the <!-- velocity macros are automatically available --> <html> ... <form action="" method="POST"> Name: #springBind( "command.name" ) <input type="text" name="${status.expression}" value="$!status.value" /><br> #foreach($error in $status.errorMessages) <b>$error</b> <br> #end <br> ... <input type="submit" value="submit"/> </form> ... </html> <!-- freemarker macros have to be imported into a namespace. We strongly recommend sticking to spring --> <#import "/spring.ftl" as spring /> <html> ... <form action="" method="POST"> Name: <@spring.bind "command.name" /> <input type="text" name="${spring.status.expression}" value="${spring.status.value?default("")}" /><br> <#list spring.status.errorMessages as error> <b>${error}</b> <br> </#list> <br> ... <input type="submit" value="submit"/> </form> ... </html>
The optional form of the macro called Additional convenience macros for both languages simplify both binding and form generation (including validation error display). It is never necessary to use these macros to generate form input fields, and they can be mixed and matched with simple HTML or calls direct to the spring bind macros highlighted previously. The following table of available macros show the VTL and FTL definitions and the parameter list that each takes. Table 17.1. Table of macro definitions
The parameters to any of the above macros have consistent meanings:
Examples of the macros are outlined below some in FTL and some in VTL. Where usage differences exist between the two languages, they are explained in the notes. <!-- the Name field example from above using form macros in VTL --> ... Name: #springFormInput("command.name" "")<br> #springShowErrors("<br>" "")<br> The formInput macro takes the path parameter (command.name) and an additional attributes parameter which is empty in the example above. The macro, along with all other form generation macros, performs an implicit spring bind on the path parameter. The binding remains valid until a new bind occurs so the showErrors macro doesn’t need to pass the path parameter again - it simply operates on whichever field a bind was last created for. The showErrors macro takes a separator parameter (the characters that will be used to separate multiple errors on a given field) and also accepts a second parameter, this time a class name or style attribute. Note that FreeMarker is able to specify default values for the attributes parameter, unlike Velocity, and the two macro calls above could be expressed as follows in FTL: <@spring.formInput "command.name"/> <@spring.showErrors "<br>"/> Output is shown below of the form fragment generating the name field, and displaying a validation error after the form was submitted with no value in the field. Validation occurs through Spring’s Validation framework. The generated HTML looks like this: Name: <input type="text" name="name" value=""> <br> <b>required</b> <br> <br> The formTextarea macro works the same way as the formInput macro and accepts the same parameter list. Commonly, the second parameter (attributes) will be used to pass style information or rows and cols attributes for the textarea. Four selection field macros can be used to generate common UI value selection inputs in your HTML forms.
Each of the four macros accepts a Map of options containing the value for the form field, and the label corresponding to that value. The value and the label can be the same. An example of radio buttons in FTL is below. The form backing object specifies a default value of London for this field and so no validation is necessary. When the form is rendered, the entire list of cities to choose from is supplied as reference data in the model under the name cityMap. ... Town: <@spring.formRadioButtons "command.address.town", cityMap, "" /><br><br> This renders a line of radio buttons, one for each value in Town: <input type="radio" name="address.town" value="London">London</input> <input type="radio" name="address.town" value="Paris" checked="checked">Paris</input> <input type="radio" name="address.town" value="New York">New York</input> If your application expects to handle cities by internal codes for example, the map of codes would be created with suitable keys like the example below. protected Map referenceData(HttpServletRequest request) throws Exception { Map cityMap = new LinkedHashMap(); cityMap.put("LDN", "London"); cityMap.put("PRS", "Paris"); cityMap.put("NYC", "New York"); Map m = new HashMap(); m.put("cityMap", cityMap); return m; } The code would now produce output where the radio values are the relevant codes but the user still sees the more user friendly city names. Town: <input type="radio" name="address.town" value="LDN">London</input> <input type="radio" name="address.town" value="PRS" checked="checked">Paris</input> <input type="radio" name="address.town" value="NYC">New York</input> Default usage of the form macros above will result in HTML tags that are HTML 4.01 compliant and that use the default value for HTML escaping defined in your web.xml as used by Spring’s bind support. In order to make the tags XHTML compliant or to override the default HTML escaping value, you can specify two variables in your template (or in your model where they will be visible to your templates). The advantage of specifying them in the templates is that they can be changed to different values later in the template processing to provide different behavior for different fields in your form. To switch to XHTML compliance for your tags, specify a value of true for a model/context variable named xhtmlCompliant: # for Velocity.. #set($springXhtmlCompliant = true) <-- for FreeMarker --> <#assign xhtmlCompliant = true in spring> Any tags generated by the Spring macros will now be XHTML compliant after processing this directive. In similar fashion, HTML escaping can be specified per field: <#-- until this point, default HTML escaping is used --> <#assign htmlEscape = true in spring> <#-- next field will use HTML escaping --> <@spring.formInput "command.name" /> <#assign htmlEscape = false in spring> <#-- all future fields will be bound with HTML escaping off --> XSLT is a transformation language for XML and is popular as a view technology within web applications. XSLT can be a good choice as a view technology if your application naturally deals with XML, or if your model can easily be converted to XML. The following section shows how to produce an XML document as model data and have it transformed with XSLT in a Spring Web MVC application. This example is a trivial Spring application that creates a list of words in the
Configuration is standard for a simple Spring application. The dispatcher servlet config
file contains a reference to a <bean id="homeController"class="xslt.HomeController"/>
The controller logic is encapsulated in a subclass of protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { Map map = new HashMap(); List wordList = new ArrayList(); wordList.add("hello"); wordList.add("world"); map.put("wordList", wordList); return new ModelAndView("home", map); } So far we’ve done nothing that’s XSLT specific. The model data has been created in the
same way as you would for any other Spring MVC application. Depending on the
configuration of the application now, that list of words could be rendered by JSP/JSTL
by having them added as request attributes, or they could be handled by Velocity by
adding the object to the In order to create a DOM document from our list of words or any other model data, we
must subclass the (provided)
package xslt; // imports omitted for brevity public class HomePage extends AbstractXsltView { protected Source createXsltSource(Map model, String rootName, HttpServletRequest request, HttpServletResponse response) throws Exception { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); Element root = document.createElement(rootName); List words = (List) model.get("wordList"); for (Iterator it = words.iterator(); it.hasNext();) { String nextWord = (String) it.next(); Element wordNode = document.createElement("word"); Text textNode = document.createTextNode(nextWord); wordNode.appendChild(textNode); root.appendChild(wordNode); } return new DOMSource(root); } } A series of parameter name/value pairs can optionally be defined by your subclass which
will be added to the transformation object. The parameter names must match those defined
in your XSLT template declared with The views.properties file (or equivalent xml definition if you’re using an XML based view resolver as we did in the Velocity examples above) looks like this for the one-view application that is My First Words: home.(class)=xslt.HomePage home.stylesheetLocation=/WEB-INF/xsl/home.xslt home.root=words Here, you can see how the view is tied in with the Finally, we have the XSLT code used for transforming the above document. As shown in the
above <?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" omit-xml-declaration="yes"/> <xsl:template match="/"> <html> <head><title>Hello!</title></head> <body> <h1>My First Words</h1> <xsl:apply-templates/> </body> </html> </xsl:template> <xsl:template match="word"> <xsl:value-of select="."/><br/> </xsl:template> </xsl:stylesheet> A summary of the files discussed and their location in the WAR file is shown in the simplified WAR structure below. ProjectRoot | +- WebContent | +- WEB-INF | +- classes | | | +- xslt | | | | | +- HomePageController.class | | +- HomePage.class | | | +- views.properties | +- lib | | | +- spring-*.jar | +- xsl | | | +- home.xslt | +- frontcontroller-servlet.xml You will also need to ensure that an XML parser and an XSLT engine are available on the classpath. JDK 1.4 provides them by default, and most Java EE containers will also make them available by default, but it’s a possible source of errors to be aware of. Returning an HTML page isn’t always the best way for the user to view the model output, and Spring makes it simple to generate a PDF document or an Excel spreadsheet dynamically from the model data. The document is the view and will be streamed from the server with the correct content type to (hopefully) enable the client PC to run their spreadsheet or PDF viewer application in response. In order to use Excel views, you need to add the poi library to your classpath, and for PDF generation, the iText library. Document based views are handled in an almost identical fashion to XSLT views, and the following sections build upon the previous one by demonstrating how the same controller used in the XSLT example is invoked to render the same model as both a PDF document and an Excel spreadsheet (which can also be viewed or manipulated in Open Office). First, let’s amend the views.properties file (or xml equivalent) and add a simple view definition for both document types. The entire file now looks like this with the XSLT view shown from earlier: home.(class)=xslt.HomePage home.stylesheetLocation=/WEB-INF/xsl/home.xslt home.root=words xl.(class)=excel.HomePage pdf.(class)=pdf.HomePage If you want to start with a template spreadsheet or a fillable PDF form to add your model data to, specify the location as the url property in the view definition The controller code we’ll use remains exactly the same from the XSLT example earlier other than to change the name of the view to use. Of course, you could be clever and have this selected based on a URL parameter or some other logic - proof that Spring really is very good at decoupling the views from the controllers! Exactly as we did for the XSLT example, we’ll subclass suitable abstract classes in
order to implement custom behavior in generating our output documents. For Excel, this
involves writing a subclass of
Here’s the complete listing for our POI Excel view which displays the word list from the model map in consecutive rows of the first column of a new spreadsheet: package excel; // imports omitted for brevity public class HomePage extends AbstractExcelView { protected void buildExcelDocument(Map model, HSSFWorkbook wb, HttpServletRequest req, HttpServletResponse resp) throws Exception { HSSFSheet sheet; HSSFRow sheetRow; HSSFCell cell; // Go to the first sheet // getSheetAt: only if wb is created from an existing document // sheet = wb.getSheetAt(0); sheet = wb.createSheet("Spring"); sheet.setDefaultColumnWidth((short) 12); // write a text at A1 cell = getCell(sheet, 0, 0); setText(cell, "Spring-Excel test"); List words = (List) model.get("wordList"); for (int i=0; i < words.size(); i++) { cell = getCell(sheet, 2+i, 0); setText(cell, (String) words.get(i)); } } } And the following is a view generating the same Excel file, now using JExcelApi: package excel; // imports omitted for brevity public class HomePage extends AbstractJExcelView { protected void buildExcelDocument(Map model, WritableWorkbook wb, HttpServletRequest request, HttpServletResponse response) throws Exception { WritableSheet sheet = wb.createSheet("Spring", 0); sheet.addCell(new Label(0, 0, "Spring-Excel test")); List words = (List) model.get("wordList"); for (int i = 0; i < words.size(); i++) { sheet.addCell(new Label(2+i, 0, (String) words.get(i))); } } } Note the differences between the APIs. We’ve found that the JExcelApi is somewhat more intuitive, and furthermore, JExcelApi has slightly better image-handling capabilities. There have been memory problems with large Excel files when using JExcelApi however. If you now amend the controller such that it returns The PDF version of the word list is even simpler. This time, the class extends
package pdf; // imports omitted for brevity public class PDFPage extends AbstractPdfView { protected void buildPdfDocument(Map model, Document doc, PdfWriter writer, HttpServletRequest req, HttpServletResponse resp) throws Exception { List words = (List) model.get("wordList"); for (int i=0; i<words.size(); i++) { doc.add( new Paragraph((String) words.get(i))); } } } Once again, amend the controller to return the JasperReports ( http://jasperreports.sourceforge.net) is a powerful open-source reporting engine that supports the creation of report designs using an easily understood XML file format. JasperReports is capable of rendering reports in four different formats: CSV, Excel, HTML and PDF. Your application will need to include the latest release of JasperReports, which at the time of writing was 0.6.1. JasperReports itself depends on the following projects:
JasperReports also requires a JAXP compliant XML parser. To configure JasperReports views in your Spring container configuration you need to
define a Typically, you will use the <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename" value="views"/> </bean> Here we’ve configured an instance of the The Spring Framework contains five different Table 17.2. JasperReports View classes
Mapping one of these classes to a view name and a report file is a matter of adding the appropriate entries in the resource bundle configured in the previous section as shown here: simpleReport.(class)=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView simpleReport.url=/WEB-INF/reports/DataSourceReport.jasper Here you can see that the view with name JasperReports has two distinct types of report file: the design file, which has a
The The public ModelAndView handleSimpleReportMulti(HttpServletRequest request, HttpServletResponse response) throws Exception { String uri = request.getRequestURI(); String format = uri.substring(uri.lastIndexOf(".") + 1); Map model = getModel(); model.put("format", format); return new ModelAndView("simpleReportMulti", model); } In this example, the mapping key is determined from the extension of the request URI and
is added to the model under the default format key: By default the following mapping key mappings are configured in
Table 17.3. JasperReportsMultiFormatView Default Mapping Key Mappings
So in the example above a request to URI /foo/myReport.pdf would be mapped to the
In order to render your report correctly in the format you have chosen, you must supply
Spring with all of the data needed to populate your report. For JasperReports this means
you must pass in all report parameters along with the report datasource. Report
parameters are simple name/value pairs and can be added to the When adding the datasource to the model you have two approaches to choose from. The
first approach is to add an instance of private Map getModel() { Map model = new HashMap(); Collection beanData = getBeanData(); model.put("myBeanData", beanData); return model; } The second approach is to add the instance of private Map getModel() { Map model = new HashMap(); Collection beanData = getBeanData(); Collection someData = getSomeData(); model.put("myBeanData", beanData); model.put("someData", someData); return model; } Here you can see that two simpleReport.(class)=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView simpleReport.url=/WEB-INF/reports/DataSourceReport.jasper simpleReport.reportDataKey=myBeanData Be aware that when using the first approach, Spring will use the first instance of
JasperReports provides support for embedded sub-reports within your master report files. There are a wide variety of mechanisms for including sub-reports in your report files. The easiest way is to hard code the report path and the SQL query for the sub report into your design files. The drawback of this approach is obvious: the values are hard-coded into your report files reducing reusability and making it harder to modify and update report designs. To overcome this you can configure sub-reports declaratively, and you can include additional data for these sub-reports directly from your controllers. To control which sub-report files are included in a master report using Spring, your report file must be configured to accept sub-reports from an external source. To do this you declare a parameter in your report file like so: <parameter name="ProductsSubReport" class="net.sf.jasperreports.engine.JasperReport"/> Then, you define your sub-report to use this sub-report parameter: <subreport> <reportElement isPrintRepeatedValues="false" x="5" y="25" width="325" height="20" isRemoveLineWhenBlank="true" backcolor="#ffcc99"/> <subreportParameter name="City"> <subreportParameterExpression><![CDATA[$F{city}]]></subreportParameterExpression> </subreportParameter> <dataSourceExpression><![CDATA[$P{SubReportData}]]></dataSourceExpression> <subreportExpression class="net.sf.jasperreports.engine.JasperReport"> <![CDATA[$P{ProductsSubReport}]]></subreportExpression> </subreport> This defines a master report file that expects the sub-report to be passed in as an
instance of <property name="subReportUrls"> <map> <entry key="ProductsSubReport" value="/WEB-INF/reports/subReportChild.jrxml"/> </map> </property> Here, the key of the This step is entirely optional when using Spring to configure your sub-reports. If you
wish, you can still configure the data source for your sub-reports using static queries.
However, if you want Spring to convert data returned in your <property name="subReportDataKeys" value="SubReportData"/> Here, the key you supply must correspond to both the key used in your If you have special requirements for exporter configuration — perhaps you want a
specific page size for your PDF report — you can configure these exporter parameters
declaratively in your Spring configuration file using the <bean id="htmlReport" class="org.springframework.web.servlet.view.jasperreports.JasperReportsHtmlView"> <property name="url" value="/WEB-INF/reports/simpleReport.jrxml"/> <property name="exporterParameters"> <map> <entry key="net.sf.jasperreports.engine.export.JRHtmlExporterParameter.php_FOOTER"> <value>Footer by Spring! </td><td width="50%">&nbsp; </td></tr> </table></body></html> </value> </entry> </map> </property> </bean> Here you can see that the Both
public class SampleContentAtomView extends AbstractAtomFeedView { @Override protected void buildFeedMetadata(Map<String, Object> model, Feed feed, HttpServletRequest request) { // implementation omitted } @Override protected List<Entry> buildFeedEntries(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // implementation omitted } } Similar requirements apply for implementing public class SampleContentAtomView extends AbstractRssFeedView { @Override protected void buildFeedMetadata(Map<String, Object> model, Channel feed, HttpServletRequest request) { // implementation omitted } @Override protected List<Item> buildFeedItems(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // implementation omitted } } The For an example of creating an Atom view please refer to Alef Arendsen’s SpringSource Team Blog entry. The The JSON mapping can be customized as needed through the use of Jackson’s provided
annotations. When further control is needed, a custom
|