jsTree のクリックイベント捕捉の書き方

Webページに Tree 描画させるのに、Wicket が提供しているものは、Java Object で要素&ツリー体系を作らなくてはならず、
これは手間もかかって非常に辛い。巨大なツリーで children が大量の時、結果表示スピードも心配だ。
だから Wicket の Tree 機能は使ってない。

JSON → ツリー描画 させる jQuery が世の中にはたくさんある。MIT ライセンスでそこそこ簡単に使えそうなもので
jsTree : https://www.jstree.com/
GitHub - vakata/jstree: jquery tree plugin

この jsTree でツリーのノードをクリック=選択した時の捕捉は、
 select_node.jstree か、changed.jstree イベントを捕捉する
サンプルで書くと、、

<div id="tree1" class="demo"></div>

select_node.jstree と、changed.jstree を on でバインド

$.jstree.defaults.core.themes.variant = "large";
$.jstree.defaults.core.themes.responsive = true;

$('#tree1').jstree({ 'core':{
   'data':[
      {   "id":1, "text":"Root node",
         "children":[
            {   "id":2, "text":"Child node 1",
               "children":[
                  { "id":3, "text":"aaa" },
                  { "id":4, "text":"bbb" }
               ],
            },
            { "id":5, "text":"Child node 2" }
         ]
      },
      {   "id":6, "text":"Root node2",
         "children":[
            { "id":7, "text":"Child node 1" },
            { "id":8, "text":"Child node 2" }
         ]
      }
   ]
}})
.on("select_node.jstree", function(e, data){
   console.log("selected is : id =" + data.node.id +"  "+ data.node.text);
})
.on("changed.jstree", function(e, data){
    console.log("changed is : id =" + data.node.id +"  "+ data.node.text);
});

もう1つ捕捉の書き方、jQuery の原始的な捕捉と書き方で、、

var tree = $('#tree1').jstree({ 'core':{
   'data':[
      {   "id":1, "text":"Root node",
         "children":[
            {   "id":2, "text":"Child node 1",
               "children":[
                  { "id":3, "text":"aaa" },
                  { "id":4, "text":"bbb" }
               ],
            },
            { "id":5, "text":"Child node 2" }
         ]
      },
      {   "id":6, "text":"Root node2",
         "children":[
            { "id":7, "text":"Child node 1" },
            { "id":8, "text":"Child node 2" }
         ]
      }
   ]
}});
tree.on('click', '.jstree-anchor', function(e){
   var id = $(this).parent().attr('id');
   var text = $(this)[0].text;
   console.log("id = " + id + "  text = " + text);
});

この書き方、$(this).parent().attr('id') で、IDの参照
$(this)[0].text で、ノードのテキストを参照
をマスタしておいた方が、ダブルクリック等の他のイベント捕捉時を書くときに役立つ。

Tabulator のカスタムフィルタ

Tabulator のチェック値のフィルタは、標準ではONを抽出するフィルタである。
サンプル

$('#example-table').tabulator({
   layout:"fitDataFill",
   columns:[
      { title:"名前",  field:"name",    sortable:true, headerFilter:true, headerFilterPlaceholder:"名前フィルタ..." } ,
      { title:"Point", field:"point",   align:"right", sortable:true, },
      { title:"登録日", field:"rdate",  sortable:true, sorter:"date" },
      { title:"Active", field:"active", align:"center", formatter: "tickCross", headerFilter: true, width: 80, headerSort: false },
   ],
});
var tabledata = [
   {id:1, name:"あああ", point:12,  rdate:"",           active: 1 },
   {id:2, name:"いいい", point:0,   rdate:"1982/05/14", active: 0 },
   {id:3, name:"ううう", point:42,  rdate:"1982/05/22", active: 1 },
   {id:4, name:"ABCDE-1234567890", point:1125, rdate:"1980/04/01", active: 0 },
   {id:5, name:"12345", point:16,   rdate:"1999/01/31", active: 1 },
];
$("#example-table").tabulator("setData", tabledata);

