توحيد نماذج من جهة العميل
مقدمة
إنّ مفهوم استخدام النماذج ليس جديدًا في مجال تطوير الويب. في الواقع، إنّ لغات/محرّكات النماذج من جهة الخادم، مثل Django (Python) وERB/Haml (Ruby) وSmarty (PHP)، متوفّرة منذ فترة طويلة. في العامين الماضيين، شهدنا انتشارًا كبيرًا لأُطر عمل MVC. تختلف جميعها قليلاً، ومع ذلك، يشترك معظمها في آلية مشتركة لعرض طبقة العرض التقديمي (المعروفة أيضًا باسم العرض): القوالب.
دعونا نواجه الأمر. النماذج رائعة. يمكنك طرح أسئلة على الآخرين. حتى تعريفه يجعلك تشعر بالدفء والراحة:
"... لا يلزم إعادة إنشائه في كل مرة..." لا أعرف عنك، لكني أحب تجنب العمل الإضافي. لماذا إذًا تفتقر منصة الويب إلى دعم أصلي لشيء يهتم به المطورون بوضوح؟
مواصفات نماذج HTML من WhatWG هي الحلّ. ويحدّد عنصر <template> جديدًا يصف نهجًا عاديًا مستندًا إلى نموذج DOM لإنشاء النماذج من جهة العميل. تتيح لك النماذج تحديد أجزاء من الترميز يتم تحليلها كملفات HTML، ولا يتم استخدامها عند تحميل الصفحة، ولكن يمكن إنشاء مثيل لها
لاحقًا في وقت التشغيل. على حد قول رافائيل وينشتاين:
وهي مكان لوضع مجموعة كبيرة من رموز HTML التي لا تريد أن يعبث بها المتصفّح على الإطلاق… لأي سبب كان.
رافائيل وينشتاين (مؤلف المواصفات)
رصد الميزات
لاكتشاف ميزة <template>، أنشِئ عنصر DOM وتأكَّد من توفّر السمة .content:
function supportsTemplate() {
return 'content' in document.createElement('template');
}
if (supportsTemplate()) {
// Good to go!
} else {
// Use old templating techniques or libraries.
}
الإعلان عن محتوى النموذج
يمثّل عنصر HTML <template> نموذجًا في الترميز. ويحتوي على
"محتوى النموذج"، وهو في الأساس أجزاء خاملة من نموذج DOM قابلة للاستنساخ.
فكِّر في النماذج على أنّها أجزاء من السقالات التي يمكنك استخدامها (وإعادة استخدامها) طوال
عمر تطبيقك.
لإنشاء محتوى مستند إلى نموذج، عليك تعريف بعض الترميز وتضمينه في العنصر <template>:
<template id="mytemplate">
<img src="" alt="great image">
<div class="comment"></div>
</template>
الركائز
يمنحنا تضمين المحتوى في <template> بعض الخصائص المهمة.
ويكون محتواه غير نشط إلى أن يتم تفعيله. بشكل أساسي، يتم إخفاء الترميز في DOM ولا يتم عرضه.
لن يكون لأي محتوى داخل النموذج آثار جانبية. لا يتم تشغيل النص البرمجي، ولا يتم تحميل الصور، ولا يتم تشغيل الصوت… إلى أن يتم استخدام النموذج.
يُعتبر المحتوى غير مضمَّن في المستند. لن يؤدي استخدام
document.getElementById()أوquerySelector()في الصفحة الرئيسية إلى عرض العُقد الفرعية للنموذج.يمكن وضع النماذج في أي مكان داخل
<head>أو<body>أو<frameset>ويمكن أن تحتوي على أي نوع من المحتوى المسموح به في هذه العناصر. يُرجى العِلم أنّ عبارة "في أي مكان" تعني أنّه يمكن استخدام<template>بأمان في الأماكن التي لا يسمح بها المحلل اللغوي لـ HTML… باستثناء نموذج المحتوى الفرعي. ويمكن أيضًا وضعها كعلامة ثانوية للعلامة<table>أو<select>:
<table>
<tr>
<template id="cells-to-repeat">
<td>some content</td>
</template>
</tr>
</table>
تفعيل نموذج
لاستخدام نموذج، عليك تفعيله. وإلا لن يتم عرض محتواه أبدًا.
وأبسط طريقة لإجراء ذلك هي إنشاء نسخة كاملة من .content باستخدام document.importNode(). السمة .content هي DocumentFragment للقراءة فقط تحتوي على محتوى النموذج.
var t = document.querySelector('#mytemplate');
// Populate the src at runtime.
t.content.querySelector('img').src = 'logo.png';
var clone = document.importNode(t.content, true);
document.body.appendChild(clone);
بعد إنشاء نموذج، يصبح محتواه "متاحًا". في هذا المثال تحديدًا، يتم استنساخ المحتوى، وإرسال طلب الصورة، وعرض الترميز النهائي.
العروض التوضيحية
مثال: نص برمجي غير نشط
يوضّح هذا المثال خمول محتوى النموذج. لا يعمل <script> إلا عند الضغط على الزر، مما يؤدي إلى ختم القالب.
<button onclick="useIt()">Use me</button>
<div id="container"></div>
<script>
function useIt() {
var content = document.querySelector('template').content;
// Update something in the template DOM.
var span = content.querySelector('span');
span.textContent = parseInt(span.textContent) + 1;
document.querySelector('#container').appendChild(
document.importNode(content, true)
);
}
</script>
<template>
<div>Template used: <span>0</span></div>
<script>alert('Thanks!')</script>
</template>
مثال: إنشاء Shadow DOM من نموذج
يربط معظم المستخدمين Shadow DOM بمضيف من خلال ضبط سلسلة من الترميز على .innerHTML:
<div id="host"></div>
<script>
var shadow = document.querySelector('#host').createShadowRoot();
shadow.innerHTML = '<span>Host node</span>';
</script>
تكمن المشكلة في هذا الأسلوب في أنّه كلما زاد تعقيد Shadow DOM،
زادت عمليات دمج السلاسل التي تجريها. لا يمكن توسيع نطاقها، وتصبح الأمور فوضوية
بسرعة، ويبدأ الأطفال في البكاء. هذا النهج هو أيضًا كيف وُلدت ثغرة XSS في المقام الأول! "<template>" في مهمة إنقاذ.
من الأفضل العمل مع DOM مباشرةً من خلال إلحاق محتوى النموذج بجذر ظل:
<template>
<style>
:host {
background: #f8f8f8;
padding: 10px;
transition: all 400ms ease-in-out;
box-sizing: border-box;
border-radius: 5px;
width: 450px;
max-width: 100%;
}
:host(:hover) {
background: #ccc;
}
div {
position: relative;
}
header {
padding: 5px;
border-bottom: 1px solid #aaa;
}
h3 {
margin: 0 !important;
}
textarea {
font-family: inherit;
width: 100%;
height: 100px;
box-sizing: border-box;
border: 1px solid #aaa;
}
footer {
position: absolute;
bottom: 10px;
right: 5px;
}
</style>
<div>
<header>
<h3>Add a Comment
</header>
<content select="p"></content>
<textarea></textarea>
<footer>
<button>Post</button>
</footer>
</div>
</template>
<div id="host">
<p>Instructions go here</p>
</div>
<script>
var shadow = document.querySelector('#host').createShadowRoot();
shadow.appendChild(document.querySelector('template').content);
</script>
Gotchas
في ما يلي بعض المشاكل التي واجهتها عند استخدام <template> في بيئة حقيقية:
- إذا كنت تستخدم modpagespeed، يُرجى الانتباه إلى هذا الخطأ. يمكن نقل النماذج التي تحدّد
<style scoped>مضمّنة إلى الرأس باستخدام قواعد إعادة كتابة CSS في PageSpeed. - لا يمكن "عرض نموذج مسبقًا"، ما يعني أنّه لا يمكنك تحميل مواد العرض مسبقًا أو معالجة JavaScript أو تنزيل CSS الأوّلي وما إلى ذلك، سواء على الخادم أو العميل. يتم عرض النموذج فقط عندما يتم نشره.
يُرجى توخّي الحذر عند استخدام النماذج المُدمَجة. ولا تعمل على النحو المتوقّع. على سبيل المثال:
<template> <ul> <template> <li>Stuff</li> </template> </ul> </template>لن يؤدي تفعيل النموذج الخارجي إلى تفعيل النماذج الداخلية. وهذا يعني أنّ النماذج المُدمَجة تتطلّب تفعيل نماذجها الفرعية يدويًا أيضًا.
الطريق إلى معيار
يجب ألا ننسى من أين أتينا. لقد كان الطريق إلى نماذج HTML المستندة إلى المعايير طويلاً. على مرّ السنين، توصّلنا إلى بعض الحيل الذكية لإنشاء نماذج قابلة لإعادة الاستخدام. في ما يلي اثنان من الأخطاء الشائعة التي واجهتها. وأنا أدرجها في هذه المقالة للمقارنة.
الطريقة الأولى: نموذج DOM خارج الشاشة
من الأساليب التي يستخدمها الأشخاص منذ فترة طويلة إنشاء نموذج DOM "خارج الشاشة" وإخفاؤه عن العرض باستخدام السمة hidden أو display:none.
<div id="mytemplate" hidden>
<img src="logo.png">
<div class="comment"></div>
</div>
على الرغم من أنّ هذه الطريقة فعّالة، إلا أنّها تنطوي على بعض السلبيات. إليك ملخّصًا عن هذه التقنية:
- استخدام نموذج DOM: يعرف المتصفّح نموذج DOM. وهو بارع في ذلك. يمكننا استنساخه بسهولة.
- لا يتم عرض أي شيء: تؤدي إضافة
hiddenإلى منع ظهور الكتلة. - غير خامل: على الرغم من أنّ المحتوى مخفي، لا يزال يتم إرسال طلب شبكة للصورة.
- صعوبة في التنسيق وتطبيق المظاهر: يجب أن تسبق جميع قواعد CSS في الصفحة المضمّنة السمة
#mytemplateمن أجل حصر الأنماط في النموذج. هذا غير مستقر ولا توجد ضمانات بأننا لن نواجه تعارضات مستقبلية في التسمية. على سبيل المثال، سنواجه مشكلة إذا كانت الصفحة المضمّنة تحتوي على عنصر يحمل رقم التعريف نفسه.
الطريقة الثانية: التحميل الزائد للنص البرمجي
هناك أسلوب آخر يتمثل في التحميل الزائد لـ <script> ومعالجة محتواه
كسلسلة. ربما كان "جون ريسيج" أول من أظهر ذلك في عام 2008 من خلال أداة Micro Templating.
هناك الآن العديد من المحركات الأخرى، بما في ذلك بعض المحركات الجديدة مثل handlebars.js.
على سبيل المثال:
<script id="mytemplate" type="text/x-handlebars-template">
<img src="logo.png">
<div class="comment"></div>
</script>
إليك ملخّصًا عن هذه التقنية:
- لا يتم عرض أي محتوى: لا يعرض المتصفّح هذه المجموعة لأنّ السمة
<script>تكونdisplay:noneتلقائيًا. - Inert: لا يحلّل المتصفّح محتوى النص البرمجي على أنّه JS لأنّ نوعه مضبوط على قيمة أخرى غير "text/javascript".
- المشاكل المتعلّقة بالأمان: يشجّع هذا الإجراء على استخدام
.innerHTML. يمكن أن يؤدي تحليل السلاسل في وقت التشغيل للبيانات التي يقدّمها المستخدم إلى ثغرات أمنية في XSS بسهولة.
الخاتمة
هل تتذكر عندما جعلت jQuery العمل مع DOM بسيطًا للغاية؟ وكانت النتيجة إضافة querySelector()/querySelectorAll() إلى المنصة. هذا فوز واضح، أليس كذلك؟ وهي مكتبة شاع استخدامها في جلب DOM
باستخدام أدوات اختيار لغة CSS، وتم اعتمادها لاحقًا في المعايير. لا تنجح هذه الطريقة دائمًا، ولكنّني أحب عندما تنجح.
أعتقد أنّ <template> هي حالة مشابهة. فهو يوحّد طريقة إنشاء النماذج من جهة العميل، والأهم من ذلك أنّه يغنينا عن استخدام حيل عام 2008.
إنّ جعل عملية تأليف الويب بأكملها أكثر منطقية وأسهل في الصيانة وأكثر شمولاً هو أمر جيد دائمًا في رأيي.
مراجع إضافية
- مواصفات WhatWG
- مقدّمة حول مكوّنات الويب
- <web>components</web> (فيديو): عرض تقديمي شامل ورائع من إعدادي.