Views, Locales, and Themes

We already touched on the View interface, but we simply stated its uses. It is now time to examine it in more detail. Let's start with a custom implementation of the View interface. This demonstrates how simple it is to create a custom view and what Spring does to look up (and instantiate) an appropriate instance of a view when we refer to the view by its name.

Using Views Programmatically

In this example, we manually implement a View and return this implementation in the ModelAndView class, which is a result of the AbstractController.handleRequestInternal() method.

Our view must implement only a single method from the View interface: render(Map, HttpServletRequest, HttpServletResponse). The View implementation we create in Listing 17-11 outputs all data from the model to a text file and sets the response headers to indicate to the client that the returned content is a text file and that it should be treated as an attachment.

Listing 17-11: PlainTextView Implementation

image from book
package com.apress.prospring.ch17.web.views;      import java.io.PrintWriter; import java.util.Iterator; import java.util.Map;      import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;      import org.springframework.web.servlet.View;          public class PlainTextView implements View {          public void render(Map model, HttpServletRequest request,          HttpServletResponse response) throws Exception {              response.setContentType("text/plain");         response.addHeader("Content-disposition",              "attachment; filename=output.txt");                  PrintWriter writer = response.getWriter();         for (Iterator k = model.keySet().iterator(); k.hasNext();) {             Object key = k.next();             writer.print(key);             writer.println(" contains:");             writer.println(model.get(key));         }     }      } 
image from book

In Listing 17-12, we modify the IndexController class from Listing 17-5 to return our custom view.

Listing 17-12: Modified IndexController Class

image from book
public class IndexController extends AbstractController {          protected ModelAndView handleRequestInternal(         HttpServletRequest request, HttpServletResponse response)          throws Exception {                  setCacheSeconds(10);         Map model = new HashMap();         model.put("Greeting", "Hello World");         model.put("Server time", new Date());                  return new ModelAndView(new PlainTextView(), model);     }      }
image from book

Let's now make a request to the /index.html path. The IndexController.handleRequestInternal method is called and it returns an instance of ModelAndView with View set to the instance of PlainTextView and a model Map containing the keys Greeting and Server time. The render() method of PlainTextView sets the header information that prompts the client to display a Save As window like the one shown in Figure 17-3.

image from book
Figure 17-3: PlainTextView implementation

The content of the output.txt file, shown in Figure 17-4, is simply the model displayed as plain text.

image from book
Figure 17-4: Downloaded output.txt file

The result is exactly what we expected: there were two entries in the model MapGreeting and Server time—with Hello World and a current Date value.

This example has one disadvantage: the code in IndexController creates an instance of PlainTextView for each request. This is not necessary, because the view is a stateless object. In Listing 17-13, we improve the application and make PlainTextView a Spring bean.

Listing 17-13: PlainTextView as Spring Bean

image from book
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"      "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>     <bean           />     <!-- other beans as usual --> </beans> 
image from book

We need to modify the IndexController's handleRequestInternal() method to use the plainTextView bean instead of the instantiating the instance of PlainTextView for each request. We do this in Listing 17-14.

Listing 17-14: Modified IndexController Class

image from book
public class IndexController extends AbstractController {          protected ModelAndView handleRequestInternal(         HttpServletRequest request, HttpServletResponse response)          throws Exception {                  setCacheSeconds(10);         Map model = new HashMap();         model.put("Greeting", "Hello World");         model.put("Server time", new Date());         View view = (View)getApplicationContext().getBean("plainTextView");                  return new ModelAndView(view, model);     }      }
image from book

This is better—each request gets the same instance of the PlainTextView bean. However, it is still far from ideal. A typical web application consists of a rather large number of views, and it is inconvenient to configure all views this way. Moreover, certain views require further configuration. Take a JSP view, for example; it needs a path to the JSP page. If we configure all views as Spring beans manually, we have to configure each JSP page as a separate bean. It would be nice if we had an easier way to define the views and delegate all the work to Spring. This is where view resolvers come into play.

Using ViewResolver Implementations

A ViewResolver is a strategy interface Spring uses to look up and instantiate an appropriate view based on its name and locale. Various view resolvers all implement the ViewResolver interface's single method—View resolveViewName(String viewName, Locale locale) throws Exception—which allows your applications to be much easier to maintain. The locale parameter suggests that the ViewResolver can return views for different client locales.

Table 17-4 explores the various implementations of the ViewResolver interface.

Table 17-4: ViewResolver Implementations

Implementation

Description

BeanNameViewResolver

This is a simple ViewResolver implementation that tries to get the view as a bean configured in the application context. You may find this resolver useful for smaller applications where you do not want to create another file that holds the view definitions. However, this resolver has several limitations; the most annoying one is that you have to configure the views as Spring beans in the application context. Also, it does not support internalization.

