JavaScript : 複雑なbindのリーディング

月花です。

今回は、複雑なJavaScriptを見つけて、リーディングに時間がかかったので、その仕組みを紹介したいと思います。
単純な修正なのに、2時間くらいかけて読んで30分くらいかけて修正することになったので、次修正するとき忘れないように書いておきます。

バインド時にイベントハンドラの動的生成をして、event や this をさらにその先へ引き回す、というコードです。
主に以下の内容についての解説です。

  • argumentsオブジェクト
  • apply()
  • .on()
$(function() {
    $(document).on("click", ".targetA", returnFunc(hoge));
    $(document).on("click", ".targetB", returnFunc(fuga));
});

var returnFunc = function () {
    var func = arguments[0];
    
    return function applyImpl () {
        return func.apply(this, new Array(2, 3));
    };
};

var hoge = function(foo, bar){
    result = foo + bar;
    alert(result);
}
var fuga = function(foo, bar){
    result = foo * bar;
    alert(result);
}

上記のコードはどのように動くだろうか。

See the Pen GBbNgN by gekka (@gekka9) on CodePen.

というように動作する。

今回はこのコードを読んでいく。
説明のため、各所に番号を振ったものを以下に掲載する

$(function() {
    $(document).on("click", ".targetA", returnFunc(hoge)); // [1] 関数を収めた変数を引数として渡す
    $(document).on("click", ".targetB", returnFunc(fuga));
});
var returnFunc = function () {
    var func = arguments[0]; // [2] argumentsによる引数の受け取り
    
    return function handler () {
        return func.apply(this, new Array(2, 3)); // [3] apply()による関数の呼び出し
    };
};

var hoge = function(foo, bar){
    result = foo + bar;
    alert(result);
}
var fuga = function(foo, bar){
    result = foo * bar;
    alert(result);
}

目的

今回のコードリーディングの目的は、このクリックイベントの処理内で新たに event と this を使った追加の処理を挿入することである。
ざっくり読んでもわからなかったので、深く読んでみることになった。

もくじ

  • [1] 関数を収めた変数を引数として渡す
  • [2] argumentsによる引数の受け取り
  • [3] apply()による関数の呼び出し

[1] 関数を収めた変数を引数として渡す

$(function() {
    $(document).on("click", ".targetA", returnFunc(hoge)); // [1] 関数を収めた変数を引数として渡す
});

.on() の引数は以下のようになる。

第1引数 イベント種別 今回はclickなのでクリックしたときに発火する。
第2引数 セレクタ(オプション) 今回は targetA というクラスを指定する。※今回はイベントデリゲートの話はしません。
第3引数 データ (オプション) イベントハンドラにデータを渡すことができる。今回は省略されている。
第4引数 ハンドラ イベントハンドラを設定する。

したがって、ここで行っているのは、targetA というクラスを持つ要素に処理をバインドする処理である。

コード中のイベントハンドラとして記述している部分に着目すると、何やら関数を呼び出しているようだ。
この関数の定義を読んでみよう。

var returnFunc = function () {
    var func = arguments[0]; // ← ここはあとで解説する
    
    return function handler () {
        return func.apply(this, new Array(2, 3)); // ← ここもあとで解説する
    };
};

着目してほしいのは4行目のreturn文で、この関数は関数を返すものであることがわかる。
この関数によって返却されてきた関数こそがハンドラとしてバインドされることになる。
最終的に、バインドされるのは handler() 。

つまり、このクリックイベントを取得したい場合や、クリックされた要素自体を $(this) として掴むには、ここを以下のように修正すればよいことになる。

$(function() {
    $(document).on("click", ".targetA", returnFunc(hoge));
});
var returnFunc = function () {
    var func = arguments[0];
    
    return function handler (event) { // ← イベントを引数として取得
        piyo = $(this); // ← クリックされた要素自体の取得
        return func.apply(this, new Array(2, 3)); 
    };
};

なんと、$(this) が関数を生成する関数の、しかもreturn文の中に入ってしまった
これは良くない。なにか他の書き方ができないか、もっと読んでみよう。

ちなみに、よくある簡素な書き方では、

$(function() {
    $(document).on("click", ".targetA",function(event){
        piyo = $(this);
    });
});

というように、ネストの中で記述することができる。

[2] argumentsによる引数の受け取り

$(function() {
    $(document).on("click", ".targetA", returnFunc(hoge));
});
var returnFunc = function () {
    var func = arguments[0]; // [2] argumentsによる引数の受け取り
    
    return function handler () {
        return func.apply(this, new Array(2, 3)); // ← ここはあとで解説する
    };
};

これは関数を定義して変数に収める記述だが、よく見ると関数定義に引数がないにも関わらず呼び出し時に変数を渡している。
そしてこれは、渡された変数をちゃんと使っている。

argumentsオブジェクト

JavaScriptの関数では、argumentsというローカル変数が提供される。
この中には様々なデータが入っているが、単純に連番の添字を見た場合には引数が入っている。
argumentsの中には定義した引数の数に関わらず(たとえゼロでも)呼びだれたときに渡されたものをそのまま保持している。
なのでたとえば、

for (  var i = 0;  i < arguments.length;  i++  ) {
    console.log ( arguments[ i ] );
}

というようにループすることができる。
おそらくこれが本来の使い方で、決して引数を定義する必要がないわけではない

arguments の中には他にも、呼び出し元関数を収めた arguments.caller や 呼び出し先関数(つまり arguments がローカル変数として所属する関数自身)を収めた arguments.callee などがあった
現在は caller は削除、 callee は非推奨となっている。
callee は無名関数での再帰処理に使うことができた。本来名前がないから再帰ができない無名関数だが、arguments.callee にはきちんと収まるため、arguments.callee を擬似的な識別子として扱えることを利用できた。

しかし、この arguments はスマートな処理追加の解法とは関係なさそうだ。

[3] apply()による関数の呼び出し

$(function() {
    $(document).on("click", ".targetA", returnFunc(hoge));
    $(document).on("click", ".targetB", returnFunc(fuga));
});
var returnFunc = function () {
    var func = arguments[0];
    
    return function handler () {
        return func.apply(this, new Array(2, 3)); // [3] apply()による関数の呼び出し
    };
};

var hoge = function(foo, bar){
    result = foo + bar;
    alert(result);
}
var fuga = function(foo, bar){
    result = foo * bar;
    alert(result);
}

JavaScript での関数の呼び出しは、 単純に関数名に引数を渡すように書くことで呼び出すが、それよりアドバンスな呼び出しをする関数が提供されている。

apply(thisArg, argsArray)

apply関数は、JavaScriptの関数オブジェクトが持っている関数なので、関数なら基本的に使用できる。

$(function() {
    $(document).on("click", ".targetA", returnFunc(hoge)); // hoge() を渡している
});
var returnFunc = function () {
    var func = arguments[0]; // hoge() を受け取っている
    
    return function handler () {
        return func.apply(this, new Array(2, 3)); // hoge() をapply() を使って呼び出す
    };
};

