Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Thymeleaf Bootstrap CSS

I'm trying to use Bootstrap CSS with my Spring + Security + Thymeleaf web application. The CSS gets picked up the by my custom login screen and everything is fine - all styles are applied. However, when I progress from the login screen to the next screen for some reason my html page is not picking up the CSS file and the styles are not being applied. Can someone assist?

Below are the relevant files:

Login html file (which is working fine):

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="utf-8"></meta>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"></meta>
    <meta name="viewport" content="width=device-width, initial-scale=1"></meta>
    <meta name="description" content=""></meta>
    <meta name="author" content=""></meta>

    <title>motodoc hub Sign in</title>

    <!-- Bootstrap core CSS -->
    <link href="resources/css/bootstrap-3.3.2-dist/bootstrap.min.css" rel="stylesheet" />

    <!-- Custom styles for this template -->
    <link href="resources/css/signin.css" rel="stylesheet" />

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>

  <body>

    <div class="container">

        <p th:if="${loginError}">Wrong username or password</p>
      <form th:action="@{/login}" method="post" class="form-signin">
        <h2 class="form-signin-heading">Please sign in</h2>
        <label for="username" class="sr-only">Username</label>
        <input type="text" id="username" name="username" class="form-control" placeholder="Username" />
        <label for="password" class="sr-only">Password</label>
        <input type="password" id="password" name="password" class="form-control" placeholder="Password" />
        <!-- <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>  -->
        <input type="submit" value="Log in" class="btn btn-lg btn-primary btn-block" />
      </form>

    </div> <!-- /container -->


    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <script src="resources/js/ie10-viewport-bug-workaround.js"></script>
  </body>
</html>

Login controller (again - all is working fine with this):

package com.motodoc.hub.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController {

    // Login form
    @RequestMapping("/login")
    public String login() {
        System.out.println("in here ....");
        return "login";
    }

    // Login form with error
    @RequestMapping("/login-error")
    public String loginError(Model model) {
        model.addAttribute("loginError", true);
        return "login";
    }
}

Home html file (this is the file where for some reason the CSS is not 'getting picked'):

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>

    <meta charset="utf-8"></meta>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"></meta>
    <meta name="viewport" content="width=device-width, initial-scale=1"></meta>
    <meta name="description" content=""></meta>
    <meta name="author" content=""></meta>

    <!-- Bootstrap core CSSS -->
    <link href="resources/css/bootstrap-3.3.2-dist/bootstrap.min.css" rel="stylesheet" />

    <title>Home</title>
</head>
<body>
    <div th:include="templates/navbar :: navigationBar"></div>
    <br></br>
    <h1>Login success!</h1><br></br>

    <div>
    <table class="table table-striped">
    <caption th:text="#{user.table.caption}">Site Users</caption>
    <thead>
        <tr>
            <th scope="col" th:text="#{user.id.label}">Id</th>
            <th scope="col" th:text="#{user.firstname.label}">First Name</th>
            <th scope="col" th:text="#{user.lastname.label}">Last Name</th>
            <th scope="col" th:text="#{user.username.label}">Username</th>
        </tr>
    </thead>
    <tbody>
        <tr th:each="user : ${currentResults.content}">
            <td th:text="${user.id}">1</td>
            <td th:text="${user.firstName}">George</td>
            <td th:text="${user.lastName}">Washington</td>
            <td th:text="${user.username}">gwash</td>
        </tr>
    </tbody>
</table>
</div>



    <!-- Bootstrap core JavaScript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
    <script src="resources/js/bootstrap-3.3.2-dist/bootstrap.min.js"></script>

</body>
</html>

Home controller class:

@Controller
public class HomeController {

    @Autowired
    private ISecurityUserService securityUserService;

    @RequestMapping("/home")
    public String redirect() {      
        return "redirect:/home/page/1";
    }

    // home
    @RequestMapping(value="/home/page/{pageNumber}", method = RequestMethod.GET)
    public String getUsers(
            @PathVariable("pageNumber") Integer pageNumber, 
            ModelMap model) {

        Page<Person> currentResults = securityUserService.findAll(pageNumber-1);

        model.addAttribute("currentResults", currentResults);

        //Pagination variables
        int startIndex = Math.max(1, pageNumber - 5);
        int endIndex = Math.min(startIndex + 10, currentResults.getTotalPages());

        model.addAttribute("url", "home");
        model.addAttribute("startIndex", startIndex);
        model.addAttribute("endIndex", endIndex);
        model.addAttribute("currentIndex", pageNumber);
        //model.addAttribute("users", users);

        return "home";
    }   
}

