jQuery 하면 빼놓을 수 없는 것이 Ajax입니다. jQuery의 함수와 이벤트를 사용할 때 발생하는 이점이 Ajax에서는 더욱 증가됩니다. 순수 JavaScript의 Ajax는 너무 복잡하기 때문이죠. jQuery의 Ajax 함수를 사용하면 정말 간단하게 Ajax 통신을 할 수 있고 넥스트리 홈블로그의 이전 글에서 소개된 바가 있습니다. Ajax 통신을 너무 많이 하게되면 코드가 늘어나며 복잡해질 수 있다는 문제점은 있지만 사용자의 편리성과 서버의 분산 처리 측면에서 장점 또한 많습니다. 혹은 서버에서의 데이터 유효성 체크는 Ajax가 필수라고 할 수 있습니다. 본 글에서는 이러한 jQuery Ajax를 사용할 때 어떠한 이벤트가 발생하는지, 특히 전역 이벤트의 특징과 사용법을 알아봅니다.

1. Ajax Event

jQuery Ajax 이벤트는 Local(지역) 이벤트와 Global(전역) 이벤트로 나눕니다. 지역 이벤트는 하나의 Ajax 요청에 대해서만 발생하며 $.ajax()나 $.ajaxSetup()의 속성인 beforeSend, success, error, complete에 정의할 수 있습니다. 지역 이벤트는 JavaScript나 jQuery의 이벤트처럼 해당 이벤트가 발생하면 핸들러(리스너)가 동작하는 것이 아닙니다. 단순히 Ajax 수행에서 특정 시점에 함수가 호출 되는 callback 형태입니다.

반면 전역 이벤트는 jQuery에서 정의한 커스텀 이벤트입니다.  개별 혹은 전체의 Ajax 전송에 대한 특정 시점에 실제 이벤트가 발생하며 bind()나 on() 함수로 이벤트 핸들러를 등록할 수 있습니다. 또는 click(), focus()와 같이 jQuery에서 제공하는 간단한 형태인 ajaxStart(), ajaxSuccess() ajaxError(), ajaxComplete(), ajaxStop()로 핸들러를 등록할 수 있습니다.

2. Ajax Global Event

앞서 말한 것처럼 jQuery Ajax Event에는 지역 이벤트와 전역 이벤트가 있습니다. 본 글에서는 Ajax 전송 요청을 할 때 많이 사용하는 지역 이벤트 보다는 조금 생소할 수 있는 전역 이벤트를 중점으로 각 특징을 살펴봅니다. 그 전에 Ajax 전역 이벤트에 공통 적용되는 몇 가지 사항이 있습니다.

  • Ajax 전역 이벤트는 $.ajax()나 $.ajaxSetup()의 옵션 중 global 속성 값이 true일때만 발생합니다. 기본값이 true이므로 명시적으로 false 값을 주면 전역 이벤트가 발생하지 않습니다.
  • 1.8 버전 이전에서는 이벤트가 발생할 때 DOM에 있는 모든 엘리먼트로 이벤트가 전파 되었습니다. 그래서 어떠한 DOM 엘리먼트에도 핸들러를 등록할 수 있었으나 1.8 버전부터 document 엘리먼트로만 이벤트가 전파 되기 때문에 반드시 document에 핸들러를 등록해야 합니다. $(document).ajaxStart()와 같은 형태로 사용하면 됩니다.
  • 핸들러 등록 함수는 jQuery 확장집합을 반환하므로 메서드 체이닝(Method Chainning)을 사용할 수 있습니다.
  • 지역과 전역 둘 다 존재하는 beforeSend와 ajaxSend, success와 ajaxSuccess, error와 ajaxError, complete와 ajaxComplete 이벤트 들은 항상 지역 이벤트 callback이 실행된 후에 전역 이벤트가 발생합니다.
  • 핸들러가 동작할 때 함수 컨텍스트(this)는 핸들러가 등록된 DOM 엘리먼트가 됩니다. 1.8 버전부터는 항상 document가 될 것 입니다.

핸들러에는 매개변수로 많은 정보가 전달 되는데 대표적으로 jQuery 이벤트 객체, XHR(XmlHttpRequest) 객체, $.ajax()에 전달된 옵션 등이 있습니다. 그럼 각 Ajax 이벤트 핸들러 등록 함수의 특징을 살펴봅니다.

ajaxStart( handler() )

진행중인 Ajax 요청이 없는 상태일때 최초 Ajax 요청이 시작되는 시점에 발생합니다.

매개변수

  • handler( event )
    : (function) 이벤트 발생시 동작 함수. jQuery 이벤트 객체를 매개변수로 전달합니다.

ajaxSend( handler() )

