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

DecoratorPanelを使ったが、padding,border,marginが反映されなかったとき

ちょっとはまったので。
以下の内容が記述されたCSSファイルが読み込まれるように自分で設定すること。
たとえば、decoratorPanel.cssという名前のファイルにして読み込む

decoratorPanel.css

.gwt-DecoratorPanel {

}
.gwt-DecoratorPanel .topCenter,
.gwt-DecoratorPanel .bottomCenter {
background: url(images/hborder.png) repeat-x;
}
.gwt-DecoratorPanel .middleLeft,
.gwt-DecoratorPanel .middleRight {
background: url(images/vborder.png) repeat-y;
}
.gwt-DecoratorPanel .topLeftInner,
.gwt-DecoratorPanel .topRightInner,
.gwt-DecoratorPanel .bottomLeftInner,
.gwt-DecoratorPanel .bottomRightInner {
width: 5px;
height: 5px;
zoom: 1;
}
.gwt-DecoratorPanel .topLeft {
background: url(images/corner.png) no-repeat 0px 0px;
-background: url(images/corner_ie6.png) no-repeat 0px 0px;
}
.gwt-DecoratorPanel .topRight {
background: url(images/corner.png) no-repeat -5px 0px;
-background: url(images/corner_ie6.png) no-repeat -5px 0px;
}
.gwt-DecoratorPanel .bottomLeft {
background: url(images/corner.png) no-repeat 0px -5px;
-background: url(images/corner_ie6.png) no-repeat 0px -5px;
}
.gwt-DecoratorPanel .bottomRight {
background: url(images/corner.png) no-repeat -5px -5px;
-background: url(images/corner_ie6.png) no-repeat -5px -5px;
}
html>body .gwt-DecoratorPanel {
}
* html .gwt-DecoratorPanel .topLeftInner,
* html .gwt-DecoratorPanel .topRightInner,
* html .gwt-DecoratorPanel .bottomLeftInner,
* html .gwt-DecoratorPanel .bottomRightInner {
width: 5px;
height: 5px;
overflow: hidden;
}

 
参考
DecoratorPanel (GWT Javadoc)
Showcase of GWT Features

slim3 使用時にEGITでチェックアウトできないとき

 slim3はModelのmetaファイルを作成するためにATP (Annotation Processing)を使用している。ATPを有効にしたままだとEGITでCheckOutしようとしたときに、変更を破棄するかを聞いてくるダイアログが何回も出てきてCheckOutできない。CheckOutするためには

 メニューバーの Project -> Properties -> Java Compiler -> Annotation Processing

の中にある、「enable annotation processing」のチェックをはずす。

Google App Engine のデプロイで苦労した話

投稿日 : 2015.3.13

GoogleAppEngineはjspファイルを含んでいる場合、デプロイが失敗することがある、ということはググれば結構出てくるが、それでもはまったのでメモを残す。

 

java.lang.RuntimeException: Cannot get the System Java Compiler. Please use a JDK, not a JRE.

というエラーメッセージが出てくるときは、原因として環境要因(eclipse.iniでjdkを指定していなかった)とソースファイル要因(jspコンパイルエラー、でもローカルサーバで実行時は普通に動く)が考えられる。

 

●環境要因の解決法 参考:GAE−JSPがあるとデプロイが失敗する件 - sabonightの日記

Javaはjdk7以下を使うこと。GAEがまだ8に対応してないからUnsupportedClassVersionErrorが出る (2015.3.13)

eclipse.ini に以下のコードを追加

-vm 
C:\Program Files\Java\jdk1.7.0_75\bin\javaw.exe(jdkに含まれるjavaw.exeへのパス)

-vmargs
-Dosgi.requiredJavaVersion=1.7
環境変数パスの java_homeをjdk7のものに(C:\Program Files\Java\jdk1.7.0_75\ など)、変数pathの最初に%java_home%\binを移動

ソースファイル要因の解決法 参考:Issue 8725 - googleappengine - Failed to deplay - Google App Engine - Google Project Hosting

jspファイルからすべてimport文を除外。クラス名はすべて完全修飾クラス名で書く。

