Wicket 8 で AJAX イベントビヘビアを Generic に

Wicket 8 で AJAX イベントビヘビアを
oboe2uran.hatenablog.com

と書いたものの、落ち着いてみれば、一般用に以下のとおり書けるし、その方がビヘビアを追加した先の
コンポーネントを参照したビヘビアが書ける。。

import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.danekja.java.util.function.serializable.SerializableBiConsumer;

/**
 * GenericAjaxEventBehavior
 */
public abstract class GenericAjaxEventBehavior extends AjaxEventBehavior{

   private GenericAjaxEventBehavior(String event){
      super(event);
   }

   public static GenericAjaxEventBehavior of(String eventName, SerializableBiConsumer<Component, AjaxRequestTarget> consumer){
      return new GenericAjaxEventBehavior(eventName){
         private static final long serialVersionUID = 1L;
         @Override
         protected void onEvent(AjaxRequestTarget target){
            consumer.accept(getComponent(), target);
         }
      };
   }
}

xxx というコンポーネントに add する AJAX のビヘビアは、、

xxx.add(GenericAjaxEventBehavior.of("click", (c, t)->{
    // クリックしたコンポーネント = c を参照
    // AjaxRequestTarget = t を処理
});

AJAXDownload ビヘビア、

Wicket8 が前提の話だが、ビヘビアをラムダで。。を考え出すと、次から次へと出てくる。。。
AJAX の振る舞いで動かすダウンロード、以前、
Wicket 6.x→7.x でファイルダウンロード時のファイル名の注意 - Oboe吹きプログラマの黙示録
この中で、public abstract class AJAXDownload を書いた。
でもこれは、以下のように、インスタンス生成の staticメソッド-ラムダを用意すれば、
もう少しスッキリする。

import java.io.IOException;
import java.io.OutputStream;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.AbstractAjaxBehavior;
import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
import org.apache.wicket.request.resource.ContentDisposition;
import org.apache.wicket.util.resource.AbstractResourceStreamWriter;
import org.apache.wicket.util.resource.IResourceStream;
import org.danekja.java.util.function.serializable.SerializableConsumer;
import org.danekja.java.util.function.serializable.SerializableSupplier;
/**
 * AJAX Download.
 */
public abstract class AJAXDownload extends AbstractAjaxBehavior{
   protected abstract IResourceStream getResourceStream();

   public void callBackDownload(AjaxRequestTarget target){
      target.appendJavaScript("setTimeout(\"window.location.href='" + getCallbackUrl().toString() + "'\", 100);");
   }

   @Override
   public void onRequest(){
      try{
      ResourceStreamRequestHandler handler = new ResourceStreamRequestHandler(getResourceStream(), getFileName());
         handler.setContentDisposition(ContentDisposition.ATTACHMENT);
         getComponent().getRequestCycle().scheduleRequestHandlerAfterCurrent(handler);
      }catch(Exception e){
         throw new RuntimeException(e.getMessage(), e);
      }
   }
   protected String getFileName(){
      return null;
   }

   public static AJAXDownload of(SerializableConsumer<OutputStream> write
   , SerializableSupplier<String> getConteType, SerializableSupplier<String> getName){
      return new AJAXDownload(){
         @Override
         protected IResourceStream getResourceStream(){
            return new AbstractResourceStreamWriter(){
               @Override
               public void write(OutputStream out) throws IOException{
                  write.accept(out);
                  out.close();
               }
               @Override
               public String getContentType(){
                  return getConteType.get();
               }
            };
         }
         @Override
         protected String getFileName(){
            return getName.get();
         }
      };
   }
}

とすれば、、

final AJAXDownload download = AJAXDownload.of(out->{
   // out への出力
}, ()->"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ()->"foo.xlsx");
compoment.add(download);
queue(AjaxLink.onClick("download", t->{
   // 出力準備の後で、、
   download.callBackDownload(t);
}).add(download));

OutputStream の close の書き忘れを気にしなくてよい。

AjaxEventBehavior スクロール もラムダで。

Wicket8 ビヘビアもラムダの恩恵が沢山あるだろう。

Wicket 7 で、SerializableConsumer が無かったおかげで例えば、AjaxEventBehavior で スクロールイベントの処理は
以下のようなクラスを用意して、

import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.danekja.java.util.function.serializable.SerializableConsumer;

/**
 * スクロール時の AjaxEventBehavior.
 */
public abstract class AjaxScrollEventBehavior extends AjaxEventBehavior{

   protected abstract void onScroll(AjaxRequestTarget target);

   public AjaxScrollEventBehavior(){
      super("scroll");
   }

   @Override
   protected void onEvent(AjaxRequestTarget target){
      onScroll(target);
   }
}

リストのスクロールに対して

listcontainer.add(new AjaxScrollEventBehavior(){
   @Override
   protected void onScroll(AjaxRequestTarget target){
      // 処理
   }
});

と書いていた。

Wicket8 で SerializableConsumer を使い、この AjaxScrollEventBehavior に、更に以下の
private コンストラクタと、static メソッドを用意すれば、ラムダが使える。

private AjaxScrollEventBehavior(String event){
   super(event);
}

public static AjaxScrollEventBehavior onScroll(SerializableConsumer<AjaxRequestTarget> c){
   return new AjaxScrollEventBehavior("scroll"){
      private static final long serialVersionUID = 1L;
      @Override
      protected void onScroll(AjaxRequestTarget target){
         c.accept(target);
      }
   };
}

たかが、2,3行の削減ではないかと言うかもしれないが、たくさん出現するとやはり、かさ張ってきて見づらくなる。

listcontainer.add(AjaxScrollEventBehavior.onScroll(t->{
    // 処理
});

Wicket7 の native Websocket を使う場合、jQuery UI 使用は注意。

Wicket7 の native Websocket を使うともう1つ、煩わしいことが見つかりました。

Websocket 接続するページのHTMLの head タグの最後に、jQuery 設定として以下のように指定されてしまうのです。

<script type="text/javascript" src="./wicket/resource/org.apache.wicket.resource.JQueryResourceReference/jquery/jquery-1.12.4-ver-1489464145840.js"></script>
<script type="text/javascript" src="./wicket/resource/org.apache.wicket.ajax.AbstractDefaultAjaxBehavior/res/js/wicket-event-jquery-ver-1489464145840.js"></script>
<script type="text/javascript" src="./wicket/resource/org.apache.wicket.ajax.AbstractDefaultAjaxBehavior/res/js/wicket-ajax-jquery-ver-1489464145840.js"></script>
<script type="text/javascript" src="./wicket/resource/org.apache.wicket.protocol.ws.api.WicketWebSocketJQueryResourceReference/res/js/wicket-websocket-jquery-ver-1489464457872.js"></script>
<script type="text/javascript" >
/*<![CDATA[*/
Wicket.Event.add(window, "domready", function(event) { 
;(function(undefined) {
   'use strict';

   if (typeof(Wicket.WebSocket.appName) === "undefined") {
      jQuery.extend(Wicket.WebSocket, { pageId: 28, resourceName: '',
         baseUrl: 'feed?28', contextPath: '/sample', appName: 'WebSocketFilter',
         filterPrefix: '' });
      Wicket.WebSocket.createDefaultConnection();
   }
})();
;
Wicket.Event.publish(Wicket.Event.Topic.AJAX_HANDLERS_BOUND);
;});
/*]]>*/
</script>
</head>

すると、Page クラスコーディング側で、jquery-2.1.4.min.js と jquery-ui-1.11.x.min.js を指定しても
head タグの最後が、上記のようにされてしまっては、任意の jQuery-UI の Widget を実行できない等の障害が起きてしまう。

仕方なく、Page の html で、ルール違反だが body タグの前に、jquery-2.1.4.min.js と jquery-ui-1.11.x.min.js の再読み込みを
記述することで、 任意の jQuery-UI の Widget をできるようにする。

<script type="text/javascript" src="/sample/js/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="/sample/js/jquery-ui-1.11.4.custom.min.js"></script>
<body>

wicket nativeでURLパターンが /wicket/* でなければならない理由は

Wicket7 の native Websocket を使う時、web.xml に設定するWebSocketFilter の URLパターンが /wicket/* でなければならない
理由は、

wicket-native-websocket-core-7.x-.jar の javascriptwicket-websocket-jquery.js で、Websocket 接続のURLプロトコル
以下のように

protocol = document.location.protocol
         .replace('https:', 'wss:')
         .replace('http:', 'ws:');

url = protocol + '//' + document.location.host + WWS.contextPath + WWS.filterPrefix + '/wicket/websocket';

となってるからである。

WebSocketFilter で、リクエストから URL から "/wicket" に該当する部分を割だして、
Websocket の URLを作るように、して欲しいものだ。

oboe2uran.hatenablog.com

Wicket 7 native Websocket の制約?!

Wicket 7 で native Websocket を使ってサーバ PUSH に近いことをしようと作っていて、気持ち悪い制約に気が付いた。

Wicket のサイト web.xml では、たいてい WebFilter として、org.apache.wicket.protocol.http.WebApplication 継承クラスのマッピングには、

   <filter-mapping>
       <filter-name>WebFilter</filter-name>
       <url-pattern>/*</url-pattern>
   </filter-mapping>

と書いてサイトURL以下全て、WebApplication を通すであろう。

Tomcat 7 の環境だと Wicket が用意する org.apache.wicket.protocol.ws.tomcat7.Tomcat7WebSocketFilter を Websocket 用のフィルタとして

   <filter>
      <filter-name>WebSocketFilter</filter-name>
      <filter-class>org.apache.wicket.protocol.ws.tomcat7.Tomcat7WebSocketFilter</filter-class>
      <init-param>
         <param-name>applicationClassName</param-name>
         <param-value>sample.SamplApplication</param-value>
      </init-param>
   </filter>

と書くであろう。
  残念はことに 2017-3-14 現在は、まだ Tomcat 8 用のこの WebSocketFilter はWicket から配布されていない。

filter-mapping にすごくとんでもないルールがあって、url-pattern は、/wicket/ で開始しないと
受信するページで、WebSocketBehavior を登録してもソケット接続コネクションが通知されず結果としてメッセージ受信できない。

   <filter-mapping>
     <filter-name>WebSocketFilter</filter-name>
     <url-pattern>/wicket/*</url-pattern>
   </filter-mapping>

と書かないとならない。
これは、BookmarkablePageLink が生成するリンクのURLと被ってしまう。
BookmarkablePageLink が生成するにのは、
サイト名 + "/wicket/bookmarkable/" + PageクラスPATH
のはずだ。

これが嫌で、filter-mapping で、 /wsocket/* のように書いてしまうと、
接続はされないで受信するページを作れない。

/wicket/* でなければいけないようである。

Wicket 8 が、もっとちゃんと進めばこんな制約はなくなるのだろうか?

Wicket 8 の AjaxButton 、Link など

Wicket 8 になるとこれまでよりもシリアライズ化された Consumer などによりラムダ記述は、
前に投稿した 
Wicket8 の LambdaModel - Oboe吹きプログラマの黙示録 の他に、

AjaxButton 、FeedBackPanel 、Link なども簡潔に書けるようになってくる。

AjaxButton には、

// onSubmit と onError の BiConsumer
onSubmit(String id, SerializableBiConsumer<AjaxButton, AjaxRequestTarget> onSubmit
  , SerializableBiConsumer<AjaxButton, AjaxRequestTarget> onError)

// あるいは、onSubmit だけ
onSubmit(String id, SerializableBiConsumer<AjaxButton, AjaxRequestTarget> onSubmit)

このおかげて、

queue(AjaxButton.onSubmit("submit", (b, t)->{
   /* onSubmit の処理 */
}, (b, t)->{
   /* onError の処理 */
});

// あるいは、
queue(AjaxButton.onSubmit("submit", (b, t)->{
   /* onSubmit の処理 */
});

と書ける。

FeedBackPanel でセットする IFeedbackMessageFilter メッセージも、setFilter は、

boolean feedbackError_flg = false;
final FeedbackPanel feedback = new FeedbackPanel("feedback");
feedback.setOutputMarkupId(true);
feedback.setFilter(e->feedbackError_flg);
queue(feedback);

と書いて

queue(AjaxButton.onSubmit("send", (b, t)->{
  feedbackError_flg = false;

  /* エラーで、feedbackError_flg を true 、error("Error message"); */
  t.add(feedback);
}));

とすることができる。

リンクの onClick も、Link.onClick で、シリアライズConsumerで、

queue(Link.onClick("bklink", k->{
   setResponsePage(HomePage.class);
}));

と書くことができる。