국비교육(22-23)

34일차(2)/jsp(20) : 회원 프로필 이미지 등록, 수정 기능 구현

서리/Seori 2022. 11. 25. 00:38

34일차(2)/jsp(20) : 회원 프로필 이미지 등록, 수정 기능 구현

 

 

- 어떤 이미지를 업로드한다면, 파일이 실제 업로드되는 폴더는 /upload 가 아니다

 

- /webapp/upload/kim1.png 라는 이미지파일이 업로드된다면
  DB에는 /upload/kim1.png 을 저장하고
  해당 이미지를 출력할때는 DB에 저장된 /upload/kim1.png를 불러와서
  <img src="${pageContext.request.contextPath}/upload/kim1.png"> 형식으로 이미지를 출력하는 것이다.

 

- 파일을 업로드하면 저장된 파일 경로를 알 수 있다.
 그 경로를 통해 DB에 이미지를 저장하 고, 그 경로에서 불러와서 src에 출력하는 것이다.

 

- upload는 webapp아래에 있어서 클라이언트가 요청을 통해서 접근 가능한 폴더이다.

 

- DB칼럼에 경로 값을 저장해두고, ajax로 이미지를 업로드 한다.

 


 

<updateform.jsp> 수정

<%@page import="test.users.dao.UsersDao"%>
<%@page import="test.users.dto.UsersDto"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	//session scope에 저장된 아이디를 이용해서
	String id=(String)session.getAttribute("id");
	//수정할 회원의 정보를 얻어온다.
	UsersDto dto=UsersDao.getInstance().getData(id);
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/users/private/updateform.jsp</title>
<style>
	/* 이미지 업로드 폼을 숨긴다 */
	#imageForm{
	   display: none;
	}
	#profileImage{
	   width: 100px;
	   height: 100px;
	   border: 1px solid #cecece;
	   border-radius: 50%;
	}
</style>

</head>
<body>
	<div class="container">
		<h3>회원가입 수정 폼입니다.</h3>
		
		<a id="profileLink" href="javascript:">
			<%if(dto.getProfile() == null){%>
				<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-circle" viewBox="0 0 16 16">
					<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
					<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
				</svg>
			<%}else{ %>
				<img id="profileImage" src="${pageContext.request.contextPath }<%=dto.getProfile()%>">
			<%} %>
		</a>
		<form action="update.jsp" method="post">
			<input type="hidden" name="profile" 
				value="<%=dto.getProfile()==null ? "empty" : dto.getProfile()%>"/>
			<div>
				<label for="id">아이디</label>
				<input type="text" id="id" value="<%=dto.getId() %>" disabled />
			</div>
			<div>
				<label for="email">이메일</label>
				<input type="text" id="email" name="email" value="<%=dto.getEmail() %>" />
			</div>
			<button type="submit">수정확인</button>
			<button type="reset">취소</button>		
		</form>
		<form id="imageForm" action="profile_upload.jsp" method="post" enctype="multipart/form-data">
			프로필 사진
			<input type="file" id="image" name="image" accept=".jpg, .png, .gif"/>
			<button type="submit">업로드</button>
		</form>
	</div>
	<!-- sy_util.js 로딩 -->
	<script src="${pageContext.request.contextPath }/js/sy_util.js"></script>
	<script>
		//프로필 이미지 링크를 클릭하면 
		document.querySelector("#profileLink").addEventListener("click", function(){
        // input type="file" 을 강제 클릭 시킨다. 
        document.querySelector("#image").click();
      	});   
	
     	//프로필 이미지를 선택하면(바뀌면) 실행할 함수 등록
      	document.querySelector("#image").addEventListener("change", function(){
        	//ajax 전송할 폼의 참조값 얻어오기
        	const form=document.querySelector("#imageForm");
        	//sy_util.js 에 있는 함수를 이용해서 ajax 전송하기 
         	ajaxFormPromise(form)
         	.then(function(response){
	            return response.json();
         	})
         	.then(function(data){
            	console.log(data);
            	// input name="profile" 요소의 value 값으로 이미지 경로 넣어주기
            	document.querySelector("input[name=profile]").value=data.imagePath;
            	
            	//img 요소를 문자열로 작성한 다음
            	let img=`<img id="profileImage" 
               		src="${pageContext.request.contextPath }\${data.imagePath}">`;
               	//id가 profileLink인 요소의 내부(자식요소)에 덮어쓰기 하면서 html 형식으로 해석해주세요 라는 의미  
            	document.querySelector("#profileLink").innerHTML=img;
         	});
        });		
   </script>	
</body>
</html>

 

- 파일을 업로드할 수 있는 폼을 새로 하나 만들고, 제출을 막는다.

 

