Web/Spring

Spring boot - 게시판 만들기 4 ( 서비스, 컨트롤러 및 View 만들기 )

dev_sr 2020. 10. 13. 13:01

 

1. 서비스 처리하기

 

MVC 패턴에서 모델에 해당함

 

비즈니스 로직을 담당한다.

-DB에서 조회, 저장할 내용 가공, 처리등을 담당

 

⭐인터페이스와 실행 클래스를 구분하는 이유

비지니스 로직을 처리하는 모델은 요청사항에 따라 언제든지 변화할 수 있는데

코드의 재사용과 확장을 염두하여 인터페이스로 구성하게 되었다.

 

 

MVC 구조에서 service와 serviceImpl

이제는 JSP 개발시 MVC 패턴으로 작업하는게 당연시 되고있다. MVC패턴이란 화면에 보여줄 view 에 대한 작업, DB에서 조회 혹은 DB에 저장할 내용을 중간에 가공, 처리하는 비즈니스로직, DB에 연결��

multifrontgarden.tistory.com

 

 

서비스를 구현할 인터페이스를 만든다.

package com.board2.service;

import java.util.List;

import com.board2.domain.BoardDTO;

public interface BoardService {

	public boolean registerBoard(BoardDTO params);
	public BoardDTO getBoardDetail(Long idx);
	public boolean deleteBoard(Long idx);
	public List<BoardDTO> getBoardList();
	public boolean cntPlus(Long idx);
}

 

 

인터페이스를 구현할 클래스 생성

package com.board2.service;

import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.board2.domain.BoardDTO;
import com.board2.mapper.BoardMapper;

@Service
public class BoardServiceImpl implements BoardService{

	@Autowired
	private BoardMapper boardMapper;
	
	@Override
	public boolean registerBoard(BoardDTO params) {
	
		int queryResult =0;
		if(params.getIdx()==null) {
			queryResult= boardMapper.insertBoard(params);
		} else {
			queryResult = boardMapper.updateBoard(params);
		}
		return (queryResult==1)?true:false;
	}
	
	@Override
	public BoardDTO getBoardDetail(Long idx) {
		return boardMapper.selectBoardDetail(idx);
	}
	
	@Override
	public boolean deleteBoard(Long idx) {
		int queryResult=0;
		BoardDTO board = boardMapper.selectBoardDetail(idx);
		
		if(board!=null && "N".equals(board.getDeleteYn())){
			queryResult = boardMapper.deleteBoard(idx);
		}
		return (queryResult==1)?true:false;
	}
	
	@Override
	public List<BoardDTO> getBoardList(){
		List<BoardDTO> boardList = Collections.emptyList();
		int boardTotalCount = boardMapper.selectBoardTotalCount();
		if(boardTotalCount>0) {
			boardList= boardMapper.selectBoardList();
		}
		
		return boardList;
	}
	

	@Override
	public boolean cntPlus(Long idx) {
		return boardMapper.cntPlus(idx);
	}
}

 

registerBoard

인덱스가 null이라면 글을 등록

null이 아니라면 수정

 

getBoardDetail

게시글 조회

 

deleteBoard

특정 게시글을 조회하고, 조회 중인 상태의 글만 삭제함

 

getBoardList

삭제되지 않은 게시글 조회

Collections.emptyList 메서드를 통해 빈 리스트를 선언해줌

 

 

 

 

2. 컨트롤러 처리하기 

 

사용자의 요청과 응답 처리 담당

모델(서비스)와 뷰의 중간 역할

 

 

package com.board2.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import com.board2.service.BoardService;

@Controller
public class BoardController {

	@Autowired
	private BoardService boardService;
	
	@GetMapping("/board/write.do")
	public String openBoardWrite(Model model) {
		return "board/write";
	}
}

 

@Controller

사용자의 요청과 응답을 처리하는 컨트롤러

 

리턴 타입

ModelAndView를 사용하거나, String을 이용해서 html의 경로를 불러올 수 있다.

 

model

데이터를 view 로 전달하는 데 사용됨

 

 

 

 

 

3. HTML 만들기

 

templates 에 board라는 폴더를 만들어주고

write.html 추가

 

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>Write Page</title>
	</head>
	<body>
		<h2>게 시 판</h2>
	</body>
</html>

 

 

4. Model 인터페이스로 값 전달해보기

 

컨트롤러를 이렇게 바꿔줌

package com.board2.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import com.board2.service.BoardService;

@Controller
public class BoardController {

	@Autowired
	private BoardService boardService;
	
	@GetMapping("/board/write.do")
	public String openBoardWrite(Model model) {
		
		String title ="제목";
		String content ="내용";
		String writer ="홍길동";
		
		model.addAttribute("title", title);
		model.addAttribute("content", content);
		model.addAttribute("writer", writer);
		
		return "board/write";
	}
}

 

