[Web Component] มาตรฐานใหม่ของการเขียนเว็บด้วยการสร้าง Web Component จาก Custom Elements API

NSLog0
NSLog0
Mar 14 · 4 min read

โพสเก่าจาก Blog เดิม กันยายน 21, 2018

Image for post
Image for post

อัพเดท: ตอนนี้ Web Component มีการ Support แบบ 100% ในทุกๆ Browser แล้วนะครับ อ้างอิงจากประกาศของทางผู้พัฒนา

ดังนั้นบทความนี้อาจจะมีเนื้อหาบางอย่างที่เก่าไปเพราะเขียนไว้เมื่อสองปีที่แล้วครับ


วันนี้เราจะมาเรียนการ Custom element ด้วย JavaScript API ซึ่งเป็น Native API ที่สามารถทำให้เราสร้างแท็ก HTML ได้เองหรือจะสามารถ Extend ตัว components ของคนอื่นมาได้ด้วย ซึ่งเอกสารของ Custom Elements API เขียนอยู่ในเว็บของ WHATWG แล้วสามารถเข้าไปอ่านกันได้ จริงๆ เราจะเห็นได้ว่ามันมีการพูดถึงน้อย แต่ก็มีการเริ่มใช้งานกันแล้ว เราก็มาทำความรู้จักกันก่อนและวันนึงที่มันใช้กันเยอะแล้ว เราก็จะได้เอามาทำงานได้ก่อนคนอื่น จริงๆ คำว่า Component มีมานานแล้ว อย่างถ้าใครเขียน Angular 1 มาจะมีการทำ Custom tag ได้ที่เรียกว่า Drirective หรือ Framwork ช่วงหลังๆ ก็มีการแนะนำให้เขียนเว็บแบบเป็น Component เพื่อง่ายต่อการจัดการและการนำไปใช้งานต่อ อย่างเช่น ReactJS, VueJS ในขณะที่เราต้องทำผ่าน Framework แต่ตัวเทคโนโลยีนี้สามารถให้เราเขียน Component ขึ้นมาได้เองจาก Native API ซึ่งอนาคตต่อไปเราจะได้เห็นอะไรแบบนี้มากขึ้น


ก่อนจะเริ่มผมขออธิบายสองอย่างที่อ้างอิงมาในบทความก่อนนะ

Native API

Native API หมายถึง JavaScript API ที่สามารถเรียกได้ผ่านเบราว์เซอร์ เลย โดยไม่ผ่านไลบรารี่ใดๆ ช่วย เช่น การเปิดกล้องมือถือหรือคอมพิวเตอร์ด้วย JavaScript อย่าง navigator.getUserMedia() ซึ่งก็เป็น Native API ที่สามารถเรียกผ่านเบราว์เซอร์ได้ตรงๆ

WHATWG

คำที่สองคือ WHATWG หรือย่อมาจาก Web Hypertext Application Technology Working Group เป็นการรวมตัวขององค์กรไอทีใหญ่ๆ ที่เกิดจากปัญหาการกำหนดมาตรฐานของ HTML ช้าของ W3C ทำให้เกิดการแยกตัวออกมาจาก W3C เกิดเป็นองค์กรใหม่เพื่อพัฒนาสเปกของ HTML5 หรือ Standard อื่นๆ กันไปก่อน หลังจากนั้นจึงให้ทาง W3C มารับรองมาตรฐาน แต่ทั้ง W3C และ WHATWG นั้นไม่ค่อยเห็นไปทางเดียวกันเพราะ W3C มีการออกมาตรฐานแบบที่ตายตัวและไม่ค่อยเปลี่ยนแปลงและในขณะที่เทคโนโลยีและผู้ที่พัฒนาเบราว์เซอร์ยังต้องพัฒนาอยู่เรื่อยๆ จึงทำให้เกิดมาตรฐานใหม่ชื่อ Living Standard หรือก็คือการพัฒนามาตรฐานใหม่อย่างต่อเนื่อง ซึ่งเป็นแนวคิดของ WHATWG เพราะงั้นใครอยากอ่านอัพเดทมาตรฐาน HTML5 หรือ Standard อื่นๆ ที่มาใหม่ๆ ก็ลองไปอ่านในเว็บของ WHATWG ได้ครับ


ทำความรู้จัก Syntax

