settings/Profile.java
private String profileImage;
public Profile(Account account) {
this.bio = account.getBio();
this.url = account.getUrl();
this.occupation = account.getOccupation();
this.location = account.getLocation();
this.profileImage = account.getProfileImage();
}
추가해주기
<div class="form-group">
<input id="profileImage" type="hidden" th:field="*{profileImage}" class="form-control" />
</div>
type="hidden" 주목, Cropper.JS 사용해서 이미지 영역을 잘라서 지정할 수 있다. (마우스 휠을 이용해서) 잘라낸 만큼만 프로필 이미지로 사용할 수 있도록 할 것이다.
<div class="card-header">
프로필 이미지
</div>
<div id="current-profile-image" class="mt-3">
<svg th:if="${#strings.isEmpty(profile.profileImage)}" class="rounded"
th:data-jdenticon-value="${account.nickname}" width="125" height="125"></svg>
<img th:if="${!#strings.isEmpty(profile.profileImage)}" class="rounded"
th:src="${profile.profileImage}"
width="125" height="125" alt="name" th:alt="${account.nickname}"/>
</div>
현재 이미지를 보여주는 곳, profile.profileImage 가 비어있는 값이면 account.nickname 의 data-jdenticon-value로 자동 생서된 이미지를 보여준다. profile.profileImage 가 있으면 프로필 이미지를 그대로 src 소스로 사용해서 보여준다. 우리는 Image 를 파일로 저장하지 않을 것이다. 문자열 그대로를 저장할 것이다.
db에 저장이 되어있다. 아바타는 유저 정보를 불러올 때마다 불러와야 하고, 그때마다 파일을 불러오는 게 번거롭다. html에서 DataURL 를 제공한다.
/settings/profile.html
<div class="card text-center">
<div class="card-header">
프로필 이미지
</div>
<div id="current-profile-image" class="mt-3">
<svg th:if="${#strings.isEmpty(profile.profileImage)}" class="rounded"
th:data-jdenticon-value="${account.nickname}" width="125" height="125"></svg>
<img th:if="${!#strings.isEmpty(profile.profileImage)}" class="rounded"
th:src="${profile.profileImage}"
width="125" height="125" alt="name" th:alt="${account.nickname}"/>
</div>
<div id="new-profile-image" class="mt-3"></div>
<div class="card-body">
<div class="custom-file">
<input type="file" class="custom-file-input" id="profile-image-file">
<label class="custom-file-label" for="profile-image-file">프로필 이미지 변경</label>
</div>
<div id="new-profile-image-control" class="mt-3">
<button class="btn btn-outline-primary btn-block" id="cut-button">자르기</button>
<button class="btn btn-outline-success btn-block" id="confirm-button">확인</button>
<button class="btn btn-outline-warning btn-block" id="reset-button">취소</button>
</div>
<div id="cropped-new-profile-image" class="mt-3"></div>
</div>
</div>
처음 나오는 곳에서는 자르기, 확인, 취소 버튼을 숨겨줘야 한다.
/settings/profile.html
<script type="application/javascript">
$(function() {
cropper = '';
// html에 있는 각각의 요소들
let $confirmBtn = $("#confirm-button"); //확인 버튼
let $resetBtn = $("#reset-button"); //취소 버튼
let $cutBtn = $("#cut-button"); // 자르기 버튼
let $newProfileImage = $("#new-profile-image"); // 새로 선택한 이미지
let $currentProfileImage = $("#current-profile-image"); // 현재 이미지
let $resultImage = $("#cropped-new-profile-image"); // 선택한 이미지 중 잘라낸 영역만 나타낸 이미지
let $profileImage = $("#profileImage"); // 최종적으로 form 에다가 넣어줘야 하는 값
// 먼저 필요 없는 영역과 버튼을 숨겨줌
$newProfileImage.hide();
$cutBtn.hide();
$resetBtn.hide();
$confirmBtn.hide();
// 프로필 이미지 변경 Browse를 선택하면 이 창의 값이 바뀌면
$("#profile-image-file").change(function(e) {
if (e.target.files.length === 1) { // 파일을 하나 선택했으면
const reader = new FileReader(); //1. File Reader 를 먼저 만듦
reader.onload = e => { // 파일이 읽어와 졌으면
if (e.target.result) { // event에서 target를 가져와서
let img = document.createElement("img"); // 이미지 태그를 만들어서 가져온 이미지를 채워넣음
img.id = 'new-profile';
img.src = e.target.result;
img.width = 250;
// $newProfileImage 영역에다가 새로운 태그(img)를 추가한다.
$newProfileImage.html(img);
$newProfileImage.show(); // 새로운 이미지 보여주고
$currentProfileImage.hide(); // 현재 이미지는 숨김
let $newImage = $(img); // jquery 로 img 감싸고 cropper 를 적용
$newImage.cropper({aspectRatio: 1});
cropper = $newImage.data('cropper');
$cutBtn.show();
$confirmBtn.hide(); // 확인 버튼 숨기기 (잘라낸 다음에 사용해야 하므로)
$resetBtn.show();
}
};
reader.readAsDataURL(e.target.files[0]); // 2. 그리고 그 파일을 읽어옴
}
});
// reset 버튼이 사용될 경우
$resetBtn.click(function() {
$currentProfileImage.show();
$newProfileImage.hide();
$resultImage.hide();
$resetBtn.hide();
$cutBtn.hide();
$confirmBtn.hide();
// reset 버튼 누르는 순간 최종적으로 셋팅해야 하는 인풋에 해당하는 값을 비어있는 문자열로 셋팅한다. 저장하더라도 아무 일도 발생하지 않도록!
$profileImage.val('');
});
// 자르기 버튼
$cutBtn.click(function () {
let dataUrl = cropper.getCroppedCanvas().toDataURL();
let newImage = document.createElement("img");
newImage.id = "cropped-new-profile-image"; // 실제로 잘라낸 이미지 만큼큼 newImage.src = dataUrl;
newImage.width = 125;
$resultImage.html(newImage);
$resultImage.show();
$confirmBtn.show(); // 확인 버튼 보여줌
// 잘라낸 영역만을 $profileImage 셋팅함
$confirmBtn.click(function () {
$newProfileImage.html(newImage);
$cutBtn.hide();
$confirmBtn.hide();
$profileImage.val(dataUrl);
});
});
});
</script>
개발자 도구를 켜서 실제 src 값을 확인하면 굉장히 값이 들어가 있음을 볼 수 있다. 이 값 자체가 이미지라고 생각하면 된다. 그대로 db에 저장된다.
updateProfile 메소드에 이미지도 넣어준다.
account.setProfileImage(profile.getProfileImage());
fragments.html
<svg data-jdenticon-value="user127"
th:data-jdenticon-value="${#authentication.name}"
width="24" height="24" class="rounded border bg-light">
</svg>
수정하기 버튼을 눌러서 저장을 하더라도 네비게이션에 반영이 안된다.
<svg th:if="${#strings.isEmpty(account?.profileImage)}" th:data-jdenticon-value="${#authentication.name}"
width="24" height="24" class="rounded border bg-light">
</svg>
<img th:if="${!#strings.isEmpty(account?.profileImage)}" th:src="${account.profileImage}"
width="24" height="24" class="rounded border"/>
?. 은 user가 null이 아닌 경우에 네비게이션 하는 것이다.
account?.profileImage 프로필 이미지가 비어있지 않으면, 있으면 account에 있는 프로필 이미지를 그대로 보여지도록 한다.
출처 : 인프런 백기선님의 스프링과 JPA 기반 웹 애플리케이션 개발