خدمة HTML: نموذج HTML

يمكنك استخدام النماذج لدمج رمز برمجة تطبيقات Google مع HTML من أجل إنشاء صفحات ديناميكية بأقل مجهود. إذا كنت قد استخدمت لغات إنشاء نماذج تمزج بين الرمز وHTML، مثل PHP أو ASP أو JSP، من المفترض أن يكون بناء الجملة مألوفًا.

Scriptlets

يمكن أن تحتوي نماذج برمجة تطبيقات على ثلاث علامات خاصة تُعرف باسم scriptlets. داخل جزء من النص البرمجي، يمكنك كتابة أي رمز برمجي يعمل في ملف عادي من ملفات Apps Script، إذ يمكن لأجزاء النص البرمجي استدعاء الدوال المحدّدة في ملفات الرموز البرمجية الأخرى أو الإشارة إلى المتغيرات العامة أو استخدام أي من واجهات برمجة التطبيقات في Apps Script. يمكنك حتى تحديد الدوال والمتغيرات ضمن Scriptlets، مع العلم أنّه لا يمكن استدعاؤها من خلال الدوال المحدّدة في ملفات التعليمات البرمجية أو النماذج الأخرى.

إذا لصقت المثال التالي في أداة تعديل النصوص البرمجية، سيظهر محتوى العلامة <?= ... ?> (وهي برنامج نصي صغير للطباعة) بخط مائل. يتم تنفيذ هذا الرمز على الخادم قبل عرض الصفحة للمستخدم. بما أنّ رمز السكربتليت يتم تنفيذه قبل عرض الصفحة، لا يمكن تشغيله إلا مرة واحدة لكل صفحة. بخلاف JavaScript من جهة العميل أو دوال برمجة تطبيقات التي تستدعيها من خلال google.script.run، لا يمكن إعادة تنفيذ scriptlets بعد تحميل الصفحة.

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    Hello, World! The time is <?= new Date() ?>.
  </body>
</html>

يُرجى العِلم أنّ الدالة doGet الخاصة بملفات HTML المستندة إلى نماذج تختلف عن الأمثلة الخاصة بإنشاء ملفات HTML الأساسية وعرضها. تنشئ الدالة الموضّحة هنا كائن HtmlTemplate من ملف HTML، ثم تستدعي الطريقة evaluate لتنفيذ البرامج النصية الصغيرة وتحويل النموذج إلى كائن HtmlOutput يمكن أن يعرضه البرنامج النصي للمستخدم.

مقتطفات البرامج النصية العادية

تنفّذ البرامج النصية الصغيرة العادية، التي تستخدم الصيغة <? ... ?>، الرمز بدون عرض المحتوى بشكل صريح على الصفحة. ومع ذلك، كما يوضّح هذا المثال، يمكن أن تؤثّر نتيجة الرمز داخل جزء من البرنامج النصي في محتوى HTML خارج جزء البرنامج النصي:

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <? if (true) { ?>
      <p>This is always served!</p>
    <? } else  { ?>
      <p>This is never served.</p>
    <? } ?>
  </body>
</html>

تؤدي طباعة scriptlets، التي تستخدم الصيغة <?= ... ?>، إلى إخراج نتائج الرمز البرمجي إلى الصفحة باستخدام الهروب السياقي.

يشير مصطلح "الهروب السياقي" إلى أنّ برمجة تطبيقات تتتبّع سياق الناتج على الصفحة، سواء كان داخل سمة HTML أو داخل علامة script من جهة العميل أو في أي مكان آخر، وتضيف تلقائيًا رموز الإلغاء للحماية من هجمات البرمجة النصية على مواقع متعدّدة (XSS).

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

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <?= 'My favorite Google products:' ?>
    <? var data = ['Gmail', 'Docs', 'Android'];
      for (var i = 0; i < data.length; i++) { ?>
        <b><?= data[i] ?></b>
    <? } ?>
  </body>
</html>

يُرجى العِلم أنّ نص البرمجة الصغير الخاص بالطباعة لا يعرض سوى قيمة الجملة الأولى، وتتصرّف أي جمل متبقية كما لو كانت مضمّنة في نص برمجة صغير عادي. وبالتالي، فإنّ البرنامج النصي الصغير <?= 'Hello, world!'; 'abc' ?> وحده يطبع "Hello, world!"

