Banyak Web Front-End Framework pada saat ini mempromosikan kemampuannya dalam meminimalisir kode yang berulang karena menerapkan teknik components dan modules

Component jadi hal yang sangat populer karena dengannya, kita dapat mudah memasang dan mempreteli kumpulan element (component) pada website.
Component bersifat reusable, sehingga kita bisa menggunakanya pada banyak project tanpa harus membuat ulang. Bahkan kita dapat menggunakan component yang dibuat dan dibagikan oleh orang lain. 
Inilah mengapa Front-End Framework seperti ReactAngular, ataupun Vue sangat populer karena terdapat penerapan component di dalamnya.
Sejak dulu setiap framework atau library pasti memiliki caranya sendiri dalam penggunaan/pembuatannya. 
Termasuk dalam penggunaan/pembuatan component-nya. Sehingga component akan bersifat reusable, dengan catatan selama masih dalam framework yang sama. Apakah itu menjadi masalah? Tentu!
Terlalu nyaman dalam salah satu framework yang digunakan akan menjadi masalah. Karena jika kita berada di framework yang berbeda, komponen yang kita biasa gunakan belum tentu dapat digunakan pada framework tersebut. 
Contohnya, Jika kita menuliskan library Angular dan kita ingin ia berfungsi pada framework Vue? Apakah bisa? Maka dari itu kita perlu menuliskan berdasarkan standar umum dalam membuat komponen sehingga dapat digunakan oleh framework dan browser manapun. 
Web component merupakan salah satu fitur standar yang terdapat pada Browser API. Dengan ini kita jadi mudah membuat component UI yang bersifat reusable. 
Pada materi kali ini, kita akan membahas seputar web component mulai dari bagaimana membuatnya hingga menerapkannya pada pada proyek Club Finder

What is Web Component

Web component merupakan salah satu fitur yang ditetapkan standar World Wide Web Consortium (W3C). Fitur ini memudahkan developer dalam membuat komponen UI websitenya menjadi lebih modular.
20200310180135577d410ea4b01a4ec8f6c0f13ce55190.gif
Dengan semakin pesatnya perkembangan website saat ini kita perlu menetapkan teknik yang lebih modern dalam mengembangkan website. Salah satunya membuat komponen UI pada website agar mampu sesuai dengan kebutuhan dan digunakan ulang. Kebanyakan developer saat ini menggunakan framework untuk membantu pengembangan website menjadi mudah dalam membuat dan menggunakan komponen UI. 
Namun apakah Anda tahu beberapa kelebihan web component dibandingkan komponen yang dibuat menggunakan framework?
  • Standard : Web Component merupakan standar yang ditetapkan oleh WC3 dalam membuat komponen yang reusable.
  • Compatibility : Karena web component merupakan standard maka dapat digunakan pada framework seperti Angular, React, ataupun Vue.
  • Simple : Menggunakan web component tidak memerlukan konfigurasi khusus layaknya framework yang ada. Karena web component dibangun tak lain hanya menggunakan JS/CSS/HTML murni.
Web component bersifat reusable. Bahkan dapat digunakan walaupun kita menggunakan framework sekalipun. Apa pasal? Web component dibangun tak lain menggunakan JS/HTML/CSS murni. Terdapat dua API penting dalam menerapkan web component, yakni:
  • Custom Elements: Digunakan untuk membuat elemen baru (custom element). Kita juga bisa menentukan perilaku element tersebut sesuai kebutuhan.
  • Shadow DOM: Digunakan untuk membuat HTML element terenkapsulasi dari gangguan luar. Biasanya digunakan pada custom element, agar elemen tersebut tidak terpengaruh oleh styling yang ditetapkan di luar dari custom elemen-nya

Custom Element

HTML memberikan kemudahan dalam mengatur struktur website. Untungnya seluruh browser sepakat untuk menggunakannya. Pada kelas Belajar Dasar Pemrograman Web kita sudah belajar penulisan dan penggunaan tag pada HTML dalam membuat struktur website. 
Ada banyak sekali tags HTML yang dapat kita manfaatkan secara langsung. Apakah Anda tahu pada HTML5 hampir terdapat 100 tag standar yang bisa kita gunakan? Karena banyaknya tag HTML yang tersedia, seharusnya kita bisa membuat struktur website yang memiliki arti (semantic meaning).
Untuk membuat struktur website memiliki arti, kita harus tahu HTML tag mana yang tepat untuk digunakan. Sebelum HTML5, hampir seluruh bagian pembentuk layout pada website dibuat menggunakan tag

. Baik itu untuk header, footer, artikel, ataupun konten samping. 
20200310180507ede3cf1615fb43775e53c5893786c7bc.png
Setelah hadirnya HTML5, kita dikenalkan pada beberapa elemen yang dapat digunakan dalam mengelompokkan sebuah elemen dengan lebih jelas dan memiliki arti (semantic meaning). Elemen-elemen ini memiliki nama sesuai dengan fungsi atau peran dari elemen tersebut.
2020031018053892a731d399ccaf1a172064c4888440e3.png
Element 

 memang digunakan untuk mencakup elemen yang belum atau tidak tersedia. Biasanya kita menyiasati penggunaan tag div dengan menambahkan attribute id ataupun class untuk menunjukkan fungsinya. Namun dalam penulisan nilai atributnya, terkadang kita tidak memiliki standar khusus sehingga kode tersebut akhirnya hanya kita sendirilah yang mengetahuinya.
