본문 바로가기
코딩/Spring Boot

Bootstrap 적용

by skyoon 2025. 1. 10.

Git Code 버전 : 0.0.2-SNAPSHOT

  Spring Boot 위주로 작업을 하기 때문에 React, Vue JS 같은 Frontend 전문 Framework 대신에 Bootstrap를 사용하기로 정했다. CSS를 직접 편집하기는 어려워서 Bootstrap 무료 테마를 가져다 썼다. 아래 사이트에서 zip 파일을 다운로드하여 프로젝트의 src/main/resources/templates 아래 압축을 해제한다.

https://startbootstrap.com/template/sb-admin#google_vignette

 

Start Bootstrap

 

startbootstrap.com

 

HTML을 표시하기 위해 Spring Boot의 View는 Thymeleaf를 사용하기로 정했다. 이를 위해 아래 순서대로 설정해 준다.

  1. build.gradle에 thymeleaf 라이브러리 추가
dependencies {
    // 다른 Spring Boot Lib
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
}
  1. application.properties에 thymeleaf를 설정하는데 yml 파일로 변경하여 작업한다.
    1. application.properties를 application.yml으로 변경
    2. 기존 spring.application.name=stock을 yml 형식으로 변경하고 아래와 같이 추가
    3. classpath:/templates가 html 파일들이 위치한 폴더 앞서 압축을 풀어둔 폴더
#spring.application.name=stock을 아래처럼 수정
spring:
    application:
        name: stock

    thymeleaf:
        cache: false # default true, 개발시에는 false로 두는 것이 좋음
        prefix: classpath:/templates/
        check-template-location: true
        suffix: .html
        mode: HTML
  1. Hello World를 출력한 LoginController에서 아래처럼 return을 "index"로 수정
  2. 수정을 하고 template에 푼 테마 파일을 아래처럼 구성해 준다.
    1. assets, css, js 폴더를 src/main/resources/static 아래로 이동

코드를 실행시켜 아래처럼 나오는지 확인한다.

기본 동작 검증이 되면 불필요한 파일들을 정리하고, 앞으로 추가할 기능들을 위해 html의 thymeleaf 문법에 맞게 layout을 재구성한다

  • tables.html, register.html, password.html, layout-static.html, layout-sidenav-light.html, charts.html 파일은 삭제
  • templates 아래 error, layouts 폴더를 생성
  • 401.html, 404.html, 500.html 파일은 error 아래 위치

다음 5개 파일을 만들어준다.

  • header.html : html의 header Tag 안에 정의된 meta, css link 포함 
더보기
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head th:fragment="headerFragment(title)">
        <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 th:replace="${title}">title</title>

        <link href="https://cdn.jsdelivr.net/npm/simple-datatables@7.1.2/dist/style.min.css" rel="stylesheet" />
        <link href="https://cdn.datatables.net/1.13.3/css/jquery.dataTables.min.css" rel="stylesheet" />
        <link href="css/styles.css" rel="stylesheet" />

        <script src="https://use.fontawesome.com/releases/v6.3.0/js/all.js" crossorigin="anonymous"></script>
    </head>
</html>
  • top.html : 테마 상단 메뉴 구성
더보기
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <nav th:fragment="top-nav" class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
        <!-- Navbar Brand-->
        <a class="navbar-brand ps-3" href="/">Home</a>
        <!-- Sidebar Toggle-->
        <button class="btn btn-link btn-sm order-1 order-lg-0 me-4 me-lg-0" id="sidebarToggle" href="#!"><i class="fas fa-bars"></i></button>
        <!-- Navbar-->
        <ul class="navbar-nav ms-auto ms-md-0 me-3 me-lg-4">
            <li class="nav-item dropdown">
                <a class="nav-link dropdown-toggle" id="navbarDropdown" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="fas fa-user fa-fw"></i></a>
                <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
                    <li><a class="dropdown-item" href="#!">Settings</a></li>
                    <li><a class="dropdown-item" href="401.html">Activity Log</a></li>
                    <li><hr class="dropdown-divider" /></li>
                    <li><a class="dropdown-item" href="/login">Login</a></li>
                </ul>
            </li>
            <li class="nav-item" th:if="${isLoggedIn}">
                <form action="/logout" method="post" style="display: inline;">
                    <input type="hidden" th:name="${csrfParameterName}" th:value="${csrfToken}"/>
                    <button type="submit" class="nav-link btn btn-link">Logout</button>
                </form>
            </li>
        </ul>
    </nav>
</html>
  • left-side.html : 테마 좌측 메뉴 구성