Spring security config:

@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**");
    }

    @Override
    protected void configure( HttpSecurity http ) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/resources/**").permitAll()
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .loginPage("/login")
            .permitAll()
            .defaultSuccessUrl("/home")
            .failureUrl("/login-error")
            .and()
        .logout()
            .invalidateHttpSession(true)
            .logoutUrl("/logout")
            .deleteCookies("JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE")
            .logoutSuccessUrl("/")
            .permitAll();
    }

    @Bean
    public CustomAuthenticationProviderImpl authenticationProvider() {
        return new CustomAuthenticationProviderImpl();
    }

    @Bean
    public BCryptPasswordEncoder encoder() {
        return new BCryptPasswordEncoder(10);
    }
}

Application context config class:

@Configuration 
@ComponentScan(basePackages = {"com.motodoc.hub.*"})
@EnableWebMvc
@Import({SpringDataConfig.class, 
    ThymeleafConfig.class, 
    SecurityConfig.class, 
    ServiceBeans.class})
@ImportResource("classpath:trace-context.xml")
@PropertySource("classpath:spring.properties")
public class ApplicationContext extends WebMvcConfigurerAdapter {

    // Maps resources path to webapp/resources
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }

    // Only needed if we are using @Value and ${...} when referencing properties
    @Bean
    public static PropertySourcesPlaceholderConfigurer properties() {
        PropertySourcesPlaceholderConfigurer propertySources = new PropertySourcesPlaceholderConfigurer();
        Resource[] resources = new ClassPathResource[] { 
                new ClassPathResource("spring.properties") };
        propertySources.setLocations(resources);
        propertySources.setIgnoreUnresolvablePlaceholders(true);
        return propertySources;
    }

    // Provides internationalization of messages
    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource source = new ResourceBundleMessageSource();
        source.setBasename("messages");
        return source;
    }
}

Thymeleaf config class:

@Configuration 
public class ThymeleafConfig {

    @Bean 
    public ServletContextTemplateResolver templateResolver() {
        ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode("HTML5");
        resolver.setOrder(1);
        return resolver;
    }

    @Bean 
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setTemplateResolver(templateResolver());

        //add spring security dialect
        Set<IDialect> dialects = new HashSet<IDialect>();
        dialects.add(springSecurityDialect());
        engine.setAdditionalDialects(dialects);

        return engine;
    }

    @Bean 
    public ThymeleafViewResolver thymeleafViewResolver() {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine());
        return resolver;
    }

    @Bean
    public SpringSecurityDialect springSecurityDialect() {
        return new SpringSecurityDialect();
    }
}

Webapp Project Directory structure:

enter image description here

like image 688
Zahanghir Avatar asked Mar 04 '15 14:03

Zahanghir


People also ask

Can I use bootstrap with Thymeleaf?

There are two ways we can add a bootstrap CSS file to the Thymeleaf template: Using bootstrap CSS CDN remote links. Downloading Bootstrap CSS file locally, add to project, and finally add a file path to Thymeleaf template.

How do I add CSS to Thymeleaf?

Adding CSS. We load the stylesheet using the link tag with Thymeleaf's special th:href attribute. If we've used the expected directory structure, we only need to specify the path below src/main/resources/static. In this case, that's /styles/cssandjs/main.

Is Thymeleaf still relevant?

While Thymeleaf is more of a template engine for server-side application development. But Thymeleaf's popularity is on a steady rise. The developer community is slowly moving away from 'once a common' MVC framework for Javascript-based development.


1 Answers

Figured it out ... schoolboy error on my part ...

The following link in my home.html file wasn't resolving to the right location:

<link href="resources/css/bootstrap-3.3.2-dist/bootstrap.min.css" rel="stylesheet" />

So instead, I used context-relative URL as follows:

<link th:href="@{/resources/css/bootstrap-3.3.2-dist/bootstrap.min.css}" rel="stylesheet" />

And this did the trick.

like image 192
Zahanghir Avatar answered Nov 15 '22 22:11

Zahanghir