Namun sekarang, penggunaan elemen 

 pada website seharusnya dapat lebih diminimalisir lagi. Dengan membuat dan menggunakan custom element, struktur HTML kita dapat dibaca lebih mudah.
20200310180628b4595d1b6bdfcd820d3871f6e2ce21ea.png
(Kiri) Struktur HTML dengan menggunakan tag 

. (Kanan) Struktur HTML dengan Custom Element.
Dengan Custom Element kita dapat membuat struktur elemen HTML yang lebih rapi lagi. Karena dengannya, kita dapat membuat DOM element kita sendiri sesuai kebutuhan. Anda melihat contoh penerapannya pada gambar di atas.

Write your first Custom Element

Dalam membuat custom element, kita menuliskannya dengan menggunakan JavaScript class. Class tersebut mewarisi sifat dari HTMLElementHTMLElement merupakan interface yang merepresentasikan element HTML. Interface ini biasanya diterapkan pada class JavaScript sehingga terbentuklah element HTML baru melalui class tersebut (custom element).
Berikut contoh penulisan dalam membuat custom element:

  1. class ImageFigure extends HTMLElement {

  2.  

  3. }


Yeay! ImageFigure sekarang merupakan sebuah HTML element baru. Namun tunggu dulu. Untuk menggunakannya pada berkas HTML, kita perlu menetapkan nama tag yang nantinya digunakan pada HTML. Caranya dengan menggunakan variabel customElements seperti ini:


  1. customElements.define("image-figure", ImageFigure);


customElements merupakan global variable yang digunakan untuk mendefinisikan custom element dan memberitahu bahwa terdapat HTML tag baru. Di dalam customElements terdapat method yang bernama define(). Di sinilah kita meletakan tag name baru kemudian diikuti dengan JavaScript class yang menerapkan sifat HTMLElement.
“Dalam penamaan tag untuk custom element, nama tag harus terdiri dari dua kata yang dipisahkan oleh dash (-). Jika tidak, pembuatan custom element tidak akan berhasil. Hal Ini diperlukan untuk memberi tahu browser perbedaan antara elemen asli HTML dan custom element.”
Setelah mendefinisikan custom element, barulah ia siap digunakan pada berkas HTML. Kita cukup menuliskan tagnya layaknya elemen HTML biasa.




Jangan lupa lampirkan script pada berkas yang digunakan untuk menuliskan class ImageFigure.


  1. <script src="image-figure.js">


Berikut kode lengkapnya:




  1.  

  2.    <meta charset="utf-8">

  3.    <meta name="viewport" content="width=device-width">

  4.    My First Custom Element

  5.  

  6.  

  7.    

  8.    <script src="image-figure.js">

  9.  




  1. class ImageFigure extends HTMLElement {

  2.  

  3. }

  4.  

  5. customElements.define("image-figure", ImageFigure);


Coba jalankan kode di atas pada browser, kita tidak akan mendapatkan apapun. Sampai saat ini, element  berperan layaknya element 

 ataupun  yang tidak memiliki fungsi khusus. 
Karena kita belum menetapkan seperti apa jadinya element baru ini. 
20200310181513ebac1cf89024807a90e2cd2729534e21.png
Untuk menetapkan seperti apa fungsi dari elemen baru, kita lakukan semuanya dengan menggunakan kode JavaScript yang dituliskan di dalam class ImageFigure. Tapi sebelum itu, kita pelajari dulu siklus hidup (life cycle) dari elemen HTML

Life Cycle of Custom Element

Ketika sebuah JavaScript class mewarisi sifat dari HTMLElement maka class tersebut akan memiliki siklus hidup layaknya sebuah elemen HTML. 
Kita dapat menerapkan logika pada setiap siklus hidup yang ada dengan memanfaatkan lifecycle callbacks yang ada. Berikut ini lifecycle callbacks yang ada pada HTMLElement:
  • connectedCallback() : Akan terpanggil setiap kali elemen berhasil ditambahkan ke dokumen HTML (DOM). Callback ini merupakan tempat yang tepat untuk menjalankan konfigurasi awal seperti mendapatkan data, atau mengatur attribute.
  • disconnectedCallback() : Akan terpanggil setiap kali elemen dikeluarkan (remove()) dari DOM. Callback ini merupakan tempat yang tepat untuk membersihkan data yang masih disimpan pada elemen. Bisa itu event, state, ataupun objek. 
  • attributeChangedCallback() : Akan terpanggil setiap kali nilai atribut yang di-observe melalui fungsi static get observedAttributes diubah. Callback ini bisa kita manfaatkan untuk memuat ulang data yang ditampilkan oleh elemen.
  • adoptedCallback() : Akan terpanggil setiap kali elemen dipindahkan pada dokumen baru. Kita relatif jarang menggunakan callback ini, namun jika kita memanfaatkan tag  maka callback ini akan terpanggil.
Untuk mempermudah memahami urutan siklus hidup element pada HTML kita bisa lihat pada ilustrasi berikut:
20200310181931e74da220c3a1a4a3ffd73cce428f7cc7.png
Walaupun sebenarnya constructor() bukan termasuk siklus hidup HTML Element, namun fungsi tersebut sering digunakan untuk melakukan konfigurasi awal ketika pertama kali element dibuat. Seperti menentukan event listener, atau menetapkan Shadow DOM.
Ketika kita mengimplementasikan constructor pada custom element, kita wajib memanggil method super(). Jika tidak, maka akan menghasilkan error:
ReferenceError: Must call super constructor in derived class before accessing ‘this’ or returning from derived constructor
Class yang merupakan custom element lebih ketat dibandingkan class lain. Kita tidak dapat membuat argument pada constructor class-nya. Karena instance dibuat tidak menggunakan keyword new seperti class JavaScript umumnya.
Terdapat dua cara membuat instance dari custom element. Yang pertama adalah menggunakan nama tagnya langsung yang dituliskan pada kode HTML. Contohnya:



  1.    



