×

[PR]この広告は3ヶ月以上更新がないため表示されています。
ホームページを更新後24時間以内に表示されなくなります。



>> ユーザーガイド >> Uiの構築 >> Htmlサービス | Uiサービス | Guiビルダー |


Htmlサービス

Google.Apps.Scriptが提供するHtmlサービスはスクリプトからのHTML製作と出力に役立つでしょう。


WWW GoogleStyle


概要

Htmlサービスで作成されたユーザーインターフェースのスクリプトには HTML,JavaScript,CSSを含めることができます。
スクリプトは以下の3つの方法でユーザーインターフェースを表示することができます。



HTMLファイル

Google.Apps.Scriptでは、標準的なHTMLとテンプレート化されたHTMLの両方を含むHTMLファイルを作成できます。

HTMLファイルの作成

HTMLファイルを作成するために、スクリプトエディタでFile > New > Html Fileと選択してください。この新規のファイルはプロジェクトのなかに作成され、下のコードで始まります。


<html>
<⁄html>

このHTMLファイルには標準的なHTMLが書き込めます。現在のところGoogle.Apps.ScriptはHTML.4.01をサポートしていますが、スペックはこちら(W3C/HTML 4.01 Specification)で確かめてください。

お馴染みの"Hello World!"を表示するサンプルが下のコードです。"myPage"というHTMLファイルとして名前を付けて保存してください。


<html>
<body>
Hello World!
<⁄body>
<⁄html>

<html>と<body>タグはオプションです。

さて、"myCode"という名前のスクリプトを作ってみます。下のコードを貼り付けてください。このコードの動作は、次のセクションHtmlサービスでさらに理解できますが、 簡単に言えばmyPage.htmlからHTMLコードをロードしています。


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

これでmyPage.htmlmyCode.gsというファイルを持つプロジェクトが出来上がりました。 仕上げきるまで次のステップに従ってください。

  1. Webアプリケーションとして設置する前に、スクリプトのバージョンを保存します。 スクリプトエディタでFile > Manage Versionsを選択して、バージョン名を与え、Save New Versionをクリックします。
  2. ガイドのスクリプトをWeb Appとして設置するに従ってください。
  3. ブラウザでWebアプリに与えられたURLにアクセスします。下のイメージのようになっているでしょう。

テンプレート化されたHTMLを書く

Google.Apps.ScriptのHTMLファイルでは標準的なHTMLに加えてテンプレート化されたHTMLも作成できます。Google.Apps.Scriptテンプレートを使用すると、Google.Apps.ScriptとクライアントHTMLを混在させることができて、ダイナミックなページがちょっとした努力で作成できます。 ミックスコードを使ってHTMLテンプレート言語(PHP、ASP、JSP、組み込みJavaScriptなど)を使用したことがあれば、文法は非常に身近に感じるはずです。

メッセージ(現在日時を含む)を持つ"Hello World!"を表示したいと思えば大変簡単に作れます。テンプレートを使ってこれを完成させるには、前のセクションで作ったコードに 少し手を加えます。


<html>
<body>
Hello World!
<⁄body>
<⁄html>

myPage.html内の上のコードを下のように置き換えます。


Hello World! The time is < ?= 
new
Date()
? >

myCode.gs内のコードも下のように置き換えます。


function
doGet() {
return
HtmlService.
createTemplateFromFile(
'myPage'
).evaluate(); }

スクリプトを新しいバージョンとして保存し、Webアプリとして再度設置すれば下のイメージのように表示されるはずです。

-- [ テンプレートの動作 ] --

テンプレート化されたHTMLは、主に2つの方法で標準のHTMLとは異なります。まず、HTMLファイルに特殊な構文を使用します。これは、ユーザーにページを提供する前にサーバー上で実行するいくつかのコードを指示するためです。 上記の例で言えば< ?= new Date() ? >タグがそれです。HTMLファイルのスクリプトエディタでは、このタグの内容がイタリック体になっていたことに気づいたかもしれません。 イタリック体で示されるコード部分はサーバー側で実行されます。

標準のHTMLと異なる二つ目の点はdoGet関数においてです。ここではHtmlOutputオブジェクトの替わりにHtmlTemplateオブジェクトがあります。
インラインスクリプトを実行した後、スクリプトは、テンプレートのコンテンツを含む新しいHtmlOutputオブジェクトを取得するためにevaluate()を呼び出していることです。 ユーザに提供されるのは、この生成されたHtmlOutputファイルです。

-- [ スクリプトレット・タグ ] --

新たにスクリプトレット・タグと呼ばれるGoogle.Apps.Scriptテンプレートが使用できるタグが、3つあります。スクリプトレットの中では、通常のGoogle.Apps.Script・コード・ファイル上でできることはすべて行うことができます。 さらに、他のコードファイルに定義された関数で、グローバル変数の参照やGoogle.Apps.ScriptのAPIが利用できます。
スクリプトレット内で関数や変数を定義することもできます。ただし、唯一テンプレート内でのみで表示されること、他のテンプレートやコードファイルで定義された関数は呼び出すことができないという警告付きです。

-- [ 標準的なスクリプトレット・タグ ] --

標準的なスクリプトレットは、<?・・・・?>構文を使用する単純なスクリプトレット・タグです。 標準的なスクリプトレットはコードの実行はしますが、何も出力はしません。簡単な例を挙げておきます。


<? 
if
(
true
) { ?> This will always be printed! <? }
else
{ ?> This will never be printed! <? } ?>

if (true)は常に真なので、このテンプレートを評価すれば、"This will always be printed!"をプリントします。 これは、テンプレートの使い方を理解するためのキーポイントになります。つまり、スクリプトレット内部のコードは、スクリプトレット外部にあるHTMLコンテンツに影響を与えることができるということです。

