Part 15. 검색 처리
15.4 화면에서 검색 조건 처리
- 화면에서 검색은 다음과 같은 사항들을 주의해서 개발해야 한다.
- 페이지 번호가 파라미터로 유지되었던 것처럼 검색 조건과 키워드 역시 항상 화면 이동 시 같이 전송되어야 한다.
- 화면에서 검색 버튼을 클릭하면 새로 검색을 한다는 의미이므로 1페이지로 이동한다.
- 한글의 경우 GET 방식으로 이동하는 경우 문제가 생길 수 있으므로 주의해야 한다.
15.4.1 목록 화면에서의 검색 처리
- 목록 화면인 list.jsp에서는 검색 조건과 키워드가 들어갈 수 있게 HTML을 수정해야 한다.
- views 폴더 내의 list.jspp를 수정해 페이지 처리 바로 위쪽에 아래의 내용들을 추가한다.
< list.jsp에 검색 조건 처리 >
</c:forEach>
</table>
<div class='row'>
<div class="col-lg-12">
<form id='searchForm' action="/board/list" method='get'>
<select name='type'>
<option value="">--</option>
<option value="T"> 제목</option>
<option value="C"> 내용</option>
<option value="W"> 작성자</option>
<option value="TC"> 재목 or 내용</option>
<option value="TW"> 제목 or 작성자</option>
<option value="TWC"> 제목 or 내용 or 작성자</option>
</select>
<input type='text' name='keyword' />
<input type='hidden' name='pageNum' value='${pageMaker.cri.pageNum }'>
<input type='hidden' name='amount' value='${pageMaker.cri.amount }'>
<button class='btn btn-default'>Search</button>
</form>
</div>
</div>
- 화면에서는 다음과 같은 모습으로 보여지게 된다.
- 수정된 HTML을 보면 페이징 처리를 위해 만들어둔 < form > 태그에 < select> 와 < input > 태그가 추가된 것을 볼 수 있다.
- < form > 내 < button >의 기본 동작은 submit이므로 별도의 처리 없이 검색이 되는지 확인한다.
- 항상 테스트는 영문과 한글을 모두 테스트해야 한다.
- Chrome 브라우저는 한글로 검색하는 경우에 주소창에는 한글이 깨지지 않고 나오지만 실제로는 그림에 있는 박스의 내용물처럼 전송된다.
- IE에서는 박스의 내용과 동일하게 출력되는 것을 볼 수 있다.
- 검색이 처리된 후에는 몇 가지 문제가 있다는 사실을 알게 되는데 1) 예를들어, 3페이지를 보다가 검색을 하면 3페이지로 이동하는 문제, 2) 검색 후 페이지를 이동하면 검색 조건이 사라지는 문제, 3) 검색 후 화면에서는 어떤 검색 조건과 키워드를 이용했는지 알 수 없는 문제들이 남이있는 것을 볼 수 있다.
검색 버튼의 이벤트 처리
- 여러 문제들 중에서 검색 버튼을 클릭하면 검색은 1페이지를 하도록 수정하고, 화면에 검색 조건과 키워드가 보이게 처리하는 작업을 우선으로 진행한다.
< list.jsp의 검색 버튼의 이벤트 처리 >
var searchForm = $("#searchForm");
$("#searchForm button").on("click", function(e){
if(!searchForm.find("option:selected").val()){
alert("검색종류를 선택하세요");
return false;
}
if(!searchForm.find("input[name='keyword']").val()){
alert("키워드를 입력하세요");
return false;
}
searchForm.find("input[name='pageNum']").val("1");
e.preventDefault();
searchForm.submit();
});
- 브라우저에서 검색 버튼을 클릭하면 < form > 태그의 전송은 막고, 페이지의 번호는 1이 되도록 처리한다.
- 화면에서 키워드가 없다면 검색을 하지 않도록 제어한다.
- 검색 후에는 주소창에 검색 조건과 검색 키워드가 같이 GET 방식으로 처리되므로 이를 이용해 < select > 태그나 < input > 태그의 내용을 수정한다.
< list.jsp에서 검색 조건과 키워드 보여주는 부분 >
<form id='searchForm' action="/board/list" method='get'>
<select name='type'>
<option value=""
<c:out value="${pageMaker.cri.type == null? 'selected':''}"/>>--</option> <option value="T"
<c:out value="${pageMaker.cri.type eq 'T'?'selected':''}"/>>제목</option> <option value="C"
<c:out value="${pageMaker.cri.type eq 'C'?' 'selected':''}"/>>내용</option> <option value="W"
<c:out value="${pageMaker.cri.type eq 'W'?' 'selected':''}"/>>작성자</option> <option value="TC"
<c:out value="${pageMaker.cri.type eq 'TC'?' 'selected':''}"/>>제목 or 내용</option> <option value="TW"
<c:out value="${pageMaker.cri.type eq 'TW'?' 'selected':''}"/>>제목 or 작성자</option> <option value="TWC"
<c:out value="${pageMaker.cri.type eq 'TWC'?' 'selected':''}"/>>제목 or 내용 or 작성자</option> </select>
<input type='text' name='keyword' value='<c:out value="${pageMaker.cri.keyword }" />' />
<input type='hidden' name='pageNum' value='<c:out value="${pageMaker.cri.pageNum }" /> ' />
<input type='hidden' name='amount' value='<c:out value="${pageMaker.cri.amount }" /> ' />
<button class='btn btn-default'>Search</button>
</form>
- < select > 태그 내부는 삼항 연산자를 이용해 해당 조건으로 검색되었다면 'selected'라는 문자열을 출력하게 해서 화면에서 선택된 항목으로 보이도록 한다.
- 페이지 번호를 클릭해서 이동할 때에도 검색 조건과 키워드는 같이 전달되어야 하므로 페이지 이동에 사용한 < form > 태그를 아래와 같이 수정한다.
< list.jsp >
<form id='actionForm' action="/board/list" method='get'>
<input type='hidden' name='pageNum' value='${pageMaker.cri.pageNum}'>
<input type='hidden' name='amount' value='${pageMaker.cri.amount}'>
<input type='hidden' name='type' value='<c:out value="${ pageMaker.cri.type }" />'>
<input type='hidden' name='keyword' value='<c:out value="${ pageMaker.cri.keyword }" />'>
</form>
- 검색 조건과 키워드에 대한 처리가 되면 검색 후 페이지를 이동해서 동일한 검색 사항들이 계속 유지되는 것을 볼 수 있다.
15.4.2 조회 페이지에서 검색 처리
- 목록 페이지에서 조회 페이지로의 이동은 이미 < form > 태그를 이용해 처리했기 때문에 별도의 처리가 필요하지 않다.
- 다만 조회 페이지는 아직 Criteria의 type과 keyword에 대한 처리가 없기 때문에 이 부분을 수정해 줘야 한다.
< views/board/get.jsp >
<form id='operForm' action="/boad/modify" method="get">
<input type='hidden' id='bno' name='bno' value='<c:out value="${board.bno}"/>'>
<input type='hidden' name='pageNum' value='<c:out value="${cri.pageNum}"/>'>
<input type='hidden' name='amount' value='<c:out value="${cri.amount}"/>'>
<input type='hidden' name='keyword' value='<c:out value="${cri.keyword}"/>'>
<input type='hidden' name='type' value='<c:out value="${cri.type}"/>'>
</form>
15.4.3 수정/삭제 페이지에서 검색 처리
- 조회 페이지에서 수정/삭제 페이지로의 이동은 GET 방식을 통해 이동하고, 이동 방식 역시 < form > 태그를 이용하는 방식이므로 기존의 < form > 태그에 추가적인 type과 keyword 조건만을 추가한다.
< views/board/modify.jsp >
<form role="form" action="/board/modify" method="post">
<input type='hidden' name='pageNum' value='<c:out value="${cri.pageNum }"/>'>
<input type='hidden' name='amount' value='<c:out value="${cri.amount }"/>'>
<input type='hidden' name='type' value='<c:out value="${cri.type }"/>'>
<input type='hidden' name='keyword' value='<c:out value="${cri.keyword }"/>'>
- 수정/삭제 처리는 BoardController에서 redirect 방식으로 동작하므로 type과 keyword 조건을 같이 리다이렉트 시에 포함시켜야만 한다.
< org.zerock.controller.BoardController >
// 게시글 수정
@PostMapping("/modify")
public String modify(BoardVO board, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {
log.info("modify: " + board);
if(service.modify(board)) {
rttr.addFlashAttribute("result", "success");
}
rttr.addAttribute("pageNum", cri.getPageNum());
rttr.addAttribute("amount", cri.getAmount());
rttr.addAttribute("type", cri.getType());
rttr.addAttribute("keyword", cri.getKeyword());
return "redirect:/board/list";
}
// 게시글 삭제
@PostMapping("/remove")
public String remove(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {
log.info("remove..." + bno);
if(service.remove(bno)) {
rttr.addFlashAttribute("result", "success");
}
rttr.addAttribute("pageNum", cri.getPageNum());
rttr.addAttribute("amount", cri.getAmount());
rttr.addAttribute("type", cri.getType());
rttr.addAttribute("keyword", cri.getKeyword());
return "redirect:/board/list";
}
- 리다이렉트는 GET 방식으로 이루어지기 때문에 추가적인 파라미터를 처리해야 한다.
- modify.jsp에서는 다시 목록으로 이동하는 경우에 필요한 파라미터만 전송하기 위해서 < form > 태그의 모든 내용을 지우고 다시 추가하는 방식을 이용했으므로 keyword와 type 역시 추가하도록 아래와 같이 관련된 JavaScript 코드를 수정해야 한다.
< modify.jsp >
<script type="text/javascript">
$(document).ready(function() {
var formObj = $("form");
$('button').on("click", function(e){
e.preventDefault();
var operation = $(this).data("oper");
console.log(operation);
if(operation === 'remove'){
formObj.attr("action", "/board/remove");
}else if(operation === 'list'){
formObj.attr("action", "/board/list").attr("method", "get");
var pageNumTag = $("input[name='pageNum']").clone();
var amountTag = $("input[name='amount']").clone();
var keywordTag = $("input[name='keyword']").clone();
var typeTag = $("input[name='type']").clone();
formObj.empty();
formObj.append(pageNumTag);
formObj.append(amountTag);
formObj.append(keywordTag);
formObj.append(typeTag);
}
formObj.submit();
});
});
</script>
- 수정/조회 화면에서 어떤 작업을 하던지 다시 목록 페이지로 검색 조건이 유지되는지 확인해야 한다.
- 검색한 상태에서 특정 페이지의 게시물을 수정하면 검색 조건은 유지한 채 목록 페이지로 이동하는지를 테스트한다.
UriComponentsBuiler를 이용하는 링크 생성
- 웹페이지에서 매번 파라미터를 유지하는 일이 번거롭고 힘들다면 한 번쯤 UriComponentsBuilder라는 클래스를 이용해 볼 필요가 있다.
- org.springframework.web.util.UriComponentsBuilder는 여러 개의 파라미터들을 연결해서 URL의 형태로 만들어 주는 기능을 가지고 있다.
- URL을 만들어주면 리다이렉트를 하거나, < form > 태그를 사용하는 상황을 많이 줄여줄 수 있다.
- 검색 조건을 유지하는 org.zerock.domain.Criteria 클래스에 링크를 생성하는 기능을 추가한다.
< Criteria 클래스 >
// 링크 생성 기능 추가
public String getListLink() {
UriComponentsBuilder builder = UriComponentsBuilder.fromPath("")
.queryParam("pageNum", this.pageNum)
.queryParam("amount", this.getAmount())
.queryParam("type", this.getType())
.queryParam("keyword", this.getKeyword());
return builder.toUriString();
}
- UriComponentsBuilder는 queryParam()이라는 메서드를 이용해 필요한 파라미터들을 손쉽게 추가할 수 있다.
- 예를 들어, 아래와 같은 조건들로 Criteria가 생성된다고 가정해 보면
Criteria cri = new Criteria();
cri.setPageNum(3);
cri.setAmount(20);
cri.setKeyword("새로");
cri.setType("TC");
- 위와 같은 데이터를 가진 Criteria의 getListLink()의 결과는 '?pageNum=3&amount=20&type=TC&keyword=%EC%83%88%EB%A1%9C'와 같이 GET 방식에 적합한 URL 인코딩된 결과로 만들어진다(가장 편리한 점은 한글 처리에 신경 쓰지 않아도 된다는 점이다.).
- getLisLink()를 이용하면 BoardController의 modify()와 remove()를 다음과 같이 간단하게 정리할 수 있다.
< org.zerock.controller.BoardController >
// 게시글 수정
@PostMapping("/modify")
public String modify(BoardVO board, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {
log.info("modify: " + board);
if(service.modify(board)) {
rttr.addFlashAttribute("result", "success");
}
return "redirect:/board/list" + cri.getListLink();
}
// 게시글 삭제
@PostMapping("/remove")
public String remove(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {
log.info("remove..." + bno);
if(service.remove(bno)) {
rttr.addFlashAttribute("result", "success");
}
return "redirect:/board/list" + cri.getListLink();
}
- UriComponentsBuilder로 생성된 URL은 화면에서도 유용하게 사용될 수 있는데, 주로 JavaScript를 사용할 수 없는 상황에서 링크를 처리해야 하는 상황에서 사용된다.