수업내용/Javascript & jQuery

[2022.11.25.금] web-board 에 자바스크립트 기능 (입력값 누락방지, 검색)

주니어주니 2022. 11. 25. 18:14

 

 

 

1. 회원가입 기능

 

  • 아이디, 비밀번호, 비밀번호 확인, 이름, 이메일 -> 필수입력 자바스크립트 추가하기 

 

- form.jsp

	<form class="bg-light border p-3" method="post" action="register.jsp" onsubmit="return checkForm();">		<%-- 같은 폴더에 있는 register.jsp --%>
		<div class="mb-3">
			<label class="form-label">아이디</label>
			<input type="text" class="form-control" name="id" />
		</div>
		<div class="mb-3">
			<label class="form-label">비밀번호</label>
			<input type="password" class="form-control" name="password" />
		</div>
		<div class="mb-3">
			<label class="form-label">비밀번호 확인</label>
			<input type="password" class="form-control" name="password2" />
		</div>
		<div class="mb-3">
			<label class="form-label">이름</label>
			<input type="text" class="form-control" name="name" />
		</div>
		<div class="mb-3">
			<label class="form-label">이메일</label>
			<input type="text" class="form-control" name="email" />
		</div>
		<div class="text-end">
			<a href="../home.jsp" class="btn btn-secondary">취소</a>				<%-- 상위 폴더에 있는 home.jsp --%>
			<button type="submit" class="btn btn-primary">회원가입</button>
		</div>
	</form>
</div>
<script type="text/javascript">
	function checkForm(){		// 제출할 때 아이디,비밀번호 체크하기
		var idField = document.querySelector("[name=id]");
		var passwordField = document.querySelector("[name=password]");
		var password2Field = document.querySelector("[name=password2]");
		var nameField = document.querySelector("[name=name]");
		var emailField = document.querySelector("[name=email]");
		
		if(idField.value === ""){
			alert("아이디는 필수입력값입니다.");
			idField.focus();
			return false; 
		}
		if(passwordField.value === ""){
			alert("비밀번호는 필수입력값입니다.");
			passwordField.focus();
			return false;
		}
		if(passwordField.value != password2Field.value){
			alert("비밀번호가 서로 일치하지 않습니다.");
			passwordField.focus();
			return false;
		}
		if(nameField.value === ""){
			alert("이름은 필수입력값입니다.");
			nameField.focus();
			return false;
		}
		if(emailField.value === ""){
			alert("이메일은 필수입력값입니다.");
			emailField.focus();
			return false;
		}
		
		return true;
	}
</script>
</body>
</html>

 

 

 

 

 

2. 로그인 기능 

 

  • 입력값 누락되지 않는 자바스크립트 

 

- login.jsp

	<div id="error-message-box" class="alert alert-danger d-none">
		<strong>입력값 누락</strong> <span id="message-box"></span>
	</div>
	
	<form class="bg-light border p-3" method="post" action="login.jsp" onsubmit="return checkLoginForm();">
		<div class="mb-3">
			<label class="form-label">아이디</label>
			<input type="text" class="form-control" name="id" />
		</div>
		<div class="mb-3">
			<label class="form-label">비밀번호</label>
			<input type="password" class="form-control" name="password" />
		</div>
		<div class="text-end">
			<button type="submit" class="btn btn-primary">로그인</button>
			<a href="form.jsp" class="btn btn-secondary">회원가입</a>
		</div>
	</form>
</div>
<script type="text/javascript">
	function checkLoginForm(){
		var idField = document.querySelector("[name=id]");
		var passwordField = document.querySelector("[name=password]");
		var errorMessageBox = document.querySelector("#error-message-box");
		var messageBox = document.querySelector("#message-box");
		
		if(idField.value === ""){
			errorMessageBox.classList.remove("d-none");
			messageBox.textContent = "아이디는 필수입력값입니다.";
			idField.focus();
			return false; 
		}
		
		if(passwordField.value === ""){
			errorMessageBox.classList.remove("d-none");
			messageBox.textContent = "비밀번호는 필수입력값입니다.";
			passwordField.focus();
			return false;
		}
		
		return true;
	}
</script>
</body>

 

 

 

 

 

 

3. 검색기능 (동적 쿼리) 

 

 

검색어가 없을 때와 있을 때, 조회순 판매량 순으로 정렬할 때마다 SQL을 각각 작성해야 하면 비효율

-> 다이나믹 쿼리 (동적 쿼리) !! 

 

 

 

 

검색어 포함하는 부분이 어떨 때는 포함되고, 어떨 때는 포함되지 않도록 하고 싶어 

-> 다이나믹 쿼리 ( <dynamic></dynamic> ) 

-> 언제 포함하고싶은데?

