Koâplab komponent turlari, masalan tablar, menyular, rasm galereyalari va boshqalar, render qilish uchun kontentga muhtoj.
Xuddi oârnatilgan brauzer <select> elementi <option> elementlarini kutganidek, bizning <custom-tabs> ham haqiqiy tab kontentini qabul qilishi mumkin. Va <custom-menu> menyu elementlarini kutishi mumkin.
<custom-menu>dan foydalanadigan kod quyidagicha koârinishi mumkin:
<custom-menu>
<title>Shirinlik menyusi</title>
<item>Lolipop</item>
<item>Mevali tost</item>
<item>Kek</item>
</custom-menu>
â¦Keyin bizning komponentimiz uni toâgâri render qilishi kerak, berilgan sarlavha va elementlar bilan chiroyli menyu sifatida, menyu hodisalarini boshqarishi va hokazo.
Buni qanday amalga oshirish mumkin?
Biz element kontentini tahlil qilishga va DOM tugunlarini dinamik ravishda nusxalash-qayta tartibga solishga harakat qilishimiz mumkin. Bu mumkin, lekin agar biz elementlarni shadow DOM ga koâchirayotgan boâlsak, unda hujjatdan CSS uslublari u yerda qoâllanilmaydi, shuning uchun vizual uslub yoâqolishi mumkin. Shuningdek, bu biroz kodlashni talab qiladi.
Yaxshiyamki, bizga buni qilish shart emas. Shadow DOM <slot> elementlarini qoâllab-quvvatlaydi, ular light DOM dan kontent bilan avtomatik toâldiriladi.
Nomlangan slotlar
Slotlar qanday ishlashini oddiy misolda koâraylik.
Bu yerda <user-card> shadow DOM ikki slot taqdim etadi, light DOM dan toâldiriladi:
<script>
customElements.define('user-card', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `
<div>Ism:
<slot name="username"></slot>
</div>
<div>Tug'ilgan kun:
<slot name="birthday"></slot>
</div>
`;
}
});
</script>
<user-card>
<span slot="username">Jon Smit</span>
<span slot="birthday">01.01.2001</span>
</user-card>
Shadow DOM da <slot name="X"> "kiritish nuqtasi"ni belgilaydi, slot="X" boâlgan elementlar render qilinadigan joy.
Keyin brauzer âkompozitsiyaâ amalga oshiradi: u light DOM dan elementlarni oladi va ularni shadow DOM ning tegishli slotlarida render qiladi. Oxirida biz aynan xohlagan narsaga ega boâlamiz â maâlumotlar bilan toâldirilishi mumkin boâlgan komponent.
Mana kompozitsiyani hisobga olmasdan skriptdan keyingi DOM tuzilmasi:
<user-card>
#shadow-root
<div>Ism:
<slot name="username"></slot>
</div>
<div>Tug'ilgan kun:
<slot name="birthday"></slot>
</div>
<span slot="username">Jon Smit</span>
<span slot="birthday">01.01.2001</span>
</user-card>
Biz shadow DOM yaratdik, demak u #shadow-root ostida. Endi elementda ham light, ham shadow DOM bor.
Render qilish maqsadlari uchun shadow DOM dagi har bir <slot name="..."> uchun brauzer light DOM da bir xil nomga ega slot="..." ni qidiradi. Bu elementlar slotlar ichida render qilinadi:
Natija âtekislanganâ DOM deb ataladi:
<user-card>
#shadow-root
<div>Ism:
<slot name="username">
<!-- slotlangan element slot ichiga kiritiladi -->
<span slot="username">Jon Smit</span>
</slot>
</div>
<div>Tug'ilgan kun:
<slot name="birthday">
<span slot="birthday">01.01.2001</span>
</slot>
</div>
</user-card>
â¦Lekin tekislangan DOM faqat render qilish va hodisalarni boshqarish maqsadlari uchun mavjud. Bu bir navi âvirtualâ. Narsalar shunday koârsatiladi. Lekin hujjatdagi tugunlar aslida harakatlantirilmaydi!
Buni agar querySelectorAll ni ishga tushirsak, osongina tekshirish mumkin: tugunlar hali ham oâz joylarida:
// light DOM <span> tugunlari hali ham bir xil joyda, `<user-card>` ostida
alert( document.querySelectorAll('user-card span').length ); // 2
Shunday qilib, tekislangan DOM slotlarni kiritish orqali shadow DOM dan olinadi. Brauzer uni render qiladi va uslub merosi, hodisalar tarqalishi uchun ishlatadi (bu haqda keyinroq). Lekin JavaScript hali ham hujjatni "bor hol"da, tekislashdan oldin koâradi.
slot="..." atributi faqat shadow host ning bevosita bolalari uchun toâgâri (bizning misolimizda <user-card> elementi). Ichki joylashgan elementlar uchun u eâtiborga olinmaydi.
Masalan, bu yerdagi ikkinchi <span> eâtiborga olinmaydi (<user-card> ning yuqori darajadagi bolasi emas):
<user-card>
<span slot="username">Jon Smit</span>
<div>
<!-- noto'g'ri slot, user-card ning bevosita bolasi bo'lishi kerak -->
<span slot="birthday">01.01.2001</span>
</div>
</user-card>
Agar light DOM da bir xil slot nomiga ega bir nechta element boâlsa, ular slotga birin-ketin qoâshiladi.
Masalan, bu:
<user-card>
<span slot="username">Jon</span>
<span slot="username">Smit</span>
</user-card>
<slot name="username"> da ikki element bilan bunday tekislangan DOM ni beradi:
<user-card>
#shadow-root
<div>Ism:
<slot name="username">
<span slot="username">Jon</span>
<span slot="username">Smit</span>
</slot>
</div>
<div>Tug'ilgan kun:
<slot name="birthday"></slot>
</div>
</user-card>
Slot zaxira kontenti
Agar biz <slot> ichiga biror narsa qoâysak, u zaxira, âsukut boâyichaâ kontent boâladi. Brauzer light DOM da tegishli toâldiruvchi boâlmasa, uni koârsatadi.
Masalan, shadow DOM ning ushbu qismida, agar light DOM da slot="username" boâlmasa, Anonim render qilinadi.
<div>Ism:
<slot name="username">Anonim</slot>
</div>
Sukut boâyicha slot: birinchi nomsiz
Shadow DOM dagi nomi boâlmagan birinchi <slot> âsukut boâyichaâ slot hisoblanadi. U light DOM dan boshqa joyga slotlanmagan barcha tugunlarni oladi.
Masalan, keling, foydalanuvchi haqida barcha slotlanmagan maâlumotlarni koârsatadigan sukut boâyicha slotni <user-card> ga qoâshaylik:
<script>
customElements.define('user-card', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `
<div>Ism:
<slot name="username"></slot>
</div>
<div>Tug'ilgan kun:
<slot name="birthday"></slot>
</div>
<fieldset>
<legend>Boshqa ma'lumotlar</legend>
<slot></slot>
</fieldset>
`;
}
});
</script>
<user-card>
<div>Men suzishni yaxshi ko'raman.</div>
<span slot="username">Jon Smit</span>
<span slot="birthday">01.01.2001</span>
<div>...Va voleybol o'ynashni ham!</div>
</user-card>
Barcha slotlanmagan light DOM kontenti âBoshqa maâlumotlarâ fieldset ichiga tushadi.
Elementlar slotga birin-ketin qoâshiladi, shuning uchun ikkala slotlanmagan maâlumot boâlagi sukut boâyicha slotda birga boâladi.
Tekislangan DOM quyidagicha koârinadi:
<user-card>
#shadow-root
<div>Ism:
<slot name="username">
<span slot="username">Jon Smit</span>
</slot>
</div>
<div>Tug'ilgan kun:
<slot name="birthday">
<span slot="birthday">01.01.2001</span>
</slot>
</div>
<fieldset>
<legend>Boshqa ma'lumotlar</legend>
<slot>
<div>Men suzishni yaxshi ko'raman.</div>
<div>...Va voleybol o'ynashni ham!</div>
</slot>
</fieldset>
</user-card>
Menyu misoli
Endi bob boshida tilga olingan <custom-menu> ga qaytaylik.
Biz elementlarni taqsimlash uchun slotlardan foydalanishimiz mumkin.
Mana <custom-menu> uchun markup:
<custom-menu>
<span slot="title">Shirinlik menyusi</span>
<li slot="item">Lolipop</li>
<li slot="item">Mevali tost</li>
<li slot="item">Kek</li>
</custom-menu>
Toâgâri slotlar bilan shadow DOM shabloni:
<template id="tmpl">
<style> /* menyu uslublari */ </style>
<div class="menu">
<slot name="title"></slot>
<ul><slot name="item"></slot></ul>
</div>
</template>
<span slot="title"><slot name="title">ga boradi.- Shablonda koâplab
<li slot="item">bor, lekin shablonda faqat bitta<slot name="item">. Shuning uchun barcha bunday<li slot="item">lar<slot name="item">ga birin-ketin qoâshiladi va shunday qilib roâyxat hosil boâladi.
Tekislangan DOM quyidagicha boâladi:
<custom-menu>
#shadow-root
<style> /* menyu uslublari */ </style>
<div class="menu">
<slot name="title">
<span slot="title">Shirinlik menyusi</span>
</slot>
<ul>
<slot name="item">
<li slot="item">Lolipop</li>
<li slot="item">Mevali tost</li>
<li slot="item">Kek</li>
</slot>
</ul>
</div>
</custom-menu>
Toâgâri DOM da <li> <ul> ning bevosita bolasi boâlishi kerakligini payqash mumkin. Lekin bu tekislangan DOM, u komponent qanday render qilinishini tasvirlaydi, bunday narsa bu yerda tabiiy ravishda sodir boâladi.
Biz faqat roâyxatni ochish/yopish uchun click ishlovchisini qoâshishimiz kerak va <custom-menu> tayyor:
customElements.define('custom-menu', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
// tmpl shadow DOM shabloni (yuqorida)
this.shadowRoot.append( tmpl.content.cloneNode(true) );
// biz light DOM tugunlarini tanlay olmaymiz, shuning uchun slotdagi bosishlarni boshqaraylik
this.shadowRoot.querySelector('slot[name="title"]').onclick = () => {
// menyuni ochish/yopish
this.shadowRoot.querySelector('.menu').classList.toggle('closed');
};
}
});
Mana toâliq demo:
Albatta, biz unga koâproq funksionallik qoâshishimiz mumkin: hodisalar, metodlar va hokazo.
Slotlarni yangilash
Agar tashqi kod dinamik ravishda menyu elementlarini qoâshish/olib tashlashni xohlasa-chi?
Brauzer slotlarni kuzatadi va slotlangan elementlar qoâshilsa/olib tashlansa, renderni yangilaydi.
Shuningdek, light DOM tugunlari nusxalanmagani, faqat slotlarda render qilingani uchun, ular ichidagi oâzgarishlar darhol koârinadigan boâladi.
Shuning uchun renderni yangilash uchun hech narsa qilishimiz shart emas. Lekin agar komponent kodi slot oâzgarishlari haqida bilishni xohlasa, slotchange hodisasi mavjud.
Masalan, bu yerda menyu elementi 1 soniyadan keyin dinamik ravishda kiritiladi va sarlavha 2 soniyadan keyin oâzgaradi:
<custom-menu id="menu">
<span slot="title">Shirinlik menyusi</span>
</custom-menu>
<script>
customElements.define('custom-menu', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `<div class="menu">
<slot name="title"></slot>
<ul><slot name="item"></slot></ul>
</div>`;
// shadowRoot da hodisa ishlovchilari bo'lishi mumkin emas, shuning uchun birinchi boladan foydalanamiz
this.shadowRoot.firstElementChild.addEventListener('slotchange',
e => alert("slotchange: " + e.target.name)
);
}
});
setTimeout(() => {
menu.insertAdjacentHTML('beforeEnd', '<li slot="item">Lolipop</li>')
}, 1000);
setTimeout(() => {
menu.querySelector('[slot="title"]').innerHTML = "Yangi menyu";
}, 2000);
</script>
Menyu render bizning aralashuvimiz siz har safar yangilanadi.
Bu yerda ikkita slotchange hodisasi bor:
-
Initsializatsiyada:
slotchange: titledarhol ishga tushadi, chunki light DOM danslot="title"tegishli slotga tushadi. -
1 soniyadan keyin:
slotchange: itemishga tushadi, yangi<li slot="item">qoâshilganda.
Eâtibor bering: 2 soniyadan keyin slotchange hodisasi yoâq, slot="title" kontenti oâzgartirilganda. Buning sababi slot oâzgarishi yoâq. Biz slotlangan element ichidagi kontentni oâzgartiramiz, bu boshqa narsa.
Agar biz JavaScript dan light DOM ning ichki oâzgarishlarini kuzatishni xohlasak, bu ham umumiyroq mexanizm yordamida mumkin: MutationObserver.
Slot API
Nihoyat, slot bilan bogâliq JavaScript metodlarini eslatib oâtaylik.
Avval koârganimizdek, JavaScript âhaqiqiyâ DOM ga, tekislashsiz qaradi. Lekin agar shadow daraxt {mode: 'open'} ga ega boâlsa, biz qaysi elementlar slotga tayinlanganini va aksincha, element boâyicha slotni aniqlay olamiz:
node.assignedSlotânodetayinlangan<slot>elementini qaytaradi.slot.assignedNodes({flatten: true/false})â slotga tayinlangan DOM tugunlari.flattenopsiyasi sukut boâyichafalse. Agar aniq ravishdatruega oârnatilsa, u tekislangan DOM ga chuqurroq qaradi, ichki komponentlar holatida ichki slotlarni va hech qanday tugun tayinlanmagan boâlsa zaxira kontentni qaytaradi.slot.assignedElements({flatten: true/false})â slotga tayinlangan DOM elementlari (yuqoridagi bilan bir xil, lekin faqat element tugunlari).
Bu metodlar biz slotlangan kontentni koârsatishimiz shart boâlmaganda, balki JavaScript da uni kuzatishimiz kerak boâlganda foydali.
Masalan, agar <custom-menu> komponenti nima koârsatayotganini bilishni xohlasa, u slotchange ni kuzatishi va slot.assignedElements dan elementlarni olishi mumkin:
<custom-menu id="menu">
<span slot="title">Shirinlik menyusi</span>
<li slot="item">Lolipop</li>
<li slot="item">Mevali tost</li>
</custom-menu>
<script>
customElements.define('custom-menu', class extends HTMLElement {
items = []
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `<div class="menu">
<slot name="title"></slot>
<ul><slot name="item"></slot></ul>
</div>`;
// slot kontenti o'zgarsa ishga tushadi
this.shadowRoot.firstElementChild.addEventListener('slotchange', e => {
let slot = e.target;
if (slot.name == 'item') {
this.items = slot.assignedElements().map(elem => elem.textContent);
alert("Elementlar: " + this.items);
}
});
}
});
// elementlar 1 soniyadan keyin yangilanadi
setTimeout(() => {
menu.insertAdjacentHTML('beforeEnd', '<li slot="item">Kek</li>')
}, 1000);
</script>
Xulosa
Odatda, agar elementda shadow DOM boâlsa, uning light DOM koârsatilmaydi. Slotlar light DOM dan elementlarni shadow DOM ning belgilangan joylarida koârsatish imkonini beradi.
Ikki xil slot mavjud:
- Nomlangan slotlar:
<slot name="X">...</slot>âslot="X"bilan light bolalarni oladi. - Sukut boâyicha slot: nomsiz birinchi
<slot>(keyingi nomsiz slotlar eâtiborga olinmaydi) â slotlanmagan light bolalarni oladi. - Agar bir xil slot uchun koâplab elementlar boâlsa â ular birin-ketin qoâshiladi.
<slot>elementining kontenti zaxira sifatida ishlatiladi. Slot uchun light bolalar boâlmasa koârsatiladi.
Slotlangan elementlarni ularning slotlari ichida render qilish jarayoni âkompozitsiyaâ deb ataladi. Natija âtekislangan DOMâ deb ataladi.
Kompozitsiya aslida tugunlarni siljitmaydi, JavaScript nuqtai nazaridan DOM hali ham bir xil.
JavaScript slotlarga metodlar yordamida kirishi mumkin:
slot.assignedNodes/Elements()âslotichidagi tugunlar/elementlarni qaytaradi.node.assignedSlotâ teskari xususiyat, tugun boâyicha slotni qaytaradi.
Agar biz nimani koârsatayotganimizni bilishni xohlasak, slot kontentini quyidagilar yordamida kuzatishimiz mumkin:
slotchangehodisasi â slot birinchi marta toâldirilganda va slotlangan elementning har qanday qoâshish/olib tashlash/almashtirish operatsiyasida ishga tushadi, lekin uning bolalari emas. Slotevent.targethisoblanadi.- MutationObserver slot kontentiga chuqurroq kirish, uning ichidagi oâzgarishlarni kuzatish uchun.
Endi light DOM dan elementlarni shadow DOM da qanday koârsatishni bilganimizdan keyin, ularni toâgâri uslublash haqida koâraylik. Asosiy qoida shundaki, shadow elementlar ichkarida, light elementlar esa tashqarida uslublanadi, lekin sezilarli istisnolar bor.
Tafsilotlarni keyingi bobda koâramiz.
Izohlar
<code>yorlig'ini ishlating, bir nechta satrlar uchun - ularni<pre>yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepenâ¦)