각각의 Ajax 전송 요청마다 요청이 전송되기 전에 발생합니다. XHR 객체가 생성된 상태이며 수정할 수 있으며 false를 반환하면 요청이 취소 됩니다.

매개변수

  • handler( event, jqXHR, ajaxOptions )
    : (function) 이벤트 발생시 동작 함수. jQuery 이벤트 객체, jqXHR 객체(1.5 버전 이전은 XMLHttpRequest), $.ajax() 옵션 객체를 매개변수로 전달합니다.

ajaxSuccess( handler() )

각각의 Ajax 전송 요청마다 상태코드가 성공인 응답이 반환될 때 발생합니다.

매개변수

  • handler( event, XMLHttpRequest, ajaxOptions, Data Object )
    : (function) 이벤트 발생시 동작 함수. jQuery 이벤트 객체, XHR 객체, $.ajax() 옵션 객체, 응답 본문 data 객체를 매개변수로 전달합니다.

ajaxError( handler() )

각각의 Ajax 전송 요청마다 상태코드가 에러인 응답이 반환될 때 발생합니다.

매개변수

  • handler( event, jqXHR, ajaxOptions, thrownError )
    : (function) 이벤트 발생시 동작 함수. jQuery 이벤트 객체, jqXHR 객체(1.5 버전 이전은 XMLHttpRequest), $.ajax() 옵션 객체, 존재할 경우에 XHR 객체가 반환 한 예외 상태 문자열을 매개변수로 전달합니다.

ajaxComplete( handler() )

각각의 Ajax 전송 요청마다 상태 코드에 상관없이 응답이 반환될 때 발생합니다.

매개변수

  • handler( event, XMLHttpRequest, ajaxOptions )
    : (function) 이벤트 발생시 동작 함수. jQuery 이벤트 객체, XHR 객체, $.ajax() 옵션 객체를 매개변수로 전달합니다.

ajaxStop( handler() )

진행중인 모든 Ajax 요청이 완료될 때 발생합니다.

매개변수

  • handler( event )
    : (function) 이벤트 발생시 동작 함수. jQuery 이벤트 객체를 매개변수로 전달합니다.

Ajax Global Event 발생 순서

jQuery의 Ajax 전역 이벤트 API를 알아봤습니다. 그럼 두개의 Ajax 전송이 순차적으로 수행 될 때 이벤트가 발생하는 시점을 보겠습니다.

[그림 01] jQuery Ajax Event 발생 시점

Ajax 전송의 응답을 받기 전에 또 다른 Ajax 전송이 이루어진 상황의 흐름입니다. 최초 전송 요청이 시작할 때 ajaxStart, 마지막 전송 요청의 응답이 왔을 때 ajaxStop가 한번씩 발생하고 각 Ajax 요청마다 지역 이벤트 callback 실행과 전역 이벤트 핸들러가 실행됩니다. 지역과 전역이 세트로 있는 이벤트들은 항상 지역 이벤트에 대한 callback이 먼저 실행되고 전역 이벤트 핸들러가 실행됩니다.

3. Ajax Global Event를 이용한 메시지 창 띄우기

jQuery의 Ajax 지역, 전역 이벤트와 그에 대한 API, 그리고 발생 시점까지 알아보았습니다. 지역 이벤트에 대한 callback은 $.ajax()의 옵션에 넣어 많이 사용을 합니다. 그러면 전역 이벤트는 어떤 상황에서 사용할 수 있을까요? 지금부터 Ajax 통신의 문제상황과 이를 Ajax 이벤트로 해결하는 것을 살펴 보겠습니다.

문제상황

Ajax 통신은 비동기로 이루어지기 때문에 Ajax 통신이 진행되는 중에도 사용자는 다른 기능을 이용할 수 있습니다. 이것이 Ajax의 장점 중 하나인데 이러한 점으로 인해 문제가 발생할 때도 있습니다. 아래의 화면을 보겠습니다.

[그림02] 시스템 구성화면

중앙에 트리구조로 구성된 영역이 있습니다. 이 트리구조는 3단계의 역할 – 역할과목 – 역할과목매핑 으로 구성됩니다. 사용자는 미리 정의된 역할에 역할과목을 생성하고 역할과목에 코스타 과목을 검색하여 매핑을 합니다. 그러면 역할과목매핑 데이터가 생성 되고 1개의 과목에는 여러 개의 과목 매핑이 가능 합니다.  우측 상단영역이 역할과목 하위에 있는 역할과목매핑 목록이고 우측 하단영역이 코스타 과목 목록입니다. 코스타 과목을 검색 후 원하는 과목을 선택하여 추가 버튼을 누르면 역할과목매핑 데이터가 생성 됩니다.

