Tabulator のチェックONOFFデザインを持ってくる。(2)

昨日書いた
oboe2uran.hatenablog.com

やはり、IE11でも表示できるJS版にします
原始的に、DOMの innerHTML を使います。

HTML

<h2>width × height : 24</h2>
<div class="case1"></div>

<h3>border</h3>
<div class="case2"></div>

<h2>width × height : 14</h2>
<div class="case3"></div>

CSS は前回と同じです。

svg.chk-on path {
   fill: #2dc214;
   clip-rule: evenodd;
   fill-rule: evenodd;
}
svg.chk-off path {
   fill: #ce1515;
}
.case1 svg {
   width: 24px;
   height: 24px;
}
.case2 svg {
   width: 24px;
   height: 24px;
   border: 2px solid #c0c0c0;
   border-radius: 6px;
}
.case3 svg {
   width: 14px;
   height: 14px;
   border: 2px solid #c0c0c0;
   border-radius: 4px;
}

JavaScript jQuery

/* innerHTML にセットする svg - path を定義 */
var svgCheckOn = '<svg class="chk-on" viewBox="0 0 24 24">'
      + '<path d="M21.652----(省略)----652,3.211z">'
      + '</path></svg>';
var svgCheckOff = '<svg class="chk-off" viewBox="0 0 24 24">'
      + '<path d="M22.245----(省略)----245,4.015z">'
      + '</path></svg>';

document.getElementsByClassName('case1')[0].innerHTML = svgCheckOn + svgCheckOff;
document.getElementsByClassName('case2')[0].innerHTML = svgCheckOn + svgCheckOff;
document.getElementsByClassName('case3')[0].innerHTML = svgCheckOn + svgCheckOff;

このように、div に innerHTML するしかなく、svgタグ で innerHTML 実行できないのです。

Tabulator のチェックONOFFデザインを持ってくる。(1)

jQueryプラグインTabulator のチェックON/OFF のデザインは、SVGで作成されているので
Tabulator サンプル等を表示して、SVGコードを抽出して他で利用できそうである。
f:id:posturan:20180817161923j:plain
このチェックON、OFFのSVGコードのHTMLタグを抽出すると以下である。
とても長い path タグの d 属性は、この投稿では適当なところで改行しているが、実際は1行である。
省略してしまうとコピぺできないので途中改行して書いてます。

<!-- ON の SVG -->
<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve">
      <path fill="#2DC214" clip-rule="evenodd"  fill-rule="evenodd"
 d="M21.652,3.211c-0.293-0.295-0.77-0.295-1.061,0L9.41,14.34  c-0.293,0.297-0.771,0.297-
1.062,0L3.449,9.351C3.304,9.203,3.114,9.13,2.923,9.129C2.73,9.128,2.534,9.201,2.387,9.3
51  l-2.165,1.946C0.078,11.445,0,11.63,0,11.823c0,0.194,0.078,0.397,0.223,0.544l4.94,5.1
84c0.292,0.296,0.771,0.776,1.062,1.07  l2.124,2.141c0.292,0.293,0.769,0.293,1.062,0l14.3
66-14.34c0.293-0.294,0.293-0.777,0-1.071L21.652,3.211z">
       </path>
</svg>

<!-- OFF の SVG -->
<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve">
      <path fill="#CE1515" d="M22.245,4.015c0.313,0.313,0.313,0.826,0,1.139l-6.276,6.27c-
0.313,0.312-0.313,0.826,0,1.14l6.273,6.272  c0.313,0.313,0.313,0.826,0,1.14l-2.285,2.27
7c-0.314,0.312-0.828,0.312-1.142,0l-6.271-6.271c-0.313-0.313-0.828-0.313-1.141,0  l-
6.276,6.267c-0.313,0.313-0.828,0.313-1.141,0l-2.282-2.28c-0.313-0.313-0.313-0.826,0-
1.14l6.278-6.269  c0.313-0.312,0.313-0.826,0-1.14L1.709,5.147c-0.314-0.313-0.314-0.8
27,0-1.14l2.284-2.278C4.308,1.417,4.821,1.417,5.135,1.73  L11.405,8c0.314,0.314,0.82
8,0.314,1.141,0.001l6.276-6.267c0.312-0.312,0.826-0.312,1.141,0L22.245,4.015z">
       </path>