model.addAttribute의 인자로 이름, 값이 전달되는데

웬만하면 이름과 값을 동일하게 지정한다.

 

 

<!DOCTYPE html>
<html lang ="ko" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta charset="UTF-8">
		<title>Write Page</title>
	</head>
	<body>
		<h2>게 시 판</h2>
		<span th:text="${title}">여기는 제목</span>
		<span th:text="${content}">여기는 내용</span>
		<span th:text="${writer}">여기는 작성자</span>
	</body>
</html>

 

xmlns:th

타임리프의 th 속성을 사용하기 위해 선언된 네임스페이스

 

<span>

인라인 요소(inline-element)들을 하나로 묶을 때 사용

 

th:text

텍스트 형식으로 화면에 출력

${ }를 이용해서 컨트롤러에서 전달받은 데이터에 접근 가능

 

 

 

5. view 만들기

 

 

아래 파일을 다운 받고 압축을 풀면

부트스트랩 플러그인, css, 자바스크립트가 있는데

src/main/resources 의 static에 풀어줌

 

static.zip
1.00MB

 

 

 

 

1) 타임리프의 레이아웃 기능을 이용하기 위해 라이브러리 추가

 

build.gradle의 dependencies에 선언함

implementation 끼리 모아두어야함

 

작성하고 프로젝트에 마우스 우클릭->Gradle -> Refresh Gradle Project를 해줌

 

 

 

 

 

2) 공통으로 사용할 레이아웃의 헤더와 바디를 나눠서 만들기

 

먼저 폴더와 html을 추가해준다.

 

 

header.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
	<head th:fragment="main-head">
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
		<meta name="description" content="">
		<meta name="author" content="">

		<th:block layout:fragment="title"></th:block>

		<link rel="stylesheet" th:href="@{/css/style.css}" />
		<link rel="stylesheet" th:href="@{/plugin/mCustomScrollbar/jquery.mCustomScrollbar.min.css}" />

		<th:block layout:fragment="add-css"></th:block>
	</head>
</html>

 

 

th: fragment 

이름을 지정하여 다른 HTML에서 include, replace하여 사용할 수 있음

 

th: block

layout:fragment 속성에 이름을 지정해서 실제 Content 페이지의 내용을 채우는 기능

동적인 처리가 필요할 때 사용

 

th:href

<a href>, <link href> 와 동일함 

@를 붙여서 특정 url로 이동하거나 서버내 특정 위치로 이동하는 URL을 제공함

 

<meta>

해당 문서에 대한 정보인 메타데이터(metadata)를 정의할 때 사용

언제나 <head> 요소 내부에 위치해야 함

만약 name 속성이나 http-equiv 속성이 명시되었다면

반드시 content 속성도 함께 명시되어야 하며,

반대로 두 속성이 명시되지 않았다면 content 속성 또한 명시될 수 없다.

 

<link>

해당 문서와 외부 소스 사이의 관계를 정의할 때 사용

head에만 위치할 수 있고, 개수에 제한은 없다.

외부 스타일 시트를 연결할 때 사용

 

 

 

 

 

body.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
	<body th:fragment="main-body">
		<div class="fixed-navbar">
			<div class="pull-left">
				<h1 class="page-title">Board</h1>
			</div>
		</div>
		<!-- /.fixed-navbar -->

		<div id="wrapper">
			<div class="main-content">
				<div class="row row-inline-block small-spacing">
					<div class="col-xs-12">
						<div class="box-content">
							<div class="clearfix">
								<h4 class="box-title pull-left"></h4>
								<!-- /.box-title -->
	
								<th:block layout:fragment="search"></th:block>
	
							</div>
							<!-- //.clearfix -->
	
							<th:block layout:fragment="content"></th:block>
	
							<th:block layout:fragment="paging"></th:block>
						</div>
						<!-- /.box-content -->
						
						<th:block layout:fragment="add-content"></th:block>

					</div>
					<!-- /.col-xs-12 -->
				</div>
				<!-- /.row row-inline-block small-spacing -->
				<footer class="footer">
					<ul class="list-inline">
						<li>2016 © NinjaAdmin.</li>
					</ul>
				</footer>
			</div>
			<!-- /.main-content -->
		</div>
		<!-- /#wrapper -->

		<script th:src="@{/scripts/jquery.min.js}"></script>
		<script th:src="@{/plugin/mCustomScrollbar/jquery.mCustomScrollbar.concat.min.js}"></script>
		<script th:src="@{/plugin/bootstrap/js/bootstrap.min.js}"></script>

		<script th:src="@{/scripts/main.js}"></script>
		<script th:src="@{/scripts/common.js}"></script>

		<th:block layout:fragment="script"></th:block>
	</body>
</html>
 

 

