תבנית, יחידת קיבולת (Slot) והצללה

היתרון של רכיבי אינטרנט הוא השימוש החוזר שלהם: אפשר ליצור ווידג'ט בממשק המשתמש פעם אחת, ולעשות בו שימוש חוזר. בזמן ש צריכים JavaScript כדי ליצור רכיבי אינטרנט, אין צורך בספריית JavaScript. HTML וממשקי ה-API המשויכים מספקים את כל מה שאתם צריכים.

תקן רכיב האינטרנט מורכב משלושה חלקים: תבניות HTML, Custom Elements ואת Shadow DOM. יחד, הם מאפשרים לבנות רכיבים מותאמים אישית שעומדים בפני עצמם (מוסתרים) לשימוש חוזר, וניתן לשלב אותם בצורה חלקה. באפליקציות קיימות, כמו כל רכיבי HTML אחרים שכבר עסקנו.

בקטע הזה ניצור את הרכיב <star-rating>, רכיב אינטרנט שמאפשר למשתמשים לדרג חוויה סולם של כוכב אחד עד חמישה כוכבים. כשאתם נותנים שם לרכיב מותאם אישית, מומלץ להשתמש באותיות קטנות בלבד. כמו כן, הוסיפו מקף, כי כך ניתן להבחין בין רכיבים רגילים לבין רכיבים מותאמים אישית.

נדון בשימוש ברכיבים <template> ו-<slot>, במאפיין slot וב-JavaScript כדי ליצור תבנית עם Shadow DOM סגור. לאחר מכן נשתמש שוב ברכיב המוגדר, נבצע התאמה אישית של קטע טקסט, כמו כל רכיב או רכיב אינטרנט. בנוסף, נדון בקצרה על השימוש ב-CSS מתוך האלמנט המותאם אישית ומחוצה לו.

הרכיב <template>

הרכיב <template> משמש להצהרה על שכפול של קטעי HTML והוספתם ל-DOM באמצעות JavaScript. התוכן של הרכיב לא מעובד כברירת מחדל. במקום זאת, הם נוצרים באמצעות JavaScript.

<template id="star-rating-template">
  <form>
    <fieldset>
      <legend>Rate your experience:</legend>
      <rating>
        <input type="radio" name="rating" value="1" aria-label="1 star" required />
        <input type="radio" name="rating" value="2" aria-label="2 stars" />
        <input type="radio" name="rating" value="3" aria-label="3 stars" />
        <input type="radio" name="rating" value="4" aria-label="4 stars" />
        <input type="radio" name="rating" value="5" aria-label="5 stars" />
      </rating>
    </fieldset>
    <button type="reset">Reset</button>
    <button type="submit">Submit</button>
  </form>
</template>

התוכן של הרכיב <template> לא נכתב במסך, ולכן הנכס <form> והתוכן שלו לא עוברים רינדור. כן, ה-Codepen הזה ריק, אבל אם בודקים את כרטיסיית ה-HTML, רואים את תגי העיצוב <template>.

בדוגמה הזו, <form> הוא לא צאצא של <template> ב-DOM. במקום זאת, התוכן של רכיבי <template> הוא ילדים מתוך DocumentFragment שהוחזר על ידי HTMLTemplateElement.content לנכס. כדי שהתוכן יהיה גלוי, יש להשתמש ב-JavaScript כדי לשלוף את התוכן ולצרף אותו ל-DOM.

ה-JavaScript הקצר הזה לא יצר רכיב מותאם אישית. במקום זאת, הדוגמה הזו צירפה את התוכן של <template> ל-<body>. התוכן הפך לחלק מה-DOM הגלוי והסגנון.

צילום מסך של ה-codepen הקודם כפי שמוצג ב-DOM.

דרישה מ-JavaScript להטמעת תבנית עבור דירוג כוכבים אחד בלבד אינה שימושית מאוד, אבל יצירת רכיב אינטרנט עבור ווידג'ט של דירוג כוכבים בשימוש חוזר, שניתן להתאמה אישית.

הרכיב <slot>

