이 글은 안젤리나 파브로(Angelina Fabbro)의 Custom Elements for Custom Applications – Web Components with Mozilla’s Brick and X-Tag 한국어 번역본입니다.
이번 글에서 우리는 모질라에서 공개한 브릭(Brick)과 X-Tag 라이브러리를 살펴볼 겁니다. 먼저 브릭을 사용해서 간단한 애플리케이션을 위한 프로토타입을 빠르게 만들어보겠습니다. 그리고 X-Tag를 사용해 커스텀 웹 컴포넌트를 만들 겁니다.
기술적인 배경
브릭(Brick): 엄선한 웹 컴포넌트
브릭은 모듈 형태로 재사용할 수 있는 UI 컴포넌트의 집합입니다. 컴포넌트는 적응형( adaptive), 반응형(responsive) 애플리케이션과 ‘모바일 퍼스트‘를 실현하기 위한 웹 기반의 애플리케이션 개발을 위한 최선의 선택을 제공하기 적합하게 설계되었습니다. 이런 철학과 디자인 패턴은 넓은 범위의 디바이스를 수용할 수 있습니다. 브릭 컴포넌트는 모바일 앱을 위한 것이 아니라 모던 앱을 위한 것입니다.
브릭은 일종의 라이브러리입니다. 하지만 엄선된 웹 컴포넌트의 집합이라고 해도 무리가 없습니다.
브릭에 포함된 컴포넌트는 HTML에서 일반적인 태그처럼 사용합니다. 그리고 보통의 요소들처럼 CSS를 사용해 스타일을 적용할 수 있습니다. 필요한 경우에는 브릭 컴포넌트를 사용하기 위한 개별적인 API도 제공합니다. 컴포넌트를 다루는 것은 진짜 블록을 쌓는 것과 같습니다. 레고(Lego)를 좋아한다면 브릭도 맘에 들겁니다.
브릭 웹 컴포넌트는 X-Tag 커스텀 요소 폴리필(polyfill. 구 버전 웹브라우저에서 지원되지 않는 HTML5/CSS3 등의 기능을 구현하기 위한 플러그인)을 사용해 만듭니다.
X-Tag는 무엇인가요?
X-Tag는 브라우저에서 웹 컴포넌트의 일부(나중에는 모든) 기능이 동작하게 만드는 라이브러리입니다. 특히 X-Tag는 폴리필을 지원하는 커스텀 요소를 만드는 것에 초점을 맞추고 있습니다. 이렇게 하면 자체적으로 정의한 문법과 요소 규격 API를 사용해 DOM을 확장할 수 있습니다.
브릭에 포함된 컴포넌트를 사용한다는 것은 X-Tag 라이브러리를 사용해 만든 웹 컴포넌트를 사용하는 것입니다. 브릭은 X-Tag의 코어를 이미 포함하고 있습니다. 그래서 브릭을 사용해 나만의 커스텀 요소를 만들 때는 X-Tag를 중복해서 참조하지 않도록 주의해야 합니다. X-Tag의 모든 기능은 이미 브릭에 포함되어 있으니깐요.
데모 프로젝트 파일 내려받기
데모 프로젝트 파일을 내려받습니다. 그리고 simple-app-with-bricks 폴더에 있는 내용부터 살펴보죠.
앱에서 브릭 사용하기
<x-appbar>, <x-deck>, <x-card> 3개의 태그만 사용해서 간단한 앱을 만들어볼 겁니다. x-appbar는 애플리케이션에 필요한 메뉴바를 만들어주고 x-card는 x-deck의 자식으로 위치해 여러 화면을 전환하면서 볼 수 있습니다.
먼저 기본적인 HTML 문서를 생성하고 브릭 CSS 파일과 JS 파일과 애플리케이션에 필요한 코드(이번 예제에서는 app.css와 app.js 파일입니다)를 포함합니다.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <link rel="stylesheet" type="text/css" href="css/brick.min.css"> <link rel="stylesheet" type="text/css" href="css/app.css"> <title>Simple - Brick Demo</title> </head> <body> <!-- 브릭 컴포넌트가 위치할 곳입니다 --> <script type="text/javascript" src="js/brick.min.js"></script> <script type="text/javascript" src="js/app.js"></script> </body> </html> |
이제 브릭 요소를 추가해봅시다.
<x-appbar id="bar"> <header>Simple Brick App</header> <button id="view-prev">Previous View</button> <button id="view-next">Next View</button> </x-appbar> |
x-appbar 요소 아래에 x-deck 요소를 만들고 자식 요소로 x-card를 추가합니다. x-card에는 원하는 콘텐츠 어떤 것이든 추가할 수 있습니다.
<!-- x-appbar 바로 뒤에 x-deck 요소를 배치해주세요 --> <x-deck id="views"> <x-card> <h1>View 1</h1> <p>Hello, world!</p> </x-card> <x-card> <h1>Pick a Date</h1> <p><x-datepicker>s are a polyfill for <input type="date"></p> <x-datepicker></x-datepicker> <p>Just here to show you another tag in action!</p> </x-card> <x-card> <h1>A Random Cat To Spice Things Up</h1> <!-- 콘텐츠에 필요한 적절한 이미지를 배치합니다 --> <img src="http://lorempixel.com/300/300/cats"> </x-card> </x-deck> |
간단한 애플리케이션 만들기가 거의 끝나갑니다. 남은 것은 약간의 CSS와 자바스크립트를 추가해 멋지게 동작하게 하는 겁니다.
document.addEventListener('DOMComponentsLoaded', function() { // 브릭 컴포넌트가 처음 로딩될 때 실행할 코드를 입력합니다 // jQuery에서 사용하는 document.ready()와 비슷합니다 // x-deck과 x-appbar에 있는 2개의 버튼을 로컬 변수로 지정해줍니다 var deck = document.getElementById("views"), nextButton = document.getElementById("view-next"), prevButton = document.getElementById("view-prev"); // 버튼을 클릭했을 때 화면 전환을 처리할 수 있도록 이벤트 리스너를 추가합니다 prevButton.addEventListener("click", function(){ deck.previousCard(); }); nextButton.addEventListener("click", function(){ deck.nextCard(); }); }); |
그리고 풋내기 애플리케이션을 멋지게 보이게 만들 약간의 CSS 코드를 추가합니다.
html, body { margin: 0; padding: 0; font-family: sans-serif; height: 100%; } h1 { font-size: 100%; } x-deck > x-card { background: #eee; padding: 0.6em } |
자! 이렇게 간단한 마크업을 정의하고 약간의 코드를 추가해서 하나의 HTML 문서에서 여러 개의 뷰를 가지는 앱을 만들기 위한 기본적인 뼈대를 만들었습니다. 개발자 도구에서 마크업을 확인해보면 브릭의 커스텀 요소가 다른 HTML 요소와 사이좋게 배치된 것을 확인할 수 있습니다. 개발자 도구를 사용해 문서를 탐색하고 다루는 방식은 다른 HTML을 다루는 방법과 같습니다.
이번에는 X-Tag를 사용해서 나만의 커스텀 요소를 어떻게 만드는지 살펴봅시다.
X-Tag를 사용해 커스텀 요소(나만의 ‘브릭’) 만들기
예를 들어 사용자가 무언가를 요청하면 한 번에 하나씩만 처리해주는 방식의 모바일 애플리케이션을 가지고 있다고 생각해보죠. 아마도 이 애플리케이션은 외부 서비스를 요청하고 응답을 기다리는 구조일 겁니다. 사용자가 다음 단계로 넘어가려면 먼저 서버에서 데이터를 받아야 하는데 안타깝게도 데이터가 언제 올지 모르고 마냥 기다려야 합니다. 이런 상황에서 우리는 프로그램을 수정할 수 없고 전체적인 아키텍처 역시 변경하기 어렵다고 가정해보죠. 하지만 블로킹 문제를 해결할 수 있는 좋은 방법을 찾지 못한다면 사용자를 설득하기 어려울 수 있습니다. 주어진 상황 내에서 가장 좋은 방법을 찾아야 합니다.
그래서 우리는 사용자에게 잠시 기다리라는 메시지를 전달할 수 있는 커스텀 모달 스피너(spinner. 바람개비처럼 돌아가며 대기 상태를 표시해주는 이미지)를 만들 겁니다. 이런 기능은 사용자에게 사용 중인 앱이 적절한 시간 내에 응답을 주지 못했을 때 무슨 일이 생기고 있는지 피드백을 주는 중요한 기능입니다. 이런 메시지가 없다면 사용자는 혼란에 빠져 앱에서 나가버릴 겁니다.
x-status-hud 폴더에 포함된 데모를 원하는 형태로 바꾸어보겠습니다.
1. 커스텀 요소 등록하기
X-Tag는 커스텀 요소를 감지하고 처리하기 위한 몇 가지 이벤트를 제공합니다. X-Tag는 먼저 해당 요소가 원래 소스 문서에 포함되어 있는지 확인합니다. 그리고 innerHTML 속성값으로 추가하거나 document.createElement를 사용해서 동적으로 생성합니다. 좀 더 자세한 내용을 확인하고자 한다면 X-Tag 문서 중 Helpers 항목을 살펴보세요. 커스텀 요소를 다른 일반적인 요소처럼 사용하는 데 도움을 줄 수 있는 다양한 주제를 다루고 있습니다.
먼저 해야 할 일은 X-Tag로 만든 커스텀 요소를 등록하는 겁니다. 그래야 X-Tag가 우리가 만든 커스텀 요소를 만났을 때 어떤 일을 해야 할지 알 수 있을 겁니다. xtag.register를 호출해줍니다.
xtag.register('x-status-hud', { // 스피너의 상태를 처리하기 위해 우리가 만들 태그에 필요한 동작을 지정할 겁니다 }); |
!중요: 모든 커스텀 요소는 붙임표(-)을 포함해야 합니다. 왜 그러냐고요? 표준 HTML 요소들은 붙임표를 가지고 있지 않습니다. 붙임표를 사용하면 기존 이름과 충돌하거나 겹치는 일이 없겠죠. 하지만 접두사로 ‘x-‘를 사용해야만 하는 것은 아닙니다. 다만 관습적으로 브릭 에코시스템 내 X-Tag로 만든 컴포넌트에서 사용했을 뿐입니다. W3C에서 커스텀 요소를 위한 스펙을 만들기 위한 초기 논의에서 모든 커스텀 요소는 접두사 ‘x-‘를 붙이라고 권장했지만, 나중에 수정된 스펙에서는 이런 제약이 사라졌습니다. 만들어진 요소 이름이 ‘bacon-eggs’나 ‘adorable-kitten’이라고 해도 문제가 없습니다. 물론 이름을 정할 때는 만든 요소가 무슨 기능을 하는지 또는 어떻게 동작하는지를 설명해줄 수 있는 이름이 좋겠죠.
커스텀 요소를 등록할 때 어떤 HTML 요소를 기본 요소로 설정할지 지정할 수 있습니다. 그리고 다른 요소의 기능을 참조할 수 있도록 특정 프로토타입을 지정할 수도 있습니다. 아래와 같이 선언합니다.
xtag.register('x-superinput', { extends: 'input', prototype: Object.create(HTMLInputElement.prototype) }); |
해당 요소를 만들면서 속성값을 정확하게 지정할 필요는 없습니다. 이런 속성들은 나중에 컴포넌트의 기능을 확장하거나 원하는 수준으로 조작하길 원할 때 유용하게 사용할 수 있습니다.
2. 요소의 생명주기
커스텀 요소는 특정한 시점에 작동하는 이벤트를 가지고 있습니다. 요소(element)가 만들어지거나 DOM에 추가되거나 DOM에서 삭제될 때 또는 특정 항목값(attributes)이 설정될 때 이벤트가 동작합니다. 이런 이벤트에 특정한 동작을 추가하거나 아무것도 처리하지 않을 수 있습니다.
lifecycle:{ created: function(){ // 컴포넌트가 처음 만들어지거나 처리될 때 한 번만 실행합니다 }, inserted: function(){ // 컴포넌트가 DOM 구조에 추가될 때마다 실행합니다 }, removed: function(){ // 요소가 DOM 구조에서 삭제될 때마다 실행합니다 }, attributeChanged: function(){ // 모든 항목값이 설정될 때마다 실행합니다 } |
이벤트 중에서 created
이벤트를 사용해보죠. 이벤트가 작동할 때 지정된 코드를 자식 요소로 추가합니다.
xtag.register('x-status-hud', { lifecycle: { created: function(){ this.xtag.textEl = document.createElement('strong'); this.xtag.spinnerContainer = document.createElement('div'); this.xtag.spinner = document.createElement('div'); this.xtag.spinnerContainer.className = 'spinner'; this.xtag.spinnerContainer.appendChild(this.xtag.spinner); this.appendChild(this.xtag.spinnerContainer); this.appendChild(this.xtag.textEl); } } // 요소에 필요한 추가적인 설정은 이곳에서 처리합니다 }); |
3. 커스텀 메소드 추가하기
스피너를 화면에 보여주거나 감출 수 있게 조작하는 기능이 필요해 보입니다. 이를 위해서 컴포넌트에 필요한 메소드를 추가해야 합니다. 아래와 같이 약간의 함수를 추가해봅니다. toggle()만 추가해도 충분하지만, 개별적인 동작을 수행하는 hide()와 show()도 같이 추가합니다.
xtag.register('x-status-hud', { lifecycle: { created: function(){ this.xtag.textEl = document.createElement('strong'); this.xtag.spinnerContainer = document.createElement('div'); this.xtag.spinner = document.createElement('div'); this.xtag.spinnerContainer.className = 'spinner'; this.xtag.spinnerContainer.appendChild(this.xtag.spinner); this.appendChild(this.xtag.spinnerContainer); this.appendChild(this.xtag.textEl); } }, methods: { toggle: function(){ this.visible = this.visible ? false : true; }, show: function (){ this.visible = true; }, hide: function (){ this.visible = false; } } |
4. 커스텀 접근자 추가하기
커스텀 요소에서 속성과 관련해 중요하게 기억해야 할 것이 있습니다. 바로 항목값을 미리 연결해놓지 않는다는 점입니다. 항목값이 복잡하게 연결될 수도 있고 명확하게 대응하는 값이 없을 수도 있기 때문에 그렇습니다.
특정 속성과 항목값이 연결되는 것을 선호한다면 아무것도 없는 오브젝트 리터럴을 attribute에 담습니다. 아래 예제에서는 label 항목에 설정된 내용을 확인해볼 수 있습니다.
xtag.register('x-status-hud', { lifecycle: { created: function(){ this.xtag.textEl = document.createElement('strong'); this.xtag.spinnerContainer = document.createElement('div'); this.xtag.spinner = document.createElement('div'); this.xtag.spinnerContainer.className = 'spinner'; this.xtag.spinnerContainer.appendChild(this.xtag.spinner); this.appendChild(this.xtag.spinnerContainer); this.appendChild(this.xtag.textEl); } }, methods: { toggle: function(){ this.visible = this.visible ? false : true; }, show: function (){ this.visible = true; }, hide: function (){ this.visible = false; } }, accessors: { visible: { attribute: { boolean: true } }, label: { attribute: {}, set: function(text) { this.xtag.textEl.innerHTML = text; } } } }); // 태그 선언 끝 |
항목(attributes)과 속성(properties)이라는 개념이 혼란스럽다면 StackOverflow에 올라온 답변을 참고하세요. 물론 완전 다른 분야(jQuery)에 대한 이야기지만 항목과 속성 개념을 이해하는 데 큰 도움이 될 겁니다.
컴포넌트 마무리하기
예제로 작성한 코드에서는 커스텀 요소가 이미 로딩되었다고 가정하고 있습니다. 이벤트 리스너를 추가하고 컴포넌트 로딩이 끝났을 때 동작하도록 처리하고 있습니다. 해당 기능은 jQuery에서 사용하는 document.ready와 같습니다.
<script type="text/javascript"> document.addEventListener('DOMComponentsLoaded', function(){ // 필요하다면 아래 내용은 수정할 수 있습니다 // 여기에서는 테스트를 위해 실제 동작하는 것처럼 보이게 합니다 var testHUD = document.getElementById("test"); testHUD.label = "Please Wait..."; testHUD.show(); }, false); </script> |
이제 끝났습니다. 클라이언트에서 사용하는 코드에 모듈화되어 재사용 가능한 위젯을 만들었습니다. 이를 가지고 더 멋진 것도 만들어볼 수 있을 겁니다. 물론 이게 끝은 아니죠.
해당 요소를 향상할 수 있는 아이디어가 몇 가지 있습니다.
attributeChanged
이벤트가 작동할 때 컴포넌트의 크기를 텍스트에 맞게 다시 계산해주는 겁니다. 이렇게 하면 표시하지 못하는 글자를 생략하는 것보다 더 좋아 보일 겁니다.- 개발자가 CSS 스피너 대신 움직이는 GIF 파일과 같은 이미지 파일을 직접 지정할 수 있다면 더 나은 사용자 경험을 제공할 수 있을 겁니다.
- 스피너 대신 실제 상태를 표시해주는 상태표시바를 만들 수 있다면 사용자가 작업 진행 상태에 대해 좀 더 많은 정보를 얻을 수 있을 겁니다.
이 외에 여러분의 창의력을 발휘해서 멋지고 실용적인 기능을 추가해보기 바랍니다.
이번 예제를 따라오면서 커스텀 요소를 사용할 수 있도록 DOM을 확장하는 방법은 미리 알고 있어야 합니다. 혹 따라오기가 힘들다면 댓글로 남겨주세요(영문 블로그에 직접!!). 필요한 도움을 드릴 수 있도록 하겠습니다. 예제를 잘 따라왔다면 깃헙 저장소에 만든 코드를 올려놓고 브릭과 X-Tag를 어떻게 활용했는지 알려주세요. 그럼 즐코딩!
작성자: Joon-Ha Lee
左手はそえるだけ
댓글이 없습니다.