</svg>

この SVGタグ属性で省略可能なものがあります。
enable-background は、省略できます。というより将来消滅するかもしれず、奨励されてないと思います。
enable-background はフィルタ処理する時に使用するもので単にWebページ表示では関係ないでしょう。
xml:space="preserve" → XMLの扱いで空白文字を保存ということですが、Webページ表示でこれも省略しても問題ないでしょう。
次に重要な属性、、、
path の fill属性、これが fill(塗りつぶし)の色を指定するものです。
そして、SVG の viewVox 属性、これが単純に、X座標、Y座標、width, height ということではないらしいんです。
X座標、Y座標 → 左上角の位置、そこから、X軸に width 、Y軸に、height の位置が右下角の座標です。

これらから、CSSにできるものはCSSで書いてみます。
以下のように、サンプルを書きました。適当に使い回せると思います。

チェックON/OFF属性の path と、3ケースのSVGタグのCSSです。

svg.chk-on path {
   fill: #2dc214;
   clip-rule: evenodd;
   fill-rule: evenodd;
}
svg.chk-off path {
   fill: #ce1515;
}
.case1 svg {
   width: 24px;
   height: 24px;
}
.case2 svg {
   width: 24px;
   height: 24px;
   border: 2px solid #c0c0c0;
   border-radius: 6px;
}
.case3 svg {
   width: 14px;
   height: 14px;
   border: 2px solid #c0c0c0;
   border-radius: 4px;
}

サンプル表現の為のCSS

div{
    padding: 20px;
    background-color: #f0f0f0;
}

HTML
path の d属性は、とても長く上で書いたので省略してます。

<h2>width × height : 24</h2>
<div class="case1">
   <svg class="chk-on" viewBox="0 0 24 24">
     <path d="M21.652-----(省略)----- 652,3.211z">
     </path>
  </svg>
   <svg class="chk-off" viewBox="0 0 24 24">
     <path d="M22.245-----(省略)------ 245,4.015z">
     </path>
  </svg>
</div>

<h3>border</h3>
<div class="case2">
   <svg class="chk-on" viewBox="0 0 24 24">
     <path d="M21.652-----(省略)----- 652,3.211z">
     </path>
  </svg>
   <svg class="chk-off" viewBox="0 0 24 24">
     <path d="M22.245-----(省略)------ 245,4.015z">
     </path>
  </svg>

</div>

<h2>width × height : 14</h2>
<div class="case3">
   <svg class="chk-on" viewBox="0 0 24 24">
     <path d="M21.652-----(省略)----- 652,3.211z">
     </path>
  </svg>
   <svg class="chk-off" viewBox="0 0 24 24">
     <path d="M22.245-----(省略)------ 245,4.015z">
     </path>
  </svg>
</div>

これを表示すると、、、
f:id:posturan:20180817165509j:plain
という結果でした。。
IE11でも表示できます。
これを JS から動的に生成表示してみます。CSSは、そのままです。

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script type="text/javascript">
$(function(){
     $('svg').attr('viewBox', '0 0 24 24');
     $('svg.chk-on').html('<path d="M21.652,-----(省略)-----652,3.211z"></path>');
     $('svg.chk-off').html('<path d="M22.245,-----(省略)-----245,4.015z"></path>');
});
</script>

Chrome ブラウザで問題なく、想定どおり表示されるのですが、、
IE11 が表示されません。