ResourceBundleViewResolver

This is a far more complex resolver. In this case, the view definitions are kept in a separate configuration file; you do not have to configure the view beans in the application context file. This resolver supports internalization.

UrlBasedViewResolver

This resolver instantiates the appropriate view based on the URL. You can configure the URL to have prefixes and suffixes. This resolver gives you more control over the views than BeanNameViewResolver, but it can become difficult to manage in a larger application and does not support internalization.

XmlViewResolver

This view resolver is similar to ResourceBundleViewResolver because the view definitions are kept in a separate file. Unfortunately, this resolver does not support internalization.

Now that we know what ViewResolvers are available in Spring and their advantages and disadvantages, we can improve the sample application. We are going to discuss the ResourceBundleViewResolver because it offers the most complex functionality.

In Listing 17-15, we start by updating the application context file to include the viewResolver bean definition.

Listing 17-15: ResourceBundleViewResolver Definition

image from book
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>     <bean bold">viewResolver"          bold">ResourceBundleViewResolver">         <property name="basename"><value>views</value></property>     </bean>     <!-- etc --> </beans> 
image from book

This introduces the viewResolver bean that Spring uses to resolve all view names. The class is ResourceBundleViewResolver, and its basename property is views. This means that the ViewResolver is going to look for the views_<LID>.properties file on the classpath, where LID is the locale identifier (EN, CS, and so on). If the resolver cannot locate the views_<LID>.proper- ties file, it tries to open the views.properties file. To demonstrate the internalization support in this resolver, we create views.properties and views_CS.properties. We use the first file for any language other than Czech. The syntax of the properties file is viewname.class=class-name and viewname.url=view-url, as shown in Listing 17-16.

Listing 17-16: Views.properties File Syntax

image from book
#index products-index.class=org.springframework.web.servlet.view.JstlView products-index.url=/WEB-INF/views/product/index.jsp
image from book

We found that the best way to keep this file reasonably easy to maintain is to follow the logical structure of the application, using a dash as the directory separator. If you create a

User ® Edit view definition, we recommend using code similar to that presented in Listing 17-17.

Listing 17-17: Additions to the views.properties File

image from book
#index user-edit.class=org.springframework.web.servlet.view.JstlView user-edit.url=/WEB-INF/views/user /edit.jsp
image from book

Similarly, we create index.jsp and index_CS.jsp in /web-src/as-web/WEB-INF/views/ product. Finally, in Listing 17-18, we modify ProductController to return a dummy list of Product objects and display this list in the view.

Listing 17-18: Modified ProductController

