Windows 8 빌드
개발자용 Windows 스토어 블로그
Visual Studio blog
IE 블로그
Windows 팀 블로그
Windows Live 블로그 인사이드
Windows 8 Release Preview 다운로드
개발 센터 - 메트로 스타일 앱
Windows 개발자 팔로우
Windows 컨퍼런스 빌드
Windows Metro 스타일 앱
JavaScript로 Windows 스토어 앱을 작성한 경험이 있는 개발자라면 대부분 JavaScript용 Windows 라이브러리(WinJS)를 접해 보았을 것입니다. 이 라이브러리에서 제공하는 CSS 스타일, JavaScript 컨트롤 및 유틸리티를 사용하면 Windows 스토어용 UX 지침을 충족하는 앱을 신속하게 제작할 수 있습니다. WinJS에서 제공하는 유틸리티 중에는 앱의 사용자 지정 컨트롤을 만드는 데 유용한 함수 집합이 있습니다.
WinJS에서 제공하는 라이브러리 함수는 단지 하나의 선택 사항일 뿐이며, 개발자는 자신이 원하는 다른 모든 패턴이나 라이브러리를 사용하여 JavaScript 컨트롤을 만들 수 있습니다. WinJS를 사용하여 컨트롤을 만들 때의 가장 큰 이점은 라이브러리의 다른 컨트롤과 일관되게 작동하는 자신만의 컨트롤을 제작할 수 있다는 점입니다. 자신만의 컨트롤을 개발하는 작업의 패턴은 WinJS.UI 네임스페이스의 다른 모든 컨트롤을 만드는 방법과 동일합니다.
이 글은 설정 가능한 옵션, 이벤트 및 공용 메서드에 대한 지원을 통해 자신만의 컨트롤을 제작하는 방법에 대해 설명합니다. 한편, 이와 동일한 주제인 XAML 컨트롤 개발에 관심이 많은 분들을 위해 이에 대한 글도 곧 게재할 예정입니다.
우선 페이지에 WinJS 컨트롤을 삽입하는 방법을 다시 알아보겠습니다. 여기에는 두 가지 방법, 즉 간섭되지 않는 방식으로 JavaScript만 사용하는 명령과 HTML 요소의 추가 특성을 사용하여 자신의 HTML 페이지에 컨트롤 삽입하는 선언이 있습니다. 선언은 도구 상자에서 컨트롤 끌기와 같은 디자인 타임 경험을 제공합니다. 자세한 내용은 WinJS 컨트롤 및 스타일 추가에 대한 MSDN 빠른 시작을 참조하세요.
이번 글에서는 WinJS에서의 선언적 프로세싱 모델의 장점을 취하는 JavaScript 컨트롤의 생성 방법에 대해 설명하겠습니다. 페이지에 선언적으로 컨트롤을 삽입하려면 다음과 같은 일련의 절차를 따르세요.
<script src="//Microsoft.WinJS.1.0/js/base.js"></script><script src="//Microsoft.WinJS.1.0/js/ui.js"></script>
<script src="js/hello-world-control.js"></script>
<div data-win-control="Contoso.UI.HelloWorld" data-win-options="{blink: true}"></div>
이제 아주 단순한 컨트롤을 하나 만들어 보겠습니다. Hello World 컨트롤. 이 컨트롤을 정의하는 데 사용되는 JavaScript는 다음과 같습니다. 프로젝트에 다음과 같은 코드의 새 파일을 만들고 이름을 hello-world-control.js라고 붙입니다.
function HelloWorld(element) { if (element) { element.textContent = "Hello, World!"; }};WinJS.Utilities.markSupportedForProcessing(HelloWorld);
그런 다음 페이지의 body에 아래의 마크업을 사용하여 컨트롤을 삽입합니다.
<div data-win-control="HelloWorld"></div>
앱을 실행하면 컨트롤이 로드되어 페이지의 body에 “Hello, World!” 텍스트가 표시되는 것을 확인할 수 있습니다.
WinJS에만 해당되는 단 한 줄의 이 코드는 선언적 프로세싱으로 사용하도록 호환되는 코드를 표시하는 WinJS.Utilities.markSupportedForProcessing으로의 호출입니다. 이것이 바로 WinJS가 페이지에 콘텐츠를 삽입하는데 있어 믿을 만한 코드라고 할 수 있는 이유입니다. 이에 대해 자세한 내용은 WinJS.Utilities.markSupportedForProcessing 함수에 대한 MSDN 설명서를 참조하세요.
앞에서 WinJS를 실제로 사용하지 않고 선언적 컨트롤을 만드는 방법을 설명했습니다. 그럼 이제, WinJS를 사용하지 않고 구현한 아래의 코드 조각을 살펴보겠습니다. 이러한 코드 조각은 이벤트, 설정 가능한 옵션 및 공용 메서드로 구성된 보다 복잡한 컨트롤입니다.
(function (Contoso) { Contoso.UI = Contoso.UI || {}; Contoso.UI.HelloWorld = function (element, options) { this.element = element; this.element.winControl = this; this.blink = (options && options.blink) ? true : false; this._onblink = null; this._blinking = 0; element.textContent = "Hello, World!"; }; var proto = Contoso.UI.HelloWorld.prototype; proto.doBlink = function () { var customEvent = document.createEvent("Event"); customEvent.initEvent("blink", false, false); if (this.element.style.display === "none") { this.element.style.display = "block"; } else { this.element.style.display = "none"; } this.element.dispatchEvent(customEvent); }; proto.addEventListener = function (type, listener, useCapture) { this.element.addEventListener(type, listener, useCapture); }; proto.removeEventListener = function (type, listener, useCapture) { this.element.removeEventListener(type, listener, useCapture); }; Object.defineProperties(proto, { blink: { get: function () { return this._blink; }, set: function (value) { if (this._blinking) { clearInterval(this._blinking); this._blinking = 0; } this._blink = value; if (this._blink) { this._blinking = setInterval(this.doBlink.bind(this), 500); } }, enumerable: true, configurable: true }, onblink: { get: function () { return this._onblink; }, set: function (eventHandler) { if (this._onblink) { this.removeEventListener("blink", this._onblink); this._onblink = null; } this._onblink = eventHandler; this.addEventListener("blink", this._onblink); } } }); WinJS.Utilities.markSupportedForProcessing(Contoso.UI.HelloWorld);})(window.Contoso = window.Contoso || {});
많은 개발자들이 이와 같은 방식(익명 함수, 생성자 함수, 속성, 사용자 지정 이벤트 사용)으로 컨트롤을 만들고 있습니다. 이 방식이 편하다면 그렇게 하셔도 됩니다. 하지만 이러한 코드가 다소 혼란스럽게 느껴지는 분들도 많이 있을 것입니다. 상당수의 웹 개발자들이 테크닉이 가미된 코드에 익숙하지 않기 때문입니다. 하지만 라이브러리는 이러한 코드를 작성할 때 발생하는 혼란을 없애주므로 이에 비해 훨씬 편리합니다.
또한 WinJS와 기타 라이브러리는 가독성을 높여주는 것은 물론, 많은 미묘한 문제까지 관리해 주기 때문에 개발자는 이러한 문제(프로토타입, 속성, 사용자 지정 이벤트를 효율적으로 사용)를 신경 쓸 필요가 없습니다. 또한 메모리 사용량을 최적화하고 개발자의 일반적인 실수를 방지해 줍니다. WinJS는 하나의 예일 뿐, 선택은 개발자의 몫입니다. 라이브러리가 어떻게 도움이 되는가에 대한 확실한 예로, 이 글을 모두 읽은 다음 이 단원의 코드를 다시 살펴보고, 이 글의 마지막 부분에서 WinJS 유틸리티를 사용해 구현한 동일한 컨트롤을 이전에 구현한 컨트롤과 비교해 보시기 바랍니다.
다음은 WinJS를 사용하여 JavaScript 컨트롤을 만들 때 가장 단순하면서도 모범 사례로 꼽히는 패턴입니다.
(function () { "use strict"; var controlClass = WinJS.Class.define( function Control_ctor(element) { this.element = element || document.createElement("div"); this.element.winControl = this; this.element.textContent = "Hello, World!" }); WinJS.Namespace.define("Contoso.UI", { HelloWorld: controlClass });})();
또한 다음과 같이 페이지에 컨트롤을 선언적으로 삽입할 수 있습니다.
<div data-win-control="Contoso.UI.HelloWorld"></div>
어떤 면에서 특히 WinJS를 처음 접하는 경우 이 방법이 익숙하지 않을 수도 있으니 차근차근 알아보도록 하겠습니다.
(function () {…})();
좀 더 흥미로운 예를 들기 위해 컨트롤에 설정 가능한 옵션에 대한 지원을 추가해 보겠습니다. 여기서 사용자가 콘텐츠를 깜박이게 할 수 있는 옵션을 추가해 보도록 하죠.
var controlClass = WinJS.Class.define( function Control_ctor(element, options) { this.element = element || document.createElement("div"); this.element.winControl = this; // Set option defaults this._blink = false; // Set user-defined options WinJS.UI.setOptions(this, options); element.textContent = "Hello, World!" }, { _blinking: 0, blink: { get: function () { return this._blink; }, set: function (value) { if (this._blinking) { clearInterval(this._blinking); this._blinking = 0; } this._blink = value; if (this._blink) { this._blinking = setInterval(this._doBlink.bind(this), 500); } } }, _doBlink: function () { if (this.element.style.display === "none") { this.element.style.display = "block"; } else { this.element.style.display = "none"; } }, }); WinJS.Namespace.define("Contoso.UI", { HelloWorld: controlClass });
이번에는 페이지에 컨트롤을 삽입할 때 data-win 옵션 특성을 사용하여 깜박임 옵션을 설정할 수 있습니다.
옵션을 위한 지원을 추가하기 위해 코드에 다음과 같은 변경 사항을 적용했습니다.
이 예제에서 가장 어려운 부분은 WinJS.UI.setOptions()로의 호출입니다. 유틸리티 함수인 setOptions는 옵션 개체의 각 필드를 순환하며 setOptions에 대한 최초 매개 변수인 대상 개체의 동일 이름의 필드에 그 값을 할당합니다.
이 예제에서는 'blink' 필드에 대해 true 값을 전달하는 win-control에 대한 data-win 옵션 인수를 통해 옵션 개체를 구성합니다. 생성자 함수에서 setOptions()로의 호출은 이후 'blink'라 명명된 필드를 찾게 되며, 그 값을 컨트롤 개체에서의 동일한 이름의 필드에 복사합니다. 이와 같이 blink라 명명된 속성을 정의했으며, 이 속성은 setter 함수를 제공합니다. setter 함수는 setOptions()에 의해 호출되며 컨트롤의 _blink 멤버를 설정합니다.
이번에 구현한 oh-so-useful blink 옵션을 가지고 필요할 때마다 깜박임 기능을 사용할 수 있도록 이벤트 지원을 추가해 보겠습니다.
var controlClass = WinJS.Class.define( function Control_ctor(element, options) { this.element = element || document.createElement("div"); this.element.winControl = this; // Set option defaults this._blink = false; // Set user-defined options WinJS.UI.setOptions(this, options); element.textContent = "Hello, World!" }, { _blinking: 0, _blinkCount: 0, blink: { get: function () { return this._blink; }, set: function (value) { if (this._blinking) { clearInterval(this._blinking); this._blinking = 0; } this._blink = value; if (this._blink) { this._blinking = setInterval(this._doBlink.bind(this), 500); } } }, _doBlink: function () { if (this.element.style.display === "none") { this.element.style.display = "block"; } else { this.element.style.display = "none"; } this._blinkCount++; this.dispatchEvent("blink", { count: this._blinkCount }); }, }); WinJS.Namespace.define("Contoso.UI", { HelloWorld: controlClass }); // Set up event handlers for the control WinJS.Class.mix(Contoso.UI.HelloWorld, WinJS.Utilities.createEventProperties("blink"), WinJS.UI.DOMEventMixin);
앞에서와 같이 페이지에 컨트롤을 삽입합니다. 나중에 요소를 검색할 수 있도록 이 요소에 ID를 추가했습니다.
<div id="hello-world-with-events" data-win-control="Contoso.UI.HelloWorld" data-win-options="{blink: true}"></div>
이러한 변경을 통해 이제 'blink' 이벤트를 수신할 수 있는 이벤트 수신기를 연결할 수 있습니다. (참고: 이 예제에서는 document.getElementById를 $로 이름 붙였습니다.)
$("hello-world-with-events").addEventListener("blink", function (event) { console.log("blinked element this many times: " + event.count); });
이 코드를 실행하면 Visual Studio의 JS Console에 매 500밀리초 마다 메시지가 출력됩니다.
이러한 동작을 지원하기 위해 세 가지 변경 사항이 컨트롤에 적용되었습니다.
여기서 컨트롤의 생성자에 this.element를 설정한 경우 dispatchEvent()only로의 호출이 작동한다는 점에 주목하세요. 이벤트 mix-in의 내부에서는 DOM의 요소에 액세스할 것을 요구합니다. 앞서 언급했던 사례 중 하나는 컨트롤 개체에 요소 멤버가 요구된다는 것입니다. 이로써 이벤트가 DOM Level 3 이벤트 패턴의 페이지에서 상위 요소를 포함할 수 있습니다.
컨트롤의 마지막 변경 사항으로, 깜박임 기능이 언제든지 실행되도록 호출할 수 있는 공용 doBlink() 함수를 추가합니다.
var controlClass = WinJS.Class.define( function Control_ctor(element, options) { this.element = element || document.createElement("div"); this.element.winControl = this; // Set option defaults this._blink = false; // Set user-defined options WinJS.UI.setOptions(this, options); element.textContent = "Hello, World!" }, { _blinking: 0, _blinkCount: 0, blink: { get: function () { return this._blink; }, set: function (value) { if (this._blinking) { clearInterval(this._blinking); this._blinking = 0; } this._blink = value; if (this._blink) { this._blinking = setInterval(this.doBlink.bind(this), 500); } } }, doBlink: function () { if (this.element.style.display === "none") { this.element.style.display = "block"; } else { this.element.style.display = "none"; } this._blinkCount++; this.dispatchEvent("blink", { count: this._blinkCount }); }, }); WinJS.Namespace.define("Contoso.UI", { HelloWorld: controlClass }); // Set up event handlers for the control WinJS.Class.mix(Contoso.UI.HelloWorld, WinJS.Utilities.createEventProperties("blink"), WinJS.UI.DOMEventMixin);
이는 단지 형식적인 변경이며, _doBlink 함수의 이름을 doBlink로 바꿀 수 있습니다.
JavaScript를 통해 doBlink() 함수를 호출하려면 컨트롤에 대한 개체에 레퍼런스가 필요합니다. 컨트롤을 명령적으��� 생성한 경우 이미 레퍼런스가 있을 것입니다. 선언적 프로세싱을 사용하는 경우 컨트롤에 대한 HTML 요소의 winControl 속성을 사용하여 컨트롤 개체를 액세스할 수 있습니다. 예를 들어, 이전과 동일한 마크업이라 가정할 경우 다음을 통해 컨트롤 개체에 액세스할 수 있습니다.
$("hello-world-with-events").winControl.doBlink();
지금까지 다음과 같은 사항들을 구현하기 위해 필요한 컨트롤의 가장 일반적인 측면에 대해 알아보았습니다.
JavaScript로 간단한 사용자 지정 컨트롤을 만들 때 본 자습서가 많은 도움이 되기를 바랍니다. 컨트롤 제작 관련 질문이 있는 경우 Windows 개발자 센터의 포럼을 통해 문의하세요. 아울러 XAML 개발자를 위해 XAML 컨트롤 개발에 대한 동일한 주제의 글이 곧 게재될 예정이니 여러분의 많은 관심을 바랍니다.
Microsoft Visual Studio 프로그램 매니저, Jordan Matthiesen