나중에 구현할 search, content, paging, add-content 이 나타날 영역을 미리 설정해 놓음

 

⭐Jquery를 가장 먼저 불러와야 나중에 view.html을 작성할 때 $를 인식을 못하는 오류가 발생하지 않는다

 

 

<div>

문서의 특정 영역이나 구획을 나눠 정의할 때 사용

웹페이지의 레이아웃을 설정하거나, 자바스크립트로 특정 작업을 수행하기 위한 컨테이너로 사용 됨

 

<footer>

특정 세선의 footer를 정의할 때 사용

저자의 정보, 저작권 정보, 연락처, 사이트맵 등이 표현됨

 

 

 

 

 

 

3) write.html에 적용하기

 

write.html의 내용을 수정해준다.

 

<!DOCTYPE html>
<html lang="ko" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
	<head th:replace="board/fragments/header :: main-head"> </head>
	<body th:replace="board/fragments/body :: main-body"> </body>
</html>

 

th:replace

해당 속성이 선언된 html 태그를 replace에 정의된 다른 html 파일로 치환한다.

 

 

 

 

 

 

4) 레이아웃 처리하기

 

헤더와 바디(푸터)는 게시판의 모든 페이지에서 공통으로 사용되기 때문에

레이아웃으로 처리되어야한다고 함

 

layout에 basic.html을 새로 만들어주고

 

write.html 의 내용을 옮김

 

basic.html

<!DOCTYPE html>
<html lang="ko" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
	<head th:replace="board/fragments/header :: main-head"> </head>
	<body th:replace="board/fragments/body :: main-body"> </body>
</html>

 

 

Controller의 Getmapping을 수정해줌

package com.board2.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.board2.domain.BoardDTO;
import com.board2.service.BoardService;

@Controller
public class BoardController {

	@Autowired
	private BoardService boardService;
	
	@GetMapping("/board/write.do")
	public String openBoardWrite(@RequestParam(value = "idx", required = false) Long idx, Model model) {
		if (idx == null) {
			model.addAttribute("board", new BoardDTO());
		} else {
			BoardDTO board = boardService.getBoardDetail(idx);
			if (board == null) {
				return "redirect:/board/list.do";
			}
			model.addAttribute("board", board);
		}

		return "board/write";
	}
}

 

@RequestParam

뷰에서 전달받은 파라미터를 처리하는 데 사용

required는 true가 default 이지만

false로 지정해주지 않으면 idx가 파라미터로 전송되지 않았을 때 오류가 발생함.

 

 

rediect 

Web Container로 명령이 들어오면, 웹 브라우저에게 다른 페이지로 이동하라고 명령을 내리는데 웹 브라우저는 URL을 지시된 주소로 바꾸고 해당 주소로 이동한다.

다른 웹 컨테이너에 있는 주소로 이동하며 새로운 페이지에서는 Request와 Response객체가 새롭게 생성된다.

즉, 요청정보와 응답정보가 유지되지 않는다.

시스템에 변화가 생기는 요청(회원가입, 글쓰기 등)에는 redirect를 사용하는 것이 좋다고 한다.

 

여기서는 요청과 응답을 새로 만들고 list.do 페이지로 보내게 된다. (아직 없으므로 404)

 

 

 

write.html  수정

내용(컨텐츠)를 보여줄거임

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="board/layout/basic">
	<th:block layout:fragment="title">
		<title>thiThe page is a write page</title>
	</th:block>

	<th:block layout:fragment="content">
		<div class="card-content">
			<form class="form-horizontal" th:action="@{/board/register.do}" th:object="${board}" method="post" onsubmit="return registerBoard(this)">
				<!--/* update의 경우 서버로 전달할 게시글 번호 (PK) */-->
				<input type="hidden" th:if="*{idx != null and idx > 0}" th:field="*{idx}" />

				<div class="form-group">
					<label for="noticeYn" class="col-sm-2 control-label">공지글 설정</label>
					<div class="col-sm-10" style="margin-top: 10px;">
						<input type="checkbox" th:value="*{noticeYn}" id="noticeYn" name="noticeYn" th:checked="*{#strings.equals( noticeYn, 'Y' )}" />
					</div>
				</div>

				<div class="form-group">
					<label for="secretYn" class="col-sm-2 control-label">비밀글 설정</label>
					<div class="col-sm-10" style="margin-top: 10px;">
						<input type="checkbox" th:value="*{secretYn}" id="secretYn" name="secretYn" th:checked="*{#strings.equals( secretYn, 'Y' )}" />
					</div>
				</div>

				<div class="form-group">
					<label for="title" class="col-sm-2 control-label">제목</label>
					<div class="col-sm-10">
						<input type="text" th:field="*{title}" class="form-control" placeholder="제목을 입력해 주세요." />
					</div>
				</div>

				<div class="form-group">
					<label for="writer" class="col-sm-2 control-label">이름</label>
					<div class="col-sm-10">
						<input type="text" th:field="*{writer}" class="form-control" placeholder="이름을 입력해 주세요." />
					</div>
				</div>

				<div class="form-group">
					<label for="content" class="col-sm-2 control-label">내용</label>
					<div class="col-sm-10">
						<textarea th:field="*{content}" class="form-control" placeholder="내용을 입력해 주세요."></textarea>
					</div>
				</div>

				<div class="btn_wrap text-center">
					<a th:href="@{/board/list.do}" class="btn btn-default waves-effect waves-light">뒤로가기</a>
					<button type="submit" class="btn btn-primary waves-effect waves-light">저장하기</button>
				</div>
			</form>
		</div>
		<!-- /.card-content -->
	</th:block>

	<th:block layout:fragment="script">
		<script th:inline="javascript">
			/*<![CDATA[*/

			function registerBoard(form) {

				form.noticeYn.value = form.noticeYn.checked == false ? 'N' : 'Y';
				form.secretYn.value = form.secretYn.checked == false ? 'N' : 'Y';

				var result = (
						   isValid(form.title, "제목", null, null)
						&& isValid(form.writer, "이름", null, null)
						&& isValid(form.content, "내용", null, null)
				);

				if ( result == false ) {
					return false;
				}
			}
			/*[- end of function -]*/

			/*]]>*/
		</script>
	</th:block>
