티스토리 뷰

2-13 CSS 적용하기

스태틱 디렉터리

기본적으로 스프링부트 프로젝트 생성 시, src/main/resources 아래 static 폴더가 있다.

이 폴더에는 css, js, 이미지 파일을 올려서 관리한다.

 

1) style.css 라는 파일을 만들고 아래와 같이 작성한다.

textarea {
    width:100%;
}

input[type=submit] {
    margin-top:10px;
}

 

 

2) 템플릿에 스타일을 적용한다.

 

html 파일에 아래와 같이 link 를 작성하면 된다.

static 폴더를 루트 디렉터리로 인식하므로, /style.css 로만 작성하면 된다.

<link rel="stylesheet" type="text/css" th:href="@{/style.css}">

 

 

2-14 부트스트랩 적용하기

1) 아래 링크에서 부트스트랩을 설치한다.

https://getbootstrap.com/docs/5.3/getting-started/download/

 

부트스트랩에는 다양한 버전이 존재하고, 메이저 (3,4,5) 버전에 따라 사용법이 다르다.

이 책은 부트스트랩 버전 5 기준으로 실습을 진행했다.

내가 설치할 시점에는 bootstrap-5.3.3 으로 설치했다.

 

 

우측 상단에 버전 선택 후, Download

 

 

 

2) 다운로드 한 zip 파일을 압축해제 한다.

나는 c드라이브 밑에 tools 폴더 아래 압축해제 해두었다.

 

 

 

3) 압축해제한 폴더 안에 bootstrap.min.css 파일과 bootstrap.min.js 파일을 static 폴더 아래에 복사-> 붙여넣기 해준다.

 

 

 

 

4) 템플릿에 부트스트랩을 적용한다. 아래와 같이 link 를 작성하면 된다.

<link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}">

 

* link 를 작성한 코드 예제 (점프 투 스프링부트 발췌)

class="container my-3" , class="table" , class="table-dark" 와 같은 class 들은 

부트스트랩에 이미 정의되어 있는 클래스들로, 간격을 조정하고 테이블에 스타일을 지정하는 용도이다.

* 나머지 노란 부분은 설명 생략

 

 

부트 스트랩의 자세한 내용은 아래 링크를 참고한다.

https://getbootstrap.com/docs/5.3/getting-started/introduction/

 

 

questionDetail.html 파일에서 부트스트랩의 card 컴포넌트를 이용하여 디자인 해주었다.