فرض طباعة مقتطفات النصوص البرمجية

إنّ النصوص البرمجية الصغيرة التي يتم فرض طباعتها، والتي تستخدم الصيغة <?!= ... ?>، تشبه النصوص البرمجية الصغيرة التي تتم طباعتها باستثناء أنّها تتجنّب الهروب السياقي.

تكون عملية الإلغاء السياقي مهمة إذا كان النص البرمجي يسمح ببيانات أدخلها المستخدم من مستخدمين غير موثوق بهم. في المقابل، عليك فرض الطباعة إذا كان الناتج من النص البرمجي الصغير يتضمّن عمدًا رمز HTML أو نصوصًا برمجية تريد إدراجها تمامًا كما هو محدّد.

كقاعدة عامة، استخدِم برامج نصية صغيرة للطباعة بدلاً من برامج نصية صغيرة للطباعة الإجبارية، إلا إذا كنت تعلم أنّك بحاجة إلى طباعة HTML أو JavaScript بدون تغيير.

رمز برمجة التطبيقات في scriptlets

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

يُرجى العِلم أنّه بما أنّ رمز النموذج يتم تنفيذه قبل عرض الصفحة للمستخدم، لا يمكن لهذه الأساليب سوى توفير المحتوى الأوّلي للصفحة. للوصول إلى بيانات Apps Script من صفحة بشكل تفاعلي، استخدِم واجهة برمجة التطبيقات google.script.run بدلاً من ذلك.

استدعاء دوالّ "برمجة تطبيقات Google" من نموذج

يمكن أن تستدعي Scriptlets أي دالة معرَّفة في ملف أو مكتبة رمز برمجة تطبيقات. يوضّح هذا المثال إحدى طرق استخراج البيانات من جدول بيانات إلى نموذج، ثم إنشاء جدول HTML من البيانات.

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