f:id:posturan:20180921215052j:plain
チェックをすると、
f:id:posturan:20180921215119j:plain
一般的なユーザインターフェースとしてあり得ないと思うが、
敢えて、OFFのフィルタにしたい場合、
f:id:posturan:20180921215302j:plain
と、したい場合、、どうするか??
行の定義で、headerFilterFunc という属性キーで各行に対する
対象とするか否か、true/false を返す function を設定する。

function customHeaderFilter(headerValue, rowValue, rowData, filterParams){
    return  trueを返せば対象、
}

この第1引数、the value of the header filter element という説明だが、実際、全て true が入ってくる。
今回のサンプルの場合、4列目の定義を

{ title:"Active", field:"active", align:"center", formatter: "tickCross", headerFilter: true, width: 80, headerSort: false,
   headerFilterFunc : function(headerValue, rowValue, rowData, filterParams){
      return rowValue==0
   }
},

とすれば、チェックOFFのフィルタになる。
rowData で1行のデータを参照できるので、

     return rowData.active==0

でも同じこと。return rowValue==1 あるいは、return rowData.active==1 なら、この headerFilterFunc を
追記しないのと同じことである。

try~with~resources リソース解放されない

今更ではあるが、try~with~resources 文が便利で、ついうっかりこう書いてしまう。

File file = ....;

try(PrintWriter pw = new PrintWriter(new BufferedWriter(
                                     new OutputStreamWriter(
                                     new FileOutputStream(file, file.exists()), "MS932")))){

}

これには罠があって、try() 文の中のコンストラクタでエラーが起こると、
リソース解放されないまま終わってしまう。

try( FileOutputStream out = new FileOutputStream(file, file.exists());
     OutputStreamWriter ow =  new OutputStreamWriter(out, "MS932");
     BufferedWriter bw = new BufferedWriter(ow);
     PrintWriter pw = new PrintWriter(bw)
){

}

長くなって嫌でもリスク回避ではこうすべきなのだ。

mongodb任意のコレクションの列の値をPythonでリストにする

mongodb に、"service" なるコレクションがあったとします。
”type"という列の値のリスト抽出して、‘’空文字と合わせたリストを作ります。
”type"列が存在しないレコードもあることを考慮します。
・・・というお題。

import pymongo

def open_database(dbname):
    client = pymongo.MongoClient('mongodb://xxx.xxx.xxx.xxx', 27017)
    db = client[dbname]
    return db
db = open_database('sampledb')

types = ['']
for t in set((r['type'] for r in db['service'].find({'type':{'$exists':'true'}},{'type':1}))):
    types.append(t)

''空文字 と、MongoDB の serviceコレクションの 'type' のリストとして、types を生成

厳密にソートするなら、

types = ['']
for t in sorted(set((r['type'] for r in db['service'].find({'type':{'$exists':'true'}},{'type':1}))), reverse=False):
    types.append(t)

ソート逆順

types = ['']
for t in sorted(set((r['type'] for r in db['service'].find({'type':{'$exists':'true'}},{'type':1}))), reverse=True):
    types.append(t)

長いので、こう書いてもOK

types = ['']
for t in sorted(
        set((r['type'] for r in db['service'].find({'type': {'$exists': 'true'}}, {'type': 1}))),
        reverse=True):
    types.append(t)

Tabulator ウィンドウの高さに合わせる

Tabulator は、レスポンシブだが、ウィンドウの高さに合わせた表の高さ height までは
調整してくれない。
自前でやるしかないのか。。
サンプル

$('#example-table').tabulator({
   height: window.innerHeight - 70,
   layout:"fitDataFill",
   columns:[
      // 行データ 
   ],
});

$(window).on('load resize', function(){
    $('#example-table').prop('style', "height: " + (window.innerHeight - 70) + "px");
});

Tabulator 右clickコンテキストは、stopPropagationでなくてpreventDefault

jQuery のイベント伝播の制御は、、
preventDefault() = 対象の要素のイベントをキャンセル
stopPropagation() = 親要素への伝播をキャンセル
return false = 対象と親要素への伝播をキャンセル

