wp-util #2: wp.ajax

지난 포스트에 이어 이번에는 wp.ajax 에 대해 포스팅한다. wp.ajax에 대해 소개하고 wp.ajax와 wp.template을 활용한 초간단 플러그인을 같이 제작해 본다.

wp.ajax 객체는 jQuery.ajax를 래핑해서 조금 더 사용하기 편리하게 만들었다. 워드프레스에서 Ajax를 사용할 때 몇가지 번거로운 사항이 있는데, wp.ajax는 이걸 잘 해결해놔서 편리하다.

우선 이 번거로운 사항이 뭐냐면,

  • admin-ajax.php 경로를 매번 따로 지정해야 한다는 점.
  • ajax 핵심 파라미터 ‘action’이 data에 포함되어 있어 시인성이 떨어진다는 점.
  • Response를 받은 후 success 체크를 하는 루틴을 반복적으로 코딩해야 한다는 점.
  • wp_send_json_* 함수를 사용한 경우, 응답으로 전달되는 객체에 success, data 키가 있다. 구조적으로는 훌륭한 응답이지만, 원하는 데이터가 (객체).data 쪽에 있다는 것이 썩 직관적으로 다가오진 않는다.

wp_enqueue( 'wp-util' )을 사용하면 코어가 알아서 ajax url을 로컬라이즈 객체로서 삽입해 준다. 코어의 script-loader.php 부분을 보면,

<?php 
...
$scripts->add(
 'wp-util',
 "/wp-includes/js/wp-util$suffix.js",
 array( 'underscore', 'jquery' ),
 false,
 1
);

did_action( 'init' ) && $scripts->localize(
  'wp-util',
  '_wpUtilSettings',
  array(
    'ajax' => array(
      'url' => admin_url(
        'admin-ajax.php', 'relative' ),
    ),
  )
);
...Code language: PHP (php)

이렇게 선언되어 있어 (admin-ajax.php 경로를 매번 알려줘야 하는) 할 일을 하나 줄여준다.

wp.ajax 객체 안에는 두 개의 함수가 선언되어 있다. 하나는 post(), 다른 하나는 send()인데, post는 send()를 감싼 것일 뿐이므로 여기서는 send() 만 언급하도록 하겠다.

send()의 첫번째 파라미터는 action, 두번째 파라미터는 options이다. action에는 ajax 액션으로 문자열로 넣고, 두번째는 $.ajax에 쓰는 옵션을 그대로 넣을 수 있다. 물론 이것을 무시하고 $.ajax와 동일하게 첫번째 파라미터에 객체를 몽땅 집어넣어도 문제 없게 설계되어 있다. 그러나 기왕 action 값을 시각적으로 보기 좋게 해 놨는데, 굳이 다시 뭉쳐버릴 필요는 없을 것 같다.

send()는 또한 응답을 받았을 때 반복적인 처리까지 잘 구현해 놓았다. Ajax 응답은 다음과 같이 경우의 수가 갈린다.

  • 성공의 경우:
    • 단순 텍스트 형태.
    • 임의의 JSON.
    • wp_send_json(), wp_send_json_success()로 보낸 응답.
    • 기타 등등…
  • 실패의 경우:
    • 단순 텍스트 형태.
    • 임의의 JSON.
    • wp_send_json(), wp_send_json_error()로 보낸 응답.
    • wp_send_json_error( new WP_Error( … ) ) 콤보 응답.
    • 기타 등등…

send()는 이 중 wp_send_json_* 류의 함수 응답의 success 키에 미리 대응해서 success: 1이면 성공, success: 0면 에러로 간주한다. 그리고 success, error 콜백을 할 때 success는 떼어내고 data만 콜백의 인자로 넣어준다. 이렇게 하면 콜백 함수에서 생각해야 할 경우의 수가 줄어든다.

예를 들어, 그냥 $.ajax를 사용한다면 다음처럼 콜백을 작성해야 한다.

var options = {
  method: 'post',
  data: {
    action: 'foo',
    nonce: '...',
    ...
  },
  beforeSend: function() {
    // 보통 여기에 버튼을 disable 하는 등의
    // ajax 호출을 중복 방지하는 루틴,
    // 또는 로딩 애니메이션을 삽입함.
  },
  success: function (r) {
    if (r.success) {
      // 성공
      console.dir(r.data);
    } else {
      // 실패
      var buffer = [];
      if (Array.isArray(r.data)) {
        r.data.forEach(function (item) {
          if (item.hasOwnProperty('code') &&
              item.hasOwnProperty('message')
          ) {
            // wp_send_json_error(new WP_Error( ... ));
            buffer.push(item.code + ': '
                        + item.message);
          } else {
            buffer.push(item.toString());
          }
        });
        console.error(buffer.join('\n'));  
      } else {
        // 이외의 경우
        console.error(r.data);
      }
    }
  },
  error: function (jqXhr, textStatus, errorThrown) {
    console.error(jqXhr.status + ': ' + errorThrown);
  },
  complete: function (r) {
    // 그리고 여기에 disable 된 버튼을 원상 복구하는
    // 루틴을 삽입함.
  }
};
Code language: JavaScript (javascript)