// このコードでの呼び出し先関数 hoge() の定義
var hoge = function(foo, bar){
    result = foo + bar;
    alert(result);
}
第1引数 this ここで渡したオブジェクトが呼び出し先の関数内で this を使って参照するオブジェクトとなる。
第2引数 引数の配列 呼び出し先関数の引数を収めた配列

これは、たとえばすでに配列になっているものを分解して引数として渡す手間を省いたり、既存の関数を非破壊的に上書きすることに使われる。

今回の記述を追いかけてみると、[1] の章で書いたとおり、handler() 関数はハンドラなので、 this にはクリックされた要素自体が入っていることが想定されるものである。
なので、handler() にとっての this をapply() を使って呼び出し先に渡してやることで、呼び出し先にとっての this が handler() にとっての this と同じものになる。
第2引数として渡した配列長2の配列はそのまま呼び出し先関数の第1引数・第2引数となり、キモとなる演算(ここでは加算や乗算)が行われる。

これらを踏まえると、

$(function() {
    $(document).on("click", ".targetA", returnFunc(hoge));
});
var returnFunc = function () {
    var func = arguments[0];
    
    return function handler (event) { 
        piyo = $(this); // ← これと
        return func.apply(this, new Array(2, 3)); 
    };
};
var hoge = function(foo, bar){
    piyo = $(this);// ← これ
    result = foo + bar;
    alert(result);
}

このコメントアウトした2行の $(this) は等価になる。
なので、[1] の章 で書いたように $(this) を handler() の中で使うように書くよりもむしろ handler() では書かずに、呼び出し先変数で書くべきだろう。

しかし、この修正では残念ながら event を呼び出し先変数に渡すことはできなかった。
正確には、 apply() の引数として追加してやればよいが、そのためには既存の関数に引数の定義を追加しなくてはならない。
解決したのは this を使う処理部分だけだが、 event を使った処理は大したことない上に仮に呼ばれすぎでも問題はない処理なので、これで良いこととしてしまった。

まとめ

結局このコードはなんだったのか

$(function() {
    // returnFunc() に hoge() や fuga() という関数を渡す
    // returnFunc() は渡された引数を使ってハンドラを動的に生成し、返す
    // 返ってきた関数はハンドラとしてバインドされ
    $(document).on("click", ".targetA", returnFunc(hoge));
    $(document).on("click", ".targetB", returnFunc(fuga));
});

// ハンドラを作成して返す関数の定義
var returnFunc = function () {
    // argumentsによる引数(中身は関数)の受け取り
    var func = arguments[0]; 
    
    // ハンドラ関数を生成して返却
    return function handler () {
        // apply() を使って呼び出すことで、呼び出し先に this を引き回す
        return func.apply(this, new Array(2, 3));
    };
};

// キモとなる演算を行う関数の定義
var hoge = function(foo, bar){
    result = foo + bar;
    alert(result);
}
var fuga = function(foo, bar){
    result = foo * bar;
    alert(result);
}

とこのように、バインド時にイベントハンドラの動的生成をして event や this をさらにその先へ引き回す、というコードであることがわかった。

結局私がしたい修正は、 event や this を使って追加の処理をしたかっただけだったので、[3] の章のように呼び出し先関数を修正することで対応した。

コードリーディングを終えて

確かに動的にハンドラを生成するためだったり重複コードをまとめるのにはこの手法は有効であると感じたが、読むのに時間がかかってしまった。

なかでも this の引き回しは修正の難易度が高かった。
最初に修正したのは handler() の中だったが、異なる呼び出し先をこれから呼ぼうという場所で書くは憚られ、結果として呼び出し先変数でも書けることがわかったので良かったが、気づいていなければかき混ぜてしまう修正になりかねなかった。

確かにスマートな書き方だがメンテナンスが楽じゃないという点では微妙で、私がPMならレビューで弾く、そんなコードだった。

PHP : エクスクラメーションマーク2つによる二重否定

月花です。
ちょっとコードリーディングをしていて、なんだこれって思ったのでまとめてみます。

<?php
$hoge = !!$foo;

というように、PHPでexclamation markによる二重否定( double not ) を行うコードが見つかった。
最初は double not だと思っていたのだが、実はどちらかというと double not ではなく twice not で、「二重否定」というよりは「否定の二重適用」というような挙動だった。

毎度のことながらここまでは検索用の文なので、以降は日本人に戻ってカタカナで書きます。

  • エクスクラメーションマーク2つによる二重否定
  • なぜ二重否定でboolean型へのキャストとなるのか
  • PHPでの比較演算
  • 他のboolean型への変換手法
  • 結論:どれ使ったら良いの

エクスクラメーションマーク2つによる二重否定

<?php

$hoge = !!$foo;

というようなコードを見つけた。
2文字の演算子なので、一見比較演算子か論理演算子かと思ったのだが、コードの文脈上違うようだ。
また、代入を行っているので、なんらかのキャストのようだと思ったのだが、結論から言うと

<?php

$hoge = (bool)$foo;

と同義のようだ。

なぜ二重否定でboolean型へのキャストとなるのか

二重否定は、このように置き換えられる。

<?php

$hoge = !( !$foo );

否定を行う論理演算子を一度適用し、さらにもう一度否定している流れである。
PHPではエクスクラメーションマークによる否定ではboolean型への暗黙的なキャストが行われ、boolean型にキャストされたものに対してもう一度否定をしてやることで、キャストしつつ元々の結果に反転させることで、遠回りにキャストを実現している。
冗長に書き直すと、

<?php

$hoge = !$foo;    // 暗黙的なキャストと論理否定
$hoge = !$hoge; // 2回目の論理否定

となる。

これは、

<?php

$hoge =1;
var_dump($hoge);              // int(1)
var_dump($hoge . '');         // string(1) "1"

というような変換と似た、演算での暗黙変換を利用するキャストの手法である。

PHPでの比較演算

では、2つの変数の比較結果のbooleanを変数に押し込めたい時、どうすればよいだろうか。
PHPでは比較演算の結果は必ず返却されるため、if文の条件式中で使うだけではなく変数に代入することもできる。
つまり、

<?php

if( $hoge == $piyo ){
    $result1 = true;
}else{
    $result1 = false;
}

$result2 = ($hoge == $piyo) ? true : false;

$result3 = ($hoge == $piyo);

これらはすべて等価である。

他のboolean型への変換手法

二重否定によるキャストは、単純に2つ書いただけでそんなことが可能であると知らなければ読めないので、いささか可読性に欠けると言える。
では、他にスマートな書き方はないだろうか。
以下は全て二重否定と等価の書き方である。

<?php

// 二重否定
$hoge = !!$foo;

// 明示的キャスト
$hoge = (bool)$foo;

// 変換関数
$hoge = boolval( $foo );

// ifの利用
if( $foo ){
    $hoge = true;
}else{
    $hoge = false;
}
$hoge =$foo ? true : false;

