버튼에 이벤트를 걸어서 타겟을 열고 닫고 하는 건 상당히 자주 구현하게 된다. 근데 이게 졸라 반복되는 패턴이다. 몇 번 시도를 했었는데 귀찮아서 구현하지 않고 있다가 얼마 전에 그냥 구현을 했다. 앞으론 그냥 이거 긁어서 사용할 생각이다.

▶ Demo부터 보기

일단 아이디어는, 버튼에 js-open-target이라는 클래스를 놓고, data-target 속성에 접었다 폇다 할 target의 selector를 명시해 주는 것이다. 이러면 클래스와 속성을 부여하는 것만으로 접었다 폈다 하는 것을 할 수 있다.

js

js 코드는 아래와 같다.

$(document).ready(function(){
  // 열리는 이벤트를 건다.
  $('.jquery-on-container').on('click', '.js-open-target', function(e){

    // a나 submit 버튼에 클래스를 매긴 경우 링크 이동이나 submit을 막는다.
    e.preventDefault();
    var target_selector = $(this).data('target');
    $(target_selector).slideDown();
    $(this).removeClass('js-open-target').addClass('js-close-target');
  });

  /*
   * 처음엔 .js-close-target 클래스가 DOM에 로드돼 있지 않다.
   * on 함수를 이용하면 감시하고 있던 요소 안에 정해 준 요소가 등장했을 때 이벤트를 건다.
   * 이 경우, .jquery-on-container를 감시하고 있다가 .js-close-target가 등장하면
   * 닫히는 이벤트를 건다. 앞의 열리는 이벤트를 on 함수로 건 이유도 마찬가지다. 
   * 클래스를 js-open-target으로 했다가 js-close-target으로 했다가 하기 때문이다.
   */
  $('.jquery-on-container').on('click', '.js-close-target', function(e){
    e.preventDefault();
    var target_selector = $(this).data('target');
    $(target_selector).slideUp();
    $(this).removeClass('js-close-target').addClass('js-open-target');
  })

  // js를 끈 경우엔 컨텐츠가 보이도록 js로 열 것은 js에서 감춘다.
  $('.js-open-target').each(function(){
    var target_selector = $(this).data('target');
    $(target_selector).hide();
  });
});

HTML

그리고 이런 HTML을 넣었다.

<h2 class="jquery-on-container">
  <!-- 키보드 focus가 가능하도록 a 태그를 이용했다. -->
  <a class="js-open-target" data-target=".closed-content">Toggle Target</a>
</h2>
<div class="closed-content">
  <p>대중이 역사의 무대에 등장하는 순간을 혁명이라 한다면, 우고 차베스의 볼리바르 혁명은 2002년 4월 11일에 시작했다.</p>
  <p>우익 쿠데타 세력은 차베스를 납치하고 새 정부를 선포했다. 그 정부는 겨우 48시간 동안만 유지됐다.</p>
  <p>차베스 지지자 수만 명이 차베스의 복귀를 요구하며 대통령궁을 포위했다.</p>
  ...
</div>

이 경우 h2 요소의 제목을 .jquery-on-container로 만들고, .js-open-target 요소는 그 안에 span으로 집어넣었다. 하지만 .jquery-on-container를 좀더 넓은 div로 잡아도 크게 문제될 건 없다. 단, body에 해당 클래스를 놓지는 마라. .live() 함수가 그런 함수였고, 성능 이슈가 발생해서 deprecated된 것이다. 혹시 몰라서 밝혀 두는데, .on() 함수에 대한 건 주석에 설명을 적어 두었다.

접었다가 폈다가 할 요소는 closed-content라는 클래스가 붙은 div다. 그래서 js-open-target 요소의 data-target에는 .closed-content라는 jQuery Selector를 넣어 줬다. js 코드를 보면 알겠지만 이 안에는 어떤 jQuery Selector를 넣어도 된다. 심지어 여러 개를 넣어도 되고, 여러 개에 영향을 미치는 Selector를 넣어도 된다.

CSS

마지막으로, CSS3를 살짝 이용했다. 접은 것엔 펼칠 수 있다는 암시를, 펼친 것엔 접을 수 있다는 암시를 줘야 하기 때문에, :after 가상 선택자를 이용해서 삼각형 화살표를 넣어 줬고, 마우스 포인터를 클릭할 수 있다는 암시를 주는 cursor로 바꿔 줬다.

.js-open-target, .js-close-target {
  cursor: pointer;
}
.js-open-target:after {
  content: "▼";
  font-size: .6em;
  font-family: helvetica;
  color: #888;
  vertical-align: middle;
  margin-left: .5em; 
}
.js-close-target:after {
  content: "▲";
  font-size: .6em;
  font-family: helvetica;
  color: #888;
  vertical-align: middle;
  margin-left: .5em; 
}

이렇게 삼각형 기호를 넣어 주면, 이미지를 넣는 것보다 해상도 대응이 쉬워 진다. vertical-align 속성을 이용해서 세로 위치를 가운데로 맞췄고, font-size는 맥락에 맞게 유동적으로 대응할 수 있도록 0.6em으로 지정해 줬다. 글꼴에 따라 모양이 달라지는 걸 방지하기 위해 어디에나 있을 법한 영문 글꼴인 helvetica를 글꼴로 지정해 줬다.

자, 그러면 만든 것을 데모 페이지에서 감상하면 되겠다.