การเขียน Custom Element นั้นเราจะใช้ ES6 เข้ามาช่วยคือใช้ keyword class เข้ามาช่วยเพื่อใช้ฟีเจอร์ extends ของตัว class ที่ชื่อ HTMLElement เพื่อเอาความสามารถของมันมาใช้งานและเพื่อให้เบราว์เซอร์เข้าใจ Element ของเรา (class และ extends ก็เป็น Native เราสามารถใช้งานได้เลย)

class AppDrawer extends HTMLElement {...}
window.customElements.define('app-drawer', AppDrawer);

การสร้าง Element จะมีสองส่วนใหญ่ๆ อย่างแรกคือต้องสร้าง class ขึ้นมาโดยต้อง extends HTMLElement เสมอและเมื่อต้องการจะให้เบราว์เซอร์มองเห็นแท็กของเราก็ให้เรียกฟังก์ชัน window.customElements.define ใส่ชื่อ ที่จะเรียกเป็นแท็กลงไปและก็ class ที่เราสร้างไว้เพื่อ Register เข้าไปในเบราว์เซอร์ เท่านี้เราก็มีแท็กเป็นของตัวเองแล้วจากนั้นเราก็ไปเรียกใช้ <app-drawer></app-drawer>


การใช้งาน JavaScript API และการสร้าง Element ให้รับคำสั่งผ่าน JavaScript ได้

การทำงานกับ JavaScript ทั่วไปและการใช้งาน ES6

อย่างแรกเราสามารถใช้ constructor ได้และทุกครั้งต้องเรียก super เพื่อรับเอาความสามารถของ class HTMLElement ตรงนี้ใครไม่เข้าใจ OOP อาจจะงงได้ และเกือบทั้งหมดจะมีเรื่องของ OOP ปะปนอยู่ด้วย แต่ไม่เป็นไรเอาเป็นว่าอย่าลืมเรียก super() ก็พอ

class appCounter extends HTMLElement {

constructor() {
super();

this.createButton();
this.createCounter();

document
.querySelector('app-counter button')
.addEventListener('click', e => {

const elm = document.querySelector('app-counter span');

let number = elm.textContent;

elm.textContent = parseInt(number) + 1;
});
}

createButton() {
let btn = document.createElement("BUTTON");
let txt = document.createTextNode("Counter");
btn.appendChild(txt);
this.appendChild(btn);
}

createCounter() {
let btn = document.createElement("SPAN");
let txt = document.createTextNode("0");
btn.appendChild(txt);
this.appendChild(btn);
}
}
window.customElements.define('app-counter', appCounter);

เมื่อเราทำการเรียก <app-counter></app-counter> เมื่อไรเท่ากับว่าเรา new Object() และตัว constructor ก็จะทำงาน และผมก็สร้าง <button></button> และ <span></span> ขึ้นมาใน Custom element อีกทีนึงและใส่ addEventListener เมื่อกดปุ่มให้ไปเพิ่มเลข

ลองมาดูตัวอย่างแอพ Counter ดูครับ อันนี้ผมทำเป็นตัวอย่างง่ายๆ ซึ่งไม่ได้ถูกหลักการสักเท่าไร ยังและมีบัคนิดหน่อย ซึ่งถ้าเราเรียกแท็กหลายๆ รอบมันจะทำงานได้อันเดียว แต่อันนี้เป็นตัว Demo ไม่เป็นเราก็เรียกมันอันเดียวพอ ถ้าดูจากโค้ดที่ผมเขียนก็จะเห็นได้ชัดเจนเลยครับ


การทำให้ Element เป็นเป็น Public สามารถรับคำสั่งผ่าน JavaScript ได้หรือสร้างการกำหนด attribute เป็นของตัวเอง

class AppDrawer extends HTMLElement {

static get observedAttributes() {
return ['open'];
}

get open() {
return this.hasAttribute('open');
}
set open(val) {
if (val) {
this.setAttribute('open', '');
} else {
this.removeAttribute('open');
}
}
constructor() {
super();

this.addEventListener('click', e => {
this.open = false;
this.textContent = this.hasAttribute('open');
});

this.textContent = this.hasAttribute('open');
}

attributeChangedCallback(attrName, oldVal, newVal) {
if(this.open) {
this.innerHTML = this.hasAttribute('open');
};
}
}
window.customElements.define('app-drawer', AppDrawer);setTimeout(() =>{
document.querySelector('app-drawer').open = true;
}, 1000);

