부트스트랩(Bootstrap)은 디자이너의 도움 없이도 개발자 혼자서 상당히 괜찮은 수준의 웹 페이지를 만들수 있게 도와주는 프레임워크이다. 부트스트랩은 트위터(Twitter)를 개발하면서 만들어졌고 현재 지속적으로 관리되고 있는 오픈소스 프로젝트이다.
✅ 부트스트랩 다운로드
https://getbootstrap.com/docs/5.2/getting-started/download/
bootstrap.min.css
파일을 카피하여 스태틱 디렉터리에 저장bootstrap-5.2.3-dist.zip/bootstrap-5.2.3-dist/css/bootstrap.min.css
옮겨왔으면 sbb 프로젝트에서 우클릭 후 refresh
부트스트랩 클래스 | 설명 |
---|---|
card, card-body, card-text | 부트스트랩 Card 컴포넌트 |
badge | 부트스트랩 Badge 컴포넌트 |
form-control | 부트스트랩 Form 컴포넌트 |
border-bottom | 아래방향 테두리 선 |
my-3 | 상하 마진값 3 |
py-2 | 상하 패딩값 2 |
p-2 | 상하좌우 패딩값 2 |
d-flex justify-content-end | 컴포넌트의 우측 정렬 |
bg-light | 연회색 배경 |
text-dark | 검은색 글씨 |
text-start | 좌측 정렬 |
btn btn-primary | 부트스트랩 버튼 컴포넌트 |
<!-- 부트스트랩 적용 -->
<link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}">
<div class="container py-1"> <!-- 부트스트랩 적용 -->
<table class = "table"> <!-- 부트스트랩 적용 -->
<thead class = "table-info"> <!-- 부트스트랩 적용 -->
<tr>
<th>번호</th> <!-- 테이블 항목 추가 -->
<th>제목</th>
<th>작성일시</th>
</tr>
</thead>
<tbody>
<tr th:each="questionlist, loop : ${questionList}"> <!-- loop구문 사용 -->
<td th:text="${loop.count}"></td>
<td>
<a th:href="@{|/question/detail/${questionlist.id}|}" th:text="${questionlist.subject}"></a>
</td>
<!-- questionlist.createDate값을 포맷함 -->
<td th:text="${#temporals.format(questionlist.createDate, 'yyyy-MM-dd HH-mm')}"></td>
</tr>
</tbody>
</table>
</div>
✅ <link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}">
✅ <div class="container my-3">
class="container my-3"
: 부트스트랩 스타일시트에 정의되어 있는 클래스
여기서 사용된 부트스트랩 클래스인 my-3은 위아래 방향 마진 3을 의미한다. py-2는 위아래 padding 2를 의미한다. d-flex justify-content-end 는 컴포넌트의 우측 정렬을 의미한다.
✅ <table class = "table">
👉 기본적으로 class에 "table"을 추가하여 Bootstrap table을 사용할 수 있다.
✅ <thead class = "table-info">
👉 색상을 지정함
✅ 테이블 항목으로 <th>번호</th>
를 추가했다. 번호는 loop.count
를 사용하여 표시했다.
✅ <td th:text="${#temporals.format(questionlist.createDate, 'yyyy-MM-dd HH-mm')}"></td>
#temporals.format(날짜객체, 날짜포맷)
: 날짜객체를 날짜포맷에 맞게 변환한다.
질문 페이지가 예쁘게 수정되었다! 상세페이지에도 부트스트랩을 적용해보자!
<link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}">
<div class="container my-3">
<!-- 질문 -->
<h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;" th:text="${question.content}"></div>
<div class="d-flex justify-content-end">
<div class="badge bg-light text-dark p-2 text-start">
<div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
</div>
</div>
</div>
<!-- 답변의 갯수 표시 -->
<h5 class="border-bottom my-3 py-2"
th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
<!-- 답변 반복 시작 -->
<div class="card my-3" th:each="answer : ${question.answerList}">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;" th:text="${answer.content}"></div>
<div class="d-flex justify-content-end">
<div class="badge bg-light text-dark p-2 text-start">
<div th:text="${#temporals.format(answer.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
</div>
</div>
</div>
<!-- 답변 반복 끝 -->
<!-- 답변 작성 -->
<form th:action="@{|/answer/create/${question.id}|}" method="post" class="my-3">
<textarea name="content" id="content" rows="10" class="form-control"></textarea>
<input type="submit" value="답변등록" class="btn btn-primary my-2">
</form>
</div>
수정된 부분이 많으니 질문부분부터 보자!
<!-- 질문 -->
<h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;" th:text="${question.content}"></div>
<div class="d-flex justify-content-end">
<div class="badge bg-light text-dark p-2 text-start">
<div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
</div>
</div>
</div>
✅ <h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
👉 아래방향 테두리 선에 상하 패딩값 2를 준다
✅ <div class="card my-3">
👉 Card 컴포넌트를 적용시키고 상하 마진값 3을 준다.
✅ <div class="card-body">
👉 Card-body
✅ <div class="card-text" style="white-space: pre-line;" th:text="${question.content}"></div>
👉 Card-text
부트스트랩의 컴포넌트
👉 style="white-space: pre-line;"
💬 white-space는 스페이스와 탭, 줄바꿈, 자동줄바꿈을 어떻게 처리할지 정하는 속성
¹스페이스와 탭 ²줄바꿈 ³자동 줄바꿈 normal 병합 병합 O nowrap 병합 병합 X pre 보존 보존 X pre-wrap 보존 보존 O pre-line 병합 보존 O 1. 연속된 스페이스와 탭의 처리 방법입니다. 병합은 1개의 공백으로 바꾸는 것이고, 보존은 입력된 그대로 출력하는 것입니다. 2. 줄바꿈의 처리방법입니다. 병합은 1개의 공백으로 바꾸는 것이고, 보존은 입력된 그대로 출력하는 것입니다. 3. 내용이 영역의 크기를 벗어날 때 처리방법입니다. O는 자동으로 줄바꿈하여 영역 내에 내용을 표시하는 것이고, X는 영역을 벗어나더라도 입력된 대로 출력하는 것입니다.
✅ <div class="d-flex justify-content-end">
👉 컴포넌트의 우측 정렬
✅ <div class="badge bg-light text-dark p-2 text-start">
👉 badge
부트스트랩의 컴포넌트 / bg-light
연회색 배경 / text-dark
검은색 글씨 / p-2
상하좌우 패딩값 2 / text-start
좌측정렬
답변 개수부분을 보자!
<!-- 질문 -->
<h5 class="border-bottom my-3 py-2"
th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
✅ <h5 class="border-bottom my-3 py-2" th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
👉 아래방향 테두리 선에 상하 마진값3, 상하 패딩값 2를 준다
답변 반복 시작 부분을 보자
<!-- 질문 -->
<div class="card my-3" th:each="answer : ${question.answerList}">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;" th:text="${answer.content}"></div>
<div class="d-flex justify-content-end">
<div class="badge bg-light text-dark p-2 text-start">
<div th:text="${#temporals.format(answer.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
</div>
</div>
</div>
✅ <div class="card my-3" th:each="answer : ${question.answerList}">
✅ <div class="card-body">
✅ <div class="card-text" style="white-space: pre-line;" th:text="${question.content}"></div>
✅<div class="badge bg-light text-dark p-2 text-start"><div th:text="${#temporals.format(answer.createDate, 'yyyy-MM-dd HH:mm')}"></div>
질문 부분과 거의 비슷하다!
답변 작성부분을 보자!
<form th:action="@{|/answer/create/${question.id}|}" method="post" class="my-3">
<textarea name="content" id="content" rows="10" class="form-control"></textarea>
<input type="submit" value="답변등록" class="btn btn-primary my-2">
</form>
✅ class="form-control"
👉 input, textaerea, select 등 하나의 요소를 감싸는 레벨과 클래스 ->.form-group이 있다. form-control 클래스는 input, textarea, select 요소에 삽입이 되어야한다.
✅ class="btn btn-primary my-2"
부트스트랩의 버튼 컴포넌트
❓ margin과 padding의 차이는?
❗ Margin은 Object와 화면과의 여백(외부여백)을 말하며 Padding은 Object내의 내부여백을 의미합니다.
<link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}">
<div class="container my-3">
<!-- 질문 -->
<div class="card my-3">
<h2 class="card-header bg-info text-white border-bottom p-3" th:text="${question.subject}"></h2>
<div class="card-body bg-light">
<div class="card-text p-2" style="white-space: pre-line;" th:text="${question.content}"></div>
<div class="d-flex justify-content-end">
<div class="badge rounded-pill bg-info text-white">
<div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
</div>
</div>
</div>
<!-- 답변의 갯수 표시 -->
<div class="card my-3">
<h5 class="card-header bg-info text-white border-bottom p-3"
th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
<!-- 답변 반복 시작 -->
<div class="list-group list-group-flush" th:each="answer, loop : ${question.answerList}">
<div class="list-group-item bg-light">
<h8 class="card-text" style="white-space: pre-line;" th:text="${answer.content}"></h8>
<div class="badge bg-primary text-white" th:text="${loop.count}"></div>
<div class="d-flex justify-content-end">
<div class="badge rounded-pill bg-info text-white">
<div th:text="${#temporals.format(answer.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
</div>
</div>
</div>
</div>
<!-- 답변 반복 끝 -->
<!-- 답변 작성 -->
<form th:action="@{|/answer/create/${question.id}|}" method="post" class="my-5">
<textarea name="content" id="content" rows="3" class="form-control"></textarea>
<input type="submit" value="답변등록 " class="btn btn-primary my-2">
</form>
</div>