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

google.script.run هي واجهة برمجة تطبيقات غير متزامنة لجافا سكريبت من جهة العميل تتيح لصفحات خدمة 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. في 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) لتحديد عنصر سيتم تمريره إلى المعالج كمَعلمة ثانية. يتيح لك &quot;عنصر المستخدم&quot; هذا، الذي يجب عدم الخلط بينه وبين الفئة 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 التطبيقات

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

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

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