GWTでlombokを使用する

  1. eclipseにlombokをインストール
  2. (lombokを使用してjavaファイルを作成)
  3. GWT Compile Project -> Advanced -> VM arguments: に次の文を追加
  • javaagent:lib/lombok.jar=ECJ (プロジェクトのlibフォルダにlombok.jarを入れているとき)

「(ゲッター名)is undefined for the type」のようなエラーが出たとき

対象のフィールドにいったん @Getterをつけてから再コンパイルすると直ることがある。

参考:
Project Lombok


@Data
@Model(schemaVersion = 1)
public class MyModel implements Serializable {
    private static final long serialVersionUID = 1L;

    @Attribute(primaryKey = true)
    private Key key;
    @Attribute(version = true)
    private Long version;
    
//    @Getter
    private String title;
    @Attribute(unindexed=true)
    private String content; 
    @Attribute(lob=true)
    private String description; 
}

slim3プロジェクトで新しいバージョンのGAEを使用する

記事作成日:2015.4.9


slim3プラグインはアップデートされていないようで、そのままプロジェクトを作成すると GoogleAppEngine SDKのバージョンが1.6.0になる。
slim3関連ファイルを新しいSDKに移動しても動作するみたいなので手順をまとめる。

空のslim3プロジェクトを作成する

  • New Project -> Slim3 -> Slim3 Project
  • Use Google Web Toolkit にチェックを入れてプロジェクトを作成。このslim3プロジェクトからslim3関連ファイルを抜き取ってくることになる。

本命のプロジェクトの作成

  • New Project -> Google -> New Web Application Project
  • 以下のファイルを移動

プロジェクトルートディレクトリの
build.properties
build.xml

war/WEB-INF/lib ディレクトリの
junit-4.7.jar
ktrwjr.jar
slim3-1.0.15.jar

  • 以下のフォルダを移動

プロジェクトルートディレクトリの
lib
libsrc

  • war/WEB-INFのweb.xmlを編集

slim3側のweb.xmlの中身を本命のweb.xmlへ移す。
タグの中身をすべて移動すればよい。

このときエラーが出るので<web-resource-name>を追加する

        <web-resource-collection>
            <url-pattern>*.jsp</url-pattern>
        </web-resource-collection>

        <web-resource-collection>
        	<web-resource-name></web-resource-name>
            <url-pattern>*.jsp</url-pattern>
        </web-resource-collection>

パッケージ名も変更

	<context-param>
        <param-name>slim3.rootPackage</param-name>
        <param-value>(空のslim3プロジェクトのパッケージ名)</param-value>
    </context-param>

	<context-param>
        <param-name>slim3.rootPackage</param-name>
        <param-value>(本命のプロジェクトのパッケージ名)</param-value>
    </context-param>
  • ビルドパスの編集

プロジェクト内にある
lib\slim3-gen-1.0.15.jar
war\WEB-INF\lib\junit-4.7.jar
war\WEB-INF\lib\slim3-1.0.15.jar
への参照を追加

  • gwt.xmlファイルに以下の文を追加
    <inherits name='org.slim3.gwt.emul.S3Emulation' />
    

(メニューバー) -> Project -> Properties -> Java Compiler -> Annotation Processing
 アノテーションプロセッシングを有効にする
-> Factory Path
 以下のjarファイルを追加

    • lib/slim3-gen-x.x.xx.jar

GWTのRPCで、特定のフィールドは送受信したくないとき

GWTのRPCで、特定のフィールドは送受信したくないときは、クラスのフィールドにtransient修飾子をつけるとよい。
アノテーションをつけて抑制する方法とかもありそうだけどわからなかった。

なお、クライアント - サーブレット間で送受信したいが、データベースには保存したくないフィールドは
@Attribute(persistent=false)
のようにアノテーションをつける

使用例

//データモデルにコンポジットパターンとリスナーパターンを使用するとき。
@Model(schemaVersion = 1)
public abstract class Composite implements Serializable{

@Attribute(primaryKey = true)
private Key key;

@Attribute(version = true)
private Long version;

//データベースにComposite役の参照関係を保存するとき
//Composite役をデータベースに直接保存するとデータ容量がすごいことになる。
//Composite役への参照はサーブレット - クライアント間で送受信するが、
//データベースには保存しない。
@Attribute(persistent=false)
protected List<Composite> childrenList = new ArrayList<Composite>(); //子要素のリスト

//データベース内で子要素との関連を保持するためのリスト
//本当はInverseModelListRefを使った方がいいかも。
@Attribute(unindexed=true)
private List<Key> childrenKeyList = new ArrayList<Key>();

//オブザーバパターンのためのリスナー
// RPCでサーバに送ろうとすると、リスナー側のパネルまでサーバに送ってしまい、NotSerializableExceptionがでる。
@Attribute(persistent=false)
transient protected List<ItemListener> _listeners = new ArrayList<ItemListener>();

//(後略)

}

データベース - サーブレット - クライアント間のデータ送受信の抑制

○:送受信する!  ×:抑制

クラスのフィールドにtransient修飾子をつける フィールドに @Attribute(persistent=false) アノテーションをつける
DB
× ×
Servlet
× ×
Client

GWTでサーバを使用せずにZipファイルをクライアントへ保存

外部JavaScript(JS)ライブラリとJSNIを使って実装する。FileSaverとJSZipを利用する。