// trueとの比較 (falseとの比較もできる)
if( $foo == true ){
    $hoge = true;
}else{
    $hoge = false;
}
$hoge = ($foo == true) ? true : false;
$hoge = ($foo == true);

// trueとの論理積
$hoge = ($foo and true);
$hoge = ($foo && true);

もっとありそうだけど、思いつくのはこれくらい。
結局読みやすくてスマートなのは、boolval() 、 (bool)、trueとの比較、 三項演算子かな、と思う。
論理積はスマートだけど、他に良い方法あるし、これをあえて選ばなくても、というくらいかな。

リテラルへの適用

ちなみに、$fooの部分はリテラルであってもよい。

<?php

$hoge = !!'0';
$hoge = !!0;
$hoge = !!(1+2+3);

ここで、ちょっとガチャガチャとやってみよう。
PHPでは、0以外の数字、及び、'0'以外または''以外の文字列リテラルはtrueとなる。
これらを . 演算子で繋ぐことによって、整数リテラルを文字列リテラルに変換することができる。
では、これと二重否定を絡めてみよう。

<?php

$foo = 1;                     // 整数リテラル
$hoge = !!( $foo.'' );  // 整数リテラルを文字列リテラルにして、それを二重否定してboolean
var_dump( $hoge );  // bool(true)

この通りたった3行で型がゴシャゴシャになるので、絶対にやるなよ!!!!!!
そんなことしねえと思ったそこのお前!!!!!
長いこと煮詰まったコードではこれらが分散して、たとえば$fooの初期化が120行目、リテラル変換が140行目、二重否定が170行目にそれぞれあったりするんだからな!!!!その間に煩雑なコードが追加されていったりするから$fooについて追うまで気づかないんだ!!!!!
絶対にこの手の成長をする種を蒔くな!!!!!!!

結論:どれ使ったら良いの

これはチームが採用している規約などに応じて変えるべきだとは思うが、規定がないのであれば、ボトムの実力に合わせるべきである。
教科書的なif文しか理解されそうもない、という環境ではやはり

<?php

if( $foo == true ){
    $hoge = true;
}else{
    $hoge = false;
}

が強いと思う。
これは教科書みたいな技術書にも最初の方に書いてあるようなif文でしかないので。

それ以上の実力があるなら、キャストかboolval()の利用。

演算結果を変数に押し込めることを知っているレベルなら、三項演算子論理積が強いと思う。
見た目にもtrueとの演算であることがすぐわかり、そもそもtrueとfalseの文字列が出てくるのでgrepや目検にも強いという有用性がある。
もしくは、何らかの定数が導入されて比較対象を変える場合にも、明示的に比較演算子やtrueなどbooleanリテラルがコードに現れているとわかりやすい。
二重否定は、それがシンタックスエラーではなく成立するということを知らない限り使えない局所的な知識なので、推奨はできない。

PostgreSQL : 9.4より古いなら、マテビューをFROM句に入れるビューは良くない

月花です。

PostgreSQL の MATERIALIZED VIEW を REFRESH すると、 その MATERIALIZED VIEW を FROM句とする VIEW がどうなってしまうのか、
ということをよく知らずに、ばかなことをしてしまったなー、という記事です。
これは検索ワードのための文なので、ここから日本人になります。

  • なにをしたのか
  • 前提
    • マテビュー
    • ビュー
  • マテビューのリフレッシュとロック
    • リフレッシュ時のロック対策
    • あるビューのFROM句にいるマテビューをリネームするとどうなるのか
  • 実際に何が起こっていたか
  • 解決したかった
  • じゃあどうすればよかったのか

なにをしたのか

あるマテビューを作り、そのマテビューをFROM句とするビューを作った。
そこで、ロジックで分岐させてどのビューを読むのか、というようなことをしていた。

使っていたpostgresのバージョンが9.4より古かったので、マテビューのリフレッシュ時は ACCESS EXCLUSIVE LOCK という、SELECTすら許さない強いロックがかかる。
それを嫌って、同じマテビューを2つ用意して交互にリフレッシュとリネームをしながら、常にリフレッシュしていないマテビューが存在するようにしていた。

そう、マテビューをFROM句とするビューがあるとき、そのマテビューをリネームするとビューはどうなるのか。実装時の私は、全く気づいていなかったのである。

前提

ここではサンプルとして、下記のような構成とする。

マテビュー

商品テーブルで、名前とカテゴリ、値段を持っている。
この話で重要なのはカテゴリだけなので、他はてきとう。
sample_products

product_id name category_id price
1 apple 1 100
2 beef 3 110
3 carrot 2 120
4 durian 1 130
5 eggplant 2 140
6 fish 3 150
7 grape 1 160

ビュー

商品マテビューのカテゴリごとにビューが作ってある。
なんでだろうね?インデックス貼っとけば速いしviewじゃなくていいじゃんなんだけど、サンプルってことで許して欲しい。

CREATE VIEW sample_view_fluits AS
SELECT * FROM sample_products WHERE category_id=1;

CREATE VIEW sample_view_vegetables AS
SELECT * FROM sample_products WHERE category_id=2;

CREATE VIEW sample_view_meats AS
SELECT * FROM sample_products WHERE category_id=3;

マテビューのリフレッシュとロック

postgresのバージョンが9.4よりも古い場合、マテビューをリフレッシュする際には、そのマテビューにACCESS EXCLUSIVE LOCKという強い排他ロックがかかる。
つまり、リフレッシュ中は他からのSELECTを待たせることになる。
大した事ないマテビューならよいが、10秒もユーザは待ってくれないので、対策が必要となる。

リフレッシュ時のロック対策

いろいろやり方はあるのだが、私がとったのは、2つのマテビューを交互にリフレッシュしていく方式。
まず全く同じ構成のマテビューを2つ用意し、片方をリザーブとする。
その上で、リフレッシュの際にはリザーブだけ更新し、リザーブとそうでない方をリネームで入れ替える。
すると、ビジネスロジックから発行されるクエリは常にリザーブでない方をSELECTするため、ロックの対象にはならない、という方式である。

これは具体的には、下記のようなクエリになる。

BEGIN;

REFRESH MATERIALIZED VIEW sample_products_reserve;

ALTER MATERIALIZED VIEW sample_products RENAME TO sample_products_temp;
ALTER MATERIALIZED VIEW sample_products_reserve RENAME TO sample_products;
ALTER MATERIALIZED VIEW sample_products_temp RENAME TO sample_products_reserve;

COMMIT;

このときの流れは、元々のマテビューのバージョンが1で、リフレッシュすると1増えて2になると表現すると、下記のようになる

sample_products_reserve のリフレッシュ( 1 -> 2 )

sample_products( 1 ) -リネーム-> sample_products_temp( 1 )
sample_products_reserve( 2 ) -リネーム-> sample_products( 2 )
sample_products_temp( 1 ) -リネーム-> sample_products_reserve( 1 )