-- [ プリントするスクリプトレット ] --

プリント用スクリプトレットは<?・・・・?>構文を使用します。このスクリプトレットはコンテキストのエスケープを使用しながら、コードを実行して最終的なHTMLへ何らかの出力をします。 コンテキストのエスケープとはテンプレートとしてのHTML出力です。Google.Apps.Scriptは、現在のコンテキスト(HTML属性内、クライアントサイドの<script>内など)を追跡し 正しいエスケープを自動的に適用します。
コンテキストのエスケープは、クロスサイトスクリプティング(XSS)攻撃からスクリプトとユーザーを保護します。たとえば下のプリントスクリプトレットは出力HTMLに"Hello World"を出力します。


<?= 'Hello World!' ?>

プリント用スクリプトレットは最初のステートメントを出力するだけです。そこで、たとえば<?= 'Hello World!' ; 'abc' ?>というスクリプトレットでも"Hello World!"だけしかプリントアウトしません。 プリント用スクリプトレット内の残りのステートメントは、標準的なスクリプトレット内にあるかのように動作します。

次は少し複雑なプリント用スクリプトレットのサンプルです。


My favorite Google products!
<?
var
data = [
'Gmail', 'Docs', 'Android'
];
for
(
var
i = 0; i < data.length; ++i)
{ ?>
<b>
<?
= data[i]
?>
</b>
<? } ?>

このテンプレートを評価すると次の結果が得られます。
My favorite Google products!<br/> <b>Gmail>/b><br/> <b>Docs</b><br/> <b>Android</b>

-- [ 強制プリント用スクリプトレット ] --

強制プリント用スクリプトレットの構文は< ? != ... ? >です。プリント用スクリプトレットと同様、その最初のステートメントの値を最終的なHTMLへ出力しますが、コンテキストをエスケープしません。 コンテキストのエスケープは、信頼できないユーザーが入力をしているときに役立ちます。しかし場合によっては、まったくエスケープせずに指定した通り正確に出力したい時もあります。 出力に追加する文字列にマークアップやスクリプトが含まれている場合です。

原則としては、プリント用スクリプトレットを強制プリント用スクリプトレットよりも利用すべきです。どうしてもマークアップやスクリプトが必要で、変更されないように扱える場合は別ですが。

-- [ テンプレートにデータを取り込む ] --

前のセクションではコンテンツから出力する方法を扱いました。ここではデータへのアクセスを取得するための方法を3つ扱います。 1つ目の方法はGoogle.Apps.Scriptのスクリプトファイル内で定義された関数を呼び出すことです。
以下の3つの例は、スプレッドシート内のdataRange(名前付き範囲)からデータを取得して、セル内のそれらのデータをHTMLテーブルへ出力する方法です。

スクリプトファイルで次の関数を定義しておきます。


My favorite Google products!
function
getData() {
return
SpreadsheetApp.
openById(
'SPREADSHEET_KEY_GOES_HERE'
).getRangeByName(
'dataRange'
).getValues(); }

myTemplate.htmlという名前のHTMLファイルでテンプレートを次のように追加します。


<table>
  <? var data = getData();
     for (var i = 0; i < data.length; ++i) { ?>
    <tr>
      <? for  (var j = 0; j < data[i].length; ++j) { ?>
        <td><?= data[i][j]?></td>
      ? } ?>
    </tr>
  <? } ?>
</table>

この簡単なテンプレートは、スプレッドシートのdataRangeという範囲の値をHTMLのテーブル(表)にプリントします。

テンプレートがデータにアクセスする2つ目の方法は、テンプレートに直接データをロードするための呼び出しを行う方法です。スクリプトレットコードは(Google.Apps.Scriptができる)任意の呼び出しを呼び出せるので、テンプレートだけでスプレッドシート自体のデータをロードできます。


<table>
<?
var
data =
SpreadsheetApp.
getActiveRange().getValues(); ...

3つ目の方法です。下のようにスクリプトファイル内のテンプレートに直接変数を追加します。


function
doGet() {
var
t =
HtmlService.
createTemplateFromFile(
'myTemplate'
); t.data =
SpreadsheetApp.
openById(
'SPREADSHEET_KEY_GOES_HERE'
).getRangeByName(
'dataRange'
).getValues();
return
t.evaluate(); }

テンプレートのコード内部では、変数dataHtmlTemplateオブジェクトの設定した結果を所持します。 任意に設定された変数は、どれも同様に利用できます。また、必要に応じて、同じテンプレートでこれらのアプローチを混在させることができます。

-- [ クライアントサイドのスクリプトを書く ] --

テンプレートは単にHTMLをプリントするのみではありません。もっとも強力な使い方としては、クライアントサイドのスクリプトをダイナミックに構築することです。

上の例ではスプレッドシート内の値をHTMLテーブルに書き出しました。
さて、ユーザーのブラウザ内で実行されるスクリプトにスプレッドシートの値を利用することを想像してみてください。
スクリプトはすべてのデータの並べ替えをした後、それらのすべて(あるいは一部分)を表示しない可能性があります。ここでは、クライアントサイドのスクリプトにデータを取り込む方法を示す簡単な例です。


<script>
var
x =
<?!=
JSON.stringify(
SpreadsheetApp.
openById(
'SPREADSHEET_KEY_GOES_HERE'
) .getRangeByName(
'dataRange'
).getValues
();) ?>
</script>

上のコードは評価された後に、スプレッドシート内のデータに応じて下のようなコードになります。
この例で重要なことは、クライアントサイド・スクリプトレットの一部として評価して欲しいので、強制プリント用スクリプトレットが使われていることです。 もし、プリント用スクリプトレット使用してしまったら、オブジェクトが文字列となって表示されてしまいます。