이러한 전체적인 구조와 매핑정보를 한눈에 파악하기 쉽도록 위와 같이 화면을 구성하였습니다. 우측 영역은 트리구조의 노드를 선택해야 나오기 때문에 사용자의 유연한 기능수행을 위해 과목 추가는 Ajax 통신으로 이루어집니다. 그런데 하나의 역할과목에는 여러 개의 역할과목매핑이 가능하지만 동일한 과목은 중복이 불가능 합니다. 또한 이 기능의 수행시간은 대략 2초정도가 걸립니다. 바로 여기에서 문제가 발생 합니다. 같은 과목이 중복으로 추가 될 수 없는데 Ajax 통신 수행시간이 짧지 않기 때문에 수행 중 동일한 과목을 추가 할 경우 데이터가 꼬이게 됩니다.

해결책으로 Ajax 통신이 시작될 때 화면에서 선택된 과목을 없애는 방법도 있겠지만 본문에서 알아본 Ajax 이벤트를 이용하여 Ajax 통신이 수행되는 동안 ‘처리중입니다.’라는 메시지 창과 함께 브라우저의 전체 영역을 잠금 처리하는 방법으로 해결하겠습니다.
이것이 Ajax 전역 이벤트가 사용되는 하나의 예입니다. 그럼 메시지 창 띄우기를 코드와 함께 진행해 보겠습니다.

메시지 창 만들기

우선 메시지 창 안에서 사용할 progress 이미지를 다운로드 받습니다. http://www.ajaxload.info 에 가면 색상과 모양을 선택하여 이미지를 받으실 수 있습니다.

[그림03] AjaxLoad Homepage

적당한 이미지를 받았으면 메시지 창과 화면 전체를 modal 처리하는 레이아웃과 CSS 등을 만들어야 하는데 우리의 초점은 Ajax 이벤트이기 때문에 간단하게 jQuery UI를 이용하여 작성을 했습니다. 우선 jQuery UI 웹사이트의 데모를 참조하여 샘플 페이지를 만들었습니다.

<!DOCTYPE html>

<html >
<head>
<meta charset="utf-8">
<title>jQuery UI Dialog</title>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
<script src="http://code.jquery.com/jquery-1.10.2.js"></script>
<script src="http://code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
<script>
$(function() {
	//
	$("#button").click(function () {
		//
		$('#dialog-modal').dialog({
	        height: 140,
	        modal: true
	    });
	});
});
</script>
</head>
<body>
 
<div id="dialog-modal" title="서버 통신" style="display:none;">
  <p><img src="D:\ajax-loader.gif"/> 처리 중입니다...</p>
</div>
 
<p>Sed vel diam id libero
    <a href="http://example.com">rutrum convallis</a>.
    Donec aliquet leo vel magna. Phasellus rhoncus faucibus ante.
    Etiam bibendum, enim faucibus aliquet rhoncus, arcu felis ultricies neque, sit amet auctor elit eros a lectus.
</p>
<button id="button">메시지창</button>
 
</body>
</html>
[그림04] jQuery UI Basic Modal Demo 결과화면

jQuery UI로 modal 속성이 true인 dialog를 생성하였습니다. 위의 결과화면처럼 화면 중앙에 메시지 팝업과 함께 progress 이미지가 나오고 팝업 외의 영역은 modal 처리가 됐습니다. 서버 없이 html로 만들었기 때문에 소스코드의 이미지 경로는 애교로 봐주세요..^^;

Ajax 이벤트 핸들러 등록

그럼 이제 샘플을 만들어봤으니 Ajax 이벤트에 핸들러를 등록 할 차례입니다. 우선 어떤 이벤트에 등록할지를 정해야겠죠? 메시지 창을 띄우는 이벤트는 ajaxStart, ajaxSend 창을 닫는 이벤트는 ajaxSuccess, ajaxComplete, ajaxStop 정도가 적당할 것 같습니다. 그럼 이 이벤트들간의 차이를 현재 상황과 함께 고민해봐야 합니다.

ajaxStart는 여러 Ajax 전송 요청의 최초 시작에 발생하니 모든 Ajax 전송에 대해 메시지 창 처리가 필요할 때 사용하면 좋습니다. 반면 Ajax 전송 중에서도 메시지 창 처리가 필요한 일부에만 적용한다면 ajaxSend 혹은 지역 이벤트인 beforeSend의 callback에서 수행하면 됩니다. ajaxStart에 핸들러를 적용하면 메시지 창 처리를 하지 않는 Ajax 수행 도중에 메시지 창 처리를 하는 Ajax 전송 요청이 수행되면 메시지 창이 안 뜰 것입니다. 현재 상황은 Ajax 통신이 네트워크 상황에 따라 얼마나 걸릴지 모르므로 모든 Ajax의 통신중에 메시지 창을 띄우기로 하였고 ajaxStart를 사용합니다.

