Uncaught TypeError: Cannot read property 'step' of undefined

jQuery 2.x → 3.3.1 upgrade した時、jquery-ui エラー
Uncaught TypeError: Cannot read property 'step' of undefined
StackoverFlow に書いてあるように、
https://stackoverflow.com/questions/45356723/jquery-ui-cannot-read-property-step-of-undefined

jQuery slim バージョン(jquery-3.3.1.slim.min.js)を使うと
jquery-ui 内部で重要ないくつかの機能 function が削除されてしまうとのこと、、

こうすべきだ。(CDN使用なら)

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet"/>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>

ブラウザへのファイルドロップ禁止する

JavaScript で抑制します。

window.addEventListener('dragover', function(ev){
     ev.preventDefault();
}, false);
window.addEventListener('drop', function(ev){
     ev.preventDefault();
     ev.stopPropagation();
}, false);

これで、マウスでPCのローカルファイルをドラッグしてブラウザにドロップしても
ドロップしたファイルをブラウザが開こうとするのを回避できます。

では、この JavaScript を実行するけど、HTML のある任意の範囲部分へのファイル・ドラッグ&ドロップは
有効にしたい場合、どうするか??

心配したのですが、先日投稿した Wicket の AjaxFileDropBehavior を使用した場合、
ドロップしてファイル読み込まなくなってしまうのではと心配したのですが、、
大丈夫でした。上記 JavaScript を動かして、ドロップさせたい範囲エリアを限定させることができます。
oboe2uran.hatenablog.com

Wicket のファイルアップロード、じつはドラッグ操作に対応していた。

システム要件で意外とファイルアップロードの要件は少ないもので、今まで気づかなかったのですが、
ドラッグ操作でファイルアップロードする(処理)振る舞いを Wicket-extension は、すでにあったのですね。
wicket-extensions-8.0.0.jar の中にありますね。
今でもそうなのか、<input> タグ type="file" formタグを、method="post" enctype="multipart/form-data" なんて書いて
殆どが、実装しているのでは?

でも、Wicket-extension では、そんなこと書かなくても、
form タグは、<form wicket:id="form"> のままの記述で、<input> タグ type="file" も書かずに、
適当にページデザインで、ドラッグ&ドロップする場所作って、
AjaxFileDropBehavior というビヘビアを書けば、
ファイルをマウスでドラッグ操作すると、勝手にファイルアップロード実行して、サーバサイドで読み込めます。
(サンプル)
まず、これは無くてもいいんですが、現在のファイルアップロード状況をページに表示するテキスト宣言を
グローバル変数 public で持ちます。。

public String filelistText = "";

ファイルアップロード状況のラベルです。グローバル変数 で filelistText を宣言したので、LambdaModel で書きます。
setOutputMarkupId で true にして後から結果を反映させます。

/* ファイルアップロード状態テキスト */
Label filelistTextLabel 
= new Label("uploadfiles_list", LambdaModel.of(()->filelistText, t->{ filelistText = t; }));
filelistTextLabel.setEscapeModelStrings(false);
filelistTextLabel.setOutputMarkupId(true);
queue(filelistTextLabel);

div タグなど、適当スペースのタグにAjaxFileDropBehavior を割り当てます。読み込んでサイズを認識してます。

WebMarkupContainer droparea = new WebMarkupContainer("droparea");
droparea.add(new AjaxFileDropBehavior(){
   protected void onFileUpload(AjaxRequestTarget target, List<FileUpload> files){
      Optional.ofNullable(files).ifPresent(fu->{
         StringBuilder sb = new StringBuilder();
         sb.append("<ul>");
         fu.stream().forEach(file->{
            sb.append("<li>");
            sb.append(file.getClientFileName());
            sb.append("  Size: ");
            sb.append(Bytes.bytes(file.getSize()).toString());
            sb.append(" byte");
            sb.append("</li>");
         });
         sb.append("</ul>");
         filelistText = sb.toString();
         target.add(filelistTextLabel);
      });
   }
   @Override
   protected void onError(AjaxRequestTarget target, FileUploadException fux)   {
      logger.debug(fux.getMessage());
   }
});
queue(droparea);

これだけです。
Wicket は基本ステートフルなので、あとは煮るなり焼くなり好きにします。

さらに、以下のとおり誤った操作でドロップした時の対処も必要、
oboe2uran.hatenablog.com

