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

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

مقدمة

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

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

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

والإجابة عن هذا السؤال هي مواصفات نماذج HTML لمواقع الويب (WG). وهي تحدّد عنصر <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. يكون محتوى التطبيق غير نشط إلى أن يتم تفعيله. في الأساس، يُعدّ تنسيق HTML الخاص بك عنصر نموذج بيانات متخفيًا ولا يتم عرضه.

  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);

بعد التخلص من نموذج، يتم نشر محتواه. في هذا المثال بالتحديد، يتم استنساخ المحتوى وتقديم طلب الصورة وعرض العلامات النهائية.

إصدارات تجريبية

مثال: نص برمجي Inert

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

الخاتمة

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

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

مراجع إضافية