そして結果的に、バージョンが1つ上がった sample_products と元のバージョンのままの sample_products_reserve が残る。
tempは交代のためのただのプレースホルダなので、処理が終わればもう存在しない。

次に同じ処理が走ると、下記のようになる。

sample_products_reserve のリフレッシュ( 1 -> 3 )

sample_products( 2 ) -リネーム-> sample_products_temp( 2 )
sample_products_reserve( 3 ) -リネーム-> sample_products( 3 )
sample_products_temp( 2 ) -リネーム-> sample_products_reserve( 2 )

つまり、常に最新バージョンの sample_products と ひとつ古いバージョンの sample_products_reserve が存在するサイクルとなる。
これらをトランザクションに包み込んでしまえば、うまくいくはずだった。

あるビューのFROM句にいるマテビューをリネームするとどうなるのか

あたりまえの話だけど、ひも付けが変わる。

たとえば、前章で紹介したビューの、

CREATE VIEW sample_view_fluits AS
SELECT * FROM sample_products WHERE category_id=1;

をみると、先程のサイクル上にいるマテビューをFROM句としている。
このマテビューがあのサイクルにのるとどうなるのかを、このFROM句に対応するマテビューに☆印をつけることで追いかけてみる。

sample_products_reserve のリフレッシュ( 1 -> 2 )

☆sample_products( 1 ) -リネーム-> ☆sample_products_temp( 1 )
sample_products_reserve( 2 ) -リネーム-> sample_products( 2 )
☆sample_products_temp( 1 ) -リネーム-> ☆sample_products_reserve( 1 )

なんと、 sample_products_reserve( 1 ) になってしまったのである。
ちなみに、次のサイクルでは、

☆sample_products_reserve のリフレッシュ( 1 -> 3 )

sample_products( 2 ) -リネーム-> sample_products_temp( 2 )
☆sample_products_reserve( 3 ) -リネーム-> ☆sample_products( 3 )
sample_products_temp( 2 ) -リネーム-> sample_products_reserve( 2 )

どういうことかというと、マテビューがリネームされても、そんなマテビューないよってことにならないように、追跡してくれるのだ。
あったりまえじゃ〜ん。

他言語によるビジネスロジックからの読み込みはテーブル名による参照で、ビューからの読み込みは内部参照値によるものなのだ。
だから、ビジネスロジックからマテビューを検索するぶんにはこれでよい。だけど、ビューがこのマテビューを使う場合は話が違ったということだ。

そしてこの流れを繰り返すと、このビューは、奇数回ではバージョンが上がらず、偶数回で一気に2段階バージョンが上がる奇妙なビューとなった。
奇数回のリフレッシュは実質意味がない。
その上、これがやっかいなのだが、偶数回ではビューが見てしまっているリザーブに対してそのままリフレッシュをしてしまう。
つまり、あれだけ避けたくてローテーションまで組んだ ACCESS EXCLUSIVE LOCK が、実際には2回に1回発生していたことになる。
悲しい以外の言葉がない。

実際に何が起こっていたか

  • なんか反映遅いよね
  • 2回連続でリフレッシュすると変わる気がする
  • よくわかんないねえ
  • とりあえずこのマテビューに修正入るんで確認します
  • あ〜〜〜そういうことか!!!!!

解決したかった

とりあえず一旦の対策として、リフレッシュして3回リネーム後、改めてビューを再定義して、リザーブじゃないほうに紐付けるとしてみた。

deadlock detected

あ〜あ

たぶん、ビューの再定義でローテーションせず単純に CREATE OR REPLACE VIEW を使ったので、検索されてきたSELECT文が ACCESS SHARE LOCK を取っている間に、この再定義が ACCESS EXCLUSIVE LOCK を取り、 ハマってしまったのではないか、と予想している。
幸いにもすぐロックは解除され、無限にガッチリハマってしまったというほどの事故ではなかったのだが、それは混雑具合に左右されうる話。ちょっと許容できなかった。

じゃあどうすればよかったのか

どうすればいいんですか?

  • 9.4以降にバージョンアップする
  • そもそもマテビューをFROM句とするビューを作らない
    • DB設計を最初からちゃんとする

のどっちかが正解っていうか両方やるのが正解だと思う。
なかなかうまくはいかないけど。

Brackets拡張機能を作った話1 パネルを追加・表示するまで

月花です。
Brackets拡張機能を作ってみたので、その話をします。

この記事で扱う範囲

最終的に搦め手になり、いろんな場所をいじったので記事が長くなるため、3つくらいに分ける。
この記事では、Brackets拡張機能を追加し、機能が搭載されていないパネルを表示するまでとする。
次の記事で機能の紹介をし、最後の記事で実際に拡張機能として搭載して完成するまでの話をする。

環境

High Sierraにはアップデートしてないんですけど、多分動くと思う。
ていうかアップデートに1時間以上かかるオフィスから持ち帰れないPC、どうやってアップデートしたらいいんですか?

どんな拡張機能を作ったか

f:id:gekka9:20171128013312p:plain
// なんか動画にしようと思ったけど、よくわからなかったので静止画です。伝わってください。

BracketsからiTunesを操作することで、コーディングをしながら1画面で快適なミュージックライフをサポートする。
音楽を全部詰めてぐちゃぐちゃなシャッフルをするタイプなので、ウィンドウ切り替えたりスマホいじったりで中断するのが嫌だったので。

手法と問題点

JXA ( JavaScript for Automation ) を使う。
developer.apple.com

これまで、スクリプトAutomatorからアプリを操作する場合はAppleScriptのみが対応していたが、JavaScriptでも書けるようになった。
ちょっと使ってみようかな、という動機しか無く、最終的にJavaScriptである必要はなくなった。

当初は、BracketsJavaScriptでできていて、かつアプリなので、横断してそのまま呼べるんじゃないかと思っていたが、そうはいかなかった。
というのも、Brackets自体がブラウザであるようで、エディタなどの機能はその上で動くフレームワークに過ぎなかった。
であれば、ブラウザからローカルのアプリが動かせるというのはセキュリティ的に危険極まりないのでできるわけがなかった。
そのため、搦め手で実装することにした。

Brackets自体からでは無理だが、権限のあるユーザが、適切なスクリプトを動かせばよい。
具体的には、mac標準のapacheでローカルサーバを建て、その中のPHPからJXAで書いたjsファイルを実行する。
BracketsからこのPHPへは、ajaxによる非同期通信で行う。
この流れであれば、サーバ上のバッチが動くのと同義になるため、動かせるはずだ。というか、動かすことができた。

拡張機能の作り方

基本的にはBracketsAPIのページを参考に組み立てていく。
Brackets API

BracketsJavaScriptフレームワークなので、言語は全てJavaScriptとなり、画面上の全てはHTMLである。
デバッグ機能は、メニューの デバッグ > 開発者ツールを表示 で立ち上がる。
どうもChromiumで動いているようで、Chromeの開発者ツールとほぼ同じものが立ち上がった。
ということは console.log() などが呼べるので、変数ダンプもらくちんで非常に助かる。