-> 조건 삽입

<isNotNull property="keyword">

map에서 keyword값을 찾았는데 null이 아닐 때

 

 

 

- list.jsp

<%@page import="com.sample.util.Pagination"%>
<%@page import="com.sample.vo.User"%>
<%@page import="com.sample.vo.Board"%>
<%@page import="java.util.List"%>
<%@page import="com.sample.dao.BoardDao"%>
<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@page import="com.sample.util.StringUtils"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
<title>익명 게시판</title>
</head>
<body>
<jsp:include page="../common/header.jsp">
	<jsp:param name="menu" value="board"/>
</jsp:include>
<div class="container my-3">
<%
	int rows = StringUtils.stringToInt(request.getParameter("rows"), 10);		// 없으면 10개씩 가져오기 
	String sort = StringUtils.nullToValue(request.getParameter("sort"), "date");
	int currentPage = StringUtils.stringToInt(request.getParameter("page"), 1);
	String opt = StringUtils.nullToValue(request.getParameter("opt"), "title");
	String keyword = StringUtils.nullToValue(request.getParameter("keyword"), "");
	
	BoardDao boardDao = new BoardDao();

	Map<String, Object> param = new HashMap<>();
	if(!opt.isEmpty() && !keyword.isEmpty()){			// keyword가 ""일 때는 추가x(keyword=null), 값이 있을 때는 추가
		param.put("opt", opt);
		param.put("keyword", keyword); 		
	}
	
	// 총 게시글 개수 조회 (키워드에 해당하는)
	int totalRows = boardDao.getTotalRows(param);	// 빈 keyword -> 비어있는 param전달, 값있는 keyword -> 값있는 param전달
	
	// Pagination 객체 생성
	Pagination pagination = new Pagination(currentPage, totalRows, rows); 
	
	// 게시글 목록 조회
	param.put("sort", sort);
	param.put("begin", pagination.getBegin());
	param.put("end", pagination.getEnd());
	
	List<Board> boardList = boardDao.getBoards(param);	// begin,end는 무조건 포함, keyword는 포함 될수도 안될수도 있음
	
%>
	<h1 class="mb-3 fs-4 border p-2 bg-light">게시글 리스트</h1>
	<div class="d-flex justify-content-between">
		<div>
			<a href="list.jsp?rows=5" class="btn btn-sm <%=rows == 5 ? "btn-dark" : "btn-outline-dark" %>" onclick="changeRows(event, 5)">5개씩</a>
			<a href="list.jsp?rows=10" class="btn btn-sm <%=rows == 10 ? "btn-dark" : "btn-outline-dark" %>" onclick="changeRows(event, 10)">10개씩</a>
			<a href="list.jsp?rows=20" class="btn btn-sm <%=rows == 20 ? "btn-dark" : "btn-outline-dark" %>" onclick="changeRows(event, 20)">20개씩</a>
		</div> 
		
		<div>
			<a href="list.jsp?sort=date" class="btn btn-sm <%="date".equals(sort) ? "btn-dark" : "btn-outline-dark" %>" onclick="changeSort(event, 'date')">최신순</a>
			<a href="list.jsp?sort=title" class="btn btn-sm <%="title".equals(sort) ? "btn-dark" : "btn-outline-dark" %>" onclick="changeSort(event, 'title')">제목순</a>
			<a href="list.jsp?sort=read" class="btn btn-sm <%="read".equals(sort) ? "btn-dark" : "btn-outline-dark" %>" onclick="changeSort(event, 'read')">조회수 많은 순</a>
		</div>
		
		<form class="row row-cols-lg-auto g-3 align-items-center" action="list.jsp">
			<input type="hidden" name="page" value="<%=currentPage %>" />			<!-- 서버로 보내야 하는 값을 여기다가 적어 -->
			<input type="hidden" name="rows" value="<%=rows %>" />
			<input type="hidden" name="sort" value="<%=sort %>" />
			<div class="col-12">
				<select class="form-select form-select-sm" name="opt">
					<option value="title" <%="title".equals(opt) ? "selected" : "" %>> 제목</option>
					<option value="writer" <%="writer".equals(opt) ? "selected" : "" %>> 작성자</option>
					<option value="content" <%="content".equals(opt) ? "selected" : "" %>> 내용</option>
				</select>
			</div>
			<div class="col-12">
				<input type="text" class="form-control form-control-sm" name="keyword" value="<%=keyword %>">
			</div>
			<div class="col-12">
				<button type="button" class="btn btn-primary btn-sm" onclick="submitForm(1);">검색</button>	<!-- submit을 해버리면 저대로 보내버려서 page번호를 1로 못바꿈 -->
			</div>
		</form>
	</div>
	
	<table class="table">
		<thead>
			<tr>
				<th>번호</th>
				<th>제목</th>
				<th>작성자</th>
				<th>조회수</th>
				<th>리뷰갯수</th>
				<th>등록일</th>
			</tr>
		</thead>
		<tbody>