リンクをクリックでシリアライズ化した Consumer ラムダを実行

Wicket の 簡単な記述で実現してみたくなりました。
Wicket 8 を使います。

org.apache.wicket.markup.html.link.Link を継承したクラスで作ります。
どんな名称にしたら良いか悩みましたが、センス無いとは思いましたが、
もういっそのこと、そのまんま、 Click ... Link ... bind というキーワードで以下を作成

package org.yipuran.wicketcustom;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.util.lang.Args;
import org.danekja.java.util.function.serializable.SerializableConsumer;

/**
 * リンク click → シリアライズ化した Consumer の実行.
 */
public abstract class ClickLink<T> extends Link<T>{

   /**
    * private コンストラクタ.
    * @param コンポーネントID
    */
   private ClickLink(String id){
      super(id);
   }
   /**
    * コンポーネントID→シリアライズ Consumer 設定コンポーネント生成.
    * @param id component ID
    * @param onClick SerializableConsumer<String> parameter is component ID
    * @return ClickLink
    */
   public static <T> ClickLink<T> bind(String id, SerializableConsumer<String> onClick){
      Args.notNull(onClick, "onClick");
      return new ClickLink<T>(id){
         private static final long serialVersionUID = 1L;
         @Override
         public void onClick(){
            onClick.accept(id);
         }
      };
   }
}

使い方のサンプル、、、

<a wicket:id="back" href="#">HomePage にもどる</a>

に対して、、

    queue(ClickLink.bind("back", t->setResponsePage(HomePage.class)));

Wicket の日付時刻入力フィールド

Wicket 日付時刻入力フィールドは、昔から wicket-extension に DateTextField があるが
今の時代の LocalDate , LocalDateTime には対応してなく、書式や手数も多く使いづらい。
Wicket 以外の DatePicker を使うことが多く、TextField<Strring> を使う方がバグも少なかった。

LocalDate、LocalTime は、TextField<Strring> で、型クラス指定すれば、
ModelObject もその型で受け取れる

日付入力、書式、「yyyy/MM/dd」で以下が可能

TextField<LocalDate> dateField = new TextField<>("date", Model.of(LocalDate.now()), LocalDate.class);

時分入力、書式、「HH:mm」で以下が可能

TextField<LocalTime> timeField = new TextField<>("time", Model.of(LocalTime.now()), LocalTime.class);

Uncaught SyntaxError: Unexpected token Error

Wicket8 の Web を書いていてこれに遭遇。
Uncaught SyntaxError: Unexpected token <
Webコンテキスト直下に js ディレクトリ、jquery-3.3.1.min.js を置いて
単純に、wicket:head タグで、

<wicket:head>
<script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
</wicket:head>

と書いただけなのに、、凄く悩んだ、
原因は、WebApplication の init() に書いた、、

getRootRequestMapperAsCompound().add(new NoVersionMapper("/", HomePage.class));

どうもこれが悪さをするみたい。
URLにページ番号を付けない ようにと思って書いたのだけど、
先頭を対象にした "/" にこれを書いてしまうと、、src="js/jquery-3.3.1.min.js" で指定している
ものは、ブラウザで直接URL入力すると解るが、JSは読み込まず、web.xml で書いた
フィルタ

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

ここから、強制的に WebApplication で指定する getHomePage() が効いてしまう。。
でも、ブラウザのデバッガは、js/jquery-3.3.1.min.js に対して HTTP sstatis = 200
である。
そして、訳のわからぬ→ Uncaught SyntaxError: Unexpected token <
"/" に対する NoVersionMapper の設定を廃止すれば、問題なくなる!

文字列のHEXダンプを作る

Apache の commons io の HEXダンプ、
org.apache.commons.io.HexDump の dump(byte[] data, long offset, OutputStream stream, int index)
結果は、左端に offset のサイズ目盛があって良いのだけれど、
以下のように少し横幅が長く、HEXの表示部と文字列部の区分が見にくい思う時がある。

00000000 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 30 123456789ABCDEF0
00000010 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 30 123456789ABCDEF0
00000020 31 32 33 34 35 36                               123456

offset のサイズ目盛を省いて

31323334 35363738 39414243 44454630  [123456789ABCDEF0]
31323334 35363738 39414243 44454630  [123456789ABCDEF0]
31323334 3536                        [123456]

