برچسب الگوی جدید HTML

استانداردسازی قالب‌بندی سمت کارخواه

مقدمه

مفهوم الگوبرداری در توسعه وب جدید نیست. درواقع، زبان‌ها/موتورهای قالب‌سازی سمت سرور مانند Django (Python)،‏ ERB/Haml (Ruby)، و Smarty (PHP) مدت زیادی است که وجود دارند. بااین‌حال، در چند سال گذشته، شاهد انفجار چارچوب‌های MVC بوده‌ایم. همه آن‌ها کمی متفاوت هستند، اما اکثر آن‌ها مکانیک مشترکی برای ارائه لایه نمایشی خود (یا همان نما) دارند: الگوها.

بیایید با آن روبه‌رو شویم. الگوها فوق‌العاده هستند. بفرمایید، از دیگران بپرسید. حتی تعریف آن باعث می‌شود احساس گرما و راحتی کنید:

«…لازم نیست هربار دوباره ایجاد شود…» نمی‌دانم شما چطورید، اما من عاشق اینم که از کار اضافی اجتناب کنم. پس چرا پلاتفرم وب فاقد پشتیبانی بومی برای چیزی است که توسعه‌دهندگان به‌وضوح به آن اهمیت می‌دهند؟

مشخصات الگوهای HTML WhatWG پاسخ این سؤال است. این عنصر یک عنصر جدید <template> را تعریف می‌کند که رویکرد استاندارد مبتنی بر DOM را برای قالب‌بندی سمت کارخواه توصیف می‌کند. الگوها به شما امکان می‌دهند قطعات نشانه‌گذاری را که به‌عنوان HTML تجزیه می‌شوند، در بارگیری صفحه استفاده نمی‌شوند، اما می‌توانند بعداً در زمان اجرا نمونه‌سازی شوند، اعلام کنید. به نقل از رافائل واینستین:

They're a place to put a big wad of HTML that you don't want the browser to mess with at all…for any reason.

رافائل واینستین (نویسنده مشخصات)

تشخیص ویژگی

برای تشخیص ویژگی <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. محتوای آن تا زمانی که فعال نشود، عملاً بی‌اثر است. اساساً، نشان‌گذاری شما DOM پنهان است و ارائه نمی‌شود.

  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>

Gotchas

در اینجا چند نکته ظریف وجود دارد که هنگام استفاده از <template> در محیط واقعی با آن‌ها مواجه شده‌ام:

  • اگر از modpagespeed استفاده می‌کنید، مراقب این اشکال باشید. الگوهایی که <style scoped> درون‌خطی را تعریف می‌کنند، ممکن است با قوانین بازنویسی CSS مربوط به PageSpeed به بخش head منتقل شوند.
  • هیچ راهی برای «پیش‌پرداخت» الگو وجود ندارد، یعنی نمی‌توانید دارایی‌ها را پیش‌بارگیری کنید، جاوااسکریپت را پردازش کنید، 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> و دستکاری محتوای آن به‌عنوان رشته است. جان رسیگ احتمالاً اولین کسی بود که این را در سال ۲۰۰۸ با ابزار 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> پرونده مشابهی است. این روش، نحوه الگوبرداری سمت کارخواه را استاندارد می‌کند، اما مهم‌تر از آن، نیاز به رخنه‌های سال ۲۰۰۸ را ازبین می‌برد. به‌نظر من، منطقی‌تر، قابل‌نگهداری‌تر، و پرویژگی‌تر کردن کل فرایند تألیف وب همیشه چیز خوبی است.

منابع بیشتر