58
WEB COMPONENTS & POLYMER 2014.11.04 박재성

Web Components & Polymer

Embed Size (px)

Citation preview

Page 1: Web Components & Polymer

WEB COMPONENTS&

POLYMER2014.11.04

박재성

Page 3: Web Components & Polymer

일반적인 웹 개발은?

Page 4: Web Components & Polymer

WEB COMPONENTS?재사용 가능한 컴포넌트를 만들 수 있는 표준 기술들의 모음

Custom Elements : 커스텀 태그를 통한 요소 생성

HTML Imports : HTML 페이지 로딩

HTML Templates : 템플릿

Shadow DOM : DOM과 스타일의 캡슐화

Page 5: Web Components & Polymer

CUSTOM ELEMENTS커스텀 태그를 통한 요소 생성

새로운 HTML 요소를 생성

다른 요소를 확장해 생성가능

단일 태그에 커스텀 기능의 묶음 가능

기존 DOM 요소의 API를 확장

Page 6: Web Components & Polymer

태그명에 '-'(dash)는 반드시 포함해야 한다.

CUSTOM ELEMENTS기본 사용방법

// document.registerElement()를 통해 등록 var NTag = document.registerElement('n-tag');

// 또는 다음과 같이 특정 HTML 인터페이스를 사용해 등록할 수도 있다. // custom element는 기본적으로 'HTMLElement'를 상속한다. var NTag = document.registerElement('n-tag', { prototype: Object.create(HTMLElement.prototype) });

// 문서에 추가 document.body.appendChild(new NTag());

Page 7: Web Components & Polymer

CUSTOM ELEMENTSvar NTag = document.registerElement('n-tag', { prototype: Object.create(HTMLElement.prototype)});

document.body.appendChild(new NTag());

//alert(document.querySelector("n-tag"));//alert(document.getElementsByTagName("n-tag")[0].__proto__.constructor +"");

JavaScript

Page 8: Web Components & Polymer

CUSTOM ELEMENTS기존 요소를 확장

// <button> 태그를 확장 var XButton = document.registerElement('x-button', { prototype: Object.create(HTMLButtonElement.prototype), extends: 'button' });

확장된 요소들은 type extension custom elements라 불리우며,"element X is a Y"와 같이 사용

<button is="x-button"></button>

Page 9: Web Components & Polymer

CUSTOM ELEMENTS속성과 메서드 추가

// 1. HTML 객체 생성var XTestProto = Object.create(HTMLElement.prototype);

// 2. ‘hi’ 메서드 추가XTestProto.hi = function() { alert('안녕하세요.'); };

// 3. 읽기 전용 ‘bar’ 속성 설정Object.defineProperty(XTestProto, "bar", {value: 5});

// 4. <x-test> 태그 등록. 태그명에는 '-'가 꼭 포함되어야 함.var XTest = document.registerElement('x-test', { prototype: XTestProto });

