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

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

النصوص البرمجية الصغيرة

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

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

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' ?>.

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

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

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

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

رمز Apps Script في نصوص برمجية صغيرة

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

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

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

يمكن للوحدات النصية الصغيرة استدعاء أي دالة محدّدة في ملف رمز أو مكتبة 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 مباشرةً

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

في ما يلي النموذج البسيط الذي يعرض قائمة بمنتجات Google مرة أخرى، followed by the result of 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. */ ?>