Pengantar
Web sangat tidak memiliki ekspresi. Untuk memahami apa yang saya maksud, coba lihat aplikasi web "modern" seperti Gmail:
Tidak ada yang modern tentang sup <div>
. Namun, beginilah cara kami membangun aplikasi web. Sedih sekali.
Bukankah seharusnya kita lebih banyak meminta dari platform kita?
Markup yang seksi. Mari kita membuatnya menjadi hal penting
HTML memberi kita alat yang sangat baik untuk menyusun dokumen, tetapi kosakatanya terbatas pada elemen yang ditentukan oleh standar HTML.
Bagaimana jika markup untuk Gmail tidak buruk? Bagaimana jika hasilnya indah:
<hangout-module>
<hangout-chat from="Paul, Addy">
<hangout-discussion>
<hangout-message from="Paul" profile="profile.png"
profile="118075919496626375791" datetime="2013-07-17T12:02">
<p>Feelin' this Web Components thing.
<p>Heard of it?
</hangout-message>
</hangout-discussion>
</hangout-chat>
<hangout-chat>...</hangout-chat>
</hangout-module>
Menyegarkan sekali! Aplikasi ini juga masuk akal. Format ini bermanfaat, mudah dipahami, dan yang terpenting, dapat dipelihara. Nanti saya/Anda akan tahu persis apa yang dilakukannya hanya dengan memeriksa backbone deklaratifnya.
Memulai
Elemen Kustom memungkinkan developer web menentukan jenis elemen HTML baru. Spesifikasi ini adalah salah satu dari beberapa primitif API baru yang muncul dalam naungan Komponen Web, tetapi mungkin saja yang paling penting. Komponen Web tidak akan ada tanpa fitur yang dibuka oleh elemen khusus:
- Menentukan elemen HTML/DOM baru
- Membuat elemen yang memperluas dari elemen lain
- Menggabungkan fungsi kustom ke dalam satu tag secara logis
- Memperluas API elemen DOM yang ada
Mendaftarkan elemen baru
Elemen kustom dibuat menggunakan document.registerElement()
:
var XFoo = document.registerElement('x-foo');
document.body.appendChild(new XFoo());
Argumen pertama untuk document.registerElement()
adalah nama tag elemen.
Nama harus berisi tanda hubung (-). Jadi misalnya, <x-tags>
, <my-element>
, dan <my-awesome-app>
adalah nama yang valid, sedangkan <tabs>
dan <foo_bar>
tidak valid. Batasan ini memungkinkan parser
membedakan elemen kustom dari elemen reguler, tetapi juga memastikan
kompatibilitas maju saat tag baru ditambahkan ke HTML.
Argumen kedua adalah objek (opsional) yang menjelaskan prototype
elemen.
Ini adalah tempat untuk menambahkan fungsi khusus (misalnya properti dan metode publik) ke elemen Anda.
Info selengkapnya tentang hal itu akan dibahas nanti.
Secara default, elemen kustom mewarisi dari HTMLElement
. Dengan demikian, contoh sebelumnya setara dengan:
var XFoo = document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype)
});
Panggilan ke document.registerElement('x-foo')
mengajarkan browser tentang elemen baru, dan menampilkan konstruktor yang dapat Anda gunakan untuk membuat instance <x-foo>
.
Atau, Anda dapat menggunakan teknik pembuatan instance elemen
lainnya jika tidak ingin menggunakan konstruktor.
Memperluas elemen
Elemen kustom memungkinkan Anda memperluas elemen HTML (native) yang ada serta elemen kustom lainnya. Untuk memperluas elemen, Anda harus meneruskan registerElement()
nama
dan prototype
elemen yang akan diwarisi.
Memperluas elemen native
Katakan Anda tidak puas dengan Regular Joe <button>
. Anda ingin
meningkatkan kemampuannya agar menjadi "Tombol Mega". Untuk memperluas elemen <button>
,
buat elemen baru yang mewarisi prototype
dari HTMLButtonElement
dan extends
nama elemen tersebut. Dalam hal ini, "button":
var MegaButton = document.registerElement('mega-button', {
prototype: Object.create(HTMLButtonElement.prototype),
extends: 'button'
});
Elemen kustom yang diwarisi dari elemen native disebut elemen kustom ekstensi jenis.
Elemen tersebut mewarisi dari versi khusus HTMLElement
sebagai cara untuk mengatakan, "elemen X adalah Y".
Contoh:
<button is="mega-button">
Memperluas elemen kustom
Untuk membuat elemen <x-foo-extended>
yang memperluas elemen kustom <x-foo>
, cukup warisi prototipenya
dan sebutkan tag yang Anda warisi:
var XFooProto = Object.create(HTMLElement.prototype);
...
var XFooExtended = document.registerElement('x-foo-extended', {
prototype: XFooProto,
extends: 'x-foo'
});
Lihat Menambahkan properti dan metode JS di bawah untuk informasi selengkapnya tentang cara membuat prototipe elemen.
Cara mengupgrade elemen
Pernahkah Anda bertanya-tanya mengapa parser HTML tidak sesuai dengan tag non-standar?
Misalnya, akan sangat menyenangkan jika kami mendeklarasikan <randomtag>
di halaman. Menurut spesifikasi HTML:
Maaf <randomtag>
! Anda non-standar dan mewarisi dari HTMLUnknownElement
.
Hal yang sama tidak berlaku untuk elemen khusus. Elemen dengan nama elemen kustom yang valid mewarisi dari HTMLElement
. Anda dapat memverifikasi fakta ini dengan mengaktifkan Konsol: Ctrl + Shift + J
(atau Cmd + Opt + J
di Mac), dan menempelkan baris kode berikut; kode tersebut akan menampilkan true
:
// "tabs" is not a valid custom element name
document.createElement('tabs').__proto__ === HTMLUnknownElement.prototype
// "x-tabs" is a valid custom element name
document.createElement('x-tabs').__proto__ == HTMLElement.prototype
Elemen yang belum terselesaikan
Karena elemen kustom didaftarkan oleh skrip menggunakan document.registerElement()
, elemen tersebut dapat
dideklarasikan atau dibuat sebelum definisinya didaftarkan oleh browser. Misalnya,
Anda dapat mendeklarasikan <x-tabs>
di halaman, tetapi pada akhirnya memanggil document.registerElement('x-tabs')
di kemudian hari.
Sebelum elemen diupgrade ke definisinya, elemen tersebut disebut elemen yang belum di-resolve. Ini adalah elemen HTML yang memiliki nama elemen kustom yang valid, tetapi belum didaftarkan.
Tabel ini dapat membantu menjelaskannya:
Nama | Mewarisi dari | Contoh |
---|---|---|
Elemen yang belum terselesaikan | HTMLElement |
<x-tabs> , <my-element> |
Elemen tidak diketahui | HTMLUnknownElement |
<tabs> , <foo_bar> |
Membuat instance elemen
Teknik umum pembuatan elemen masih berlaku untuk elemen kustom. Seperti pada elemen standar lainnya, elemen ini dapat dideklarasikan di HTML atau dibuat di DOM menggunakan JavaScript.
Membuat instance tag kustom
Deklarasikan:
<x-foo></x-foo>
Buat DOM di JS:
var xFoo = document.createElement('x-foo');
xFoo.addEventListener('click', function(e) {
alert('Thanks!');
});
Gunakan operator new
:
var xFoo = new XFoo();
document.body.appendChild(xFoo);
Membuat instance elemen ekstensi jenis
Membuat instance elemen kustom gaya ekstensi jenis sangat mirip dengan tag kustom.
Deklarasikan:
<!-- <button> "is a" mega button -->
<button is="mega-button">
Buat DOM di JS:
var megaButton = document.createElement('button', 'mega-button');
// megaButton instanceof MegaButton === true
Seperti yang dapat Anda lihat, sekarang ada versi kelebihan muatan document.createElement()
yang menggunakan atribut is=""
sebagai parameter kedua.
Gunakan operator new
:
var megaButton = new MegaButton();
document.body.appendChild(megaButton);
Sejauh ini, kita telah mempelajari cara menggunakan document.registerElement()
untuk memberi tahu browser tentang tag baru...tetapi
hal itu tidak terlalu berguna. Mari tambahkan properti dan metode.
Menambahkan properti dan metode JS
Keunggulan elemen kustom adalah Anda dapat menggabungkan fungsi yang disesuaikan dengan elemen dengan menentukan properti dan metode pada definisi elemen. Anggap ini sebagai cara membuat API publik untuk elemen Anda.
Berikut adalah contoh lengkapnya:
var XFooProto = Object.create(HTMLElement.prototype);
// 1. Give x-foo a foo() method.
XFooProto.foo = function() {
alert('foo() called');
};
// 2. Define a property read-only "bar".
Object.defineProperty(XFooProto, "bar", {value: 5});
// 3. Register x-foo's definition.
var XFoo = document.registerElement('x-foo', {prototype: XFooProto});
// 4. Instantiate an x-foo.
var xfoo = document.createElement('x-foo');
// 5. Add it to the page.
document.body.appendChild(xfoo);
Tentu saja ada beberapa ribu cara untuk membuat prototype
. Jika Anda bukan penggemar membuat prototipe seperti ini, berikut adalah versi yang lebih ringkas dari hal yang sama:
var XFoo = document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype, {
bar: {
get: function () {
return 5;
}
},
foo: {
value: function () {
alert('foo() called');
}
}
})
});
Format pertama memungkinkan penggunaan Object.defineProperty
ES5. Fungsi kedua memungkinkan penggunaan get/set.
Metode callback siklus proses
Elemen dapat menentukan metode khusus untuk memanfaatkan saat-saat menarik dari keberadaannya. Metode ini diberi nama dengan tepat callback siklus proses. Masing-masing memiliki nama dan tujuan tertentu:
Nama callback | Ditelepon saat |
---|---|
createdCallback | instance elemen dibuat |
attachedCallback | sebuah {i>instance<i} disisipkan ke dalam dokumen |
detachedCallback | instance telah dihapus dari dokumen |
attributeChangedCallback(attrName, oldVal, newVal) | atribut telah ditambahkan, dihapus, atau diperbarui |
Contoh: menentukan createdCallback()
dan attachedCallback()
di <x-foo>
:
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {...};
proto.attachedCallback = function() {...};
var XFoo = document.registerElement('x-foo', {prototype: proto});
Semua callback siklus proses bersifat opsional, tetapi tentukan jika/ketika diperlukan.
Misalnya, elemen Anda cukup kompleks dan membuka koneksi ke IndexedDB di createdCallback()
. Sebelum dihapus dari DOM, lakukan pekerjaan pembersihan
yang diperlukan di detachedCallback()
. Catatan: Anda tidak boleh mengandalkan hal ini,
misalnya, jika pengguna menutup tab, tetapi anggaplah hal ini sebagai kemungkinan hook pengoptimalan.
Callback siklus proses kasus penggunaan lainnya adalah untuk menyiapkan pemroses peristiwa default pada elemen:
proto.createdCallback = function() {
this.addEventListener('click', function(e) {
alert('Thanks!');
});
};
Menambahkan markup
Kita telah membuat <x-foo>
, memberinya JavaScript API, tetapi kosong. Haruskah kita memberikan
beberapa HTML untuk dirender?
Callback siklus proses sangat berguna di sini. Secara khusus, kita dapat menggunakan
createdCallback()
untuk memberikan elemen dengan beberapa HTML default:
var XFooProto = Object.create(HTMLElement.prototype);
XFooProto.createdCallback = function() {
this.innerHTML = "**I'm an x-foo-with-markup!**";
};
var XFoo = document.registerElement('x-foo-with-markup', {prototype: XFooProto});
Membuat instance tag ini dan memeriksa di DevTools (klik kanan, pilih Inspect Element) akan menampilkan:
▾<x-foo-with-markup>
**I'm an x-foo-with-markup!**
</x-foo-with-markup>
Mengenkapsulasi internal dalam Shadow DOM
Dengan sendirinya, Shadow DOM adalah alat yang canggih untuk mengenkapsulasi konten. Gunakan bersama dengan elemen khusus dan semuanya akan menjadi ajaib!
Shadow DOM memberikan elemen kustom:
- Cara untuk menyembunyikan isi perut mereka, sehingga melindungi pengguna dari detail implementasi yang mengerikan.
- Enkapsulasi gaya...tanpa batas.
Membuat elemen dari Shadow DOM sama seperti membuat elemen yang merender markup dasar. Perbedaannya ada pada createdCallback()
:
var XFooProto = Object.create(HTMLElement.prototype);
XFooProto.createdCallback = function() {
// 1. Attach a shadow root on the element.
var shadow = this.createShadowRoot();
// 2. Fill it with markup goodness.
shadow.innerHTML = "**I'm in the element's Shadow DOM!**";
};
var XFoo = document.registerElement('x-foo-shadowdom', {prototype: XFooProto});
Daripada menyetel .innerHTML
elemen, saya membuat
Shadow Root untuk <x-foo-shadowdom>
, lalu mengisinya dengan markup.
Dengan setelan "Show Shadow DOM" diaktifkan di DevTools, Anda akan melihat
#shadow-root
yang dapat diperluas:
▾<x-foo-shadowdom>
▾#shadow-root
**I'm in the element's Shadow DOM!**
</x-foo-shadowdom>
Itu Akar Bayangan!
Membuat elemen dari template
Template HTML adalah primitif API baru lainnya yang sangat cocok dengan dunia elemen kustom.
Contoh: mendaftarkan elemen yang dibuat dari <template>
dan Shadow DOM:
<template id="sdtemplate">
<style>
p { color: orange; }
</style>
<p>I'm in Shadow DOM. My markup was stamped from a <template>.
</template>
<script>
var proto = Object.create(HTMLElement.prototype, {
createdCallback: {
value: function() {
var t = document.querySelector('#sdtemplate');
var clone = document.importNode(t.content, true);
this.createShadowRoot().appendChild(clone);
}
}
});
document.registerElement('x-foo-from-template', {prototype: proto});
</script>
<template id="sdtemplate">
<style>:host p { color: orange; }</style>
<p>I'm in Shadow DOM. My markup was stamped from a <template>.
</template>
<div class="demoarea">
<x-foo-from-template></x-foo-from-template>
</div>
Beberapa baris kode ini mengemas banyak pukulan. Mari kita pahami semua yang terjadi:
- Kami telah mendaftarkan elemen baru di HTML:
<x-foo-from-template>
- DOM elemen dibuat dari
<template>
- Detail menakutkan elemen disembunyikan menggunakan Shadow DOM
- Shadow DOM memberikan enkapsulasi gaya elemen (misalnya
p {color: orange;}
tidak mengubah seluruh halaman menjadi oranye)
Bagus!
Menata gaya elemen kustom
Seperti pada tag HTML lainnya, pengguna tag kustom Anda dapat menata gayanya dengan pemilih:
<style>
app-panel {
display: flex;
}
[is="x-item"] {
transition: opacity 400ms ease-in-out;
opacity: 0.3;
flex: 1;
text-align: center;
border-radius: 50%;
}
[is="x-item"]:hover {
opacity: 1.0;
background: rgb(255, 0, 255);
color: white;
}
app-panel > [is="x-item"] {
padding: 5px;
list-style: none;
margin: 0 7px;
}
</style>
<app-panel>
<li is="x-item">Do</li>
<li is="x-item">Re</li>
<li is="x-item">Mi</li>
</app-panel>
Menata gaya elemen yang menggunakan Shadow DOM
Lubang kelinci menjadi jauh lebih dalam saat Anda memasukkan Shadow DOM ke dalam campuran. Elemen kustom yang menggunakan Shadow DOM mewarisi manfaat besarnya.
Shadow DOM menanamkan sebuah elemen dengan enkapsulasi gaya. Gaya yang ditentukan di Shadow Root tidak kebocoran dari host dan tidak keluar dari halaman. Untuk elemen kustom, elemen itu sendiri adalah host. Properti enkapsulasi gaya juga memungkinkan elemen kustom menentukan gaya default untuk dirinya sendiri.
Penataan gaya Shadow DOM adalah topik yang sangat besar! Jika Anda ingin mempelajari lebih lanjut, saya merekomendasikan beberapa artikel saya yang lain:
- "Panduan untuk Menata Gaya Elemen" di dokumentasi Polymer.
- "Shadow DOM 201: CSS & Penataan Gaya" di sini.
Pencegahan FOUC menggunakan :unresolved
Untuk mengurangi FOUC, elemen khusus menentukan pseudo class CSS baru, :unresolved
. Gunakan untuk menargetkan elemen yang belum terselesaikan, hingga titik saat browser memanggil createdCallback()
(lihat metode siklus proses).
Setelah itu terjadi, elemen tersebut bukan lagi elemen yang belum terselesaikan. Proses upgrade
selesai dan elemen telah berubah menjadi definisinya.
Contoh: memperjelas tag "x-foo" saat tag tersebut didaftarkan:
<style>
x-foo {
opacity: 1;
transition: opacity 300ms;
}
x-foo:unresolved {
opacity: 0;
}
</style>
Perlu diingat bahwa :unresolved
hanya berlaku untuk elemen yang belum terselesaikan,
bukan elemen yang mewarisi dari HTMLUnknownElement
(lihat Cara elemen diupgrade).
<style>
/* apply a dashed border to all unresolved elements */
:unresolved {
border: 1px dashed red;
display: inline-block;
}
/* x-panel's that are unresolved are red */
x-panel:unresolved {
color: red;
}
/* once the definition of x-panel is registered, it becomes green */
x-panel {
color: green;
display: block;
padding: 5px;
display: block;
}
</style>
<panel>
I'm black because :unresolved doesn't apply to "panel".
It's not a valid custom element name.
</panel>
<x-panel>I'm red because I match x-panel:unresolved.</x-panel>
Histori dan dukungan browser
Deteksi fitur
Deteksi fitur adalah cara untuk memeriksa apakah document.registerElement()
ada:
function supportsCustomElements() {
return 'registerElement' in document;
}
if (supportsCustomElements()) {
// Good to go!
} else {
// Use other libraries to create components.
}
Dukungan browser
document.registerElement()
pertama kali mulai berada di belakang flag di Chrome 27 dan Firefox ~23. Namun, sejak saat itu, spesifikasinya sedikit berubah. Chrome 31 adalah yang pertama memiliki
dukungan sebenarnya untuk spesifikasi yang diperbarui.
Sebelum dukungan browser terkenal, ada polyfill yang digunakan oleh Polymer Google dan X-Tag Mozilla.
Apa yang terjadi pada HTMLElementElement?
Bagi pihak yang telah mengikuti pekerjaan standardisasi, Anda tahu sebelumnya sudah ada <element>
.
Tadi itu lutut lebah. Anda dapat menggunakannya untuk mendaftarkan elemen baru secara deklaratif:
<element name="my-element">
...
</element>
Sayangnya, ada terlalu banyak masalah pengaturan waktu pada proses upgrade,
kasus sudut, dan skenario seperti Armageddon. <element>
harus di rak. Pada Agustus 2013, Dimitri Glazkov memposting ke aplikasi web publik yang mengumumkan penghapusannya, setidaknya untuk saat ini.
Perlu diperhatikan bahwa Polymer menerapkan bentuk deklaratif pendaftaran elemen
dengan <polymer-element>
. Bagaimana caranya? Bagian ini menggunakan document.registerElement('polymer-element')
dan
teknik yang saya jelaskan dalam Membuat elemen dari template.
Kesimpulan
Elemen khusus memberi kita alat untuk menambah kosakata HTML, mengajarinya trik baru, dan menjelajahi platform web. Kombinasikan komponen tersebut dengan
primitif platform baru lainnya seperti Shadow DOM dan <template>
, dan kita akan mulai mendapatkan
gambaran Komponen Web. Markup bisa terlihat menarik lagi!
Jika Anda tertarik untuk memulai komponen web, sebaiknya lihat Polymer. Ini lebih dari cukup untuk membuat Anda siap.