<%
	if (boardList.isEmpty()) {
%>
			<tr><td class="text-center" colspan="6"> 게시글 정보가 없습니다. </td></tr>
<%
	} else {
		for (Board board : boardList) {
%>
			<tr>
				<td><%=board.getNo() %></td>
				<td><a href="detail.jsp?no=<%=board.getNo() %>"><%=board.getTitle() %></a></td>
				<td><%=board.getWriter() %></td>
				<td><%=board.getReadCount() %></td>
				<td><%=board.getReviewCount() %></td>
				<td><%=StringUtils.dateToText(board.getCreatedDate()) %></td>
			</tr>
<%		
		}
	}
%>
		</tbody>
	</table>
	
<%
	int beginPage = pagination.getBeginPage();	// 시작 페이지번호
	int endPage = pagination.getEndPage();		// 끝 페이지번호
	boolean isFirst = pagination.isFirst();		// 첫 페이지인지 여부, 이전 버튼의 비활성화에서 사용
	boolean isLast = pagination.isLast();		// 마지막 페이지인지 여부, 다음 버튼의 비활성화에서 사용
	int prevPage = pagination.getPrevPage();	// 이전 페이지번호, 이전 버튼에서 사용
	int nextPage = pagination.getNextPage();	// 다음 페이지번호, 다음 버튼에서 사용
%>
	<div aria-label="navigation">
		<ul class="pagination justify-content-center">
			<li class="page-item">
				<a class="page-link <%=isFirst ? "disabled" : "" %>"  
					href="list.jsp?page=<%=prevPage %>"
					onclick="changePage(event, <%=prevPage %>)">이전</a>
			</li>
<%
	for (int number = beginPage; number <= endPage; number++) {
%>
			<li class="page-item">
				<a class="page-link <%=currentPage == number ? "active" : "" %>"  
					href="list.jsp?page=<%=number %>" 
					onclick="changePage(event, <%=number %>)"><%=number %></a>
			</li>
<%
	}
%>
			<li class="page-item">
				<a class="page-link <%=isLast ? "disabled" : "" %>" 
					href="list.jsp?page=<%=nextPage %>" 
					onclick="changePage(event, <%=nextPage %>)">다음</a>
			</li>
		</ul>
	</div>
	<div>
		<a href="form.jsp" class="btn btn-primary btn-sm float-end">새 글 등록</a>
	</div>
</div>

<script type="text/javascript">
	// 표시할 행 개수를 클릭했을 때 실행되는 이벤트 핸들러 함수다.
	function changeRows(event, rows) {
		event.preventDefault();									// 링크의 기본동작(클릭한 페이지로 이동)이 일어나지 않게 한다.
		var rowsField = document.querySelector("[name=rows]");	// <input type="hidden" name="rows" /> input 엘리먼트를 조회한다.
		rowsField.value = rows;									// 위에서 조회한 input 엘리먼트의 값을 변경한다. rows 값이 변경된다.
		
		submitForm(1);	// 폼 입력값을 서버로 제출하는 함수를 실행한다. 한번에 표시할 행의 갯수를 변경했기 때문에 페이지번호는 1이 되어야 한다.
	}
	
	// 정렬방식을 클릭했을 때 실행되는 이벤트 핸들러 함수다.
	function changeSort(event, sort) {
		event.preventDefault();
		var sortField = document.querySelector("[name=sort]");	// <input type="hidden" name="sort" /> input 엘리먼트를 조회한다.
		sortField.value = sort;									// 위에서 조회한 input 엘리먼트의 값을 변경한다. sort 값이 변경된다.
		
		submitForm(1);	// 폼 입력값을 서버로 제출하는 함수를 실행한다. 정렬방식을 변경했기 때문에 페이지번호는 1이 되어야 한다.
	}
	
	// 페이지번호를 클릭했을 때 실행되는 이벤트 핸들러 함수다.
	function changePage(event, page) {
		event.preventDefault();	// 링크의 기본동작이 일어나지 않게 한다.
		
		submitForm(page);		// 폼 입력값을 서버로 제출하는 함수를 실행한다. 
	}
	
	// 검색버튼을 클릭했을 때 실행되는 이벤트 핸들러 함수다.
	function submitForm(page) {
		var pageField = document.querySelector("[name=page]");	// <input type="hidden" name="page" /> input 엘리먼트를 조회한다.
		pageField.value = page;									// 위에서 조회한 input 엘리먼트의 값을 변경한다. page 번호가 변경된다.
		
		var form = document.querySelector("form");				// <form /> 엘리먼트를 조회한다.
		form.submit();	// 폼 입력값을 서버로 제출한다. rows, page, keyboard가 한번에 서버로 제출된다.
	}