אנחנו כוללים משבצת כדי לכלול מקרא מותאם אישית לכל אירוע. קוד ה-HTML מספק <slot> בתור placeholder בתוך <template>, אם מזינים שם, נוצר 'משבצת עם שם'. אפשר להשתמש במשבצת עם שם כדי להתאים אישית תוכן בתוך רכיב אינטרנט. הרכיב <slot> מאפשר לנו לקבוע איפה הצאצאים של התאמה אישית בתוך עץ הצללים שלו.

בתבנית שלנו, אנחנו משנים את <legend> ל-<slot>:

<template id="star-rating-template">
  <form>
    <fieldset>
      <slot name="star-rating-legend">
        <legend>Rate your experience:</legend>
      </slot>

המאפיין name משמש להקצאת יחידות קיבולת (Slot) לרכיבים אחרים אם לרכיב יש מאפיין משבצת שהערך שלו תואם ל- של משבצת זמן עם שם. אם לרכיב המותאם אישית אין התאמה למשבצת, התוכן של <slot> יעובד. לכן כללנו <legend> עם תוכן גנרי שאפשר לעבד אם מישהו כולל את <star-rating></star-rating>, ללא תוכן, ב-HTML.

<star-rating>
  <legend slot="star-rating-legend">Blendan Smooth</legend>
</star-rating>
<star-rating>
  <legend slot="star-rating-legend">Hoover Sukhdeep</legend>
</star-rating>
<star-rating>
  <legend slot="star-rating-legend">Toasty McToastface</legend>
  <p>Is this text visible?</p>
</star-rating>

המאפיין משבצת הוא מאפיין גלובלי שנעשה בו שימוש כדי להחליף את התוכן של <slot> בתוך <template>. ברכיב המותאם אישית שלנו, הרכיב עם המאפיין משבצת הוא <legend>. זו לא חובה. בתבנית שלנו, הערך <slot name="star-rating-legend"> יוחלף ב-<anyElement slot="star-rating-legend">, כאשר <anyElement> יכול להיות כל רכיב, אפילו עוד רכיב מותאם אישית.

רכיבים לא מוגדרים

ב-<template> השתמשנו ברכיב <rating>. זהו לא רכיב מותאם אישית. במקום זאת, זהו אלמנט לא ידוע. דפדפנים נכשלים כשהם לא מזהים רכיב. הדפדפן מתייחס לרכיבי HTML לא מזוהים כרכיבים אנונימיים מוטבעים שאפשר לעצב באמצעות CSS. בדומה ל-<span>, לא הופעל סוכן משתמש ברכיבים <rating> ו-<star-rating> או של סמנטיקה.

לתשומת ליבך, <template> והתוכן לא עוברים רינדור. <template> הוא רכיב ידוע שמכיל תוכן לא מיועד לעיבוד. הרכיב <star-rating> עדיין לא הוגדר. עד שנגדיר רכיב, הדפדפן יציג אותו כמו כל האלמנטים הלא מזוהים. בשלב הזה, המאפיין <star-rating> הלא מזוהה נחשב לאלמנט מוטבע אנונימי, לכן התוכן כולל מקרא ו<p> ב<star-rating> השלישי, כפי שהם היו מוצגים אם היו ב<span> במקום זאת.

נגדיר את הרכיב שלנו כדי להמיר את הרכיב הלא מזוהה הזה לרכיב מותאם אישית.

רכיבים מותאמים אישית

JavaScript נדרש כדי להגדיר רכיבים מותאמים אישית. לאחר ההגדרה, התוכן של הרכיב <star-rating> יוחלף ב- שורש הצללית שמכיל את כל התוכן של התבנית ששוייכו אליה. רכיבי <slot> מהתבנית מוחלפים בתוכן של הרכיב בתוך <star-rating> שערך המאפיין slot שלו תואם לערך השם של <slot>, אם יש אחד. אם לא, יוצג תוכן המשבצות של התבנית.

תוכן בתוך רכיב מותאם אישית שאינו משויך למיקום — <p>Is this text visible?</p> ב-<star-rating> השלישי – לא נכלל ב- שורש הצללית ולכן הוא לא מוצג.

אנחנו מגדירים את הרכיב המותאם אישית בשם star-rating על ידי הרחבה של HTMLElement:

customElements.define('star-rating',
  class extends HTMLElement {
    constructor() {
      super(); // Always call super first in constructor
      const starRating = document.getElementById('star-rating-template').content;
      const shadowRoot = this.attachShadow({
        mode: 'open'
      });
      shadowRoot.appendChild(starRating.cloneNode(true));
    }
  });

עכשיו, אחרי שהרכיב מוגדר, בכל פעם שהדפדפן ייתקל ברכיב <star-rating>, הוא יעובד כפי שהוגדר לרכיב עם המאפיין #star-rating-template, שהוא התבנית שלנו. הדפדפן יצרף עץ DOM צללים לצומת, בצירוף שכפול של תוכן התבנית ל-DOM של הצללית הזו. חשוב לשים לב שהרכיבים שבהם אפשר attachShadow() הם מוגבלים.

const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(starRating.cloneNode(true));

אם תסתכלו בכלים למפתחים, תוכלו לראות שה-<form> מ-<template> הוא חלק משורש הצללית של כל רכיב מותאם אישית. אפשר לראות שכפול של התוכן מ-<template> בכל רכיב מותאם אישית בכלים למפתחים, אבל הוא מופיע בדפדפן של הרכיב המותאם אישית עצמו לא מעובדים במסך.

צילום מסך של כלי הפיתוח שבו מוצג תוכן התבנית המשוכפלת בכל רכיב מותאם אישית.

בדוגמה <template>, הוספנו את תוכן התבנית לגוף המסמך, וכך הוספנו את התוכן ל-DOM הרגיל. בהגדרה customElements, השתמשנו באותה appendChild(), אבל תוכן התבנית המשוכפלת צורף DOM של צללית בתוך הארגון.

שמת לב איך הכוכבים חזרו להיות לחצני בחירה לא מעוצבים? מכיוון שהיא חלק מ-DOM של צללית ולא מ-DOM הרגיל, העיצוב בכרטיסיית CSS של Codepen לא רלוונטי. שירות ה-CSS של הכרטיסייה הזו סגנונות מוגדרים למסמך, לא ל-DOM של הצללית, כך שהסגנונות לא מוחלים. אנחנו צריכים ליצור מקודדים כדי לעצב את התוכן המוסתר של Shadow DOM.

DOM של הצללה

השדה של Shadow DOM מגדיר היקף של סגנונות CSS לכל עץ צלל, ומבודד אותו משאר המסמך. כלומר שירות CSS חיצוני לא רלוונטי לרכיב שלך, ולסגנונות של הרכיבים אין השפעה על שאר המסמך, אלא אם אנחנו עושים זאת באופן מכוון להפנות אותו אליהם.

מכיוון שצירפנו את התוכן ל-DOM של צל, נוכל לכלול רכיב <style> לספק CSS מוטמע לרכיב המותאם אישית.

מאחר בהיקף של הרכיב המותאם אישית, אנחנו לא צריכים לחשוש מסגנונות שיופיעו עד לשאר המסמך. אנחנו יכולים ולצמצם באופן משמעותי את מידת הספציפיות של הסלקטורים. לדוגמה, ערכי הקלט היחידים שנעשה בהם שימוש ברכיב המותאם אישית הם רדיו. אנחנו יכולים להשתמש ב-input במקום ב-input[type="radio"] כבורר.

 <template id="star-rating-template">
  <style>
    rating {
      display: inline-flex;
    }
    input {
      appearance: none;
      margin: 0;
      box-shadow: none;
    }
    input::after {
      content: '\2605'; /* solid star */
      font-size: 32px;
    }
    rating:hover input:invalid::after,
    rating:focus-within input:invalid::after {
      color: #888;
    }
    input:invalid::after,
      rating:hover input:hover ~ input:invalid::after,
      input:focus ~ input:invalid::after  {
      color: #ddd;
    }
    input:valid {
      color: orange;
    }
    input:checked ~ input:not(:checked)::after {
      color: #ccc;
      content: '\2606'; /* hollow star */
    }
  </style>
  <form>
    <fieldset>
      <slot name="star-rating-legend">
        <legend>Rate your experience:</legend>
      </slot>
      <rating>
        <input type="radio" name="rating" value="1" aria-label="1 star" required/>
        <input type="radio" name="rating" value="2" aria-label="2 stars"/>
        <input type="radio" name="rating" value="3" aria-label="3 stars"/>
        <input type="radio" name="rating" value="4" aria-label="4 stars"/>
        <input type="radio" name="rating" value="5" aria-label="5 stars"/>
      </rating>
    </fieldset>
    <button type="reset">Reset</button>
    <button type="submit">Submit</button>
  </form>