</html>

 

<form>

사용자로부터 입력을 받을 수 있는 HTLM 입력 폼(form)을 정의할 때 사용

 

 

th:action

form 태그 사용 시, 해당 경로로 요청을 보낼 때 사용. (url)

 

 

th:object

form submit을 할 때, form의 데이터가 th:object에 설정해준 객체로 받아진다.

여기서 ${board} 는 컨트롤러에서 뷰로 전달한 BoardDTO의 객체이다.

 

 

method

get, post 등 메서드 지정

 

onsubmit

폼 데이터를 컨트롤러로 전송하기 전에 유효성을 검사함

submit을 했을 때 onsubmit이 실행되어서 아래쪽의 registerBoard를 실행한다. (기본적으로 true를 반환)

유효성을 검사하고 true를 반환한다면 Controller로 폼 데이터를 넘기게 됨

return이 없다면 registerBoard가 실행되지 않고 바로 컨트롤러를 호출하게 됨

 

 

th:inline

<script>에 th:inline 속성을 javascript로 지정해야 자바스크립트 사용 가능

 

 

<![CDATA[]]>

특수문자를 전부 문자열로 치환해줌

보통 마이바티스에서 쿼리문 작성시

<, >, & 를 사용하여 비교연산자를 나타내야할 때 <>괄호로 인식하게 하지 않기 위해서 사용한다고 한다.

타임리프문 안에서 자바스크립트를 사용할 때도 무조건 묶어줘야한다고 한다. 

 

 

registerBoard

자기자신인 form 객체를 전달 받아서 유효성을 검사함

noticeYn과 secretYn의 체크여부를 따지고 N이나 Y를 반환함

 

 

isValid 메서드의 경우 

위에서 받아둔 파일의 scripts -> common.js에서 찾을 수 있는데

폼 데이터로 넘어온 제목, 작성자, 내용이 비어있지 않은지 검사한 뒤 

비어있지 않다면 true를 반환하고 그렇지 않다면 false를 반환한다.

/**
 * field의 값이 올바른 형식인지 체크 (정규표현식 사용)
 * 
 * @param field - 타겟 필드
 * @param fieldName - 필드 이름 (null 허용)
 * @param focusField - 포커스할 필드 (null 허용)
 * @returns 메시지
 */
function isValid(field, fieldName, focusField) {

	if (isEmpty(field.value) == true) {
		/* 종성으로 조사(을 또는 를) 구분 */
		var message = (charToUnicode(fieldName) > 0) ? fieldName + "을 확인해 주세요." : fieldName + "를 확인해 주세요."; 
		field.focus();
		alert(message);
		return false;
	}

	return true;
}

 

 

 

 

여기까지 하면 이렇게 제대로 안나올 수도 있는데

 

sts4를 다시 껐다 키면 제대로 나옴

 

 

 

 

5) 리스트 페이지 만들기

 

컨트롤러에 게시글 목록을 보여줄 코드를 작성해줌

 

	@GetMapping(value = "/board/list.do")
	public String openBoardList(Model model) {
		List<BoardDTO> boardList = boardService.getBoardList();
		model.addAttribute("boardList", boardList);

		return "board/list";
	}

 

 

 

