علامة نموذج HTML الجديدة

توحيد النماذج من جهة العميل

مقدمة

إنّ مفهوم النماذج ليس جديدًا في مجال تطوير الويب. في الواقع، كانت لغات/محرّكات النماذج من جهة الخادم، مثل Django (Python) وERB/Haml (Ruby) وSmarty (PHP)، متوفرة منذ فترة طويلة. ومع ذلك، شهدنا في العامَين الماضيَين ازديادًا كبيرًا في استخدام إطارات عمل MVC. تختلف هذه العروض عن بعضها قليلاً، ولكن تشترك معظمها في آلية مشتركة لعرض الطبقة التقديمية (المعروفة أيضًا باسم "عرض البيانات"): النماذج.

لنواجه الأمر. النماذج رائعة. يمكنك الاستفسار من حولك. حتى تعريفه يشعرك بالدفء والراحة:

"…لا يلزم إعادة إنشائه في كل مرة…" لا أعرف رأيك، ولكنّني أحب تجنُّب العمل الإضافي. لماذا لا تتوفّر منصة الويب بدعم أصلي لميزة يهتم بها المطوّرون بوضوح؟

مواصفات WhatWG HTML Templates هي الحلّ. ويحدِّد العنصر <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> بعض الخصائص المهمة.

  1. يكون محتوى التطبيق غير نشط إلى أن يتم تفعيله. في الأساس، يُعدّ تنسيق ترميزك عنصر نموذج بيانات متخفيًا ولا يتم عرضه.

  2. ولن يكون لأي محتوى ضمن النموذج أيّ تأثيرات جانبية. عدم تنفيذ النص البرمجي، وعدم تحميل الصور، وعدم تشغيل الصوت، إلى أن يتم استخدام النموذج

  3. يُعتبر أنّ المحتوى غير مضمّن في المستند. لن يؤدي استخدام document.getElementById() أو querySelector() في الصفحة الرئيسية إلى عرض العناصر الفرعية للنموذج.

  4. يمكن وضع النماذج في أي مكان داخل <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 غير المرئي

من الأساليب التي يستخدمها المطوّرون منذ فترة طويلة هي إنشاء DOM "خارج الشاشة" وإخفاؤه من العرض باستخدام السمة hidden أو display:none.

<div id="mytemplate" hidden>
  <img src="logo.png">
  <div class="comment"></div>
</div>

على الرغم من أنّ هذه الطريقة فعّالة، إلا أنّ لها عددًا من الجوانب السلبية. في ما يلي ملخّص لهذه التقنية:

  • استخدام نموذج DOM: يتعرّف المتصفّح على نموذج DOM. هذا جيد. يمكننا استنساخها بسهولة.
  • لا يتم عرض أي محتوى: تؤدي إضافة hidden إلى منع ظهور الكتلة.
  • غير ثابت: على الرغم من إخفاء المحتوى، يتم إرسال طلب إلى الشبكة للحصول على الصورة.
  • صعوبة التصميم واختيار المظهر: يجب أن تضيف صفحة التضمين #mytemplate إلى كل قواعد CSS لتحديد نطاق الأنماط وصولاً إلى النموذج. هذه الطريقة غير متينة ولا تتضمّن أي ضمانات بأنّنا لن نواجه تعارضات في الأسماء في المستقبل. على سبيل المثال، لن نتمكّن من إجراء ذلك إذا كانت صفحة التضمين تحتوي على عنصر يحمل هذا المعرّف.

الطريقة 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: لا يفكّك المتصفّح محتوى النص البرمجي بصفته JavaScript لأنّ نوعه تم ضبطه على شيء آخر غير "text/javascript".
  • مشاكل الأمان: ينصح باستخدام .innerHTML. يمكن أن يؤدي تحليل سلاسل أثناء التشغيل للبيانات المقدَّمة من المستخدِم بسهولة إلى ظهور ثغرات XSS.

الخاتمة

هل تذكُّر عندما سهّلت مكتبة jQuery العمل مع DOM؟ نتيجةً لذلك، تمت إضافة querySelector()/querySelectorAll() إلى المنصة. فوز واضح، أليس كذلك؟ وقد تبنّت مكتبة رائجة لاحقًا جلب DOM باستخدام أدوات اختيار لغة CSS ومعاييرها. لا تنجح هذه الطريقة دائمًا، ولكنّني أحب استخدامها عندما تنجح.

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

مراجع إضافية