IE11が表示できない理由が解りません。
IEが、、、」
Microsoft が、、、、]
毎回、聞こえてくる呟きです。。
なんで、Mycrosoft は、こういつも、ダメなの?
仕方なく、、、
oboe2uran.hatenablog.com

jQuery ui ダイアログのタイトルバーの大きさ調整

jQuery ui ダイアログのタイトルバーが大きすぎると思ったので調整
通常は、、、
f:id:posturan:20180817101005j:plain

これが大きいと思うなら、、

.ui-dialog-titlebar{
   padding: 0.3rem !important;
   font-size: 0.5rem;
}

f:id:posturan:20180817100730j:plain
タイトル文字列を指定しなければ、、
f:id:posturan:20180817100754j:plain

jQuery ui ダイアログの制御ボタンの表示位置調整

久々に jQuery UI ダイアログのコードを書きました。ちょっとしたメモとしてここに残します。
何も気にせずに、マニュアルどおりに書けば、、、
HTML・・・ダイアログ表示させる div

<div id="dialog"></div>

JS・・・ダイアログ

var dlg = $('#dialog').dialog({
   autoOpen: false,
   modal: true,
   title: "タイトル",
   width: 320,
   height: 180,
   buttons: {
      "Yes": function(){
         $(this).dialog("close");
      },
      "No": function(){
         $(this).dialog("close");
      }
   },
   close: function(){
      $('#dialog').empty();
   },
});

表示の実行

$('#dialog').append("<h2>メッセージ</h2>");
dlg.dialog('open');

buttons: { } で書いたボタンは、ui-dialog-buttonpane の ui-dialog-buttonset として
以下の様に表示されます。
f:id:posturan:20180815212159j:plain
ボタンが右に寄ってしまい、ボタンの隙間が狭くて押し間違いの危険もあります。

なんとか、中央寄せにしてボタンの隙間もできるだけ均等にします。
CSSスタイルシートを、jquery-ui.css の次に読み込むように、以下を記述します。

.ui-dialog-buttonset{
   width: 100%;
   display: flex;
   display: -webkit-flex;
   text-align: center;
   justify-content: space-around;
   -webkit-justify-content: space-around;
}

すると、以下のようにできます。
f:id:posturan:20180815212604j:plain

ここで使用した jQuery ui は以下のとおりです。

<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>

Tabulator CSV download を BOM 付きにする

Tabulator で表示した表から、BOM付きのCSVダウンロードするには、
2通りの方法がある。
・Custom File Formatter を使う方法
・Format Data Before Download を定義する方法

いずれも、対策するためにカスタマイズする方法を示したマニュアルが
http://tabulator.info/docs/#download ページに書いてある。

(1)Custom File Formatter を使う方法
   先頭に BOM を付けるCSV作成のフォーマット関数を用意する。

var csvFormatter = function(columns, data, options, setFileContents){
   var self = this,
   titles = [],
   fields = [],
   delimiter = options && options.delimiter ? options.delimiter : ",",
   fileContents;
   columns.forEach(function(column){
      if (column.field){
         titles.push('"' + String(column.title).split('"').join('""') + '"');
         fields.push(column.field);
      }
   });
   fileContents = ["\uFEFF" + titles.join(delimiter)];
   data.forEach(function(row){
      var rowData = [];
      fields.forEach(function(field){
         var value = self.getFieldValue(field, row);
         switch(typeof value === 'undefined' ? 'undefined' : _typeof(value)){
            case "object":
            value = JSON.stringify(value);
            break;
            case "undefined":
            case "null":
            value = "";
            break;
            default:
            value = value;
         }
         rowData.push('"' + String(value).split('"').join('""') + '"');
      });
      fileContents.push(rowData.join(delimiter));
   });
   setFileContents(fileContents.join("\n"), "text/csv")
}

ダウンロード実行するイベントで、2番目のパラメータダウンロードするファイルの種別の代わりに
用意した function を指定する

  $("#example-table").tabulator("download", csvFormatter, "data.csv");