image from book
package com.apress.prospring.ch17.web.product;      public class ProductController extends MultiActionController {          private List products;          private Product createProduct(int productId, String name,          Date expirationDate) {         Product product = new Product();         product.setProductId(productId);         product.setName(name);         product.setExpirationDate(expirationDate);                  return product;     }          public ProductController() {         products = new ArrayList();         Date today = new Date();         products.add(createProduct(1, "test", today));         products.add(createProduct(2, "Pro Spring Appes", today));         products.add(createProduct(3, "Pro Velocity", today));         products.add(createProduct(4, "Pro VS.NET", today));     }          public ModelAndView index(HttpServletRequest request,          HttpServletResponse response) {                  return new ModelAndView("products-index", "products", products);     }     // other methods omitted for clarity }
image from book

As you can see, we did not modify ProductController in any unexpected way; the only change is that the ModelAndView constructor we call in the index() method is ModelAndView(String, String, Object) instead of ModelAndView(View, …).

To test the application, make sure to set the preferred language in your browser to anything other than Czech. Spring creates product-index View of type JstlView with the URL set to /WEB-INF/product/index.jsp and renders the output, which appears in Figure 17-5.

image from book
Figure 17-5: English version of the site

If we now change the preferred language to Czech, the view resolver creates an instance of index_CS, which is a JstlView, and its URL property points to /WEB-INF/products/index_CS.jsp. Figure 17-6 shows the resulting site.

image from book
Figure 17-6: Czech version of the site

Using view resolvers rather than manually instantiating the views has the obvious benefit of simpler configuration files, but it also reduces the application's memory footprint. If we define each view as a Spring bean, it is instantiated on application start; if we use view resolvers, the view is instantiated and cached on first request.

Using Localized Messages

Before we can discuss using Locales in Spring web applications, we must look at how Spring resolves the actual text for messages to be displayed either in the spring:message tag (covered in

The default bean name Spring looks up is messageSource; our definition of this bean is shown in Listing 17-19.

Listing 17-19: messageSource Bean Definition

image from book
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>     <bean           >         <property name="basename"><value>messages</value></property>     </bean> </beans>
image from book

A more detailed discussion of MessageSource is offered in Chapter 18.

Using Locales

We have already discussed the internalization support in ResourceBundleViewResolver; let's now take a look at how things work under the hood.

Spring uses the LocaleResolver interface to intercept the request and calls its methods to get or set the locale. There are several implementations of LocaleResolver, each with its particular uses and properties, shown in Table 17-5.

Table 17-5: LocaleResolver Implementations

Implementation

Description

AcceptHeaderLocaleResolver

This locale resolver returns the locale based on an accept- language header sent by the user agent to the application. If this resolver is used, the application automatically appears in the user's preferred language (if you take the time to implement it). If the user wishes to switch to another language, he has to change his browser settings.

CookieLocaleResolver

This locale resolver uses cookie on the client's machine to identify the locale. This allows the user to specify the language she wants the application to appear in without changing her browser settings. It is not hard to imagine that a user in Prague has an English web browser, yet she expects to see the application in Czech. Using this locale resolver, we can store the locale settings using the user's browser cookie store.

FixedLocaleResolver

This is a very simple implementation of LocaleResolver that always returns one configured locale.

SessionLocaleResolver

This resolver works very much like CookieLocaleResolver, but the locale settings are not persisted in a cookie and are lost when the session expires.

Using Themes

In addition to providing the application's views in the users' language, you can use themes to further improve the users' experience. Theme is usually a collection of stylesheets and images embedded into the rendered output. Spring also provides a tag library that you can use to enable theme support in your JSP pages. Let's start with a directory structure we are going to use to demonstrate the usage of themes, as shown in Figure 17-7.

image from book
Figure 17-7: Directory and file structure for themes

As you can see from Figure 17-7, we added a themes directory and created two new properties files: cool.properties and default.properties. The content of the properties file specifies the location of static theme resources, as shown in Listing 17-20.

Listing 17-20: cool.properties File

image from book
css=/themes/cool/main.css
image from book

The key in the properties file specifies the key that is exposed by the theme resolver, and the value of the property specifies the location of the themed resource. We can use this definition in a JSP page using the Spring tag library, as shown in Listing 17-21.

Listing 17-21: Contents of the index.jsp File

image from book
<%@taglib prefix="c" uri="http://java.sun.com/jstl/core"%> <%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>      <html>     <head>         <c:set var="css"><spring:theme code="css"/></c:set>         <c:if test="${not empty css}">             <link rel="stylesheet"                   href="<c:url value="${css}"/>" type="text/css" />         </c:if>     </head> <body> This page lists all available products:<br> <c:forEach items="${products}" var="product">     <c:out value="${product.name}"/>     <a href="view.html?productId=         <c:out value="${product.productId}"/>">[View]</a>&nbsp;     <a href="edit.html?productId=         <c:out value="${product.productId}"/>">[Edit]</a>&nbsp;<br>     <hr> </c:forEach><br> <a href="edit.html">[Add]</a> </body> </html>
image from book

Finally, we need to modify the Spring application context and add a themeResolver bean, as shown in Listing 17-22.

Listing 17-22: themeResolver Bean Definition

image from book
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>     <bean           bold">FixedThemeResolver">         <property name="defaultThemeName"><value>cool</value></property>     </bean>     <!-- other beans as usual --> </beans>
image from book

This application context file specifies that the application uses FixedThemeResolver with defaultThemeName set to cool. The theme is therefore loaded from the cool.properties file in the root of the classpath.

Themes can contain references to any kind of static content, such as images and movies, not just stylesheets. This also means that themes must support internalization because the images may contain text that needs to be translated into other languages. The internalization support in theme resolvers works exactly the same as internalization support in ResourceBundleViewResolver. The theme resolver tries to load theme_<LID>.properties, where LID is the locale identifier (EN, CS, and so on). If the properties file with the LID does not exist, the resolver tries to load the properties file without the LID.

Just like the ViewResolvers and the LocaleResolvers, there are several implementations of ThemeResolvers, as shown in Table 17-6.

Table 17-6: ThemeResolver Implementations

Theme Resolver

Description

CookieThemeResolver

This allows the theme to be set per user and stores the theme preferences by storing a cookie on the client's computer.

FixedThemeResolver

This theme resolver returns one fixed theme, which is set in the bean's defaultThemeName property.

SessionThemeResolver

This allows the theme to be set per a user's session. The theme is not persisted between sessions.

Adding support for themes is not very difficult and it can give your application an extra visual kick with very little programming effort.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net