Wicket full size ModalWindow

昔、Wicket の ModalWindow の caption を非表示にする css を書いたり、
Wicketモーダルウィンドウの外観を変更する - Oboe吹きプログラマの黙示録
CSS の記述で、画面いっぱいの ModalWindow 表示を書いたことがありました。
Wicket ModalWindow full screen - Oboe吹きプログラマの黙示録
でも、これだと通常のNormal な表示のModalWindow と併用して、ある時は Normalで表示、別のある時は、
画面いっぱいの ModalWindow ということができません。
そこで、もっと簡単な JavaScript で1つの Page の中で併用できるようにします。

更に、ブラウザウィンドウのサイズを変更した時に追従するようにします。.on("resize"., fuction を設定します。
.on() の前に、.off() するのを忘れると、caption 有りと無し両方のフルサイズ2通りを1つのページで
実行すると、片方ができなくなります。

var _adjust_full_nocaption =  function(){
   $(".wicket-modal").attr("style", "top:0;left:0;width:100%;position:fixed;visibility:visible;"),
   $(".w_content_container").attr("style", "overflow:auto;height:" + window.innerHeight + "px;");
   $("div.w_caption").attr("style", "display:none;"),
   $("div.w_top_1").attr("style", "display:none;"),
   $("div.w_bottom_1").attr("style", "display:none;"),
   $("div.w_right_1").attr("style", "margin:0;"),
   $("div.w_content_1").attr("style", "margin:0;");
}
var _adjust_ful_captionON =  function(){
   $(".wicket-modal").attr("style", "top:0;left:0;width:100%;position:fixed;visibility:visible;"),
   $(".w_content_container").attr("style", "overflow:auto;height:"+ (window.innerHeight - 10) + "px;");
}
/* caption 無し;ハンドル無し、フル表示 */
var adjustFull = function(msec){
   setTimeout("_adjust_full_nocaption()", msec);
   $(window).off('resize');
   $(window).on('resize', function(){
      _adjust_full_nocaption();
   });
};

/* caption ハンドル有り、フル表示 */
var adjustFullHandle = function(msec){
   setTimeout("_adjust_ful_captionON()", msec);
   $(window).off('resize');
   $(window).on('resize', function(){
      _adjust_ful_captionON();
   });
};

このJavaScript を、ModalWindow 表示時に、AjaxRequestTarget の appendJavaScript で実行するのです。

final ModalWindow window = new ModalWindow("modalwindow").setResizable(false);
//
AjaxRequestTarget target;
//
window.show(target);
target.appendJavaScript("adjustFull(100)");

少し不安なのは、height の計算です。

$(".w_content_container").attr("style", "overflow:auto;height:"
 + (window.innerHeight
     - $(".w_caption").height()
    - $(".w_bottom_1").height() - $(".w_top_1").height() - 6) 
 + "px;");

としなければならないか、実践は調整しなくてはならないかもしれません。

style のセットを jQuery の prop でなくて、attr にしているのは、IE11対策です。

jQuery ui Datepicker 年月プルダウンにした時の調整

Datepicker は jQuery ui 以外にも沢山あるのでまだ、jQuery ui を使うなんてという批判はさておき、
使い慣れてもいるので。。。
年月セレクタをプルダウンにした時、、
jquery-ui-1.12.1.min.js
と、

<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1/i18n/jquery.ui.datepicker-ja.min.js"></script>

で、以下のようにdatepicker を設定して表示した時、、、

$.datepicker.setDefaults($.datepicker.regional["ja"]);
$("#datepicker").datepicker({
   prevText:"前月", nextText:"翌月",
   changeMonth: true,
   changeYear: true, yearRange: '-5:+4',
   firstDay: 1,
});

このままだと以下のように、年と月のプルダウンの僅かに配置がずれてしまいます。
f:id:posturan:20181030215730j:plain

このちょっとのズレをなくす方法が昔はわかりませんでした。
でも、近年のCSSは、flex-box があります。
ui-datepicker-title クラスに、display: flex を指定して 
justify-content : center もしくは、space-around などを指定します。

.ui-datepicker-title{
   display: flex;
   justify-content: center;
   -webkit-justify-content: center;
}
.ui-datepicker select.ui-datepicker-year{
    margin-right: 0.1rem;
    width: auto;
}
.ui-datepicker select.ui-datepicker-month{
    margin-left: 0.3rem;
    width: auto;
}
.ui-datepicker{
	font-size: 80%;
}

これで、無事、年月プルダウンが揃って配置されます。
f:id:posturan:20181030220455j:plain

justify-content: center; を justify-content: space-around; にした場合は、、、
f:id:posturan:20181030220548j:plain

となります。個人的には、center の方が好みです。

Wicket Tomcat の background process が PageStoreManager で NullPointerException

まだプロジェクト開発中で、EclipseWTP 上で、 Tomcat 9.0.10 , Wicket 8.1.0 で実験してるのですが、
起動後、Tomcat の バックグラウンドプロセスが実行する Wicket PageStoreManager
ページセッションクリアで NullPointerException が発生する。

10月 27, 2018 21:48:48 午前 org.apache.catalina.core.StandardContext backgroundProcess
警告: Exception processing manager [StandardManager[StandardEngine[Catalina].StandardHost[localhost].StandardContext[/labo]] background process
java.lang.NullPointerException
at org.apache.wicket.page.PageStoreManager$SessionEntry.clear(PageStoreManager.java:364)
at org.apache.wicket.page.PageStoreManager$SessionEntry.valueUnbound(PageStoreManager.java:353)
at org.apache.catalina.session.StandardSession.removeAttributeInternal(StandardSession.java:1784)
at org.apache.catalina.session.StandardSession.expire(StandardSession.java:856)
at org.apache.catalina.session.StandardSession.isValid(StandardSession.java:659)
at org.apache.catalina.session.ManagerBase.processExpires(ManagerBase.java:573)
at org.apache.catalina.session.ManagerBase.backgroundProcess(ManagerBase.java:558)
at org.apache.catalina.core.StandardContext.backgroundProcess(StandardContext.java:5456)
at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1396)
at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1400)
at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1400)
at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1368)
at java.base/java.lang.Thread.run(Thread.java:834)

