اذهب إلى المحتوى

البحث في الموقع

المحتوى عن 'html template'.

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المحتوى


التصنيفات

  • الإدارة والقيادة
  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • السلوك التنظيمي في المؤسسات
  • عالم الأعمال
  • التجارة والتجارة الإلكترونية
  • نصائح وإرشادات
  • مقالات ريادة أعمال عامة

التصنيفات

  • مقالات برمجة عامة
  • مقالات برمجة متقدمة
  • PHP
    • Laravel
    • ووردبريس
  • جافاسكربت
    • لغة TypeScript
    • Node.js
    • React
    • Vue.js
    • Angular
    • jQuery
    • Cordova
  • HTML
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • SQL
  • لغة C#‎
    • ‎.NET
    • منصة Xamarin
  • لغة C++‎
  • لغة C
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • لغة Rust
  • برمجة أندرويد
  • لغة R
  • الذكاء الاصطناعي
  • صناعة الألعاب
  • سير العمل
    • Git
  • الأنظمة والأنظمة المدمجة

التصنيفات

  • تصميم تجربة المستخدم UX
  • تصميم واجهة المستخدم UI
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب GIMP
    • كريتا Krita
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
    • Blender
  • نصائح وإرشادات
  • مقالات تصميم عامة

التصنيفات

  • مقالات DevOps عامة
  • خوادم
    • الويب HTTP
    • البريد الإلكتروني
    • قواعد البيانات
    • DNS
    • Samba
  • الحوسبة السحابية
    • Docker
  • إدارة الإعدادات والنشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
    • ريدهات (Red Hat)
  • خواديم ويندوز
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH
  • شبكات
    • سيسكو (Cisco)

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • استسراع النمو
  • المبيعات
  • تجارب ونصائح
  • مبادئ علم التسويق

التصنيفات

  • مقالات عمل حر عامة
  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • العمل الحر المهني
    • العمل بالترجمة
    • العمل كمساعد افتراضي
    • العمل بكتابة المحتوى

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجنتو
    • بريستاشوب
    • أوبن كارت
    • دروبال
  • الترجمة بمساعدة الحاسوب
    • omegaT
    • memoQ
    • Trados
    • Memsource
  • برامج تخطيط موارد المؤسسات ERP
    • تطبيقات أودو odoo
  • أنظمة تشغيل الحواسيب والهواتف
    • ويندوز
    • لينكس
  • مقالات عامة

التصنيفات

  • آخر التحديثات

أسئلة وأجوبة

  • الأقسام
    • أسئلة البرمجة
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات

التصنيفات

  • كتب ريادة الأعمال
  • كتب العمل الحر
  • كتب تسويق ومبيعات
  • كتب برمجة
  • كتب تصميم
  • كتب DevOps

ابحث في

ابحث عن


تاريخ الإنشاء

  • بداية

    نهاية


آخر تحديث

  • بداية

    نهاية


رشح النتائج حسب

تاريخ الانضمام

  • بداية

    نهاية


المجموعة


النبذة الشخصية

