아티클

WAI-ARIA 패턴라이브러리 : 체크 박스 패턴라이브러리 소개 및 사용법

2021-06-24 17:34:25

안녕하세요, 엔비전스입니다.

지난번에는 간략하게 패턴 라이브러리를 제작하는 방법에 대해 설명했습니다.

이번에도 마찬가지로, 패턴 라이브러리를 만드는 방법 하나와 완성된 패턴 라이브러리 한 가지를 소개하고, 사용법을 알려주는 글을 쓰고자 합니다.

체크 박스 패턴 라이브러리, 이렇게 만들어졌습니다.

이번에 소개할 패턴 라이브러리는 지난 아티클에서 짧게 소개한 WebComponents API를 사용하여 만들었습니다.

WebComponents API는 customElements라는 객체의 define이라는 메소드와 ES6의 class 사용하여 새 HTML 요소를 만들 수 있는 API입니다.

태그 형태로 새로운 커스텀 요소 만들기

class CustomCheckboxElements extends HTMLElement{
  constructor(){
    super();
  }
  
  set checked(v){ // checked 값을 설정합니다.
    ...
  }
  get checked(){ // checked 값을 가져옵니다.
    return ...
  }

  connectedCallback(){ // 이 요소가 DOM에 연결되었을 때, 자동으로 실행되는 callback 메소드입니다.
    ...
  }
  disconnectedCallback() // 이 요소가 DOM으로부터 완전히 사라졌을 때 실행되는 callback 메소드입니다.
  attributeChangedCallback(
  AttrName/*속성 이름, key*/,
  OldValue/*이전 속성값*/,
  newValue/*새 속성값*/){ /* 다음에 나올 static get observedAttributes()에 설정되어 있는 
  속성을 감지하고, 바뀐 내용이 있을 때 마다 실행되는 callback 메소드입니다.
  oldValue는 전에 속성값에 담겼던 값(value)이며, newValue는 새로 담긴 값입니다.

  이 callback 안에서 조건문을 사용하여, 특정 속성이 추가되었을 때, aria 속성을 추가하거나 할 수 있씁니다.
  
  newValue는 속성이 삭제되었다면 null 값이 담기며, 속성값이 있다면 빈 문자열 또는 문자열을 담습니다.
  oldValue는 반대로, 속성이 추가되었다면 oldValue에는 null, newValue에는 추가된 속성의 비거나 채워진 문자열 값이 담깁니다.
  */

    ...
  }

  static get observedAttributes(){
    /*
    return ['disabled','checked','aria-controls'];
    return으로 반환된 배열에 안에 있는 텍스트와 동일한 HTML 속성을 감시합니다.
    이 속성 외에는 감시하지 않습니다. 위에서 작성한 attributeChangedCallback에서 
    이 속성들의 변화를 캐치할 수 있습니다.
    */ 
  }

  ...
  
}//Class 끝

//customElements.define('커스텀-태그이름',커스텀_태그_인스턴스_클래스);
customElements.define('nv-checkbox',CustomCheckboxElement);

이런 방식으로 CustomElements를 만들고, HTML에 로드한 다음, 이렇게 사용할 수 있습니다.

<nv-checkbox checked></nv-checkbox>

customElements는 위와 같이 “{소문자 영문}-{소문자 영문}” 형식의 태그 이름을 허용해요. 대문자가 포함되거나, 하이픈을 사용하지 않는 형식의 태그 이름으로는 만들 수가 없습니다.

기존 HTML 요소 확장하기

또한, 아래와 같이 기존 HTML 태그를 확장하여 사용할 수도 있습니다.

이미 HTML 요소로 널리 사용되고 있고, 디자인과 기능만을 추가할 것이라면 이 방법이 조금 더 접근성에는 좋습니다.

class FencyButton extends HTMLButtonElement{
  constructor(){
    super();
    ...
  }
  ....
}

/*아까와는 달리 class extends에 HTMLElement가 아닌 HTMLButtonElement를 넣은 것을 볼 수 있으며,
 3번째 인자에 옵션 Object가 담깁니다. 옵션 인자에는 extends라는 속성이 있으며, class에서 상속받기로 한 인스턴스(혹은 프로토타입)의 태그 이름을 적습니다.*/
customElements.define('nv-fency-button',FencyButton,{
  extends:'button'
})

그리고 다음과 같이 사용됩니다.

<button is="nv-fency-button">Hello, I'm a very cool fency-button!</button>

이전 코드처럼 class에 특정 네이티브 요소 인스턴스를 지정하여 커스텀 요소를 만들면 태그처럼 사용할 수 없습니다. 대신 이처럼 is 속성으로 지정해 주어야 사용할 수 있어요.

이렇게 확장된 요소는 네이티브 HTML 요소의 기능을 고스란히 물려받는 대신, 마크업으로 만들어질 때부터 개발자가 원하는 기능을 실행하게 됩니다.

lodash-fp를 통한 class 합성