起動時とか、仕方ないタイミングがあるんだろうけど、
その後、害がなくてもこれら警告メッセージがあるだけで客は嫌がる。
また、これは必ずいつも発生するというものでもなく、PCが違うと発生数が違う。

しかたないので、Tomcat 側を調整してみる。
Apache Tomcat 9 Configuration Reference (9.0.12) - The Engine Container
に、server.xml Engineタグの設定説明があります。

backgroundProcessメソッドの呼び出しの遅延時間(秒)の設定でデフォルト10秒で
負の値でなければバックグラウンドプロセス呼出しスレッドが生成されないとのことで、
server.xml の Engineタグの属性を設定します。

server.xml の Engineタグ デフォルトが以下になっている

<Engine defaultHost="localhost" name="Catalina">

の部分を

<Engine defaultHost="localhost" name="Catalina" backgroundProcessorDelay="-1">

としてみました。
これで、しばらく様子を見てみようと思います。

Wicket 8 以降では ClientProperties は使わないように。。

ツィッターで気づいたのですが、、
クライアントの情報を、WebSession経由で取得する
org.apache.wicket.protocol.http.ClientProperties
もう取得しない方が良い。


読んでみると、
Wicket 8 ClientProperties some Methods deprecated - Stack Overflow

これから、ガイドに書くということ、、こんなの気づかない。。。

ClientProperties cprop = WebSession.get().getClientInfo().getProperties();

これは、将来使えなくなる。。

以下を使うのが、お奨めということなので、
GitHub - nielsbasjes/yauaa: Yet Another UserAgent Analyzer

