とりあえず雑記帳(跡地)
GWT
最終更新:
fujiyan
-
view
WebコミックLibraryhttp://web-comi.appspot.com/ GAE/JとSlim3で作成してみた、各出版社から配信されているWebコミックをまとめて閲覧できるサイトです。只今、実験運用中… |
参考サイト
- GWTの本家
Google Web Toolkit - Google Code
日本語ページは更新が遅いので、英語ページが良い
日本語ページは更新が遅いので、英語ページが良い
- GWTについては、とりあえずここを見てみよう
構成要素
Module
- Organizing Projects - Google Web Toolkit - Google Code
- GWTアプリケーションの構成単位。GWTアプリケーション=1つ以上のModuleの集まり。
1アプリケーションを1つのModuleで表すのも可能だけど、それだとModule定義が肥大化するので、サブシステム単位でModuleを分割するんですよね、きっと
→Moduleは、UIフォームに対応していると考えたほうが良いか?
→Moduleは基本的に[slim3.rootPackage]直下に置け、とのこと。そうなると、あまり数を増やすわけにもいけないので、やっぱサブシステム単位で?
→Moduleは基本的に[slim3.rootPackage]直下に置け、とのこと。そうなると、あまり数を増やすわけにもいけないので、やっぱサブシステム単位で?
- Moduleは[モジュール名].gwt.xmlファイルで定義される。定義を構成する要素として下記のものがある。
- 継承元のModule
- クラス継承みたいな仕組みというよりは、importに近いイメージ。必要に応じて必要なModuleをinheritsで指定する(複数可)。
- 通常用いる、標準Moduleの一覧:Organizing Projects - Google Web Toolkit - Google Code
- Entry Pointクラス
- 後述
- HTMLから参照されるModuleの場合、必ず1つ以上のEntry Pointクラスの記述が必要にある。逆に、HTMLから参照されないModule(継承されることを前提としたModule等)ならば必須ではない。
- sourceパス
- JavaScriptに変換されるJavaソースファイルを置くところ
- publicパス
- 静的リソースファイル(CSS/画像ファイル等)を置くところ。例えばgwt.xmlファイルの<stylesheet>要素のsrc属性で相対パスを指定した場合、publicパスが起点となる。
- 継承元のModule
theme
- ModuleのXMLファイルにて、UIのthemeを設定できる。設定しておかないと、DialogBoxとか透明になってしまう。
- 設定するには、themeのModuleを継承する。下記はChromeの場合
<inherits name='com.google.gwt.user.theme.chrome.Chrome'/>
- 他にStandardとDarkがある。
themeのスタイルの一部を変更する。
- themeのスタイルで気に入らないところがあれば上書きが可能
- gwt.xmlファイルの<stylesheet>属性で、変更後のスタイルを記述したcssファイルを指定する。
- 相対パスで指定した場合は、<public>要素のpath属性で指定した場所が起点となる。
<stylesheet src="foo.css"/>
- cssファイルに変更後のスタイルを記述する。下記の場合、全般的にフォントサイズを10pt、Buttonだけフォントサイズを20ptにする。
* { font-size: 10pt; } .gwt-Button { font-size: 20pt; }
Entry Pointクラス
- クライアント(ブラウザ)からのアクセスを受け付ける。
- onModuleLoad()メソッドを唯一持ち、Entry Pointクラスを参照するHTMLの表示時にメソッドが呼び出される。メソッド内でコントロールの初期化やイベントハンドラの設定等を行う。
HTML
- <script>タグで、参照するModuleを呼び出す。
<script type="text/javascript" language="javascript" src="jp.fujiyan.gae.example.slim3.gwt.Main/jp.fujiyan.gae.example.slim3.gwt.Main.nocache.js"></script>
- Moduleが呼び出されると、Moduleで定義されたEntry PointクラスのonModuleLoad()メソッドが呼び出される。
- HTMLで、GWTウィジェットを配置したい場所に<div>タグを定義し、Entry PointクラスのonModuleLoad()内で、定義したレイアウトを<div>タグに設定する。
HTML
<div id="gwtDiv"></div>
Entry Point
private VerticalPanel mainPanel = new VerticalPanel(); public void onModuleLoad() { (mainPanelにレイアウト設定) RootPanel.get("gwtDiv").add(mainPanel); }
UiBinder
- Entry Pointクラスだけでレイアウトを作成する場合、onModuleLoad()メソッドの中のJavaコードでレイアウトを実装する必要がある。
- そこで、GWT2.0から導入されたUiBinderを用いると、XML(or HTML)でレイアウトの定義が可能になる。
- レイアウト記述するXMLファイルと、イベントハンドラなどを実装するクラスの2つから成り立つ。
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent"> <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui"> <ui:style> /* Add CSS here. See the GWT docs on UI Binder for more details */ .important { font-weight: bold; } </ui:style> <g:HTMLPanel> <!-- Add GWT widgets here. Enter < and hit Ctrl-Space to auto-complete widget names. The ui:field attribute binds an element to a field in the owner class. See the GWT docs on UI Binder for more details. --> Hello, <g:Button styleName="{style.important}" ui:field="button" /> </g:HTMLPanel> </ui:UiBinder>
public class FooView extends Composite { private static FooViewUiBinder uiBinder = GWT.create(FooViewUiBinder.class); interface FooViewUiBinder extends UiBinder<Widget, FooView> { } @UiField Button button; public FooView(String firstName) { initWidget(uiBinder.createAndBindUi(this)); // Can access @UiField after calling createAndBindUi button.setText(firstName); } @UiHandler("button") void onClick(ClickEvent e) { Window.alert("Hello!"); } }
- <g:Button>がwidgetのタグ。ui:fieldで指定した名前と、Javaクラスで@UiFieldアノテーションを適用したフィールド名を同じにすることで関連づける(上記の場合、"button")。
- イベントハンドラは、メソッドに@UiHanderアノテーションを適用する。
- アノテーションで、イベントソースとなるwidgetのui:field名を指定する(上記の場合、"button")
- ハンドラの引数のクラスで、ハンドリングするイベントを指定できる(上記の場合、ClickEventが引数なので、Clickイベントがハンドリングできる)
- よって、イベントハンドラのメソッド名は好きにしてよい。
Module作成
- メニューの[File]-[New]-[Module](またはプロジェクトのコンテキストメニュー→[New]-[Other...]-[Google Web Toolkit]-[Module])を選択し、[Package]にModuleのパッケージ名(先のslim3.rootPackageで指定したパッケージ以下にすること)、[Module name]に作成するModule名(例=Main)を記述し、[Finish]をクリックする。
- 上記で作成された、src/[Moduleのパッケージ名/[Module名].gwt.xml"を下記のように修正する。
<module> <inherits name="com.google.gwt.user.User" /> <inherits name='org.slim3.gwt.emul.S3Emulation' /> <inherits name='com.google.gwt.user.theme.standard.Standard' /> <source path="client" /> <source path="shared" /> </module>
Entry Pointクラス作成
- メニューの[File]-[New]-[Entry Point Class](またはプロジェクトのコンテキストメニュー→[New]-[Other...]-[Google Web Toolkit]-[Entry Point Class])を選択し、[Package]に[Moduleのパッケージ名].client、[Name]に作成するEntry Pointクラス名([Module名]EntryPoint)を記述し、[Finish]をクリックする。
HTMLページ作成
- メニューの[File]-[New]-[HTML Page](またはプロジェクトのコンテキストメニュー→[New]-[Other...]-[Google Web Toolkit]-[HTML Page])を選択し、[File name]に作成するHTMLページのファイル名を記述し、[Finish]をクリックする。
UiBinder作成
- メニューの[File]-[New]-[UiBinder](またはプロジェクトのコンテキストメニュー→[New]-[Other...]-[Google Web Toolkit]-[UiBinder])を選択し、[Package]に[Moduleのパッケージ名].client、[Name]に作成するUiBinder名([Module名]と同じ?)を記述し、[Finish]をクリックする。
- Entry Pointクラスを下記のように修正する。
public void onModuleLoad() { RootPanel.get().add(new Main("fujiyan")); }
実行
- プロジェクトのコンテキストメニュー→[Google]→[GWT Compile]で[GWT Compile]ダイアログを開き、[Compile]をクリック(または、ツールバーのアイコンをクリック)
- Web Application起動
- ブラウザからアクセス
- チュートリアル通りだと、下記のようなアプリになる。
GWT module [Module名] may need to be (re)compiled
- ブラウザからのアクセス時に、上記のようなメッセージが表示された場合は、プロジェクトのコンテキストメニュー→[Google]→[GWT Compile]で[GWT Compile]ダイアログを開き、[Compile]をクリック(または、ツールバーのアイコンをクリック)
戻るボタン管理(かなり試行錯誤中…)
- GWTの画面遷移はページの切替ではなく、JavaScriptによる画面の書き換えのため、戻るボタンを押しても前の画面に戻ることは無い。
- HistoryクラスとHyperlinkクラスを利用することで、画面遷移時にイベントを発生させ、ハンドリングすることができる。これは戻るボタンが押された際にもイベントを発生させることができる。
- イベントが発生するだけなので、実際にどの画面を表示するべきかは自前で実装する必要がある。Hyperlinkクラスを使えば、勝手に戻るボタンの動きが実現されるわけでは無いので注意。
- 各画面に対してtokenを定義する。tokenは画面を一意に識別するためのものであり、画面遷移イベント発生時に、次に表示すべき画面に割り当てられたtokenがパラメータとして渡される。それを元に、対応する画面の表示処理を記述する。
画面遷移のイベント取得
- EntryPointクラスのonModuleLoad()メソッド内で、Historyクラスに対してValueChangeHandlerを設定する。
public void onModuleLoad() { // 初期画面のtokenを設定する。 String initToken = History.getToken(); if (initToken.length() == 0) { History.newItem("first"); } RootPanel.get().add(new FirstView());//初期画面表示(本題には関係無し) // 戻るボタンクリック時のイベントハンドラ設定 History.addValueChangeHandler(new ValueChangeHandler<String>(){ // このメソッドが呼ばれます。 public void onValueChange(ValueChangeEvent<String> event) { //例として、戻るボタンクリック後に遷移する画面のtokenをalertで表示します。 Window.alert(event.getValue()); } }); }
- 次画面に遷移するHyperlinkのtargetHistoryToken属性に、次画面のtokenを記述しておく。
<g:Hyperlink ui:field="nextViewLink" targetHistoryToken="next">次画面</g:Hyperlink>
- 上記のコードによって、次画面遷移時には"next"がalertで表示され、戻るボタンをクリックすると"first"がalertで表示される。
- ということで、画面遷移の処理はリンクのクリックイベントハンドラで記述するのではなく、History.addValueChangeHandler()に与えたValueChangeHandlerで記述するのがイイみたい?
- 以下、現時点での試行錯誤の結果。参考:Coding Basics - History
- EntryPointのonModuleLoad()では、History.fireCurrentHistoryState();でViewChangeEventを発生させ、初期画面を表示させる。
public class FooEntryPoint implements EntryPoint { public void onModuleLoad() { History.addValueChangeHandler(new FooViewManager()); History.fireCurrentHistoryState(); } }
- FooViewManagerで、tokenを解析して表示させる画面を決定し、パラメータを渡す。
public class FooViewManager implements ValueChangeHandler<String> { /** * tokenをパラメータのMapに変換する。 * * @param token * @return パラメータのMap */ public static Map<String, String> toParameterMap(String token) { Map<String, String> map = new HashMap<String, String>(); String[] params = token.split("&"); for (String param : params) { int pos = param.indexOf('='); String name = URL.decodeComponent(param.substring(0, pos)); String value = URL.decodeComponent(param.substring(pos + 1)); map.put(name, value); } return map; } /** * パラメータのMapをtokenに変換する。 * * @param parameterMap パラメータのMap * @return token */ public static String toToken(Map<String, String> parameterMap) { StringBuilder sb = new StringBuilder(); for (String name : parameterMap.keySet()) { if (sb.length() > 0) { sb.append('&'); } String value = parameterMap.get(name); sb.append(URL.encodeComponent(name)).append('=').append(URL.encodeComponent(value)); } return new String(sb); } public void onValueChange(ValueChangeEvent<String> event) { RootPanel root = RootPanel.get(); root.clear(); Composite nextView; String token = event.getValue(); // tokenをパラメータのMapに変換 Map<String, String> parameterMap = toParameterMap(token); // viewパラメータで表示する画面を決定させる String view = parameterMap.get("view"); if ((view == null) || view.equals("firstView")) { // viewパラメータがnullの場合は初期表示 nextView = new FirstView(); } else if (view.equals("secondView")) { // view以外のパラメータの使い方は各画面に依存するので、ここではMapをそのまま渡すだけ nextView = new SecondView(parameterMap); } else { Window.alert("undefined view=[" + view + "]"); return; } root.add(nextView); } }
- tokenとパラメータのMapとの変換で用いているURL.encodeComponent()/decodeComponent()は、URL-encodeを行うためのメソッド。
- URL.encode()/decode()では&や=を変換してくれないので、URL.encodeComponent()/decodeComponent()を利用
- URLクラスを用いるためには、gwt.xmlファイルで、com.google.gwt.http.HTTP moduleをinheritしておく必要がある。
<module> <!-- 略 --> <inherits name="com.google.gwt.http.HTTP"/> <!-- 略 --> </module>
- パラメータを受け取る画面のコンストラクタで、実際に必要なパラメータを取得して画面表示に反映させる。
public class SecondView extends Composite { public SecondView (Map<String, String> parameterMap) { initWidget(uiBinder.createAndBindUi(this)); String name = parameterMap.get("name"); String kana = parameterMap.get("kana"); if (name != null) { // パラメータがあれば、その内容で画面表示 nameTextBox.setText(name); kanaTextBox.setText(kana); } } }
- パラメータが不要な画面へのリンクはviewパラメータのみにする。もちろん、リンクのClickイベントで次画面を生成する処理とかは不要。
<g:Hyperlink targetHistoryToken="view=firstView">初期画面</g:Hyperlink>
- 検索画面で、検索結果を履歴に含めたい場合は、検索処理の後に検索パラメータをtokenに持たせたURLを履歴に加えれば良い。
HashMap<String, String> parameterMap = new HashMap<String, String>(); parameterMap.put("view", "secondView"); parameterMap.put("name", name); parameterMap.put("kana", kana); History.newItem(FooViewManager.toToken(parameterMap), false);
UiBinderでDialogBox(正解不明、ごまかし版)
- DialogBoxもUiBinderで作れたら楽だよね
- でも、呼び出し元のXMLに<g:DialogBox>を埋め込むのはイヤ。他画面からの再利用ができない。
手順
- とりあえず、通常の手順(メニューの[File]-[New]-[UiBinder])でUiBinderを作成する。
- ui.xmlファイルを修正する。
- <ui:Binder>の直下のWidgetは、Panel系のWidget1つ(下記の場合は<g:VerticalPanel>)のみ。
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent"> <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui"> <ui:style> </ui:style> <g:VerticalPanel> <g:TextBox ui:field="valueTextBox" visibleLength="40"/> <g:Button ui:field="closeButton">閉じる</g:Button> </g:VerticalPanel> </ui:UiBinder>
- .javaファイルを修正する。
- 親クラスをCompositeからDialogBoxに変更する。
- コンストラクタ内のinitWidget(uiBinder.createAndBindUi(this))を、add(uiBinder.createAndBindUi(this))に変更する。
- setText()でダイアログのキャプション(タイトルバーのテキスト)を設定する。
- 閉じるのはhide()メソッド。
public class TestDialog extends DialogBox { private static TestDialogUiBinder uiBinder = GWT.create(TestDialogUiBinder.class); interface TestDialogUiBinder extends UiBinder<Widget, TestDialog> { } public TestDialog() { add(uiBinder.createAndBindUi(this)); setText("テストダイアログ"); } @UiField TextBox valueTextBox; @UiField Button closeButton; @UiHandler("closeButton") public void onClick_closeButton(ClickEvent e) { hide(); } }
- 呼び出し
- インスタンス生成後、位置を設定しないと表示されない。下記の例では、center()で中央に設定する。
@UiHandler("dialogButton") public void onClick(ClickEvent e) { TestDialog dialogBox = new TestDialog(); dialogBox.center(); dialogBox.show(); }
欠点
- タイトルのキャプションはXMLで設定できない。
PushButtonとVerticalPanel
- PushButtonをVerticalPanelの直下に配置すると、幅がVerticalPanelのサイズに合わせられる。
- つまり、VerticalPanel直下で最も大きいwidgetの幅に合わせられる。
- 回避するためには、PushButtonをHorizontalPanelで囲んでおく。
<g:VerticalPanel> <g:HorizontalPanel> <g:PushButton ui:field="testbutton">ボタン</g:PushButton> </g:HorizontalPanel> <g:TextBox ui:field="nameTextBox" visibleLength="40"/> <g:TextBox ui:field="kanaTextBox" visibleLength="80"/> </g:VerticalPanel>
Moduleのjarファイル化(Module jarファイル)
- 再利用可能なModuleをjarファイル化して、GWTにおけるライブラリとして用いる。
- 簡単に言えば、通常のjarファイル内に、javaソースファイルとgwt.xmlファイルを含ませればいい。
作成方法
- 通常のjavaプロジェクトを作成する。
- プロジェクトのクラスパスにApp Engine SDKとGWT SDKを追加する
- プロジェクトのプロパティダイアログを開き(プロジェクトのコンテキストメニュー→[Properties])、[Java Build Path]→[Add Library...]で、下記2つを追加
- Google App Engine
- Google Web Toolkit
- プロジェクトのプロパティダイアログを開き(プロジェクトのコンテキストメニュー→[Properties])、[Java Build Path]→[Add Library...]で、下記2つを追加
- srcフォルダ内にルートパッケージ(任意のパッケージでOK。例:jp.fujiyan.gwtroot)を作成する。
- クライアント側で利用するクラスはルートパッケージ以下に作成する(サーバー側のみで利用でするクラスであれば、ルートパッケージ以下で無くても良い)。
- ルートパッケージ直下にgwt.xmlファイルを作成する。
- ファイル名が、利用する側で指定するModule名になる(Foo.gwt.xmlならFooをModule名として指定)。
- gwt.xmlファイル内で、クライアント側で利用するクラスを格納するフォルダ(ルートパッケージからの相対パス)を<source>要素で指定する。
- jp.fujiyan.gwtroot.clientにクライアント側で利用するクラスを格納する場合
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0.3//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.0.3/distro-source/core/src/gwt-module.dtd"> <module> <source path="client" /> </module>
Module jarのプロジェクトではGWTコンパイルを実施しないので、<inherits>でUser等のModuleを参照する必要は無い(よね? 多分…)
- クラスを作成する。
- 上述の通り、クライアント側で利用するクラスは、<source>要素で指定したフォルダ以下に作成する。
- サーバー側のみの場合は自由に…
- jarファイルにパッケージングする。
- classファイルと、そのソースのjavaファイルは、同じフォルダに格納しておく。
- もちろん、gwt.xmlファイルもルートパッケージのフォルダに格納しておく。
- ソースが必要なのはクライアント側で利用するクラスの分だけなので、サーバー側のみで利用するクラスについては、javaファイルを同梱しなくてもよい。
利用方法
- Module jarファイルをプロジェクトのクラスパスに追加する。
- 利用するModuleのgwt.xmlファイルで、Module jarファイルのModuleを継承するように指定する。
- Module jarファイル内で、gwt.xmlファイルがjp/fujiyan/gwtroot/Foo.gwt.xmlにある場合、下記の様に指定する。
<inherits name="jp.fujiyan.gwtroot.Foo"/>
JavaScript Native Interface(JSNI)
参考
Coding Basics - JavaScript Native Interface (JSNI)
Java開発者のためのAjax: Google Web Toolkitを探る - developerWorks Japan
Coding Basics - JavaScript Native Interface (JSNI)
Java開発者のためのAjax: Google Web Toolkitを探る - developerWorks Japan
- Javaソース内でJavaScriptのコードを記述する。
- nativeメソッドとして宣言
- JavaScriptを記述するメソッド本体は/*- -*/で囲む
public static native void alert(String msg) /*-{ $wnd.alert(msg); }-*/;
JavaScriptObject
- Java側とJavaScript側とでオブジェクトを交換する際の、JavaScriptのObjectに対するJava側のクラス
- JavaScriptをextendsしたクラスを作成し、JavaScriptのObjectのプロパティにアクセスするためのgetter/setterをJSNIで記述する。
- Java側では、一旦JavaScriptObjectで受け取り、extendsしたクラスにキャストする。
class FooObject extends JavaScriptObject { protected FooObject() {} public final native String getText() /*-{ return this.text; }-*/; }
package jp.fujiyan.jsni.client; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.user.client.Window; public class JsniTest { private native void foo() /*-{ var obj = new Object(); obj.text = "test"; this.@jp.fujiyan.jsni.client.JsniTest::bar(Lcom/google/gwt/core/client/JavaScriptObject;)(obj); }-*/; public void bar(JavaScriptObject data) { Window.alert(((FooObject) data).getText()); } }
JavaScriptから呼び出されるコールバックメソッド内から他のメンバにアクセスできない
public class Test implements EntryPoint, FooHandler { private Foo foo = new Foo(); public native void barJava(FooHandler h) /*-{ barJs(h.@jp.fujiyan.test.client.foo.FooHandler::callback()); // 下記コードならcallback()内からfooにアクセスできる // h.@jp.fujiyan.test.client.foo.FooHandler::callback()(); }-*/; public void callback() { Window.alert(String.valueOf(foo)); } /** * This is the entry point method. */ public void onModuleLoad() { barJava(this); } }
FooHandler.java
package jp.fujiyan.test.client.foo; public interface FooHandler { void callback(); }
bar.js
function barJs(callback) { callback(); }
Test.html
~略 <head> ~略 <script type="text/javascript" language="javascript" src="bar.js"></script> <script type="text/javascript" language="javascript" src="test/test.nocache.js"></script> ~略 </head> ~略
- 上記のコードの場合、まずTest#onModuleLoad()が呼び出され、TestクラスのインスタンスがbarJava()に渡される。
- barJava()はJSNIで記述されており、渡されたFooHandlerのcallback()インスタンス(Functionのインスタンス)がbarJs()(外部ファイルbar.jsで定義されているので、Testクラスのメンバ?ではないfunction)に渡される。
- barJs()では、渡されたFunctionのインスタンス(=FooHandler#callback())を呼び出す。
- 呼び出されたcallback()内で、外部クラスのインスタンスフィールドであるfooにアクセスしようとするが、fooが未定義とみなされていまう。
- これは、fooのアクセスについて、GWTコンパイラがthis.fooと変換するが(実際はfooというシンボルはGWTコンパイラが適当に変換するけど…)、このときのthisが外部クラスを指しておらず、fooが定義されていないとしてエラーとなる。
- たぶん、このときのthisは、Functionのインスタンスを指しているから?
- ということで、Java側のメソッドをコールバック関数に直接指定してはダメ。
- 対処方法として、JavaScript側でラッパー関数を作成し、それをコールバック関数に指定する。ラッパー関数はインスタンスとメソッドを受け取り、コールバックされた際にcall()/apply()メソッドを用いて、受け取ったインスタンスをthisとしてメソッドを呼び出す。
Test.java
public class Test implements EntryPoint, FooHandler { private Foo foo = new Foo(); public native void barJava(FooHandler h) /*-{ // ラッパー関数を呼び出す。 wrappedBarJs(h, h.@jp.fujiyan.test.client.foo.FooHandler::callback()); }-*/; public void callback() { Window.alert(String.valueOf(foo)); } /** * This is the entry point method. */ public void onModuleLoad() { barJava(this); } }
bar.js
function barJs(callback) { callback(); } // ラッパー関数 // instanceは、コールバック関数内でthisにしたいインスタンス function wrappedBarJs(instance, callback) { barJs( function() { callback.apply(instance); } ); }
Channel API(作成中)
- Channel APIを使えば、サーバーからのpush通信でクライアントにイベントを発生させることが可能
- GAE SDK1.3.5時点でJavadocには記載されていないものの、jarファイル内にはクラスが存在している模様
クライアント側のJavaScriptはhttp://talkgadget.google.com/talkgadget/channel.jsを利用。GWTからJSNIでアクセスする。
直接アクセスせずに、appengine-api.jar内から/apphosting/tools/dev-channel-js.jsを抽出し、それをwarディレクトリ内に配置せよ、とのこと。
配置の際には、warディレクトリに/_ah/channelディレクトリを作成し、その中に抽出したjsファイルを"jsapi"にリネームして配置する。
参考:Dance Dance Robot error / Channel API
配置の際には、warディレクトリに/_ah/channelディレクトリを作成し、その中に抽出したjsファイルを"jsapi"にリネームして配置する。
参考:Dance Dance Robot error / Channel API
- 上記パスで指定しておけば、1.3.6から正式リリースされた際にもパスの修正不要らしい。
- そのうちGWTのサポートクラスが出来るらしい。それまではとりあえずDance Dance Robot!にある
- com.google.appengine.demos.dda.client.channel.Channel
- com.google.appengine.demos.dda.client.channel.ChannelFactory
- com.google.appengine.demos.dda.client.channel.Socket
- com.google.appengine.demos.dda.client.channel.SocketListener
をまるパクリする(汗
- Serverから送ることができるのはString
- よって、オブジェクトをStringにエンコードして送ることになる。
- Dance Dance Robotでは、com.google.gwt.user.server.rpc.RPCを使ってオブジェクトをエンコードして送っている。
- 本来はRCP.encodeResponse()でエンコードすれば楽だけど、privateメソッドらしいので仕方なくRCP.encodeResponseForSuccess()でエンコードしている。そのため、いちいちダミーメソッドを定義したりと回り道。