การทำ attribute (คล้ายๆ กับ พวก src="" ของ <img/> หรืออื่นๆ) ให้กับ element ทำได้ง่ายๆ คือการทำฟังก์ชัน Encapsulation (getter/setter) โดยใช้ความสามารถของ ES6 ผ่านการประกาศ get/set ไว้ที่หน้าฟังก์ชันแบบในตัวอย่าง และเราก็สามารถที่จะเรียกเอาค่าออกมาใช้ได้เพียงแค่ใช้ this และตามด้วยชื่อฟังก์ชัน ตัวอย่าง เช่นผมอยากดึงค่าหรือใส่ค่าให้กับ open ผมก็เพียงเรียก this.open และเมื่อเราทำ get/set หมายความว่าเราก็สามารถเรียก Element แบบนี้ได้ <app-drawer open></app-drawer> หรือ app-drawer open="true"></app-drawer> เป็นต้น ยังไม่พอเราสามารถที่จะใช้การสั่งผ่าน JavaScript ได้ เช่น document.querySelector('app-drawer').open = true หรือจะไลบรารี่ที่ทำงานกับ DOM อื่นๆ

ถัดมาผมเพิ่มฟังก์ชัน API ที่มีให้เราเรียกใช้ได้คือ attributeChangedCallback มันเป็นการทำ Observation ว่าถ้า attribute เรามีการตั้งค่าหรือเปลี่ยนแปลงค่ามันจะมี callback วิ่งเข้ามาในฟังก์ชั่นและเราก็จัดการกับสิ่งที่เกิดขึ้นได้ ลองมาดูโปรแกรมด้านล่างที่ผมเขียนให้ดูกัน

โปรแกรมนี้หลักการง่ายๆ คือผมสร้าง Element ขึ้นมาอันนึงและใช้ JavaScript ตั้งค่า attribute จากนั้นตอนแรกค่ามาจะเป็น false เพราะผมยังไม่ได้ใส่อะไรใน open ลงไปและเมื่อผ่านไป 1 วินาที ผมก็ได้ใส่ open ลงไปและค่าจะเปลี่ยนเป็น true จากนั้นผมก็ทำให้ Element กดได้ ถ้าลองกดมันจะเปลี่ยนเป็น false เพราะผมเข้าไปใส่ตั่งค่าใหม่ให้รับค่า false เข้าไป นอกจากนี้ยังมี API อีกให้เราเรียกใช้ในการทำ Reaction หรือพูดง่ายๆ เป็นการทำให้มันสามารถยิงค่าหรือรับส่งค่าได้ เขาเรียกว่า custom element reactions. สามารถอ่านต่อได้ที่นี่

การใส่ Content หรือใส่แท็กอื่นๆ ลงไป

พูดง่ายๆ ว่าเป็นการใส่ข้อความลงไปแบบที่ตัวอย่างด้านบนแหละครับ เราก็จะเรียก this.innerHTML เป็น Pure JavaScript ครับ และการจะใส่แท็กแบบในตัวอย่างผมก็ใช้ this.appendChild ลงไป ซึ่งตรงนี้ถ้าใครเขียน JavaScript ได้คล่องอยู่แล้ว ก็ไม่เป็นปัญหาเรียกคำสั่งที่มีบนเบราว์เซอร์ได้เลยหรือจะใส่เหมือนเขียน HTML ทั่วๆ ไป

<custom-elm class="greeting"> 
<div class="text-center">
<p>Hello, World</p>
</div>
</custom-elm>

ความสามารถอื่นๆ

Shadow DOM

Shadow DOM คือการทำให้ Element มี style เป็นของตัวเองได้โดยไม่ต้องออกมาเขียน CSS แยกแบบที่ผมทำ สำหรับตรงนี้ผมคงไม่สอนไรมาก สามารถอ่านเองได้จากเอกสารของ Google และเอกสารของ W3C หรือถ้าใครไม่อยากทำอะไรให้ยุ่งยากผมก็แนะนำว่าเขียน CSS แยกได้ครับผม และทำเป็นไลบรารี่แยกแล้วเรียกเข้ามาใช้งานเอา เช่นการสร้างเป็นโปรเจคแยกเป็นต้น

Extending

เป็นความสามารถที่น่าสนใจเพราะว่าปกติเราจะทำการ extends HTMLElement เพื่อสร้างแท็กแล้ว แต่ว่าเรายัง extends ตัวแท็กที่คนอื่น Custom มาหรือว่าเราทำเองก็ได้ครับ เช่น

