업데이트:

실무에서 중복 사용되는 jQuery 코드를 통합해서 관리할 수 없을까 하는 생각을 하던 중 결국 플러그인을 만들어보기로 했고, 매일은 아니지만 시간날 때마다 작업을 하고 있다.

배경

다음과 같이 마크업되어 있다고 가정해보자.

1
2
3
4
5
<a href="#gnb" class="hashToggle">주메뉴 열기</a>
<div id="gnb"></div>

<a href="#layer-popup" class="hashToggle">레이어팝업 열기</a>
<div id="layer-popup"></div>

각 a 요소를 클릭 시, href 속성값과 매칭되는 id값을 가진 요소를 toggle하는 JS를 작성해야 한다면?

1
2
3
4
5
$(".hashToggle").on("click", function(event) {
    event.preventDefault();

    $(this.hash).toggle();
});

각 a 요소마다 일일이 JS를 작성하지 않고 hashToggle이라는 공통 클래스에 의해 제어된다. toggle만 할 거면 문제가 없으나, slideToggle, fadeToggle 등 여러 메소드를 사용해야 한다거나 이벤트가 발생할 때마다 특정한 로직이 들어가야 한다면?

그래서 플러그인을 만들어보기로 결심하고 작업을 시작하였다. 타인의 것을 가져다 쓰기만 하다가 직접 만들어보려니 막막한 느낌도 든다.

머릿속에 담아둔 요구사항

나름 구상해본 요구사항(?)은 다음과 같다.

  1. 모든 이벤트를 바인딩할 수 있어야 한다. click, mouseover
  2. toggle, fadeToggle 등 모든 메소드를 사용할 수 있어야 한다.
  3. 원하는 클래스를 toggle할 수 있어야 한다.
  4. 원하는 element에 클래스를 toggle할 수 있어야 한다.
  5. 이벤트 발생 시마다 원하는 로직을 넣을 수 있어야 한다.

확장성이 있어야 한다..로 요약할 수 있겠다. 당연히 플러그인은 확장성이 있어야 한다. 확장성이 없으면 플러그인이 아니다.

샘플

어느정도 뼈대를 설계했고, 다음과 같은 방식으로 작동하게 만들었다.

1
2
3
4
5
6
7
8
9
10
11
// 옵션 기본값 세팅
var option = $.extend({
    event: "click",
    action: "toggle",
    duration: 300,
    animateStop: true,
    toggleClass: null,
    addClass: null,
    removeClass: null,
    afterEvent: null
}, options);

event 옵션은 바인딩할 이벤트를 설정한다. 기본값은 click으로 설정했는데, 위에 예시로 든 코드를 봐도 알겠지만 대부분 click으로 뭔가를 toggle하는 경우가 많기 때문. 어쨌든 해당 옵션은 모든 이벤트를 사용할 수 있다.

action 옵션은 나타나고 사라지는 메소드를 설정한다. toggle, show, hide, slideDown, fadeOut 등 모든 메소드를 사용할 수 있다.

duration 옵션은 말 그대로 지연시간을 설정한다. 예) slideDown(300)

animateStop 옵션은 jQuery 메소드인 stop()을 걸어줄 수 있다.

toggleClass, addClass, removeClass 옵션은 jQuery의 그것과 같다. 예) toggleClass: "active"
클래스가 붙는 요소는 a 요소의 href 속성값과 매칭되는 id값을 가진 요소(이하 타겟요소)이다.

afterEvent는 바인딩한 이벤트에 대한 핸들러를 작성할 수 있는 콜백함수로, event 매개변수를 갖는다. 아래 afterEvent 옵션 구현이 매우 어렵다. 부분에서 자세히 설명하겠음.

이제 플러그인 호출을 해볼 것이다.

1
2
3
4
5
6
$("[href='#foo']").hashToggle({
    event: "mouseover mouseout",
    action: "fadeToggle",
    duration: 600,
    toggleClass: "active"
});

작동하는지 보려면 아래 codepen에 올려놓은 코드를 보면 된다.

See the Pen PozZZay by sel (@selucky) on CodePen.

afterEvent 옵션 구현이 매우 어렵다.

그나저나 머릿속에 담아둔 요구사항 중 마지막 “원하는 로직을 넣을 수 있어야 한다”는 게 무슨 뜻이냐, 위에 언급한 afterEvent 옵션 사용법은 다음과 같다.

1
2
3
4
5
$("[href='#foo']").hashToggle({
    afterEvent: function(event) {
        $(this).attr("title", "선택됨"); // 탭 ui의 웹 접근성 대응 코드
    }
});

우선 afterEvent 콜백함수 내에서 this를 호출했더니 option 객체로 나와서, hashToggle 함수를 호출한 객체에 this를 할당하려고 call 함수를 이용하였다. 까먹을까봐 메모하고 넘어감..


“이벤트 발생(예: click)” → “afterEvent 콜백함수에 작성한 핸들러 실행” 순서로 작동한다. 웹 접근성 대응이 필요할 경우 위와 같은 코드를 넣어야 하는데, 플러그인에서 해당 옵션을 제공하지 않는다면 플러그인이 가지는 중요한 의미인 확장성이 아무 의미없게 된다.

또 중요한 것은, afterEvent 옵션에 작성한 핸들러도 toggle 되듯이 흘러가야 한다는 것임. 로직 순서를 풀어보자면

  1. 이벤트 발생 (예: mouseover)
  2. 특정 콜백함수(예: afterFirstEvent)에 작성한 핸들러 실행
  3. 다시 이벤트 발생 (예: mouseout)
  4. 특정 콜백함수(예: afterLastEvent)에 작성한 핸들러 실행

이렇게 구현해야 하는데 어려워서 잘 풀리지 않고 있다.. 하루 아침에 되는 것도 아니고 계속 하다보면 될 것이라고 생각함.. 일단 플러그인 파일명은 jQuery.hashToggle-1.0.js 라고 계획했고.. 얼른 만들자..


2020/12/10, event.type을 검사해서 분기 처리하는 로직을 구성하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$("[href='#foo']").hashToggle({
    event: "focusin focusout",
    action: "slideToggle",
    duration: 400,
    animateStop: false,
    afterEvent: function(event) {
        if (event.type === "focusin") {
            $(this).attr("title", "선택됨");
        }

        if (event.type === "focusout") {
            $(this).attr("title", "");
        }
    }
});

복잡하게 afterFirstEvent/afterLastEvent 같은 방식보단 훨씬 깔끔하고 직관적인듯? 이제 완성이 머지 않았.. 미루지 말고 화이팅~

Hero image from viarami