であるから、Tabulator 右click時、ブラウザのコンテキストメニュー表示が走らないようにと、
stopPropagation() ではダメで、preventDefault() をする必要がある。思い違いをしやすい。
return false がなぜかダメだった。。

rowContext:function(ev, row){
    // ev = event object
    // row = ダブルclick した行
    ev.preventDefault();
},

クリック

rowlClick:function(ev, row){
    // ev = event object
    // row = click した行
},

ダブルクリック

rowDblClick:function(ev, row){
    // ev = event object
    // row = ダブルclick した行
},

これら、クリック、ダブルクリックした行のデータを参照する時は、getData()を使う
行データに、"id" キーのデータがある場合、

rowDblClick:function(ev, row){
    id = row.getData().id;
},

ついでに、メモ
タップ

rowTap:function(ev, row){
    // ev = event object
    // row = タップ した行
},

ダブルタップ、300msec 以内の間隔

rowDblTap :function(ev, row){
    // ev = event object
    // row = ダブルタップ した行
},

1秒以上長押し

rowTapHold :function(ev, row){
    // ev = event object
    // row = タップした行
},

Wicket で原始的な Response を使う注意の1つ

Wicket に文句をつけたいところは、getRequestCycle で引っ張ってくるオブジェクトのインターフェースが
今まで、何度も実装が変わってきたことだ。。
8年前は、、、

HttpServletRequest request = getWebRequestCycle().getWebRequest().getHttpServletRequest();
HttpServletResponse response = getWebRequestCycle().getWebResponse().getHttpServletResponse();

が成立したが、、Wicket 8.x 以降では、これはNGである。
⇒ http://oboe2uran.hatenablog.com/entry/2010/09/15/211605 これは今やとなってはNG

org.apache.wicket.request.http.WebResponse

WebResponse webresponse = (WebResponse)getRequestCycle().getResponse();

原始的なことをするなら、この WebResponse で実行することが基本だ。
WebResponse には、getContainerResponse() というメソッドがあるが、これはあくまでもWebコンテナに依存した
クラスを返す。Tomcat の時は、org.apache.catalina.connector.ResponseFacade が返ってくれる。

getRequestCycle().getResponse() の使用例
判りやすく、昨日の投稿に合わせて、、、画像を返す WebPage 継承クラスである。

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.request.http.WebResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * SampleResponseImage
 * 画像は、、リソースの下、/img/canion.jpg
 */
public class SampleResponseImage extends WebPage{
   Logger logger = LoggerFactory.getLogger(this.getClass());

   public SampleResponseImage(){
      String classesPath = ((WebApplication)getApplication()).getServletContext().getRealPath("WEB-INF/classes");
      File fileCanion = new File(classesPath + "/img/canion.jpg");

      WebResponse webresponse = (WebResponse)getRequestCycle().getResponse();
      webresponse.setContentType("image/jpeg");
      try(InputStream in = new FileInputStream(fileCanion);OutputStream out = webresponse.getOutputStream()){
         in.transferTo(out);
      }catch(Exception ex){
         logger.error(ex.getMessage(), ex);
      }
   }
}

WebApplicarion の init() で、

mountPage("/SampleResponseImage", SampleResponseImage.class);

HTML

<img src="/samples/SampleResponseImage" width="20%" height="20%">

わざわざ、Wicket から原始的なオブジェクトを取得して処理するのは、
Wicket が折角提供する各種コンポーネントを利用しないことになってセンスがないかもしれない。
でも、原始的な処理の面倒なところから、簡単な記述で済むようなコンポーネントの提供まで、
柔軟にいろいろ書くことが許されているのが魅力なんだと思う。
しかし、この getRequestCycle() 回りは、過去から今まで何度も変更があって、
困ったものだ。
ServletAPI 2.3→ 2.5 → 3.0 → 3.1 → 4.0 → 4.1 と歴史上、仕方なかったのだろうか?。。。