/*var XTestProto = document.registerElement('x-test', {

JavaScript

HTML

Page 10: Web Components & Polymer

CUSTOM ELEMENTSLifecycle callback

콜백 발생시점

createdCallback 인스턴스가 생성될 때

attachedCallback 생성된 인스턴스가 문서에 추가될때

detachedCallback 인스턴스가 문서에서 제거될 때

attributeChangedCallback

(attrName, oldVal, newVal)

속성이 변경될 때(추가/삭제/수정)

var proto = Object.create(HTMLElement.prototype);

// 콜백 등록 proto.createdCallback = function() { ... }; proto.attachedCallback = function() { ... };

var XFoo = document.registerElement('x-foo', {prototype: proto});

Page 11: Web Components & Polymer

CUSTOM ELEMENTS컨텐츠가 포함된 custom element

var NTagProto = Object.create(HTMLElement.prototype);

NTagProto.createdCallback = function() { this.innerHTML = "<b>반갑습니다~</b>";};

var NTagHi = document.registerElement('n-tag-hi', { prototype: NTagProto });

JavaScript

HTML

Page 12: Web Components & Polymer

CUSTOM ELEMENTS아직 요소의 등록이 제대로 되지 않은 상태인 경우,

FOUC* 상태로 페이지가 렌더링될 수 있다.

:unresolved pseudo class를 사용해 이를 방지할 수 있다.

x-test { ... } x-test:unresolved { opacity: 0; }

*FOUC(Flash Of Unstyled Content) - http://en.wikipedia.org/wiki/Flash_of_unstyled_content

Page 13: Web Components & Polymer

HTML IMPORTSHTML 페이지를 로딩

JS/HTML/CSS를 묶음 형태로 사용 → 단일 URL로 호출

HTML Import를 통한 추가되는 컴포넌트들은 중복되는 경우라도 호출,

파싱 및 실행은 단 한 번만 수행

Import 내의 스크립트는 메인 문서의 파싱을 블럭하지 않음

스크립트는 import시 실행되나, 다른 요소(마크업,CSS 등)들은

메인 페이지에 추가되는 시점에 활성화

Page 14: Web Components & Polymer

HTML IMPORTS기본 사용방법

<link rel="import" href="/path/file_name.html">

다른 도메인의 파일을 임포트 하기 위해선CORS가 활성화 되어 있어야 한다.

Page 15: Web Components & Polymer

HTML IMPORTS이벤트 (load & error)

<link rel="import" href="/path/file_name.html" onload="handleLoad(event)" onerror="handleError(event)">

브라우저가 <link rel="import"> 태그를 만나면즉시 로딩하기 때문에 이벤트 핸들러는import 태그 이전에 선언되어야 한다.

var el = document.createElement("link");el.rel = "import";el.href="./resource/blank.html";

el.onload = function(e) { // TODO

};

document.head.appendChild(el);

JavaScript

Page 16: Web Components & Polymer

HTML IMPORTSimport된 컨텐츠의 활용

import가 수행된다고 해서 그 지점에 컨텐츠가포함되는 형태는 아니며, 브라우저가 해당 파일을

파싱하고 사용할 수 있도록 준비되는 것.

import 속성을 사용해 컨텐츠 문서에 접근할 수 있다.

// import된 문서를 얻는다.var contentDoc = document.querySelector('link[rel="import"]').import;

// 컨텐츠 문서에서 셀렉터를 통해 요소를 선택한다.//alert(contentDoc.querySelector("*").outerHTML);

JavaScript

Page 17: Web Components & Polymer

main.html

import.html

HTML IMPORTS페이지의 동작

<link rel="import" href="import.html">

<link rel="stylesheet" href="common.css"><style>

</style><script>

</script>

/* 이 블럭내의 스타일은 기본적으로 main.html에 반영됨 */

// import.html 문서를 가리킴. 즉, 현재 파일var importDoc = document.currentScript.ownerDocument;

// main.html 문서를 가리킴. 즉, 현재 파일을 import 하는 파일var mainDoc = document;

// import.html 문서내의 스타일시트를 main.html에 포함하고자 한다면, 아래와 같이 별도 추가작업이 필요var styles = importDoc.querySelector('link[rel="stylesheet"]');mainDoc.head.appendChild(styles.cloneNode(true));

Page 18: Web Components & Polymer

HTML IMPORTS기억할 점

스크립트는 window 문맥에서 실행import는 메인 페이지의 파싱을 블럭하지는 않지만,렌더링은 블럭

import되는 페이지에 style이 포함될 수 있으므로, 브라우저는 FOUC를 방지하기 위해 렌더링을 블럭

비동기적으로 import 하고자 한다면 async 속성을 추가<link rel="import" href="/path/some.html" async>

import 파일들은 일반적인 리소스와 마찬가지로 브라우저 캐싱됨

Page 19: Web Components & Polymer

HTML IMPORTS <!-- main.html --> <head><link rel="import" href="some.html"></head> <body> <script>

</script> </body>

var link = document.querySelector('link[rel="import"]'); var content = link.import; var el = content.querySelector('div'); document.body.appendChild(el.cloneNode(true));

<!-- some.html --> <script> </script> <div> <style> </style> <h3>HTML Imports</h3> <p>안녕하세요. 반갑습니다~!</p> </div>

alert("파일이 import 되었습니다.");

h3 { color: red; }

Page 20: Web Components & Polymer

HTML IMPORTS// TODO : import "./resource/some.html"

/*var link = document.querySelector('link[rel="import"]');var content = link.import;var el = content.querySelector('div');document.getElementById("content02").appendChild(el.cloneNode(true));*/

JavaScript