<html layout:decorate="~{common/layout}">
<div layout:fragment="content" class="container my-3">
	<h1 class="border-bottom py-2" th:text="${question.subject}"></h1>
	
	<!--질문-->
	<div class="card my-3">
		<div class="card-body">
			<!-- 마크다운 적용 위해 주석
			<div class="card-text" style="white-space:pre-line;" th:text="${question.content}"></div>
			-->
			<!--마크다운표시.. 줄바꿈을 위해 사용한 기존 style 삭제후, 마크다운 적용,
			th:text가 아닌 th:utext 사용
			th:text 사용시 html 태그들이 escape 처리되어 화면에 그대로 보이게 됨.
			마크다운으로 변환된 html문서를 제대로 표시하기 위해선 utext써야함
			-->
			<div class="card-text" th:utext="${@commonUtil.markdown(question.content)}"></div>
			
			<div class="d-flex justify-content-end">
				<div th:if="${question.modifyDate != null}" class="badge bg-light text-dark p-2 text-start mx-3">
	                <div class="mb-2">modified at</div>
	                <div th:text="${#temporals.format(question.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
	            </div>
				
				<div class="badge bg-light text-dark p-2 text-start">
					<div class="mb-2">
	                    <span th:if="${question.user != null}" th:text="${question.user.username}"></span>
	                </div>
					<div th:text="${#temporals.format(question.writeDate, 'yyyy-MM-dd HH:mm')}"></div>
				</div>
			</div>
			
			<div class="my-3">
				<a href="javascript:void(0);" th:data-uri="@{|/question/vote/${question.id}|}"
					class="recommend btn btn-sm btn-outline-secondary">
				    추천
				    <span class="badge rounded-pill bg-success" th:text="${#lists.size(question.voter)}"></span>
				</a>
				
	            <a th:href="@{|/question/modify/${question.id}|}" class="btn btn-sm btn-outline-secondary"
	                sec:authorize="isAuthenticated()"
	                th:if="${question.user != null and #authentication.getPrincipal().getUsername() == question.user.username}"
	                th:text="수정"></a>
				
				<!--삭제버튼 클릭시 alert 문구를 위해 href에 바로 url을 넣지 않음
				javascript:void(0) 설정,
				삭제를 실행할 url을 얻기 위해 th:data-uri라는 속성에 url 추가
				-->
				<a href="javascript:void(0);" th:data-uri="@{|/question/delete/${question.id}|}"
	                class="delete btn btn-sm btn-outline-secondary"
					sec:authorize="isAuthenticated()"
	                th:if="${question.user != null and #authentication.getPrincipal().getUsername() == question.user.username}"
	                th:text="삭제"></a>
	        </div>
		</div>
	</div>
	
	<!--답변갯수-->
	<h5 class="border-bottom my-3 py-2" th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
	
	<!--답변반복-->
	<!--div 바로 아래 a 태그 : 답변수정,추천 후 화면 리프레쉬될때 이 위치로 돌아오도록 함. redirect경로에 a태그id값 함께 보냄-->
	<div class="card my-3" th:each="answer : ${question.answerList}">
		<a th:id="|answer_${answer.id}|"></a>
		<div class="card-body">
			<!--마크다운 적용 위해 주석
			<div class="card-text" style="white-space:pre-line;" th:text="${answer.content}"></div>
			-->
			<div class="card-text" th:utext="${@commonUtil.markdown(answer.content)}"></div>
			
			<div class="d-flex justify-content-end">
				<div th:if="${answer.modifyDate != null}" class="badge bg-light text-dark p-2 text-start mx-3">
	                <div class="mb-2">modified at</div>
	                <div th:text="${#temporals.format(answer.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
	            </div>
				
				<div class="badge bg-light text-dark p-2 text-start">
					<div class="mb-2">
	                    <span th:if="${answer.user != null}" th:text="${answer.user.username}"></span>
	                </div>
					<div th:text="${#temporals.format(answer.writeDate, 'yyyy-MM-dd HH:mm')}"></div>
				</div>
			</div>
			
			<div class="my-3">
				<a href="javascript:void(0);" th:data-uri="@{|/answer/vote/${answer.id}|}"
					class="recommend btn btn-sm btn-outline-secondary">
	                추천
	                <span class="badge rounded-pill bg-success" th:text="${#lists.size(answer.voter)}"></span>
	            </a>
				
				<a th:href="@{|/answer/modify/${answer.id}|}" class="btn btn-sm btn-outline-secondary"
					sec:authorize="isAuthenticated()"
					th:if="${answer.user != null and #authentication.getPrincipal().getUsername() == answer.user.username}"
					th:text="수정"></a>
				<a href="javascript:void(0);" th:data-uri="@{|/answer/delete/${answer.id}|}"
	                class="delete btn btn-sm btn-outline-secondary"
					sec:authorize="isAuthenticated()"
	                th:if="${answer.user != null and #authentication.getPrincipal().getUsername() == answer.user.username}"
	                th:text="삭제"></a>
	        </div>
		</div>
	</div>
	
	<!--답변작성-->
	<!-- 필수입력체크를 위해 AnswerForm.java 작성했고, th:object 선언을 해준다 -->
	<form th:action="@{|/answer/create/${question.id}|}" th:object="${answerForm}" method="post" class="my-3">
		<!--post 호출 후 에러 발생시 문구 표출란 -->
		<div th:replace="~{common/formErrors :: formErrorsFragment}"></div>
		
		<!-- sec:authorize="isAnonymous()" : 로그인안했을 때 textarea 보이게 -->
	    <textarea sec:authorize="isAnonymous()" th:field="*{content}" rows="10" class="form-control" disabled></textarea>
		<!-- sec:authorize="isAuthenticated()" : 로그인했을 때 textarea 보이게-->
		<textarea sec:authorize="isAuthenticated()" th:field="*{content}" rows="10" class="form-control"></textarea>
		
	    <input type="submit" value="답변등록" class="btn btn-primary my-2">
	</form>
</div>

<script layout:fragment="script" type='text/javascript'>
const delete_elements = document.getElementsByClassName("delete");

Array.from(delete_elements).forEach(function(element) {
//	console.log(delete_elements)
//	console.log(element)
	
//class="delete" 에 이벤트 추가
    element.addEventListener('click', function() {
        if(confirm("정말로 삭제하시겠습니까?")) {
            location.href = this.dataset.uri;
        };
    });
});


const recommend_elements = document.getElementsByClassName("recommend");
Array.from(recommend_elements).forEach(function(element) {
    element.addEventListener('click', function() {
        if(confirm("정말로 추천하시겠습니까?")) {
            location.href = this.dataset.uri;
        };
    });
});
</script>
</html>

 

 

위 코드에서

card, card-body, card-text 등이 card 컴포넌트이고 

그 외에도 다양한 클래스를 사용했다.

 

 

 

부트스트랩의 card 컴포넌트에 대한 설명은 아래 링크를 참고한다.

https://getbootstrap.com/docs/5.3/components/card/

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/09   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함