1

Background

Using tiles in a Spring 3.x application. The application uses a standard.jsp file that acts as the web site template, which defines typical elements: doctype, navigation, header, body, etc.

The standard.jsp file should dynamically include page-specific customizations. For example, if the URL was "http://localhost/account", then the <head> element should contain:

<link rel="stylesheet" href="styles/account.css" />

Each tiles definition can be written as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions ...>
<tiles-definitions>
    <definition ...>
        <put-attribute name="viewName" value="account" />
    </definition>
</tiles-definitions>

Then, in the standard.jsp file:

<link rel="stylesheet" href="<tiles:insertAttribute name="viewName" />.css"/>

Problem

This produces the desired result, but the tiles attributes are hard-coded, effectively duplicated for every tiles.xml file.

Question

How would you avoid duplicating the put-attribute definition everywhere, yet still import specific CSS files that correspond to the view being rendered?

Idea

Using an Interceptor only works for the / URI, and nothing else. For example:

@EnableWebMvc
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("intro");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        super.addInterceptors(registry);
        registry.addInterceptor(new BaseInterceptor()).addPathPatterns("/**");
    }
}

Configuring additional view controllers won't fire the interceptor code:

registry.addViewController("/account").setViewName("account");

If that worked, then we could iterate over all the JSP files by scanning the classpath (see here and here). The tiles code is probably interfering with the view controller registry for the JSPs:

@Bean
public TilesViewResolver tilesViewResolver() {
    TilesViewResolver viewResolver = new TilesViewResolver();
    viewResolver.setViewClass(TilesView.class);

    return viewResolver;
}

@Bean
public TilesConfigurer tilesConfigurer() {
    TilesConfigurer tilesConfigurer = new TilesConfigurer();
    tilesConfigurer.setDefinitions("/WEB-INF/**/tiles.xml");

    return tilesConfigurer;
}

The interceptor is only triggered for / and can be used to inject the view name:

public class BaseInterceptor extends HandlerInterceptorAdapter {

    private static final Logger logger = LoggerFactory.getLogger(BaseInterceptor.class);

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("BASE INTERCEPTOR POST HANDLE");

        if (modelAndView != null) {
            modelAndView.addObject("stylesheet", modelAndView.getViewName() + ".css");
        }

        super.postHandle(request, response, handler, modelAndView);
    }
}

But because of tiles, the interceptor is never triggered for anything other than /.

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315

1 Answers1

2

I hope I understand your problem right: you have some value that somehow can derived from the view name - right?

My solution (Spring 3.2, Tiles 3) is to use patterns:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!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>

    <!-- base - every thing that is common -->
    <definition name="base" template="/WEB-INF/layouts/template.jspx">
        ...
    </definition>

    <!-- Tiles view definition for the complete backend --> 
    <definition name="prefix/**" extends="base">
        <put-attribute name="mainPart" value="/WEB-INF/views/{1}.jsp" />
        <put-attribute name="stylesheetUrl" value="/styles/{1}.css" />
    </definition>

</tiles-definitions>

That is the only one tiles xml that you need. When you try to render a view with name prefix/x/y, then Tiles would load the /WEB-INF/layouts/template.jspx template and set the two attributes:

  • mainPart = /WEB-INF/views/x/y.jsp
  • stylesheetUrl = /styles/x/y.css
Ralph
  • 118,862
  • 56
  • 287
  • 383
  • @Dave Jarvis: I do not get your problem with the directories (I used this technique with directories) - but maybe the problem is that I do not understand your initial question. – Ralph Jan 29 '15 at 12:40
  • 1
    @Dave Jarvis: so there is a prefix needed? – Ralph Jan 29 '15 at 18:04
  • Yes: the tiles definition cannot match `"*"`, but can match `"flow/*"`. – Dave Jarvis Jan 29 '15 at 18:23
  • @Dave Jarvis: "flow/**" should work too - right? – Ralph Jan 29 '15 at 20:32
  • I imagine so, but we didn't test. For this particular implementation, we're using `flow/*/*` so that we can use `{1}/{2}` for the JSP and `{2}` alone for the CSS/JS artefacts. – Dave Jarvis Jan 29 '15 at 20:53