النموذج والخانة والظل

تتمثل الفائدة من مكونات الويب في سهولة إعادة استخدامها: يمكنك إنشاء أداة واجهة المستخدم مرة واحدة، وإعادة استخدامها عدة مرات. وبينما تحتاج إلى JavaScript لإنشاء مكوّنات ويب، لن تحتاج إلى مكتبة JavaScript. إنّ HTML وواجهات برمجة التطبيقات المرتبطة به توفّر كل ما تحتاجه.

يتكون معيار مكوّنات الويب من ثلاثة أجزاء: نماذج HTML والعناصر المخصّصة و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).

إنّ اشتراط تنفيذ نموذج من JavaScript لتنفيذ نموذج لتقييم نجوم واحد فقط ليس مفيدًا للغاية، ولكن من المفيد إنشاء مكوّن ويب لأداة تقييم نجوم قابلة للتخصيص يتم استخدامها بشكل متكرّر.

العنصر <slot>

نقوم بتضمين خانة لتضمين وسيلة إيضاح مخصصة لكل موضع ورود. يوفّر رمز HTML عنصر <slot> كعنصر نائب داخل <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 هي سمة عامة تُستخدَم لاستبدال محتوى <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، وهو نموذجنا. يرفق المتصفّح شجرة shadow DOM بالعقدة، مع إلحاق نسخة طبق الأصل من محتوى النموذج بـ shadow DOM. لاحظ أن العناصر التي يمكنك attachShadow() مقيدة عليها.

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

عند إلقاء نظرة على أدوات المطوّرين، ستلاحظ أنّ <form> من <template> هي جزء من جذر الظل لكل عنصر مخصص. يظهر نسخة طبق الأصل من محتوى <template> في كل عنصر مخصّص في أدوات المطوّرين ويمكن رؤيته في المتصفّح، ولكن لا يتم عرض محتوى العنصر المخصّص نفسه على الشاشة.

لقطة شاشة لأدوات مطوّري البرامج تعرض محتوى النموذج المنسوخ في كل عنصر مخصّص

في المثال <template>، ألحقنا محتوى النموذج بنص المستند، مع إضافة المحتوى إلى نموذج DOM العادي. في تعريف customElements، استخدمنا appendChild() نفسها، لكن تم إلحاق محتوى النموذج المنسوخ بـ shadow DOM مغلّف.

هل لاحظت كيف عادت النجوم إلى أزرار اختيار بدون نمط؟ لا ينطبق النمط ضمن علامة التبويب CSS من Codepen لأنّه جزء من shadow DOM بدلاً من نموذج DOM العادي. يتم تحديد أنماط CSS لعلامة التبويب هذه للمستند، وليس shadow DOM، لذلك لا يتم تطبيق الأنماط. يجب علينا إنشاء أنماط مغلّفة لنمط محتوى Shadow DOM المغلّف.

Shadow DOM

يحدِّد Shadow DOM أنماط CSS لكل شجرة ظل، ويعزلها عن بقية المستند. وهو ما يعني أن CSS الخارجي لا ينطبق على المكون الخاص بك، وأنّ أنماط المكوّنات ليس لها أي تأثير على باقي المستند، ما لم نوجهها عمدًا إلى تلك العناصر.

يمكننا تضمين عنصر <style> لتوفير CSS مضمّنًا في العنصر المخصّص، وذلك لأنّنا قد ألحقنا المحتوى بـ shadow DOM.

نظرًا لتحديد نطاق العنصر المخصص، لا داعي للقلق بشأن الأنماط التي تظهر على بقية المستند. يمكننا تقليل دقة المحددات بشكل كبير. على سبيل المثال، بما أنّ المدخلات الوحيدة المستخدمة في العنصر المخصّص هي أزرار الاختيار، يمكننا استخدام 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>

على الرغم من أنّ مكوّنات الويب مضمّنة في الترميز in-<template>، يتم تحديد أنماط CSS في shadow DOM ويكون مخفيًا من كل العناصر خارج المكوّنات، إلا أنّه لا يتم تضمين محتوى الخانة الذي يتم عرضه، وهو جزء <anyElement slot="star-rating-legend"> من <star-rating>.

التصميم خارج النطاق الحالي

من الممكن، ولكن ليس بسيطًا، تصميم المستند من داخل shadow DOM وتصميم محتوى shadow DOM من الأنماط العامة. يمكن تجاوز حدود الظل، حيث ينتهي shadow DOM ويبدأ نموذج DOM العادي، ولكن عن قصد فقط عن قصد.

شجرة الظل هي شجرة نموذج العناصر في المستند (DOM) داخل shadow DOM. جذر الظل هو العقدة الجذر لشجرة الظل.

تختار الفئة الزائفة في :host العنصر <star-rating>، وهو عنصر مضيف الظل. مضيف الظل هو عقدة DOM المرتبطة بـ shadow DOM. لاستهداف إصدارات معيّنة فقط من المضيف، استخدِم :host(). سيؤدي هذا إلى تحديد عناصر مضيف الظل التي تطابق المعلمة التي تم تمريرها، مثل فئة أو أداة اختيار السمة. ولتحديد جميع العناصر المخصصة، يمكنك استخدام star-rating { /* styles */ } في CSS العامة أو :host(:not(#nonExistantId)) في أنماط النماذج. ومن حيث الخصوصية، تكون خدمة CSS العالمية هي الفوز.

يتخطى العنصر الزائف ::slotted() حدود shadow DOM من داخل shadow DOM. وتختار عنصرًا محدَّدًا بخانات إذا كانت تتطابق مع أداة الاختيار. في المثال السابق، تطابق ::slotted(legend) مع وسائل الإيضاح الثلاث.

لاستهداف shadow 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 قائمة تحقق رائعة لإنشاء عناصر مخصصة. يمكنك أيضًا التعرّف على عناصر shadow DOM التعريفية.

التحقّق من استيعابك

اختبر معلوماتك عن القالب والخانة والظل.

ستقوم الأنماط التلقائية من خارج shadow DOM بتصميم العناصر بالداخل.

صحيح
يُرجى إعادة المحاولة.
خطأ
إجابة صحيحة

ما هي الإجابة التي تصف العنصر <template> الصحيح؟

عنصر عام يُستخدَم لعرض أي محتوى في صفحتك.
يُرجى إعادة المحاولة.
عنصر نائب
يُرجى إعادة المحاولة.
عنصر يُستخدم للإعلان عن أجزاء من HTML لن يتم عرضه بشكل تلقائي.
إجابة صحيحة