(2)Format Data Before Download を定義する方法
tabulator メソッドでテーブル定義と一緒に、downloadReady を定義する

$("#example-table").tabulator({
    layout: "fitColumns",
    columnVertAlign: "bottom",
    columns:[
        // 列定義、、、この説明では省略
    ],
    downloadReady:function(fileContents, blob){
        return new Blob([new Uint8Array([0xEF, 0xBB, 0xBF])
                       , fileContents], {type:'text/csv'});
    }
});

ダウンロード実行するイベントは、標準のマニュアルどおり。

    $("#example-table").tabulator("download", "csv", "data.csv");

同様のことを Handsontable だと、どうなんだろう?と調べると
有償版 Pro なら、オプション指定があり、これは default BOM true になってる。
Handsontable - Tutorial: Export to file

TypeError: e.indexOf is not a function

jQuery を書いていて、今まで動いてたのは、
HTML に、iframe タグ、src は適当に何かを指すようにしてあって、表示サイズを0
(=あえて、隠しておく)

<iframe id="otherframe" src="xxxx" width="0px" height="0px"></iframe>

何からのイベントで、load(function(){} )で、印刷プレビューを表示していたのですが、、

$('#otherframe').load(function(){
      document.getElementById('otherframe').contentWindow.print();
});

このコード、jQuery 2.x 系では動いてたのに、3.x にしたら、
  TypeError: e.indexOf is not a function
が発生
jQuery 3.0 で、load() イベントハンドラ関数は削除されてる!

.load() | jQuery API Documentation

対処として、以下のように、on('load', function(){}) で書けば、jQuery 3.3.1 でも動きます。

$('#otherframe').on('load', function(){
     document.getElementById('otherframe').contentWindow.print();
});

Java Process の実行ラッパーをラムダで

昔、java.lang.Runtime から Runtime.getRuntime().execProcess を作ってスクリプトを実行するラッパーを書いていた。

Process p = Runtime.getRuntime().exec(this.arrange());
_ProcessStreamReader p_stderr = new _ProcessStreamReader(p.getErrorStream());
_ProcessStreamReader p_stdout = new _ProcessStreamReader(p.getInputStream());
/*プロセス起動後、プロセスが入力を求めるなら、PrintWriter pw = new PrintWriter(p.getOutputStream();
 * を用意して、次に PrintWriter  で入力内容を print する
 */
p_stderr.start();
p_stdout.start();
p_stderr.join();
p_stdout.join();
p.waitFor();
int sts = p.exitValue();

このようにするのだが、IOException を InterruptedException 捕捉を書いてどうするかを呼出し側に委ねなくてはならない。
上記をラップしたメソッドの呼び出しも使いづらい。
だったらきちんと例外を捕捉してそれを標準エラー出力結果に出すようにすればいいだけなのですが、
どうせなら、
・実行スクリプトを Supplier<String> で渡す
・標準出力とエラー出力結果を BiConsumer<String, String> = <stdout, stderr>
あるいは、BiConsumer<InputStream, InputStream> = <stdout の InputStream, stderr InputStream>
 で処理する

というのを用意します。

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
 * ラムダ・スクリプト実行.
 */
public final class ScriptExecutor{

   private ScriptExecutor(){}

