توحيد النماذج من جهة العميل
مقدمة
مفهوم إنشاء النماذج ليس جديدًا في تطوير الويب. في الواقع، نجد أن لغات/محركات النماذج مثل Django (Python) وERB/Haml (Ruby) وSmarty (PHP) موجودة منذ فترة طويلة. على الرغم من ذلك، شهدنا في العامين الماضيين ظهورًا كبيرًا في أطر عمل MVC. كل منها مختلف قليلاً، ومع ذلك يشترك معظمها في آلية مشتركة لعرض الطبقة التقديمية (المعروفة أيضًا باسم da view): القوالب.
دعونا نواجه الأمر. القوالب رائعة. استمر، واسأل حولك. حتى تعريفه، يمنحك شعورًا بالدفء والراحة:
"...لا يلزم إعادة إنشائها في كل مرة..." لا أعرف عنك، لكني أحب تجنب العمل الإضافي. لماذا تفتقر منصة الويب إلى دعم أصلي لشيء يهتم به بوضوح المطورون؟
الإجابة عن مواصفات نماذج 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.
}
التعريف بمحتوى النموذج
يمثّل عنصر <template>
في HTML نموذجًا في الترميز. ويتضمن "محتوى النموذج"؛ وهو في الأساس أجزاء inert من 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>
تحدّيات
في ما يلي بعض المشاكل التي واجهتها عند استخدام "<template>
" في الخدمة:
- إذا كنت تستخدم modpagespeed، عليك توخي الحذر
من هذا الخطأ. فالنماذج التي تحدّد
<style scoped>
المضمَّنة، يتم نقل العديد منها إلى عنوان الصفحة باستخدام قواعد إعادة كتابة CSS من PageSpeed. - لا تتوفّر طريقة "للعرض المُسبَق" للنموذج، ما يعني أنّه لا يمكنك تحميل مواد العرض مسبقًا أو معالجة لغة JavaScript أو تنزيل لغة CSS الأوّلية وما إلى ذلك. وينطبق ذلك على كلّ من الخادم والعميل. ولا يتم عرض النموذج إلا بعد نشره.
كن حذرًا مع القوالب المتداخلة. لا تتصرف كما قد تتوقع. مثال:
<template> <ul> <template> <li>Stuff</li> </template> </ul> </template>
لن يؤدي تفعيل النموذج الخارجي إلى تفعيل النماذج الداخلية. وهذا يعني أن النماذج المتداخلة تتطلب أيضًا تفعيل عناصرها الثانوية يدويًا.
الطريق إلى المعيار
دعونا لا ننسى من أين أتينا. لقد كان الطريق إلى قوالب HTML المستندة إلى المعايير طويلاً. على مر السنين، توصلنا إلى بعض الحيل الذكية جدًا لإنشاء قوالب قابلة لإعادة الاستخدام. فيما يلي نوعان شائعان صادفتهما. لذلك أدرجها في هذه المقالة للمقارنة.
الطريقة 1: نموذج العناصر في المستند (DOM) خارج الشاشة
أحد الأساليب التي استخدمها المستخدمون منذ وقت طويل هي إنشاء نموذج كائن "خارج الشاشة"
وإخفاءه عن العرض باستخدام سمة hidden
أو display:none
.
<div id="mytemplate" hidden>
<img src="logo.png">
<div class="comment"></div>
</div>
وعلى الرغم من أنّ هذه التقنية تعمل، فهناك عدد من السلبيات. ملخّص هذا الأسلوب:
- استخدام DOM - يتعرّف المتصفّح على نموذج العناصر في المستند (DOM). إنه جيد في ذلك. يمكننا استنساخه بسهولة.
- لم يتم عرض أي شيء - تؤدي إضافة
hidden
إلى منع ظهور المجموعة. - ليست غير صالحة: على الرغم من إخفاء المحتوى، يتم إجراء طلب من الشبكة للوصول إلى الصورة.
- الأنماط والتصميم غير الملائم - يجب أن تبدأ صفحة التضمين جميع قواعد CSS بالرمز
#mytemplate
من أجل تحديد نطاق الأنماط وصولاً إلى النموذج. هذه هشّة وليست هناك أي ضمانات بعدم مواجهة تضاربات التسمية في المستقبل. على سبيل المثال، يتم توفير الخدمة لنا إذا كانت صفحة التضمين تحتوي على عنصر بهذا المعرّف.
الطريقة 2: التحميل الزائد للنص البرمجي
وهناك أسلوب آخر يتمثل في زيادة تحميل <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.
إنّ جعل عملية تأليف المحتوى على الويب بأكملها أكثر منطقية وقابلية للصيانة، وإتاحة المزيد من الميزات الكاملة، من الأمور الجيدة في كتابي دائمًا.
مراجع إضافية
- مواصفات WhatsApp
- مقدمة عن مكوّنات الويب
- <web>components</web> (فيديو) - عرض تقديمي شامل ورائع من تقديمك حقًا.