さて、実際に作っていこう。
拡張機能の雛形のようなものを探してみたが見当たらなかったので、既存の拡張機能を拝借して、中身を書き換えることで実装するのが楽だと思う。
ドキュメントとにらめっこして、作りたい拡張機能に必要なモジュールを追加したり、逆に削除していけばいい。

ファイル構成はこんな感じになる。

extensions/
    user/
        html/
            assets/
                css/
                    iTunesController.css
            panel.html
        main.js
        package.json

このうち、必須なのは main.js と package.jsonで、これらはそれぞれ、拡張機能のメイン関数と情報定義を行う。
公開しないのであれば、package.jsonは適当でいいんじゃないかと思う。
公開するなら、説明文とかライセンスはキッチリ書こう。この内容が拡張機能検索画面に載ると思う。

main.js に書くべきこと

ここでは、紹介した画像のような、エディタの下にパネルを追加する形式の拡張機能を作る際に、どのような手順で何を書いていくかを説明する。
ざっくりと次のようなフェーズに分ける。

  • 依存モジュール読み込みフェーズ
  • 設定読み込み・登録フェーズ
  • コマンド設定・登録フェーズ
  • テンプレート読み込みフェーズ
  • 必須関数定義フェーズ
  • ユーザ関数定義フェーズ

これらはすべて、main.js 内の define() 内で行う。

define( function( require, exports, module ) {
    'use strict';
});

すなわち、このdefine() が、拡張機能のメイン関数となる。
use strict とあるが、厳格モードが必要かどうかはよくわかっていないが、よほど汚いコードを書かざるを得ない場合でもない限り、厳格モードでもさほど問題はないだろう。いつものようにキレイに書こうな!
なお、これ移行のサンプルコードはすべて define() 内に収まっていると思ってほしい。

依存モジュール読み込みフェーズ

まずは、これ移行の処理を行っていくために、Bracketsフレームワーク(以下このように呼称するが、正式な名称ではない)の必要なモジュールのインスタンスを確保する。

    var Menus = brackets.getModule( 'command/Menus' ),
        CommandManager = brackets.getModule( 'command/CommandManager' ),
        PreferencesManager = brackets.getModule( 'preferences/PreferencesManager' ),
        WorkspaceManager = brackets.getModule( 'view/WorkspaceManager' ),
        Resizer = brackets.getModule( 'utils/Resizer' ),
        AppInit = brackets.getModule( 'utils/AppInit' ),
        ExtensionUtils = brackets.getModule( 'utils/ExtensionUtils' );

Brackets API
こんな感じで、API辞書とにらめっこして、必要なものを読み込んでいこう。
大体名前から何をするものかはわかるので、必要そうな単語で検索して、たとえばパネルの追加なら、WorkspaceManager のページを開いて、 add とか create とかでさらに絞り込んでいけば、パネルの追加に必要な関数がわかる、という具合だ。
そのページには、そのモジュールが依存しているモジュールも記載されているので、それも読み込む必要があるが、なくても動いたりする。

テスト実行時に管理者ツールを起動しておくと、足りなかったときはエラーが出るので、それで潰していってもよい。

設定読み込み・登録フェーズ

たとえば、パネルを開きっぱなしにして閉じて、再度開いた時に、この設定に状態を書き出しておけば、以前の状態に復帰できる。

    var preferences = PreferencesManager.getExtensionPrefs( 'iTunesController' );
    preferences.definePreference( 'enabled', 'boolean', false );

必要な数だけ登録しておくとよい。
この拡張機能の場合は、開いているか閉じているか、だけとなった。
実際にパネルを開く際に、定義した設定に書き込んだり読み込んだりするので、今はどんな名前の設定を保持したいか定義するだけ。

コマンド設定・登録フェーズ

ユーザがメニューから何か選んで実行する際には、コマンドという形式で呼ばれてくる。
なので、今回はパネルを開いたり閉じたりするトグルコマンドを定義する。

    var COMMAND_ID = 'iTunesController.enable';
    CommandManager.register('iTunesController' , COMMAND_ID, togglePanel );

CommandManager.register() の第三引数は、メニューが押下された際に実行する関数で、無名関数としてもいいと思う。
関数として定義する場合は、define() の中で行おう。

次に、定義したコマンドをメニューに追加する。
このアプリでは、Macでいうところの上部にあるあのテキストメニューと、エディタ画面の右端にあるアイコンメニューに登録する。

    var menu = Menus.getMenu( Menus.AppMenuBar.VIEW_MENU ),
        contextMenu = Menus.getContextMenu(Menus.ContextMenuIds.PROJECT_MENU);
    if ( menu !== undefined ) {
        menu.addMenuDivider();
        menu.addMenuItem( COMMAND_ID, '' );
    }
    if ( contextMenu !== undefined ) {
        contextMenu.addMenuDivider();
        contextMenu.addMenuItem( COMMAND_ID );
    }

これで、メニュー上に iTunesController という名前のトグルボタンが追加された。

テンプレート読み込みフェーズ

パネルやアイコンのデザインを定義するテンプレートやCSSを読み込む。
htmlファイルにしてもいいし、DOMを作って叩き込んでもよい。

    var controllerPanelTemplate = require( 'text!html/panel.html' ),
        controllerPanel,
        projectUrl,
        controllerIcon = $( '<a href="#" title="' +EXTENSION_NAME + '" id="itunes-controller-icon"></a>' );
    // Load stylesheet.
    ExtensionUtils.loadStyleSheet( module, 'html/assets/css/iTunesController.css' );

これはあくまでもテンプレートのため、動的に動く部分はJavaScriptでDOM操作を行い、表示していく。
なので、ボタン類以外は空の、構造だけのテンプレートでよい。
もしくは、テンプレートエンジン Mustache を標準で扱えるようなので、大規模になる場合はそれで実装するのがいいだろう。

<div id="itunes-controller" class="itunes-controller bottom-panel vert-resizable top-resizer todo-file">
    <div class="toolbar simple-toolbar-layout">
        <div class="title">iTunes Controller</div>
        <div id="itunes-controller-playing">
            <i class="fa fa-folder-open-o" aria-hidden="true"></i> <span id="album"></span> / 
            <i class="fa fa-user" aria-hidden="true"></i> <span id="artist"></span> / 
            <i class="fa fa-music" aria-hidden="true"></i> <span id="name"></span>
        </div>
        <a href="#" class="close">&times;</a>
    </div>
    
    <div class="table-container resizable-content">
      <div id="itunes-controller-controller">
        <a id="itunes-controller-backward"><i class="fa fa-backward" aria-hidden="true"></i></a>
        <a id="itunes-controller-play"><i class="fa fa-play" aria-hidden="true"></i></a>
        <a id="itunes-controller-pause"><i class="fa fa-pause" aria-hidden="true"></i></a>
        <a id="itunes-controller-forward"><i class="fa fa-forward" aria-hidden="true"></i></a>
      </div>
    </div>
