11.6インチのサブモニタ

机の脇において使っていた、メーカー不明のフルセグ対応のポータブルテレビ(10インチ)が壊れてしまい、同じ大きさのテレビを探してたんですが、パナソニックとかシャープにはポータブルテレビがあるにはあるんですが、いずれも無線で信号を送る形式ばかり。
壊れたやつはHDMI入力もできて重宝してたので、HDMI入力ができるものを探してはみたんですが、なんか怪しげな中華製品が多く、どれもレビューがいまいち。

地デジBlulayレコーダーはすでに持っているので、地デジチューナー内蔵は諦めて、今度はHDMI入力ができる小型モニタを探し始めた。
・・・が、10インチ前後となるともう数が限られ、これまた怪しげな中華製品ばかり。しかもレビューもイマイチ。。。

こういうニッチ製品は大手メーカーは儲からないので作って売ってくれない。

で、こういうニッチ製品を多数作ってくれているCenturyの小型モニタに頼る。ヨドバシで29,800円で買う。23インチ前後のフルHD液晶モニタが安いやつだと19,800円ぐらいでいっぱいあるので・・・まぁ、それと比べるとちょっと高い感じがしますが、まぁ一般的にこのサイズのモニタはほとんど売れないと思うので需要と供給のバランスからみてもしょうがないんでしょうねぇ。

Century LCD-11600FHD2
FULL HD。1920×1080のみドットバイドット表示。型番に2とあったので、初代とどこが違うんだろう・・

ノングレア・IPSパネルみたいで、映りは非常にキレイです。ただ同時発色数が少ないのか夕焼けとかのグラデーション表示時、バンディングが出ます。まぁ気になるところはそれぐらいで画質面でこれといって不満はありません。

レコーダーでHDMIをつないでテレビやビデオのモニタとしてみる分には、スピーカーも内蔵されているので問題なし。
ただ、1点、分かっていたことですが、音量の調節が・・・すんげぇやりにくい・・・。パソコンとつないでサブモニタとして使うぶんには音量調節は関係ありませんが・・・、レコーダーとつないで単体で使用すると、音量調節がしにくいのは致命的・・・。レコーダー側のリモコンで音量調節できる機種(そんなのあるのかな?)だと問題ないんだけどねぇ。。。まぁ、音量調節だけ全面のフレームにつけてほしいなぁ・・・まぁ、そんなことしたらコストが跳ね上がるのはわかるんですけどね。。。

画面が思っていた以上に美しい発色なので満足してます😆

テーブルヘッダの固定

これまで、tableタグ内で thead要素を固定してtbody要素をスクロール可能にするために、
table要素の外側の要素の高さを固定してposition:relative、overflow:autoと指定して、下記のような自作の簡易jQueryプラグインを使用していました。

実際のデモ

HTMLとCSS

<div class="table-container">
  <table>
    <thead>
      <tr>
        <th>カラム1</th>
        <th>カラム2</th>
        <th>カラム3</th>
        <th>カラム4</th>
        <th>カラム5</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>カラムデータ1</td>
        <td>カラムデータ2</td>
        <td>カラムデータ3</td>
        <td>カラムデータ4</td>
        <td>カラムデータ5</td>
      </tr>
       ...
       ...
    </tbody>
  </table>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="./jquery.headerFixedTable.js"></script>
<script type="text/javascript"><!--
(function($) {
  $('.table-container').headFixed();
})(jQuery);
//--></script>

.table-container {
  position: relative;
  overflow: auto;
  height: 100px;
  width: 80%;
  border: 1px solid #ccc;
}
.table-container table {
  width: 100%;
  border-collapse: collapse;
}
.table-container table > thead {
  background-color: #eee;
}
.table-container table > thead th {
  border-bottom: 2px solid #ccc !important;
}
.table-container table > tbody {
  background-color: white;
}
.table-container table tr > * {
  text-align: left;
  border-bottom: 1px solid #ccc;
}
.table-container table > tbody > tr:last-child > * {
  border-bottom: none !important;
}

jQueryのプラグインは適当に作成。汎用性はなし。

