Spring Boot + Bootstrap + Thymeleaf Tabs

January 31, 2020 No comments Spring Boot Bootstrap Thymeleaf Tabs

Spring Boot + Bootstrap + Thymeleaf Tabs

1. Introduction

In this article, we are going to present dynamic Thymeleaf Tabs components working on a Spring Boot application. The application will use common Bootstrap tabs with lazy AJAX loading. Each tab content will be located in a separate .html template.

For more information about Thymeleaf check below links:
Thymeleaf Tutorial
Thymeleaf configuration with Spring Boot
Working with Forms in Thymeleaf

2. Dependencies

2.1. Maven dependencies

The application use three common Maven dependencies:

  • springframework - Spring Boot server, beans, controllers,
  • thymeleaf - Thymeleaf template engine,
  • lombok - setters/getters/constructor generator.
2.2. Front-End libraries

Front-end layer use two libraries:

  • bootstrap - framework for building responsive websites,
  • jquery - for creating Ajax calls.

Maven pom.xml file have the following structure:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <artifactId>thymeleaf-bootstrap-tabs</artifactId>

    <properties>
        <bootstrap.version>4.0.0-2</bootstrap.version>
        <webjars-locator.version>0.30</webjars-locator.version>
        <lombok.version>1.18.2</lombok.version>
    </properties>

    <!-- Inherit defaults from Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
    </parent>

    <!-- Add typical dependencies for a web application -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>${bootstrap.version}</version>
        </dependency>

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator</artifactId>
            <version>${webjars-locator.version}</version>
        </dependency>
    </dependencies>

    <!-- Package as an executable jar -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3. Controller and Application class

The application contains a single controller - IndexController class that handles GET request to the root context and returns rendered index.html page.

To load tab content we will use the GET endpoint that takes the name of the tab as a parameter and returns relevant Thymeleaf template.

The IndexController have the following structure:

package com.frontbackend.thymeleaf.bootstrap.controller;

import java.util.Arrays;

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

@Controller
@RequestMapping({ "/", "/index" })
public class IndexController {

    @GetMapping
    public String main(Model model) {
        return "index";
    }

    @GetMapping("{tab}")
    public String tab(@PathVariable String tab) {
        if (Arrays.asList("tab1", "tab2", "tab3")
                  .contains(tab)) {
            return "_" + tab;
        }

        return "empty";
    }
}

Spring Boot main class that starts the server has the following structure:

package com.frontbackend.thymeleaf.bootstrap;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

4. Templates

In resources/templates directory we placed five Thymeleaf templates:

  • index.html - the main page,
  • _tab1.html - the content for tab1,
  • _tab2.html - the content for tab2,
  • _tab3.html - the content for tab3,
  • empty.html - empty template.

The index.html has the following structure:

<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>Spring Boot Thymeleaf Application - Bootstrap Tabs</title>

    <link th:rel="stylesheet" th:href="@{webjars/bootstrap/4.0.0-2/css/bootstrap.min.css} "/>
</head>
<body>

<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
    <div class="container">
        <a class="navbar-brand" href="/">Thymeleaf - Bootstrap Tabs</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
                aria-controls="navbarResponsive"
                aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarResponsive">
            <ul class="navbar-nav ml-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="#">Home
                        <span class="sr-only">(current)</span>
                    </a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">About</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">Services</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">Contact</a>
                </li>
            </ul>
        </div>
    </div>
</nav>

<div class="container">

    <div class="row">
        <div class="col">
            <ul class="nav nav-tabs mt-5" id="myTabs">
                <li class="nav-item"><a href="#tab1" data-url="/tab1" class="nav-link active">Tab1</a></li>
                <li class="nav-item"><a href="#tab2" data-url="/tab2" class="nav-link">Tab2</a></li>
                <li class="nav-item"><a href="#tab3" data-url="/tab3" class="nav-link">Tab3</a></li>
            </ul>

            <div class="tab-content pt-3">
                <div class="tab-pane active" id="tab1">Active panel</div>
                <div class="tab-pane" id="tab2"></div>
                <div class="tab-pane" id="tab3"></div>
            </div>
        </div>
    </div>

</div>


<script th:src="@{/webjars/jquery/jquery.min.js}"></script>
<script th:src="@{/webjars/popper.js/umd/popper.min.js}"></script>
<script th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script>

<script>
    $('#myTabs a').click(function (e) {
        e.preventDefault();

        var url = $(this).attr("data-url");
        var href = this.hash;
        var pane = $(this);

        // ajax load from data-url
        $(href).load(url, function (result) {
            pane.tab('show');
        });
    });

    // load first tab content
    $('#tab1').load($('.nav-link.active').attr("data-url"), function (result) {
    });
</script>

</body>
</html>

To dynamically load tabs content from the backend we used jQuery ajax loading function. The script that achieves that has the following structure:

$('#myTabs a').click(function (e) {
        e.preventDefault();

        var url = $(this).attr("data-url");
        var href = this.hash;
        var pane = $(this);

        // ajax load from data-url
        $(href).load(url, function (result) {
            pane.tab('show');
        });
    });

    // load first tab content
    $('#tab1').load($('.nav-link.active').attr("data-url"), function (result) {
    });

The template with tab content have the following structure:

<h2>Tab1</h2>

<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. </p>

5. The output

The running application is available under http://locahost:8080 URL and presents the following functionality:

Thymeleaf bootstrap time picker

6. Conclusion

In this article, we presented how to create Thymeleaf Tab with lazy loading running on the Spring Boot application server.

As usual, the code used in this article is available under our GitHub repository.

{{ message }}

{{ 'Comments are closed.' | trans }}