Pengantar
Web sangat tidak memiliki ekspresi. Untuk melihat apa yang saya maksud, intip aplikasi web "modern" seperti Gmail:
Tidak ada restoran modern tentang sup <div>
. Namun, inilah cara kita membuat aplikasi web. Sedih sekali.
Bukankah kita harus menuntut lebih banyak dari platform kita?
Markup seksi. Mari jadikan topik
HTML memberi kita alat yang sangat baik untuk menyusun struktur dokumen, tetapi kosakatanya terbatas pada elemen yang ditentukan oleh standar HTML.
Bagaimana jika markup untuk Gmail tidak mengerikan? Bagaimana jika terlihat 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>
Sungguh menyegarkan! Aplikasi ini juga sangat masuk akal. Compose bermakna, mudah dipahami, dan yang terbaik, dapat dikelola. Saya/Anda di masa mendatang akan mengetahui dengan tepat apa yang dilakukannya hanya dengan memeriksa backbone deklaratifnya.
Memulai
Elemen Kustom memungkinkan developer web menentukan jenis elemen HTML baru. Spesifikasi ini merupakan salah satu dari beberapa primitif API baru yang berada dalam naungan Komponen Web, namun mungkin ini yang paling penting. Komponen Web tidak akan ada tanpa fitur yang dibuka oleh elemen kustom:
- Menentukan elemen HTML/DOM baru
- Membuat elemen yang memperluas dari elemen lain
- Menggabungkan fungsi kustom secara logis ke dalam satu tag
- 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>
semuanya 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
ke depan 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.
Selengkapnya akan dibahas nanti.
Secara default, elemen kustom mewarisi dari HTMLElement
. Jadi, contoh sebelumnya setara dengan:
var XFoo = document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype)
});
Panggilan ke document.registerElement('x-foo')
akan memberi tahu browser tentang elemen baru,
dan menampilkan konstruktor yang dapat Anda gunakan untuk membuat instance <x-foo>
.
Atau, Anda dapat menggunakan teknik lainnya untuk membuat instance elemen
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 nama
dan prototype
elemen yang akan diwarisi ke registerElement()
.
Memperluas elemen native
Misalnya, Anda tidak puas dengan Regular Joe <button>
. Anda ingin meningkatkan
kemampuannya untuk menjadi "Tombol Mega". Untuk memperluas elemen <button>
,
buat elemen baru yang mewarisi prototype
dari HTMLButtonElement
dan extends
nama elemen. Dalam hal ini, "button":
var MegaButton = document.registerElement('mega-button', {
prototype: Object.create(HTMLButtonElement.prototype),
extends: 'button'
});
Elemen kustom yang mewarisi dari elemen native disebut elemen kustom ekstensi jenis.
Elemen ini 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 ucapkan 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 elemen diupgrade
Pernahkah Anda bertanya-tanya mengapa parser HTML tidak menyesuaikan tag non-standar?
Misalnya, akan sangat bagus jika kita mendeklarasikan <randomtag>
di halaman. Sesuai dengan spesifikasi HTML:
Maaf <randomtag>
! Anda bukan 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 Console: 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 kemudian memanggil document.registerElement('x-tabs')
di lain waktu.
Sebelum diupgrade ke definisinya, elemen disebut elemen yang belum terselesaikan. Ini adalah elemen HTML yang memiliki nama elemen kustom yang valid, tetapi belum terdaftar.
Tabel ini mungkin dapat membantu menjelaskan:
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 elemen standar lainnya, elemen ini dapat dideklarasikan dalam 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, kini ada versi document.createElement()
yang kelebihan muatan
yang menggunakan atribut is=""
sebagai parameter keduanya.
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 kegunaannya tidak banyak. Mari kita tambahkan properti dan metode.
Menambahkan properti dan metode JS
Keunggulan elemen kustom adalah Anda dapat memaketkan fungsi yang disesuaikan dengan elemen dengan menentukan properti dan metode pada definisi elemen. Anggap ini sebagai cara membuat API publik untuk elemen Anda.
Berikut 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 banyak sekali cara untuk membuat prototype
. Jika Anda tidak
suka 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. Yang kedua memungkinkan penggunaan get/set.
Metode callback siklus proses
Elemen dapat menentukan metode khusus untuk memanfaatkan waktu yang menarik dalam keberadaannya. Metode ini diberi nama dengan tepat callback siklus proses. Masing-masing memiliki nama dan tujuan spesifik:
Nama callback | Dipanggil saat |
---|---|
createdCallback | instance elemen dibuat |
attachedCallback | instance disisipkan ke dalam dokumen |
detachedCallback | instance telah dihapus dari dokumen |
attributeChangedCallback(attrName, oldVal, newVal) | sebuah atribut 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/saat 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 mengandalkannya,
misalnya, jika pengguna menutup tab, tetapi anggaplah ini sebagai kemungkinan hook pengoptimalan.
Callback siklus proses kasus penggunaan lainnya adalah untuk menyiapkan pemroses peristiwa default di elemen:
proto.createdCallback = function() {
this.addEventListener('click', function(e) {
alert('Thanks!');
});
};
Menambahkan markup
Kita telah membuat <x-foo>
, yang diberi JavaScript API, tetapi kosong. Mari kita
berikan beberapa HTML untuk dirender.
Callback siklus proses berguna di sini. Khususnya, kita dapat menggunakan
createdCallback()
untuk memberi 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 memeriksanya 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 bagian dalam di Shadow DOM
Dengan sendirinya, Shadow DOM adalah alat yang canggih untuk mengenkapsulasi konten. Gunakan bersama dengan elemen khusus dan berbagai hal akan menakjubkan!
Shadow DOM memberikan elemen khusus:
- Cara menyembunyikan intuisi mereka, sehingga melindungi pengguna dari detail implementasi yang mengerikan.
- Enkapsulasi gaya...untuk bebas.
Membuat elemen dari Shadow DOM sama seperti membuat elemen yang
merender markup dasar. Perbedaannya ada di 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 telah membuat
Root Bayangan untuk <x-foo-shadowdom>
, lalu mengisinya dengan markup.
Dengan setelan "Tampilkan DOM Bayangan" 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 adalah Root Bayangan!
Membuat elemen dari {i>template<i}
Template HTML adalah primitif API baru lainnya yang 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 punya kemampuan hebat. Mari kita pahami semua yang terjadi:
- Kita telah mendaftarkan elemen baru di HTML:
<x-foo-from-template>
- DOM elemen dibuat dari
<template>
- Detail seram pada elemen ini disembunyikan menggunakan Shadow DOM
- Shadow DOM memberikan enkapsulasi gaya elemen (misalnya,
p {color: orange;}
tidak mengubah seluruh halaman menjadi oranye)
Bagus sekali!
Menata gaya elemen kustom
Seperti halnya tag HTML lain, pengguna tag kustom Anda dapat menata gayanya menggunakan 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 akan jauh lebih dalam saat Anda menghadirkan Shadow DOM ke dalam campuran. Elemen khusus yang menggunakan Shadow DOM mewarisi manfaatnya yang luar biasa.
Shadow DOM memasukkan elemen dengan enkapsulasi gaya. Gaya yang ditentukan dalam Shadow Root tidak keluar dari host dan tidak bocor dari halaman. Dalam kasus elemen kustom, elemen itu sendiri adalah host. Properti enkapsulasi gaya juga memungkinkan elemen kustom untuk menentukan gaya default untuk dirinya sendiri.
Gaya visual DOM bayangan adalah topik yang sangat besar! Jika Anda ingin mempelajari lebih lanjut, saya sarankan beberapa artikel saya yang lain:
- "Panduan untuk Menata Gaya Elemen" di dokumentasi Polymer.
- "Shadow DOM 201: CSS & Styling" di sini.
Pencegahan FOUC menggunakan :unresolved
Untuk mengurangi FOUC, elemen kustom menentukan
class pseudo CSS baru, :unresolved
. Gunakan untuk menargetkan elemen yang belum terselesaikan,
sampai saat browser memanggil createdCallback()
Anda (lihat metode siklus proses).
Setelah hal itu terjadi, elemen tersebut tidak lagi menjadi elemen yang belum terselesaikan. Proses upgrade telah selesai dan elemen telah berubah menjadi definisinya.
Contoh: memudar tag "x-foo" saat 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 untuk elemen yang diwarisi 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>
Dukungan histori dan browser
Deteksi fitur
Mendeteksi fitur adalah 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 muncul di balik tanda di Chrome 27 dan Firefox ~23. Namun, spesifikasinya telah berkembang cukup banyak sejak saat itu. Chrome 31 adalah versi pertama yang
memiliki dukungan sesungguhnya untuk spesifikasi yang diupdate.
Sebelum dukungan browser sangat bagus, ada polyfill yang digunakan oleh Polymer Google dan X-Tag Mozilla.
Apa yang terjadi pada HTMLElementElement?
Bagi Anda yang telah mengikuti pekerjaan standardisasi, Anda tahu bahwa pernah ada <element>
.
Yaitu sebesar lutut lebah. Anda dapat menggunakannya untuk mendaftarkan elemen baru secara deklaratif:
<element name="my-element">
...
</element>
Sayangnya, ada terlalu banyak masalah pengaturan waktu dengan proses upgrade,
kasus ekstrem, dan skenario seperti Armageddon untuk mengatasi semuanya. <element>
harus disimpan di rak. Pada Agustus 2013, Dimitri Glazkov memposting ke public-webapps yang mengumumkan penghapusan aplikasi tersebut, setidaknya untuk saat ini.
Perlu diperhatikan bahwa Polymer menerapkan bentuk deklaratif pendaftaran elemen
dengan <polymer-element>
. Bagaimana caranya? Tutorial ini menggunakan document.registerElement('polymer-element')
dan
teknik yang saya jelaskan dalam Membuat elemen dari template.
Kesimpulan
Elemen khusus memberi kami alat untuk memperluas kosakata HTML, mengajarkan trik baru, dan melewati lubang cacing pada platform web. Kombinasikan semua itu dengan primitif platform baru lainnya seperti Shadow DOM dan <template>
, dan kita akan mulai mendapatkan gambaran tentang Komponen Web. Markup bisa menjadi seksi lagi!
Jika Anda tertarik untuk memulai komponen web, sebaiknya lihat Polymer. Program ini lebih dari cukup untuk membantu Anda memulai.