/******************************************************************************
  thead固定テーブル
  require following... 
     <div class="table-wrap" style="position: relative;">
       <table>
         <thead>...</thead>
         <tbody>...</tbody>
       </table>
     </div>
     <script type="text/javascript">
        (function($) {
           $('.table-wrap').headFixed();
        })(jQuery);
     </script>

 要点としては、table要素を複製して、複製したテーブルはtheadだけを、元のテーブルはtbodyのみを表示するようにする。
 その際 displayプロパティにすると元table,複製tableの各々カラム幅が揃わなくなるので、
 display: none ではなく、visibility:hiddenを適用する。
******************************************************************************/
(function($) {
  $.fn.headFixed = function()
  {
    return this.each(function() {
      var $table = $(this).find('table:first');

      var $wrapElement = 
        $('<div>')
        .css({'position': 'absolute','top': 0,'left':0,'width': $table.outerWidth(),'overflow':'hidden'})
        .height($table.find('thead').outerHeight());

      var $wrap = 
        $table
          .clone()
          .insertAfter($table)
          .wrap($wrapElement)
          .css('width','100%')
          .find('tbody')
          .css({'visiblity': 'hidden'})
          .end()
          .parent();

      $table
        .find('thead')
        .css({'z-index':10,'visibility': 'hidden'});

      $(this).on('scroll',function() {
        $wrap.css({'top':$(this).scrollTop()});
      });

      // ウィンドウのリサイズに追随
      $(window).resize(function() {
        $wrap.width($table.outerWidth());
      });
    });
  };
})(jQuery);

で、最近 display: sticky というのがあって便利だよ、というのを今更ながら知りまして、chrome,firefox用の画面にはこれを使うようしました。
MDNサイトで見ると「粘着位置指定要素 (stickily positioned element)」と言うそうで、要はイケてるサイトでよく見る、
下方スクロールしたら勝手にメニューなんかがページ上部に張り付く(固定される)挙動を簡単に実現できる CSSプロパティみたいです。

stickily positioned elementとは、 position の計算値が sticky である要素です。これは包含ブロックがフロールート (又はその中でスクロールするコンテナー) 内の指定されたしきい値 (例えば top に設定された auto 以外の値など) を達するまでは相対的な配置として扱われ、包含ブロックの反対の端が来るまでその位置に「粘着」するものとして扱われます。

これ読んでも、正直よく分かりません💦 すみません。

で、これをテーブルヘッダに利用しよう、というわけです。
ググると結構記事になっていて、要するに thead要素内の th要素に displya:sticky を指定して固定するようです。

実際のデモ

theadをstickyにするのが真っ当な感じですが、現在のブラウザのバージョンでは thead を固定することはできないみたい(無視される)。
それと、th要素をstickyで固定しても、th要素に適用されている border は固定されないみたいな挙動になる感じです(若干ずれてしまう?)

/* 実際のCSS */
.table-container {
  position: relative;
  overflow: auto;
  height: 100px;
  width: 80%;
  border: 1px solid #ccc;
}
.table-container table {
  width: 100%;
  border-collapse: collapse;
}
.table-container table > thead {
  background-color: #eee;
}
.table-container table > thead th {
  border-bottom: 2px solid #ccc !important;
  position: -webkit-sticky !important;
  position: sticky !important;
  z-index: 2 !important;
  top:0 !important;
}
.table-container table > tbody {
  background-color: white;
}
.table-container table tr > * {
  text-align: left;
  border-bottom: 1px solid #ccc;
}
.table-container table > tbody > tr:last-child > * {
  border-bottom: none !important;
}

HTMLはほとんど同じ 。最後のscript要素が要らないだけ。

<div class="table-container">
  <table>
    <thead>
      <tr>
        <th>カラム1</th>
        <th>カラム2</th>
        <th>カラム3</th>
        <th>カラム4</th>
        <th>カラム5</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>カラムデータ1</td>
        <td>カラムデータ2</td>
        <td>カラムデータ3</td>
        <td>カラムデータ4</td>
        <td>カラムデータ5</td>
      </tr>
       ...
       ...
    </tbody>
  </table>
</div>