ドキュメントのとおりに、、
Basic usage · Yauaa: Yet Another UserAgent Analyzer

WebRequest を取得して→getHeader で User-Agent の文字列を取得して parse すると、

UserAgentAnalyzer uaa = UserAgentAnalyzer
            .newBuilder()
            .hideMatcherLoadStats()
            .withCache(10000)
            .build();
UserAgent ua = uaa.parse(((WebRequest)getRequest()).getHeader("User-Agent"));
ua.getAvailableFieldNamesSorted().stream().forEach(key->{
	System.out.println("[" + key +"] = [" + ua.getValue(key) + "]");
});

これで取得できるのは、沢山あるので一部だけ結果を紹介すると、

[DeviceClass] = [Desktop]
[DeviceName] = [Desktop]
[DeviceBrand] = [Unknown]	
[DeviceCpu] = [Intel x86_64]
[DeviceCpuBits] = [64]
[OperatingSystemClass] = [Desktop]
[OperatingSystemName] = [Windows NT]
[OperatingSystemVersion] = [7]
[OperatingSystemNameVersion] = [Windows 7]
[AgentClass] = [Browser]
[AgentName] = [Chrome]
[AgentVersion] = [69.0.3497.100]

しかし、この parse ははっきり言って遅い!

ZIP の脆弱性??Zip Slipディレクトリトラバーサル脆弱性

私のプロジェクトにも、Apache commons-compress を使ってるせいなのか、
Git-Hub から通知が来てしまいました。→ yipuran-compress

We found a potential security vulnerability in one of your dependencies.
Only the owner of this repository can see this message. 
Manage your notification settings or learn more about vulnerability alerts.

Apache commons-compress が結構たくさん他のライブラリに依存があるので、
元々、圧縮に関するものは、 yipuran-core から切り離し、yipuran-compress にあります。
とりあえず、 yipuran-compress の pom.xml だけ修正しました。
Git-Hub から上の通知だけで、、だから、何の脆弱性なのかはっきり書かれてないので、
想定するしかなく、、、
https://www.infoq.com/jp/news/2018/06/zip-slip
この脆弱性かな?としか思い当らなかった。。。

とにかく、yipuran-compress の pom.xml を修正した。

commons-compress 
version 1.14→ 1.18

Wicket で、 Confirm の ModalWindow

前の投稿、Wicket メッセージ表示するだけのModalWindow - Oboe吹きプログラマの黙示録 に続いて、
Confirm を表示するもの。
Yes-No 、 OK-cancel これらどちらを左ー右にするか、AppleMicrosoft . Android  逆であるので
導入するシステムの納品先しだいになるので、left - right として指定するコードにしてある。

import java.util.Optional;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.core.util.string.JavaScriptUtils;
import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.resource.CssResourceReference;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.yipuran.wicketcustom.function.SerialThrowableConsumer;
/**
 * 確認メッセージモーダルウィンドウ.
 */
