GAE + GWT プログラミングメモ

Google App Engine とGoogle Web Toolkit のメモ

GWTのPlaceとActivity

作成中のアニメ特化アンテナサイト

雲の中の2次

ブラウザの履歴に残るようにする

GWTAjaxでクリックイベントを処理するので、ブラウザの履歴に残らない。
ただ、通常の感覚としては、リンククリックしたらブラウザの戻るで戻ってほしい。
そこで、GWTでは、PlaceとActivityというクラスでこの仕組みを提供している。

UI

anchor1とanchor2を用意し、anchor1がクリックされたらHello anchor1、anchor2がクリックされたらHello anchor2と表示されるサイトを作成する。
このクリックをブラウザバックに対応させる。
f:id:kumo2ji:20130810155614p:plain

UiBinder

anchor1とanchor2を追加し、Labelに結果を表示する。

<!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:HTMLPanel>
        <g:VerticalPanel>
            <g:Anchor text="anchor1" ui:field="anchor1" />
            <g:Anchor text="anchor2" ui:field="anchor2" />
            <g:Label ui:field="label" />
        </g:VerticalPanel>
    </g:HTMLPanel>
</ui:UiBinder> 

HelloViewImplコンストラクタはHelloの後に続く文字列を第2引数でとる。
HelloPlaceコンストラクタは第1引数にtokenをとり、この文字列がURLに追加される。
ここでは、押されたリンクに依存し、anchor1もしくはanchor2を指定する。

public class HelloViewImpl extends Composite {
    private static HelloViewImplUiBinder uiBinder = GWT
            .create(HelloViewImplUiBinder.class);

    interface HelloViewImplUiBinder extends UiBinder<Widget, HelloViewImpl> {
    }

    @UiField
    Anchor anchor1;
    @UiField
    Anchor anchor2;
    @UiField
    Label label;
    private PlaceController placeController;

    public HelloViewImpl(PlaceController placeController, String text) {
        initWidget(uiBinder.createAndBindUi(this));
        this.placeController = placeController;
        label.setText("Hello " + text);
    }

    @UiHandler("anchor1")
    void onAnchor1Click(ClickEvent event) {
        placeController.goTo(new HelloPlace(anchor1.getText()));
    }

    @UiHandler("anchor2")
    void onAnchor2Click(ClickEvent event) {
        placeController.goTo(new HelloPlace(anchor2.getText()));
    }
}

Place

PlaceはURLに追加されるトークンを規定する。
具体的には#HelloPlace:tokenが追加される。TokenizerのgetPlace(String token)の第1引数tokenはコロン以降の文字列が入る。

public class HelloPlace extends Place {
    private String token;

    public HelloPlace(String token) {
        this.token = token;
    }

    public String getToken() {
        return token;
    }

    public static class Tokenizer implements PlaceTokenizer<HelloPlace> {
        @Override
        public String getToken(HelloPlace place) {
            return place.getToken();
        }

        @Override
        public HelloPlace getPlace(String token) {
            return new HelloPlace(token);
        }
    }
}

Activity

ActivityはPlace(トークン)とUI(UiBinder)を繋ぐ。
startのオーバーライドで、HelloViewImplをインスタンス化し、containerWidgetにsetWidgetする。
containerWidgetはActivityManagerのsetDisplayで指定したWidgetになる。

public class HelloActivity extends AbstractActivity {
    private String token;
    private PlaceController placeController;

    public HelloActivity(HelloPlace place, PlaceController placeController) {
        this.token = place.getToken();
        this.placeController = placeController;
    }

    @Override
    public void start(AcceptsOneWidget containerWidget, EventBus eventBus) {
        containerWidget.setWidget(new HelloViewImpl(placeController, token));
    }
}

また、Activityを利用するためには、Gwt.gwt.xmlにcom.google.gwt.activity.Activityを追加する必要がある。

<?xml version="1.0" encoding="UTF-8"?>
<!-- When updating your version of GWT, you should also update this DTD reference, 
    so that your app can take advantage of the latest GWT module capabilities. -->
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.5.1//EN"
  "http://google-web-toolkit.googlecode.com/svn/tags/2.5.1/distro-source/core/src/gwt-module.dtd">
<module rename-to='gwt'>

    <inherits name='com.google.gwt.user.User' />

    <inherits name='com.google.gwt.user.theme.clean.Clean' />
    <inherits name="com.google.gwt.activity.Activity" />

    <entry-point class='com.ry.gwt.client.Gwt' />

    <source path='client' />
    <source path='shared' />

</module>

ActivityMapper

ActivityMapperはPlaceとActivityを繋ぐ。
instanceofでplaceのクラスを特定し、適切なActivityのサブクラスを返すようにgetActivityをオーバーライドする。

public class AppActivityMapper implements ActivityMapper {
    private PlaceController placeController;
    public AppActivityMapper(PlaceController placeController) {
        this.placeController = placeController;
    }
    @Override
    public Activity getActivity(Place place) {
        if (place instanceof HelloPlace) {
            return new HelloActivity((HelloPlace) place, placeController);
        }
        return null;
    }
}

PlaceHistoryMapper

PlaceHistoryMapperはPlaceをHistory(履歴)にマッピングする。

@WithTokenizers({HelloPlace.Tokenizer.class})
public interface AppPlaceHistoryMapper extends PlaceHistoryMapper
{
}

エントリーポイント

エントリーポイントで、これらの要素を繋ぐ。
Activityのstartメソッドの第1引数AcceptsOneWidget containerWidgetはここで指定したSimplePanelになる。

public class Gwt implements EntryPoint {
    @Override
    public void onModuleLoad() {
        SimplePanel appWidget = new SimplePanel();
        RootPanel.get().add(appWidget);
        
        EventBus eventBus = new SimpleEventBus();
        PlaceController placeController = new PlaceController(eventBus);

        // Start ActivityManager for the main widget with our ActivityMapper
        ActivityMapper activityMapper = new AppActivityMapper(placeController);
        
        ActivityManager activityManager = new ActivityManager(activityMapper, eventBus);
        activityManager.setDisplay(appWidget);

        // Start PlaceHistoryHandler with our PlaceHistoryMapper
        AppPlaceHistoryMapper historyMapper = GWT.create(AppPlaceHistoryMapper.class);
        PlaceHistoryHandler historyHandler = new PlaceHistoryHandler(historyMapper);
        historyHandler.register(placeController, eventBus, new HelloPlace("anchor1"));

        // Goes to the place represented on URL else default place
        historyHandler.handleCurrentHistory();
    }
}

参考

GWT Project