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

google.script.run هي واجهة برمجة تطبيقات غير متزامنة من جهة العميل بلغة JavaScript تتيح لصفحات خدمة HTML استدعاء دوال في "برمجة تطبيقات Google" من جهة الخادم. يوضّح المثال التالي أبسط وظائف 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. في Firefox مثلاً، يبلغ الحد الأقصى 6. وتؤخّر معظم المتصفحات بشكل مشابه الطلبات الزائدة من الخادم إلى أن يكتمل أحد الطلبات الحالية.

المَعلمات وقيم الإرجاع

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

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

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

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

بما أنّ طلبات google.script.run غير متزامنة، يواصل الرمز البرمجي من جهة العميل السطر التالي بدون انتظار ردّ. لتحديد دالة ردّ الاتصال التي يتم تنفيذها عندما يستجيب الخادم، استخدِم 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' throws 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 كمَعلمة، سيصبح النموذج كائنًا واحدًا يحتوي على أسماء الحقول كمفاتيح وقيم الحقول كقيم. يتم تحويل جميع القيم إلى سلاسل، باستثناء محتوى حقول إدخال الملفات التي تصبح كائنات 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 لعرض مربّع حوار أو شريط جانبي في &quot;مستندات Google&quot; أو &quot;جداول بيانات Google&quot; أو نماذج Google، لا يمكنك إغلاق الواجهة عن طريق استدعاء window.close. بدلاً من ذلك، عليك استدعاء google.script.host.close. للاطّلاع على مثال، راجِع القسم حول عرض HTML كواجهة مستخدم في Google Workspace.

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

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