سرویس HTML: با توابع سرور ارتباط برقرار کنید

google.script.run یک API جاوا اسکریپت ناهمزمان سمت کلاینت است که به صفحات سرویس HTML اجازه می‌دهد تا توابع Apps Script سمت سرور را فراخوانی کنند. مثال زیر اساسی‌ترین عملکرد google.script.run را نشان می‌دهد - فراخوانی یک تابع روی سرور از جاوا اسکریپت سمت کلاینت.

کد.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function doSomething() {
  Logger.log('I was called!');
}

فهرست.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      google.script.run.doSomething();
    </script>
  </head>
</html>

اگر این اسکریپت را به عنوان یک برنامه وب مستقر کنید و از URL آن بازدید کنید، چیزی نخواهید دید، اما اگر گزارش‌ها را مشاهده کنید، خواهید دید که تابع سرور doSomething() فراخوانی شده است.

فراخوانی‌های سمت کلاینت به توابع سمت سرور ناهمزمان هستند: پس از اینکه مرورگر درخواست می‌کند که سرور تابع doSomething() را اجرا کند، مرورگر بلافاصله بدون انتظار برای پاسخ، به خط بعدی کد ادامه می‌دهد. این بدان معناست که فراخوانی‌های تابع سرور ممکن است به ترتیبی که انتظار دارید اجرا نشوند. اگر دو تابع را همزمان فراخوانی کنید، هیچ راهی برای دانستن اینکه کدام تابع ابتدا اجرا خواهد شد وجود ندارد. نتیجه ممکن است هر بار که صفحه را بارگذاری می‌کنید متفاوت باشد. در این شرایط، کنترل‌کننده‌های موفقیت و شکست به کنترل جریان کد شما کمک می‌کنند.

رابط برنامه‌نویسی کاربردی google.script.run امکان فراخوانی همزمان ۱۰ تابع سرور را فراهم می‌کند. اگر در حالی که ۱۰ تابع هنوز در حال اجرا هستند، فراخوانی یازدهم را انجام دهید، تابع سرور تا زمانی که یکی از ۱۰ جایگاه آزاد شود، به تأخیر می‌افتد. در عمل، به ندرت باید به این محدودیت فکر کنید، به خصوص که اکثر مرورگرها از قبل تعداد درخواست‌های همزمان به یک سرور را به عددی کمتر از ۱۰ محدود می‌کنند. به عنوان مثال، در فایرفاکس، این محدودیت ۶ است. اکثر مرورگرها به طور مشابه درخواست‌های اضافی سرور را تا زمانی که یکی از درخواست‌های موجود تکمیل شود، به تأخیر می‌اندازند.

پارامترها و مقادیر بازگشتی

شما می‌توانید یک تابع سرور را با پارامترهایی از کلاینت فراخوانی کنید. به طور مشابه، یک تابع سرور می‌تواند مقداری را به عنوان پارامتری که به یک کنترل‌کننده موفقیت ارسال می‌شود، به کلاینت بازگرداند.

پارامترهای قانونی و مقادیر بازگشتی، مقادیر اولیه جاوا اسکریپت مانند Number ، Boolean ، String یا null و همچنین اشیاء و آرایه‌های جاوا اسکریپت هستند که از مقادیر اولیه، اشیاء و آرایه‌ها تشکیل شده‌اند. یک عنصر form در صفحه نیز به عنوان یک پارامتر قانونی است، اما باید تنها پارامتر تابع باشد و به عنوان یک مقدار بازگشتی قانونی نیست. اگر سعی کنید یک عنصر Date ، Function ، DOM را علاوه بر یک form یا نوع ممنوعه دیگر، از جمله انواع ممنوعه درون اشیاء یا آرایه‌ها، ارسال کنید، درخواست‌ها با شکست مواجه می‌شوند. اشیاء که ارجاعات دایره‌ای ایجاد می‌کنند نیز با شکست مواجه می‌شوند و فیلدهای تعریف نشده در آرایه‌ها null می‌شوند.

توجه داشته باشید که شیء ارسالی به سرور، یک کپی از شیء اصلی می‌شود. اگر یک تابع سرور، شیء‌ای را دریافت کند و ویژگی‌های آن را تغییر دهد، ویژگی‌های شیء در کلاینت تحت تأثیر قرار نمی‌گیرند.

گردانندگان موفقیت

از آنجا که کد سمت کلاینت بدون انتظار برای تکمیل فراخوانی سرور، به خط بعدی می‌رود، withSuccessHandler(function) به شما امکان می‌دهد یک تابع فراخوانی سمت کلاینت را مشخص کنید تا هنگام پاسخ سرور اجرا شود. اگر تابع سرور مقداری را برگرداند، API مقدار را به عنوان پارامتر به تابع جدید ارسال می‌کند.

مثال زیر هنگام پاسخ سرور، یک هشدار مرورگر نمایش می‌دهد. توجه داشته باشید که این نمونه کد نیاز به مجوز دارد زیرا تابع سمت سرور به حساب Gmail شما دسترسی دارد. ساده‌ترین راه برای مجوز دادن به اسکریپت، اجرای دستی تابع getUnreadEmails() از ویرایشگر اسکریپت، یک بار قبل از بارگذاری صفحه است. همچنین، هنگام استقرار برنامه وب ، می‌توانید آن را به عنوان "کاربری که به برنامه وب دسترسی دارد" اجرا کنید، که در این صورت هنگام بارگذاری برنامه از شما درخواست مجوز می‌شود.

