خدمة HTML: التواصل مع دوال الخادم

google.script.run هي واجهة برمجة تطبيقات JavaScript تناشد وظائف Apps Script على جانب العميل، وهي غير متزامنة، وتسمح لصفحات خدمة HTML باستدعاء وظائف Apps Script على جانب الخادم. يوضّح المثال التالي الوظيفة الأساسية لـ google.script.run، وهي استدعاء دالة على الخادم من JavaScript على جانب العميل.

Code.gs

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

function doSomething() {
  Logger.log('I was called!');
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      google.script.run.doSomething();
    </script>
  </head>
</html>

في حال نشر هذا النص البرمجي كتطبيق ويب وزيارة عنوان URL الخاص به، لن يظهر أي شيء، ولكن في حال عرض السجلات، ستلاحظ أنّه تمّ استدعاء دالة الخادم doSomething().

تكون طلبات العميل للوظائف من جهة الخادم غير متزامنة: بعد أن يطلب المتصفّح من الخادم تنفيذ الدالة doSomething()، ينتقل المتصفّح مباشرةً إلى سطر الرمز البرمجي التالي بدون انتظار ردّ. وهذا يعني أنّه قد لا يتم تنفيذ استدعاءات دوال الخادم بالترتيب الذي تتوقّعه. إذا أجريت دعوتَين لإجراء وظيفتَين في الوقت نفسه، لا تتوفّر طريقة لمعرفة الوظيفة التي سيتم تنفيذها أولاً، وقد تختلف النتيجة في كل مرة تحمّل فيها الصفحة. في هذه الحالة، تساعدك معالجات حالات النجاح ومعالجات حالات الخطأ في التحكّم في تسلسل رمزك البرمجي.

تسمح واجهة برمجة التطبيقات google.script.run بإجراء 10 طلبات متزامنة لوظائف الخادم. إذا أجريت مكالمة 11 بينما لا تزال 10 مكالمات جارية، سيتم تأخُّر معالجة الخادم إلى أن يتم تحرير إحدى المساحة الـ 10. من الناحية العملية، نادرًا ما تحتاج إلى التفكير في هذا القيد، خاصةً أنّ معظم المتصفّحات تحدّ من عدد الطلبات المتزامنة إلى الخادم نفسه بقيمة أقل من 10. في Firefox مثلاً، يكون الحدّ الأقصى هو 6. وتؤجل معظم المتصفّحات أيضًا طلبات الربط الزائدة بالخادم إلى أن يكتمل أحد الطلبات الحالية.

المَعلمات والقيم المعروضة

يمكنك استدعاء دالة خادم باستخدام مَعلمات من العميل. وبالمثل، يمكن أن تعرِض دالة الخادم قيمة للعميل كمَعلمة يتم تمريرها إلى معالج الإجراء الناجح.

المَعلمات القانونية وقيم الإرجاع هي عناصر أساسية في JavaScript، مثل Number أو Boolean أو String أو null، بالإضافة إلى عناصر وكائنات صفائف JavaScript التي تتكون من عناصر وكائنات وصفائف. إنّ عنصر form ضمن الصفحة هو أيضًا مقبول كمَعلمة، ولكن يجب أن تكون المَعلمة الوحيدة للدالة، ولا يُسمح به كقيمة معروضة. تفشل الطلبات في حال محاولة تمرير Date أو Function أو عنصر نموذج DOM إلى جانب form أو نوع آخر محظور، بما في ذلك الأنواع المحظورة داخل الكائنات أو الصفائف. ولن تنجح أيضًا الكائنات التي تنشئ مراجع دائرية، وستصبح الحقول غير المحدّدة ضمن الصفائف null.

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

معالِجات النجاح

بما أنّ الرمز البرمجي من جهة العميل ينتقل إلى السطر التالي بدون انتظار اكتمال معالجة طلب العميل، withSuccessHandler(function) يتيح لك تحديد دالة استدعاء من جهة العميل لتشغيلها عندما يستجيب العميل. إذا كانت دالة الخادم تعرِض قيمة، تُرسِل واجهة برمجة التطبيقات القيمة إلى الدالة الجديدة كمَعلمة.

يعرض المثال التالي تنبيهًا للمتصفّح عندما يستجيب الخادم. يُرجى ملاحظة أنّ نموذج الرمز البرمجي هذا يتطلّب تفويضًا لأنّ الوظيفة من جهة الخادم تؤدي إلى الوصول إلى حسابك على Gmail. إنّ أبسط طريقة للحصول على إذن النص البرمجي هي تشغيل دالة getUnreadEmails() يدويًا من محرِّر النصوص البرمجية مرة واحدة قبل تحميل الصفحة. بدلاً من ذلك، عند نشر تطبيق الويب، يمكنك اختيار تنفيذه بصفتك "المستخدم الذي يصل إلى تطبيق الويب"، وفي هذه الحالة سيُطلب منك منح الإذن عند تحميل التطبيق.

Code.gs

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

function getUnreadEmails() {
  return GmailApp.getInboxUnreadCount();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function onSuccess(numUnread) {
        var div = document.getElementById('output');
        div.innerHTML = 'You have ' + numUnread
            + ' unread messages in your Gmail inbox.';
      }

      google.script.run.withSuccessHandler(onSuccess)
          .getUnreadEmails();
    </script>
  </head>
  <body>
    <div id="output"></div>
  </body>
</html>

معالِجات حالات الفشل

في حال تعذّر على الخادم الاستجابة أو عرض خطأ، تسمح لك دالة withFailureHandler(function) بتحديد معالِج تعذُّر بدلاً من معالِج نجاح، مع تمرير العنصر Error (إن توفّر) كوسيطة.

إذا لم تحدِّد معالجًا للأعطال، يتم تلقائيًا تسجيل الأعطال في وحدة تحكّم JavaScript. لإلغاء هذا الإجراء، يمكنك استدعاء withFailureHandler(null) أو تقديم معالج أخطاء لا يفعل أي شيء.

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

Code.gs

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

function getUnreadEmails() {
  // 'got' instead of 'get' will throw an error.
  return GmailApp.gotInboxUnreadCount();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function onFailure(error) {
        var div = document.getElementById('output');
        div.innerHTML = "ERROR: " + error.message;
      }

      google.script.run.withFailureHandler(onFailure)
          .getUnreadEmails();
    </script>
  </head>
  <body>
    <div id="output"></div>
  </body>
</html>

عناصر المستخدمين

يمكنك إعادة استخدام معالِج النجاح أو الفشل نفسه لإجراء عدة طلبات إلى الخادم من خلال استدعاء withUserObject(object) لتحديد عنصر سيتم تمريره إلى المعالِج كمَعلمة ثانية. يتيح لك هذا "عنصر المستخدم"، الذي لا يجب الخلط بينه وبين فئة User، الردّ على السياق الذي تواصل فيه العميل مع الخادم. وبما أنّ عناصر المستخدمين لا يتم إرسالها إلى الخادم، يمكن أن تكون أيّ عنصر تقريبًا، بما في ذلك الدوالّ وعناصر DOM وغيرها، بدون القيود المفروضة على المَعلمات والقيم المعروضة لطلبات الخادم. ومع ذلك، لا يمكن أن تكون عناصر المستخدم عناصر تم إنشاؤها باستخدام عامل التشغيل new.

في هذا المثال، سيؤدي النقر على أيّ من الزرَّين إلى تعديل هذا الزرّ باستخدام قيمة من الخادم مع ترك الزرّ الآخر بدون تغيير، على الرغم من أنّهما يتشاركان معالِجًا واحدًا للنجاح. داخل معالِج onclick، تشير الكلمة الرئيسية this إلى button نفسها.

Code.gs

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

function getEmail() {
  return Session.getActiveUser().getEmail();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function updateButton(email, button) {
        button.value = 'Clicked by ' + email;
      }
    </script>
  </head>
  <body>
    <input type="button" value="Not Clicked"
      onclick="google.script.run
          .withSuccessHandler(updateButton)
          .withUserObject(this)
          .getEmail()" />
    <input type="button" value="Not Clicked"
      onclick="google.script.run
          .withSuccessHandler(updateButton)
          .withUserObject(this)
          .getEmail()" />
  </body>
</html>

النماذج

إذا استدعيت دالة خادم باستخدام عنصر form كمَعلمة، يصبح النموذج كائنًا واحدًا يتضمّن أسماء الحقول كمفاتيح وقيم الحقول كقيم. تتم تحويل كل القيمة إلى سلاسل، باستثناء محتوى حقول input-file التي تصبح كائنات Blob.

يعالج هذا المثال نموذجًا، بما في ذلك حقل إدخال ملف، بدون إعادة تحميل الصفحة، بل يحمّل الملف إلى Google Drive ثم يطبع عنوان URL للملف في صفحة جهة العميل. داخل معالِج onsubmit، تشير الكلمة الرئيسية this إلى النموذج نفسه. يُرجى العلم أنّه عند تحميل جميع النماذج في الصفحة، يتم إيقاف إجراء الإرسال التلقائي من قِبل preventFormSubmit. ويؤدي ذلك إلى منع الصفحة من إعادة التوجيه إلى عنوان URL غير دقيق في حال حدوث استثناء.

Code.gs

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

function processForm(formObject) {
  var formBlob = formObject.myFile;
  var driveFile = DriveApp.createFile(formBlob);
  return driveFile.getUrl();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      // Prevent forms from submitting.
      function preventFormSubmit() {
        var forms = document.querySelectorAll('form');
        for (var i = 0; i < forms.length; i++) {
          forms[i].addEventListener('submit', function(event) {
            event.preventDefault();
          });
        }
      }
      window.addEventListener('load', preventFormSubmit);

      function handleFormSubmit(formObject) {
        google.script.run.withSuccessHandler(updateUrl).processForm(formObject);
      }
      function updateUrl(url) {
        var div = document.getElementById('output');
        div.innerHTML = '<a href="' + url + '">Got it!</a>';
      }
    </script>
  </head>
  <body>
    <form id="myForm" onsubmit="handleFormSubmit(this)">
      <input name="myFile" type="file" />
      <input type="submit" value="Submit" />
    </form>
    <div id="output"></div>
 </body>
</html>

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

يمكنك اعتبار google.script.run أداة إنشاء "لمشغّل النصوص البرمجية". إذا أضفت معالِجًا للنجاح أو معالِجًا للفشل أو عنصر مستخدم إلى مشغّل نص برمجي، لن يؤدي ذلك إلى تغيير مشغّل النصوص البرمجية الحالي، بل ستحصل بدلاً من ذلك على مشغّل نص برمجي جديد بسلوك جديد.

يمكنك استخدام أيّ تركيبة وأيّ ترتيب من withSuccessHandler() withFailureHandler() وwithUserObject(). يمكنك أيضًا استدعاء أيّ من الدوالّ المعدِّلة في مشغّل النصوص البرمجية الذي سبق أن تم ضبط قيمة له. تلغي القيمة الجديدة القيمة السابقة ببساطة.

يحدِّد هذا المثال معالِج أخطاء شائعًا لجميع طلبات الخادم الثلاثة، ولكن معالِجَين منفصلَين للنجاح:

var myRunner = google.script.run.withFailureHandler(onFailure);
var myRunner1 = myRunner.withSuccessHandler(onSuccess);
var myRunner2 = myRunner.withSuccessHandler(onDifferentSuccess);

myRunner1.doSomething();
myRunner1.doSomethingElse();
myRunner2.doSomething();

الدوال الخاصة

تُعتبر دوال الخادم التي تنتهي أسماؤها بشرطة سفلية خاصة. لا يمكن استدعاء هذه الدوال من خلال google.script ولا يتم أبدًا إرسال أسمائها إلى العميل. ويمكنك بالتالي استخدامها لإخفاء تفاصيل التنفيذ التي يجب الحفاظ على سريتها على الخادم. لا يمكن لـ google.script أيضًا الاطّلاع على الدوالّ ضمن المكتبات والدوالّ التي لم يتم تحديدها في المستوى الأعلى من النص البرمجي.

في هذا المثال، تتوفّر الدالة getBankBalance() في رمز العميل، ويمكن للمستخدم الذي يتحقّق من رمز المصدر اكتشاف اسمها حتى إذا لم تطلبها. ومع ذلك، لا يمكن للعميل الاطّلاع على الدالتَين deepSecret_() وobj.objectMethod().

Code.gs

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

function getBankBalance() {
  var email = Session.getActiveUser().getEmail()
  return deepSecret_(email);
}

function deepSecret_(email) {
 // Do some secret calculations
 return email + ' has $1,000,000 in the bank.';
}

var obj = {
  objectMethod: function() {
    // More secret calculations
  }
};

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function onSuccess(balance) {
        var div = document.getElementById('output');
        div.innerHTML = balance;
      }

      google.script.run.withSuccessHandler(onSuccess)
          .getBankBalance();
    </script>
  </head>
  <body>
    <div id="output">No result yet...</div>
  </body>
</html>

تغيير حجم مربّعات الحوار في Google Workspace التطبيقات

يمكن تغيير حجم مربّعات الحوار المخصّصة في "مستندات Google" أو "جداول بيانات Google" أو "نماذج Google" من خلال استدعاء google.script.host setWidth(width) أو setHeight(height) في الرمز البرمجي على جانب العميل. (لضبط الحجم الأولي لمربّع حوار، استخدِم HtmlOutput الطريقة setWidth(width) و setHeight(height).) يُرجى العِلم أنّ مربّعات الحوار لا تتم إعادة وضعها في منتصف النافذة الرئيسية عند تغيير حجمها، ولا يمكن تغيير حجم أشرطة القوائم الجانبية.

إغلاق مربّعات الحوار والألواح الجانبية في Google Workspace

إذا كنت تستخدم خدمة HTML لعرض مربّع حوار أو شريط جانبي في "مستندات Google" أو "جداول بيانات Google" أو "نماذج Google"، لا يمكنك إغلاق الواجهة من خلال استدعاء window.close(). بدلاً من ذلك، عليك الاتصال بالرقم google.script.host.close(). على سبيل المثال، اطّلِع على القسم المعنيّ بموضوع عرض صفحات HTML ك Google Workspace واجهة مستخدم.

نقل تركيز المتصفّح في Google Workspace

لتبديل التركيز في متصفّح المستخدم من مربّع حوار أو شريط جانبي إلى أداة تحرير "مستندات Google" أو "جداول بيانات Google" أو "نماذج Google"، ما عليك سوى استدعاء الأسلوب google.script.host.editor.focus(). تكون هذه الطريقة مفيدة بشكل خاص عند استخدامها مع أسلوبَي خدمة المستندات Document.setCursor(position) و Document.setSelection(range).