GWTに限った話ではありませんが、Javascript→Javaへの橋渡しにはJSONを使うのが一般的です。
で、Overlay Typesを使うことで結構らくちんできました、というお話。
今日のお題
はい、では今日のお題。
例えば複数のチェックボックスを表示する場合なんですが、
UiBinderを使うとこんな感じになると思います。
(ui.xml)
<g:CheckBox ui:name="test" ui:formValue="1" ui:field="foo">FOO</g:CheckBox> <g:CheckBox ui:name="test" ui:formValue="2" ui:field="bar">BAR</g:CheckBox> <g:CheckBox ui:name="test" ui:formValue="3" ui:field="baz">BAZ</g:CheckBox>
(Java)
public class HogePanel extends Composite{ @UiField CheckBox foo; @UiField CheckBox bar; @UiField CheckBox baz; ...(以下略) }
。。。もう何だかこの時点でメンドクサイ。と思いません?
3つならまだいいけど、チェックボックスが10個もあった日にゃそれはもう。
普通に書くと
まあ、こんな感じになると思います。
public class CheckBoxGroup extends Composite implements HasText, HasName { /** 以下の2つのプロパティのsetter/getterは省略 */ private String name; private String text; private final Panel basePanel = new VerticalPanel(); public CheckBoxGroup() { this.initWidget(this.basePanel); } public void onAttach() { this.basePanel.clear(); this.addCheckBoxes(); super.onAttach(); } private void addCheckBoxes() { final Panel layoutPanel = new HorizontalPanel(); final JSONArray entries = (JSONArray) JSONParser.parseStrict(this.text); for (int i = 0; i < entries.size(); i++) { layoutPanel.add(this.createCheckBox((JSONObject) entries.get(i))); } this.basePanel.add(layoutPanel); } private CheckBox createCheckBox(final JSONObject entry) { final CheckBox checkBox = new CheckBox(); checkBox.setName(this.name); checkBox.setText(((JSONString) entry.get("text")).stringValue()); checkBox.setFormValue(((JSONString) entry.get("formValue")).stringValue()); return checkBox; } }
(※実はコレ、試してません。たぶんベタに書くとこんな感じになるのでは、と。。。)
で、上記のようなコンポーネントを作ることで、
ui.xmlでこのように書けるようになるわけです。
<g:Label>好きなモビルスーツ</g:Label> <r:CheckBoxGroup ui:name="test" ui:field="mobilesuits"> [ { text: 'ガンダム', formValue: 'RX-78' } , { text: 'ザク', formValue: 'MS-06F' } , { text: 'アッガイ', formValue: 'MSM-04' } ] </r:CheckBoxGroup>
public class HogePanel extends Composite{ @UiField CheckBoxGroup mobilesuits; ...(以下略) }
結構スッキリ書けるようになりました、が。。。
しかし、コレはコレで別の問題が発生しています。
何がアレかというと
JSONArrayやJSONObjectはタイプセーフでないということ。
だから、
final JSONArray entries = (JSONArray) JSONParser.parseStrict(this.text);
とか
checkBox.setText(((JSONString) entry.get("text")).stringValue()); checkBox.setFormValue(((JSONString) entry.get("formValue")).stringValue());
とか、イヤ〜な感じのコードを書くことが強制されてしまいます。
今時、Generics使えないとかありえないっス。
Java5が発表されて以降、Genericsにどっぷりクビまで浸かったワタクシとしては、
キャストを使うなんて有り得ません。全部タイプセーフじゃなきゃヤダ!
_, ,_ (`Д´ ∩ < ヤダヤダ!! ⊂ ( ヽ∩ つ ジタバタ 〃〃
そこでOverlay Typesの登場
GWTのOverlay Typesとは要するに、
JavaScriptのObjectを『見かけ上、POJOとして扱うようにする』ための仕組みです。
(※overlay→うわべ、外面という意味)
だから、
var something = { foo: 1, bar: 'abc' };
なんて値が存在したとき、これをムリヤリ
public class Something { public int getFoo() { ... } public String getBar() { ... } }
で扱えるようにしてしまう、というモノ。
使い方は簡単で、以下のルールを守ったPOJOクラスを作ればいいだけ。
例えば上記のSomethingクラスは、真面目に書くと次のようになります。
ただし、
- OverlayTypesはあくまでも見せかけだけの機能である
- GWTは、実際にはJavascriptにコンパイルされてクライアントサイドで動作する
なので、実際のJavascriptの値にアクセスするには次のようにJSNIを使う必要があります。
public class Something extends JavaScriptObject { protected Something() {} /** ファクトリメソッド */ public static native Something parse(String json)/*-{ return eval(json); }*/; public final native int getFoo() /*-{ return this.foo; }-*/; public final native String getBar() /*-{ return this.bar; }-*/; }
Overlay Typesを使うと
先程のCheckBoxGroupは、こんな感じにリファクタリング出来るようになるわけです。
public class CheckBoxGroup extends Composite implements HasText, HasName { // ...ここまで全く同一なので省略 private void addCheckBoxes() { final Panel layoutPanel = new HorizontalPanel(); final JsArray<CheckBoxEntry> entries = CheckBoxEntry.parse(text); for (int i = 0; i < entries.length(); i++) { layoutPanel.add(this.createCheckBox(entries.get(i))); } this.basePanel.add(layoutPanel); } private CheckBox createCheckBox(final CheckBoxEntry entry) { final CheckBox checkBox = new CheckBox(); checkBox.setName(this.name); checkBox.setText(entry.getText()); checkBox.setFormValue(entry.getFormValue()); return checkBox; } public static class CheckBoxEntry extends JavaScriptObject { protected CheckBoxEntry() {} public final native String getText()/*-{ return this.text; }-*/; public final native String getFormValue()/*-{ return this.formValue; }-*/; public static native JsArray<CheckBoxEntry> parse(final String json)/*-{ return eval(json); }-*/; } }
キャストが一つも存在しない美しい世界に。スバラシイ!
。。。ま、結局のところ見せかけだけなので、実行時エラーは避けられないんですけどね。