lodash는 자바스크립트 기본 문법으로는 긴 코드를 써야 하는 여러 가지 메소드나 함수를 간략한 형태로 만들어 모아 놓은 라이브러리입니다. 그중에 lodash-fp라는 모듈 안에는 compose라는 기능이 있습니다.

현대 자바스크립트의 기법 중, mixin 기법을 조금 더 쉽고, 깔끔하게 사용할 수 있도록 해주는 함수입니다. 자바스크립트는 다중 상속을 지원하지 않습니다.

class가 일반적으로 우리가 생각하는 동일한 개념이 아니기 때문이며, 그 대안으로 mixin을 사용합니다.

mixin 기법은 Ojbect.assign을 사용하는 기법과 함수를 중첩으로 사용하여 여러 단계로 걸처 상속시키는 방법이 있습니다.

class를 사용하는 믹스인은 주로 함수를 중첩하여 합성합니다.

const disabledMixin = base =>class extends base {
  set disabled(v){...}
  get disabled(){...}
  ...
}

const checkedMixin = base =>class extends base {
  set checked(v){...}
  get checked(){...}
  toggle(){...}
}

CustomCheckboxElement extends disabledMixin(checkedMixin(HTMLElement)){
  constructor(){
    super();
    ...
  }
  set checked(v){
    //만약에 상속된 속성, 메소드을 덮어씌우되 동작을 더 추가하려면 super로 호출하면 됩니다.
    super.checked = v;
    //이 아래부터 추가할 동작 코드를 또 쓰면 됩니다.
  }
}

사용할 이름으로 변수를 정의, 화살표 함수를 만들고 담습니다. 인자로는 인스턴스나 함수를 받게끔 합니다.

그리고, 위처럼 계속 함수를 중첩, 쌓아가면서 합성할 수 있습니다. 합성은 위처럼 유연하게 CustomElements에도 사용할 수 있습니다.

하지만 위 방법에는 단점이 있습니다. 아래와 같은 예를 들 수 있지요.

class Developer {
  ...
}

class AwesomeDeveloper extends skillMarkup(skillDatabase(skillPHP(skillNodeJS(skillReactAndVue(...(Developer)))))){
  ...
}

네, 보기만 해도 구역질 나지요? 물론, 저렇게 작성하는 사람은 없습니다.

조금만 생각해 봐도, 유사점이나 관련이 있는 기능끼리 묶어서 충분히 가독성 있게 만들 수 있을 겁니다.

하지만, 그렇게 한다고 해도 가독성이 좋아지지는 않을 겁니다.

이 라이브러리를 만들 때, 그래서 lodash-fp에 있는 compose를 사용하게 되었습니다.

import {compose} from 'lodash-fp';
import {
  skillMarkup,
  skillDatabase,
  skillPHP,
  skillJavascriptBasic,
  skillNodeJS,
  skillReact,
  skillReactNative,
  skillJavaWithAndroidAppDevelopment,
  skillSwiftWithIOSAppDevelopment,
  ...
} from './devSkills.js';


class Developer {...}

AwesomeDeveloperSkillList = [
  skillMarkup,
  skillDatabase,
  skillPHP,
  skillJavascriptBasic,
  skillNodeJS,
  skillReact,
  skillReactNative,
  skillJavaWithAndroidAppDevelopment,
  skillSwiftWithIOSAppDevelopment,
  skill...
]

class AwesomeDeveloper compose(
  ...AwesomeDeveloperSkillList
)( Developer ){
  constructor(...args){
    super(...args)
    ...
  }
}

훨씬 길고 많은 내용이 들어가 있지만, 눈으로 보기에는 아주 깔끔해졌습니다.

이번에 소개할 커스텀 체크 박스는 이런 식으로 checked, pressed 등, 요소에 따라 필요한 상태 관리 메소드를 부품처럼 mixin 패턴으로 분리하고, 합성하여 만들었습니다.

webpack과 babel

사실 이 부분까지 나간다면 너무 내용이 길어질 것이며, 사실 접근성 아티클 특성상 위 내용도 개발에 치우쳐 있다고 볼 수 있습니다. 다만, 조금 더 효율적이고, 안정적인 컴포넌트를 만들려면, 위 두 가지가 필요합니다.

특히, 어쩔 수 없이 Internet Explorer를 지원해야 하는 페이지는 이 둘을 사용하는 것이 필연이라고 생각합니다.

2022년 6월부터 Internet Explorer 서비스가 종료됨에 따라, IE에 대한 호환성 걱정을 덜어줄 것으로 보이나, 여전히 크로스 브라우징 단계에서 사용될 것으로 보입니다.

본 아티클에서 소개할 컴포넌트도 webpack과 babel을 통해 번들링 했습니다.

nv-checkbox 소개

nv-checkbox[다운로드] [소스 저장소]는 <nv-checkbox> 태그 또는 is=“nv-tagname-checkbox” 속성을 마크업 하면 커스텀 체크 박스를 만들어주는 라이브러리입니다. 사용법은 다음과 같습니다.

