Reloading this page will cause modal window to disappear を回避

Wicket の モーダルウィンドウを閉じる時に、ページ遷移 setResponsePage 

を何も対処せずに実行すると、、

  Reloading this page will cause modal window to disappear

が発生してしまう。

これを回避するには、モーダルウィンドウを設置するページで初期処理として

以下の JavaScript を実行しておくことだ。

  Wicket.Window.unloadConfirmation = false;

 

GSON でjson生成時の null を処理する(2)

先日書いたGSON使用時の  String型のNULLパターン { "aaa": "null" } でなくて、

{ "aaa": "" } にするための TypeAdapterFactory を書き直しました。

import java.io.IOException;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
/**
 * NullStringToEmptyAdapterFactory
 */
public class NullStringToEmptyAdapterFactory implements TypeAdapterFactory{
   @Override
   public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
      Class<T> rawType = (Class<T>)type.getRawType();
      if (rawType != String.class){
          return null;
      }
      return (TypeAdapter<T>) new TypeAdapter<String>(){
         @Override
         public String read(JsonReader reader) throws IOException{
            if (reader.peek()==JsonToken.NULL){
               reader.nextNull();
               return "";
            }
            return reader.nextString();
         }
         @Override
         public void write(JsonWriter writer, String value) throws IOException{
            if (value==null){
               writer.value("");
               return;
            }
            writer.value(value);
         }
      };
   }
}
Integer型も、対応するなら、ちょっと長くなりますが、、、

import java.io.IOException;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
/**
 * NullStringIntegerToEmptyAdapterFactory
 */
public class NullStringIntegerToEmptyAdapterFactory implements TypeAdapterFactory{
   @Override
   public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
      Class<T> rawType = (Class<T>)type.getRawType();
      if (rawType != String.class && rawType != Integer.class){
          return null;
      }
      if (rawType==Integer.class){
         return (TypeAdapter<T>) new TypeAdapter<Integer>(){
            @Override
            public Integer read(JsonReader reader) throws IOException {
               if (reader.peek()==JsonToken.NULL){
                  reader.nextNull();
                  return null;
               }
               String s = reader.nextString();
               return s==null || "".equals(s) ? null : new Integer(s);
            }
            @Override
            public void write(JsonWriter writer, Integer value) throws IOException {
               if (value==null){
                  writer.value("");
                  return;
               }
               writer.value(value.toString());
            }
         };
      }
      return (TypeAdapter<T>) new TypeAdapter<String>(){
         @Override
         public String read(JsonReader reader) throws IOException{
            if (reader.peek()==JsonToken.NULL){
               reader.nextNull();
               return "";
            }
            return reader.nextString();
         }
         @Override
         public void write(JsonWriter writer, String value) throws IOException{
            if (value==null){
               writer.value("");
               return;
            }
            writer.value(value);
         }
      };
   }
}

GSON でjson生成時の null を処理する場合、

google  GSON でJSONを生成する場合、そのままだと、nullオブジェクトは無視(出力されない)なので、GsonBuilder の serializeNulls() を呼び出して、  { "a": "null" } のように出す。

しかし、String オブジェクトの結果として、{ "a": "" } が欲しい場合がある。

JsonSerializer<String> を実装した adapter を書いてみたが、うまくいかない。

古い情報だが、

http://stackoverflow.com/questions/9483348/gson-treat-null-as-empty-string

にヒントがあった。当時のバージョンよりバージョンUPが期待されていたが、2.6.2でも一向に解決されてない。stackoverflow に書いてあった TypeAdapterFactoryの方法が最も有効で、stackoverflowに書いてあったものを修正すると思いどおりになる。

 

import java.io.IOException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
/**
 * StringAdapter
 */
public class StringAdapter extends TypeAdapter{
   @Override
   public String read(JsonReader reader) throws IOException{
      if (reader.peek() == JsonToken.NULL){
         reader.nextNull();
         return "";
      }
      return reader.nextString();
   }
   @Override
   public void write(JsonWriter writer, String value) throws IOException {
      if (value == null) {
         writer.value("");
         return;
      }
      writer.value(value);
   }
}

public class NullStringToEmptyAdapterFactory implements TypeAdapterFactory{
    public  TypeAdapter create(Gson gson, TypeToken type){
       Class rawType = (Class) type.getRawType();
	   if (rawType != String.class){
			 return null;
		}
		return (TypeAdapter) new StringAdapter();
	}
}
この TypeAdapterFactory の create で無名クラスで返すように書きたかったのだが、 

うまく書けなかった。

Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new NullStringToEmptyAdapterFactory())
.create();

でも、Google group の投稿には、gsonで作成済の json 文字列に対して、replaceAll("nill", "\"\"") を実行すればいいじゃないのように、書いてある。

いや、確かにそうなんだけど。。。。

