خدمة 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 طلبات متزامنة لوظائف الخادم. إذا أجريت الاستدعاء الحادي عشر وكان الرقم 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).