재사용을 위해 loading.html 만들기
<!-- loading.html -->
<div class="loading-container">
<div class="spinner"></div>
<div id="loading-text">Loading...</div>
<div>
<img src="/images/Hourglass.gif" alt="Loading img">
</div>
</div>
<style>
.loading-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.spinner {
border: 8px solid #f3f3f3; /* Light grey */
border-top: 8px solid #3498db; /* Blue */
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#loading-text {
color: white;
margin-top: 20px;
font-size: 16px;
}
</style>
로딩 페이지를 사용할 페이지 html에 적용
body에 div 추가
<div id="loadingContainer"></div>
script 추가
ajax를 통해 로딩이 끝나는 때를 알 수 있음.
<script>
$(document).ready(function () {
// 로딩 HTML을 동적으로 불러와 삽입하는 함수
function includeLoadingHTML() {
$('#loadingContainer').load('/fragments/loading.html', function (responseText, textStatus) {
if (textStatus === "success") {
$('.loading-container').hide(); // 처음에는 숨겨둠
} else {
console.error("Failed to load loading.html:", textStatus);
}
});
}
// 로딩 중 상태를 설정하는 함수
function showLoading() {
$('.loading-container').show();
}
// 로딩이 끝난 상태를 설정하는 함수
function hideLoading() {
$('.loading-container').hide();
$('.content').show();
}
// 에러 메시지를 표시하는 함수
function displayErrors(errors) {
console.log('displayErrors function called with errors:', errors);
if (!errors || errors.length === 0) {
console.log('No errors to display');
return;
}
// 모든 기존의 에러 메시지를 초기화
$('.field-error').each(function() {
$(this).html('');
});
// 각 에러를 필드별로 표시
errors.forEach(function (error) {
var field = error.field;
var message = error.defaultMessage;
if (!field) {
console.log('Field is undefined or null');
return;
}
console.log("field:", field);
console.log("message:", message);
var inputOrSelect = $('input[name="' + field + '"], select[name="' + field + '"]');
var closestMb3 = inputOrSelect.closest('.mb-3');
var fieldError = closestMb3.find('.field-error');
fieldError.html(message).css({ 'display': 'block', 'visibility': 'visible' });
});
}
includeLoadingHTML(); // 로딩 HTML 초기화
// Form submit을 AJAX로 처리
$('form').submit(function (event) {
event.preventDefault(); // 폼의 기본 제출 동작을 중지
showLoading(); // 폼이 제출되면 로딩 화면 표시
// AJAX 요청 설정
$.ajax({
url: $(this).attr('action'),
method: $(this).attr('method'),
data: $(this).serialize(),
dataType: 'json',
success: function (response) {
console.log('Form submitted successfully:', response);
if (response.redirectUrl) {
window.location.href = response.redirectUrl; // 서버 리다이렉트 URL로 이동
} else {
hideLoading(); // 리다이렉트 URL이 없으면 로딩 화면 숨기기
}
},
error: function (jqXHR) {
console.error('Error submitting form:', jqXHR);
var errors = [];
if (jqXHR.responseJSON) {
console.log('jqXHR responseJSON:', jqXHR.responseJSON);
for (var key in jqXHR.responseJSON) {
if (jqXHR.responseJSON.hasOwnProperty(key)) {
errors.push({
field: key,
defaultMessage: jqXHR.responseJSON[key]
});
}
}
} else if (jqXHR.responseText) {
try {
var parsedResponse = JSON.parse(jqXHR.responseText);
for (var key in parsedResponse) {
if (parsedResponse.hasOwnProperty(key)) {
errors.push({
field: key,
defaultMessage: parsedResponse[key]
});
}
}
} catch (e) {
console.error('Error parsing responseText:', e);
}
} else {
$('#errorContainer').html('Unknown error occurred.').show();
}
if (errors.length > 0) {
console.log('Errors extracted:', errors);
displayErrors(errors);
} else {
console.log('No errors to display');
}
hideLoading(); // AJAX 요청이 실패한 경우에도 로딩 화면 숨기기
}
});
});
});
</script>
추가해둔 #loadingContainer에 loading.html 넣는 함수
function includeLoadingHTML() {
$('#loadingContainer').load('/fragments/loading.html', function (responseText, textStatus) {
if (textStatus === "success") {
$('.loading-container').hide(); // 처음에는 숨겨둠
} else {
console.error("Failed to load loading.html:", textStatus);
}
});
}
로딩 페이지를 보여주는 함수 (제출 버튼을 누르기 전에는 감춰둠)
function showLoading() {
$('.loading-container').show();
}
로딩이 끝난 후에 로딩 페이지를 숨기고 원래 컨텐츠를 보여주는 함수
function hideLoading() {
$('.loading-container').hide();
$('.content').show();
}
원래 띄워져 있던 .field-error의 텍스트를 비우고, 발생한 오류를 띄움.
.field-error를 찾아가는 것은 상황에 맞게 수정해야함.
function displayErrors(errors) {
console.log('displayErrors function called with errors:', errors);
if (!errors || errors.length === 0) {
console.log('No errors to display');
return;
}
// 모든 기존의 에러 메시지를 초기화
$('.field-error').each(function() {
$(this).html('');
});
// 각 에러를 필드별로 표시
errors.forEach(function (error) {
var field = error.field;
var message = error.defaultMessage;
if (!field) {
console.log('Field is undefined or null');
return;
}
console.log("field:", field);
console.log("message:", message);
//여기부터 아래 부분은 상황에 맞게 수정해야함.
var inputOrSelect = $('input[name="' + field + '"], select[name="' + field + '"]');
var closestMb3 = inputOrSelect.closest('.mb-3');
var fieldError = closestMb3.find('.field-error');
fieldError.html(message).css({ 'display': 'block', 'visibility': 'visible' });
});
}
Controller 수정
로딩 페이지를 적용하기 전에는 String 타입으로 리턴하였는데, ajax를 사용하기 때문에 ResponseEntity 타입으로 리턴해야한다.
그래서 Controller 수정이 필요하다.
Map<String, String> response = new HashMap<>();
if (bindingResult.hasErrors()) {
for (FieldError fieldError : bindingResult.getFieldErrors()) {
log.info(bindingResult.getFieldError().getDefaultMessage());
response.put(fieldError.getField(), fieldError.getDefaultMessage());
}
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
response 해시맵을 만들어두고, 에러가 발생한 것을 response에 field명:defaultMessage로 넣는다.
그러면 작성한 javascript가 response에서 찾아와서 field명에 맞게 field-error에 넣어준다.
th:errors 지우기
마지막으로 Controller가 String타입으로 리턴할 때, field-error는
<div class="field-error" th:errors="*{flightNum}"></div>
이렇게 thymeleaf를 사용했다.
그런데 javascript를 통해 수동으로 넣어야 하는 경우에는 th:errors를 하면 에러가 없을 경우에는 <div class="field-error"></div>가 없어서 .field-error를 찾지 못한다.
그래서 그냥
<div class="field-error"></div>
이렇게 th:errors 부분을 지워주면 .field-error를 잘 찾아서 넣어준다.
'Spring' 카테고리의 다른 글
[원티드 백엔드 챌린지 11월] 객체지향스러운 아키텍쳐 설계 (0) | 2024.11.22 |
---|---|
[원티드 백엔드 챌린지 11월] Spring의 기능 자세히 살펴보기 (0) | 2024.11.22 |
Querydsl에서 동적 쿼리와 함께 페이징하기 (2) | 2024.08.08 |
[JPA 활용 2편] API 개발 고급 - 실무 필수 최적화 (0) | 2024.07.08 |
[JPA 활용 2편] API 개발 고급 - 컬렉션 조회 최적화 (1) | 2024.07.05 |