list.html 을 추가하고 작성해줌

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="board/layout/basic">
	<th:block layout:fragment="title">
		<title>This page is a list page</title>
	</th:block>

	<th:block layout:fragment="search">
		<form action="#" id="searchform-header" class="searchform js__toggle active pull-right">
			<input type="search" placeholder="Search..." class="input-search">
			<button class="mdi mdi-magnify button-search" type="submit">
				<i class="fa fa-search" aria-hidden="true"></i>
			</button>
		</form>
	</th:block>

	<th:block layout:fragment="content">
		<div class="table-responsive clearfix">
			<table class="table table-hover">
				<thead>
					<tr>
						<th>번호</th>
						<th>제목</th>
						<th>작성자</th>
						<th>등록일</th>
						<th>조회 수</th>
					</tr>
				</thead>
				<tbody>
					<tr th:if="${not #lists.isEmpty( boardList )}" th:each="row : ${boardList}">
						<td scope="row" th:text="${#strings.equals( row.noticeYn, 'Y' ) ? '공지' : row.idx}"></td>
						<td class="text-left">
							<a th:href="@{/board/view.do( idx=${row.idx} )}" th:text="${row.title}"></a>
						</td>
						<td th:text="${row.writer}"></td>
						<td th:text="${#temporals.format( row.insertTime, 'yyyy-MM-dd' )}"></td>
						<td th:text="${row.viewCnt}"></td>
					</tr>
					<tr th:unless="${not #lists.isEmpty( boardList )}">
						<td colspan="5">조회된 결과가 없습니다.</td>
					</tr>
				</tbody>
			</table>

			<div class="btn_wrap text-right">
				<a th:href="@{/board/write.do}" class="btn btn-primary waves-effect waves-light">Write</a>
			</div>

			<th:block layout:fragment="paging">
				<nav aria-label="Page navigation" class="text-center">
					<ul class="pagination">
						<li>
							<a href="#" aria-label="Previous">
								<span aria-hidden="true">&laquo;</span>
							</a>
						</li>
						<li><a href="#">1</a></li>
						<li><a href="#">2</a></li>
						<li><a href="#">3</a></li>
						<li><a href="#">4</a></li>
						<li><a href="#">5</a></li>
						<li>
							<a href="#" aria-label="Next">
								<span aria-hidden="true">&raquo;</span>
							</a>
						</li>
					</ul>
				</nav>
			</th:block>
		</div>
	</th:block>
</html>

 

th:if  th:unless

if else문과 비슷한데

if문에 들어간 조건이 unless에도 동일하게 들어가야한다.

 

 

#list.isEmpty

리스트가 비어있는가 확인함

static->scripts->common.js 에 있음

/**
 * 자료형에 상관없이 값이 비어있는지 확인
 * 
 * @param value - 타겟 밸류
 * @returns true or false
 */
function isEmpty(value) {
	if (value == null || value == "" || value == undefined || (value != null && typeof value == "object" && !Object.keys(value).length)) {
		return true;
	}

	return false;
}

 

th:each

foreach 기능임

row 라는 이름으로 boardList를 순환해서 데이터를 출력함

 

<li>

리스트에 포함되는 아이템을 정의할 때 사용

 

 

 

 

 

6) 게시글 하나 상세히 보기

 

컨트롤러에 게시글을 읽고 view.html을 불러오는 메서드 작성

	@GetMapping("/board/view.do")
	public String openBoardDetail(@RequestParam(value="idx", required=false)
	Long idx, Model model) {
		if(idx==null)
		{
			//올바르지 않은 접근
			return "redirect:/board/list.do";
		}
		
		BoardDTO board = boardService.getBoardDetail(idx);
		if(board==null || "Y".equals(board.getDeleteYn())) {
			//없는 게시글 or 이미 삭제된 게시글
			return "redirect:/board/list.do";
		}
		model.addAttribute("board",board);
		
		return "board/view";
	}

 

 