jspのinclude元・include先で変数名の衝突が起こらないようにする。例えば、多くのjspファイルから参照される、ページレイアウトを定義したjspファイルなどは変数名を冗長にしておく。

 

環境要因とソースファイル要因を切り分けるためには?

 まず、(GooglePlugInを利用するなどして)jspを含まない新規GAEプロジェクトを作成し、デプロイを試す。これは成功するはず。

 次に、warファイルに単純なjspファイルを作成(ファイル内でjavaが使われていなくてもいい。Eclipseなら、PackageExplorerでwarフォルダを右クリック -> New -> Other... -> Web -> JSP File で作成したデフォルトのJSPファイルで十分)。デプロイを試す。失敗したら環境要因を修正する必要がある。成功すれば環境要因はクリア。デプロイしようとしているプロジェクトに含まれるJSPファイルに問題があることになる。

 

-------------------------------------------------------------------------------------------

以下経緯。反面教師。

 

エラーメッセージ-----------------

Preparing to deploy:
Created staging directory at: 'R:\Temp\appcfg6583028287260996029.tmp'
Scanning for jsp files.
Compiling jsp files.
java.lang.RuntimeException: Cannot get the System Java Compiler. Please
use a JDK, not a JRE.

Debugging information may be found in
R:\Temp\appengine-deploy8586597779396298325.log

----------------------------------------
JREじゃなくてJDKを使えって書いてあるけど、ちゃんとそのように指定した。
 → eclipse.iniの-vm に jdk7を指定(jdk 6
32bitも試した)参考:https://groups.google.com/forum/#!topic/google-app-engine-japan/ymbsf-1Mf4I

 ⇒ だめ


 appcfg.cmdを使うとシステムのJDKを取得できる 参考:http://qiita.com/MOKYN/items/cec526f981f1daba6afb

Googleアカウントにログインするときに、普通のパスワード入力だと試行ブロックに引っかかり、その後ログインできなくなった。--oauth2 パラメータを利用することで解決)⇒ だめ

 実はJSPコンパイルエラー 参考:https://code.google.com/p/googleappengine/issues/detail?id=8725

 プリコンパイルのできるJSPページを追加してアクセス(slim3を使っていたので、FrontServletによるリダイレクトは解除しないとエラーでるようだ)、.jspファイルからすべてimport文を除外(アップロード用コンパイラはローカルサーバ用コンパイラよりも繊細なためエラーが出るとか) ⇒ だめ

↓ 

環境(java version?)かプロジェクト(JSP?)かの切り分けをためしてみる
 初期プロジェクトデプロイ : 成功
 index.jspを追加(jspにはjavaコードすら書かれていない): 失敗

eclipse.iniを修正
-vm (半角スペース)(改行)
javawへのパス(/区切り)
とすればよいらしい

 ⇒ 初期プロジェクト+jspファイル : 成功
 ⇒ でもメインのプロジェクトはだめ

jspファイル間のローカル変数の衝突をさけるため、レイアウト関連jspはファイル名を変数名の先頭につけてみる

 ⇒ エラーログが変わった
前:java.lang.RuntimeException: Cannot get the System Java
Compiler. Please use a JDK, not a JRE.(実際はjspのプリコンパイルエラー)
後: com.google.apphosting.utils.config.AppEngineConfigException:
Received SAXException parsing the input stream

web.xmlは日本語コメント使えない 参考:psycho cafe: gaeのweb.xmlでは日本語コメントが使えない

 ⇒デプロイ成功!

  でも、jspが動作しないようだ。java8でjspコンパイルしたが、gaeはjava7までしか対応していないため、「ava.lang.UnsupportedClassVersionError:
クラス名 (Unsupported major.minor version
Mj.Mi)」の例外が発生。環境変数pathを書き換え(pathの値は最初の方から順番に評価されるようだ。最初が「C:\ProgramData\Oracle\Java\javapath」だったため、java8でコンパイルされたのかも。
 参考:Javaアプリケーション メモ(Hishidama's Java-Application Memo)

  ⇒ 完全にデプロイ成功