마찬가지로 ajaxStop은 모든 전송에 메시지 창 처리가 필요할 때 사용합니다. 일부 전송에만 적용한다면 ajaxSuccess와 ajaxComplete 혹은 success, complete의 callback이 있습니다. ajaxSuccess는 Ajax 요청 응답이 에러일 경우에는 메시지 창이 안닫힌다는 점이 있습니다. 이번에도 모든 Ajax 통신에 대해서 마지막 응답에서만 메시지 창을 해제 할 것 이므로 ajaxStop을 사용합니다. 아래는 적용한 소스코드 입니다.

roadjs.sendRequest = function (url, param, optiopns, callback) {
    // ajaxOptions 객체 생성 .. 

    var messageDiv = '',
        $messageDiv = $('#_messageDiv');

    // 동일 페이지 내에서 최초로 sendRequest()를 호출했을때만 수행
    if ($messageDiv.length === 0) {
        messageDiv += '<div id="_messageDiv" title="서버 통신">';
        messageDiv += '<br/><span style="font-size:25pt;"><img src="' + DOC.ROOT + 'style/img/loading01.gif"/> 처리 중입니다...</span>';
        messageDiv += '</div>';
				
        $messageDiv = $(messageDiv);
	$(document).prepend($messageDiv);
				
        $messageDiv.dialog({
            minHeight: 120,
            maxHeight: 120,
            width: 350,
            modal: true,
            closeOnEscape: false,
            open: function(event, ui) {
                $('.ui-dialog-titlebar-close').hide();
            }
        })
        .dialog('option', "resizable", false);
				
        $.ajaxStart(function () {
            //
            $messageDiv.dialog('open');
        })
        .ajaxStop(function () {
            //
            $messageDiv.dialog('close');
        });
    }
    // Ajax 요청 시작
    $.ajax(ajaxOptions);
}

$.ajax()를 편하게 사용하기 위해서 공통 js에 sendRequest()라는 내부적으로 $.ajax()를 수행하는 함수가 있습니다. sendRequest()가 최초 호출 되었을 때만 메시지 창 처리와 해제 핸들러를 등록합니다. 이후에는 ajaxStart와 ajaxStop 이벤트가 발생할 때 등록 되있는 핸들러가 계속 동작합니다. 메시지 창을 사용자가 임의로 닫지 못하도록 헤더 영역의 X 버튼 숨김과 esc 종료를 막는 처리가 추가되었습니다. 아래는 결과 화면입니다.

[그림05] 메시지 창 처리 결과화면

메시지 창 처리가 잘 적용되어 이제는 기능이 잘못 동작하거나 데이터가 꼬이는 경우가 없을 것 같습니다. 이로써 메시지 창을 띄워 Ajax 처리 중 기능이 잘못 동작하는 것을 막아 보았습니다.

4. 마치며..

jQuery Ajax의 지역 이벤트와 전역 이벤트를 살펴 보았습니다. 메시지 창 처리는 좋은 jQuery 플러그인이 많기 때문에, 현장에서 이렇게 사용하는 경우는 많지 않습니다. 하지만 이것을 알고 사용하는 것과 모르고 사용하는 것의 차이는 큽니다. 정확히 알고 필요한 곳에 상황에 맞게 적용하는 것이 의미가 있다고 생각합니다. 조금 더 하위 레벨로 간다면 jQuery UI도 사용하지 않고 직접 다 만들어보면 더 좋겠죠. 저도 위에서의 sendRequest() 기능을 수행하는 완성된 함수를 사용만 하다가 직접 개발하고 적용하는 과정에서 많은 것을 배웠습니다. 감사합니다.

참조 도서 및 사이트

  • 윤인성 저, ‘모던 웹을 위한 JavaScript + jQuery 입문’, 한빛미디어, 2013
  • 베어 바이볼트·예후다 카츠 공저 / 이두원·송효종·차기용 공역, ‘프로그래밍 jQuery 2판’, 인사이트, 2011
  • jQuery, http://jquery.com
  • jQuery UI, http://jqueryui.com
  • Ajaxload, http://www.ajaxload.info
  • 서지수, ‘JavaScript, jQuery, 그리고 Ajax’, 넥스트리 홈블로그, 2014
  • Salman A, ’Remove close button on jQueryUI Dialog?’
namoosori
안녕하세요. 나무소리 입니다. 나무소리는 넥스트리(주)의 교육 브랜드 입니다.넥스트리가 지난 20년 동안 쌓아온 개발 및 교육 경험들을 나무소리를 통해 많은 분들과 공유 하려고 합니다.앞으로 저희 나무소리를 통해 보다 나은 교육을 경험 하실 수 있도록 구성원 모두 최선을 다하겠습니다.