Lalu cara kedua adalah dengan menggunakan sintaks JavaScript. Sama seperti membuat element HTML biasa, kita gunakan method document.createElement dalam membuat elemen baru.

  1. const imageFigureElement = document.createElement("image-figure");

  2. document.body.appendChild(imageFigureElement);


Kita bisa mencobanya sendiri dengan menuliskan kode-kode berikut dan menjalankannya pada browser. Kemudian lihat output yang dihasilkan pada browser. Output tersebut akan menunjukan urutan siklus hidup yang terpanggil.




  1.  

  2.    <meta charset="utf-8">

  3.    <meta name="viewport" content="width=device-width">

  4.    Element life cycle

  5.  


  6.   <!-- silakan hapus tag untuk membuatnya secara sintaks JavaScript -->

  7.    

  8.    <script src="image-figure.js">

  9.    <script src="main.js">

  10.  




  1. class ImageFigure extends HTMLElement {

  2.  constructor() {

  3.    super();

  4.    console.log("constructed!")

  5.  }

  6.  

  7.  

  8.  connectedCallback() {

  9.    console.log("connected!");

  10.  }

  11.  

  12.  

  13.  disconnectedCallback() {

  14.    console.log("disconnected!");

  15.  }

  16.  

  17.  

  18.  adoptedCallback() {

  19.    console.log("adopted!");

  20.  }

  21.  

  22.  

  23.  attributeChangedCallback(name, oldValue, newValue) {

  24.    console.log(`Attribute: ${name} changed!`);

  25.  }

  26.  

  27.  

  28.  // digunakan untuk mengamati perubahan nilai attribute caption

  29.  /* kita bisa menetapkan lebih dari satu atribut yang diamati.

  30.     dengan memisahkan nama atribut menggunakan koma. Contoh: */

  31.  // return ["caption", "title", "src", ...]

  32.  static get observedAttributes() {

  33.    return ["caption"];

  34.  }

  35. }

  36.  

  37.  

  38. customElements.define("image-figure", ImageFigure);



  1. let imageFigureElement = document.querySelector("image-figure");

  2.  

  3. // Jika tidak tersedia pada DOM maka dibuat secara sintaksis.

  4. if (!imageFigureElement) {

  5.  imageFigureElement = document.createElement("image-figure");

  6.  document.body.appendChild(imageFigureElement);

  7. }

  8.  

  9. // mengubah/manambahkan nilai attribute caption.

  10. setTimeout(() => {

  11.  imageFigureElement.setAttribute("caption", "Gambar 1");

  12. }, 1000);

  13.  

  14.  

  15. // menghapus imageFigureElement dari DOM

  16. setTimeout(() => {

  17.  imageFigureElement.remove();

  18. }, 3000);


20200310182652be0450ebbab22838a5eee1b22bd53873.gif
2020031018272778b0288d1d32c9983ccc17ea1b7fa7d3.gif
Implementasi lifecycle callback pada custom element bersifat opsional. Kita tidak perlu menuliskannya jika memang tidak diperlukan.

Custom element attribute and method

Selain memiliki siklus hidup, class yang mewarisi sifat HTMLElement juga memiliki properti dan method yang sama seperti objek DOM. 
Di mana ia memiliki properti dan method seperti innerHTMLinnerTextappendChild()remove(), dan sebagainya. 
Melalui properti dan method ini kita dapat menetapkan apa yang harus ditampilkan atau mendapatkan nilai atribut pada custom element. Contohnya seperti ini:
  1. class ImageFigure extends HTMLElement {
  2.  
  3.  connectedCallback() {
  4.    this.src = this.getAttribute(“src”) || null;
  5.    this.alt = this.getAttribute(“alt”) || null;
  6.    this.caption = this.getAttribute(“caption”) || null;
  7.  
  8.    this.innerHTML = `
  9.      
  10.        <img src="${this.src}"
  11.            alt=”${this.alt}”>
  12.        
    ${this.caption}
  13.      

  14.    `;
  15.  }
  16. }
  17.  
  18. customElements.define(“image-figure”, ImageFigure);
Dari kode di atas ketika element  tampak pada DOM, maka ia akan mendapatkan nilai yang ditetapkan pada atribut srcalt, dan caption. Kemudian nilai atribut tersebut akan ditampilkan dalam format elemen 

 dengan memanfaatkan innerHTML.
Untuk memberikan atribut dan nilainya pada custom element, tidak ada bedanya dengan element HTML biasa. Kita bisa melakukannya langsung pada elemennya, atau melalui JavaScript.


  1. <image-figure

  2.      src="https://i.imgur.com/iJq78XH.jpg"

  3.      alt="Dicoding Logo"

  4.      caption="Huruf g dalam logo Dicoding">




  1. const imageFigureElement = document.createElement("image-figure");

  2.  

  3. imageFigureElement.setAttribute("src", "https://i.imgur.com/iJq78XH.jpg");

  4. imageFigureElement.setAttribute("alt", "Dicoding Logo");

  5. imageFigureElement.setAttribute("caption", "Huruf g dalam logo Dicoding");

  6.  

  7. document.body.appendChild(imageFigureElement);


