مقدمة
"مكونات الويب" هي مجموعة من المعايير المتطوّرة التي:
- إتاحة إنشاء التطبيقات المصغّرة
- …التي يمكن إعادة استخدامها بشكل موثوق
- ...والذي لن يقسم الصفحات إذا غيّر الإصدار التالي من تفاصيل التنفيذ الداخلية.
هل هذا يعني أنّ عليك تحديد حالات استخدام HTML/JavaScript و حالات استخدام Web Components؟ لا يمكن استخدام HTML وJavaScript لإنشاء مواد مرئية تفاعلية. التطبيقات المصغّرة هي عناصر مرئية تفاعلية. من المنطقي الاستفادة من مهاراتك في HTML وJavaScript عند تطوير تطبيق مصغّر. تم تصميم معايير Web Components لمساعدتك في تنفيذ ذلك.
ولكن هناك مشكلة أساسية تجعل من الصعب استخدام التطبيقات المصمّمة باستخدام HTML وJavaScript: لا يتم تغليف شجرة نموذج DOM داخل التطبيق عن بقية الصفحة. ويؤدي هذا النقص في الحزمة إلى أن يتم تطبيق ملف أسلوب المستند عن طريق الخطأ على أجزاء داخل التطبيق المصغّر، وقد يُعدِّل JavaScript أجزاءً داخل التطبيق المصغّر عن طريق الخطأ، وقد تتداخل أرقام التعريف الخاصة بك مع أرقام التعريف داخل التطبيق المصغّر، وهكذا.
تتكون مكونات الويب من ثلاثة أجزاء:
يعالج Shadow DOM مشكلة "تجميع شجرة نموذج DOM". تم تصميم الأجزاء الأربعة من Web Components للعمل معًا، ولكن يمكنك أيضًا اختيار أجزاء Web Components التي تريد استخدامها. يوضِّح لك هذا الدليل التمهيدي كيفية استخدام Shadow DOM.
مرحبًا، Shadow World
باستخدام Shadow DOM، يمكن للعناصر الحصول على نوع جديد من العقد المرتبطة بها. ويُسمى هذا النوع الجديد من العُقد جذر الظل. يُطلق على العنصر الذي يحتوي على جذر ظل مرتبط به اسم مضيف الظل. لا يتم عرض محتوى مضيف الظل، بل يتم عرض محتوى جذر الظل بدلاً من ذلك.
على سبيل المثال، إذا كان لديك ترميز على النحو التالي:
<button>Hello, world!</button>
<script>
var host = document.querySelector('button');
var root = host.createShadowRoot();
root.textContent = 'こんにちは、影の世界!';
</script>
ثم بدلاً من
<button id="ex1a">Hello, world!</button>
<script>
function remove(selector) {
Array.prototype.forEach.call(
document.querySelectorAll(selector),
function (node) { node.parentNode.removeChild(node); });
}
if (!HTMLElement.prototype.createShadowRoot) {
remove('#ex1a');
document.write('<img src="SS1.png" alt="Screenshot of a button with \'Hello, world!\' on it.">');
}
</script>
شكل صفحتك
<button id="ex1b">Hello, world!</button>
<script>
(function () {
if (!HTMLElement.prototype.createShadowRoot) {
remove('#ex1b');
document.write('<img src="SS2.png" alt="Screenshot of a button with \'Hello, shadow world!\' in Japanese on it.">');
return;
}
var host = document.querySelector('#ex1b');
var root = host.createShadowRoot();
root.textContent = 'こんにちは、影の世界!';
})();
</script>
ولا يقتصر الأمر على ذلك فقط، إذ يسأل رمز JavaScript في الصفحة عن
textContent
للزر، لن تظهر هذه
الإشارة إلى الزر
" الضبْد" (佱韸界! )، ولكن "مرحبًا، العالم!" لأنّ الشجرة الفرعية DOM الموجودة
ضمن جذر الظل مضمّنة
فصل المحتوى عن العرض التقديمي
سننظر الآن في استخدام Shadow DOM لفصل المحتوى عن طريقة العرض. لنفترض أنّ لدينا علامة الاسم هذه:
<style>
.ex2a.outer {
border: 2px solid brown;
border-radius: 1em;
background: red;
font-size: 20pt;
width: 12em;
height: 7em;
text-align: center;
}
.ex2a .boilerplate {
color: white;
font-family: sans-serif;
padding: 0.5em;
}
.ex2a .name {
color: black;
background: white;
font-family: "Marker Felt", cursive;
font-size: 45pt;
padding-top: 0.2em;
}
</style>
<div class="ex2a outer">
<div class="boilerplate">
Hi! My name is
</div>
<div class="name">
Bob
</div>
</div>
في ما يلي الترميز. هذا هو ما ستكتبه اليوم. لا يتم استخدام Shadow DOM:
<style>
.outer {
border: 2px solid brown;
border-radius: 1em;
background: red;
font-size: 20pt;
width: 12em;
height: 7em;
text-align: center;
}
.boilerplate {
color: white;
font-family: sans-serif;
padding: 0.5em;
}
.name {
color: black;
background: white;
font-family: "Marker Felt", cursive;
font-size: 45pt;
padding-top: 0.2em;
}
</style>
<div class="outer">
<div class="boilerplate">
Hi! My name is
</div>
<div class="name">
Bob
</div>
</div>
ولأنّ شجرة نموذج عناصر المستند لا تتضمّن عملية تضمين، يتم عرض بنية علامة name بالكامل في المستند. إذا كانت عناصر أخرى في الصفحة تستخدم عن طريق الخطأ أسماء الفئات نفسها لتنسيق المحتوى أو كتابة النصوص البرمجية، قد تواجهنا مشكلة.
يمكننا تجنُّب حدوث أي مشكلة.
الخطوة 1: إخفاء تفاصيل العرض التقديمي
من الناحية الدلالية، لا يهمّنا سوى ما يلي:
- إنها علامة اسم.
- اسمي "بوب".
أولاً، نكتب علامات أقرب إلى الدلالات الحقيقية التي نريدها:
<div id="nameTag">Bob</div>
ثم نضع جميع الأنماط ودوال div المستخدمة للعرض
في عنصر <template>
:
<div id="nameTag">Bob</div>
<template id="nameTagTemplate">
<span class="unchanged"><style>
.outer {
border: 2px solid brown;
… same as above …
</style>
<div class="outer">
<div class="boilerplate">
Hi! My name is
</div>
<div class="name">
Bob
</div>
</div></span>
</template>
في هذه المرحلة، يتم عرض "بوبي" فقط. بما أنّنا
نقلنا عناصر DOM للعرض التقديمي داخل
عنصر <template>
، لا يتم عرضها، ولكن
يمكن الوصول إليها من JavaScript. نفعل ذلك الآن لتمتَلء جذر النسخة الاحتياطية:
<script>
var shadow = document.querySelector('#nameTag').createShadowRoot();
var template = document.querySelector('#nameTagTemplate');
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);
بعد أن أعددنا جذرًا للظل، يتم عرض علامة الاسم مجددًا. إذا نقرت بزر الماوس الأيمن على علامة الاسم وفحصت العنصر ، ستلاحظ أنّه تنسيق دلالي رائع:
<div id="nameTag">Bob</div>
يوضّح ذلك أنّنا أخفينا تفاصيل عرض علامة الاسم من المستند باستخدام Shadow DOM. يتم تضمين تفاصيل العرض في Shadow DOM.
الخطوة 2: فصل المحتوى عن العرض التقديمي
تخفي علامة الاسم الآن تفاصيل العرض من الصفحة، ولكنها لا تفصل العرض عن المحتوى، لأنّه على الرغم من أنّه يتوفر المحتوى (الاسم "عماد") في الصفحة، إلا أنّ الاسم الذي يتم عرضه هو الاسم الذي نسخناه إلى جذر الصورة الرمزية. إذا أردنا تغيير الاسم الوارد في علامة الاسم، سنحتاج إلى إجراء ذلك في مكانَين، وقد لا تتم مزامنةهما.
عناصر HTML هي عناصر تركيبية، فيمكنك مثلاً وضع زرّ داخل جدول. نحتاج إلى التركيبة هنا: يجب أن تكون علامة الاسم مركبة من الخلفية الحمراء، ونص "مرحبًا!"، والمحتوى الذي يظهر على بطاقة الاسم.
يمكنك أنت، مؤلف المكوّن، تحديد كيفية عمل التركيب مع
أداة العرض باستخدام عنصر جديد يُسمى <content>
. يؤدي ذلك إلى إنشاء نقطة إدراج في عرض التطبيق المصغّر، وتعمل نقطة الإدراج على اختيار المحتوى من المضيف الظلّ لعرضه في تلك النقطة.
إذا غيّرنا علامات الترميز في Shadow DOM إلى ما يلي:
<span class="unchanged"><template id="nameTagTemplate">
<style>
…
</style></span>
<div class="outer">
<div class="boilerplate">
Hi! My name is
</div>
<div class="name">
<content></content>
</div>
</div>
<span class="unchanged"></template></span>
عند عرض علامة الاسم، يتم إسقاط محتوى مضيف الظل
في الموضع الذي يظهر فيه
عنصر <content>
.
أصبح الآن تنسيق المستند أبسط لأنّ الاسم يظهر فقط في مكان واحد، وهو المستند. إذا أردت تعديل اسم المستخدم في صفحتك، ما عليك سوى كتابة ما يلي:
document.querySelector('#nameTag').textContent = 'Shellie';
وهذا كل شيء. يعدّل المتصفّح تلقائيًا عرض علامة الاسم، لأنّنا نعرض محتوى علامة الاسم في مكانه باستخدام <content>
.
<div id="ex2b">
لقد نجحنا الآن في فصل المحتوى عن العرض. يكون المحتوى في المستند، والعرض التقديمي في Shadow DOM. ويحافظ المتصفّح على مزامنتها تلقائيًا عند حلول وقت عرض محتوى معيّن.
الخطوة 3: تحقيق الربح
من خلال فصل المحتوى عن طريقة العرض، يمكننا تبسيط
الرمز البرمجي الذي يعالج المحتوى. في مثال علامة الاسم، يحتاج
الرمز البرمجي إلى التعامل فقط مع بنية بسيطة تحتوي على
<div>
واحد بدلاً من عدة علامات.
الآن إذا قمنا بتغيير العرض التقديمي، فلا نحتاج إلى تغيير أي من التعليمات البرمجية!
على سبيل المثال، لنفترض أنّنا نريد ترجمة علامة الاسم. ولا تزال علامة اسم، لذا لا يتغيّر المحتوى الدلالي في المستند:
<div id="nameTag">Bob</div>
يبقى رمز إعداد جذر الظل كما هو. تغيّر المحتوى الذي يتم وضعه في جذر الصورة الرمزية:
<template id="nameTagTemplate">
<style>
.outer {
border: 2px solid pink;
border-radius: 1em;
background: url(sakura.jpg);
font-size: 20pt;
width: 12em;
height: 7em;
text-align: center;
font-family: sans-serif;
font-weight: bold;
}
.name {
font-size: 45pt;
font-weight: normal;
margin-top: 0.8em;
padding-top: 0.2em;
}
</style>
<div class="outer">
<div class="name">
<content></content>
</div>
と申します。
</div>
</template>
وهذا تحسين كبير مقارنةً بالوضع الحالي على الويب، لأنّه يمكن أن يعتمد رمز تعديل الاسم على بنية المكوّن البسيطة والمتسقة. لا يحتاج رمز تعديل اسمك إلى معرفة البنية المستخدَمة في عملية العرض. إذا أخذنا في الاعتبار ما يتم عرضه، يظهر الاسم ثانيًا باللغة الإنجليزية (بعد "مرحبًا. اسمي هو")، ولكن أولاً باللغة اليابانية (قبل "と申します"). هذا التمييز ليس له معنى من الناحية الدلالية من وجهة نظر تعديل الاسم المعروض، لذلك ليس من الضروري أن يعرف رمز تعديل الاسم هذه التفاصيل.
النقاط الإضافية: التوقّع المتقدّم
في المثال أعلاه، يختار العنصر <content>
كل المحتوى من مضيف الظل. باستخدام سمة
select
، يمكنك التحكّم في نوع
المحتوى الذي يعرضه العنصر. يمكنك أيضًا استخدام عناصر محتوًى
متعدّدة.
على سبيل المثال، إذا كان لديك مستند يحتوي على ما يلي:
<div id="nameTag">
<div class="first">Bob</div>
<div>B. Love</div>
<div class="email">bob@</div>
</div>
وجذر الظل الذي يستخدم أدوات اختيار لغة CSS لتحديد محتوى معيّن:
<div style="background: purple; padding: 1em;">
<div style="color: red;">
<content **select=".first"**></content>
</div>
<div style="color: yellow;">
<content **select="div"**></content>
</div>
<div style="color: blue;">
<content **select=".email">**</content>
</div>
</div>
يتطابق العنصر <div class="email">
مع كل من
العنصرَين <content select="div">
و<content
select=".email">
. كم عدد المرات التي يظهر فيها عنوان البريد
الإلكتروني لبوب، وبأي ألوان؟
الإجابة هي أنّ عنوان البريد الإلكتروني لـ "بدر" يظهر مرة واحدة، وهو أصفر اللون.
والسبب هو أنّه، كما يعرف المتخصّصون في اختراق Shadow DOM، فإنّ
إنشاء شجرة لما يتم عرضه فعليًا على الشاشة يشبه
حفلة كبيرة. عنصر المحتوى هو الدعوة التي تسمح بنقل
المحتوى من المستند إلى الطرف المعني بعرض Shadow DOM. يتم إرسال هذه الدعوات بالترتيب، ويعتمد مَن يتلقّى دعوة على الجهة المُرسَلة إليها (أي سمة select
). وبمجرد أن تتم دعوة المحتوى، يقبل دائمًا الدعوة (من لا يرفض؟!) ويتوقف عن إرسالها. إذا تم إرسال دعوة لاحقة إلى هذا العنوان مرة أخرى، لن يحضر أحد إلى حفلتك.
في المثال أعلاه، يتطابق العنصر<div class="email">
مع كل من أداة الاختيارdiv
وأداة الاختيار.email
، ولكن بما أنّ عنصر المحتوى الذي يتضمّن أداة الاختيارdiv
يقع في وقت سابق من المستند،
ينتقل<div class="email">
إلى المجموعة الصفراء،
ولا يتوفّر أحد للحضور إلى المجموعة الزرقاء. (قد يكون هذا هو السبب وراء كون اللون أزرق جدًا، على الرغم من أن البؤس يحب رفقة رفقاء لا تعرفها أبدًا).
إذا تمت دعوة شخص ما إلى ما مِن حفلة، لن يتم عرضه على الإطلاق. هذا ما حدث لنص "Hello, world" في المثال الأول. يكون ذلك مفيدًا في حال أردت الحصول على عرض مختلف جذريًا: اكتب النموذج الدلالي في المستند، وهو المحتوى الذي يمكن الوصول إليه من خلال النصوص البرمجية في الصفحة، مع إخفائه لأغراض العرض وربطه بنموذج عرض مختلف في Shadow DOM باستخدام JavaScript.
على سبيل المثال، يحتوي HTML على أداة اختيار تاريخ رائعة. إذا كتبت <input
type="date">
، ستظهر لك نافذة منبثقة أنيقة للتقويم. ولكن ماذا لو أردت
السماح للمستخدم باختيار نطاق تواريخ لعطلته
على الجزيرة (مثلاً، مع أرجوحة شبكية مصنوعة من الكروم الأحمر). يمكنك
إعداد المستند على النحو التالي:
<div class="dateRangePicker">
<label for="start">Start:</label>
<input type="date" name="startDate" id="start">
<br>
<label for="end">End:</label>
<input type="date" name="endDate" id="end">
</div>
ولكن عليك إنشاء Shadow DOM الذي يستخدم جدولاً لإنشاء تقويم أنيق يبرز نطاق التواريخ وما إلى ذلك. عندما ينقر المستخدم على الأيام في التقويم، يعدّل المكوّن الحالة في الإدخالين startDate وendDate. وعندما يرسل المستخدم النموذج، يتم إرسال القيم من عناصر الإدخال هذه.
لماذا أدرجت التصنيفات في المستند إذا لم يكن سيتم عرضها؟ والسبب في ذلك هو أنّه إذا عرض المستخدم النموذج باستخدام متصفّح لا يتيح استخدام Shadow DOM، سيظل النموذج قابلاً للاستخدام، ولكن ليس بدرجة الجمال نفسها. سيرى المستخدم ما يلي:
<div class="dateRangePicker">
<label for="start">Start:</label>
<input type="date" name="startDate" id="start">
<br>
<label for="end">End:</label>
<input type="date" name="endDate" id="end">
</div>
اجتياز اختبار أساسيات Shadow DOM
هذه هي أساسيات Shadow DOM، ولقد اجتزت المستوى الأول من Shadow DOM. يمكنك تنفيذ المزيد من الإجراءات باستخدام Shadow DOM، على سبيل المثال، يمكنك استخدام عناصر shadow متعددة على مضيف shadow واحد، أو عناصر shadow متداخلة للتغليف، أو تخطيط صفحتك باستخدام "المشاهد المستندة إلى النماذج" (MDV) وShadow DOM. وWeb Components أكثر من مجرد Shadow DOM.
سنوضّح ذلك في المشاركات اللاحقة.