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

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