תג תבנית חדש של HTML'

יצירת סטנדרטים לתבניות בצד הלקוח

מבוא

המושג של תבניות הוא לא חדש בפיתוח אינטרנט. למעשה, שפות/מנועי תבניות בצד השרת כמו 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 רדומים שאפשר לשכפל. אפשר לחשוב על תבניות כחלקים של פיגומים שאפשר להשתמש בהם (ולהשתמש בהם מחדש) לאורך כל משך החיים של האפליקציה.

כדי ליצור תוכן לפי תבנית, מגדירים רכיב markup ומעטפת אותו ברכיב <template>:

<template id="mytemplate">
    <img src="" alt="great image">
    <div class="comment"></div>
</template>

העמודים

כשעוטפים תוכן ב-<template>, אנחנו מקבלים כמה מאפיינים חשובים.

  1. התוכן שלו לא פעיל עד שמפעילים אותו. בעיקרון, ה-Markup הוא 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>

דברים שחשוב לדעת

ריכזתי כאן כמה דברים שצריך לשים לב אליהם כשמשתמשים ב-<template> בשטח:

  • אם אתם משתמשים ב-modpagespeed, חשוב לשים לב לבאג הזה. תבניות שמגדירות <style scoped> מוטבעות, רבות מהן מועברות לראש הדף עם כללי השכתוב של CSS של PageSpeed.
  • אין דרך לבצע 'עיבוד מראש' של תבנית, כלומר אי אפשר לטעון מראש נכסים, לעבד JS, להוריד 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> וטיפול בתוכן שלו כמחרוזת. ג'ון רסיג (John Resig) היה כנראה הראשון שהראה את זה בשנת 2008 באמצעות הכלי שלו ליצירת תבניות מיקרו. היום יש הרבה ספריות אחרות, כולל כמה ספריות חדשות כמו handlebars.js.

לדוגמה:

<script id="mytemplate" type="text/x-handlebars-template">
  <img src="logo.png">
  <div class="comment"></div>
</script>

הסבר על השיטה הזו:

  • לא מתבצע רינדור – הדפדפן לא מבצע רינדור של הבלוק הזה כי הערך של <script> הוא display:none כברירת מחדל.
  • לא פעיל – הדפדפן לא מנתח את תוכן הסקריפט כ-JS כי הסוג שלו מוגדר כמשהו שאינו 'text/javascript'.
  • בעיות אבטחה – מעודדת את השימוש ב-.innerHTML. ניתוח מחרוזות בזמן ריצה של נתונים שהמשתמשים סיפקו עלול להוביל בקלות לנקודות חולשה מסוג XSS.

סיכום

לזכור את העובדה ש-jQuery הפך את העבודה עם DOM הכוללות לפשוטה? התוצאה נוספה לפלטפורמה querySelector() מתוך querySelectorAll(). ניצחון מובהק, נכון? ספרייה ששימשה להצגת DOM באמצעות סלקטורים של CSS, ותקנים שאומצו מאוחר יותר. זה לא תמיד עובד ככה, אבל אני אוהב כשזה קורה.

לדעתי, <template> הוא מקרה דומה. הוא מספק סטנדרטים ליצירת תבניות בצד הלקוח, אבל חשוב יותר, הוא מבטל את הצורך בהאקים שלנו משנת 2008. לדעתי, תמיד כדאי לשפר את תהליך כתיבת התוכן באינטרנט כך שיהיה הגיוני יותר, קל יותר לתחזוקה ומלא יותר בתכונות.

מקורות מידע נוספים