document.querySelector("#image").click();

- console창에서 테스트해보면 이 버튼을 javascript에서 강제로 클릭시킬 수 있다.

- 입력하면 id가 image인 버튼이 강제로 클릭된다.

 

 

- 아이디가 image인 요소(input)의 참조값을 읽어와서 제어한다.

 

- 기본 아이콘을 하나 넣어주고, 

 이 아이콘을 클릭하면 아래 [파일 선택] 버튼이 강제로 클릭되도록 수정할 예정이다.

 

<a id="profileLink" href="javascript:">

<script>
	document.querySelector("#profileLink").addEventListener("click", function(){
		document.querySelector("#image").click();
	});   
</script>

→ javascript 를 추가해서 아이콘에 위 코드를 연결해준다.

 

- style에서 input 요소를 display: none으로 한다. 보이지않도록!

- 그러면 폼이 없어지고, 아이콘을 누르면 파일선택이 열리는 것으로만 보인다.

 

- 이제 폴더에서 사진을 선택하는 순간, ajax로 파일을 업로드하도록 할 것이다.

- 바로 제출되어 업로드를 하고, 응답을 json으로 주는 것이다.

  그러면 클라이언트가 바로 볼 수 있도록 해줄 것이다.(응답)

 

- 이미지 링크를 만들어서 올린다.

 

 

- profile_upload.jsp 페이지에서 파일 업로드 처리를 한다.

 

 

- 경로를 구성해서 JSON으로 응답한다.

- 이 " " 안쪽은 동적으로 작동한다.

 

- 위에 있는 <% %>설정값을 contentType="application/json; 으로 바꿔주기

 

- 경로가 저장되고, 프로필 이미지가 바뀐다

(아직 DB에 저장한 것은 아니다. upload폴더에 저장하고 브라우저에 응답만 한 것이다.

 

- 이 안에 들어있는 imagePath를 이용해서 이미지를 부여하고 innerHTML로 집어넣은 것이다.

 동적으로 만든 마크업!

 

- svg(기본아이콘)가 들어있던 자리에 다른 내용을 넣는 방법은

 <a>의 .innerHTML="<img xx>"; 으로 이 위치를 대체한다. innerhtml 에 들어갈 내용을 바꾸어 준다.

 

- javascript 를 사용해서 방금 업로드한 이미지로 문자열을 만들어서 집어넣으면서 html로 해석해주세요~ 한 것이다.

 

- 이 수정된 이미지 정보는 폼이 제출될때 같이 제출되어야 한다. 그래야 수정이 반영되니까!

- 즉 input type="hidden"으로 같이 보내면 된다.

 

- hidden으로 들어간 input type. 지금은 value가 empty이다.

 

- 이미지를 넣어주면 value 값이 수정된 것을 볼 수 있다. 이미지를 수정하면 해당 이미지의 경로가 나오게된다. 

 

- update.jsp에서는 getProfile로 얻어낼 수 있다.

 

- 다음에 수정폼에 들어오면 위 이미지가 출력된 상태로나온다.

 

 

<update.jsp> 수정

<%@page import="test.users.dao.UsersDao"%>
<%@page import="test.users.dto.UsersDto"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	//1.수정할 회원의 정보를 읽어와서
	String id=(String)session.getAttribute("id");
	String email=request.getParameter("email");
	//프로필 이미지의 경로 읽어오기(등록하지 않았으면 "empty"이다)
	String profile=request.getParameter("profile");
	
	UsersDto dto=new UsersDto();
	dto.setId(id);
	dto.setEmail(email);
	
	//만일 profile 이미지를 등록했다면(profile 이미지가 empty가 아니라면)
	if(!profile.equals("empty")){
		//dto에 전송된 프로필 이미지 경로를 담아준다.
		dto.setProfile(profile);
	}
	//2. DB에 수정반영하고
	boolean isSuccess=UsersDao.getInstance().update(dto);
	//3.응답하기
%>    
  
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/users/private/update.jsp</title>
</head>
<body>
	<script>
		<% if(isSuccess){%>
			alert("수정했습니다.");
			location.href="info.jsp";
		<%}else{%>
			alert("수정 실패!");
			location.href="updateform.jsp";		
		<%}%>
	</script>
</body>
</html>

 

String profile=request.getParameter("profile");

if(!profile.equals("empty")){
	dto.setProfile(profile);
}

- 이 내용을 추가해줌! 들어온 프로필 파라미터 값을 읽어오기.

 

 

<UsersDao>의 update 메소드도 수정

//개인정보(이메일)를 수정하는 메소드
public boolean update(UsersDto dto) {
    Connection conn = null;
    PreparedStatement pstmt = null;
    int rowCount = 0;
    try {
        conn = new DbcpBean().getConn();
        String sql = "UPDATE users"
                + " SET email=?, profile=?"
                + " WHERE id=?";
        pstmt = conn.prepareStatement(sql);
        //?에 바인딩
        pstmt.setString(1, dto.getEmail());
        pstmt.setString(2, dto.getProfile());
        pstmt.setString(3, dto.getId());
        //INSERT or UPDATE or DELETE문을 수행하고 수정,삭제,추가된 row의 개수 리턴
        rowCount = pstmt.executeUpdate();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (pstmt != null)
                pstmt.close();
            if (conn != null)
                conn.close();
        } catch (Exception e2) {
        }
    }

    if (rowCount > 0) {
        return true;
    } else {
        return false;
    }
}