</script>
</body>
</html>

 

 

- board.xml

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" 
	"http://ibatis.apache.org/dtd/sql-map-2.dtd">
 
<sqlMap namespace="boards">

	<insert id="insertBoard" parameterClass="com.sample.vo.Board">
		insert into sample_boards
			(board_no, board_title, board_writer, board_content, board_file_name)
		values
			(sample_boards_seq.nextval, #title#, #writer#, #content#, #fileName#)
	</insert>
	
	<select id="getBoards" parameterClass="map" resultClass="com.sample.vo.Board">
		select
			board_no as no,
			board_title as title,
			board_writer as writer,
			board_read_count as readCount,
			board_review_count as reviewCount,
			board_content as content,
			board_deleted as deleted,
			board_created_date as createdDate,
			board_updated_date as updatedDate
		from (select 
				<dynamic>
					<isEqual property="sort" compareValue="date">
						row_number() over (order by board_no desc) row_numbers,
					</isEqual>
					<isEqual property="sort" compareValue="title">
						row_number() over (order by board_title asc) row_numbers,
					</isEqual>
					<isEqual property="sort" compareValue="read">
						row_number() over (order by board_read_count desc) row_numbers,					
					</isEqual>
				</dynamic>
					board_no,
					board_title,
					board_writer,
					board_read_count,
					board_review_count,
					board_content,
					board_deleted,
					board_created_date,
					board_updated_date
			  from
			  		sample_boards
			  where
			  		board_deleted = 'N'
			  	<dynamic>	
					<isNotNull property="opt">
						<isEqual property="opt" compareValue="title">
							and board_title like '%' || #keyword# || '%'
						</isEqual>
						<isEqual property="opt" compareValue="writer">
							and board_writer like '%' || #keyword# || '%'
						</isEqual>
						<isEqual property="opt" compareValue="content">
							and board_content like '%' || #keyword# || '%'
						</isEqual>
					</isNotNull>
				</dynamic>		
			  )
		where
			row_numbers between #begin# and #end#
	</select>
	
	<select id="getBoardByNo" parameterClass="int" resultClass="com.sample.vo.Board">
		select
			board_no			as no, 
			board_title			as title,
			board_writer		as writer,
			board_read_count	as readCount,
			board_review_count	as reviewCount,
			board_content		as content,
			board_deleted		as deleted,
			board_created_date	as createdDate,
			board_updated_date	as updatedDate,
			board_file_name		as fileName
		from 
			sample_boards
		where
			board_no = #value#
	</select>
	
	<select id="getTotalRows" parameterClass="map" resultClass="int">
		select
			count(*)
		from
			sample_boards
		where
			board_deleted = 'N'
		<dynamic>	
			<isNotNull property="opt">
				<isEqual property="opt" compareValue="title">
					and board_title like '%' || #keyword# || '%'
				</isEqual>
				<isEqual property="opt" compareValue="writer">
					and board_writer like '%' || #keyword# || '%'
				</isEqual>
				<isEqual property="opt" compareValue="content">
					and board_content like '%' || #keyword# || '%'
				</isEqual>
			</isNotNull>
		</dynamic>	
	</select>
	
	<update id="updateBoard" parameterClass="com.sample.vo.Board">
		update
			sample_boards
		set
			board_title = #title#, 
			board_writer = #writer#,
			board_read_count = #readCount#,
			board_review_count = #reviewCount#, 
			board_content = #content#, 
			board_deleted = #deleted#, 
			board_file_name = #fileName#,
			board_updated_date = sysdate
		where
			board_no = #no#
	</update>
</sqlMap>

 

 

- BoardDao

package com.sample.dao;

import java.util.List;
import java.util.Map;

import com.sample.util.SqlMapper;
import com.sample.vo.Board;

public class BoardDao {

	public void insertBoard(Board board) {
		SqlMapper.insert("boards.insertBoard", board);
	}
	
	@SuppressWarnings("unchecked")
	public List<Board> getBoards(Map<String, Object> param) {
		return (List<Board>)SqlMapper.selectList("boards.getBoards", param);
	}
	
	public int getTotalRows(Map<String, Object> param) {
		return (Integer) SqlMapper.selectOne("boards.getTotalRows", param);
	}
	
	public Board getBoardByNo(int boardNo) {
		return (Board) SqlMapper.selectOne("boards.getBoardByNo", boardNo);
	}
	
	public void updateBoard(Board board) {
		SqlMapper.update("boards.updateBoard", board);
	}
}