public class ConfirmModalPanel extends Panel{
   /**
    * コンストラクタ.
    * @param id Wicket-ID
    * @param model 確認メッセージを格納したModel
    * @param left 左側ボタンラベル
    * @param right 右側ボタンラベル
    * @param leftConsumer 左側ボタンクリックで実行する処理 AjaxRequestTarget のシリアライズ化した Throwable な Consumer
    * @param rightConsumer  右側ボタンクリックで実行する処理 AjaxRequestTarget のシリアライズ化した Throwable な Consumer
    */
   public ConfirmModalPanel(String id, IModel<String> model, final String left, final String right
   , final SerialThrowableConsumer<AjaxRequestTarget> leftConsumer
   , final SerialThrowableConsumer<AjaxRequestTarget> rightConsumer){
      super(id, model);
      queue(new Label("message", Optional.ofNullable(model.getObject()).orElse("")));
      queue(new Form<Void>("form"));

      queue(new AjaxButton("left"){
         @Override
         public void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag){
            replaceComponentTagBody(markupStream, openTag, left);
         }
         @Override
         protected void onSubmit(AjaxRequestTarget target){
            leftConsumer.accept(target);
            ModalWindow.closeCurrent(target);
         }
      });
      queue(new AjaxButton("right"){
         @Override
         public void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag){
            replaceComponentTagBody(markupStream, openTag, right);
         }
         @Override
         protected void onSubmit(AjaxRequestTarget target){
            rightConsumer.accept(target);
            ModalWindow.closeCurrent(target);
         }
      });
   }
   @Override
   protected void onAfterRender(){
      super.onAfterRender();
      JavaScriptUtils.writeJavaScript(getResponse(), "setTimeout('sizefitConfirmModal();', 100);" );
   }
   @Override
   public void renderHead(IHeaderResponse response){
      super.renderHead(response);
      response.render(CssHeaderItem.forReference(
         new CssResourceReference(ConfirmModalPanel.class, "confirm-modal.css")));
      response.render(JavaScriptHeaderItem.forReference(
         new JavaScriptResourceReference(ConfirmModalPanel.class, "confirm-modal.js")));
   }
}

HTML ConfirmModalPanel.html

<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<body>
   <wicket:panel>
      <form wicket:id="form">
         <section class="comfirm-panel">
            <div class="comfirm-content">
               <ul>
                  <li><span wicket:id="message">メッセージ</span></li>
                  <li>
                     <table>
                        <tbody>
                           <tr>
                              <td>
                                 <button wicket:id="left" type="button">Left</button>
                              </td>
                              <td>
                                 <button wicket:id="right" type="button">Right</button>
                              </td>
                           </tr>
                        </tbody>
                     </table>
                  </li>
               </ul>
            </div>
         </section>
      </form>
   </wicket:panel>
</body>
</html>


confirm-modal.css

@charset "UTF-8";
/**
 * confirm-modal.css
 */
.comfirm-panel{
   display: flex;
   align-items: center;
   justify-content: center;
}
.comfirm-content{
   width: auto;
   display: flex;
   align-items: center;
   justify-content: space-around;
}
.comfirm-content ul{
   margin: 0;
   padding: 0;
}
.comfirm-content li{
   list-style-type: none;
   white-space: nowrap;
   margin: 1rem;
   display: flex;
   align-items: center;
   justify-content: space-around;
}
.comfirm-content table{
   border-collapse: separate;
   border-spacing: 0.4rem;
}
.comfirm-content td{
   padding: 0 2rem;
}
.comfirm-content button{
   min-width: 3rem;
   white-space: nowrap;
}

confirm-modal.js

var sizefitConfirmModal = function(){
   $('.w_content_container').css("height", $('.comfirm-content ul').outerHeight(true) + "px" );
   $('.wicket-modal').css("width", $('.comfirm-content ul').outerWidth(true) + 22 + "px" );
};


使用する Java コード

ModalWindow confirmWindow = new ModalWindow("confirmWindow").setResizable(false).setAutoSize(true);
confirmWindow.setWindowClosedCallback(new WindowClosedCallback(){
   @Override
   public void onClose(AjaxRequestTarget target){
      // CLOSE された場合に必ず実行される。場合によってここで処理を書く
   }
});
queue(confirmWindow);

queue(new Button("_send").add(AjaxFormSubmitBehavior.onSubmit("click", target->{
   Optional.ofNullable(item_Field.getModelObject()).ifPresent(e->{
      String msg = e + " を削除してよろしいですか?";
      confirmWindow.setContent(new ConfirmModalPanel(confirmWindow.getContentId()
      , Model.of(msg), "Yes", "No",
         t->{
            // 左(Yes)をクリックした時の処理
         },
         t->{
            // 右(No)をクリックした時の処理
         }
      ));
      confirmWindow.show(target);
   });
})));