class CounterWithRounding extends CounterWithNoStyle {
constructor() {
super();
...
}
doSomething() {
}

}
window.customElements
.define(
'counter-with-rounding',
CounterWithRounding
);

หรือ extends จากแท็ก HTML Native ก็ได้เช่น

class FancyButton extends HTMLButtonElement {
constructor() {
super();

this.addEventListener('click', e => this.drawRipple(
e.offsetX,
e.offsetY
));
}
// Material design ripple animation.
drawRipple(x, y) {
let div = document.createElement('div');
div.classList.add('ripple');
this.appendChild(div);
div.style.top = `${y - div.clientHeight/2}px`;
div.style.left = `${x - div.clientWidth/2}px`;
div.style.backgroundColor = 'currentColor';
div.classList.add('run');
div.addEventListener('transitionend', e => div.remove());
}
}
customElements.define(
'fancy-button',
FancyButton,
{ extends: 'button' }
);

บางคนอาจจะสงสัยว่าจะรู้ได้ยังไงว่า HTML ตัวไหนคือ Class Inerface อะไรที่จะสามารถเรียกได้ เราสามารถดูได้จากเว็บของ WHATWG

Browser support

แน่นอนว่าใครอ่านมาถึงตรงนี้ต้องสงสัยว่ามันใช้งานได้มากน้อยแค่ไหน และอาจจะเป็นส่วนสำคัญอันนึงของบทความนี้เพราะไม่ว่าเราจะเขียนอะไรลงไปในเว็บได้ตามใจสุดท้ายแล้วเราต้องตัดสินใจว่ามันจะทำงานได้ครบทุกเบราว์เซอร์หรือไหม ซึ่งถ้าเราอ้างอิงจากเว็บของ Google Developer ระบุว่า Chrome 54 (status) และ Safari 10.1 (status) รองรับแล้ว ส่วน Edge เพิ่งเริ่มทำ prototype และ Mozilla เปิดให้มีการรายงานบัคแล้ว

Polyfill

ส่วนที่ขาดไม่ได้อีกอย่างของการเขียน JavaScript คือการทำ polyfill และถ้าอธิบายสั้นๆ ก็คือการนำไลบราลี่หรือการเขียนฟังก์ชันเพื่อรองรับการใช้งานฟังก์ชันที่ไม่ได้มีในบางบราว์เซอร์ครับ ซึ่งตัว Web Component ให้ใส่ webcomponents.js loader ลงไปในเว็บที่เราได้ทำการใช้งาน Custom Element เนื่องจากการรองรับเบราว์เซอร์ยังไม่เยอะมาก เพื่อป้องกันเว็บพังโดยไม่ได้ตั้งใจ ก็ให้ทำ polyfill ไปด้วย ไม่งั้นจะไม่สามารถเรียกใช้งานจริงบน Production ได้และจะเจอ Error แบบนี้

Image for post
Image for post

เอกสารอ้างอิง

สำหรับเอกสารผมได้สรุปมาให้จากเว็บของ Google Developer เป็นส่วนใหญ่ประกอบกับการค้นหาอีกนิดนึงใครที่อยากอ่านฉบับภาษาอังกฤษเต็มก็สามารถเข้าไปอ่านดูได้เลยครับ


การใช้งานจริง

เนื่องจากว่าเขียนด้วย JavaScript ES6 แนะนำให้เอา Babel เข้ามา complie ให้เป็น JavaScrip ปกติก่อนจะเอาไปใช้งานจริง หรือก็คือการทำ build distribution source ไว้มีโอกาสผมอาจจะมาสอนการทำตรงนี้ให้เพิ่มเติม


ตัวอย่างงาน Web Compoment

สำหรับ Web Component ถึงจะใหม่แต่ก็มีการเริ่มใช้งานแล้วอย่าง Github เองเขาก็มีให้ทดลองใช้งานได้เหมือนกันเขาก็ทำไว้ใช้ในเว็บตัวเอง และถ้าอยากหาโปรเจคที่เป็น Web Component ก็เข้าไปหาได้ที่ Github [link] หรือเข้าเว็บตรงเลยที่ www.webcomponents.org

AlgorithmTut

May the force be with you. **Tut stand for Tutorial**

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store