ただし、Internet Explorerには全バージョンを通して非対応なので(display:stickyは単に無視される)、
かっこ悪いけどブラウザで判別し(navigator.userAgent)、IEの時のみjQueryプラグインを使用するように分岐するようにした。

たとえば、ウェブシステムで行をjavascriptで動的に増やしたり減らしたりするときは、行を追加したり削除したりする関数を一個作っておき、もしブラウザがIEなら 複製テーブル、複製元テーブル両方に行を追加したり、削除したり。なんにしてもメンドクサイが。
IEユーザーがいなくなれば、これも必要なくなるかなと。

samba(linux)とgVim(windows)でハマる

おっさんは、忘れてしまうのが異常に早いので、忘れずに備忘録。
ドキュメント書いたことも忘れてしまう。意味ないじゃ~~~ん。

閑話休題。

開発マシンを新しくCentOS7にした時、ファイル共有(Samba4)の設定で罠にはまった。
気づいたのは、CentOS7上に共有ディレクトリを設定して、Windows10の gVim(Kaoriya)でdockerの Dockerfile と docker-compose.yaml 他諸々の自動化シェルスクリプトを編集してた時に気づきました。

「あれ? (Windows10の) gVimで編集して保存(:w)したとき、groupのパーミッションに実行ビットが勝手につきやがるな・・・」

↓の図で Dockerfile のパーミッションに見慣れない(+)記号と group のパーミッションに x が付きます。。。

/etc/samba/smb.conf の設定で、create_maskとかforce create mode とかいろいろ試行錯誤するも状況に変化なし。不思議なことに メモ帳で開いて、保存すると、上記のようなことは起こらない。
Windows版のvim/gVimの時だけそうなる。。。

で、見慣れない + 記号の意味を調べると、Windowsの拡張属性?みたいなものがくっつくと + 記号が出るみたい。。。
そこで、sambaのリファレンスからそれらしい設定を見つけた。

  • map archive
  • map system
  • map hidden
  • nt acl support

DOSでおなじみ、A,H,S属性をLinux側とマッピング?するオプションなのかな?
nt acl support はそのものずばり、WindowsのACLのサポートをするかどうか。
とりあえず、これらを片っ端から no に設定して samba を再起動したら直った・・・・と思ったけど、今度は実行ビットが立ったファイルをgVim(windows)で編集して保存すると、実行ビットがなくなる。。。
smb.confで、create mask を 644 にしているのでは???と思い、gVim の保存方法に問題があるんじゃねーの?って推定して、ググると、どうやら backupcopy 変数が関係しているらしい、とのこと。

:helpで調べると unix以外のvimでは backupcopyの初期値は auto だそうで、backup=yes を ~/_vimrc に追加すると、僕の期待した通りの挙動に一応なりました。

ってなわけで、Linux側で共有されたファイルをWindowsのgVimで編集するときは、:set backupcopy=yes で、/etc/samba/smb.conf の map **** と nt acl support を no に設定。

こっそりルーター化

これのCentOS7版

備忘録です。

開発用のサーバーを更新。CentOS8まで待とうかと少し思いましたが、まぁ、いいや。
ってことで、WiFi環境がないので、ポータブルのWiFiルータを開発用サーバーに接続して使えるように設定。

図A 前提

正直ネットワーク素人なので、zone:public に –add-masquerade するのがいいのかどうかわからんが、ちゃんとスマホからアクセスできるのでこれでいいか。

zoneをデフォルトのpublicではなく、work にした方が良かったのかもしれない。

#ゾーン publicとhomeに通信を許可するサービスをそれぞれに追加。
firewall-cmd --zone=public --add-service http --add-service https --add-service mysql --add-service dns --add-service samba --permanent
firewall-cmd --zone=home --add-service http --add-service https --add-service dns --permanent 

#eth1 は ELECOMのUSB接続タイプのNIC。これを home ゾーンに変更
nmcli c mod eth1 connection.zone home

#publicに対してNAPTをしかける
firewall-cmd --zone=public --add-masquerade --permanent

# リロード
firewall-cmd --reload

ウェブブラウザ上のJavaScriptでExcelファイルをゴニョゴニョしたい