</template>

רכיבי אינטרנט מכוסים באמצעות תגי עיצוב וסגנונות CSS בתוך <template>, בהיקף של DOM צללים ומוסתרים. מכל מה שמחוץ לרכיבים, תוכן מיקומי המשבצות שמעובד, <anyElement slot="star-rating-legend"> של <star-rating>, לא נכלל.

העיצוב לא כלול בהיקף הנוכחי

אפשר, אבל לא פשוט, לעצב את המסמך מתוך DOM של צל ולעצב את התוכן של DOM של צל את הסגנונות הגלובליים. ניתן לעבור את גבולות הצללית, המקום שבו מסתיימת ה-DOM של הצללה וה-DOM הרגיל מתחיל, אבל רק באופן מכוון.

עץ הצללים הוא עץ ה-DOM שבתוך DOM של הצללות. שורש הצל הוא צומת השורש של עץ הצל.

המחלקה המדומה :host בוחרת את <star-rating>, רכיב המארח של הצללית. מארח הצללית הוא צומת ה-DOM שאליו מוצמד ה-DOM של הצללית. כדי לטרגט רק לגרסאות ספציפיות של המארח, משתמשים ב-:host(). הפעולה הזו תבחר רק את רכיבי מארח הצללית שתואמים לפרמטר שהועבר, כמו מחלקה או בורר מאפיינים. כדי לבחור את כל הרכיבים המותאמים אישית, תוכלו להשתמש ב-star-rating { /* styles */ } ב-CSS הגלובלי או ב-:host(:not(#nonExistantId)) בסגנונות התבנית. במונחים ספציפיות, שירות ה-CSS הגלובלי מנצח.

הרכיב ::slotted() חוצה את גבול ה-DOM של הצל מתוך ה-DOM של הצל. תתבצע בחירה של רכיב מחורר אם הוא תואם לסלקטור. בדוגמה שלנו, ::slotted(legend) תואם לשלושת המקראים שלנו.

כדי לטרגט DOM של צללית מ-CSS בהיקף הגלובלי, צריך לערוך את התבנית. part לכל רכיב שרוצים לעצב. לאחר מכן משתמשים ברכיב המדומה ::part() כדי להתאים רכיבים בתוך עץ צללים שתואמים לפרמטר שעבר. רכיב העוגן או רכיב המקור של הרכיב המדומה הוא המארח, או שם הרכיב המותאם אישית, במקרה הזה star-rating. הפרמטר הוא הערך של המאפיין part.

אם תגי העיצוב של התבנית שלנו מתחילים כך:

<template id="star-rating-template">
  <form part="formPart">
    <fieldset part="fieldsetPart">

נוכל לטרגט את <form> ואת <fieldset> באמצעות:

star-rating::part(formPart) { /* styles */ }
star-rating::part(fieldsetPart) { /* styles */ }

שמות חלקים פועלים באופן דומה למחלקות: רכיב יכול להיות בעל שמות חלקים המופרדים ברווחים, ויכול להיות שרכיבים מרובים הם בעלי שם חלק זהה.

ל-Google יש רשימת משימות מעולה ליצירת רכיבים מותאמים אישית. עוד נושאים שאולי יעניינו אותך על DOMs הצהרתיים של צללים.

בדיקת ההבנה

בוחנים את הידע שלכם לגבי תבנית, משבצת וצל.

כברירת מחדל, סגנונות שמחוץ ל-DOM של הצללית יגרמו לסגנון של הרכיבים הפנימיים.

True.
אפשר לנסות שוב.
לא נכון.
תשובה נכונה!

איזו תשובה היא תיאור נכון של הרכיב <template>?

רכיב גנרי המשמש להצגת תוכן כלשהו בדף.
אפשר לנסות שוב.
רכיב placeholder.
אפשר לנסות שוב.
רכיב שמשמש להצהרה על קטעי HTML, שלא יעובדו כברירת מחדל.
תשובה נכונה!