Search code examples
javaspring-bootspring-mvcbootstrap-4thymeleaf

Bootstrap components not rendering in Thymeleaf template when having more than one path segment


I have a Spring Boot Web MVC application where I'm using a Bootstrap 4 template and Thymeleaf as view.

I'm facing a problem where a drop-down navigation item is rendering without styles and scripts when the containing page is mapped in the controller under more than one path segment. If the path consists only of one segment the component works as intended.

Page containing the component "menu.html"

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

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

    <title>Menu</title>

    <!-- Custom fonts for this template -->
    <link href="/vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
    <link href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
            rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="/css/sb-admin-2.min.css" rel="stylesheet">

    <!-- Custom styles for this page -->
    <link href="/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">

</head>

<body id="page-top">

<div id="wrapper">
    <nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">
        <ul class="navbar-nav ml-auto">
            <!-- Drop-Down Component -->
            <li class="nav-item dropdown no-arrow">
                <a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button"
                   data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                    <span class="mr-2 d-none d-lg-inline text-gray-600 small" th:text="${user.getName()}"></span>
                    <img class="img-profile rounded-circle" src="img/undraw_profile.svg" width="50" height="50">
                </a>
                <!-- Dropdown - User Information -->
                <div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="userDropdown">
                    <a class="dropdown-item" href="#" data-toggle="modal" data-target="#logoutModal">
                        <i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
                        Logout
                    </a>
                </div>
            </li>
        </ul>
    </nav>

    <!-- Logout Modal-->
    <div class="modal fade" id="logoutModal" tabindex="-1" role="dialog" aria-labelledby="logoutModalLabel"
         aria-hidden="true">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="logoutModalLabel">Ready to Leave?</h5>
                    <button class="close" type="button" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">×</span>
                    </button>
                </div>
                <div class="modal-body">Select "Logout" below if you are ready to end your current session.</div>
                <div class="modal-footer">
                    <button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
                    <a class="btn btn-primary" href="/login?logout=true">Logout</a>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- Bootstrap core JavaScript-->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>

<!-- Core plugin JavaScript-->
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>

<!-- Custom scripts for all pages-->
<script src="js/sb-admin-2.min.js"></script>

<!-- Page level plugins -->
<script src="vendor/datatables/jquery.dataTables.min.js"></script>
<script src="vendor/datatables/dataTables.bootstrap4.min.js"></script>

<!-- Page level custom scripts -->
<script src="js/demo/datatables-demo.js"></script>

</body>

</html>

Using the following mapping in my controller the component works fine:

@GetMapping(value = "/first-segment")
public String showModal(Model model) {

    model.addAttribute("user", userService.getAuthenticatedUser());
    return "menu";
}

But if I add a second segment no styling is displayed and the component can't be clicked on:

@GetMapping(value = "/first-segment/second-segment")
public String showModal(Model model) {

    model.addAttribute("user", userService.getAuthenticatedUser());
    return "menu";
}

Note

I'd also like to add that not all Bootstrap components don't render correctly under two path segments. As far as I've noticed only drop-down and modal components share this issue.


Solution

  • Replace:

    <script src="vendor/jquery/jquery.min.js"></script>
    

    with:

    <script src="/vendor/jquery/jquery.min.js"></script>
    

    Note the extra slash at the start. Otherwise, the Javascript file is supposed to be relative to the current file. That is why it worked for the first segment, but not the second segment.

    By starting with a slash, it is always relative to the root of the path.