src / main /resources  templates/board 에 view.html을 추가해줌

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="board/layout/basic">
	<th:block layout:fragment="title">
		<title>this is view page</title>
	</th:block>

	<th:block layout:fragment="content">
		<div class="card-content">
			<form class="form-horizontal form-view" th:object="${board}">
				<div class="form-group">
					<label for="inp-type-1" class="col-sm-2 control-label">제목</label>
					<div class="col-sm-10">
						<p class="form-control" th:text="*{title}"></p>
					</div>
				</div>

				<div class="form-group">
					<label for="inp-type-2" class="col-sm-2 control-label">이름</label>
					<div class="col-sm-10">
						<p class="form-control" th:text="*{writer}"></p>
					</div>
				</div>

				<div class="form-group">
					<label for="inp-type-5" class="col-sm-2 control-label">내용</label>
					<div class="col-sm-10">
						<p class="form-control" th:text="*{content}"></p>
					</div>
				</div>

				<div class="form-group">
					<label for="inp-type-5" class="col-sm-2 control-label">등록일</label>
					<div class="col-sm-10">
						<p class="form-control" th:text="*{#temporals.format( insertTime, 'yyyy-MM-dd' )}"></p>
					</div>
				</div>

				<div class="form-group">
					<label for="inp-type-5" class="col-sm-2 control-label">조회 수</label>
					<div class="col-sm-10">
						<p class="form-control" th:text="*{viewCnt}"></p>
					</div>
				</div>
			</form>

			<div class="btn_wrap text-center">
				<a th:href="@{/board/list.do}" class="btn btn-default waves-effect waves-light">뒤로가기</a>
				<a th:href="@{/board/write.do( idx=${board.idx} )}" class="btn btn-primary waves-effect waves-light">수정하기</a>
				<button type="button" class="btn btn-danger waves-effect waves-light" th:onclick="deleteBoard([[ ${board.idx} ]])">삭제하기</button>
			</div>
		</div>
		<!-- /.card-content -->
	</th:block>

	<th:block layout:fragment="add-content">
		<div class="box-content">
			<div class="card-content">
				<div class="clearfix">
					<h4 class="box-title pull-left">Comment</h4>
					<!-- /.box-title -->
				</div>

				<form class="form-horizontal form-view">
					<div class="input-group margin-bottom-20">
						<input type="email" class="form-control" placeholder="">
						<div class="input-group-btn"><button type="button" class="btn waves-effect waves-light"><i class="fa fa-commenting" aria-hidden="true"></i></button></div>
						<!-- /.input-group-btn -->
					</div>
					<ul class="notice-list">
						<li>
							<span class="name">Betty Simmons</span>
							<span class="desc">There are new settings available</span>
							<span class="time">2019.03.26</span>
							<button type="button" class="btn btn-xs btn-circle"><i class="fa fa-close" aria-hidden="true"></i></button>
						</li>
						<li>
							<span class="name">Betty Kim</span>
							<span class="desc">There are new settings available</span>
							<span class="time">2019.03.15</span>
							<button type="button" class="btn btn-xs btn-circle"><i class="fa fa-close" aria-hidden="true"></i></button>
						</li>
					</ul>
				</form>
			</div>
			<!-- /.card-content -->
		</div>
		<!-- /.box-content -->
	</th:block>
</html>

 

layout: fragment = "content" 부분에는

콘텐츠와 버튼이 들어가고

 

layout: fragment = "add-content" 부분에는

댓글이 출력됨

 

 

 

이제 글을 클릭하면 상세 페이지로 이동함

 

 

 

수정하기 누르고 내용을 바꿔봤을 때

 

 

저장하기를 누르면 수정된 사항이 적용됨

 

 

 

 

 

7) 삭제하기

 

컨트롤러에 삭제 메서드 추가

	@PostMapping("/board/delete.do")
	public String deleteBoard(@RequestParam(value="idx", required = false)
	Long idx) {
		if(idx==null) {
			//올바르지 않은 접근
			return "redirect:/board/list.do";
		}
		try {
			boolean isDeleted = boardService.deleteBoard(idx);
			if(isDeleted==false) {
				//게시글 삭제 실패
			}
		} catch(DataAccessException e) {
			//데이터 베이스 처리과정에 문제 발생
		} catch(Exception e) {
			//시스템 문제 발생
		}
		return "redirect:/board/list.do";
	}

 

지금은 눌러도 아무일도 일어나지 않음

 

 

view.html 삭제하기 버튼 부분

<div class="btn_wrap text-center">
	<a th:href="@{/board/list.do}" class="btn btn-default waves-effect waves-light">뒤로가기</a>
	<a th:href="@{/board/write.do( idx=${board.idx} )}" class="btn btn-primary waves-effect waves-light">수정하기</a>
	<button type="button" class="btn btn-danger waves-effect waves-light" th:onclick="deleteBoard([[ ${board.idx} ]])">삭제하기</button>
</div>

 

([[ ${data} ]])

자바스크립트에서 타임리프 문법쓰기

 

th:onclick 

자바스크립트 deleteBoard 메서드의 인자로 idx를 받아옴

 

 

deleteBoard 메서드는 밑에 추가해줌

 

	<th:block layout:fragment="script">
		<script th:inline="javascript">
			/*<![CDATA[*/

			function deleteBoard(idx) {

				if (confirm(idx + "번 게시글을 삭제할까요?")) {
					console.log(idx);
					var uri = /*[[@{/board/delete.do}]]*/"";
					var html = "";

					html += '<form name="dataForm" action="'+uri+'" method="post">';
					html += '<input type="hidden" name="idx" value="'+idx+'" />';
					html += '</form>';

					$("body").append(html);
					document.dataForm.submit();
				}
			}
			/*[- end of function -]*/

			/*]]>*/
		</script>
	</th:block>

 