</div>

この拡張機能では、こんな感じのテンプレートになった。
画像を作るのがめんどくさいので、FontAwesome がしれっとでてきているけど、本筋ではないのでファイルツリーとかサンプルコードとしては記載しない。
fonts ディレクトリ作って、CSS追加で読み込むだけ。

必須関数定義フェーズ

前項までで、ようやく定義が終わり、ここからは実際にビジネスロジックを書いていく。
このフェーズでは、今回の拡張機能に必須な関数を定義していく。
今回は1つだけ、Brackets が ready になった際に呼ばれる関数を実装する。

    AppInit.appReady( function() {
        var panelHTML = Mustache.render( controllerPanelTemplate, {} );

        WorkspaceManager.createBottomPanel( 'itunesController.panel', $( panelHTML ), 50 );
        controllerPanel = $( '#itunes-controller' );
        controllerPanel
            .on( 'click', '.close', function() {
                enablePanel( false );
            } );

        setNowPlaying();
        setInterval(function(){
            setNowPlaying();
        },5000);
        $('#itunes-controller-backward').on('click',function(){
            controll('previous');
            return false;
        });
        $('#itunes-controller-play').on('click',function(){
            controll('play');
            return false;
        });
        $('#itunes-controller-pause').on('click',function(){
            controll('pause');
            return false;
        });
        $('#itunes-controller-forward').on('click',function(){
            controll('next');
            return false;
        });

        controllerIcon.click( function() {
            CommandManager.execute( COMMAND_ID );
        } ).appendTo( '#main-toolbar .buttons' );

        if ( preferences.get( 'enabled' ) ) {
            enablePanel( true );
        }
      
    } );

まずはパネルのレンダーと、パネルの create 、そして各種リスナーの定義を行っている。
setNowPlaying() や controll() はユーザ関数で、詳しくは次の記事で扱うが、ここでは基本的にリスナーの定義・登録に留めるとスッキリすると思う。
setNowPlaying() は現在再生中の曲を取得する関数で、初回実行による初期化と5秒ごとのタイマー実行の定義を行っており、定義の範疇からは抜け出していない・・・と思っている。
その後、アイコンメニューへのリスナー追加と、設定ファイルから前回の情報を読み出して、パネルを開いておく、ということをやっている。

ユーザ関数定義フェーズ

前項でいくつか呼び出していたような、独自関数の定義を行う。
ビジネスロジックは基本的にここで書くべきだと思う。
今回は下記のような独自関数を実装した。

  • enablePanel() : 実際のパネル表示の切り替え
  • togglePanel() : 現在の開閉状況に従い、 enablePanel() を呼び出す
  • setNowPlaying() : 現在再生中の曲を取得し、パネルへ記載する
  • controll(string) : 再生や一時停止、前後の曲への移動などの操作を行う

iTunes 操作関連は JXA が絡み、ややこしいので次の記事で詳しく話す。

まとめ

ここまでで、ビジネスロジックを抜きにして、ユーザ関数の定義やリスナー追加もふっ飛ばせば、とりあえずパネルが開くだけの拡張機能ができているはずだ。
あとはガリガリビジネスロジックを載せていくだけとなる。

PostgreSQL : 正規化されたテーブルをSQLだけで入れ子のJSONにする

月花です。

SQL芸人としての活動のメモです。

今回は、正規化された一対多のテーブルから、多の方を一行を連想配列JSONにしながら、複数の行をJSONにする、入れ子構造のJSONを抽出します。
結果的になんてことない単純なクエリになったのですが、割とパズル的に悩んでしまったので、残しておきます。

3人の生徒に、それぞれに2回の試験があり、その合計6つの結果を生徒ごとに分割して描画したい、というシナリオで書いていきます。
例では、PHPでHTMLを出力することにします。

通常であれば、普通にJOINして6レコードをループし、
生徒ごとにまとめてさらに3回のループを行うか、
ソートしてから、前のループと生徒IDが違えば閉じタグと開きタグを出力する、
みたいなことになりますが、条件式多くて読みづらいです。
なので、生徒ごとにまとめるところまでSQLにやらせてしまいましょう。レコードの集約はJSONにすれば、言語に依存しません。

目次です。

  • テーブル構造
    • 生徒テーブル students
    • 試験テーブル exams
    • 生徒-試験リレーションテーブル student_exams
  • この例での求めたいデータ
    • 普通にJOINしてみた場合
    • 理想の抽出結果
  • アプローチ
    • 横に広がっている試験データを連想配列に集約する
    • 生徒ごとに連想配列にした試験データを集約する
  • 取れたレコードをPHPで扱う

テーブル構造のサンプル

例では、生徒テーブルと試験テーブル、そしてそれらを繋ぐ生徒-試験リレーションテーブルの3つのテーブルが存在しているとする。
そこから、生徒テーブルを軸にして、生徒数分のレコードの中に、複数の試験をまとめて収めるJSONを書いていく。

生徒テーブル students

student_id name
1 田中
2 佐藤
3 木村

試験テーブル exams

exam_id name date math_point japanese_point english_point
1 中間試験 2017-09-10 10 20 30
2 中間試験 2017-09-10 40 50 60
3 中間試験 2017-09-10 70 80 90
4 期末試験 2017-09-30 15 25 35
5 期末試験 2017-09-30 45 55 65
6 期末試験 2017-09-30 75 85 95

なお、本来であれば試験名と日付も正規化するべきであるが、今回の話にはそれほど影響しないので、簡略化のために今回は正規化しない。

生徒-試験リレーションテーブル student_exams

student_id exam_id
1 1
2 2
3 3
1 4
2 5
3 6

この例での求めたいデータ

普通にJOINしてみた場合

SELECT
    s.*,
    e.*
FROM
    students s
    JOIN student_exams se ON s.student_id=se.student_id
    JOIN exams e ON se.exam_id = e.exam_id;

で、こうなる

student_id name exam_id name date math_point japanese_point english_point
1 田中 1 中間試験 2017-09-10 10 20 30
2 佐藤 2 中間試験 2017-09-10 40 50 60
3 木村 3 中間試験 2017-09-10 70 80 90
1 田中 4 期末試験 2017-09-30 15 25 35
2 佐藤 5 期末試験 2017-09-30 45 55 65
3 木村 6 期末試験 2017-09-30 75 85 95

しかし、これでは、たとえばPHPでこれを処理する場合、愚直にやれば6回ループになり、テーブルならtrタグが6つできたりする。
そういうテーブルを描画するならそれでいいが、生徒ごとのブロックとして閉じタグを挟みたい場合に厄介となる。
なので、3つにどうにか収めるように配列を操作するループを別途書いたり、ソートしてから生徒が変わったかを検知するif文を書いたりしないといけないので、端的にいってクソである。
なので、3レコードにしたい。

理想の抽出結果