설치 방법

문서 body 끝에 <script src="customCheckbox.js"></script>처럼 불온 다음, 마크업 하세요. head에 넣을 것이라면 defer HTML 속성을 추가로 작성해주세요.

레퍼런스

<nv-checkbox> (기본)
기본적인 사용법입니다. 일반적인 체크 박스처럼 HTML에 마크업 하세요.
<nv-label for="YouCantCheckMe">넌 날 체크할 수 없어<nv-label>
<nv-checkbox checked disabled id="YouCantCheckMe"></nv-checkbox>
<div is=“nv-div-checkbox”></div>
div태그를 nv-checkbox로 구현합니다.
<nv-label for="DIVCHECK">난 DIV지만 체크 박스가 될거야<nv-label>
<div is="nv-div-checkbox" checked id="DIVCHECK"></div>
<span is=“nv-span-checkbox”></span>
span 태그를 nv-checkbox로 구현합니다.
<nv-label for="SPANCHECK">나 span은 체크 박스가 되는 것에 동의합니다.</nv-label>
<div is="nv-div-checkbox" checked id="SPANCHECK"></div>
<span is=“nv-span-checkbox”></span>
p 태그를 nv-checkbox로 구현합니다.
<nv-label for="PARAGRAPHCHECK">마크업은 p로 되어있지만, 난 더는 문단 태그가 아니란다<nv-label>
<span is="nv-span-checkbox" checked id="PARAGRAPHCHECK"></span>
<span is=“nv-span-checkbox”></span>
p 태그를 nv-checkbox로 구현합니다.
<nv-label for="PARAGRAPHCHECK">마크업은 p로 되어있지만, 난 더는 문단 태그가 아니란다<nv-label>
<p is="nv-p-checkbox" checked id="PARAGRAPHCHECK"></p>
<a is=“nv-span-checkbox”></a>
a 태그를 nv-checkbox로 구현합니다.
<nv-label for="ANCHORCHECK">제발 나를 어설픈 가짜로 만들지 마! 진짜 같은 가짜로 만들어 줘!<nv-label>
<a is="nv-a-checkbox" checked id="ANCHORCHECK"></a>
다른 태그와는 달리 조금 다른 점이 있다면, a태그에 href가 있다면 콘솔에 href 속성이 사라진다는 warning을 표시하는 점입니다.

위에 설명한 모든 방법은 자바스크립트 DOM 메소드인 document.createElement()로도 사용하여 체크 박스를 만들 수 있습니다.

  • document.createElement('nv-checkbox')
  • document.createElement('div',{is:"nv-div-checkbox"})
  • document.createElement('span',{is:"nv-span-checkbox"})
  • document.createElement('p',{is:"nv-p-checkbox"})
  • document.createElement('a',{is:"nv-a-checkbox"})

부가기능 1 : 하위 체크 상자 지정하기

모든 nv-checkbox에 aria-controls를 사용하면 전체 선택과 같은 기능을 수행하는 체크 박스로 만들 수 있습니다.

aria-controls=“IDREF”로 하위 체크 박스가 모여있는 컨테이너를 연결하면 됩니다.

부가기능 2 : 객체 메소드

nv-checkbox는 HTMLElement의 확장이므로 네이티브 요소처럼 document.querySelector()나 getElementById() 등으로 불러와 바로 메소드를 사용할 수 있습니다.

const NV_CHECKBOX1 = document.getElementById('NV_CHK1');

NV_CHECKBOX1.checked = true // 체크
NV_CHECKBOX1.checked = false // 체크 해제
NV_CHECKBOX1.disabled = false // 사용 가능
NV_CHECKBOX1.disabled = true // 사용 불가

부가기능 3 : 레이블 연결하기

커스텀 요소이기 때문에 네이티브 label은 사용할 수 없지만, <nv-label for=“IDREF”> 태그를 사용하면 동일한 효과를 낼 수 있습니다.

이 라이브러리의 한계

원래는 Internet Explorer에서도 작동하도록 만들고자 하였으나, 알 수 없는 이유로 오류가 발생하여 아쉽게도 IE에 대한 지원이 되지 않은 상태로 배포됨을 알립니다.

이번 아티클은 여기까지이며, 이 시리즈는 구현 방법을 중점으로 다루는 아티클이 될 예정이었으나, 방향을 약간 달리하도록 했습니다. 이 시리즈는 매시간 요소를 하나 만들고, 그것에 대한 간략한 설명과 약간의 구현 방법, 그리고 만들어진 요소 라이브러리를 배포하고 설명하는 식의 아티클이 될 예정입니다.

다소 부족하고, 긴 글이었지만 끝까지 읽어주신 분들께 감사의 말씀드리며, 다음 아티클에서는 더 알찬 내용, 누구나 더 쉽게 이해할 수 있는 발전된 모습으로 찾아뵙겠습니다. 감사합니다.

댓글 0
댓글을 작성하려면 해주세요.