<![CDATA[]]>

자바스크립트에서 타임리프 문법을 사용하기 위해 묶어줌

 

 

confirm 

자바스크립트 Alert 창과 유사함

확인을 누르면 true, 취소를 누르면 false를 리턴하게 됨

 

 

uri

BoardController에 선언한 게시글 삭제 메서드와 매핑된 URI

 

⭐ javascript inlining

타임리프에선 자바스크립트 변수를 

var message = /*[[${message}]]*/;

이렇게 쓸 수 있음

 

그런데 ; 에서 exception 에러가 발생하는 대참사가 발생할 수도 있음 

 

어차피 변수니까 밑의 uri 자리에 값을 직접 넣어봐도 되지 않을까 싶어서 해봄

안됨

 

이걸 해결하려면  뒤에 ""을 붙여줘야함

var message = /*[[${message}]]*/""; 

 

Setting up a JavaScript variable from Spring model by using Thymeleaf

I am using Thymeleaf as template engine. How I pass a variable from Spring model to JavaScript variable? Spring-side: @RequestMapping(value = "message", method = RequestMethod.GET) public String

stackoverflow.com

www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#javascript-inlining

 

 

 

 

 

html

dataForm이라는 폼 안에 파라미터로 전달받은 idx를 hidden 타입으로 추가한다고 함

게시글 삭제 이벤트 시에만 추가하기 위해 동적으로 이렇게 폼을 생성할 수 있다고 함

 

<input type="hidden"> 

사용자에게 보이지 않는 입력창이지만 전송은 한다.

 

 

$("body").append(html)

html <body> 태그 안에 html 변수에 담긴 폼을 추가한다 

fragments에 body.html 을 작성할 때 jquery 를 불러오는 script 태그를 작성할 때

jquery 를 가장 위에 써주지 않으면 $를 인식하지 못하는 에러가 발생함 

 

body.html 하단

		<script th:src="@{/scripts/jquery.min.js}"></script>
		<script th:src="@{/plugin/mCustomScrollbar/jquery.mCustomScrollbar.concat.min.js}"></script>
		<script th:src="@{/plugin/bootstrap/js/bootstrap.min.js}"></script>

		<script th:src="@{/scripts/main.js}"></script>
		<script th:src="@{/scripts/common.js}"></script>

 

 

uncaught referenceerror $ is not defined

제이쿼리를 사용하다보면 간혹 이런 에러가 나올떄 있다. uncaught referenceerror $ is not defined 필자...

blog.naver.com

 

document,dataForm.submit( )

<body> 에 추가된 폼을 찾아 컨트롤러로 서브밋한다.

 

 

 

 

view.html 전체 코드

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	layout:decorator="board/layout/basic">
<th:block layout:fragment="title">
	<title>this is view page</title>
</th:block>

<th:block layout:fragment="content">
	<div class="card-content">
		<form class="form-horizontal form-view" th:object="${board}">
			<div class="form-group">
				<label for="inp-type-1" class="col-sm-2 control-label">제목</label>
				<div class="col-sm-10">
					<p class="form-control" th:text="*{title}"></p>
				</div>
			</div>

			<div class="form-group">
				<label for="inp-type-2" class="col-sm-2 control-label">이름</label>
				<div class="col-sm-10">
					<p class="form-control" th:text="*{writer}"></p>
				</div>
			</div>

			<div class="form-group">
				<label for="inp-type-5" class="col-sm-2 control-label">내용</label>
				<div class="col-sm-10">
					<p class="form-control" th:text="*{content}"></p>
				</div>
			</div>

			<div class="form-group">
				<label for="inp-type-5" class="col-sm-2 control-label">등록일</label>
				<div class="col-sm-10">
					<p class="form-control"
						th:text="*{#temporals.format( insertTime, 'yyyy-MM-dd' )}"></p>
				</div>
			</div>

			<div class="form-group">
				<label for="inp-type-5" class="col-sm-2 control-label">조회 수</label>
				<div class="col-sm-10">
					<p class="form-control" th:text="*{viewCnt}"></p>
				</div>
			</div>
		</form>

		<div class="btn_wrap text-center">
			<a th:href="@{/board/list.do}"
				class="btn btn-default waves-effect waves-light">뒤로가기</a> <a
				th:href="@{/board/write.do( idx=${board.idx} )}"
				class="btn btn-primary waves-effect waves-light">수정하기</a>
			<button type="button" class="btn btn-danger waves-effect waves-light"
				th:onclick="deleteBoard([[ ${board.idx} ]])">삭제하기</button>
		</div>
	</div>
	<!-- /.card-content -->
</th:block>