function getData() {
  return SpreadsheetApp
      .openById('1234567890abcdefghijklmnopqrstuvwxyz')
      .getActiveSheet()
      .getDataRange()
      .getValues();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <? var data = getData(); ?>
    <table>
      <? for (var i = 0; i < data.length; i++) { ?>
        <tr>
          <? for (var j = 0; j < data[i].length; j++) { ?>
            <td><?= data[i][j] ?></td>
          <? } ?>
        </tr>
      <? } ?>
    </table>
  </body>
</html>

استدعاء واجهات برمجة التطبيقات في "برمجة تطبيقات Google" مباشرةً

يمكنك أيضًا استخدام رمز برمجة تطبيقات Google مباشرةً في النصوص البرمجية الصغيرة. يحقّق هذا المثال النتيجة نفسها التي حقّقها المثال السابق من خلال تحميل البيانات في النموذج نفسه بدلاً من تحميلها من خلال دالة منفصلة.

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <? var data = SpreadsheetApp
        .openById('1234567890abcdefghijklmnopqrstuvwxyz')
        .getActiveSheet()
        .getDataRange()
        .getValues(); ?>
    <table>
      <? for (var i = 0; i < data.length; i++) { ?>
        <tr>
          <? for (var j = 0; j < data[i].length; j++) { ?>
            <td><?= data[i][j] ?></td>
          <? } ?>
        </tr>
      <? } ?>
    </table>
  </body>
</html>

إرسال المتغيّرات إلى النماذج

أخيرًا، يمكنك إرسال متغيرات إلى نموذج من خلال تعيينها كسمات للكائن HtmlTemplate. مرة أخرى، يحقّق هذا المثال النتيجة نفسها التي حقّقتها الأمثلة السابقة.

Code.gs

function doGet() {
  var t = HtmlService.createTemplateFromFile('Index');
  t.data = SpreadsheetApp
      .openById('1234567890abcdefghijklmnopqrstuvwxyz')
      .getActiveSheet()
      .getDataRange()
      .getValues();
  return t.evaluate();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <table>
      <? for (var i = 0; i < data.length; i++) { ?>
        <tr>
          <? for (var j = 0; j < data[i].length; j++) { ?>
            <td><?= data[i][j] ?></td>
          <? } ?>
        </tr>
      <? } ?>
    </table>
  </body>
</html>

نماذج تصحيح الأخطاء

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

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

الدالة getCode

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

في ما يلي النموذج الذي يعرض قائمة بمنتجات Google مرة أخرى، متبوعًا بنتيجة getCode:

Code.gs

function myFunction() {
  Logger.log(HtmlService
      .createTemplateFromFile('Index')
      .getCode());
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <?= 'My favorite Google products:' ?>
    <? var data = ['Gmail', 'Docs', 'Android'];
      for (var i = 0; i < data.length; i++) { ?>
        <b><?= data[i] ?></b>
    <? } ?>
  </body>
</html>

LOG (EVALUATED)

(function() { var output = HtmlService.initTemplate(); output._ =  '<!DOCTYPE html>\n';
  output._ =  '<html>\n' +
    '  <head>\n' +
    '    <base target=\"_top\">\n' +
    '  </head>\n' +
    '  <body>\n' +
    '    '; output._$ =  'My favorite Google products:' ;
  output._ =  '    ';  var data = ['Gmail', 'Docs', 'Android'];
        for (var i = 0; i < data.length; i++) { ;
  output._ =  '        <b>'; output._$ =  data[i] ; output._ =  '</b>\n';
  output._ =  '    ';  } ;
  output._ =  '  </body>\n';
  output._ =  '</html>';
  /* End of user code */
  return output.$out.append('');
})();

الدالة getCodeWithComments

تشبه الدالة getCodeWithComments الدالة getCode()، ولكنها تعرض الرمز الذي تم تقييمه كتعليقات تظهر جنبًا إلى جنب مع النموذج الأصلي.

استعراض الرمز البرمجي الذي تم تقييمه

أول ما ستلاحظه في أي عيّنة من الرمز البرمجي الذي تم تقييمه هو العنصر الضمني output الذي تم إنشاؤه بواسطة الطريقة HtmlService.initTemplate. هذه الطريقة غير موثّقة لأنّ النماذج فقط هي التي تحتاج إلى استخدامها. ‫output هو كائن HtmlOutput خاص يتضمّن خاصيتين باسمين غير عاديين، _ و_$، وهما اختصاران لاستدعاء append و appendUntrusted.

يحتوي output على سمة خاصة أخرى، وهي $out، وتشير إلى عنصر HtmlOutput عادي لا يملك هذه السمات الخاصة. يعرض النموذج هذا العنصر العادي في نهاية الرمز.

بعد فهم هذه الصيغة، يمكنك متابعة بقية الرمز البرمجي. تتم إضافة محتوى HTML خارج البرامج النصية الصغيرة (مثل العلامة b) باستخدام output._ = (بدون إلغاء الترميز السياقي)، وتتم إضافة البرامج النصية الصغيرة كرمز JavaScript (مع إلغاء الترميز السياقي أو بدونه، حسب نوع البرنامج النصي الصغير).

تحتفظ الرموز البرمجية التي تم تقييمها بأرقام الأسطر من النموذج. إذا ظهرت لك رسالة خطأ أثناء تشغيل الرمز الذي تم تقييمه، سيتوافق السطر مع المحتوى المكافئ في النموذج.

التسلسل الهرمي للتعليقات

بما أنّ الرمز البرمجي الذي تم تقييمه يحتفظ بأرقام الأسطر، يمكن للتعليقات داخل البرامج النصية الصغيرة أن تحذف برامج نصية صغيرة أخرى وحتى رمز HTML. توضّح هذه الأمثلة بعض التأثيرات المفاجئة للتعليقات:

<? var x; // a comment ?> This sentence won't print because a comment begins
inside a scriptlet on the same line.

<? var y; // ?> <?= "This sentence won't print because a comment begins inside
a scriptlet on the same line.";
output.append("This sentence prints because it's on the next line, even though
it's in the same scriptlet.") ?>

<? doSomething(); /* ?>
This entire block is commented out,
even if you add a */ in the HTML
or in a <script> */ </script> tag,
<? until you end the comment inside a scriptlet. */ ?>