Shift_JISは扱えません!!

  • 使用するjsライブラリ
  • war/js に使用するJSファイルを入れておく
  • htmlファイルでJSファイルをリンクしておくのも忘れずに
  • javaファイルでは、JSNIで外部JSライブラリにアクセス($wndを忘れずに)。JSNI内部ではjava.util.Mapが使えないようなので、JSONObjectを使用する。
  • JSONObjectを使うために、.gwt.xmlファイルにinheritsを追加する必要がある。

参考
gwt - Java Hashmap and Mutlidimensional array type sig in JSNI? - Stack Overflow
eligrey/FileSaver.js · GitHub
JSZip



 *.html

(前略)
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link type="text/css" rel="stylesheet" href="/css/global.css" />
    <script language="javascript" src="js/jszip.js"></script>
    <script language="javascript" src="js/FileSaver.js"></script>
</head>

(後略)

 *.java

        public void onModuleLoad() {

            Button save = new Button("保存");
            save.addClickHandler(new ClickHandler() {
                public void onClick(ClickEvent event) {
                    //JSONObject がMap代わりに使える
                    JSONObject obj = new JSONObject();
                    for (int i = 0; i < 3; i++) {
                        obj.put("k"+i, new JSONString("v"+i));
                    }
                    JavaScriptObject jsObj = obj.getJavaScriptObject();
                    saveAsZip(jsObj);
                }
            });

        RootPanel.get().add(save);
    }

    /**
     * @param fileMap
     *            key:ファイル名 value:ファイル内容
     * @return
     */
    private native boolean saveAsZip(JavaScriptObject fileMap) /*-{
		var zip = new $wnd.JSZip();

		for (key in fileMap) {
			zip.file(key + ".txt", fileMap[key]);
		}
		var blob = zip.generate({
			type : "blob"
		});
		$wnd.saveAs(blob, "text.zip");
		return true;
    }-*/;

*.gwt.xml

<module>
	<inherits name="com.google.gwt.user.User" />
	<inherits name="com.google.gwt.user.theme.standard.Standard" />
	<inherits name="com.google.gwt.json.JSON" />

	<entry-point class="(エントリーポイントクラス)" />
	<source path="client" />
</module>

GWTをSuper Dev ModeでデバッグするときGWTで書いたページが更新されていない

 GWTデバッグするために、SuperDevModeでローカルサーバを立ち上げ、デバッグしたいWebページにアクセスしたが、ページの内容が古いままのとき。もしくは、ページにアクセスしてもGWTコンパイルが始まらないとき。

 いったんローカルサーバを終了 ⇒ GWTコンパイル ⇒ 再度ローカルサーバ起動・開発中Webページにアクセス

とすれば、今後WebページにアクセスするたびにGWTコンパイルが実行されるはず。

FlowPanelの要素にPanelを追加しても浮動化してくれない

GWTを使用していて、FlowPanelの要素にPanelを追加しても浮動化してくれないとき。

追加する要素を<span></span>で囲み、CSSでfloat属性を指定すればよい

参考:
Simplifying GWT Markup with HTML Widgets « TurboManage



Main.java

public class Gwt_tmp implements EntryPoint {
	
	private static final int COUNT = 10;

	public void onModuleLoad() {

		VerticalPanel mainPanel = new VerticalPanel();
		RootPanel.get().add(mainPanel);

		mainPanel.addStyleName("contents");
		{
			mainPanel.add(new Label("ボタンをFlowPanelに追加"));
			FlowPanel fp = new FlowPanel();
			fp.setWidth("800px");
			mainPanel.add(fp);
			fp.clear();
			for (int i=0; i<COUNT; i++)
		    {
				Button item= new Button("button"+i);
				item.setWidth("100px");
		        fp.add(item);
		    }
			
		}
		mainPanel.add(new HTML("<hr>"));[f:id:black-skin:20150315175347p:plain]
		{
			mainPanel.add(new Label("VerticalPanelはFlowPanelでも横配置できない"));
			FlowPanel fp = new FlowPanel();
			mainPanel.add(fp);
			for (int i = 0; i < COUNT; i++) {
				VerticalPanel item = new VerticalPanel();
				item.setStyleName("name-" + i);
				item.add(new Label("title" + i));
				item.add(new Button("ボタン"));
				fp.add(item);
			}

		}
		

		mainPanel.add(new HTML("<hr>"));
		{
			mainPanel.add(new Label("<span>で囲み、floatを指定"));
			FlowPanel fp = new FlowPanel();
			fp.setWidth("800px");
			mainPanel.add(fp);
			fp.clear();
			for (int i=0; i<COUNT; i++)
		    {
				VerticalPanel item = new VerticalPanel();
				item.setWidth("150px");
				item.setStyleName("name-" + i);
				item.add(new Label("title" + i));
				item.add(new Button("ボタン"));
		        fp.add(new FloatSpanItemWidget(item));
		    }
			
		}
	}
}

FloatSpanItemWidget.java


import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;

public class FloatSpanItemWidget extends SimplePanel
{
    public FloatSpanItemWidget()
    {
        super((Element) Document.get().createSpanElement().cast());
        addStyleName("float");
    }
 
    public FloatSpanItemWidget(String s)
    {
        this();
        getElement().setInnerText(s);
    }
 
    public FloatSpanItemWidget(Widget w)
    {
        this();
        this.add(w);
    }
}
<!--(中略)-->
.float{
float: left;
}

f:id:black-skin:20150315175705p:plain