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

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

Scriptlets

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

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

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 will always be served!</p>
    <? } else  { ?>
      <p>This will never be served.</p>
    <? } ?>
  </body>
</html>

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

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

يشير مصطلح "الهروب السياقي" إلى أنّ خدمة Apps Script تتتبّع سياق الناتج على الصفحة، سواء كان داخل سمة 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.

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

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

يمكن أن تستدعي Scriptlets أي دالة معرَّفة في ملف أو مكتبة رموز برمجية في Apps Script. يوضّح هذا المثال إحدى الطرق لسحب البيانات من جدول بيانات إلى نموذج، ثم إنشاء جدول 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>

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

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

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() سلسلة تتضمّن الرمز الذي ينشئه الخادم من النموذج. إذا سجّلت الرمز، ثم ألصقته في أداة تعديل النصوص البرمجية، يمكنك تشغيله وتصحيح أخطائه كأي رمز عادي في Apps Script.

في ما يلي النموذج البسيط الذي يعرض قائمة بمنتجات 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 will print 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. */ ?>