UiAppでGUIを作る

UiAppは、GoogleがJava用に提供しているGWTを、Google Apps Scriptから利用する為のクラスです。HTML Servicesと対をなすGUI構築の為のクラスですが、HTML Servicesよりも、やや自由度が高目で、リッチなUIを実現することが可能です。但し、HTML ServicesがHTML + CSS + JavaScriptで構成されていて、資料も結構あるのに対して、UiAppは広く使われてはおらず、資料も少ないので、正直な所あまりおすすめできる状況にありません。

HTML Services同様に、doGet()で起動して、表示するので、例えばダイアログやサイドバー、ウェブアプリケーションを作るなど、用途はHTML Servicesと変わりません。なお、この項目は既に作成している「色々なメッセージボックスを使いたい」や「ファイルやフォルダの選択画面を作りたい」、「検索窓と結果の返り値」にもつながる項目です。

概要

UiAppは、HTML Servicesとは異なり、Google Apps Script上でGUIの部品となるモノ、ハンドラー、返り値を処理するものなどを全て実装するもので、少々難解な仕組みになっています。しかし、一方でGWTの各種コンポーネントをGUI部品として使える為、HTML Servicesで制限が多い中がんばって作るよりも、リッチなUIを実現できるので、よりアプリケーションよりのサービスを作りたいとなると、これしか選択肢がありません。但し、見た目はそれほど変えられないので、GUIがダサいと言ってしまうとそれまでになってしまいます。

超基本のコード

//単なるボタンを表示するだけのスクリプト
function doGet(){
    var app = UiApp.createApplication();
    var button = app.createButton("送信");
    app.add(button);
    return app;
}

実行結果

注意点

  • かならず、用意した部品はUiAppにaddして、最後は表示するために、returnする事。
  • Spreadsheetなどの場合のダイアログで表示する時は、showメソッドでないと表示されない(returnじゃダメって事)。
  • ウェブアプリケーションとして表示する場合には、「ウェブアプリケーションとして導入」をしなければならない。スクリプトエディタできちんと公開しておくこと。
  • ユーザは最初の1回は、承認や実行に当ってのメッセージの非表示などの作業を行わなければなりません。
  • Googleアカウントを持たない匿名ユーザの場合、ウェブアプリケーションとして導入時に、アクセスできるユーザを匿名を含むにしないと、表示されません。

GUIレイアウト

そのまま、部品を次々にcreateして、作成したappに.addしていってもいいのですが、あまりにも無骨過ぎます。なので、通常は、ある程度の部品群をまとめる部品を使ってレイアウトを組むことになります。以前はこの当たりは、GUI Builderというツールがあって、マウスでポチポチ作れたのですが、現在はなくなっています。その為、頭の中でイマジネーションを働かせて、このまとめる部品にボタンやテキストボックスなどを配置することになります。ちなみに、このまとめる部品のことを「パネル」と呼び、パネルを使わない場合、addされた部品は、次々に縦にそのまんま並ぶだけになります。

このパネルも何種類か存在しており、自分が創りあげたいインターフェースに応じて、そのパネルも選ぶことになります。

パネルを使ったコードの1例

function doGet(e){
    var app = UiApp.createApplication().setTitle("テストだよ");
    var lb1 = app.createLabel("パネルを使ったサンプルのページ");
    app.add(lb1);
    
    var panel = app.createHorizontalPanel();  
    app.add(panel);
    
    var lb2 = app.createLabel("label1");
    lb2.setId("label1");
    lb2.setText("簡単なテキスト操作を行います。");
    panel.add(lb2);
    
    var tbox = app.createTextBox();
    tbox.setName("tbox1");
    panel.add(tbox);
    
    var btn = app.createButton("click");
    panel.add(btn);
    
    return app;
}

実行結果

ヒント

  • コードの上から順番にaddした順に配置されていくので、配置順序とコードの記述順序に注意が必要です。
  • 今回は、HorizontalPanelを使って、コントロールは横に配置してみました。1個目のラベルだけは、パネル内ではなく、直接appにaddしてある。
  • 2個目のラベルだけは、IDをセットしてある。これは次の項目で使うイベントハンドラーで使うもので、HTMLでいうDOMのgetElementByIdで項目を特定したりするのに使うものと同じである。

イベントハンドラー

UiAppがややこしく難解な理由の一つが、このイベントハンドラー。HTML ServicesでもJavaScriptによるボタンへのイベントハンドラー設置がありましたが、基本的にはあれと似たようなものです。しかし、問題なのはこのイベントハンドラーが2種類あり、尚且つ資料が少ないために、どのようにして値のやり取りをしたら良いのかが分かりにくい点にあります。