student_id name exam_json
1 田中 {試験内容のJSON}
2 佐藤 {試験内容のJSON}
3 木村 {試験内容のJSON}

こうなればよい。
このJSONには、例でいえば、配列長が2で、その中には試験のデータをいれた1つの連想配列がはいっていればいい。
3回のループで生徒ごとに区切りつつ、内部のループで2つの試験のデータを出力すればいいのだ。
この条件に合う結果のJSONは、たとえばこうなる。

"[
    {"student_id":1,"student_name":"田中",
     "exam_data":[
        {"exam_id":1,"exam_name":"中間試験","date":"2017-09-10","math_point":10,"japanese_point":20,"english_point":30},
        {"exam_id":4,"exam_name":"中間試験","date":"2017-09-30","math_point":15,"japanese_point":25,"english_point":35}
      ]
    },
    {"student_id":2,"student_name":"佐藤",
     "exam_data":[
        {"exam_id":2,"exam_name":"中間試験","date":"2017-09-10","math_point":40,"japanese_point":50,"english_point":60},
        {"exam_id":5,"exam_name":"中間試験","date":"2017-09-30","math_point":45,"japanese_point":55,"english_point":65}
      ]
    }
]"

長くなったが、このJSONを作り、上のレコードを抽出することを目的とする。

アプローチ

まず、これを実現するためには、2回の集約が必要となる。

  1. 横に広がっている試験データを連想配列に集約する
  2. 生徒ごとに連想配列にした試験データを集約する

横に広がっている試験データを連想配列に集約する

まずは、試験データを1レコードずつ連想配列に変換していく。
次のステップで生徒IDを使うので、ここで生徒-試験リレーションテーブルをJOINして、生徒IDも含めてしまおう。

student_id exam_id json
1 1 {試験内容のJSON}
1 4 {試験内容のJSON}
2 2 {試験内容のJSON}

(省略)

このためには、json_agg関数を用いる。
生徒を軸に、試験テーブルの1レコードごとに集約するために、GROUP BYには生徒ID、試験IDを指定して、試験テーブルのデータを集約する。
そのためのクエリは、単純に上記を落とし込んで、こうなる。

SELECT
    student_id,
    e.exam_id,
    json_agg(e) AS json
FROM student_exams se
    JOIN exams e ON se.exam_id=e.exam_id
GROUP BY student_id,e.exam_id
ORDER BY student_id ASC

すると結果は、このようになる。