Page 21: Web Components & Polymer

HTML TEMPLATES재사용을 위한 템플릿

비활성화 상태의 복제 가능한 DOM chunk

새로운 태그 : <template> … </template>

태그 내의 태그들은 사용되기 전까진 파싱은 되나 렌더링되지 않음

컨텐츠는 클론/사용 되기전까진 비활성

페이지의 일부분이 아님

중첩된 <template>은 동작하지 않음

Page 22: Web Components & Polymer

HTML TEMPLATES // 1. content 속성을 사용해 템플릿의 노드(#document-fragment)에 접근할 수 있다. var content = document.getElementById("count").content;

// 2. 템플릿내의 DOM에 대한 작업을 한다. var span = content.querySelector('span'); span.textContent = parseInt(span.textContent, 10) + 1;

// 3. 메인 DOM에 document.importNode()를 통해 추가한다. document.getElementById("content03").appendChild( document.importNode(content, true));

<!-- 템플릿 --> <template id="count"> <div>Template used: <span>0</span></div> <script> </script> </template>

alert('클릭하셨네요!');

Page 23: Web Components & Polymer

HTML TEMPLATES// 1. content 속성을 사용해 템플릿의 노드(#document-fragment)에 접근할 수 있다.var content = document.getElementById("count").content;

// 2. 템플릿내의 DOM에 대한 작업을 한다.var span = content.querySelector('span');span.textContent = parseInt(span.textContent, 10) + 1;

// 3. 메인 DOM에 document.importNode()를 통해 추가한다.document.getElementById("content03").appendChild(document.importNode(content, true));

JavaScript

<template id="count"> <div>Template used: <span>0</span></div> <script>alert('클릭하셨네요!');</script></template>

HTML

Page 24: Web Components & Polymer

SHADOW DOMDOM과 스타일의 캡슐화

별도의 스코프를 갖는 DOM

새로운 root node → "shadow root"

shadow root를 가지고 있는 요소는 "shadow host"라고 불리움

shadow host의 컨텐츠는 렌더링되지 않으며, 대신 shadow root의 컨텐츠가 렌더링됨

Polymer에서 생성하는 모든 요소들은 shadow DOM으로 처리

<video src="http://v2v.cc/~j/theora_testsuite/320x240.ogg" controls="controls"></video>

0:03

Page 25: Web Components & Polymer

SHADOW DOM기본 사용방법

// shadow root를 포함할 host를 얻는다.var host = document.getElementById('shadow_btn');

// shadow root를 생성한다.var root = host.createShadowRoot();root.textContent = '안녕하세요.';

JavaScript

<button id="shadow_btn">Hello, world!</button> HTML

Page 26: Web Components & Polymer

SHADOW DOMPesudo class

Shadow DOM에 정의된 css 스타일은기본적으로 ShadowRoot 스코프를 갖는다.

:host / :host(selector)host 요소를 의미 (ShadowRoot context내에서만 사용가능)

var host = document.getElementById('shadow_btn2');var root = host.createShadowRoot();

// ShadowRoot를 host 하는 요소를 가리킨다.root.innerHTML = '<style>' + ':host { text-transform: uppercase; }' + '</style>' + '<content></content>';

JavaScript

My Button

Page 27: Web Components & Polymer

SHADOW DOMPesudo class

:host-context(selector)host의 조상 요소들중 selector와 매칭되는 경우에만 host에 스타일을 지정

var host = document.getElementById('shadow_btn3');var root = host.createShadowRoot();

// ShadowRoot를 host 하는 조상 요소들 중 매칭되는 경우에만 스타일을 지정root.innerHTML = '<style>' + ':host-context(.host-context) { text-transform: uppercase; }' + '</style>' + '<content></content>';

JavaScript

<div class="host-context"> <button id="shadow_btn3" class="red">My Button</button></div>

HTML

Page 28: Web Components & Polymer

SHADOW DOMPesudo element

::shadowShadowRoot를 갖는 요소

var host = document.getElementById('shadow_div');var root = host.createShadowRoot();

root.innerHTML = '<span>Shadow DOM</span>' + '<content></content>';

JavaScript

<style>#shadow_div::shadow span { color: red }</style>

<div id="shadow_div"> <span>Light DOM</span></div>

HTML

Page 29: Web Components & Polymer