정말 제대로 에러 캐치를 하려면 이렇게 길게(!) 작성해야 한다. 여기서 가장 문제가 되는 점은 바로 success 콜백에서도 r.success 여부를 판단하여 성공/에러를 한 번 더 검사해야 한다는 점. 즉 성공-성공, 성공-실패, 실패, 이렇게 세 가지 경우의 수를 생각해야 한다.

거기에 실패시 WP_Error 를 통해 전달된 에러 포맷인지, 아니면 다른 텍스트 형태의 에러 포맷인지까지 일일이 체크하면 위와 같은 거적떼기같은 코딩이 된다. 나는 wp.ajax.send를 발견하고 이렇게 경우의 수가 많이 갈리는 경우를 하나라도 줄여주는 녀석이 되게 반가웠다.

그러면 이제 예제 플러그인을 통해 wp.ajax와 wp.template을 조합해 보자. 코드는 gist 에 올려두었다. 플러그인은 단순하다. 활성화시켜서 확인해 보면 메뉴 하나, 메뉴에는 입력 상자와 버튼 뿐이다.

초간단 플러그인.

아무 텍스트나 치면 그 아래에 텍스트가 그대로 출력된다. 텍스트는 (쓸데없이) AJAX 전송을 통해 서버로 전달되고, 서버는 그냥 그대로 그 텍스트를 돌려줄 뿐이다. wp.ajax를 활용해 보기 위함일 뿐이다.

여기서 약간이나마 재미를 넣은 것이 ‘s:’, ‘p:’ 접두 키워드이다. ‘s:foo’라고 치면 ‘foo’라는 문자열이 <strong> 태그 안에 넣어져 나온다. 또 ‘p:bar’라고 치면 ‘paragraph: bar’ 문자열로 <p> 태그 안에 넣어져 나온다.

<# … #> 태그 안에는 어떤 자바스크립트라도 상관 없다. 그리고 마치 <?php … ?> php 문법처럼 if ~ else, for loop 등을 중간중간 분할해도 태그를 열고 닫는 것만 올바르면 문제 없이 인식된다.

그리고 wp.ajax.send()의 응답 콜백의 동작을 알아보기 위한 예시를 작성했다. $.ajax와 완전히 동일한 옵션을 사용하면 되니 직관적이고 편리하다. 하지만 success 콜백은 이제 r.success = false로 떨어지는 케이스를 더이상 고려하지 않아도 된다. success는 오직 성공 케이스만, 에러는 온전히 error 쪽에 전념하면 되도록 개선되었다.

그리고 예제는 여러 에러 응답 케이스를 고려해 보기 위해 일부러 조금 두껍게 작성되었다. 입력 문자열에 따라 일부러 여러 에러를 낼 수 있도록 해 보았다. ‘e1’, ‘e2’, ‘e3’, ‘e4’를 입력하면 각각 의도적인 에러를 낼 수 있다.

  • e1: wp_die()를 통한 400 에러를 낸다.
  • e2: HTTP Status는 200이지만, wp_send_json_error( WP_Error )를 이용한 폼 입력값의 에러를 가정한다. 여러 에러 메시지를 보여준다.
  • e3, e4: e2와 마찬가지이나, 메시지 본문의 포맷이 다르다.
위부터 e1 ~ e4 에러 메시지 창.

뭐, wp.ajax.send() 가 마냥 좋은 것은 아니다. 일단 underscore와 jQuery를 반드시 포함해야 한다. 개인적으로 underscore 스크립트나 jQuery를 더 추가한다고 해서 문제가 있다고는 생각하지는 않지만, 일부는 프론트에서 스크립트 로딩, 용량, 의존성 같은 부분에서 문제를 일으킬 수도 있다고 생각하겠지?

이제 정리하자. wp.template은 동적으로 파라미터를 활용한 템플릿 생성에 매우 편리한 유틸리티 함수이다. 적절히 활용하면 좀 더 복잡한 구조를 가진 요소를 생성할 때 편리하다. 특히 N개의 항목을 가지는, 동적인 개수를 가지는 옵션 필드 같은 것을 생성할 때는 더할 나위없이 믿음직한 녀석이다.

사실 wp.ajax는 이번에 조사하면서 알게된 녀석이다. 이것도 꽤 괜찮은 것 같다. 앞서 말한대로 action 파라미터 분리되는 거나, 응답 콜백이 조금 더 간편해지는 것, 지긋지긋하게 admin-ajax.php 경로 지정해준 것을 생략해 주는 것이 매우 반갑다. 앞으로 나는 자주 사용할 것 같다.

wp-util 소개 포스팅은 둘로 나눔에도 불구하고 매우 분량이 많아 버거웠다. 🙁 다음에는 내 본래의 목적(?) 대로 대충대충인 1일 1워프 포스팅이 되길.

댓글 남기기