特に需要はないと思いますが、極たま~~~にウェブでエクセルファイルを扱うことがあります。
なんつっても、世の中の文書フォーマットは、マイクロソフトのエクセル(Excel)がデファクトスタンダードです💦
これはもうどうにもなりません。CSVで、つっても、エクセルファイルをよこしやがります。

まぁ、やっぱり業務システムにはエクセルは欠かせません、というか、これなしには、日本は動きません。日本の企業とか国、地方公共団体は、エクセルで動いているんです。これはもう動かしようのない事実であり当分の間は変わりませんし、変化の兆しも見えません。

しょうがない。

と、開発者の方が思ったかどうかわかりませんが、ブラウザのJavaScriptでエクセルファイルをパースして編集して、出力してくれる ライブラリがあるんですね。サーバーサイドのNode.jsでも使えます、というかそっちがメインのライブラリなのかも。

一般的にMS-Officeをインストールしたパソコンにはおまけ?として、EXCELのオートメーションが使えるようになってます。あくまでOfficeを買えば!の話ですが。
Windows Scripting HostなどからVBScriptやJScriptを使ってカンタンにエクセルファイルをアーダコーダできます。

・・・が、今回はこういう噺ではありません。
一般的なブラウザ上で、エクセルファイルをアーダコーダできるライブラリがあったんです。需要がなかったので今まで知らなかった!

SheetJS Spreadsheets simplified

で、ググったりしていろいろ調べると、意外にカンタンに使えちゃいますね。ただし、javascriptの blobとかFileReaderとかArrayBufferとかUint8Arrayとか・・・そういうちょっとややこしめの知識が必要です。

昔と違ってブラウザでローカルファイルとか普通に扱えます。ですが、その時は必ず、上記のFile/Blob/FileReader/ArrayBuffer/TypedArrayとかが絡んできます。
FileとBlobの関係、ArrayBufferとTypedArray(Uint8Arrayとか)の関係、さらにはFileReaderとFile/Blobの関係。このあたりは鬼門です。なんでこんなめんどくさいんだよ!っていつも思います。

MDNとかのリファレンスを読むのが手っ取り早いのですが・・・とりあえず、MDNのサイトでは、こういう場合は、こうする、という「お約束」の手順が書かれているので、まずそれを丸覚えするのがいいと思います。公文式です(笑) 理屈は後から学べばいいんです。ただ、ググってブログ記事を参考にするのは結局は理解するのが遅くなってしまうので、リファレンスとサンプルを読んで、書いて、試してみたほうがいいと思ってます。

※ たぶんIEでは動かないと思う。試してないけど。IEは既にMSも認めたオワコンなんで、どーでもいい。

下のデモ(テストコード)