Jika kita lihat hasilnya, maka akan tampak seperti ini:
202003101842396ebe6d528e732b9586f5fe3562899d10.png
Dengan custom elemen ini kita bisa membuat elemen 

 tanpa harus menuliskan lagi element  dan 
 di dalamnya. 
Cukup gunakan custom elemen ini dengan menetapkan nilai atribut yang dibutuhkan. Sudah bisa melihat kerennya custom elemen? Fungsi custom elemen bukan hanya membuat fungsi elemen baru, namun bisa juga dibuat untuk mempermudah penggunaan HTML yang ada. 

Observe Attribute Value

Ketika elemen sudah tampak pada DOM, tidak jarang kita mengganti nilai dari atributnya karena terdapat data yang perlu diperbaharui. 
Jika kita menggunakan elemen HTML standar, perubahan yang diterapkan akan langsung kita bisa lihat hasilnya pada browser. Namun bagaimana dengan custom element? Apakah sama? Mari kita coba bersama.
Pada contoh sebelumnya, kita telah membuat element  yang berfungsi layaknya element 

 dengan penggunaan yang lebih sederhana. Contohnya kita memiliki kode seperti ini:




  1.  

  2.    <meta charset="utf-8">

  3.    <meta name="viewport" content="width=device-width">

  4.    Attribute Observe

  5.  


  6.    <image-figure

  7.      src="https://i.imgur.com/iJq78XH.jpg"

  8.      alt="Dicoding Logo"

  9.      caption="Huruf g dalam logo Dicoding">

  10.      

  11.    <script src="image-figure.js">

  12.  




  1. class ImageFigure extends HTMLElement {

  2.  

  3.  connectedCallback() {

  4.    this.src = this.getAttribute("src") || null;

  5.    this.alt = this.getAttribute("alt") || null;

  6.    this.caption = this.getAttribute("caption") || null;

  7.  

  8.  

  9.    this.innerHTML = `

  10.      

  11.        <img src="${this.src}"

  12.            alt="${this.alt}">

  13.        
    ${this.caption}

  14.      

  15.    `;

  16.  }

  17. }

  18.  

  19.  

  20. customElements.define("image-figure", ImageFigure);


Lalu kita buka pada browser, maka akan tampak seperti ini:
202003101846224b05a9460bd020f4cfbd9732778240e0.png
Lalu mari kita coba mengubah nilai atribut caption dengan nilai baru menggunakan JavaScript melalui console browser.


  1. const element = document.querySelector("image-figure");

  2. element.setAttribute("caption", "Lorem ipsum dolor sit amet");


2020031018472504f1bce82240562e413552d465c4949c.gif
Kita bisa lihat pada gif di atas bahwa walaupun kita berhasil mengubah nilai atribut caption, pada browser nilai yang ditampilkan tersebut tidak berubah. Kok demikian? Pada custom element kita perlu mengimplementasi dua fungsi dalam kelasnya (ImageFigure) agar kita dapat mengobservasi nilai atribut yang berubah. Yang pertama fungsi attributeChangedCallback, dan yang kedua fungsi static get observedAttributes.
Kedua fungsi tersebut saling terhubung. Fungsi attributeChangedCallback akan terpanggil ketika nilai atribut yang diamati pada fungsi observedAttributes diubah nilainya. Kemudian pada callback fungsi attributeChangedCallback inilah kita menetapkan logika perubahan. Pada fungsi ini juga terdapat 3 (tiga) argument fungsi yang bisa dimanfaatkan yaitu:
  • name : Nama dari atribut yang berubah
  • oldValue : Nilai pada atribut sebelum diubah
  • newValue : Nilai baru yang ditetapkan pada atribut.
Urutan dari ketiga argumen tersebut sangatlah penting, jadi jangan sampai tertukar. Sebenarnya kita dapat memberikan nama apa saja untuk ketiga argumennya namun lebih baik gunakan nameoldValuenewValue guna memudahkan kita dalam penggunaannya.
Berikut contoh implementasi dari kedua fungsi tersebut:
  1. class ImageFigure extends HTMLElement {
  2.  
  3.  connectedCallback() {
  4.    this.src = this.getAttribute(“src”) || null;
  5.    this.alt = this.getAttribute(“alt”) || null;
  6.    this.caption = this.getAttribute(“caption”) || null;
  7.    this.render();
  8.  }
  9.  
  10.  render() {
  11.    this.innerHTML = `
  12.      
  13.        <img src="${this.src}"
  14.            alt=”${this.alt}”>
  15.        
    ${this.caption}
  16.      

  17.    `;
  18.  }
  19.  
  20.  attributeChangedCallback(name, oldValue, newValue) {
  21.    this[name] = newValue;
  22.    this.render();
  23.  }
  24.  
  25.  static get observedAttributes() {
  26.    return [“caption”];
  27.  }
  28. }
  29.  
  30. customElements.define(“image-figure”, ImageFigure);