Wicket メッセージ表示するだけのModalWindow

Wicket ModalWindow 単純なものを普遍的にしようとするとあらゆる場面を思い浮かべてしまい逆に難しい。
メッセージ表示だけあるいは、confirm の ModalWindow 、使い回せるものを考えた。
以前、confirm は考えて、yipuran-wicketcustom にも入れたがあれはダメだ、Java8 になる前に考えたもので使いにくい。
今回は、単純なメッセージ表示だけの ModalWindow

ModalWindow として生成するための Wicket-Panel の Javaコード

import java.util.Optional;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
import org.apache.wicket.core.util.string.JavaScriptUtils;
import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.resource.CssResourceReference;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.danekja.java.util.function.serializable.SerializableConsumer;
import org.yipuran.wicketcustom.function.SerialThrowableConsumer;
/**
 * MessageModalPanel
 */
public class MessageModalPanel extends Panel{

   /**
    * コンストラクタ.
    * @param id content Wicket-ID
    * @param model メッセージ格納 Model
    * @param SerializableConsumer モーダルコンテンツの CLOSEボタン実行時に実行する Consumer
    */
   public MessageModalPanel(String id, IModel<String> model, SerializableConsumer<AjaxRequestTarget> consumer){
      super(id, model);
      queue(new Form<Void>("form"));
      queue(new Label("message", Optional.ofNullable(model.getObject()).orElse("")));
      queue(new Button("close").add(AjaxFormSubmitBehavior.onSubmit("click", SerialThrowableConsumer.of(t->{
         consumer.accept(t);
         ModalWindow.closeCurrent(t);
      }))));
   }
   @Override
   protected void onAfterRender(){
      super.onAfterRender();
      JavaScriptUtils.writeJavaScript(getResponse(), "setTimeout('sizefitMessageModal();', 100);" );
   }
   @Override
   public void renderHead(IHeaderResponse response){
      super.renderHead(response);
      response.render(CssHeaderItem.forReference(
         new CssResourceReference(MessageModalPanel.class, "message-modal.css")));
      response.render(JavaScriptHeaderItem.forReference(
         new JavaScriptResourceReference(MessageModalPanel.class, "message-modal.js")));
   }
}

これのHTML MessageModalPanel.html

<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<body>
   <wicket:panel>
      <form wicket:id="form" action="post">
         <section class="modal-panel">
            <div class="modal-panel-content">
               <ul>
                  <li><span wicket:id="message">メッセージ</span></li>
                  <li><button wicket:id="close" type="button">CLOSE</button></li>
               </ul>
            </div>
         </section>
      </form>
   </wicket:panel>
</body>
</html>

CSSソース:message-modal.css

@charset "UTF-8";
/**
 * message-modal.css
 */
.modal-panel{
   display: flex;
   align-items: center;
   justify-content: center;
}
.modal-panel-content{
   width: auto;
   display: flex;
   align-items: center;
   justify-content: space-around;
}
.modal-panel-content li{
   list-style-type: none;
   white-space: nowrap;
   margin: 1rem;
   display: flex;
   align-items: center;
   justify-content: space-around;
}

サイズ調整している message-modal.js

var sizefitMessageModal = function(){
   $('.w_content_container').css("height", $('.modal-panel-content ul').outerHeight(true) + "px" );
   $('.wicket-modal').css("width", $('.modal-panel-content ul').outerWidth(true) + 22 + "px" );
};

CSS も JS も、Panel の renderHead で読み込ませる。

使用側 Javaコード

final ModalWindow window = new ModalWindow("message_window").setResizable(true).setAutoSize(true);
queue(window);
queue(new Button("view").add(AjaxEventBehavior.onEvent("click", target->{
    window.setContent(new MessageModalPanel(window.getContentId(), Model.of("Message"), t->{
         // close action
    }));
    window.show(target);
})));