さて、HTMLファイルをサクッと書きます。(Ryzenの価格表コピペしました。)

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style type="text/css">
      table,th,td {
        border-collapse: collapse;
        border: 1px solid #aaa;
      }
      th,td {
        padding: 5px;
        text-align: center;
        font-size: 85%;
      }
      th { 
        background-color: #f6f6f6;
      }
    </style>

  <body>
    <h2>sheet-js サンプル</h2>
    <table id="table-0">
      <tr>
        <th>モデルナンバー</th>
        <th>プロセスルール</th>
        <th>コア/スレッド数</th>
        <th>TDP</th>
        <th>周波数(ブースト時/ベース)</th>
        <th>合計キャッシュサイズ(MB)</th>
        <th>GPU</th>
        <th>PCIe 4.0 レーン(X570利用時)</th>
        <th>店頭予想価格(税別)</th>
        <th>提供開始時期</th>
      </tr>
      <tr>
        <td>Ryzen 9 3950X</td>
        <td>7nm</td>
        <td>16/32</td>
        <td>105W</td>
        <td>4.7/3.5GHz</td>
        <td>72MB</td>
        <td>-</td>
        <td>40</td>
        <td>不明</td>
        <td>9月</td>
      </tr>
      <tr>
        <td>Ryzen 9 3900X</td>
        <td>7nm</td>
        <td>12/24</td>
        <td>105W</td>
        <td>4.6/3.8GHz</td>
        <td>70MB</td>
        <td>-</td>
        <td>40</td>
        <td>59,800円</td>
        <td>7月7日</td>
      </tr>
      <tr>
        <td>Ryzen 7 3800X</td>
        <td>7nm</td>
        <td>8/16</td>
        <td>105W</td>
        <td>4.5/3.9GHz</td>
        <td>36MB</td>
        <td>-</td>
        <td>40</td>
        <td>46,980円</td>
        <td>7月7日</td>
      </tr>
      <tr class="y5 odd">
        <td>Ryzen 7 3700X</td>
        <td>7nm</td>
        <td>8/16</td>
        <td>65W</td>
        <td>4.4/3.6GHz</td>
        <td>36MB</td>
        <td>-</td>
        <td>40</td>
        <td>39,800円</td>
        <td>7月7日</td>
      </tr>
      <tr>
        <td>Ryzen 5 3600X</td>
        <td>7nm</td>
        <td>6/12</td>
        <td>95W</td>
        <td>4.4/3.8GHz</td>
        <td>35MB</td>
        <td>-</td>
        <td>40</td>
        <td>29,800円</td>
        <td>7月7日</td>
      </tr>
      <tr>
        <td>Ryzen 5 3600</td>
        <td>7nm</td>
        <td>6/12</td>
        <td>65W</td>
        <td>4.2/3.6GHz</td>
        <td>35MB</td>
        <td>-</td>
        <td>40</td>
        <td>23,980円</td>
        <td>7月7日</td>
      </tr>
      <tr>
        <td>Ryzen 5 3400G</td>
        <td>12nm</td>
        <td>4/8</td>
        <td>65W</td>
        <td>4.2/3.7GHz</td>
        <td>6MB</td>
        <td>Radeon RX Vega 11</td>
        <td>-</td>
        <td>18,800円</td>
        <td>7月7日</td>
      </tr>
      <tr>
        <td>Ryzen 3 3200G</td>
        <td>12nm</td>
        <td>4/4</td>
        <td>65W</td>
        <td>4/3.6GHz</td>
        <td>6MB</td>
        <td>Radeon RX Vega 8</td>
        <td>-</td>
        <td>11,800円</td>
        <td>7月7日</td>
      </tr>
    </table>
        
    <p>
      <label for="select-file">テーブルをエクセルファイルに追加します。</label>
      <input type="file" id="select-file">
    </p>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.14.3/xlsx.full.min.js"></script>
    <script type="text/javascript" src="./index.js"></script><!-- 下記 javascriptコード -->
  </body>
</html>

JSライブラリは適当にCDNから引っ張ってきましょう。僕は jQuery好き好き人間なので、jQueryを使用します。すみません。
INPUT[type=file]タグでローカルファイルの口としましょう。ドラッグ&ドロップを仕込んでもいいのですが、コードをカンタンにするため、普通のファイル選択にしました。ここでローカルファイルを選んでjavascriptコードに放り込みます。

で、次に実際の処理を書いていきます。まず、基本。INPUT[type=file]のonchangeイベントハンドラを起点にしています。(13行目付近)
ローカルファイルを読み込んでゴニョゴニョするときは、必ずFileReaderのインスタンスを作って、onloadイベントで処理を行います。(18行目付近)
下記例では、FileReader.readAsArrayBuffer() していますが、単純に Data URIが必要であれば FileReader.readAsDataUrl() を使用します。
ローカルの画像ファイルを読み込んで表示するときは、readAsDataUrlメソッドを使いますよねぇ。

ちなみにreadAsArrayBufferメソッドを使うと、onloadイベントハンドラ内で ev.target.result によってArrayBufferオブジェクトを得ることができますが、直接このArrayBufferオブジェクトにアクセスすることができません。必ず、TypedArray・・・たとえば、Uint8Arrayなどのオブジェクトのインスタンスからアクセスします。めんどくさいですねぇ。