<script>
  
var
x
= [[
a, b, c
]
,
[
1, 2, 3
]];
</script>

ユーザーのブラウザでは、実行されているスクリプトで変数xが利用可能なある種のデータを割り当てられていることがわかります。 スクリプトレットからクライアントサイド・スクリプトへ値を渡しているように見えるかもしれませんが、実際には評価済されるスクリプトレットとその出力がクライアントサイド・スクリプトに取り込まれているに過ぎません。 これは情報の一方向の流れです。クライアントサイド・スクリプトからサーバーサイド・スクリプトへの値の”譲渡”については意味を持ちません。

-- [ テンプレートのデバッグ ] --

テンプレートのデバッグは、通常のコードをデバッグするよりもいくらか敷居が高くなります。テンプレートは、コードを同等のJavaScriptに変換されることによって評価され、そのJavaScriptを実行します。 テンプレートがどのようにスクリプトレットを解釈しているのか、期待した結果が得られないのはなぜか、などの疑問がいつも明確にされるということではありません。 このため、HtmlTemplateクラスでは現象の理解のために役立つ2つのデバッグメソッドを用意しています。

最初のデバッグメソッドはgetCodeと呼ばれるものです。
テンプレート上にgetCodeを呼び出すと、テンプレートとして解釈されている正確なJavaScriptコードを見ることができます。 さらに、そのコードをスクリプトエディタに貼り付けて実行すれば、動作や、どこにバグがあるかさえも分かってしまいます。再度Google.productsのリストを表示する簡単なテンプレートを見てみましょう。


My favorite Google products!
<?
var
data = [
'Gmail', 'Docs', 'Android'
];
for
(
var
i = 0; i < data.length; ++i)
{ ?>
<b>
<?
= data[i]
?>
</b>
<? } ?>

下はテンプレート用にgetCodeを呼び出した結果です。


(
function
() {
var output
=
HtmlService.
initTemplate(); output._ =
'\n';
output._ =
'My favorite Google products!\n'
; var data = [
'Gmail', 'Docs', 'Android'
];
for (var i
= 0; i < data.length; ++i) { ; output._ =
'<b>'
; output._$ = data[i] ; output._ =
'</b>\n'
; } ;
/* End of user code */
return output.$out.append(''); })();

上のコードを眺めてみましょう。最初に注意することは、文書化されていない関数であるHtmlService.initTemplate()で作成された暗黙の出力オブジェクトです。 唯一テンプレート自体が使用することを目的としているのでこの関数は文書化されていないのです。それは、_や_$といった普通の名前の付け方とは違うプロパティを定義する、特別なHtmlOutputオブジェクトを生成します。 これらは実際に通常の意味でのプロパティではありませんが、そのかわりにappendappendUntrustedを呼び出すための省略記号になっています。 output._に値を割り当てれば、output.append()を呼び出すことと同じになります。また、output.$_であれば、output.appendUntrusted()を呼び出すことと同じです。

この構文が理解できれば、残りのコードも楽に分かることでしょう。スクリプトレットの外側のHTMLコンテンツはoutput._=を利用して追加されたものです。 また、スクリプトレットはJavaScriptとして提供されています。おそらく、この変化を理解する上で最も重要なことは、行が維持されていることです。 つまり、このコードを実行中にエラーが発生した場合は、エラーが発生した行番号がテンプレート内の同等の内容に正確に対応するということです。これを確かめたい場合はgetCodeWithCommentsという名前の2番目のデバッグメソッドを呼び出せばよいのです。
下はこの同じテンプレートのgetCodeWithCommentsメソッドによる出力です。


(function() { return eval('var output = HtmlService.initTemplate(); output._ =  \'\\n\';\n' +  //
  'output._ =  \'My favorite Google products!\\n\';\n' +                          // My favorite Google products!
  ' var data = [\'Gmail\', \'Docs\', \'Android\'];\n' +                           // <? var data = ['Gmail', 'Docs', 'Android'];
  '  for (var i = 0; i < data.length; ++i) { ; \n' +                              //   for (var i = 0; i < data.length; ++i) { ?>
  'output._ =  \'  <b>\'; output._$ =  data[i] ; output._ =  \'</b>\\n\';\n' +    //   <b><?= data[i] ?></b>
  ' } ; \n' +                                                                     // <? } ?>
  '/* End of user code */\n' +
  'output.$out.append(\'\');');
})();

最後に出力変数が、もう一つの特別なプロパティを持っていることを確認します。$outという名前を持ち、通常のHtmlOutputオブジェクトはこれらの特殊なプロパティは備えていません。 コードの最後の部分を見れば、これがテンプレートによって返されたものであることがわかると思います。

すでに生成されたコードを見ているので、テンプレート構文のいくつかの特性には大きな意味があることに気が付くでしょう。スクリプトレット・タグの終わりには、暗黙のうちに ; が追加されています。 こんな具合です。


<?
var x = 1
?><?
var y = 2
?>

変換後は;


var x = 1; var y = 2;

スクリプトレットにコメントを追加すると、他のスクリプトレットやHTMLコードでもコメントアウトすることができます。