   /**
    * ラムダ・スクリプト実行(String → BiConsumer)
    * @param scriptSupplier 実行するスクリプト
    * @param consumer BiConsumer<String, String> = <stdout, stderr>
    * @return java.lang.Process の exitValue() 結果、例外発生時は 1 を返す。
    */
   public static int run(Supplier<String> scriptSupplier, BiConsumer<String, String> consumer){
      int rtn = 0;
      String stdout;
      String stderr;
      try{
         Process p = Runtime.getRuntime().exec(scriptSupplier.get());
         _processStreamReader p_stderr = new _processStreamReader(p.getErrorStream());
         _processStreamReader p_stdout = new _processStreamReader(p.getInputStream());
         p_stderr.start();
         p_stdout.start();
         p_stderr.join();
         p_stdout.join();
         p.waitFor();
         rtn = p.exitValue();
         stdout = p_stdout.getString();
         stderr = p_stderr.getString();
      }catch(Exception ex){
         rtn = 1;
         stdout = "";
         StringBuilder sb = new StringBuilder();
         sb.append(ex.getMessage());
         sb.append("\n");
         sb.append(Arrays.stream(ex.getStackTrace()).map(t->t.toString()).collect(Collectors.joining("\n\t")));
         Optional.ofNullable(ex.getCause()).ifPresent(x->{
            sb.append("\n");
            sb.append("Caused by: ");
            sb.append(x.getMessage());
            sb.append("\n");
         sb.append(Arrays.stream(x.getStackTrace()).map(t->t.toString()).collect(Collectors.joining("\n\t")));
         });
         stderr = sb.toString();
      }
      consumer.accept(stdout, stderr);
      return rtn;
   }
   /**
    * ラムダ・スクリプト実行(InputStream → BiConsumer)
    * @param scriptSupplier 実行するスクリプト
    * @param consumer BiConsumer<InputStream, InputStream> = <stdout の InputStream, stderr InputStream>
    * @return java.lang.Process の exitValue() 結果、例外発生時は 1 を返す。
    */
   public static int runStream(Supplier<String> scriptSupplier, BiConsumer<InputStream, InputStream> consumer){
      int rtn = 0;
      try{
         Process p = Runtime.getRuntime().exec(scriptSupplier.get());
         p.waitFor();
         consumer.accept(p.getInputStream(), p.getErrorStream());
         return p.exitValue();
      }catch(Exception ex){
         rtn = 1;
         StringBuilder sb = new StringBuilder();
         sb.append(ex.getMessage());
         sb.append("\n");
         sb.append(Arrays.stream(ex.getStackTrace()).map(t->t.toString()).collect(Collectors.joining("\n\t")));
         Optional.ofNullable(ex.getCause()).ifPresent(x->{
            sb.append("\n");
            sb.append("Caused by: ");
            sb.append(x.getMessage());
            sb.append("\n");
         sb.append(Arrays.stream(x.getStackTrace()).map(t->t.toString()).collect(Collectors.joining("\n\t")));
         });
         try{
            consumer.accept(new ByteArrayInputStream("".getBytes())
                        , new ByteArrayInputStream(sb.toString().getBytes("UTF-8")));
         }catch(UnsupportedEncodingException e){
            e.printStackTrace();
         }
      }
      return rtn;
   }
   static class _processStreamReader extends Thread{
      StringBuffer        sb;
      InputStreamReader   inredaer;
      public _processStreamReader(InputStream in){
         super();
         this.inredaer = new InputStreamReader(in);
         this.sb = new StringBuffer();
      }
      @Override
      public void run(){
         try{
            int i;
            int BUFFER_SIZE = 1024;
            char[] c = new char[BUFFER_SIZE];
            while((i = this.inredaer.read(c,0,BUFFER_SIZE - 1)) > 0){
               this.sb.append(c,0,i);
               if (i < BUFFER_SIZE - 1){ break; }
            }
            this.inredaer.close();
         }catch(IOException e){}
      }
      public String getString(){
         return this.sb.toString();
      }
   }
}

これで呼出しは、とても整理できてきます。
例えば テストとして、Python スクリプト foo.py というのを用意して、
Windows 上で実行なら、”cmd.exe /C ”を付けて実行します

int sts = ScriptExecutor.run(()->"cmd.exe /C  python c:/work/hello.py", (t, e)->{
     //  t  = 標準出力 
     //  e  = 標準エラー出力
});
// sts = プロセス実行終了コード、Process の exitValue() 結果