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

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

يتكوّن معيار Web Component من ثلاثة أجزاء: نماذج 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 المرئي والقابل للتنسيق.

‫CodePen كما يظهر في نموذج 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>. في العنصر المخصّص، العنصر الذي يتضمّن السمة slot هو <legend>. ليس بالضرورة. في النموذج الذي نقدّمه، يتم استبدال <slot name="star-rating-legend"> بـ <anyElement slot="star-rating-legend">، حيث يمكن أن يكون <anyElement> أي عنصر، حتى لو كان عنصرًا مخصّصًا آخر.

العناصر غير المحدّدة

في <template>، استخدمنا عنصر <rating>. هذا ليس عنصرًا مخصّصًا. بل هو عنصر غير معروف. لا تفشل المتصفحات عندما لا تتعرف على عنصر ما. يتعامل المتصفح مع عناصر HTML غير المعروفة على أنها عناصر مضمنة مجهولة يمكن تصميمها باستخدام CSS. على غرار <span>، لا تتضمّن العناصر <rating> و<star-rating> أي أنماط أو دلالات مطبَّقة من وكيل المستخدم.

في CodePen هذا، لا يتم عرض <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) الظل.

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

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

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

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

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

Shadow DOM

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

بما أنّنا أضفنا المحتوى إلى shadow 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>

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

التنسيق خارج النطاق الحالي

من الممكن، ولكن ليس من السهل، تصميم المستند من داخل 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) مع مفاتيح الرسم البياني الثلاثة.

لاستهداف نموذج 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، ولن يتم عرضه تلقائيًا.
إجابة صحيحة.