کد.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function getUnreadEmails() {
  return GmailApp.getInboxUnreadCount();
}

فهرست.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) به شما این امکان را می‌دهد که به جای یک کنترل‌کننده‌ی موفقیت، یک کنترل‌کننده‌ی شکست (failure handler) تعیین کنید و شیء Error (Error) (در صورت وجود) را به عنوان آرگومان به آن ارسال کنید.

به طور پیش‌فرض، اگر یک مدیریت‌کننده‌ی خطا (failure handler) مشخص نکنید، خطاها در کنسول جاوا اسکریپت ثبت می‌شوند. برای لغو این مورد، withFailureHandler(null) را فراخوانی کنید یا یک مدیریت‌کننده‌ی خطا (failure handler) ارائه دهید که هیچ کاری انجام ندهد.

همانطور که این مثال نشان می‌دهد، سینتکس مربوط به کنترل‌کننده‌های شکست تقریباً مشابه کنترل‌کننده‌های موفقیت است.

کد.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function getUnreadEmails() {
  // 'got' instead of 'get' will throw an error.
  return GmailApp.gotInboxUnreadCount();
}

فهرست.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 باشند.

در این مثال، کلیک کردن روی هر یک از دو دکمه، آن دکمه را با مقداری از سرور به‌روزرسانی می‌کند در حالی که دکمه‌ی دیگر بدون تغییر باقی می‌ماند، حتی با وجود اینکه هر دو یک کنترل‌کننده‌ی موفقیت (failure handler) مشترک دارند. در داخل کنترل‌کننده‌ی onclick ، کلمه‌ی کلیدی this به خود button اشاره دارد.

کد.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function getEmail() {
  return Session.getActiveUser().getEmail();
}

فهرست.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 تبدیل می‌شوند.

این مثال یک فرم، شامل یک فیلد ورودی فایل، را بدون بارگذاری مجدد صفحه پردازش می‌کند؛ فایل را در گوگل درایو آپلود می‌کند و سپس آدرس اینترنتی (URL) فایل را در صفحه سمت کلاینت چاپ می‌کند. در داخل کنترل‌کننده onsubmit ، کلمه کلیدی this به خود فرم اشاره دارد. توجه داشته باشید که پس از بارگذاری همه فرم‌های صفحه، عملکرد ارسال پیش‌فرض توسط preventFormSubmit غیرفعال شده است. این کار از هدایت صفحه به یک آدرس اینترنتی نادرست در صورت بروز استثنا جلوگیری می‌کند.

کد.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function processForm(formObject) {
  var formBlob = formObject.myFile;
  var driveFile = DriveApp.createFile(formBlob);
  return driveFile.getUrl();
}

فهرست.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();

توابع خصوصی

توابع سرور که نام آنها با زیرخط (_) پایان می‌یابد، خصوصی (private) در نظر گرفته می‌شوند. این توابع را نمی‌توان توسط google.script فراخوانی کرد و نام آنها هرگز برای کلاینت ارسال نمی‌شود. بنابراین می‌توانید از آنها برای پنهان کردن جزئیات پیاده‌سازی که باید در سرور مخفی نگه داشته شوند، استفاده کنید. google.script همچنین قادر به دیدن توابع درون کتابخانه‌ها و توابعی که در سطح بالای اسکریپت تعریف نشده‌اند، نیست.

در این مثال، تابع getBankBalance() در کد کلاینت موجود است؛ کاربری که کد منبع شما را بررسی می‌کند می‌تواند نام آن را حتی اگر آن را فراخوانی نکرده باشید، کشف کند. با این حال، توابع deepSecret_() و obj.objectMethod() کاملاً برای کلاینت نامرئی هستند.

کد.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
  }
};

فهرست.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 Docs، Sheets یا Forms را می‌توان با فراخوانی متدهای google.script.host به نام‌های setWidth(width) یا setHeight(height) در کد سمت کلاینت تغییر اندازه داد. (برای تنظیم اندازه اولیه یک کادر گفتگو، از متدهای HtmlOutput setWidth(width) و setHeight(height) استفاده کنید.) توجه داشته باشید که کادرهای گفتگو هنگام تغییر اندازه، در مرکز پنجره والد قرار نمی‌گیرند و تغییر اندازه سایدبارها امکان‌پذیر نیست.

بستن پنجره‌های محاوره‌ای و نوارهای کناری در Google Workspace

اگر از سرویس HTML برای نمایش یک کادر محاوره‌ای یا نوار کناری در Google Docs، Sheets یا Forms استفاده می‌کنید، نمی‌توانید با فراخوانی window.close() رابط را ببندید. در عوض، باید google.script.host.close() را فراخوانی کنید. برای مثال، به بخش مربوط به ارائه HTML به عنوان رابط کاربری Google Workspace مراجعه کنید.

جابجایی فوکوس مرورگر در Google Workspace

برای تغییر فوکوس در مرورگر کاربر از یک کادر محاوره‌ای یا نوار کناری به ویرایشگر Google Docs، Sheets یا Forms، کافیست متد google.script.host.editor.focus() را فراخوانی کنید. این متد به ویژه در ترکیب با متدهای سرویس Document به نام‌های Document.setCursor(position) و Document.setSelection(range) مفید است.