- sql문에 profile을 추가해주고, ?의 순서가 바뀌었으므로 다시 바인딩해준다.

 

- 보이는 프로필 이미지가 수정되고, DB의 Profile 칼럼에도 값이 들어가 있는 것을 확인할 수 있다.

 


 

Q) `` 백틱을 사용하는 이유는?

A) 문자열을 이어 붙이기 위해 사용하는 것이다.

 

webapp/<test.jsp> 생성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/test.jsp</title>
</head>
<body>
	<div id="one"></div>
	<script>
		let data={imagePath:"/images/b1.png"};
		
		let img1='<img src="/Step04_Final/images/b1.png">';
		
		let img2='<img src="/Step04_Final'+data.imagePath+'">';
		
		let img3=`<img src="/Step04_Final\${data.imagePath}">`;
	</script>
</body>
</html>

- img3은 백틱을 사용한 것.

 

- 브라우저의 콘솔 창에서 자바스크립트 마크업을 해볼 것

 

- 똑같은 문자열을 만든다고 할 때, img1, img2, img3는 모두 같다.

 

- ` `에서 ${} 는 중요한 의미이다. 저 안쪽은 javascript영역으로, ``을 사용해서 이어 붙일 수 있다.

 

 

- 그런데 위 img1, 2, 3을 파일 안에 직접 작성하고 페이지 소스보기를 하면 다르게 나온다.

 

- jsp페이지에서 ${ }는 매우 특별한 기호이다.

 (이 안에서 사용할수있는 특별한 언어 EL이 있다.)

- 그런데 클라이언트에게 출력되기 전에 이미 jsp페이지가 저걸 해석해버린다.

- 그래서 클라이언트에게 출력되는 단계에서는 읽어올 수 있는 것이 없어서 아무것도 없다고 나온 것이다.

 

- 따라서 이 $를 특별하게 해석하지 말고, 그냥 문자열로 봐달라는 의미에서 \를 붙인다. 그러면 정상적으로 출력됨!

 

- 그러면 페이지 소스 보기에 있는 이 소스를 웹브라우저가 해석할 수 있다.

 

 

- object의 imagepath에 있는 경로를 이어붙이기위함이다.

- jsp 페이지가 이어붙이는 것이 아니라, 웹브라우저가 이어붙인다.

 

 

- 그 앞부분은 jsp페이지가 해석한다.

- updateform의 이 경로(cpath) 부분은 jsp가 해석했고, 

 그 뒷부분인 ${} 안쪽은 앞으로 브라우저가 해석할 예정이다.

 

 

- 각각 innerText에 img1을 넣었을 때와 innerHTML에 img1을 넣었을 때! 출력되는 내용이 달라진다.

→ 문자열 취급하느냐, 아니면 마크업으로 해석해주느냐가 달라진다.

 


 

 

document.querySelector("#image").addEventListener("change", function(){  })
→ id가 image인 요소에 change 이벤트가 발생하면 어떤 함수를 실행한다.  
const form=document.querySelector("#imageForm");

→ ajaxFormPromise() 함수에 참조값을 넣어주기 위해 참조값을 얻어온다.

 

- 폼에 참조값만 넣어주면 알아서 제출해준다.

- 초록박스를 선택했을때 (사진이 업로드되고, ajaxFormPromise() 함수가 data를 받아서 promise를 리턴해준다.

 

- 만약 sy_util.js( ajaxFormPromise() 함수)를 사용하지 않을 경우, 이렇게 작성해볼 수 있다.

- 폼에참조값만 넣어주면된다.

 

ajaxFormPromise(form)
.then(function(response){
	return response.json();
})

 

→ 페이지 전환없이 폼을 제출하기 위해(파일을 업로드 하기 위해) 사용하는 것!

 


 

 

- 파일 전송, 업로드 후 그 파일을 읽어올 때 getFilesystemName 값은 input name의 value와 동일해야 한다.

그래야 정상적으로 읽어올 수 있다.