/*******************************************************************************
  filename:  index.js
 
  description: 
   ローカルのエクセルファイルを選択すると(<input type="file" id="select-file">、
   選択したエクセルファイルへシートを追加し、テーブル要素(<table id="table-0">)を書込み、
   そのエクセルシートをダウンロードするためのリンクをdocument.body に追加します。

   ※このままのコードだと、セルの書式属性は全部消えます。
*******************************************************************************/
(function($) {

  $('input#select-file[type=file]').on('change',function(ev) {
    var files = this.files;
    var f = files[0];
    var reader = new FileReader();

    reader.onload = function(e) {
     
      // 読み込んだエクセルファイル(ArrayBuffer)をUint8Array配列にし、XLSX.readに渡します。
      var data = new Uint8Array(e.target.result);
      var workbook = XLSX.read(data, {type: 'array'});

      // 上記 HTMLのテーブルを table_to_sheetメソッドに渡しエクセルシート(ブック?)を作って、
      var new_workbook = XLSX.utils.table_to_sheet( $('#table-0').get(0) );
      
      //読み込んだエクセルに上記テーブルを変換したシート(ブック?)を新しいシートとして追加します。
      XLSX.utils.book_append_sheet(workbook, new_workbook, 'Ryzen price');

      // 新しく作成するエクセルファイルの作成オプションを設定します。
      var options = {
          bookType: 'xlsx',
          bookSST: false,
          type: 'array',
          compression: true
        };
      
      // 上記オプションを使って Blobオブジェクトに出力します。
      var blob = new Blob(
        [XLSX.write(workbook, options)],
        {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}
      );

      // Blobオブジェクトをダウンロードさせるための仮想的な?URLを作って A要素をdocument.bodyに追加します。
      // クリックすると、読み込んだエクセルファイルに表をシートに追加し、新しく作ったエクセルファイルをダウンロードできます。
      $('<a>')
        .attr({'href':window.URL.createObjectURL(blob),'download': 'シートを追加したエクセルファイル.xlsx'})
        .appendTo(document.body)
        .text('シートを追加したエクセルファイル.xls');
    };
    reader.readAsArrayBuffer(f);
  });

})(jQuery);

FileReader.onload内で、ev.target.result を そのまま Uint8Arrayコンストラクタに渡して、ArrayBufferへアクセスするための Uint8Arrayオブジェクトを作り(21行目付近)、それを XLSX.readメソッドに渡します。ただそれだけで、エクセルファイルをパースしてくれます。あとは、XLSX.utils オブジェクトのメソッドをコールしていけば、だいたいのことはできると思います。
CSVデータが欲しければ、XLSX.utils.sheet_to_csv, JSONデータが欲しければ XLSX.utils.sheet_to_json、シートを増やしたければ、XLSX.utils.append_sheet、他にも javascript配列をシートに、DOM TABLE要素をシートに・・・とか対応するメソッドがあるようです。この辺のドキュメントはgithubをたよりに試行錯誤するしかないのかな。。。

エクセルファイルにシートを追加して、そのエクセルファイルをダウンロードする、これだけのことをクライアント内(ブラウザ内)で完結することができてしまいます。
ローカル内でサーバーを立てる必要もありません。上記二つのHTMLとJSを任意のフォルダに適当な名前で保存して、file://スキームで試すことも可能です。

(1) HTMLファイル ローカルで開いたところ。

(2) エクセルを選択すると、ダウンロードリンクが追加されます。

(3) 元のエクセルはいつもダミーで使わせてもらってる疑似会社情報のエクセルファイル
( http://hogehoge.tk/personal/ )

(4) HTMLのテーブルデータをエクセルシートにぶっこんでくれます。
ただしセル書式は全部吹っ飛びます。

今回は、エクセルシートを追加してみましたけど、セルの属性とかは全部ぶっとんでしまいました💦
が、単にJSONやCSVが取れればいい、というのが大半の需要かと思いますので問題はないでしょう。

僕は エクセルファイルをサーバー側で変換するのではなく、クライアントで一旦CSVに変換してから、サーバーにアップロードして、処理を行う用途に使用しました。サーバーでエクセルファイルを処理すると重くなるんで・・・。
エクセルを読むだけなら、カンタンにできるので、本当にありがたいライブラリです。

ただ一点、ちゃんとしたリファレンスドキュメントがありません。。。これはPro版を買えってことなのかなぁ。。。Community版はApache License 2.0なんで、「おまえら、ソース読んで、自分でなんとかしろよ」ってことなんでしょうね。