تم العثور على 2 نتائج

  1. تعرفنا في المقال السابق مكونات الويب: عناصر HTML المخصصة وقوالبها على عناصر HTML المخصصة وكيفية إنشائها وسنكمل التعرف على موضوع مهم في هذا الصدد وهو شجرة DOM الخفية Shadow DOM -والتي تترجم أيضًا إلى ظل شجرة DOM- تستخدَم للتغليف encapsulation، والتي تسمح لكل مكوِّن ويب مخصص أن يمتلك شجرةً خفيةً لا يمكن للمستند الرئيسي الوصول إليها صدفةً، وقد تمتلك قواعد تنسيق خاصة وغير ذلك من الخواص. شجرة DOM الخفية هل لديك فكرة عن مدى تعقيد إنشاء وتنسيق أدوات تحكم المتصفح؟ لنفترض وجود العنصر <"input type="range> مثلًا: input[type="range" i] { appearance: auto; cursor: default; color: rgb(157, 150, 142); padding: initial; border: initial; margin: 2px; } يستخدم المتصفح ضمنيًا DOM/CSS لرسم أدوات التحكم، ولا تظهر بنية شجرة DOM لنا عادةً، لكن يمكننا رؤيتها باستخدام أدوات التطوير بتفعيل الخيار Show user agent shadow DOM من إعدادات أدوات المطور Dev Tools تحديدًا قسم Elements في المتصفح Chrome مثلًا. ستبدو الشجرة DOM للعنصر السابق كالتالي: ما نراه تحت العنوان shadow-root# يسمَّى شجرة DOM المخفية، ولا يمكن الحصول على عناصر شجرة DOM المخفية المضمنة باستخدام استدعاءات JavaScript النظامية أو المحددات selectors، فهذه العناصر ليست أبناءً نظاميةً، بل هي تقنية تغليف متقدمة. سنرى في المثال السابق السمة المفيدة pseudo، وهي سمة غير قياسية وموجودة لأسباب تاريخية، حيث يمكننا استخدامها لتنسيق العناصر الفرعية باستخدام CSS كالتالي: <style> /* make the slider track red */ input::-webkit-slider-runnable-track { background: red; } </style> <input type="range"> See the Pen JS-p3-06-Shadow-DOM -ex1 by Hsoub (@Hsoub) on CodePen. بدأت المتصفحات باختبار الشيفرة بالاستفادة من بنى DOM الداخلية لتجهيز أدوات التحكم، وبعد فترة من الزمن طوّرت شجرة DOM الخفية وأصبحت معياريةً لتسمح لنا وللمطورين بالقيام بأشياء مماثلة. سنستخدم لاحقًا المعيار الحديث لشجرة DOM الخفية الذي تغطيه مواصفات DOM وغيرها من المواصفات ذات الصلة. الشجرة الخفية Shadow tree يمكن لكائن DOM أن يمتلك نوعين من الأشجار الفرعية: شجرة ظاهرة Light tree: وتمثل شجرةً فرعيةً نظاميةً مكوّنةً من عناصر HTML أبناء، وجميع الأشجار التي رأيناها في المقال السابق من هذا النوع. شجرة خفية Shadow tree: شجرة DOM فرعية لا تظهر على صفحة HTML، بل تكون مخفيةً من الظهور والرؤية. إذا امتلك العنصر الشجرتين السابقتين فسيصيّر المتصفح الشجرة الخفية، لكن يمكن إعداد توليفة بين الشجرتين، وسنرى تفاصيل أكثر في مقالات لاحقة. يمكن استخدام الشجرة الخفية في العناصر المخصصة لإخفاء المكوّنات الداخلية وتطبيق تنسيق خاص بالمكوِّن. يخفي العنصر <show-hello> على سبيل المثال شجرة DOM الداخلية ضمن شجرة خفية: <script> customElements.define('show-hello', class extends HTMLElement { connectedCallback() { const shadow = this.attachShadow({mode: 'open'}); shadow.innerHTML = `<p> Hello, ${this.getAttribute('name')} </p>`; } }); </script> <show-hello name="John"></show-hello> See the Pen JS-p3-06-Shadow-DOM -ex2 by Hsoub (@Hsoub) on CodePen. ستظهر النتيجة في أدوات تطوير متصفح بالشكل التالي، حيث تكون جميع المحتويات تحت "shadow-root#": يُنشئ الاستدعاء ({...:elem.attachShadow({mode شجرةً مخفيةً أولًا، لكن توجد عقبتان: يمكن إنشاء جذر مخفي واحد لكل عنصر. يجب أن يكون العنصر عنصرًا مخصصًا، أو أحد العناصر التالية: "article". "blockquote". "body". "div". "footer". "h1…h6". "header". "main". "nav". "p". "section". "span". ولا يمكن لغير هذه العناصر استضافة شجرة خفية. يضبط الخيار mode مستوى التغليف، ويمكن أن يحمل إحدى القيمتين: "open": يمكن أن نحصل على الجذر الخفي من خلال elem.shadowRoot، ويمكن لأي شيفرة الوصول إلى الشجرة الخفية لــ elem. "closed": ويعيد الأمر elem.shadowRoot القيمة null دائمًا. يمكن الوصول إلى شجرة DOM الخفية عن طريق المرجع الذي يعيده attachShadow فقط، ومن الممكن أن يكون مخفيًا ضمن صنف، وتكون الأشجار الخفية الأصلية المتعلقة بالمتصفح -مثل <input type="range"‎>- من النوع المغلق، ولا يمكن الوصول إليها، ويشابه الجذر الخفي shadow root الذي يعيده attachShadow العناصر، ويمكن إظهاره باستخدام innerHTML أو توابع DOM مثل append. يُدعى العنصر الذي يمتلك جذرًا خفيًا بمضيف الشجرة الخفية shadow tree host، ويتاح للاستخدام ضمن الخاصية host للجذر الخفي: // لن يعيد شيئًا elem.shadowRoot وإلا {mode: "open"} بافتراض أن alert(elem.shadowRoot.host === elem); // محقق التغليف Encapsulation تفصَل شجرة DOM الخفية عن المستند الرئيسي: لا تكون عناصر شجرة DOM الخفية مرئيةً للتابع querySelector من شجرة DOM الظاهرة، وقد تمتلك عناصر شجرة DOM الخفية معرفات id تتعارض مع تلك الموجودة في شجرة DOM الظاهرة، فهي فريدة ضمن الشجرة الخفية فقط. لشجرة DOM الخفية أوراق تنسيق خاصة بها، إذ لا تُطبق قواعد التنسيق الخارجية عليها. إليك مثالًا: <style> /*(1) #elem لن يطبق التنسيق على الشجرة الخفية للعنصر*/ p { color: red; } </style> <div id="elem"></div> <script> elem.attachShadow({mode: 'open'}); // (للشجرة الخفية تنسيقها الخاص (2 elem.shadowRoot.innerHTML = ` <style> p { font-weight: bold; } </style> <p>Hello, John!</p> `; // مرئية فقط للاستعلامات التي تجري داخل الشجرة <p> (3) alert(document.querySelectorAll('p').length); // 0 alert(elem.shadowRoot.querySelectorAll('p').length); // 1 </script> تشير (1) و(2) و(3) أعلاه إلى الآتي: (1) لا يؤثر تنسيق الصفحة على تنسيق الشجرة الخفية. (2) يؤدي التنسيق داخل الشجرة عمله بطريقة صحيحة. (3) للحصول على العناصر من الشجرة الخفية، لا بدّ من الاستعلام عنها من داخل الشجرة. تقليم شجرة DOM الخفية وعملية التركيب تحتاج العديد من المكوّنات إلى محتوىً لتصييره، مثل النوافذ ومعارض الصور والقوائم وغيرها، ويتوقع العنصر المخصص <custom-tabs> محتوى نافذة ليمرَّر إليه، على غرار العناصر الأصلية المضمنة في المتصفح مثل <select> -عدا عناصر <option>-، ويحتاج العنصر <custom-menu> كذلك إلى محتوى على شكل عناصر قائمة. قد تبدو الشيفرة التي تستعمل العنصر <custom-menu> كالتالي: <custom-menu> <title>Candy menu</title> <item>Lollipop</item> <item>Fruit Toast</item> <item>Cup Cake</item> </custom-menu> سيتمكن بعدها مكوّننا من تصيير المحتوى بصورة صحيحة، على شكل قائمة جميلة لها عنوان وعناصر ومعالجات لأحداث القائمة وغيرها، ولكن كيف سننجز ذلك؟ يمكننا أن نحاول تحليل محتوى العناصر وأن ننسخ أو نعيد ترتيب عقد DOM ديناميكيًا، لكن إذا كنا سننقل عناصر إلى شجرة DOM الخفية shadow DOM، فلن يُطبَّق تنسيق CSS الموجود ضمن المستند عليها، وبالتالي سنفقد التنسيق المرئي، وسيحتاج ذلك إلى كتابة بعض الشيفرة، ولحسن الحظ ليس علينا فعل ذلك، إذ تدعم شجرة DOM الخفية عناصر <slot> التي تُملأ تلقائيًا بالمحتوى الموجود في شجرة DOM الظاهرة وشبهنا هذه العملية بعملية "التقليم" التي تجرى على الأشجار من قص وإنشاء فتحة في الشجرة ووضع فرع في الفتحة من شجرة أو نوع آخر لذلك أطلقنا عليها هذا الاسم وسنستعمل اسم "فتحة" المقابل للكلمة الأجنبية slot (اسم العنصر) للسهولة. الفتحات المسماة Named slots لنلق نظرةً على طريقة عمل التقليمات في هذا المثال البسيط، حيث توفر شجرة DOM الخفية للعنصر المخصص <user-card> فتحتين تُملآن من شجرة DOM الظاهرة: <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = ` <div>Name: <slot name="username"></slot> </div> <div>Birthday: <slot name="birthday"></slot> </div> `; } }); </script> <user-card> <span slot="username">John Smith</span> <span slot="birthday">01.01.2001</span> </user-card> See the Pen JS-p3-06-Shadow-DOM -ex3 by Hsoub (@Hsoub) on CodePen. يعرّف العنصر <"slot name="X> في شجرة DOM الخفية نقطة إدراج، وهو المكان الذي تُصيَّر فيه العناصر التي تعرف فيها الخاصية "slot="X، ثم ينفِّذ المتصفح عملية تركيب composition، إذ يأخذ عناصر من شجرة DOM الظاهرة، ويصيّرها ضمن الفتحات الملائمة ضمن شجرة DOM الخفية، وهكذا سنحصل في نهاية الأمر على مكوِّن يمكن ملؤه بالبيانات، وهذا ما نريده. إليك بنية الشجرة DOM بعد تنفيذ السكربت مهملين عملية التركيب: <user-card> #shadow-root <div>Name: <slot name="username"></slot> </div> <div>Birthday: <slot name="birthday"></slot> </div> <span slot="username">John Smith</span> <span slot="birthday">01.01.2001</span> </user-card> لقد أنشأنا شجرة DOM خفيةً، وسنجدها تحت ‎#shadow-root. سيمتلك العنصر الآن شجرتي DOM الظاهرة والخفية. سيبحث المتصفح -لأغراض التصيير- عن السمة "..."=slot لكل <"..."=slot name> التي لها نفس الاسم في شجرة DOM الظاهرة، وستُصيَّر هذه العناصر ضمن الفتحات. وعندها ستسمى النتيجة شجرة DOM المسطّحة flattened DOM. <user-card> #shadow-root <div>Name: <slot name="username"> <!-- slotted element is inserted into the slot --> <span slot="username">John Smith</span> </slot> </div> <div>Birthday: <slot name="birthday"> <span slot="birthday">01.01.2001</span> </slot> </div> </user-card> لقد أوُجِدت شجرة DOM المسطحة لأغراض التصيير ومعالجة الأحداث، قد تبدو الطريقة التي تُعرض بها الأمور افتراضيةً نوعًا ما، لكن لن تُنقَل العقد في المستند فعليًا، فمن السهل التحقق من أنّ العقد لا تزال في مكانها بتنفيذ الأمر querySelectorAll. alert( document.querySelectorAll('user-card span').length ); // 2 تُشتقّ شجرة DOM المسطحة من شجرة DOM الخفية بإدراج الفتحات، حيث يصيّرها المتصفح ويستخدمها في وراثة التنسيق وانتقال الأحداث -وهو ما سنوضّحه لاحقًا-، لكن نظرتها لن تغير JavaScript إلى المستند قبل الوصول إلى الشجرة المسطحة. ملاحظة: يمكن للأبناء من المستوى الأعلى فقط امتلاك السمة "…"=slot، إذ يمتلك الأبناء المباشرون للمضيف الخفي -وهو العنصر <user-card> في مثالنا- السمة "..."=slot فقط، بينما ستُهمل هذه السمة للعناصر الداخلية. وسيتجاهل المتصفح في المثال التالي العنصر <span> الثاني، فهو ليس ابنًا مباشرًا للعنصر <user-card>: <user-card> <span slot="username">John Smith</span> <div> <!-- invalid slot, must be direct child of user-card --> <span slot="birthday">01.01.2001</span> </div> </user-card> إذا وجدت عناصر لها فتحات بنفس الاسم في شجرة DOM الظاهرة، فستوضع في الفتحة واحدةً تلو الأخرى، إليك المثال التالي: <user-card> <span slot="username">John</span> <span slot="username">Smith</span> </user-card> إليك مثالًا عن شجرة DOM مسطحة تمتلك عنصرين ضمن الفتحة <"slot name="username>: <user-card> #shadow-root <div>Name: <slot name="username"> <span slot="username">John</span> <span slot="username">Smith</span> </slot> </div> <div>Birthday: <slot name="birthday"></slot> </div> </user-card> المحتوى الابتدائي للعنصر slot إذا وُضع محتوًى ما ضمن الفتحة <slot>، فسيصبح هو المحتوى الافتراضي حيث سيعرضه المتصفح إذا لم يوجد محتوىً مناسب ضمن شجرة DOM الظاهرة لملء الفتحة. عندها ستكون نتيجة تصيير هذا الجزء مثلًا من شجرة DOM الخفية هي Anonymous إذا لم توجد فتحة باسم "slot="username في شجرة DOM الظاهرة. <div>Name: <slot name="username">Anonymous</slot> </div> الفتحة الافتراضية هي أول فتحة بلا اسم ستكون أول فتحة <slot> في شجرة DOM الخفية غير مالكة لاسم هي الفتحة الافتراضية، وستحصل على كل العقد في شجرة DOM الظاهرة التي لم تُدرج في أي فتحة أخرى. لنضف في المثال التالي فتحةً افتراضيةً للعنصر المخصص <user-card> الذي سيخفي كل معلومات المستخدم غير المدرجة في فتحات: <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = ` <div>Name: <slot name="username"></slot> </div> <div>Birthday: <slot name="birthday"></slot> </div> <fieldset> <legend>Other information</legend> <slot></slot> </fieldset> `; } }); </script> <user-card> <div>I like to swim.</div> <span slot="username">John Smith</span> <span slot="birthday">01.01.2001</span> <div>...And play volleyball too!</div> </user-card> See the Pen JS-p3-06-Shadow-DOM -ex4 by Hsoub (@Hsoub) on CodePen. سيوضع محتوى شجرة DOM الظاهرة غير المُدرج في المثال السابق ضمن العنصر <fieldset> الذي يحمل تسمية Other information، حيث ستُدرج العناصر ضمن الفتحة واحدًا تلو الآخر، ولذا ستُدرج قطعتا المعلومات معًا ضمن الفتحة الافتراضية. وستبدو شجرة DOM المسطحة كالتالي: <user-card> #shadow-root <div>Name: <slot name="username"> <span slot="username">John Smith</span> </slot> </div> <div>Birthday: <slot name="birthday"> <span slot="birthday">01.01.2001</span> </slot> </div> <fieldset> <legend>Other information</legend> <slot> <div>I like to swim.</div> <div>...And play volleyball too!</div> </slot> </fieldset> </user-card> مثال القائمة لنعد الآن إلى العنصر المخصص الذي أشرنا إليه سابقًا في هذا المقال، حيث يمكن استخدام الفتحات لتوزيع العناصر، إليك ترميز HTML للعنصر المخصص <custom-menu>: <custom-menu> <span slot="title">Candy menu</span> <li slot="item">Lollipop</li> <li slot="item">Fruit Toast</li> <li slot="item">Cup Cake</li> </custom-menu> تمثل الشيفرة التالية قالب شجرة DOM الخفية مزوّدةً بالفتحات الملائمة: <template id="tmpl"> <style> /* menu styles */ </style> <div class="menu"> <slot name="title"></slot> <ul><slot name="item"></slot></ul> </div> </template> يُدرج <"span slot="title> ضمن <"slot name="title>. هناك العديد من عناصر القائمة <li slot="item"> في القالب، لكن توجد فتحة مناسبة <slot name="item"> واحدة ضمنه، لذلك ستُدرج كل عناصر القائمة -مثل <li slot="item">- ضمن الفتحة الواحد تلو الآخر، وهذا ما يُشكِّل القائمة. ستصبح شجرة DOM المسطّحة كالتالي: <custom-menu> #shadow-root <style> /* menu styles */ </style> <div class="menu"> <slot name="title"> <span slot="title">Candy menu</span> </slot> <ul> <slot name="item"> <li slot="item">Lollipop</li> <li slot="item">Fruit Toast</li> <li slot="item">Cup Cake</li> </slot> </ul> </div> </custom-menu> قد نُلاحظ تواجد العنصر <li> ضمن القائمة <ul> في شجرة DOM صالحة، لكن الشجرة في الشيفرة السابقة مسطحة تصف آلية تصيير المكوِّن، وهذا الأمر طبيعي، ويبقى علينا إضافة معالج للحدث click لفتح وإغلاق القائمة <custom-menu> لتصبح جاهزةً: customElements.define('custom-menu', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); //هو قالب الشجرة الخفية في الأعلى this.shadowRoot.append( tmpl.content.cloneNode(true) ); // لا يمكن اختيار عقد من الشجرة الظاهرة، لذلك تُعالج النقرات ضمن الفتحة this.shadowRoot.querySelector('slot[name="title"]').onclick = () => { // فتح وإغلاق القائمة this.shadowRoot.querySelector('.menu').classList.toggle('closed'); }; } }); وستكون النتيجة: See the Pen JS-p3-06-Shadow-DOM -ex5 by Hsoub (@Hsoub) on CodePen. يمكن بالطبع تعزيز وظائف القائمة من خلال الأحداث والتوابع وغيرها. تحديث الفتحات ماذا لو أرادت الشيفرة الخارجية إضافة أو إزالة العناصر ديناميكيًا؟ يراقب المتصفح الفتحات، ثم تُحدَّث العناصر المصيّرة إذا تغيرت أو أزيلت العناصر المدرجة، وطالما ستُصيَّر العناصر الظاهرة فقط في الفتحات -ولن تُنسَخ- فستظهر أي تغييرات عليها مباشرةً، فلا حاجة لفعل أي شيء لتحديث نتيجة التصيير، لكن يمكن استخدام الحدث slotchange إذا أرادت شيفرة المكوّن مراقبة التغيرات في الفتحات. يُدرَج في المثال التالي عنصر قائمة ديناميكيًا بعد ثانية، ويتغير العنوان بعد ثانيتين: <custom-menu id="menu"> <span slot="title">Candy menu</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>`; //لا يمتلك الجذر الخفي معالجات أحداث لذا سنستخدم الابن الأول this.shadowRoot.firstElementChild.addEventListener('slotchange', e => alert("slotchange: " + e.target.name) ); } }); setTimeout(() => { menu.insertAdjacentHTML('beforeEnd', '<li slot="item">Lollipop</li>') }, 1000); setTimeout(() => { menu.querySelector('[slot="title"]').innerHTML = "New menu"; }, 2000); </script> See the Pen JS-p3-06-Shadow-DOM -ex6 by Hsoub (@Hsoub) on CodePen. يُحدَّث تصيير القائمة كل مرة دون تدخلنا، وسنلاحظ في الشيفرة السابقة وجود حدثي slotchange: عند التهيئة: حيث يقع الحدث slotchange: title مباشرةً عندما يُدرج "slot="title من شجرة DOM الظاهرة ضمن الفتحة الموافقة له. بعد ثانية: يقع الحدث slotchange: item عندما يُضاف عنصر قائمة جديد <"li slot="item>. لاحظ أنه لا وجود للحدث slotchange بعد ثانيتين عندما يُعدَّل محتوى "slot="title، والسبب هو عدم وجود أي تغييرات في الفتحة، حيث عدّلنا محتوى عنصر مُدرج في الفتحة، وهذا أمر مختلف. يمكن تتبع التغيرات الداخلية ضمن شجرة DOM الظاهرة باستخدام شيفرة JavaScript من خلال الآلية المعمّمة المعروفة بـ MutationObserver. الواجهة البرمجية للفتحات لنلق نظرةً على توابع JavaScript المتعلقة بالفتحات، إذ تنظر JavaScript كما رأينا سابقًا إلى شجرة DOM الحقيقية دون تسطيح flattening، لكن إذا كانت قيمة الخيار mode في الشجرة الخفية هي "open"، فسنتمكن من معرفة أي العناصر أدرِجت في فتحات، كما يمكننا معرفة الفتحة من العنصر الذي أدرج ضمنه: node.assignedSlot: تعيد الفتحة <slot> التي أسندت إليها عقدة من الشجرة node. ({slot.assignedNodes({flatten: true/false: يعيد عقد DOM التي أسندت إلى فتحة، وستكون قيمة الخيار flatten هي "false" افتراضيًا، لكن إذا ضبطت على القيمة "true" صراحةً، فسيُنظر إلى DOM المسطحة بعمق، كما سيعيد التابع الفتحات المتداخلة nested في حال وجود مكوّنات متداخلة، ويعيد "محتوى التراجع" إذا لم يجد أي عقد أسندت إلى فتحات. ({slot.assignedElements({flatten: true/false: يعيد عناصر DOM التي أسندت إلى فتحة، وهو يشابه التابع السابق إلا أنه يعيد عقد العنصر. تُستخدَم هذه التوابع عندما نريد إظهار محتوى الفتحات وتتبعها باستخدام JavaScript. وإذا أراد المكوّن <custom-menu> مثلًا معرفة ما الذي يعرضه، فيمكنه تعقّب الحدث slotchange والحصول على العناصر من خلال slot.assignedElements: <custom-menu id="menu"> <span slot="title">Candy menu</span> <li slot="item">Lollipop</li> <li slot="item">Fruit Toast</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>`; // يقع عندما يتغير محتوى الفتحة this.shadowRoot.firstElementChild.addEventListener('slotchange', e => { let slot = e.target; if (slot.name == 'item') { this.items = slot.assignedElements().map(elem => elem.textContent); alert("Items: " + this.items); } }); } }); // items update after 1 second setTimeout(() => { menu.insertAdjacentHTML('beforeEnd', '<li slot="item">Cup Cake</li>')}, 1000); </script> See the Pen Untitled by Hsoub (@Hsoub) on CodePen. تنسيق شجرة DOM الخفية يمكن أن تحتوي شجرة DOM الخفية على الوسمين <style> و<link rel="stylesheet" href="…"‎>، وتُخزّن أوراق التنسيق في الوسم الأخير في ذاكرة HTTP المؤقتة، لذلك لن يُعاد تحميلها للمكونات المختلفة التي تستخدم نفس القالب، ويمكن أن نعد ما لي قاعدةً عامةً: يعمل التنسيق المحلي داخل الشجرة الخفية، بينما تعمل تنسيقات المستند ككل خارجها، مع وجود بعض الاستثناءات. المحدد host: يسمح هذا المحدد باختيار المضيف الخفي، أي العنصر الذي يحتوي الشجرة الخفية، لنفترض مثلًا أننا سننشئ عنصرًا مخصصًا <custom-dialog> يجب أن يتوسط الصفحة، لذلك لا بدّ من تنسيق المكوّن <custom-dialog> ذاته، وهذا ما يفعله host: تمامًا: <template id="tmpl"> <style> /* the style will be applied from inside to the custom-dialog element */ :host { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); display: inline-block; border: 1px solid red; padding: 10px; } </style> <slot></slot> </template> <script> customElements.define('custom-dialog', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}).append(tmpl.content.cloneNode(true)); } }); </script> <custom-dialog> Hello! </custom-dialog> See the Pen JS-p3-06-Shadow-DOM-styling -ex1 by Hsoub (@Hsoub) on CodePen. توريث التنسيقات Cascading يبقى المضيف الخفي -<custom-dialog> ذاته- ضمن شجرة DOM الظاهرة، لذلك سيتأثر بقواعد CSS التي يخضع لها المستند كاملاً، فإذا وجدت خاصية جرى تنسيقها محليًا ضمن host: وفي المستند بنفس الوقت، فسيُطبق حينها تنسيق المستند. فإن كان المستند مثلًا: <style> custom-dialog { padding: 0; } </style> فسيُعرض العنصر <custom-dialog> دون حاشية padding. من الملائم أن نعّرف تنسيقًا افتراضيًا للمكوّن ضمن القاعدة host:، ثم نعيد التنسيق -إذا أردنا- باستخدام تنسيق المستند، ويبقى الاستثناء في الخاصية التي نعنونها important!،عندها يُطبق التنسيق المحلي. المحدد (host(selector: هو نفسه host: لكنه يُطبّق فقط إذا تطابق اسم المضيف الخفي مع الوسيط selector، فعلى سبيل المثال: نريد توسيط المكوّن <custom-dialog> في الصفحة فقط إذا امتلك السمة centered: <template id="tmpl"> <style> :host([centered]) { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); border-color: blue; } :host { display: inline-block; border: 1px solid red; padding: 10px; } </style> <slot></slot> </template> <script> customElements.define('custom-dialog', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}).append(tmpl.content.cloneNode(true)); } }); </script> <custom-dialog centered> Centered! </custom-dialog> <custom-dialog> Not centered. </custom-dialog> See the Pen JS-p3-06-Shadow-DOM-styling -ex2 by Hsoub (@Hsoub) on CodePen. والآن سيُطبّق التوسيط فقط على المكوّن الأول <custom-dialog centered>. باختصار، يمكن استخدام عائلة المحددات host: لتنسيق العنصر الرئيسي للمكوِّن بناءً على السياق، ويمكن تغيير هذه التنسيقات -ما لم تستخدم السمة important!- من قبل تنسيق المستند. تنسيق محتوى الفتحات لننظر إلى حالة وجود فتحات slots. تأتي المكوّنات المقلمة slotted components من شجرة DOM الظاهرة، لذا ستستخدم تنسيقات المستند، ولن يؤثر التنسيق المحلي على المحتوى المركب. سنرى في المثال التالي أنّ العنصر المركب <span> سيظهر بخط سميك وفق تنسيق المستند، لكنه لن يتأثر بقيمة الخاصية background العائدة للتنسيق المحلي للعنصر: <style> span { font-weight: bold } </style> <user-card> <div slot="username"><span>John Smith</span></div> </user-card> <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = ` <style> span { background: red; } </style> Name: <slot name="username"></slot> `; } }); </script> See the Pen JS-p3-06-Shadow-DOM-styling -ex3 by Hsoub (@Hsoub) on CodePen. سيكون الخط سميكًا لكنه ليس باللون الأحمر. وإذا أردنا تنسيق العناصر المركبة في مكوننا فأمامنا خياران هما: الأول هو تنسيق الفتحة <slot> نفسه اعتمادًا على وراثة تنسيق CSS: <user-card> <div slot="username"><span>John Smith</span></div> </user-card> <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = ` <style> slot[name="username"] { font-weight: bold; } </style> Name: <slot name="username"></slot> `; } }); </script> See the Pen JS-p3-06-Shadow-DOM-styling -ex4 by Hsoub (@Hsoub) on CodePen. سيكون الخط سميكًا في <p>John Smith</p>، وذلك لأن وراثة CSS ستنقل التنسيق من الفتحة <slot> إلى المحتويات، لكن ليست كل خصائص CSS قابلة للوراثة. الخيار الثاني هو استخدام محدد الصنف الوهمي (slotted(selector::، والتي تطابق العناصر بناءً على شرطين: أن يكون العنصر مركبًا وآتيًا من الشجرة الظاهرة، ولا يهم اسم الفتحة هنا، ويطبق على العنصر المركب ذاته وليس على أبنائه. أن يطابق العنصر قيم الوسيط selector. يختار المحدد (slotted(div:: في مثالنا التالي العنصر <"div slot="username> تمامًا وليس أبناءه: <user-card> <div slot="username"> <div>John Smith</div> </div> </user-card> <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = ` <style> ::slotted(div) { border: 1px solid red; } </style> Name: <slot name="username"></slot> `; } }); </script> See the Pen JS-p3-06-Shadow-DOM-styling -ex5 by Hsoub (@Hsoub) on CodePen. لاحظ أنه يمكن للمحدد slotted:: التقدم داخل عناصر الفتحة، لذا تكون المحددات التالية غير صحيحة: ::slotted(div span) { /*مع هذا المحدد div لايتطابق العنصر */ } ::slotted(div) p { /* DOM لا يمكنه التقدم داخل */ } يمكن استخدام slotted:: أيضًا في querySelector. خطافات CSS مع خاصيات مخصصة كيف ننسق العناصر الداخلية للمكوِّن من المستند الرئيسي؟ تطبق المحددات -مثل host:- القواعد على عناصر مخصصة، مثل <custom-dialog> أو <user-card>، لكن كيف ننسق عناصر شجرة DOM الخفية ضمنها؟ لا يوجد محدد قادر على التأثير المباشر على تنسيق شجرة DOM الخفية من المستند الرئيسي، لكن -وكما عرضنا توابع للتفاعل مع مكوننا- يمكن عرض متغيرات CSS -خصائص CSS مخصصة- إلى التنسيق. تتواجد الخصائص المخصصة على كل المستويات وفي كلتا الشجرتين الخفية والظاهرة، حيث يمكن مثلًا استخدام المتغير ‎--user-card-field-color في شجرة DOM الخفية لتنسيق الحقول، ويمكن أن يضبط المستند الخارجي قيمتها: <style> .field { color: var(--user-card-field-color, black); /* معرفًا استخدم اللون الأسود --user-card-field-color */ } </style> <div class="field">Name: <slot name="username"></slot></div> <div class="field">Birthday: <slot name="birthday"></slot></div> عندها يمكن التصريح عن هذه الخاصية في المستند الخارجي للمكوّن <user-card>: user-card { --user-card-field-color: green; } تخترق خاصيات CSS المخصصة شجرة DOM الخفية، وهي مرئية في كل مكان، لذلك يمكن للقاعدة الداخلية field. استخدامها، وإليك مثالًا كاملًا: <style> user-card { --user-card-field-color: green; } </style> <template id="tmpl"> <style> .field { color: var(--user-card-field-color, black); } </style> <div class="field">Name: <slot name="username"></slot></div> <div class="field">Birthday: <slot name="birthday"></slot></div> </template> <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.append(document.getElementById('tmpl').content.cloneNode(true)); } }); </script> <user-card> <span slot="username">John Smith</span> <span slot="birthday">01.01.2001</span> </user-card> See the Pen JS-p3-06-Shadow-DOM-styling -ex6 by Hsoub (@Hsoub) on CodePen. شجرة DOM الخفية والأحداث إنّ الفكرة الأساسية من الشجرة الخفية هي تغليف تفاصيل التنفيذ الداخلي للمكوّن. لنفترض وقوع حدث نقرٍ داخل شجرة DOM الخفية للمكوّن <user-card>، لكن لا تملك السكربتات في المستند الرئيسي أي فكرة عن ما يجري داخل الشجرة، وخاصةً إذا أتى المكون من طرف ثالث، لذا يعيد المتصفح استهداف الحدث للمحافظة على تغليف التفاصيل. تستهدف الأحداث التي تحدث في شجرة DOM الخفية العنصر المضيف عندما تُلتقط خارج إطار المكوِّن. وإليك مثالًا بسيطًا: <user-card></user-card> <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = `<p> <button>Click me</button> </p>`; this.shadowRoot.firstElementChild.onclick = e => alert("Inner target: " + e.target.tagName); } }); document.onclick = e => alert("Outer target: " + e.target.tagName); </script> See the Pen JS-p3-06-Shadow-DOM-and-events -ex1 by Hsoub (@Hsoub) on CodePen. إذا نقرت على الزر فستظهر الرسائل التالية: الهدف الداخلي BUTTON: معالج حدث داخلي يحصل على الهدف الصحيح، وهو عنصر داخل الشجرة الخفية. الهدف الخارجي USER-CARD: معالج حدث المستند، والذي يحصل على المضيف الخفي مثل هدف. إنّ إعادة استهداف retargeting الأحداث أمر مفيد جدًا، إذ يُفترض أن لا يعرف المستند الخارجي ما يوجد داخل المكوّن، فالحدث قد وقع من وجهة نظره على المكوّن <user-card>. لا تحدث إعادة الاستهداف إذا وقع الحدث في عنصر مُركّب، وذلك لأنه موجود فيزيائيًا في شجرة DOM الظاهرة، فلو نقر مستخدم مثلًا على <span slot="username"‎> في المثال التالي، فسيستهدف الحدث العنصر span هذا بالتحديد، وفي كلا معالجي الحدث الخاصين بالشجرتين الظاهرة والخفية: <user-card id="userCard"> <span slot="username">John Smith</span> </user-card> <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = `<div> <b>Name:</b> <slot name="username"></slot> </div>`; this.shadowRoot.firstElementChild.onclick = e => alert("Inner target: " + e.target.tagName); } }); userCard.onclick = e => alert(`Outer target: ${e.target.tagName}`); </script> See the Pen JS-p3-06-Shadow-DOM-and-events -ex2 by Hsoub (@Hsoub) on CodePen. فإذا نُقر الاسم "John Smith"، فسيكون الهدف بالنسبة لمعالجي الحدث الداخلي والخارجي هو <"span slot="username>، وهنا لن يُعاد الاستهداف لأنه عنصر من DOM الظاهرة. من ناحية أخرى، إذا نُقر عنصر يعود إلى شجرة DOM الخفية مثل العنصر <b>Name</b>، فسُيعاد ضبط هدف الحدث event.target أثناء انسياب الحدث لخارج شجرة DOM الخفية ليصبح <user-card>. التابعان Bubbling و()event.composedPath تُستخدم DOM المسطحة لدفع الأحداث إلى الخارج bubbling، لذا إذا وُجد عنصر مركب slotted في فتحة ووقع حدث في مكان ما بداخله، فسيندفع الحدث إلى خارج العنصر باتجاه الفتحة <slot> وإلى الأعلى، ويمكن الحصول على المسار الكامل للهدف الأصلي للحدث مع كل عناصر شجرته الخفية باستخدام التابع ()event.composedPath، ويؤخذ هذا المسار -كما يدل اسم التابع- بعد عملية التركيب، وإليك شجرة DOM المسطحة للمثال السابق: <user-card id="userCard"> #shadow-root <div> <b>Name:</b> <slot name="username"> <span slot="username">John Smith</span> </slot> </div> </user-card> عند النقر على <span slot="username">، سيعيد استدعاء التابع ()event.composedPath المصفوفة التالية: [span, slot, div, shadow-root, user-card, body, html, document, window]، وهي شجرة العناصر كاملةً ابتداءًا من العنصر المُستهدف في شجرة DOM المسطحة بعد التركيب. التابع event.composed تنساب معظم الأحداث إلى خارج حدود شجرة DOM الخفية، لكن هذا لا ينطبق على بعضها، وذلك بحسب قيمة كائن الحدث composed، فإذا كانت "true" فستعبر الأحداث حدود الشجرة، وإلا فستلتقَط داخل حدود الشجرة فقط. إذا ألقينا نظرةً على توصيفات أحداث واجهة المستخدم UI Events specification، فسنجد أن قيمة composed هي "true" للأحداث: blur وfocus وfocusin وfocusout. click وdblclick. mousedown وmouseup وmousemove وmouseout وmouseover. wheel. beforeinput وinput وkeydown وkeyup. جميع أحداث اللمس والتأشير. أما الأحداث التي يكون فيها composed: false فهي: mouseenter وmouseleave: التي لا تنساب للخارج إطلاقًا. load وunload وabort وerror. select. slotchange. يمكن التقاط هذه الأحداث من قبل العناصر داخل شجرة DOM نفسها، حيث يقيم العنصر المستهدف. الأحداث المخصصة لا بدّ من ضبط الخاصيتين bubbles وcomposed على القيمة "true" إذا أردنا إيفاد أحداث مخصصة تنساب إلى مستوى أعلى وإلى خارج المكوّن، وسننشئ في المثال التالي div#inner في شجرة DOM الخفية ضمن div#outer ونطلق حدثين فيها، وسيصل الحدث الذي يمتلك الخاصية composed: true فقط إلى الخارج، وصولًا إلى المستند: <div id="outer"></div> <script> outer.attachShadow({mode: 'open'}); let inner = document.createElement('div'); outer.shadowRoot.append(inner); /* div(id=outer) #shadow-dom div(id=inner) */ document.addEventListener('test', event => alert(event.detail)); inner.dispatchEvent(new CustomEvent('test', { bubbles: true, composed: true, detail: "composed" })); inner.dispatchEvent(new CustomEvent('test', { bubbles: true, composed: false, detail: "not composed" })); </script> خلاصة شجرة DOM الخفية هي طريقة لإنشاء شجرة محلية خاصة بالمكوِّن. ({shadowRoot = elem.attachShadow({mode: open|closed: ينشئ شجرة DOM خفيةً للعنصر elem، ويمكن الوصول إليها باستخدام الخاصية elem.shadowRoot إذا كانت الخاصية "mode="open. يمكن تعميم الكائن shadowRoot باستخدام innerHTML أو توابع DOM أخرى. عناصر شجرة DOM الخفية: لها فضاء خاص لقيم المعرّفات id. غير مرئية لمحددات JavaScript الموجودة في المستند الرئيسي، مثل querySelector. تستخدم التنسيق الموجود ضمن الشجرة الخفية فقط، وليس تنسيق المستند الرئيسي. تُصيّر شجرة DOM الخفية -إن وجدت- ضمن ما يُسمى شجرة DOM الظاهرة light DOM، مثل عناصر أبناء نظاميين. إذا امتلك عنصر شجرة DOM خفيةً، فلن تُعرض شجرة DOM الظاهرة عادةً، لذلك تسمح الفتحات slots -الناتجة عن عملية تقليم الشجرة- بعرض عناصر من شجرة DOM الظاهرة في أماكن محددة من شجرة DOM الخفية، ويوجد نوعان من الفتحات: الفتحات المسماة <slot name="X">...</slot>: الحصول على أبناء شجرة DOM الظاهرة فتحتها slot="X"‎. الفتحة الافتراضية: وهي أول فتحة <slot> لا تحمل اسمًا، وتهمَل الفتحات غير المسماة التي تأتي لاحقًا، كما يحصل على أبناء شجرة DOM الظاهرة الذين لم يُدرجوا في فتحات. إذا وجدَت عدة عناصر في الفتحة نفسها، فستُدرج واحدًا بعد الآخر. يُستخدَم محتوى الفتحة <slot> محتوىً للتراجع، لذا يظهر هذا المحتوى عند عدم وجود أبناء لشجرة DOM الظاهرة في الفتحة. تُدعى عملية تصيير العناصر المدرجة في فتحات ضمن فتحاتها بالتركيب composition، وتسمى النتيجة شجرة DOM المسطّحة. لا تنقل عملية التركيب العقد في الواقع، فمن وجهة نظر JavaScript ستبقى شجرة DOM نفسها، ويمكن الوصول إلى الفتحات من خلال توابع JavaScript التالية: ()slot.assignedNodes/Elements: يعيد العقد أو العناصر داخل فتحة. node.assignedSlot: وهي الخاصية المعاكسة، وتعيد الفتحة من العقدة التي تشغلها. إذا أردنا معرفة ما نُظهره، فيمكننا تعقب محتوى الفتحة باستخدام: الحدث slotchange: ويقع في المرة الأولى التي تُملأ بها الفتحة، أو أثناء عمليات الإضافة أو الإزالة أو الاستبدال لعنصر مُدرج في الفتحة وليس لأبنائه، وستكون الفتحة event.target. MutationObserver للغوص عميقًا في محتوى الفتحة ومعرفة التغيرات التي حدثت ضمنها. بعد أن تعرفنا على كيفية إظهار عناصر من شجرة DOM الظاهرة، سنتعرف على كيفية تنسيقها بالشكل الصحيح، وتبقى القاعدة الأساسية هي أن عناصر شجرة DOM الخفية تُنسّق من الداخل، والظاهرة من الخارج -مع بعض الاستثناءات-، وهذا ما سنتعرف عليه في المقال القادم. يمكن أن تحوي شجرة DOM الخفية تنسيقات مثل <style> أو <"link rel="stylesheet>، وتؤثر التنسيقات المحلية في: الشجرة الخفية. المضيف الخفي عندما يستعمل عائلة المحددات host:. العناصر المركبة القادمة من شجرة DOM الظاهرة، حيث يسمح المحدد (slotted(selector:: باختيار العناصر المركبة ذاتها وليس أبنائها. بينما تؤثر تنسيقات المستند الرئيسي في: المضيف الخفي، إذا كان حيًّا في المستند الخارجي. العناصر المركبة ومحتوياتها، إذا كانت أيضًا في المستند الخارجي. سيُطبق تنسيق المستند الخارجي عندما يحدث تضارب في تنسيق العنصر، إلا عند استخدام الخاصية مع عنوان important!، حيث يُطبق التنسيق المحلي. تخترق خصائص CSS المخصصة شجرة DOM الخفية، وتستخدم مثل خطافات hooks لتنسيق المكوّن: يستخدم المكوِّن خصائص CSS المخصصة لتنسيق العناصر المفتاحية، مثل (<var(--component-name-title, <default value. ينشر مؤلف المكون هذه الخصائص للمطورين، وهي بأهمية توابع المكوّنات العامة. عندما لا يرغب المطور في قضاء وقت طويل على التنسيق، فسيسند الخاصية ‎--component-name-title إلى المضيف الخفي أو الخاصية التي أشرنا إليها في الأعلى. الربح طبعًا! تتجاوز الأحداث حدود شجرة DOM الخفية إذا كانت قيمة الراية composedهي true، والتي تكون كذلك لمعظم الأحداث المضمنة Built-in، كما هو محدد في التوصيفات: أحداث واجهة المستخدم. أحداث اللمس. أحداث التأشير. من الأحداث المضمنة التي يكون فيها composed: false: mouseenter وmouseleave: لا تنساب للخارج إطلاقًا. load وunload وabort وerror. select. slotchange. يمكن التقاط هذه الأحداث من قِبل العناصر داخل شجرة DOM نفسها. إذا أردنا إيفاد أحداث مخصصة CustomEvent فلا بد من ضبط الخاصية composed على القيمة "true" بشكل صريح. المراجع مراجع DOM. مراجع التوافقية Compatibility ذكرت الشجرة الخفية في الكثير من المواصفات مثل DOM Parsing. ترجمة -وبتصرف- للفصول Shadow DOM and events و Shadow DOM slots, Composition وShadow DOM وShadow DOM Style من سلسلة The Modern JavaScript Tutorial. اقرأ أيضًا إنشاء الحركات عبر جافاسكربت التنقل في شجرة DOM عبر جافاسكربت تعديل الخاصيات والأصناف والأنماط في شجرة DOM إجراء تعديلات على DOM طريقة الوصول للعناصر في DOM
  2. يصف هذا القسم مجموعةً من المعايير الحديثة لمكونات الويب، وهذه المعايير بصدد التطوير حاليًا. تُدعم بعض الميزات وتتكامل ضمن معايير HTML/DOM، بينما يبقى البعض الآخر في مرحلة الإعداد. ويمكنك تجربة الأمثلة في أي متصفح، لكن يبقى المتصفح كروم Chrome الأكثر حداثةً فيما يتعلق بدعم هذه الميزات، والسبب طبعًا هو أنّ أشخاصًا من Google هم من وضعوا العديد من هذه المواصفات. ما هو الشيء المشترك بين محطة الفضاء ومكونات الويب إن فكرة المكونات ليست جديدة، فقد استُعملت في العديد من إطارات العمل وفي منصات مختلفة، وذلك قبل الانتقال إلى تفاصيل إنجاز تلك الميزات. لنلقي نظرةً على الصورة التالية، والتي تمثل إنجازًا عظيمًا للإنسانية: إنها محطة الفضاء الدولية، وتمثل الصورة التالية المكوِّنات التي تتألف منها -تقريبيًا طبعًا-: نلاحظ الآتي: تتألف المحطة من مكوِّنات عديدة. يمتلك كل مكوِّن بدوره أجزاءً أصغر في داخله. المكوّنات معقدّة جدًا، وهي بالطبع أعقد بكثير من معظم مواقع الويب. طوّرت مكونات المحطة على صعيد عالمي، ومن قِبل فرق من دول مختلفة تتحدث بلغات مختلفة. تحلق المحطة في مدارها حول الأرض وتبقي من في داخلها على قيد الحياة، فكيف بني هذا الجهاز المعقد؟ وما هي المبادئ التي يمكن أن نستعيرها لنطوّر مكوّنات ويب لها نفس مستوى الوثوقية وقابلة للنمو، أو قريبة من ذلك على الأقل؟ معمارية المكون تنص القاعدة الشهيرة في تطوير البرمجيات المعقدة على أن لا تطوّر برمجيات معقّدة، فإذا وصل بك الأمر إلى أمر معقد، فافصله إلى أجزاء أبسط واربطها ببعضها بأكثر الطرق وضوحًا، فالمعماري الأمهر هو من يستطيع تحويل المعقد إلى بسيط. يمكن تقسيم واجهة المستخدم إلى مكوّنات مرئية لكلٍّ منها موقعه في الصفحة، وتُنفِّذ مهمةً محددةً بعناية وتكون مفصولةً عن بعضها البعض، لنلق نظرةً على موقع ويب، وليكن تويتر مثلًا، يُقسم الموقع إلى مكوّنات: مكوّن للتنقل. معلومات المستخدم. اقتراحات المتابعة. نموذج إرسال. رسائل. غيرها من المكوِّنات. قد يكون للمكوّنات مكوّنات فرعيةً أيضًا، فقد تكون الرسائل مكوّنات فرعيةً من مكوّن أعلى مستوىً مثل قائمة الرسائل، كما قد تكون صورة المستخدم التي تقبل النقر مكوّنًا.. وهكذا. كيف نحدد ما هو المكوِّن إذًا؟ يأتي ذلك من حسن البديهة والخبرة والمنطق، حيث تشكًل المكوّنات عادةً كيانات مرئيةً نميزها وفقًا لوظيفتها وكيفية تتفاعلها مع الصفحة، ففي حالة الصفحة السابقة، سنجد أنها تحوي وحدات يلعب كل منها دوره الخاص، ومن المنطق إذًا أن نجعلها مكوّنات. للمكوِّن: صنف JavaScript خاص به. بنية DOM تُدار منفردةً من قبل الصنف الخاص بها، ولا يمكن الوصول إليها من قبل شيفرة خارجية (مبدأ التغليف). تنسيق CSS يُطبَّق على المكوّن. واجهة برمجية API، أحداث وتوابع يستخدمها الصنف وغيره من الكائنات التي تتفاعل مع المكوّنات الأخرى. تذكر أنّ فكرة الكائن ككل لا تفرض وجود أي شيء خاص. تتواجد الكثير من المنصات وإطارات العمل التي تقدم وسائل لإنشاء المكوّنات، وتستخدم أصناف CSS بالإضافة إلى ترتيبات أخرى لتعزيز المكوّنات وتحديد مجال تطبيق CSS وتغليف DOM. تزوّدنا مكوِّنات الويب بإمكانيات تصفّح مدمجةً معها ولا حاجة لمحاولة تقليدها بعد الآن. عناصر مخصصة Custom elements: لتعريف عناصر HTML مخصصة. شجرة DOM مخفية Shadow DOM: لإنشاء شجرة داخلية للمكوّن مخفية عن بقية المكوّنات. مجال لتطبيق أوراق التنسيق CSS Scoping: لتعريف قواعد تنسيق يمكن تطبيقها على الشجرة الداخلية لمكوّن. إعادة توجيه الحدث Event retargeting وغيره من الأمور الثانوية: لتحسين قدرة المكوّنات على التلاؤم مع عملية التطوير. كل الميزات المدعومة جيدًا لمكوّنات الويب جيدة في الأمور التي تقدر على تنفيذها. عناصر HTML المخصصة يمكننا إنشاء عناصر HTML مخصصة يصفها الصنف الذي ننشئه لهذا الغرض مزوّدًا بتوابعه وخصائصه وأحداثه الخاصة، وسنتمكن من استخدام العنصر ضمن عناصر HTML الأصلية بعد أن نعرّفه، وهذا أمر جيد، فعلى الرغم من غنى HTML بالعناصر، إلا أنها لا تحوي عناصرًا مثل علامة جداول سهلة التعامل <easy-tabs> أو سير دائري منزلق <sliding-carousel> أو الرفع بطريقة جذابة <beautiful-upload> أو أي عناصر أخرى قد نحتاجها. يمكن تعريف العناصر المخصصة ضمن أصناف خاصة، واستخدامها كما لو أنها جزء من HTML، ويوجد نوعان من هذه العناصر المخصصة، وهما: عناصر مخصصة ذاتية التصرف Autonomous: وهي عناصر جديدة توّسع عمل الصنف المجرّد HTMLElement. عناصر أصلية مخصصة customized: توسّع العناصر الأصلية، لإنجاز أزرار مخصصة مبنية على أساس الصنف HTMLButtonElement مثلًا. سنغطي أولًا العناصر ذاتية التصرف، ثم سننتقل إلى العناصر المخصصة، ولإنشاء عنصر مخصص لا بدّ من إخبار المتصفح عن جملة من التفاصيل حوله، مثل كيف سيُظهره؟ وماذا سيفعل عند إضافته أو إزالته من الصفحة؟ يجري ذلك بإنشاء صنف له توابعه الخاصة، وسيكون الأمر بسيطًا، فهي عدة توابع فقط وجميعها اختيارية. إليك تصورًا مع قائمة كاملة من التوابع: class MyElement extends HTMLElement { constructor() { super(); // إنشاء العنصر } connectedCallback() { //يستدعيه المتصفح عند إضافة عنصر ويمكن استدعاؤه عدة مرات إن أضيف أو حذف العنصر } disconnectedCallback() { //يستدعيه المتصفح عند حذف عنصر ويمكن استدعاؤه عدة مرات إن أضيف أو حذف العنصر } static get observedAttributes() { return [/* مصفوفة من أسماء السمات التي تراقب التغيرات */]; } attributeChangedCallback(name, oldValue, newValue) { // يُستدعى عندما تتغير صفة من السمات المذكوره أعلاه } adoptedCallback() { // يُستدعى عندما يُنقل العنصر إلى صفحة أخرى } // إن كان هناك عناصر أو سمات أخرى } نحتاج بعد ذلك إلى تسجيل العنصر: // سيُخدّم من قبل الصنف الجديد <my-element> أبلغ المتصفح أن customElements.define("my-element", MyElement); ستتولد الآن نسخة عن الصنف MyElement لكل عنصر HTML له الوسم <my-element>، وستستدعى التوابع المذكورة. كما يمكن تنفيذ ذلك باستخدام شيفرة JavaScript كالتالي: document.createElement('my-element') مثال عنصر لتنسيق الوقت يُستخدم العنصر الأصلي <time> لإظهار الوقت والتاريخ، لكنه لا يقدم أي تنسيق للبيانات التي يعطيها، لننشئ إذًا العنصر <time-formatted> الذي يعرض الوقت بتنسيق جميل يأخذ اللغة بالحسبان: <script> class TimeFormatted extends HTMLElement { // (1) connectedCallback() { let date = new Date(this.getAttribute('datetime') || Date.now()); this.innerHTML = new Intl.DateTimeFormat("default", { year: this.getAttribute('year') || undefined, month: this.getAttribute('month') || undefined, day: this.getAttribute('day') || undefined, hour: this.getAttribute('hour') || undefined, minute: this.getAttribute('minute') || undefined, second: this.getAttribute('second') || undefined, timeZoneName: this.getAttribute('time-zone-name') || undefined, }).format(date); } } customElements.define("time-formatted", TimeFormatted); // (2) </script> <!-- (3) --> <time-formatted datetime="2019-12-01" year="numeric" month="long" day="numeric" hour="numeric" minute="numeric" second="numeric" time-zone-name="short" ></time-formatted> See the Pen JS-p3-06-Custom-elements -ex1 by Hsoub (@Hsoub) on CodePen. تشير (1) إلى الصنف تابع واحد فقط هو ()connectedCallback، حيث يستدعيه المتصفح عندما يُضاف العنصر إلى الصفحة، أو عندما يكتشف مفسّر HTML وجوده، ويستخدم العنصر تابع تنسيق البيانات المدمج Intl.DateTimeFormat الذي يلقى دعمًا جيدًا من معظم المتصفحات لعرض الوقت بتنسيق جميل. تعني (2) أنه لا بدّ من تسجيل العنصر الجديد باستخدام الأمر (customElements.define(tag, class). تعني (3) أنه يمكن الآن استخدام العنصر في أي مكان. تحديث العنصر المخصص إذا صادف المتصفح العنصر <time-formatted> قبل عبارة التعريف customElements.define فلن يعدّ ذلك خطأً، بل سيعدّه مجرد عنصر غير معروف مثل أي وسم غير معياري، ويمكن تنسيق هذه العناصر غير المعروفة باستخدام المحدد (not(:defined:، وهو أحد محددات CSS. ستُحدّث هذه العناصر عندما يُستدعى التابع customElement.define، وستتولد نسخة من الصنف TimeFormatted لكلٍّ منها، ثم يُستدعى التابع connectedCallback وتتحول إلى عناصر معرّفة defined:. توجد توابع عدة للحصول على معلومات عن عناصر مخصصة: (customElements.get(name: يعيد الصنف الخاص بالعنصر الذي يحمل الاسم المحدد name. (customElements.whenDefined(name: يعيد وعدًا promise يحَلَّ -دون قيمة- عندما يُعرَّف العنصر المخصص الذي له الاسم name المحدد. التصيير داخل connectedCallback لا في constructor رأينا في المثال السابق كيف جرى تصيير محتوى العنصر ضمن التابع connectedCallback، لكن لماذا لم يُصيّر ضمن الدالة البانية constructor؟ إنّ السبب بسيط، لأنه من المبكر جدًا تصيير المكوّن عند استدعاء constructor، فعندما يُنشأ العنصر في هذه المرحلة، فلن يُعالجه المتصفح أو يسند إليه السمات الخاصة به، فاستدعاء getAttribute سيعيد القيمة "لاشيء" null، وبالتالي لن نتمكن من تصيير شيء، ولو فكرنا بالأمر قليلًا فسنجد أنّ هذا الأداء أفضل لتأخير العمل حتى يكون كل شيء جاهزًا. يقع الحدث connectedCallback عندما يُضاف العنصر إلى الصفحة، ولن يُضاف إلى عنصر آخر مثل ابن فقط، بل سيغدو عمليًا جزءًا من الصفحة، وهكذا سنتمكن من بناء شجرة DOM منفصلة، وإنشاء عناصر وتحضيرها للعمل لاحقًا، وستصيّر هذه العناصر فعليًا عندما تُضاف إلى الصفحة. مراقبة السمات ليس لتغير السمات attribute في الطريقة الحالية لتنفيذ العنصر <time-formatted> أي تأثير بعد تصييره، وهذا غريب بالنسبة إلى عنصر HTML، فعندما نغيّر في صفة مثل a.href، فسنتوقع عادةً أن نرى التغير مباشرةً. نصلح هذا الأمر إذًا. يمكن مراقبة السمات بتزويد دالة الإحضار الساكنة ()observedAttributes بقائمة تضم أسماءهم، وهكذا ستُستدعى الدالة attributeChangedCallback عندما نجري أي تعديلات على تلك السمات. لن يقع هذا الحدث بالطبع عندما تتغير سمات غير مدرجة في القائمة، لأسباب تتعلق بالأداء. إليك شيفرة العنصر <time-formatted> الذي يُحدَّث تلقائيًا عندما تتغير سماته: <script> class TimeFormatted extends HTMLElement { render() { // (1) let date = new Date(this.getAttribute('datetime') || Date.now()); this.innerHTML = new Intl.DateTimeFormat("default", { year: this.getAttribute('year') || undefined, month: this.getAttribute('month') || undefined, day: this.getAttribute('day') || undefined, hour: this.getAttribute('hour') || undefined, minute: this.getAttribute('minute') || undefined, second: this.getAttribute('second') || undefined, timeZoneName: this.getAttribute('time-zone-name') || undefined, }).format(date); } connectedCallback() { // (2) if (!this.rendered) { this.render(); this.rendered = true; } } static get observedAttributes() { // (3) return ['datetime', 'year', 'month', 'day', 'hour', 'minute', 'second', 'time-zone-name']; } attributeChangedCallback(name, oldValue, newValue) { // (4) this.render(); } } customElements.define("time-formatted", TimeFormatted); </script> <time-formatted id="elem" hour="numeric" minute="numeric" second="numeric"></time-formatted> <script> setInterval(() => elem.setAttribute('datetime', new Date()), 1000); // (5) </script> See the Pen JS-p3-06-Custom-elements -ex2 by Hsoub (@Hsoub) on CodePen. يُقصَد بـ (1) أنه قد نُقل منطق التصيير إلى التابع المساعد <time-formatted>. تعني (2) أن هذا التابع يُستدعى مباشرةً في اللحظة التي يضاف فيها العنصر إلى الصفحة. تعني (3) أنه يقع الحدث attributeChangedCallback عند حدوث أي تغير في السمات التي مررت على شكل قائمة إلى ()observedAttributes تعني (4) أنه يُعاد تصيير العنصر بعد ذلك. تعني (5) أنه في النهاية يمكننا إنشاء مؤقت مباشر بكل سهولة. تسلسل التصيير عندما يبني مفسِّر HTML الشجرة DOM، تعالَج العناصر الواحد تلو الآخر والأب قبل الابن، فلو كان لدينا العنصر: <outer><inner></inner></outer> فسيتولد العنصر <outer> أولًا ويضاف إلى DOM، ثم العنصر <inner>، وسيقود ذلك إلى تبعات بالنسبة للعناصر المخصصة، فإذا أراد العنصر المخصص الوصول إلى innerHTML ضمن connectedCallback مثلًا، فلن يحصل على شيء. <script> customElements.define('user-info', class extends HTMLElement { connectedCallback() { alert(this.innerHTML); // empty (*) } }); </script> <user-info>محمد</user-info> إذا نفّذت الشيفرة السابقة فستظهر الرسالة alert فارغة، والسبب في ذلك هو عدم وجود عناصر أبناء في هذه المرحلة، وستكون الشجرة DOM غير مكتملة، يربط مفسِّر HTML العنصر المخصص <user-info> وسيستأنف العمل بعدها لربط أبنائه لكنه لم يفعلها بعد. إذا أردنا تمرير معلومات إلى عنصر مخصص، فيمكننا استخدام السمات فهي متاحة مباشرةً، وإذا كنا بحاجة فعلًا إلى الأبناء، فيمكن تأجيل الوصول إليهم باستخدام الحدث setTimeoutدون تأخير زمني Zero-delay. <script> customElements.define('user-info', class extends HTMLElement { connectedCallback() { setTimeout(() => alert(this.innerHTML)); // محمد (*) } }); </script> <user-info>John</user-info> وهكذا ستُظهر الرسالة alert في السطر (*) الكلمة "محمد" طالما أننا ننفذ الأمر دون تزامن، وذلك بعد اكتمال تفسير شيفرة HTML، ويمكن معالجة الأبناء إذا اقتضى الأمر ثم إنهاء مرحلة التهيئة، لكن هذا الحل ليس مثاليًا. فإذا استخدمت العناصر المخصصة الحدث لتهيئة نفسها، فإنها ستصطف بحيث يقع الحدث setTimeout للعنصر الخارجي ثم الداخلي، وبالتالي ستنهي العناصر الخارجية تهيئة نفسها قبل الداخلية، لنشرح ذلك من خلال المثال التالي: <script> customElements.define('user-info', class extends HTMLElement { connectedCallback() { alert(`${this.id} connected.`); setTimeout(() => alert(`${this.id} initialized.`)); } }); </script> <user-info id="outer"> <user-info id="inner"></user-info> </user-info> تسلسل الخرج: يُربط العنصر الخارجي بالشجرة. يُربط العنصر الداخلي. يُهيأ العنصر الخارجي. يُهيأ العنصر الداخلي. يمكننا أن نرى بوضوح أن العنصر الخارجي سينهي تهيئة نفسه (الخطوة 3) قبل الداخلي (الخطوة 4)، ولا يوجد استدعاء مدمج يمكن أن يقع بعد أن يصبح العنصر ضمن الشجرة، كما يمكن تنفيذ استدعاء مثل هذا عند الحاجة بأنفسنا، حيث يمكن للعناصر الداخلية إرسال أحداث مثل initialized تستمع إليها العناصر الخارجية وتتفاعل معها. العناصر الأصلية المخصصة ليس للعناصر المخصصة -مثل <time-formatted>- أي دلالات تتعلق بها، فهي غير معروفة لمحركات البحث مثلًا، ولا تستطيع الأجهزة التي تدعم مبدأ الوصول السهل accessibility التعامل معها. قد تكون هذه الأشياء مهمةً، فقد يرغب محرّك البحث بمعرفة أن هذا العنصر سيظهر الوقت، فإذا أنشأنا نوعًا خاصًا من الأزرار، فلماذا لا يمكننا إعادة استخدام وظيفة الزر الأصلي <button>؟ يمكننا توسيع وتخصيص العناصر الأصلية بالوراثة من أصنافها، فالأزرار مثلًا هي نسخ عن الصنف HTMLButtonElement، ولإنشاء عنصر يرث منه ويوسّعه عليك باتباع الآتي: وسِّع الصنف HTMLButtonElement عن طريق الصنف الذي سننشئه: class HelloButton extends HTMLButtonElement { /* custom element methods */ } أعط قيمةً للوسيط الثالث للدالة customElements.define والذي يمثّل الوسم tag المستهدف: customElements.define('hello-button', HelloButton, {extends: 'button'}); يمكن أن تشترك وسوم مختلفة بصنف DOM، لذلك سنحتاج إلى استخدام التعليمة extends. لكي نستخدم أخيرًا العنصر المخصص، استخدم الوسم الأصلي النظامي <button>، لكن أضف إليه الصفة "is="hello-button. <button is="hello-button">...</button> إليك المثال كاملًا: <script> // عند ضغطه "hello" الزر الذي يظهر class HelloButton extends HTMLButtonElement { constructor() { super(); this.addEventListener('click', () => alert("Hello!")); } } customElements.define('hello-button', HelloButton, {extends: 'button'}); </script> <button is="hello-button">Click me</button> <button is="hello-button" disabled>Disabled</button> See the Pen JS-p3-06-Custom-elements -ex3 by Hsoub (@Hsoub) on CodePen. يوسِّع الزر الجديد الزر الأصلي، وبالتالي سيحتفظ بتنسيق وميزات الزر الأصلي مثل الصفة disabled. قوالب HTML يقدم العنصر الأصلي <template> قوالبًا لتخزين شيفرة HTML ويتجاهل محتوياتها، ويتحقق من صحة التركيب اللغوي للشيفرة ضمنها حصرًا، لكن يمكن الوصول إليها واستخدامها في إنشاء عناصر أخرى. يمكن نظريًا إنشاء أي عناصر مخفية ضمن ملف HTML لتخزين شيفرة HTML، فما الغاية من <template>؟ أولًا: قد يحتوي على أي شيفرة HTM صالحة، حتى تلك التي تتطلب عادةً وسم إغلاق Closing tag، فيمكن أن نضع ضمنها أسطر جدول <tr>: <template> <tr> <td>Contents</td> </tr> </template> عندما نحاول عادةً وضع <tr> ضمن <div> مثلًا، فسيكتشف المتصفح الخطأ في بنية DOM ويصلحها بإضافة الوسم <table> وجعل السطر ضمنه تلقائيًا. من الناحية الأخرى سيُبقي <template> ما وضعناه في المكان الذي وضعناه فيه دون تعديل. يمكننا وضع تنسيقات وسكربتات ضمن <template> أيضًا: <template> <style> p { font-weight: bold; } </style> <script> alert("Hello"); </script> </template> يَعُد المتصفح محتويات <template> "خارج المستند"، أي لن تُطبق قواعد التنسيق الموجودة ضمنه ولن يعمل <video autoplay>، كما سيُصبح المحتوى حيًا -أي يُطبَّق التنسيق وتنفَّذ السكربتات- عندما ندرجه ضمن المستند. إدراج القالب يُتاح محتوى قالب ضمن الخاصية content العائدة له على شكل عقدة من النوع DocumentFragment، وهي نوع خاص من عقد DOM، ويمكن التعامل معها مثلما نتعامل مع أي عقدة DOM عدا خاصية واحدة مميزة، وهي إدراج الأبناء بدلًا من العقدة عندما نحاول إدراجها، فعلى سبيل المثال: <template id="tmpl"> <script> alert("Hello"); </script> <div class="message">Hello, world!</div> </template> <script> let elem = document.createElement('div'); // انسخ محتوى القالب لاستخدامه مرات عدة elem.append(tmpl.content.cloneNode(true)); document.body.append(elem); // سيعمل الآن السكربت الموجود ضمن القالب </script> دعونا نعيد كتابة مثال شجرة DOM الخفية في الفصل السابق باستخدام القالب <template>: <template id="tmpl"> <style> p { font-weight: bold; } </style> <p id="message"></p> </template> <div id="elem">Click me</div> <script> elem.onclick = function() { elem.attachShadow({mode: 'open'}); elem.shadowRoot.append(tmpl.content.cloneNode(true)); // (*) elem.shadowRoot.getElementById('message').innerHTML = "Hello from the shadows!"; }; </script> See the Pen JS-p3-06-Template-element -ex1 by Hsoub (@Hsoub) on CodePen. عندما ننسخ وندرج tmpl.content في السطر (*) مثل عقدة من النوع DocumentFragment، فسيُدرج أبناؤها (<style> و<p>) بدلًا منها، وسيشكلان شجرة DOM الخفية: <div id="elem"> #shadow-root <style> p { font-weight: bold; } </style> <p id="message"></p> </div> خلاصة للعناصر المخصصة نوعان: الأول عناصر مخصصة ذاتية التصرف Autonomous، لها وسم خاص جديد وتوسع الصنف HTMLElement، وإليك تخطيط التعريف لهذه العناصر: class MyElement extends HTMLElement { constructor() { super(); /* ... */ } connectedCallback() { /* ... */ } disconnectedCallback() { /* ... */ } static get observedAttributes() { return [/* ... */]; } attributeChangedCallback(name, oldValue, newValue) { /* ... */ } adoptedCallback() { /* ... */ } } customElements.define('my-element', MyElement); /* <my-element> */ أما النوع الثاني، فهو عناصر أصلية معدَّلة customized: توّسع عناصرًا أصليةً موجودة، وتتطلب وسيطًا ثالثًا للدالة define.، وإلى الصفة "..."=is ضمن وسمها. class MyButton extends HTMLButtonElement { /*...*/ } customElements.define('my-button', MyElement, {extends: 'button'}); /* <button is="my-button"> */ تدعم معظم المتصفحات العناصر المخصصة جيدًا، كما يوجد موائم polyfill أي تعويض نقص الدعم للمتصفحات غير المدعومة. قد يكون محتوى <template> أي عناصر HTML صالحة قواعديًا. يعُد المتصفح محتويات <template> خارج المستند، إذ لن يكون لها أي تأثير. يمكن الوصول إلى محتوى القالب template.content من JavaScript، ونسخها لإعادة استخدامها في مكوَّن جديد. الوسم فريد لأن: محتويات القالب <template> ستتعرض إلى تدقيق قواعدي من قِبل المتصفح، وهذا مخالف لاستخدام قالب نصي ضمن سكربت. يُسمح باستخدام وسوم HTML عالية المستوى حتى تلك التي لا يجب استخدامها دون عنصر يغلّفها، مثل <tr> التي ينبغي تغليفها بالوسم <table>. تصبح المحتويات حيّةً -أي تُطبَّق وتُنفَّذ- عندما تُدرج ضمن المستند. لا يدعم العنصر <template> المُكررات iterator، ولا آليات الربط بقواعد البيانات أو تبديل المتغيرات، لكن يمكن إدراجها في أعلى القالب. مهام لإنجازها عنصر توقيت مباشر أنشئ العنصر <live-timer> الذي يعرض الوقت الحالي: ينبغي أن يستخدم عنصر التوقيت العنصر <time-formatted> الذي أنشأناه سابقًا، بصورة ضمنية وليس بنسخ وظيفته. يُحدَّث المؤقت كل ثانية. مع كل تحديث للمؤقت سيتولد حدث خاص يُدعى tick يحمل التاريخ الحالي ضمن الخاصية event.detail، راجع فصل "إنشاء أحداث مخصصة في المتصفح عبر جافاسكربت". طريقة الاستخدام: <live-timer id="elem"></live-timer> <script> elem.addEventListener('tick', event => console.log(event.detail)); </script> إليك المثال النموذجي: يمكنك فتح المثال في بيئة تجريبية لاحظ أن: صفرنا المؤقت setInterval عند إزالة العنصر من الصفحة وهذا أمر مهم إذ بدونه سيستمر الحدث بالعمل كل ثانية في الوقت الذي لا يُستعمَل فيه، ولا يمكن للمتصفح أيضًا أن يمسح الذاكرة من تلقاء نفسه. يمكن الوصول إلى القيمة الحالية عبر الخاصية elem.date، إذ كل توابع الصنف وخاصياته هي توابع للعنصر وخاصياته. ويمكنك فتح الحل في بيئة تجريبية. المراجع HTML Living Standard Compatiblity ترجمة -وبتصرف- للفصول Custom elements وFrom Orbital Height وTemplate element من سلسلة The Modern JavaScript Tutorial. اقرأ أيضًا المقال السابق: إنشاء الحركات عبر جافاسكربت المقال التالي: مكونات الويب: التعامل مع شجرة DOM الخفية iframe { border: 1px solid #e7e5e3 !important; width: 100%; } iframe div#path { margin: auto; }
×
×
  • أضف...