GWTサンプルの分析3

2018年8月14日

さて、次にソースコードについて解説していくことにするが、しかしこのコードの解説に大した意味は無い。これを発展させて何かしようという人はいないだろうからである。その理由も示す。

GWTサンプルソースコードに大した意味の無い理由

先にも書いたようにGWTの一つのモジュールはブラウザから見れば1ページである。
モジュールのコンパイルには時間がかかるため、一つのGWTによるウェブサイトで多数のモジュールを使うことは、(開発者が多い場合は別として)あまり現実的ではない。

おそらくは、一つのモジュール(1ページ)を使い、そのブラウザ内のDOMツリーを実行時に変更し、あたかも全く別のページに遷移したかのような効果を得ることが一般的であろう。

ところが、GWTのサンプルソースコードでは、Sample.htmlにhtmlで記述された基本的なDOM構造がそのまま使われており、「すべてをクリアして新たなDOM構造を作る」と言ったことはされてない。私見だが、GWTでこういったことを行うのは稀であろうと考える。

以上がGWTサンプルソースコードにあまり意味が無い理由である。そうは言っても、要所要所でどのようなことが起こっているかは以下に見ていく。

Sample.html

この内容のコメントや不要な部分をとりさって見やすくすると以下になる。

<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">                                                    -->
    <link type="text/css" rel="stylesheet" href="Sample.css">
    <title>Web Application Starter Project</title>
    <script type="text/javascript" language="javascript" src="sample/sample.nocache.js"></script>
  </head>
  <body>
    <h1>Web Application Starter Project</h1>
    <table align="center">
      <tr>
        <td colspan="2" style="font-weight:bold;">Please enter your name:</td>        
      </tr>
      <tr>
        <td id="nameFieldContainer"></td>
        <td id="sendButtonContainer"></td>
      </tr>
      <tr>
        <td colspan="2" style="color:red;" id="errorLabelContainer"></td>
      </tr>
    </table>
  </body>
</html>

sample/sample.nocache.js

head内にある以下の部分でGWTのクライアントコード、つまりJavaをJavaScriptに変換したコードをロードする。

    <script type="text/javascript" language="javascript" src="sample/sample.nocache.js"></script>

これは、warフォルダ下に配置されるファイルであり、開発環境では以下のものが自動生成されている。

この中身をみてみれば、これがSuperDevModeを呼び出すためのものとわかる。

/**
 * This startup script is used when we run superdevmode from an app server.
 */
(function($wnd, $doc){
  // document.head does not exist in IE8
  var $head = $doc.head || $doc.getElementsByTagName('head')[0];
  // Compute some codeserver urls so as the user does not need bookmarklets
  var hostName = $wnd.location.hostname;
  var serverUrl = 'http://' + hostName + ':9876';
  var module = 'sample';
  var nocacheUrl = serverUrl + '/recompile-requester/' + module;

しかし、(ここではやらないが)warデプロイ用にGWTコードをコンパイルすると、この内容は以下に変わる(一部)。

function sample(){var O='bootstrap',P='begin',Q='gwt.codesvr.sample=',R='gwt.codesvr=',S='sample',T='startup',U='DUMMY',V=0,W=1,X='iframe',Y='javascript:""',Z='position:absolute; width:0; height:0; border:none; left: -1000px;',$=' top: -1000px;',_='CSS1Compat',ab='<!doctype html>',bb='',cb='<html><head><\/head><body><\/body>

つまり、htmlの内容は書き換えずに、開発環境ではSuperDevModeを呼び出すためのコード、本番のデプロイ環境では本番のコードになりかわるというわけだ。

要素につけられたid

他に注目すべき箇所としては、各要素につけられたidである。この部分を再掲する。

      <tr>
        <td id="nameFieldContainer"></td>
        <td id="sendButtonContainer"></td>
      </tr>
      <tr>
        <td colspan="2" style="color:red;" id="errorLabelContainer"></td>
      </tr>

後述するGWTコードの中で、既存のhtml内の要素、つまりDOMツリー上の既存要素を操作しているのだが、要素を特定するための目印としてidをつけている。
もちろん、idなどなくとも、html全体を探索してその構造を把握し、目的のものを見つけることもできるが、idをつけておいた方が楽だろう。

Sample.java

さて、具体的にクライアント側のGWTのJavaコードが何をしているかなのだが、先にも書いたように、そのエントリポイントとなるクラスは、sample.client.Sampleというクラスであり、エントリポイントはonModuleLoad()である。

ここで行うことは、先のidのついたhtml要素を取得し、Button等のクラスをそれに付け加え、それがクリックされたら何らかのアクションを起こすということである。
サンプルにしては長いので、要所を抜き出してみる。

  public void onModuleLoad() {
    final Button sendButton = new Button("Send");
    ....
    RootPanel.get("sendButtonContainer").add(sendButton);
    ....
    MyHandler handler = new MyHandler();
    sendButton.addClickHandler(handler);
    ....
  }

先のhtml要素はtdであるから、

        <td id="sendButtonContainer"></td>

Buttonを作成して、そのtd要素の中に入れている。そして、ボタンが押されたらアクションを起こす。
このアクションというのは、別途作成したTextBoxにユーザが入力した文字列をサーバ側に送ることである。
送っているのは以下の部分になる。

        greetingService.greetServer(textToServer, new AsyncCallback<String>() {
          ....
        });

ここでButtonやらTextBoxを作成しているが、これは単にhtmlの要素であるButtonやTextBoxのラッパに過ぎず、中身はhtml要素である。
これらを動的に作成して、DOMツリーに挿入しているということだ。

GreetingServiceImpl

クライアントからサーバに文字列が送られる仕組みについては後述するが、ともあれ送られたと仮定する。
送られた文字列を処理する部分はsample.server.GreetingServiceImplになる。
ここではクライアントから送られた文字列を元に別の文字列を作成して、クライアントに返している。

  public String greetServer(String input) throws IllegalArgumentException {
    ....
    return "Hello, " + input + "!<br><br>I am running " + serverInfo + ".<br><br>It looks like you are using:<br>"
        + userAgent;
  }