خدمة 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 (إن وُجد) كوسيطة.

بشكل افتراضي، إذا لم تحدد معالج إخفاق، فسيتم تسجيل حالات الإخفاق في وحدة تحكم جافا سكريبت. لإلغاء هذا، يمكنك استدعاء 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>

نماذج Google

إذا تم استدعاء وظيفة خادم مع عنصر 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.hostsetWidth(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.script.host.editor.focus(). وهذه الطريقة مفيدة بشكل خاص مع خدمة المستندات Document.setCursor(position) وDocument.setSelection(range).