Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add support for more than one Thymeleaf template modes

I have a Spring Boot app using Thymeleaf 3 as the template engine for the app pages. We use the default configuration provided by spring-boot-starter-thymeleaf, with the HTML Thymeleaf templates under the src/main/resources/templates folder.

Now we would like to use Thymeleaf to generate also some javascript files, using the new javascript template mode. Those javascript templates could be located into the same HTML templates folder or another one (ex: src/main/resources/jstemplates).

I don't know if there is a way to add this configuration without changing anything in the default configuration provided by the spring-boot-starter-thymeleaf, or I have to create a full configuration for everything.

I've tried the first option with the following configuration, which works for the javascript templates, but then html templates don't work anymore.

The configuration:

@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter
    implements ApplicationContextAware {

  private static final String UTF8 = "UTF-8";

  @Autowired
  private SpringTemplateEngine templateEngine;

  @Autowired
  private ThymeleafProperties properties;

  private ApplicationContext applicationContext;

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
  }

  @Bean
  public ThymeleafViewResolver javascriptThymeleafViewResolver() {
    ThymeleafViewResolver resolver = new ThymeleafViewResolver();
    resolver.setTemplateEngine(this.templateEngine);
    resolver.setCharacterEncoding(UTF8);
    resolver.setContentType("application/javascript");
    resolver.setViewNames(new String[] {".js"});
    resolver.setCache(this.properties.isCache());
    return resolver;
  }

  @Bean
  public SpringResourceTemplateResolver javascriptTemplateResolver() {
    SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
    resolver.setApplicationContext(applicationContext);
    resolver.setPrefix("classpath:/jstemplates/");
    resolver.setSuffix(".js");
    resolver.setTemplateMode(TemplateMode.JAVASCRIPT);
    resolver.setCharacterEncoding(UTF8);
    return resolver;
  }
}

A test javascript controller:

@Controller
public class JavascriptController {

  @RequestMapping(method = RequestMethod.GET, value = "/test.js")
  public String testjs() {
    return "test";
  }
}

The controller for the root page:

@Controller
public class MainController {

  @RequestMapping(method = RequestMethod.GET, value = "/")
  public String index(Model model) {
    return "index";
  }
}

There is a test.js file in the src/main/resources/jstemplates folder. If I try the url http://localhost:8080/test.js, it works as expected. But if I try, for example, the main url (http://localhost:8080/) it fails with the following error:

Caused by: java.io.FileNotFoundException: class path resource [jstemplates/index.js] cannot be opened because it does not exist

It should be looking for the templates/index.html instead, so it seems the javascriptTemplateResolver overrides or takes precedence over de default one, without falling back to it.

Is there a way to add another template mode support integrated with the default Thymeleaf configuration, or I need to configure everything from scratch?

like image 448
Cèsar Avatar asked Oct 28 '25 14:10

Cèsar


1 Answers

After some debugging I finally found the solution. It seems the SpringResourceTemplateResolver doesn't check by default if the template really exists. In my example configuration, the first template resolver is the one configured for the javascript templates, and it was creating a View without looking first if the template exists.

To solve it the template checkExistence property must be set to true. Ex:

  @Bean
  public SpringResourceTemplateResolver javascriptTemplateResolver() {
    SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
    resolver.setApplicationContext(applicationContext);
    resolver.setPrefix("classpath:/jstemplates/");
    resolver.setSuffix(".js");
    resolver.setTemplateMode(TemplateMode.JAVASCRIPT);
    resolver.setCharacterEncoding(UTF8);
    resolver.setCheckExistence(true);
    return resolver;
  }

The only problem I see with this configuration is that if we create a js view with the same name as a html view, it will always get the javascript one.

Edit

To solve that last issue I've made some changes in the configuration:

  • Remove the .js suffix in the resolver configuration
  • When a controller gives a javascript view name, that name already includes the .js suffix.

The final configuration is as follows:

@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter
    implements ApplicationContextAware {

  private static final String UTF8 = "UTF-8";

  @Autowired
  private ThymeleafProperties properties;

  @Autowired
  private TemplateEngine templateEngine;

  private ApplicationContext applicationContext;

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
  }

  @Bean
  public ThymeleafViewResolver javascriptThymeleafViewResolver() {
    ThymeleafViewResolver resolver = new ThymeleafViewResolver();
    resolver.setTemplateEngine(this.templateEngine);
    resolver.setCharacterEncoding(UTF8);
    resolver.setContentType("application/javascript");
    resolver.setViewNames(new String[] {"*.js"});
    resolver.setCache(this.properties.isCache());
    return resolver;
  }

  @Bean
  public SpringResourceTemplateResolver javascriptTemplateResolver() {
    SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
    resolver.setApplicationContext(this.applicationContext);
    resolver.setPrefix("classpath:/templates/js/");
    resolver.setTemplateMode(TemplateMode.JAVASCRIPT);
    resolver.setCharacterEncoding(UTF8);
    resolver.setCheckExistence(true);
    resolver.setCacheable(this.properties.isCache());
    return resolver;
  }
}

And the sample controller:

@Controller
public class JavascriptController {

  @RequestMapping(method = RequestMethod.GET, value = "/js/test.js")
  public String testjs() {
    return "test.js";
  }
}

The HTML view controllers remain unchanged.

like image 194
Cèsar Avatar answered Oct 30 '25 07:10

Cèsar



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!