2種類あるというのは、クライアント上で動かすクライアントハンドラー、そして、Googleサーバ上で動かすサーバークライアントの2種類があるという点です。値の取り方は、配置したコントロールにIDやNAMEを設定しておき、それを参照するという形で、JavaScriptのそれと変わりません。

イベントハンドラーを使ったコード

//入力された名前を取得して表示するサンプル
//ベースサンプルは、https://code.google.com/p/google-apps-script-issues/issues/detail?id=2695
function doGet() {
//タイトルの設定とお決まりのコード
  var app = UiApp.createApplication().setTitle('テキストボックスから値を受け取って表示するテスト');
  var panel = app.createVerticalPanel();
  
  return app.add(panel.add(
    app.createLabel('お名前:')).add(
    app.createTextBox().setName('doraemon')).add(
    app.createButton('送信してみる', app.createServerHandler('doPost').addCallbackElement(panel))));
}
function doPost(e) {
//お決まりのコードと受信した値の表示、およびパネルの削除
  var app = UiApp.getActiveApplication();
  return app.remove(0).add(app.createLabel('テストありがとう!!'+e.parameter.doraemon));
}

実行結果

解説とヒント

  • 今回は、buttonに対してサーバーハンドラーを設置しています。押した時に実行する関数名は、doPostで、反映先はpanelです。
  • doPostの引数である e は、様々なコントロールの値を返してくれます。今回は、テキストボックスにdoraemonという名前を付けて、e.parameter.doraemonで取得しています。
  • 同時に、現在表示しているpanelを消去する為に.removeで0番目(つまり、1個目のpanelそのもの)を消去し、新たにラベルを生成して値を表示させています。
  • サーバーハンドラーは、何かアクションをやらせたいコントロールの2番目の引数にcreateServerHandlerで設定します。予め、それそのものを変数に入れておき、引数に変数を当てても同じです。

サーバーハンドラーとクライアントハンドラー

そもそも、2種類あるハンドラーですが、サーバーハンドラーと呼ばれるものが、Google Apps Scriptでは当たり前のものです。なぜなら、このGoogle Apps 自体がサーバサイドで動いているものなのですから。対して、クライアントハンドラーは、あえてそういう仕組なのに、ブラウザ上で処理を行わせるというもので、サーバーにいちいち投げずに、即座に答えをその場で返す(簡単な計算機などのアプリケーションみたいなもの)などで使われるものです。スプレッドシートへの値の読み書きなんかは、クライアントハンドラーでは実行できません(その為、クライアントハンドラーは実行できる要素に制限があります)。

ただし、クライアントハンドラーのほうが速度が遥かに早いので、必要な部分だけサーバーハンドラーを使用し、できうる限り、クライアントハンドラーを使うというのが早く動くスクリプトを作るコツのようです。

クライアントハンドラーを使った事例コード

function doGet() {
  var app = UiApp.createApplication()
  var chkPaida = app.createCheckBox("みすず飴").setValue(true);
  var chkPaidb = app.createCheckBox("おまんじゅう").setVisible(false);
  var checkpanel = app.createHorizontalPanel().add(chkPaida).add(chkPaidb)
  var dateBox2 = app.createDateBox().setName("paidThrough").setId("paidThru").setName("paidThru");
  var Chandlera = app.createClientHandler().forTargets(dateBox2).setEnabled(false).forTargets(chkPaidb).setVisible(true).setValue(false)
  .forEventSource().setVisible(false)  
  chkPaida.addClickHandler(Chandlera)
  var Chandlerb = app.createClientHandler().forTargets(dateBox2).setEnabled(true).forTargets(chkPaida).setVisible(true).setValue(true)
  .forEventSource().setVisible(false)  
  chkPaidb.addClickHandler(Chandlerb)
  app.add(checkpanel).add(dateBox2)
  return app
}

実行結果

ベースコードは、StackOver Flowから拝借しました。

解説とヒント

このコードは一体何をやっているコードなのかは実行してみるとわかる。箇条書きで列挙すると・・・

  • チェックボックスが2個用意されており、一つをクリックするともう一つのチェックボックスが表示される
  • チェックボックスの値がONの場合、カレンダーをセレクトできるようになる。
  • これらのコントロールの挙動をクライアントハンドラーでやらせている(こんなのサーバハンドラーじゃなくていいよねって事)
  • これで、カレンダーの値をシートに書き込むだとか、何かそれを元に検索させてというのであれば、サーバハンドラーが必要になるというわけです。

関連リンク