Mari kita telaah kodenya satu persatu. Di dalam fungsi attributeChangedCallback(), kita tuliskan kode untuk mengubah nilai properti this[name] dengan nilai baru yang ditetapkan. 
this[name] ini merupakan cara dinamis untuk mengubah nilai properti sesuai dengan nama atribut yang diubah. Misalkan jika kita mengubah atribut “caption” maka akan mengubah nilai this[“caption”], jika kita mengubah atribut “alt” maka akan mengubah nilai this[“alt”].
Setelah mengubah nilainya lalu kita panggil fungsi render(). Perhatikan juga bahwa kita perlu memisahkan kode rendering HTML di browser pada fungsi yang terpisah (tidak dilakukan di connectedCallback). Tujuannya agar kita dapat memanggil fungsi tersebut tidak hanya sekali tapi setiap kali terdapat perubahan data.
Lalu terdapat juga static get observedAttributes(). Apa fungsinya? Fungsi getter statis ini berperan sebagai “seseorang” yang mengamati perubahan nilai pada atribut yang ditentukan. 
Jika terjadi perubahan, ia akan memanggil attributeChangedCallback dengan memberitahu nama atribut apa yang berubah, nilai sebelum perubahan, serta nilai baru yang akan ditetapkan. observedAttributes tidak akan mengamati seluruh atribut yang diterapkan pada custom element, hanya atribut yang dituliskan pada nilai kembaliannya yang akan diamati.

  1. return ["caption"];


Nilai kembalian dari observedAttributes merupakan array. Jika kita ingin mengamati lebih dari satu atribut, kita dapat menuliskannya layaknya array literals.


  1. return ["caption", "src", "alt"];


Setelah mengimplementasi kedua fungsi tadi seharusnya custom element sudah dapat bereaksi ketika terjadi perubahan nilai atribut.
20200310185339fbfc985571e3eec644d9202d7a27103b.gif

Styling Custom Element without Shadow DOM

Tidak ada cara khusus dalam menetapkan styling pada custom elemen jika tidak menerapkan Shadow DOM. Kita dapat menetapkan styling dengan cara yang sama seperti standar element HTML. 
Dalam arti kita bisa menggunakan nama tag sebagai selector, atribut id ataupun class sebagai selector-nya.
Pada custom element biasanya kita menuliskan styling dengan menggunakan tag  pada saat merender template HTML menggunakan innerHTML.
  1. class ImageFigure extends HTMLElement {
  2.  
  3. ……
  4.  
  5.  render() {
  6.     this.innerHTML = `
  7.      
  8.         figure {
  9.           border: thin #c0c0c0 solid;
  10.           display: flex;
  11.           flex-flow: column;
  12.           padding: 5px;
  13.           max-width: 220px;
  14.           margin: auto;
  15.         }
  16.  
  17.         figure > img {
  18.           max-width: 220px;
  19.         }
  20.  
  21.         figure > figcaption {
  22.           background-color: #222;
  23.           color: #fff;
  24.           font: italic smaller sans-serif;
  25.           padding: 3px;
  26.           text-align: center;
  27.         }
  28.      
  29.  
  30.      
  31.         <img src="${this.src}"
  32.             alt=”${this.alt}”>
  33.        
    ${this.caption}
  34.      

  35.     `;
  36.  }
  37.  
  38. ……
  39. }
  40.  
  41. customElements.define(“image-figure”, ImageFigure);
Ataupun dengan menuliskan styling pada berkas css yang ditautkan pada html.
  1. /* Berkas style.css */
  2. figure {
  3.    border: thin #c0c0c0 solid;
  4.    display: flex;
  5.    flexflow: column;
  6.    padding: 5px;
  7.    maxwidth: 220px;
  8.    margin: auto;
  9. }
  10.  
  11. figure > img {
  12.    maxwidth: 220px;
  13. }
  14.  
  15. figure > figcaption {
  16.    backgroundcolor: #222;
  17.    color: #fff;
  18.    font: italic smaller sansserif;
  19.    padding: 3px;
  20.    textalign: center;
  21. }
Maka pada browser akan menampilkan hasil seperti berikut:

2020031018584997dd30daf430969f36872543cfbea701.png
Kita tidak menetapkan Shadow DOM pada custom element ini sehingga styling pada custom element masih dapat terpengaruh oleh keadaan dari luar. Maksudnya, jika kita menetapkan styling lain dengan menargetkan figure sebagai selector, kemungkinan element 

 yang terdapat pada custom element akan ikut terpengaruhi walaupun kita sudah menetapkan styling secara eksplisit di dalam fungsi render.
 Lantas bagaimana agar custom element dapat terenkapsulasi dari gangguan luar? Kita akan membahas ini lebih detail pada pembahasan Shadow DOM

Handling complex data

Sebelumnya kita sudah belajar bagaimana custom element menampilkan data melalui atribut. 
Seperti yang kita ketahui, nilai dari atribut pada elemen lazimnya hanya data primitif. Namun bagaimana jika custom elemen membutuhkan data yang kompleks atau memiliki nilai yang banyak seperti ini?


  1. const article = {

  2.  id: 1,

  3.  title: "Lorem Ipsum Dolor",

  4.  featuredImage: "https://i.picsum.photos/id/204/536/354.jpg?grayscale",

  5.  description: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."

  6. }


Tentu jika kita simpan nilai tersebut pada atribut HTML akan terlihat berantakan pada penulisan elemennya.


  1. <article-item

  2.        id="1"

  3.        title="Lorem Ipsum Dolor"

  4. featured-image="https://i.picsum.photos/id/204/536/354.jpg?grayscale"

  5.        description="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.">