student_id exam_id json
1 1 {[{"exam_id":1,"name":"中間試験","date":"2017-09-10T00:00:00","math_point":10,"japanese_point":20,"english_point":30}]
2 4 [{"exam_id":4,"name":"期末試験","date":"2017-09-30T00:00:00","math_point":15,"japanese_point":25,"english_point":35}]
3 2 [{"exam_id":2,"name":"中間試験","date":"2017-09-10T00:00:00","math_point":40,"japanese_point":50,"english_point":60}]

(省略)

ちなみに、json_agg関数の引数をカラムではなくテーブルにした場合、 table_name.* と同義になる。
すべてのカラムを出すしかないので、カラムを絞りたい場合は、サブクエリにするかWITH句で追い出しておく必要がある。

ここで、当初の目論見とはことなり、連想配列が配列長1の配列に入ってしまっていることに留意しておく。

生徒ごとに連想配列にした試験データを集約する

次に、生徒ごとに集約するので、今度はGROUP BYに生徒IDのみを指定し、再度json_aggにかけてやればよい。
postgresのjson_aggは内部でjson型を扱えるため、
JSONの中に「JSONの形をしたstring」が入ってしまい、最終的にjson_decodeした結果をjson_decodeする必要がある、
なんてことにならないようなツリー状にまとめてくれる。
そのため、そこのところは考慮せず、単純にさきほどのクエリをサブクエリとして、json_aggをかけてやるだけでよい。

SELECT
    student_id,
    json_agg(jsons.json) AS exam_json
FROM
    (
        --ここから
        SELECT
            student_id,
            e.exam_id,
            json_agg(e) AS json
        FROM student_exams se
            JOIN exams e ON se.exam_id=e.exam_id
        GROUP BY student_id,e.exam_id
        ORDER BY student_id ASC
        --ここまで先程のクエリ
    ) jsons
GROUP BY student_id;

これだけでよい。
そして結果は下記となる。

student_id exam_json
1 {[[{"exam_id":1,"name":"中間試験","date":"2017-09-10T00:00:00","math_point":10,"japanese_point":20,"english_point":30}], [{"exam_id":4,"exam_name":"期末試験","date":"2017-09-30T00:00:00","math_point":15,"japanese_point":25,"english_point":35}]]
2 [[{"exam_id":2,"name":"中間試験","date":"2017-09-10T00:00:00","math_point":40,"japanese_point":50,"english_point":60}], [{"exam_id":5,"exam_name":"期末試験","date":"2017-09-30T00:00:00","math_point":45,"japanese_point":55,"english_point":65}]]
3 [[{"exam_id":3,"name":"中間試験","date":"2017-09-10T00:00:00","math_point":70,"japanese_point":80,"english_point":90}], [{"exam_id":6,"exam_name":"期末試験","date":"2017-09-30T00:00:00","math_point":75,"japanese_point":85,"english_point":95}]]

生徒3人と試験6回のデータが3レコードとなって収まった。

SQLはここまでで、ここからはビジネスロジックのお話になる。
ここでは、PHPで行うとするが、JSONなので言語には依存しない。

取れたレコードをPHPで扱う

想定仕様は、生徒ごとに開きタグと閉じタグを出力し、生徒ごとのブロックを描画するものである。
取れたレコードを $records に入れたとすると、下記のようなPHPで扱う事ができる。

<?php

(省略)

// 生徒ごとのループ
foreach( $records as $record){
    // 開きタグ描画処理
    
    // jsonから配列に変換(オブジェクトに変換するなら第二引数は省略かfalse)
    $exam_array = json_decode($record['exam_json'], true);

    // 試験ごとのループ
    foreach( $exam_array as $exam_data_array ){
        // ここで、この中身は配列長が1のデータだったことを思い出そう
        // 仕様上そうなってしまうだけなので、先頭要素を取るだけで良い
        $exam_data = current( $exam_data_array );

        // $exam_data['name'] や、
        // $exam_data['date'] で、試験のデータが取れるので処理や描画
    }

    //閉じタグ描画処理
}

以上で、postgresに仕事をさせ、PHPに楽をさせることができた。

例では単純なものだったのでJOINが少なかったが、本来はもっと正規化してあるはずなのでサブクエリがごちゃつきがち。
WITH句やviewに、いかにわかりやすく追い出すかが出来るSQL芸人になれるかどうかのカギである。たぶん。

Air Mac Extremeを買った

月花です。
Air Mac Extremeを買いました。

www.apple.com


かねてより、ほしいとは思っていたのだ

  • ルータの調子が悪かった
  • macのバックアップしないとそろそろヤバイ
  • ファイルぽんぽんおける場所が欲しかった
  • PS4とつながればハッピー
    • ( 結局これはだめだった)

というわけで、購入時期が早くなったのだ。

ひととおりイカとFF14をしてみたが、ものすごく快適だ。
当然ルータを変えただけで上流はそのままなので、上り下りが速くなるわけではない。
深夜3時に130Mbpsしか出ないようなベストエフォート回線がルータのカタログスペックに追いつくことはないからそこはどうでもよい。

速度ではなく安定性が段違いによいらしく、イカをやれば22時前というピークタイムにも関わらずひっかかりなくキレッキレの動作をし、FF14をやれば逃げ遅れなどの失敗は自覚できたもののみだった。

これはよい買い物をしたぞと思ったしもっと早く買うべきだった。
まあでも最後まで読めばわかるけど、本当にMacユーザにしか値段相応の恩恵がなさそうなので、全体的には割とピーキーな感じ。

ルータの調子が悪かった

これはもう散々で、イカをすれば自分も味方も不審死し、FF14をやればギミックは失敗してタンクは気づけばHPがゼロ。
FF14しかやってなかったら単にヘタクソすぎワロタってだけなのだが、イカも不審とくれば、この2つはゲームシステムが違うのでまず間違いない。
イカもヘタすぎワロタって感じだけど、そこは無視しよう。

思えば長い付き合いだった。
就職して大阪に越してきたのが3年前の4月で、そのときに、ついでにルータを買い替えた。
とはいっても、安いルータから安いルータに変えただけで、へたをすれば前より安いルータになっていたかもしれなかった。
そこへいきなり2万のルータが黒船来航、すでにゴミ袋の中である。

macのバックアップしないとそろそろヤバイ

このMac Book Airとも長い。
購入時期がEarly2014に間に合わず、Mid2013の製品なので、そろそろ4年が経とうとしている。
2回ほどクリーンインストールはしているが、ハード的にそろそろだろう。

いよいよTime Machineか、というところだが、私にTime Machineを使いこなせると思わない。
ノートPCであるMac Book Airに外付けHDDを定期的に差し込み、バックアップをさせるという動作、絶対にしないからだ。
このAir Mac Extremeに接続した外付けHDDをTime Machine用のディスクとして設定できて、LANでつながっている限り、常にTime Machineがバックアップを取ってくれる。
これくらいしてくれないと私はバックアップをしない人間なんだ。もっと甘やかしてほしい。

PS4とつながればハッピーだった

Air Mac Extremeの裏にはUSBポートがあり、外付けHDDをNASのように扱える。
てきとうに軽く調べたら、どうもPS4が認識しそうなことが書いてあったんだけど、そうはうまくいかなかった。
そもそも、NASに対応しているのは、私が共有したかったキャプチャーギャラリーではなく、メディアプレイヤーだったのだ。

当初の筋書きとしては、
PS4スクリーンショットUSBメモリを使ってPCへ運ぶのが不便だから解決のためにNASであったのだが、やはりそれはだめだった。

まあどうせ置き場所近いし、そこまで頻繁にスクショ移さないし、外付けHDDの繋ぎ先を都度変えればいいだろうってことでそうした。
PS4のUSB端子は、キーボードとコントローラでふさがっているので、USBメモリを挿しっぱなしなどできず、それよりは楽だ。

Air Mac ExtremeexFAT読めない問題。

PS4が認識するのはFAT32exFATなので、じゃあとexFATパーティションを切ってスクショをPS4からコピーしたら、Air Mac Extremeが「ディスクの修復が必要です」と言い出した。
よく調べてみると、何の事はない、Air Mac ExtremeexFATに対応していないだけで、別に修復は必要なかった。こいつは手のかかる二万円だ。

しかたがないので、FAT32は表向きは32GBまでが対応ということなので、ためしに128GBの領域をFAT32で切り出してみたが、そうするとPS4が対応していない。
まあそうだよね。

しょうがないから、32GBの領域をFAT32で切り出して、どちらからも読み書きできることを確認した。
結果的にはUSBメモリよりも容量が小さくなった。ちょっと涙をこらえた。

CSS : 要素数・ブラウザ幅が可変長なサイトでflexをキレイに使う

月花です。
今回はCSSの備忘録です。

内容は、幅が可変長のサイトにおける、flexboxで要素は左寄せ、全体は中央寄せとする方法です。

テキストだと説明しづらいので、画像をつかっていきます。

やりたいこと

このようなレイアウトを組みたいとする。
f:id:gekka9:20170728122308p:plain
コードでは下記のようになる。

See the Pen zdvwqQ by gekka (@gekka9) on CodePen.

しかし、同じCSSで横幅が少しでも異なると

See the Pen zdvwBQ by gekka (@gekka9) on CodePen.


このように、一見入りそうだけど、実はマージン込みでは入らず、下に落ちてしまう。
この右側のスキマが気になるので、直していく。

よくある失敗例

flexには、幅全体を使って等間隔に割り付ける、 justify-content:space-around という指定がある。
しかし、これを単純につかってしまうと・・・。

See the Pen YxyZMX by gekka (@gekka9) on CodePen.

このように、最下段がおかしなことになる。

解決方針

一段あたりの要素の個数が決まっているなら、よくあるハウツーでは剰余の数だけ空の要素を入れればいい。
しかし今回は、ブラウザの幅によって個数を変えて、常に入りうる最大の数を一段に押し込みたい。

つまり、下記のようになるのが理想である。
f:id:gekka9:20170728122322p:plain

このためには、下記の X から Y を計算し、 Y/2 を margin-left に設定することでできるはずだ。
f:id:gekka9:20170728122327p:plain
これも、数か幅のどちらかが固定ならば、シンプルに定まるものだが、その場合は計算せずとも space-around を使えばよいだけだ。

今回は可変長のため、一行あたりに何個入っているかをHTMLとCSSは知らないので、全体の幅から要素の横幅に個数をかけたものを引くなどというような、個数を用いる計算はできない。

そこで、個数に依存しない計算方法として剰余を用い、
・ Y = 100%(px) mod X(px)
と求めることができる。

もし、CSS の calc に剰余が実装されているならば、これでいけるはずだった。
しかし現実は非情である。実装されてないばかりか、そもそもcalcはモダンブラウザしか使えないのでIEが死ぬ。

ここはおとなしくJavaScriptを持ち出すことにする。

JavaScript を使う戦略

上記の、 Y = 100%(px) mod X(px) という計算式において、 100% と X をピクセルで用いるには、単純にJSで幅を取ってやればいいだけなので、ページロード時とリサイズ時に計算をして、CSSを書き換えてやる。
ただし、 X にマージンを含めなければならないため、 .outerWidth(true) を用いることが必要なことに留意する。

See the Pen oejwep by gekka (@gekka9) on CodePen.

これで、どのようなブラウザの幅でも、どのような要素でも柔軟に対応できる要素グリッドが出来上がった。