SHADOW DOMCombinator

/deep/::shadow 보다 강력하며, 모든 shadow boundary를 가로질러 임의의 요소와 매칭

// shadow 트리내의 모든 .hello 클래스 요소와 매칭body /deep/ .hello { /* 스타일 */ }

<style>/* 네이티브 요소 스타일링 */video /deep/ input[type="range"] { background: hotpink; }</style>

HTML

0:03

Page 30: Web Components & Polymer

SHADOW DOM<content> : host의 특정 요소를 출력(포함)

var host = document.getElementById('shadow_div06');var root = host.createShadowRoot();root.innerHTML = '<style>h3 { color: red; }\ content[select="h3"]::content > h3 { color: green; }\ ::content section p { text-decoration: underline; }</style>\ <h3>Shadow DOM</h3>\ <content select="h3"></content>\ <content select="section"></content>';

JavaScript

<div id="shadow_div06"><h3>Light DOM</h3><section> <div>I'm not underlined</div> <p>I'm underlined in Shadow DOM!</p></section></div>

HTML

Page 31: Web Components & Polymer

SHADOW DOM몇가지 특징

한개의 host에서 여러 개의 ShadowRoot를 만들 수 있음

하지만, LIFO(Last In, First Out) 스택과 같이 마지막에 추가된

ShadowRoot가 렌더링됨

제일 처음 추가된 트리는 'older tree'

→ .olderShadowRoot 속성을 통해 접근

제일 마지막에 추가된 트리는 'yonger tree'

→ .shadowRoot를 통해 접근

Page 32: Web Components & Polymer

SHADOW DOM<shadow> : shadow root에서 shadow 요소를 출력(포함)var host = document.getElementById('shadow_div7');var root1 = host.createShadowRoot();var root2 = host.createShadowRoot();

root1.innerHTML = '<div>루트 1</div><content></content>';root2.innerHTML = '<div>루트 2</div><shadow></shadow>';

JavaScript

<div id="shadow_div7">Light DOM</div> HTML

Page 33: Web Components & Polymer

SHADOW DOMElement.getDistributedNodes()

<content>로 포함된 노드들을 접근할 수 있도록 한다.

var host = document.getElementById('shadow_div8');var root = host.createShadowRoot();

// 템플릿을 ShadowRoot에 추가한다.var template = document.getElementById('sdom');var clone = document.importNode(template.content, true);root.appendChild(clone);