Cukup merepotkan bukan? Hal ini malah menghilangkan tujuan dari custom elemen yakni  semakin mudah kita baca. Lantas bagaimana cara menangani data kompleks yang dibutuhkan oleh custom element?
Karena pembuatan custom element ini memanfaatkan sebuah JavaScript class, kita bisa memanfaatkan dengan menyimpan data tersebut pada properti class. 
Masih ingat pembahasan properti accessor atau getter/setter? Nah, dengan teknik ini kita dapat menetapkan data yang kompleks pada custom element.

  1. class ArticleItem extends HTMLElement {

  2.  set article(article) {

  3.    this._article = article;

  4.    this.render();

  5.  }

  6.  

  7.  render() {

  8.    this.innerHTML = `

  9.      

  10.      

  11.          

    ${this._article.title}


  12.          

    ${this._article.description}


  13.      

  •    `;

  •  }

  • }

  •  

  • customElements.define("article-item", ArticleItem);


  • Dengan begitu tentu kita hanya bisa menetapkan data pada custom element melalui sintaks JavaScript dengan mengakses properti .article seperti ini:

    1. const articleItemElement = document.createElement("article-item");

    2. articleItemElement.article = article;


    Cukup mudah bukan? Karena kita memanggil fungsi render() di dalam set article(), maka custom element tidak akan menampilkan apapun pada browser sebelum nilai article-nya ditetapkan. Penasaran? Berikut kode lengkapnya jika Anda ingin mencobanya sendiri:




    1.  

    2.    <meta charset="utf-8">

    3.    <meta name="viewport" content="width=device-width">

    4.    Handling Complex Data

    5.    <link href="style.css" rel="stylesheet" type="text/css" />

    6.  

    7.  

    8.    <div class="container">

    9.    

  •    <script src="script.js" type="module">

  •  




    1. * {

    2.  padding: 0;

    3.  margin: 0;

    4.  box-sizing: border-box;

    5. }

    6.  

    7. body {

    8.  padding: 16px;

    9. }

    10.  

    11. .container {

    12.  max-width: 800px;

    13.  margin: 0 auto;

    14. }

    15.  

    16.  

    17. article-item {

    18.    display: block;

    19.    margin-bottom: 18px;

    20.    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);

    21.    border-radius: 10px;

    22.    overflow: hidden;

    23. }

    24.  

    25.  

    26. article-item > .featured-image {

    27.    width: 100%;

    28.    max-height: 300px;

    29.    object-fit: cover;

    30.    object-position: center;

    31. }

    32.  

    33.  

    34. article-item > .article-info {

    35.  padding: 24px;

    36. }

    37.  

    38.  

    39. article-item > .article-info > p {

    40.  margin-top: 10px;

    41. }



    1. import "./article-item.js";

    2. import article from "./article.js";

    3.  

    4. const containerElement = document.querySelector(".container");

    5.  

    6. const articleItemElement = document.createElement("article-item");

    7. articleItemElement.article = article;

    8.  

    9. containerElement.appendChild(articleItemElement);



    1. class ArticleItem extends HTMLElement {

    2.  set article(article) {

    3.    this._article = article;

    4.    this.render();

    5.  }

    6.  

    7.  render() {

    8.    this.innerHTML = `

    9.      

    10.      

    11.          

      ${this._article.title}


    12.          

      ${this._article.description}


    13.      

  •    `;

  •  }

  • }

  •  

  • customElements.define("article-item", ArticleItem);



    1. const article = {

    2.  id: 1,

    3.  title: "Lorem Ipsum Dolor",

    4.  featuredImage: "https://i.picsum.photos/id/204/536/354.jpg?grayscale",

    5.  description: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."

    6. }

    7.  

    8. export default article;


    Jika kita jalankan maka browser akan menampilkan element  dengan data yang didapat dari article.js.
    2020031019100333dd180dab2d0832b94ce5a490bc04aa.png

    Nested Custom Element

    Ketika menggunakan custom element, mungkin terdapat keadaan di mana kita membutuhkan custom element berada di dalam custom element lain. Contohnya, banyak website saat ini yang menampilkan data berupa list, entah itu daftar artikel ataupun item belanja. 
    Biasanya setiap daftar yang ditampilkan ditampung dalam sebuah container 

    . Kemudian item yang sama ditampilkan secara berulang dengan data yang berbeda pada container tersebut.
    20200310191307a4fe82d6dd70b4daae920036121ba5c4.gif
    Web component dapat memudahkan dalam mengorganisir daftar item yang ditampilkan dalam bentuk list menggunakan container. 
    Caranya kita membuat dua custom element yatu container, dan itemnya. Container digunakan untuk menampung elemen item di dalamnya. Selain itu pada container juga data (array) diberikan. 
    Nantinya container-lah yang akan membuat elemen item di dalamnya berdasarkan data yang diberikan.
    Belum terbayang seperti apa? Berikut contohnya:




    1.  

    2.    <meta charset="utf-8">

    3.    <meta name="viewport" content="width=device-width">

    4.    repl.it

    5.    <link href="style.css" rel="stylesheet" type="text/css" />

    6.  

    7.  

    8.    <script src="script.js" type="module">

    9.  




    1. import "./article-list.js";

    2. import articles from "./articles.js";

    3.  

    4.  

    5. const articleListElement = document.createElement("article-list");

    6. articleListElement.articles = articles;

    7.  

    8.  

    9. document.body.appendChild(articleListElement);



    1. import "./article-item.js"

    2.  

    3.  

    4. class ArticleList extends HTMLElement {

    5.  set articles(articles) {

    6.    this._articles = articles;

    7.    this.render();

    8.  }

    9.  

    10.  

    11.  render() {

    12.    this._articles.forEach(article => {

    13.      const articleItemElement = document.createElement("article-item");

    14.      // memanggil fungsi setter article() pada article-item.

    15.      articleItemElement.article = article;

    16.      this.appendChild(articleItemElement);

    17.    })

    18.  }

    19. }

    20.  

    21. customElements.define("article-list", ArticleList);



    1. class ArticleItem extends HTMLElement {

    2.  set article(article) {

    3.    this._article = article;

    4.    this.render();

    5.  }

    6.  

    7.  

    8.  render() {

    9.    this.innerHTML = `

    10.    

    11.    

    12.        

      ${this._article.title}


    13.        

      ${this._article.description}


    14.    

  •   `;

  •  }

  • }

  •  

  •  

  • customElements.define("article-item", ArticleItem);



    1. const articles = [

    2.  {

    3.    id: 1,

    4.    title: "Lorem Ipsum Dolor",

    5.    featuredImage: "https://i.picsum.photos/id/204/536/354.jpg",

    6.    description: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."

    7.  

    8.  

    9.  },

    10.  {

    11.    id: 2,

    12.    title: "Lorem Ipsum Dolor",

    13.    featuredImage: "https://i.picsum.photos/id/209/536/354.jpg",

    14.    description: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."

    15.  

    16.  

    17.  },

    18.  {

    19.    id: 3,

    20.    title: "Lorem Ipsum Dolor",

    21.    featuredImage: "https://i.picsum.photos/id/206/536/354.jpg",

    22.    description: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."

    23.  

    24.  

    25.  },

    26.  {

    27.    id: 4,

    28.    title: "Lorem Ipsum Dolor",

    29.    featuredImage: "https://i.picsum.photos/id/212/536/354.jpg",

    30.    description: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."

    31.  

    32.  

    33.  }

    34. ]

    35.  

    36.  

    37. export default articles;



    1. * {

    2.     padding: 0;

    3.     margin: 0;

    4.     box-sizing: border-box;

    5.   }

    6.   

    7.   body {

    8.     padding: 16px;

    9.   }

    10.   

    11.   article-list {

    12.     display: block;

    13.     max-width: 800px;

    14.     margin: 0 auto;

    15.   }

    16.   

    17.   article-item {

    18.     display: block;

    19.     margin-bottom: 18px;

    20.     box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);

    21.     border-radius: 10px;

    22.     overflow: hidden;

    23.   }

    24.   

    25.   article-item > .featured-image {

    26.     width: 100%;

    27.     max-height: 300px;

    28.     object-fit: cover;

    29.     object-position: center;

    30.   }

    31.   

    32.   article-item > .article-info {

    33.     padding: 24px;

    34.   }

    35.   

    36.   article-item > .article-info > p {

    37.     margin-top: 10px;

    38.   }


    Pada kode di atas kita bisa melihat bahwa terdapat dua buah custom component yaitu  dan . Pada article-list.js terdapat fungsi setter articles yang berfungsi untuk menyimpan nilai articles pada properti this._articles


    1. set articles(articles) {

    2.    this._articles = articles;

    3.    this.render();

    4. }


    Kemudian properti tersebut digunakan pada fungsi render() untuk ditampilkan satu persatu melalui .


    1. render() {

    2.    this._articles.forEach(article => {

    3.      const articleItemElement = document.createElement("article-item");

    4.      // memanggil fungsi setter article() pada article-item.

    5.      articleItemElement.article = article;

    6.      this.appendChild(articleItemElement);

    7.    })

    8. }


    Dengan begitu, untuk menampilkan data pada script.js akan lebih mudah. Kita tidak perlu melakukan proses perulangan lagi di sana karena proses tersebut langsung dilakukan ketika menggunakan element . Kita cukup memberikan nilai array yang akan ditampilkan.


    1. import "./article-list.js";

    2. import articles from "./articles.js";

    3.  

    4. const articleListElement = document.createElement("article-list");

    5. articleListElement.articles = articles;

    6.  

    7. document.body.appendChild(articleListElement);


    Semakin mudah kita menggunakan sebuah element maka akan semakin baik bukan? Walaupun terlihat agak sedikit merepotkan dalam membuatnya, perlu Anda ingat bahwa  web component ini bersifat reusable. Artinya, jika kita ingin membuat komponen serupa, kita tidak perlu membuatnya dari awal.
    Dengan menjalankan kode di atas, maka hasilnya akan tampak seperti ini:
    202003101921481bcb54550f43e9958256358a3c73709b.png
    Potongan kode untuk seluruh contoh custom element yang digunakan pada materi ini: https://repl.it/@dicodingacademy/163-03-custom-element?lite=true

    Solution: Membuat app-bar Component

    Apakah Anda berhasil menerapkan custom element pada proyek Club Finder? Jika belum, mari kita lakukan bersama-sama. Kita mulai dari komponen termudah terlebih dahulu yaitu App Bar.
    Agar mengelola berkas pada proyek jadi lebih mudah, kita perlu membuat folder baru dengan nama “component” di dalam folder src -> script.
    20200310192559e057df7d65da0d6b4369a31dfa0bcf60.png
    Folder ini akan menampung berkas JavaScript yang digunakan dalam membuat custom element.
    Lalu di dalam folder component, buat berkas JavaScript baru dengan nama “app-bar.js”. Kemudian kita buat class dengan nama AppBar yang mewarisi sifat HTMLElement.


    1. class AppBar extends HTMLElement {

    2.   

    3. }


    Kemudian di dalam body block classnya, kita implementasi method connectedCallback dan membuat fungsi render.


    1. class AppBar extends HTMLElement {

    2.    connectedCallback(){

    3.  

    4.    }

    5.  

    6.    render() {

    7.       

    8.    }

    9. }


    Seperti yang sudah kita ketahui, connectedCallback() akan terpanggil ketika element telah diterapkan pada DOM. Jika kita ingin element ini ketika diterapkan langsung melakukan rendering maka kita dapat memanggil fungsi this.render() di dalam connectedCallback.


    1. class AppBar extends HTMLElement {

    2.    connectedCallback(){

    3.        this.render();

    4.    }

    5.  

    6.    render() {

    7.  

    8.    }

    9. }


    Lalu pada fungsi render, kita tuliskan kode yang berfungsi untuk menampilkan elemen yang dibutuhkan pada melalui properti this.innerHTML. Apa saja yang dibutuhkan? Kita bisa melihatnya pada berkas index.html



    1.        <div id="appBar" class="app-bar">

    2.            

      Club Finder


    3.        



  • Di dalam elemen 

     terdapat elemen 

     yang menerapkan class “app-bar”. Nah kita copy element di dalam app-bar, dan paste untuk dijadikan nilai pada this.innerHTML di fungsi render().


    1. class AppBar extends HTMLElement {

    2.    connectedCallback(){

    3.        this.render();

    4.    }

    5.  

    6.    render() {

    7.        this.innerHTML = `

      Club Finder

      `
      ;

    8.    }

    9. }


    Lalu di akhir berkas app-bar.js, jangan lupa untuk definisikan custom element yang kita buat agar dapat digunakan pada DOM.


    1. class AppBar extends HTMLElement {

    2.    connectedCallback(){

    3.        this.render();

    4.    }

    5.  

    6.    render() {

    7.        this.innerHTML = `

      Club Finder

      `
      ;

    8.    }

    9. }

    10.  

    11. customElements.define("app-bar", AppBar);


    Dengan begitu kita dapat mengubah penerapan app-bar pada index.html dengan menggunakan tag .



    1.       



    Terakhir, agar kode pada berkas app-bar.js tereksekusi, impor berkas app-bar.js pada berkas app.js, seperti ini:


    1. import "./src/script/component/app-bar.js";


    Tuliskan kode tersebut pada awal berkas app.js, sehingga keseluruhan kode pada berkasnya akan tampak seperti ini:


    1. import "./src/script/component/app-bar.js";

    2. import main from "./src/script/view/main.js";

    3.  

    4. document.addEventListener("DOMContentLoaded", main);


    Kemudian coba kita buka proyeknya menggunakan local server. Inilah tampilan hasilnya:
    2020031019350448bf1ff8d96767443908e81b171563c3.png
    Oops, tampilan App Bar tampak berantakan. Kita perlu memperbaiki css yang digunakan pada elemen App Bar sebelumnya. Buka berkas appbar.css lalu ubah selector-nya dari .app-bar menjadi app-bar.


    1. app-bar {

    2.    padding: 16px;

    3.    width: 100%;

    4.    background-color: cornflowerblue;

    5.    color: white;

    6.    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);

    7. }


    Lalu lihat kita lihat hasilnya.
    2020031019360718a18a9301e96c9237e9d2bd80d3dda2.png
    Yah, kini teks “Club Finder” tidak tampak karena background element tidak bekerja dengan baik. Kenapa begini yah? Pasalnya, custom element standarnya merupakan inline element, sehingga tidak akan mengisi panjang lebar parent element-nya. Solusinya adalah dengan mengubah sifat inline pada custom element menjadi block dengan cara menambahkan properti display dengan nilai block pada selector app-bar.


    1. app-bar {

    2.    display: block;

    3.    padding: 16px;

    4.    width: 100%;

    5.    background-color: cornflowerblue;

    6.    color: white;

    7.    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);

    8. }


    Dengan begitu tampilan kita berhasil membuat custom element  dengan baik!
    20200310193753886f026632febaa362d6775a39bbc28d.png

    Solution: Membuat search-bar Component

    Pembuatan elemen  lebih sedikit rumit dari pembuatan komponen sebelumnya, karena di dalam komponen search bar terdapat element  dan . Kombinasi kedua element tersebut digunakan dalam mencari data club. Sebisa mungkin kita membuat custom element  sehingga   mempermudah kala menggunakan komponen tersebut.
    Mari kita mulai dengan membuat berkas JavaScript baru dengan nama search-bar.js. Kemudian di dalamnya kita membuat class SearchBar dengan mewarisi sifat HTMLElement.


    1. class SearchBar extends HTMLElement {

    2.   

    3. }


    Kemudian kita implementasi method connectedCallback dan membuat fungsi render.

    1. class SearchBar extends HTMLElement {

    2.    connectedCallback(){

    3.  

    4.    }

    5.   

    6.    render() {

    7.       

    8.    }

    9. }


    Lalu panggil fungsi render() di dalam connectedCallback().


    1. class SearchBar extends HTMLElement {

    2.    connectedCallback(){

    3.        this.render();

    4.    }

    5.   

    6.    render() {

    7.  

    8.    }

    9. }


    Di dalam fungsi render kita ambil elemen yang dibutuhkan untuk ditampilkan dari berkas index.html.


    1. <div id="search-container" class="search-container">

    2.    <input placeholder="Search football club" id="searchElement" type="search">

    3.     <button id="searchButtonElement" type="submit">Search



    Agar mudah, copy seluruh kode tersebut dan paste untuk dijadikan nilai this.innerHTML di dalam fungsi render.


    1. class SearchBar extends HTMLElement {

    2.    connectedCallback(){

    3.        this.render();

    4.    }

    5.   

    6.    render() {

    7.        this.innerHTML = `

    8.        

    9.            

    10.            Search

    11.        

  •        `;

  •    }

  • }