<?
var
x = 1;
// a comment ?> This HTML won't be printed; it's commented out.
<?
var
y = 2;
//
?> <?=
"This won't be printed because it's on the same line as that comment";
output.append
(
"But this will be because it's on the next line, even though it's in the same scriptlet.)
?>

もうひとつの例です。


<? doSomething(); /* ?>
All of this is commented out.
Even if you add a */ inside HTML.
And even inside of a client <script> */ </script> tag!
<? until you end the comment inside a scriptlet. */ ?>


コメントやタグセットがどのように評価されるかについて疑問がある場合は、getCode()を使用してコードを確認することができます。 しかし、通常、HTMLファイルでは、ルールを認識しているシンタックスハイライトに頼る方が簡単です。



HTMLサービス

Htmlサービスは、Google.CajaによるサニタイジングによってスクリプトからHTML出力を提供することができます。 Caja Compilerは、サードパーティ製のオープンソースのツールです。HTML、CSS、およびJavaScriptをWebサイトやWebアプリケーションに安全に埋め込むためのものです。
Htmlサービスには、主に2つのクラス -- HtmlOutputHtmlTemplate --が含まれています。

-- [ Html出力 ] --

HtmlOutputオブジェクトは、スクリプトから提供されるコンテンツが含まれています。このHtmlOutputオブジェクトは、doGetメソッド関数から返さなければならないものであり、 reference documentationHtmlOutputで使用可能なそれぞれのメソッドについて詳しく説明します。

HtmlOutputクラスは2つのappendメソッドを持っています。
強制プリントに対応して、コンテキストのエスケープを行わないappendと、プリントに対応してコンテキストのエスケープを行うappendUntrustedです。

スクリプトレットがそうであるようにHtmlOutputオブジェクトが同じ付加機能を持っていることは偶然ではありません。 テンプレート内には、テンプレートのコードでビルドされる出力と呼ばれる暗黙的に定義されたHtmlOutputがあるからです。以前のプリント用スクリプトレットの例に戻って、これに代わるものを書いてみます。


My favorite Google products!
<?
var
data = [
'Gmail', 'Docs', 'Android'
];
for
(
var
i = 0; i < data.length; ++i)
{ ?>
<b>
<?
output.appendUntrusted(data[i])
?>
</b>
<? } ?>

出力の結果はプリント用スクリプトレットを使用したものと同一のものとなります。どちらの方法を選択するかは単にスタイルの問題です。 明示的であることを好む場合は、冗長ではありますが、プリント用および強制プリント用スクリプトレットを選べばよいでしょう。

-- [ Htmlテンプレート ] --

HtmlTemplateは動的にHtmlOutputを作り出すことのできるオブジェクトです。 その内容をファイルや文字列から取得します。HtmlTemplateについての詳細はreference documentationで説明しています。

HtmlTemplateの特に注目すべき2つのメソッドはevaluategetCodeです。 evaluateメソッドは、テンプレートを実行しHtmlOutputオブジェクトを返します。getCodeメソッドは、テンプレートファイルに基づいてJavaScriptコードの文字列を返します。 返されたこのコードはテンプレートが実際に動作しているコードです。

-- [ google.script APIs ] --

google.script.APIはクライアントサイドのJavaScript.APIです。Google.Apps.ScriptのHtmlサービスによって作成されたWebアプリがGoogle.Apps.Scriptのサーバー上の関数を呼び出し、レスポンスの受信を可能にします。 HtmlServiceのWebアプリ上で利用できるgoogle.scriptを得るために特別なことをする必要はありません。すべてのHtmlServiceページでは暗黙のうち利用できるようになっています。

HtmlServiceでは2種類の関数に出会います。つまり、サーバーサイドのGoogle.Apps.Script関数とユーザーのブラウザで実行されるクライアントサイドのJavaScript関数です。 サーバー側の関数は、Google.APIs、スクリプトプロパティ、ライブラリーなどへのアクセス含むGoogle.Apps.Scriptの完全な機能を持っています。クライアント側の関数は、ブラウザページの変更やユーザーとの対話を行い、Google.Apps.Script.APIsを直接利用することはありません。 そのかわりにgoogle.scriptを介してサーバーの関数を呼び出す必要があります。

ここで簡単な例を示します。このセクションの他の例と同様、ここではスクリプトファイルのコンポーネントとHTMLファイルのコンポーネントが含まれます。

Google Apps Script:

function
doGet() {
return
HtmlService.
createHtmlOutput(
'myFile.html'
) }
function
doSomething() {
Logger.
log(
'I was called!'
); }

Html File named 'myFile':

<script>
google.script.run.doSomething();
</script>

上記の例はgoogle.scriptの最も基本的な機能を示しています。(クライアント側のJavaScripからサーバー上の関数の呼び出し) このWebアプリケーションを設置し、ブラウザでURLにアクセスしても何も表示されません。しかし、スクリプトエディタに戻ってView > Logsを見ればサーバー側の関数が呼び出されていることが分かります。

すべてのサーバー関数の呼び出しは非同期です。具体的には、ブラウザがサーバーのdoSomethingの実行を要求しても応答を待たないで、コードはすぐに次の行を続けることを意味します。 ブラウザのJavaScriptがシングルスレッドなのでこれは重要なことです。もしコードがサーバーの応答を待っていたら、応答が戻ってくるまでの間はユーザーがアプリ内でボタンをクリックしたり何らかの動作を試みても何も反応しません。 またこれは、サーバ側の関数の呼び出しが、期待した順序では実行されないということでもあります。
前のサンプルコードに少し手を加えて2つの関数を持たせてみましょう。


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

Html File named 'myFile':

<script>
google.script.run.doSomething(); google.script.run.doSomethingElse();
</script>

この関数のどちらが先に実行されるかを知る方法はありません。また、ページをロードするたびに異なる可能性があります。

サーバー側の関数をクライアントからパラメーターを使って呼び出すことができます。
適正なパラメーターとはJavaScriptのプリミティブなもの -- 数値、ブール値、文字列、nulls、JavaScriptオブジェクト、およびプリミティブオブジェクトや配列で構成された配列などです。 これらの他のどんなタイプのパラメーターを利用してもサーバー側の関数は呼び出すことはできません。例えば、サーバ側の関数を呼び出して、日付、DOM要素、JavaScript関数等を渡すと失敗します。 無効なパラメーターがその中に含まれているJavaScriptオブジェクトや配列は適しません。さらに、循環参照を持つオブジェクトもエラーになりますし、配列内の未定義のフィールドはすべてnullになります。
以下の例では、クライアントからパラメータを使用してサーバー側の関数を呼び出す方法を示しています。


function doSomethingWithParams(a, b) {
Logger.
log(
'a is '
+ a);
Logger.
log(
'b.foo is '
+ b); b.foo =
"something else"
;
// doesn't change the value on the client
}

Html File named 'myFile':

<script>
google.script.run.doSomethingWithParams(
123
, { foo:
"bar"
});
</script>

サーバー上で、関数doSomethingWithParamsは番号123及び、値"bar"を持つ"foo"という名前のプロパティのあるオブジェクトで呼び出されます。 これらのパラメータは、元のオブジェクトのコピーです。たとえば、サーバー上のb.fooの値を変更してもクライアント上のオブジェクトには影響を与えません。

-- [ 成功するハンドラー 失敗するハンドラー ] --

サーバ側の関数は非同期に呼び出されるので、それらの実行終了を待たずにクライアントコードは次の行に続きます。そこで、google.script.APIは、サーバーへのコールが完了したときに 実行される関数を指定できるように成功と失敗のハンドラーを備えています。サーバ側の関数は値を返すことができます。単なるパラメータのように、戻り値はプリミティブ型、配列型とオブジェクトで構成されている必要があります。

クライアント側のコードが実際にこの値を使用するためには、サーバーを呼び出すときにハンドラ関数を指定する必要があります。ハンドラは、サーバ側の関数が終了したときにgoogle.script.APIによって呼び出されるクライアント側の関数です。 サーバー側の関数が値を返した場合、ハンドラはその渡された値を取得します。サーバー側の関数が何も返さない場合は、ハンドラがまだ生きていれば、戻り値が無くてもサーバ側の関数が完了したときに呼び出されます。

Google Apps Script:

function
getSuperHero() {
return
{name:
"SuperGeek"
, catch_phrase:
"Don't worry ma'am, I come from the Internet"
}; }

Html File:

<script>
function
onSuccess(hero) { alert (hero.catch_phrase); } google.script.run.withSuccessHandler(onSuccess).getSuperHero();
</script>

このコードではサーバーからキャッチしたフレーズが、ポップアップで表示されます。
サーバからの応答を頻繁にテストするときは、.withSuccessHandler(alert)を利用すると便利です。 これは、単にサーバーからブラウザのネイティブメッセージポップアップ機能へ結果を渡しただけです。

エラーをスローしたりサーバーに接続できない時は、その後にサーバー側で成功のハンドラーが呼び出されることはありません。これらのケースを管理するために、障害の発生したサーバ機能に対処するためのエラーハンドラーを指定することができます。 構文はほぼ同じです。

Google Apps Script:

function
getSuperHero() {
x.y();
// Oops! This will throw a ReferenceError.
return
{name: "SuperGeek", catch_phrase:
"Don't worry ma'am, I come from the Internet"
}; }

Html File:

<script>
function
onFailure(error) { alert (error.message); } google.script.run.withFailureHandler(onFailure).getSuperHero();
</script>

時には同じ成功または失敗のハンドラーを何度も再利用したいものですが、サーバー側の関数が呼び出されたコンテキストに関する何らかの余分な識別情報を渡すことがあります。 そこで、サーバー側の関数呼び出しのある”ユーザーオブジェクト”を指定します。このオブジェクトは、サーバーに送信されません。 パラメーターや戻り値の強制力になんら制限されずに関数、DOM要素などを含めたものになることが可能です。 サーバー側の関数が完了して、正常または失敗で終了すると、ユーザーオブジェクトに成功したか失敗したかのハンドラーの2番目のパラメータが渡されます。

ここでは2つのボタンを表示する例です。
両方のボタンが同じ成功のハンドラーを使用していても、ボタンクリックでサーバーからの値を持つボタンを更新し、他方のボタンは更新しません。

Google Apps Script:

function
getCurrentDate() {
return new
Date().
toString(); }

Html File:

<script>
function updateButton(date, button) { button.value =
"Last clicked on "
+ date; }
</script>
<input
type=
'button'
value=
'Never Clicked'
onclick
=
'
google.script.run.withSuccessHandler(updateButton).withUserObject(
this
).getCurrentDate()
'
>
<input
type=
'button'
value=
'Never Clicked'
onclick
=
'
google.script.run.withSuccessHandler(updateButton).withUserObject(
this
).getCurrentDate()
'
>

ふたつは同じボタンですが、onclickでそれぞれがユーザーオブジェクトとしてサーバーを呼び出します。onclickハンドラ内では、thisキーワードは、ボタン自体を指します。 したがって、サーバー側の関数が完了すると、updateButtonは、ユーザーオブジェクトの最初のパラメーターとしてサーバーの応答とともに呼び出されます。 この場合、正しいボタンは2番目のパラメータになります。

withSuccessHandler、withFailureHandler、withUserObjectは自由に組み合わせて使うことができます。すべてオプションであり、順序は関係ありません。 すでに値が設定されているスクリプト・ランナー上で、任意の変更する関数を呼び出すことができます。新しい値は直前の値をオーバーライドします。

もう少しAPIのbehaviorを理解したければ、google.script.run"をスクリプト・ランナー'オブジェクトとして考えることができます。3つの修飾子のいずれかを使用する場合は、スクリプト・ランナーは変更されません。 代わりに、新しいbehaviorセットで新品のスクリプト・ランナーを取り戻すことができます。下のコードは完全に有効です。


// We want to use a common failure handler for all the server calls below.
var
myRunner = google.script.run.withFailureHandler(myFailureHandler); myRunner1 = myRunner.withSuccessHandler(handler1); myRunner2 = myRunner.withSuccessHandler(handler2); myRunner1.foo(); myRunner1.bar(); myRunner2.baz();

myRunnerにwithSuccessHandlerを設定しても、myRunnerに格納されている内容は変更されません。 - 新規のランナーを作成します。この間違いをしないように注意してください。


var
myRunner = google.script.run; myRunner.withFailureHandler(myFailureHandler); myRunner.foo();
// There is no failure handler set here!

google.script.APIでは、同時に実行できるサーバー側の関数の数を制限しています。現在のところ、同時に10のサーバー側関数の実行をを可能にしています。 10個が実行中に11個目を呼び出したとしても何も起きませんし、不具合が生じることもありません。10スポットのうち一つが解放されたときに関数は遅れて実行されるだけです。 実際にはこの制限を考慮する必要はほとんどありません。特にほとんどのブラウザが、同一サーバーへの同時要求の数を10以内に制限して以来は問題ありません。たとえばFirefoxの限界は6個です。 ほとんどのブラウザは、既存のリクエストのひとつが完了するまでは、次のサーバーへのリクエストを遅らせて送信する類の動作を採用しています。

最後がアンダースコアーで終わる名前をもつサーバー側の関数はGoogle.Apps.Scriptのプライベートとみなされて、クライアントから呼び出されることはありません。 これらは、たとえブラウザのコンソールウィンドウであってもクライアントコードには表示されません。サーバー上で秘密にする必要のある実装の詳細を隠すことで利用については可能です。

Google Apps Script:

function
mySecretFunction_(num) {
var
foo = num;
// do some secret calculations involving foo
return
foo; }
function
doSomethingImportant(num) {
return
"The new value is: "
+ mySecretFunction_(num); }

このコードのdoSomethingImportantは利用可能です。あなたが呼び出さなくても、もしソースコードを覗いているユーザーがいれば、この関数名を発見することができまが、mySecretFunction_はまったく知られていません。

-- [ フォーム ] --

サーバー側の関数は、プリミティブとオブジェクトや配列以外のものを使用して呼び出すことができないというルールがありましたが、これには例外がひとつあります。それは、DOMからのフォーム要素である単一のパラメーターを使用して呼び出すことです。 フォームは関数へと送信されて単一のパラメータオブジェクト(フォームのフィールド名はキーとフィールド値--常に文字列)の値に変換されます。ファイルの入力フィールドは、Blobオブジェクトになります。 ファイルに入力フィールドのあるフォームを作成し、現在実行中のスクリプトにファイルにあるフォームデータを渡す例が下です。

Html File:

<form
id=
'myForm'
>
<input
name=
'myFile'
type=
'file'
>
<input
name=
'aField'
>
</form>

Google Apps Script:

<script>
google.script.run.processForm(document.getElementById(
'myForm'
));
</script>

サーバーではprocessFormが下のような形のオブジェクトで呼び出されます。


{
myFile: <a
Blob
containing the file>; aField: <the value
in
the field>
}

他のサーバー呼び出しのように、ページのリダイレクトなしにフォームをこの方法で送信し、レスポンスをコード内処理できるように成功と失敗のハンドラーを備えることができます。 モダンブラウザがイベント送信処理をするような方法で通常の送信ボタンを使うテクニックは無理ですが、動作をエミュレートすることはできます。


<
form
id=
'myForm'
> <
input
name=
'myFile'
type=
'file'
> <
input
name=
'aField'
> <
input
type=
'button'
onclick=
'google.script.run.processForm(this.parentNode)'> </form>

このフォームの場合、onclickハンドラ内ではthisキーワードはボタン自体を指します。そして、他のDOM要素と同じように、ボタンはその外側の要素を指すparentNodeプロパティを持っています。

Caja Sanitizationを理解する

Google.Apps.ScriptのWebアプリはscript.google.comドメイン上で実行され、このことは多くの有用な機能を可能にしています。たとえば、スクリプトのGoogle.Sitesへの埋め込み、IDへのアクセス権、Webアプリへアクセス中のサインインユーザーの承認などです。 しかし、これらのWebアプリにアクセスするユーザのセキュリティを保護するために、スクリプトは任意のHTMLやJavaScriptを提供するようには許可されていません。

HtmlサービスではCaja Compilerを使ってHTMLをサニタイズしてサンドボックス化しています。 これは、ページを表示する前に、その前段階でセキュリティ攻撃を検出することと、不審なアクションを直ちに防ぐブラウザ内のサンドボックスの設置で構成されています。
次のセクションでは、Caja sanitizationプロセスの結果としてのHtmlServiceを使用して構築されたWebアプリでの制限項目をいくつか示しています。

-- [ ブラウザ サポート ] --

HtmlサービスのWebアプリは、Google Chrome, Firefox, Safari, Opera, Internet Explorer 9 以上の最近のほとんどのバージョンでサポートされています。 他のブラウザの利用者は、その動作状況や仕様によって利用の有無を決められるでしょう。

-- [ HTML サポート ] --

HtmlサービスでサポートされているHTMLは、今は許可されていない<iframe>、<object>、<embed>タグなどの少数の例外を除いて、HTML.4.01とほぼ同じです。 いくつかのHTML5の機能は、ブラウザに応じて利用できるかもしれません。HTLM上で、最後に__(2つのアンダースコア)で終わる任意のID名を使えないという制限があります。 HTMLサービスのプリプロセッサーは、コードの検証とscript.google.com上での実行の安全性を保証しますが、不正な形式のHTMLの検出には厳格です。 もしプリプロセッサーがHTMLを解析できない場合は、予想外のエラーが表示されます。

-- [ イメージ ] --

Htmlサービスのページ内に表示されるすべての画像は、Google’s image proxy経由でロードされて書き換えられています。 このプロキシは、マルウェアが埋め込まれたり、ユーザーのブラウザ操作をするHTTPエクスプロイトを使用した画像のダウンロードを防ぐことができます。 イメージプロキシがアクセスできる公開URLでホストされている画像のみが唯一表示できるという事実を除いて、画像用プロキシの使用は分かりやすいものでなければいけません。 そこで、ファイアウォールの背後にある内部のURLでホストされている画像を表示しようとすると、壊れたイメージリンクが表示されます。

-- [ アンカータグ ] --

アンカータグ、またはHtmlサービスのページ内のリンクは書き換えされません。リンクは、リンク先からの参照元フィールドを非表示にした方法で提供されています。 リファラをログに記録するような別のサーバー上のページにリンクする場合、そのページではユーザーがどこから来たのかを知ることができません。
Webアプリでのアンカータグは"_blank"と "_self"だけを設定できます。JavaScriptでアンカーのターゲットフィールドを検査をすると期待したものにはならないかもしれませんが、正しく動作します。 スプレッドシートに埋め込まれたアンカータグは_blankターゲットだけを設定できます。

-- [ フォーム ] --

サーバーに送信するフォームは普通に書くことができます。しかし、上記の項で説明したように、フォーム送信ではリファラーストリッピングが起こりえます。 Htmlサービスまたは別のスクリプトをロードしたものかどうかに関わらず、Google.Apps.Scriptにフォーム送信するためには標準形式のボタンを使用できます。 しかし、これらのすべてのケースでは、ページ全体がフォームの応答ページにリダイレクトできる場合を除いて、レスポンスを利用した他のアクションはできません。 もし現在のスクリプトにフォームを送信して、ページをリダイレクトせずにサーバーからAjaxのような応答を受信できるようにしたければ、google.script.APIのセクションで説明したように特殊フォームの構文を使用してください。

-- [ XMLHttpRequests (XHR) ] --

他サーバーへのXMLHttpRequestsは可能です。それらのサーバーに適切なCORSヘッダーが設定されて、ドメイン外からの要求を受け入れるようになっていればです。 XHRの構文は、Internet ExplorerであってもXMLHttpRequestを使うように標準化されています。Google.Apps.ScriptサーバへのXHRリクエストは禁止されていますが、代わりにスクリプトで、AJAXのような要求を行うgoogle.script.APIを使用することで可能です。

-- [ CSS ] --

HTMLでは識別子が_で終わるものに制限があったように、同様の場合、ページ内のCSSの多くは動作変化を生じません。動的計算を行うCSSはストリッピングされ、URLの値のみ背景画像に対して許可されます。

-- [ JavaScript ] --

特定のJavaScriptの言語機能はHtmlサービスのWebアプリ内では使用できません。主な制限は'strict'JavaScriptを使用しなければならないということです。 キーワード指定、変数の削除(ただしobject.propertyの削除は可能)、関数内からのarguments.calleeの参照を削除することなどです。 ブラウザがネイティブでstrictモードをサポートしていない場合でも適用され、use.strict.pragmaがオプションであることに注意してください。

Htmlサービスのページでは、strictモードを超えたいくつかの強制的な制限があります。どのようなオブジェクトであっても、クライアント側のスクリプト内に2つのアンダースコアで終わる名前、プロパティ、関数を持つことができません。 これに従わないときはエラーが発生します。


var x
= { foo__: 'illegal!' }

値にはアンダースコアーの制限がありません。


var x
= { foo: 'illegal!' }

この命名に関する制限事項は、今のところサーバー側のGoogle.Apps.Scriptコードには存在しません。そこで、もしサーバからクライアントにオブジェクトを送信させているのなら、違法プロパティを持つオブジェクトをダウン送信しないことを確認する必要があります。 最善策として、クライアントとサーバーの両方で _ で終わる識別子を使用しないことです。そうすれば問題に巻き込まれることはありません。 文字列からオブジェクトへのマップとしてのJavaScriptオブジェクト(ここではキーが潜在的に任意の文字列化される)を利用している場合、それぞれの文字列の末尾に余分な文字を追加してしまうことがあるので、予期せずして違法なキーとなってしまいます。

コードは、配列、オブジェクトは、RegExp、無限、文字列、論理値、NaNや数などのJavaScriptのプリミティブオブジェクトを再割り当てすることはできません。 よって、以下のコードはエラーになります。


Object =
myNewObject;

これらのオブジェクトやそのプロトタイプに新規のプロパティを割り当てることは禁止されて、ブラウザおよびコードによってはエラーがスローされることがあります。 たとえばつぎのようなコードを使うべきではありません。


Object.
prototype.myNewFunc =
function
() { ... }

ブラウザの環境に対してJavaScriptはフルアクセスではありえません。ウィンドウのタイトルを設定できませんし、(ただし、HtmlOutputを介して初期設定をすることはできます。) クッキーを読んだり、履歴のナビゲートも許されていません。evalの使用も禁止されています。

-- [ JavaScript ライブラリー ] --

<script>タグを経由して、コード内には外部のJavaScriptライブラリを含めることができます。
ライブラリーはページがロードされる前にサーバー上でフェッチされ、コード同様にサニタイズされます。ライブラリがHtmlサービスのコードのいずれかのルールに違反している場合はWebアプリのロードエラーとなります。 多くのJavaScriptライブラリはこれらの規則の範囲内で動作しますが、すべてではありません。

Googleは重要なライブラリーについては、Caja sanitizationを用いて積極的にルール遵守をチェックしています。現時点ではjQueryjQuery.UIが含まれています。 これらは任意のサーバーでホストされているjQueryのコピーで使用することができますが、ここにリストされている正規のURLのいずれかで使用することをお勧めします。
これらのバージョンは、コンプライアンステストが実施されており、また他の場所でホストされている同一のライブラリよりもHTMLサービス内のページに高速にロードされるように最適化されています。

簡単に参照できるjQuery(unminifiedデバッグバージョン)の正式のURLは
http://code.jquery.com/jquery-1.7.2.min.js or http://code.jquery.com/jquery-1.7.2.js です。
jQuery UI’は http://code.jquery.com/ui/1.8.21/jquery-ui.min.js または http://code.jquery.com/ui/1.8.21/jquery-ui.js です。

-- [ 動的スクリプトの追加と外部CSS ] --

HtmlサービスのJavaScriptが実行時、外部CSSスタイルに対して新しいスクリプトやリンクを動的に追加することはできません。 すべてのJavaScriptコードは、セキュリティ上の制約に違反しないことを保障するための前処理段階でHtmlサービスに利用可能でなければなりません。

この制限は具体的に次の通りです。
まず、ドキュメントにscriptタグを記述することはできません。書いた場合は無視されます。これは、すべての外部ライブラリの参照が明示的なスクリプトタグで無ければならないということ、AJAXを介して動的にロードされていない必要があることを意味します。

2つ目に、ボタンのonclick属性から実行用のJavaScriptを直接追加することはできません。たとえば、下はクリックしても何もしないボタンの例です。


<script>
document.write('<input type=
"button" onclick="alert(\'never gets alerted\')">'
)
</script>

この理由はブラウザが次のように認識するからです。ボタンのonclickプロパティは潜在的に何かをする新規のJavaScriptとしてとして扱われるのですが、HTMLサービスのpre-processorはアクセス権を持たないのです。 解決策は既存の関数を要素に追加することです。


<script>
function
doSomething() { alert(
'but this is fine!'
); } document.write('<input type=
"button" id="button1"
>') document.getElementById(
'button1'
).onclick = doSomething;
</script>

これでdoSomethingは動作します。これは、ブラウザのイベントオブジェクトの1つであるパラメーター(この例では無視されている)を使って呼び出されています。 イベントを発生させている要素を特定するために、イベントオブジェクトを使用することができます。構文が少し不格好なので、 単純化したものを提供します。関数名を呼びだすだけでパラメーターも渡さない通常のonclickの構文です。


<script>
function doSomething() { alert(
'this one works too!'
); } document.write('
<input type="button" onclick="doSomething()">'
)
</script>

これは、現在のところまだパラメータとしてイベントオブジェクトを渡しますが、変更されることがあるので当てにしてはいけません。

次の制限は、JSONPをクロスドメインスクリプトのローディング手法に関連しています。動的にロードするスクリプト上の制約のために、この手法は利用できません。 ただし、UrlFetchApp経由でサーバー上のJSONPサービスを呼び出すことによって、この技術の使用を制限することができます。これは、プリプロセッサを経由で実行されるHTMLに直接書かれた結果を利用してページを提供する前に行うことが可能です。 また、結果の解析やそれを返す等の他のサービスをもつgoogle.scriptの関数をクライアントが呼び出すことによってダイナミックに実行されます。 一般的にSONPの技術は危険(ユーザーのブラウザで実行される悪意のあるコードを引き起こす可能性)なことができるのでお勧めできません。
これに代わるより近代的な(かつ安全)技術はHTMLサービスで完全にサポートされている方法 -- CORS(Cross-Origin Resource Sharing)ヘッダーを設定したサーバーにXHRリクエストをすることです。

最後にJavaScriptとは異なり、ページ内の要素に新しいCSSスタイルを動的に追加することができます。ただし、新しい外部スタイルシートをロードする<link>タグを追加することはできません。



Webアプリとしてユーザーインターフェースを設置する

HtmlのServceを使用して構築されたユーザーインターフェイスを持つスクリプトは、Webアプリケーションとして設置することができます。スクリプトを設置するためのプロセスは、ドキュメントのWebアプリですべて説明されています。 要約すると、設置したいスクリプトのバージョンを選び、スクリプトの実行者を選ぶ -- あなた(スクリプトの所有者)か、アクティブユーザーとしてか、そして、誰にアクセスを許可するのかを選ぶことです。

Webアプリとして設置できるスクリプトには、特別な名前の関数doGetが含まれています。これは、ユーザーがWebアプリケーションのURLにアクセスしたときに実行される関数です。 doGet関数は常にHtmlOutputオブジェクトを返します。



スプレッドシートからユーザーインターフェースを設置する

スタンドアロンのWebアプリとしてユーザー・インタフェースを設置する代わりとして、スプレッドシートでcontainer-boundスクリプトを作成して、ユーザーインターフェースをスプレッドシートに表示させることができます。 これを行うには、doGet関数を見つけて単純に呼び出しを置き換えるだけです。


return
htmlOutput;

そして、次のように続けてください。


var
spreadsheet =
SpreadsheetApp.
getActiveSpreadsheet(); spreadsheet.show(htmlOutput);

htmlOutputは、返されるHtmlOutputオブジェクトの変数名です。 さらに、スプレッドシートからユーザーインターフェイスを表示したときには、この関数はdoGet名である必要がありません。 代わりに、"displayMyUi"のような名前で呼び出した後、シート内でユーザーインターフェイスを表示したいときは直接その関数を呼び出すことができます。



2012-10-23

pageTOP