Node-redのLambda式(無名関数)で、msg配下の配列の特定の値を使いたい!


ざっくりいうと

過去記事
Node-redのmustacheテンプレート中でeval的な事をしたい時はLambda式を使うと嬉しいです
の続き的な記事で、Lambda式でmsgに含まれる配列についてインデックス指定で参照したい!という事をやる方法です。

Lambda式とは

Lambda式を使ったら、mustacheテンプレートの中で関数が使えます。
例えば、Functionノードで以下のような関数を用意しておきます。

msg._unixTime2ymd = function(){
    return function(text, render){

        if ( Number(render(text)) === 0 ){
            return render("-");
        }
        var timezoneoffset = -9;     // UTC-表示したいタイムゾーン(単位:hour)。JSTなら-9

        var d = new Date( Number(render(text))  - (timezoneoffset * 60 - new Date().getTimezoneOffset()) * 60000);
        var year  = d.getFullYear();
        var month = d.getMonth() + 1;
        var day   = d.getDate();
        var hour  = ( '0' + d.getHours() ).slice(-2);
        var min   = ( '0' + d.getMinutes() ).slice(-2);
        var sec   = ( '0' + d.getSeconds() ).slice(-2);

        var D = new Date();
        var Year  = D.getFullYear();

        if (year === Year){
            return render( month + '/' + day + ' ' + hour + ':' + min + ':' + sec );
        }else{
            return render( year + '/' + month + '/' + day + ' ' + hour + ':' + min + ':' + sec );
        }


    }                                      
};

その後に、テンプレートノード中で

{{#_unixTime2ymd}}{{payload}}{{/_unixTime2ymd}}

などを記載し、Injectノードからmsg.payloadにタイムスタンプ(ms)を渡してあげると2019/01/01 00:00:00のようになるわけです。(今回の関数は、タイムスタンプが年内のものであれば年は省くので、以下のテストでやったときは6/9 21:53:15みたいになりましたが。)

ソースはこちら

[{"id":"74fd7327.6fcd14","type":"debug","z":"981e150c.5ff1b","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":650,"y":480,"wires":[]},{"id":"ca3a908e.21633","type":"template","z":"981e150c.5ff1b","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{{#_unixTime2ymd}}{{payload}}{{/_unixTime2ymd}}","output":"str","x":500,"y":480,"wires":[["74fd7327.6fcd14"]]},{"id":"c0189393.576ae8","type":"function","z":"981e150c.5ff1b","name":"set lambda","func":"msg._unixTime2ymd = function(){\n    return function(text, render){\n        \n        if ( Number(render(text)) === 0 ){\n            return render(\"-\");\n        }\n        var timezoneoffset = -9;     // UTC-表示したいタイムゾーン(単位:hour)。JSTなら-9\n\n        var d = new Date( Number(render(text))  - (timezoneoffset * 60 - new Date().getTimezoneOffset()) * 60000);\n        var year  = d.getFullYear();\n        var month = d.getMonth() + 1;\n        var day   = d.getDate();\n        var hour  = ( '0' + d.getHours() ).slice(-2);\n        var min   = ( '0' + d.getMinutes() ).slice(-2);\n        var sec   = ( '0' + d.getSeconds() ).slice(-2);\n        \n        var D = new Date();\n        var Year  = D.getFullYear();\n        \n        if (year === Year){\n            return render( month + '/' + day + ' ' + hour + ':' + min + ':' + sec );\n        }else{\n            return render( year + '/' + month + '/' + day + ' ' + hour + ':' + min + ':' + sec );\n        }\n    \n        \n    }                                      \n};\n\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":480,"wires":[["ca3a908e.21633"]]},{"id":"d2325d37.276c78","type":"inject","z":"981e150c.5ff1b","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":180,"y":480,"wires":[["c0189393.576ae8"]]}]

今回の本題

そうなると、msgに入っている配列の特定の値なんかも参照したくなっちゃう訳です。
んで、結論から行くと以下のように使うと良いです。

//Functionノードにて
msg._getArrVal = function(){
    var arr = msg.arraysample;
    return function(text,render){
        return render(arr[render(text)]);
    }
};

//msg.arraysample
["hoge","fuga","piyo"]

//mustacheテンプレート
{{#_getArrVal}}0{{/_getArrVal}}

//結果
hoge

という様な事ができる訳です。

ソース

[{"id":"d2325d37.276c78","type":"inject","z":"981e150c.5ff1b","name":"inject","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":210,"y":480,"wires":[["c0189393.576ae8"]]},{"id":"c0189393.576ae8","type":"function","z":"981e150c.5ff1b","name":"set lambda","func":"msg._getArrVal = function(){\n    var arr = msg.arraysample;\n    return function(text,render){\n        return render(arr[render(text)]);\n    }\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":480,"wires":[["c99b2df1.634268"]]},{"id":"c99b2df1.634268","type":"change","z":"981e150c.5ff1b","name":"set arraysample","rules":[{"t":"set","p":"arraysample","pt":"msg","to":"[\"hoge\",\"fuga\",\"piyo\"]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":520,"y":480,"wires":[["ca3a908e.21633"]]},{"id":"ca3a908e.21633","type":"template","z":"981e150c.5ff1b","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{{#_getArrVal}}0{{/_getArrVal}}","output":"str","x":680,"y":480,"wires":[["74fd7327.6fcd14"]]},{"id":"74fd7327.6fcd14","type":"debug","z":"981e150c.5ff1b","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":830,"y":480,"wires":[]}]

ちなみに、functionの中のfunctionにあるtextrenderという変数はおまじないなのでそのまま使う必要があります。{{#hogehoge}}value{{/hogehoge}}の様にした時にそのvalueが入ってくるのがtextちゃんです。

この

{{#_getArrVal}}0{{/_getArrVal}}

の0の箇所にもちろん変数を持ってこれるので

//Functionノードにて
msg._getArrVal = (略
msg.index = 1;

//msg.arraysample
["hoge","fuga","piyo"]

//mustacheテンプレート
{{#_getArrVal}}{{index}}{{/_getArrVal}}

//結果
fuga

と出来る訳ですね。
そもそもMustacheのLogic-lessテンプレートという考え方にそぐわない気もしなくはないですが、まぁ色々なニーズがあるのでやり方知っておくと便利かと思います。

ちなみに

今回のケースだとmsg.arraysampleは、このMustacheテンプレートが評価されるまではmsgに含まれている必要があります。
つまり、Lambda式を定義した後にmsg.arraysampleを削除したりすると、正しく動きませんのでご注意ください。(値渡しにすれば良いのかな。試してないけど。)

mustacheそのものについては、以下の翻訳記事も見ておくと良いと思います。
mustache記法について簡単にまとめてみた

では。