このように結果を出して欲しいのだ。
2バイト文字の表示は諦めて、どうせならと、、自前で作る。
スピードを考えて原始的なところから自作する。
まず必要なのは、、任意の長さバイトで括るイテレータで以下を用意

import java.io.Serializable;
import java.util.Iterator;
/**
 * Hexbyterator.
 */
public class Hexbyterator implements Iterator<byte[]>, Serializable{
   private byte[] b;
   private boolean next;
   private long seek = 0;
   private int blength = 16;

   private Hexbyterator(byte[] b, int len){
      if (len < 1) new IllegalArgumentException("pack length must be greater than 0");
      this.b = b;
      blength = len;
      next = b != null && b.length > 0 ? true : false;
   }
   public static Hexbyterator of(byte[] bytes, int len){
      return new Hexbyterator(bytes, len);
   }
   @Override
   public boolean hasNext(){
      return next;
   }
   @Override
   public byte[] next(){
      if (next){
         long n = seek + blength;
         if ( n < b.length ){
            byte[] r = new byte[blength];
            for(int i=0;i < blength;i++){
               r[i] = b[Long.valueOf(seek).intValue()];
               seek++;
            }
            next = seek < b.length;
            return r;
         }
         byte[] r = new byte[ Long.valueOf(blength - (n - b.length)).intValue() ];
         for(int i=0;i < r.length;i++){
            r[i] = b[Long.valueOf(seek).intValue()];
            seek++;
         }
         next = false;
         return r;
      }
      return null;
   }
}

これを使用して、対象文字列からダンプ出力するクラス

import java.io.ByteArrayInputStream;
import java.util.List;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
 * 16バイト単位で纏めてHEXダンプした結果テキストの Stream または List を生成する。
 * UTF-8 での HEXダンプである。
 * HEX表現ダンプとと2バイト文字以外を文字列表現でダンプする。
 * 2バイト文字は、1byte を '?'
 * 制御文字は、1 byte を '.' 文字
 */
public final class DumpHex{
   private String str;
   private DumpHex(String str){
      this.str = str;
   }
   /**
    * インスタンス生成
    * @param str ダンプ対象文字列
    * @return DumpHex
    */
   public static DumpHex of(String str){
      return new DumpHex(str);
   }
   /**
    * @return 16バイト単位ダンプ結果文字列のストリームを返す
    */
   public Stream<String> stream(){
      byte[] bs = str.getBytes();
      return StreamSupport.stream(Spliterators.spliteratorUnknownSize(Hexbyterator.of(bs, 16), 0), false)
      .map(b->{
         StringBuilder sb = new StringBuilder();
         for(int x=0;x < b.length;x++){
            if (x > 0 && x % 4 == 0) sb.append(" ");
            sb.append(String.format("%02x", b[x]));
         }
         String hexstring = sb.toString();
         if (35 > hexstring.length()){
            hexstring += "                                  ".substring(0, 35 - hexstring.length());
         }
         for(int n=0;n < b.length;n++){
            if (b[n] < 0){
               b[n] = 0x3f;
            }else if(b[n] < 0x20){
               b[n] = 0x2e;
            }
         }
         String ss = new String(b);
         return hexstring + "  [" + ss + "]";
      });
   }
   /**
    * @return 16バイト単位ダンプ結果文字列のリストを返す
    */
   public List<String> list(){
      return stream().collect(Collectors.toList());
   }
   /**
     * @return HEX表現だけのダンプ結果を区切り文字なしで文字列で返す。
    */
   public String hexstring(){
      return hexstring("");
   }
   /**
     * @return HEX表現だけのダンプ結果を1バイトずつの区切り文字を指定して文字列で返す。
    */
   public String hexstring(String splitstring){
      ByteArrayInputStream inst = new ByteArrayInputStream(str.getBytes());
      return Stream.generate(inst::read).limit(inst.available())
      .map(e->String.format("%02x", e)).collect(Collectors.joining(splitstring));
   }
}

使い方の例、、、
ロガーで出したい!!

Logger logger = LoggerFactory.getLogger(this.getClass());
DumpHex dump = DumpHex.of(string);
dump.stream().forEach(d->{
      logger.debug( d );
});

org.apache.commons.io.HexDump の dump メソッドを呼ぶ出すような
面倒くさい引数を渡す必要もない。