var html = [];[].forEach.call(root.querySelectorAll('content'), function(el) { html.push(el.select + ': ');

JavaScript

<div id="shadow_div8"> <span>HTML</span> <span>5</span> <div>Shadow DOM</div> <h5>footer</h5></div>

HTML

Page 34: Web Components & Polymer

SHADOW DOMElement.getDestinationInsertionPoints()

자신을 포함시킨 ShadowRoot 요소에 접근

var host = document.getElementById('shadow_div9');var root1 = host.createShadowRoot();var root2 = host.createShadowRoot();

root1.innerHTML = '<content select="h2"></content>';root2.innerHTML = '<shadow></shadow>';

// 해당 요소를 ShadowRoot에 포함시킨 요소에 접근var h2 = document.querySelector('#shadow_div9 h2');var insertionPoints = h2.getDestinationInsertionPoints();

var html = [];[].forEach.call(insertionPoints, function(contentEl) { html.push(contentEl.outerHTML);

JavaScript

<div id="shadow_div9"> <h2>Light DOM</h2></div>

HTML

Page 35: Web Components & Polymer

SHADOW DOMShadow DOM Visualizer

ShadowDOM Visualizer

<div id="host"> <h2>Eric</h2> <h2>Bidelman</h2> <div class="desc"> Title: <span>Digital Jedi</span> </div> <div>Not selected</div> <h4>footer text</h4></div>

HOST NODE

<header> <content select="h2"></content> </header> <section> <content select="div.desc"></content> </section> <footer> <content select="h4"></content> </footer>

SHADOW DOM

div[id=host]

header

content[select=h2]

h2 #Eric h2 #Bidelman

section

content[select=div.desc]

div[class=desc] #Title:

span #Digital Jedi

footer

content[select=h4]

h4 #footer text

COMPOSED/RENDERED TREE

What's this?

Page 36: Web Components & Polymer

WEB COMPONENT를사용하면?

태그 형태로 특정 기능을 갖는 UI 컴포넌트들을삽입할 수 있어, 손쉬운 재사용이 가능해집니다!

Page 37: Web Components & Polymer

POLYMER?Polymer is a library that makes building applications easier.

Is built on Web Components.

https://www.polymer-project.org/

Page 38: Web Components & Polymer

POLYMER ARCHITECTUREPolymer Elements : polymer.js

Polyfill : platform.js (webcomponent.js로 변경예정)

Page 39: Web Components & Polymer

POLYFILL?브라우저가 네이티브 하게 지원하지 않는 기능을

사용 가능하도록 만들어주는 코드 모음http://remysharp.com/2010/10/08/what-is-a-polyfill/

Web Components polyfillMozilla X-Tag : Google Polymer :

http://www.x-tags.org/http://www.polymer-project.org/

Page 40: Web Components & Polymer

브라우저 호환성Polyfill

ChromeAndroid Chrome Canary Firefox IE 10+ Safari 6+ Mobile

Safari

TemplateMutationObserver [1]HTML ImportsCustom Elements [1]Shadow DOMObject.observe()Web Animations

Platform

Native implementationChromeAndroid Chrome Canary Firefox IE 10+ Safari 6+ Mobile

Safari

TemplateMutationObserverHTML Imports F F 36Custom Elements 33 33Shadow DOM 35 35 35 [2]Object.observe() 35 F 35 F 36Web Animations

Platform

https://www.polymer-project.org/resources/compatibility.html

Chrome 36+ 부터 웹컴포넌트 스펙들은 네이티브하게 지원

Page 41: Web Components & Polymer

POLYMER 는EVERGREEN BROWSER*에서

문제없는 실행을 목표.*Evergreen Web Browser is a web browserthat automatically updates itself on startup.

http://www.yetihq.com/blog/evergreen-web-browser/

Page 42: Web Components & Polymer

POLYMER ELEMENTS : CORE ELEMENTS유틸리티 요소와 공통적 UI 요소들의 모음

Ajax, 애니메이션, 드래그&드롭, 아이콘 모음, 툴팁, etc.

Animation

animated!

raw raw group custom infinite

Core Elements

Animation

Collapse

Drag and Drop

Drawer Panel

Dropdown

Dropdown Menu

http://www.polymer-project.org/docs/elements/core-elements.html

Page 43: Web Components & Polymer

POLYMER ELEMENTS : PAPER ELEMENTSMaterial design*이 적용된 UI 요소들의 모음

버튼, 체크박스, 다이얼로그, 입력요소, 탭, 토스트, etc.

Checkbox

Notifications

Notify me about updates to apps or games that I'vedownloaded

Auto-updates

Auto-update apps over wifi only

Clear search history

Remove all the searches you have ever performed

Paper Elements

Checkbox

Radio Button

Toggle Button

Input

Toolbar

Progress Bar

http://www.polymer-project.org/docs/elements/paper-elements.html

Page 44: Web Components & Polymer

MATERIAL DESIGN?"머티리얼 디자인에서 표면과 그림자는 물리적인 구조를 형성하여,

사용자들이 화면 상의 어떤 부분을 터치할 수 있고

움직일 수 있는지 쉽게 이해할 수 있도록 돕습니다."

다양한 디바이스를 아우르는 일관된 디자인질감이 느껴지는 표면(tactile surfaces)대담하고 선명한 그래픽 디자인 (bold graphic design)자연스러운 애니메이션

Page 45: Web Components & Polymer

사용방법 #1이미 만들어진 요소들을 사용하는 방법

1. platform.js를 페이지에 로딩

2. 사용할 요소를 페이지에 로딩

3. 페이지 내에 새로 추가된 요소를 태그로 선언

<script src="../components/platform/platform.js"></script>

<link rel="import" href="./components/paper-checkbox/paper-checkbox.html">

<paper-checkbox></paper-checkbox>

Page 46: Web Components & Polymer

이미 만들어진 요소들을 사용하는 방법<!-- paper elementspaper-input label="이름을 입력하세요."paper-checkboxpaper-radio-button-->

HTML

Page 47: Web Components & Polymer

사용방법 #2 - 직접 요소를 생성Step 1: 요소에 해당되는 페이지 작성

1. Polymer core를 페이지에 삽입

2. <polymer-element>를 사용해 새로운 태그 등록

<link rel="import" href="../components/polymer/polymer.html">

<polymer-element name="사용자정의-태그" noscript> <template> <span>내용</span> </template></polymer-element>

Page 48: Web Components & Polymer

사용방법 #2 - 직접 요소를 생성Step 2: 사용될 페이지에서 요소 페이지 삽입 후, 태그로 선언

<head> <script src="../components/platform/platform.js"></script> <link rel="import" href="./파일.html"></head><body> <사용자정의-태그></사용자정의-태그></body>

Page 49: Web Components & Polymer

직접 생성한 요소를 사용하는 방법JavaScript

<naver-search></<naver-search> HTML

Page 50: Web Components & Polymer

POLYMER DEMO APP / Topeka Calculator

Sign In

Choose an Avatar

First Name

Last Initial

7

4

1

.

8

5

2

0

9

6

3

=

DEL

+

-

÷

×

2nd

sinh

EXP

Page 51: Web Components & Polymer

VULCANIZE사용되는 웹 컴포넌트 파일들을 병합해

HTTP request를 줄일 수 있도록 해주는 도구

# 설치 $ sudo npm install -g vulcanize

# 사용 $ vulcanize 대상파일.html --inline --strip -o 결과파일.html

https://github.com/Polymer/vulcanize

Page 52: Web Components & Polymer

WEB COMPONENTS ECOSYSTEM다양한 웹컴포넌트 생태계

http://component.kitchen/

http://customelements.io/

http://googlewebcomponents.github.io/

Page 53: Web Components & Polymer

BOWER대다수의 웹컴포넌트들은

Bower*를 통한 손쉬운 설치를 제공

$ bower install 컴포넌트명

*A package manager for the web - http://bower.io/

Page 54: Web Components & Polymer

<SORTABLE-TABLE>$ bower install sortable-table

<sortable-table columns='["과일","가","나","다"]' data='[ [ "사과", 4, 10, 2 ], [ "바나나", 0, 4, 0 ], [ "포도", 2, 3, 5 ] ]'></sortable-table>

HTML

Page 55: Web Components & Polymer

<GOOGLE-MAP>$ bower install GoogleWebComponents/google-map

<google-map latitude="37.499405" longitude="127.033716" zoom="17" fitToMarkers="true"> <google-map-marker latitude="37.499405" longitude="127.033716" title="포스코P&S타워"></google-map-marker></google-map>

HTML

Page 56: Web Components & Polymer

MOZILLA BRICK모질라에서 개발한 UI 웹컴포넌트

Brick

Blog

Components

action

appbar

calendar

deck

flipbox

form

layout

menu

tabbar

storage-indexeddb

BrickBrick is a collection of UI components designed for the easy and quick building ofweb application UIs. Brick components are built using the Web Components standardto allow developers to describe the UI of their app using the HTML syntax theyalready know.

InstallBrick can be installed using the Bower package manager:

bower install mozbrick/brick

To use Brick in your project, place the following in the <head> of your main HTML:

<script src="bower_components/brick/dist/platform/platform.js"></script>

<link rel="import" href="bower_components/brick/dist/brick.html">

https://mozbrick.github.io/

Page 57: Web Components & Polymer

REFERENCEWebComponents.org

Google I/O 2014 - Polymer and Web Components change everything you know about

Web development

Google I/O 2014 - Polymer and the Web Components revolution

Google I/O 2014 - Unlock the next era of UI development with Polymer

Custom Elements

HTML Imports

HTML's New Template Tag

Shadow DOM

http://webcomponents.org/

https://www.youtube.com/watch?v=8OJ7ih8EE7

https://www.youtube.com/watch?v=yRbOSdAe_JU

https://www.youtube.com/watch?v=HKrYfrAzqFA

http://www.html5rocks.com/en/tutorials/webcomponents/customelements/

http://www.html5rocks.com/en/tutorials/webcomponents/imports/

http://www.html5rocks.com/en/tutorials/webcomponents/template/

http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom/

http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/

http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-301/

Page 58: Web Components & Polymer

<thank-you><thank-you> 고맙습니다.고맙습니다.

</thank-you></thank-you>