<th:block layout:fragment="add-content">
	<div class="box-content">
		<div class="card-content">
			<div class="clearfix">
				<h4 class="box-title pull-left">Comment</h4>
				<!-- /.box-title -->
			</div>

			<form class="form-horizontal form-view">
				<div class="input-group margin-bottom-20">
					<input type="email" class="form-control" placeholder="">
					<div class="input-group-btn">
						<button type="button" class="btn waves-effect waves-light">
							<i class="fa fa-commenting" aria-hidden="true"></i>
						</button>
					</div>
					<!-- /.input-group-btn -->
				</div>
				<ul class="notice-list">
					<li><span class="name">Betty Simmons</span> <span class="desc">There
							are new settings available</span> <span class="time">2019.03.26</span>
						<button type="button" class="btn btn-xs btn-circle">
							<i class="fa fa-close" aria-hidden="true"></i>
						</button></li>
					<li><span class="name">Betty Kim</span> <span class="desc">There
							are new settings available</span> <span class="time">2019.03.15</span>
						<button type="button" class="btn btn-xs btn-circle">
							<i class="fa fa-close" aria-hidden="true"></i>
						</button></li>
				</ul>
			</form>
		</div>
		<!-- /.card-content -->
	</div>
	<!-- /.box-content -->
</th:block>
<th:block layout:fragment="script">
		<script th:inline="javascript">
			/*<![CDATA[*/

			function deleteBoard(idx) {

				if (confirm(idx + "번 게시글을 삭제할까요?")) {
					console.log(idx);
					var uri = /*[[@{/board/delete.do}]]*/"";
					var html = "";

					html += '<form name="dataForm" action="'+uri+'" method="post">';
					html += '<input type="hidden" name="idx" value="'+idx+'" />';
					html += '</form>';

					$("body").append(html);
					document.dataForm.submit();
				}
			}
			/*[- end of function -]*/

			/*]]>*/
		</script>
	</th:block>

</html>

 

 

이제 글 하나에 들어가서 삭제하기를 누르면 이렇게 확인 창이 나오고

 

 

확인을 누르면 글이 삭제되고 목록 화면으로 돌아감

 

 

 

8) 조회수 적용시키기

 

조회수를 올리려면 openBoardDetail 메서드에서 

return "board/view"; 바로 위에

boardService.ctnPlus(idx); 를 추가해준다.

	@GetMapping("/board/view.do")
	public String openBoardDetail(@RequestParam(value="idx", required=false)
	Long idx, Model model) {
		if(idx==null)
		{
			//올바르지 않은 접근
			return "redirect:/board/list.do";
		}
		
		BoardDTO board = boardService.getBoardDetail(idx);
		if(board==null || "Y".equals(board.getDeleteYn())) {
			//없는 게시글 or 이미 삭제된 게시글
			return "redirect:/board/list.do";
		}
		model.addAttribute("board",board);
		boardService.cntPlus(idx);
		return "board/view";
	}

 

 

 

 

 

 

 

 

html 태그 관련

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

 

 

타임리프 태그 관련

 

Standard URL Syntax - Thymeleaf

The Thymeleaf standard dialects –called Standard and SpringStandard– offer a way to easily create URLs in your web applications so that they include any required URL preparation artifacts. This is done by means of the so-called link expressions, a type

www.thymeleaf.org

 

[Thymeleaf] URL 표현하기 (th:href)

● Thymeleaf URL 표현하기 Thymeleaf에서 url을 표현하는 몇가지 방법에 대해 정리하겠습니다. ■ Absolute URL Absolute URL을 사용하면 특정 url로 직접 이동이 가능합니다. 가장 기본적인 a태그 사용법이라��

zamezzz.tistory.com

 

 

[Thymeleaf] 자주 사용하는 문법

화면에 값을 출력할 때 사용조건문처럼 사용 한다. 해당 조건이 만족할 때만 보여준다.해당 value의 error가 있는 경우 출력한다.form의 validation error를 출력할 때 사용할 수 있다.form 태그 사용 시, ��

velog.io

 

 

 

redirect / forward

 

[Web] Forward와 Redirect 차이

웹은 현재 작업중인 페이지에서 다른 페이지로 이동하기 위해 2가지 페이지 전환 기능을 제공합니다. 오늘은 2가지의 페이지 전환 방법의 차이와 사용법에 대해 알아보도록 하겠습니다. 1. Forward

mangkyu.tistory.com

 

 

 

참고한 블로그 및 파일 출처

 

스프링 부트(Spring Boot) 게시판 - 게시글 등록 구현하기 (Chapter 1. Create) [개발을 시작해봐요!]

본 포스팅의 화면 처리는 HTML5 기반의 자바 템플릿 엔진인 타임리프(Thymeleaf)를 사용합니다. 이전 글에서는 게시판 CRUD 기능의 테스트를 위해 데이터베이스에 게시글을 저장할 게시판 테이블을 ��

congsong.tistory.com