더보기
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <div th:fragment="side-nav" id="layoutSidenav_nav">
        <nav class="sb-sidenav accordion sb-sidenav-dark" id="sidenavAccordion">
            <div class="sb-sidenav-menu">
                <div class="nav">
                    <div class="sb-sidenav-menu-heading">Core</div>
                    <a class="nav-link" href="/">
                        <div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
                        대쉬보드
                    </a>
                    <a class="nav-link" href="/finance">
                        <div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
                        주가조회
                    </a>
                    <div class="sb-sidenav-menu-heading">Config</div>
                    <a class="nav-link collapsed" href="#" data-bs-toggle="collapse" data-bs-target="#collapseConfigs" aria-expanded="false" aria-controls="collapseConfigs">
                    <div class="sb-nav-link-icon"><i class="fas fa-columns"></i></div>
                        Config
                    <div class="sb-sidenav-collapse-arrow"><i class="fas fa-angle-down"></i></div>
                    </a>
                    <div class="collapse" id="collapseConfigs" aria-labelledby="headingOne" data-bs-parent="#sidenavAccordion">
                        <nav class="sb-sidenav-menu-nested nav">
                            <a class="nav-link" href="info">Info</a>
                            <a class="nav-link" href="conf">Config</a>
                            <a class="nav-link" href="users">User</a>
                        </nav>
                    </div>
                            
                    <div class="sb-sidenav-menu-heading">Interface</div>
                    <a class="nav-link collapsed" href="#" data-bs-toggle="collapse" data-bs-target="#collapseLayouts" aria-expanded="false" aria-controls="collapseLayouts">
                        <div class="sb-nav-link-icon"><i class="fas fa-columns"></i></div>
                        Layouts
                        <div class="sb-sidenav-collapse-arrow"><i class="fas fa-angle-down"></i></div>
                    </a>
                    <div class="collapse" id="collapseLayouts" aria-labelledby="headingOne" data-bs-parent="#sidenavAccordion">
                        <nav class="sb-sidenav-menu-nested nav">
                            <a class="nav-link" href="layout-static.html">Static Navigation</a>
                            <a class="nav-link" href="layout-sidenav-light.html">Light Sidenav</a>
                        </nav>
                    </div>
                    <a class="nav-link collapsed" href="#" data-bs-toggle="collapse" data-bs-target="#collapsePages" aria-expanded="false" aria-controls="collapsePages">
                        <div class="sb-nav-link-icon"><i class="fas fa-book-open"></i></div>
                        Pages
                        <div class="sb-sidenav-collapse-arrow"><i class="fas fa-angle-down"></i></div>
                    </a>
                    <div class="collapse" id="collapsePages" aria-labelledby="headingTwo" data-bs-parent="#sidenavAccordion">
                        <nav class="sb-sidenav-menu-nested nav accordion" id="sidenavAccordionPages">
                            <a class="nav-link collapsed" href="#" data-bs-toggle="collapse" data-bs-target="#pagesCollapseError" aria-expanded="false" aria-controls="pagesCollapseError">
                                Error
                                <div class="sb-sidenav-collapse-arrow"><i class="fas fa-angle-down"></i></div>
                            </a>
                            <div class="collapse" id="pagesCollapseError" aria-labelledby="headingOne" data-bs-parent="#sidenavAccordionPages">
                                <nav class="sb-sidenav-menu-nested nav">
                                    <a class="nav-link" href="401.html">401 Page</a>
                                    <a class="nav-link" href="404.html">404 Page</a>
                                    <a class="nav-link" href="500.html">500 Page</a>
                                </nav>
                            </div>
                        </nav>
                    </div>
                </div>
            </div>
            <div class="sb-sidenav-footer">
                <div class="small">Logged in as:</div>
                <p th:if="${name}" th:text="'Hello, ' + ${name} + '!'" class="mt-3"></p> <!-- 사용자 이름 표시 -->
            </div>
        </nav>
    </div>
</html>
  • footer.html : 테마 하단 footer 구성
더보기
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <footer th:fragment="footerFragment" class="py-4 bg-light mt-auto">
        <div class="container-fluid px-4">
            <div class="d-flex align-items-center justify-content-between small">
                <div class="text-muted">Copyright &copy; Your Website 2024</div>
                <div>
                    <a href="#">Privacy Policy</a>
                    &middot;
                    <a href="#">Terms &amp; Conditions</a>
                </div>
            </div>
        </div>
    </footer>
</html>
  • scripts.html : html에 포함된 script 파일 링크
더보기
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <th:block th:fragment="scriptFragment">
        <script
            src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
            crossorigin="anonymous"></script>
        <script src="js/scripts.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>

        <script src="https://cdn.datatables.net/1.13.3/js/jquery.dataTables.min.js"></script>
    </th:block>
</html>

 

  index.html에서 layout아래 정의한 html 파일들을 아래처럼 포함 (index.html은 수정 부분이 많아 git에 올려둔 코드를 참조)

<!DOCTYPE html>

<!-- 상단 html Tag에 thymeleaf 적용을 위한 속성을 추가해준다 -->
<html lang="ko" xmlns:th="http://www.thymeleaf.org">

<!-- 상단 head Tag를 아래 3줄로 대채 -->
<head th:replace="~{layouts/header :: headerFragment(~{::title})}">
    <title>Main</title>
</head>

<body class="sb-nav-fixed">
	<!--Body Tag 다음인 nav Tag을 아래줄로 대채 (<div id="layoutSidenav"> Line 전까지) -->
    <nav th:replace="~{layouts/top :: top-nav}"></nav>
    <div id="layoutSidenav">
		
        <!-- <div id="layoutSidenav_nav"> 안의 부분을 전부 삭제하고 아래코드로 대채 -->
        <!-- Left Side Menu-->
        <div th:replace="~{layouts/left-side :: side-nav}"></div>

		<div id="layoutSidenav_content">
        	<main>
        		{중략}
            </main>
            <!-- footer Tag부분을 아래 코드로 변경 -->
            <footer th:replace="~{layouts/footer :: footerFragment}"></footer>
        </div>
    </div>
    
    <!-- 스크립트는 index.html에서 사용하는 코드 일부를 남기고 아래 코드로 변경 -->
    <th:block th:replace="~{layouts/scripts :: scriptFragment}"></th:block>
    
    <!-- 아래가 유지하는 JavaScript 코드 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js" crossorigin="anonymous"></script>
    <script src="assets/demo/chart-area-demo.js"></script>
    <script src="assets/demo/chart-bar-demo.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/simple-datatables@7.1.2/dist/umd/simple-datatables.min.js" crossorigin="anonymous"></script>
    <script src="js/datatables-simple-demo.js"></script>
    </body>
</html>

 

정상적이라면 앞서 본화면과 차이 없이 아래처럼 똑같이 출력됨