Spring

로딩 페이지 (ajax 이용)과 Validation 적용하기

gogi masidda 2024. 8. 29. 18:31

재사용을 위해 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를 잘 찾아서 넣어준다. 

728x90