それなら、replaceAll(":\"null\"", "\"\"") って書くけど。。。replaceAll がもし負荷が上がるなら、TypeAdpter の方で処理するのが正解なのか。。。。

Wicket AjaxButton onSubmit の前に JavaScript を差し込む

Wicket の AjaxButton  onSubmit の実行前に処理したいJavaScript がある場合、

わざわざ jQuery$( ボタンセレクタ ) .click( function(){ ...} ); のソースを用意して

JavaScriptHeaderItem.forReference で読み込ませる方法にするか、

生成したAjaxButton に、更に、 AjaxEventBehavior("click") の onEvent で、

  AjaxRequestTarget # prependJavaScript で書くか。。

が通常、思いつく方法だが、

AjaxButton  の中の getOnClickScript メソッドをオーバーライドして、

クリック時に実行したい JavaScript を渡して JavaScript 処理後にonSubmitの処理が

走る方法もある。

           @Override

           protected String getOnClickScript(){

                    return "$('#name').val('" + foo + "')";

            }

 

GSON と LocalDate / LocalDateTime のシリアライズ・デシリアライズ

Java7をやめてJava8で開発するようになり、java.util.Dateを使わなくなり、

Google gson で LocalDate / LocalDateTime をシリアライズ・デシリアライズするのに

簡単に書ける方法を探すのに苦労したのでその過程と結果です。

GsonBuilder の registerTypeAdapter で LocalDateのシリアライズ&デシリアライズ

LocalDateTimeのシリアライズ&デシリアライズと、registerTypeAdapter の呼び出しを4回も書かなくてはならない。

  new GsonBuilder().registerTypeAdapter(LocalDate.class, new JsonSerializer<LocalDate>(){

            @Override
              public JsonElement serialize(LocalDate date, Type type, JsonSerializationContext context){
                  return new JsonPrimitive(date.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")));
              }
          }).  /* このようなものを、毎回4つ書くのは辛い。。*/

そこで、以下のような Adapter を2つ用意する。

 

@FunctionalInterface
public interface LocalDateAdapter extends JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
@Override
public default JsonElement serialize(LocalDate date, Type type, JsonSerializationContext context){

return new JsonPrimitive(date.format(getFormatter()));
}
@Override
public default LocalDate deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException{

return LocalDate.parse(json.getAsString(), getFormatter());
}

public static LocalDateAdapter create(LocalDateAdapter a){
return a;
}

public DateTimeFormatter getFormatter();

}

続きを読む

Wicket のタブで、フォーム入力を保持した切り替え

1年半くらい前、Wicket のTab ・・・org.apache.wicket.extensions.markup.html.tabs パッケージの一連のタブを使用する時のデザインを書いたことがある。

Wicket のタブページ作成作業 - Oboe吹きプログラマの黙示録

この時デザインの事しか書かなかった、実践でのTab のPanelインスタンスの状態として、書くべきことを書いていなかった。。

タブを切り替えて再度、一度表示したタブに戻ってきた時にフォーム入力したものなどを保持させたい場合があるはずだ。

最低限やらなければならないのは、AbstractTabの getPanel が一度生成したものを常に返すようにすること。そしてタブ切り替えの前に実行されることを捕捉して

保持を約束する。

例)

Panel tab1;
Panel tab2;
Panel tab3;

public TabedPage(){

    List<ITab> tabs = new ArrayList<ITab>();

     // tab1~3 まで生成済のTab Panelを返すようにする。
    tabs.add(new AbstractTab(new Model<String>("Tab-1")){
        @Override
        public Panel getPanel(String panelId){
        if (tab1==null) tab1 = new Tab1Panel(panelId);
            return tab1;
        }
    });
    queue(new AjaxTabbedPanel<ITab>("tabs", tabs));
}

タブの中で保持させるフォームの入力は、onchangeイベントあるいは、onblurイベントを捕捉して、ページインスタンスに保持させてしまう。

→ AjaxEventBehavior で捕まえて、コンポーネントの getInput を結果を、setModelObejct でセットして、AjaxRequestTarget の add メソッドを実行する!

当然、対象のコンポーネントは、setOutputMarkupId(true) を実行する。

タブ Panel の中で 入力テキストがある場合、例えば、、、

final TextField<String> name_Field = new TextField<String>("name", new Model<String>());
name_Field.setOutputMarkupId(true);
name_Field.add(new AjaxEventBehavior("change"){
    @Override
    protected void onEvent(AjaxRequestTarget target){ 
          name_Field.setModelObject(name_Field.getInput());
          target.add(name_Field);
     }
 });
queue(name_Field);

タブをクリックした時のイベントを捕捉しようとしたが、=onclickじゃなくて、、

Wincket が本来、Tab 専用にイベントを拾って処理してるところに、差し込んだ処理で書きたかったのだが、なかなかうまくいかないので、これで逃げることにした