20191012のJavaScriptに関する記事は25件です。

【JavaScript】特定のスクロール量を基準にする

開始タグ

「スクロールしたらヘッダーをあれこれしたい」的なことは日々増えている気がするのでメモ。

方法

例えば、
・スクロールしたらヘッダーの背景を白色にしたい
・ヘッダーの文字サイズを大きくしたい

みたいなCSSで解決できそうなものであれば
・特定のスクロール量を基準にクラス名をつけ外しさせる
方法で対処できます。

common.js
$(window).on("load resize scroll", function() {
    var body = $("body");
    var browseWid = $(window).width(); //ブラウザの横幅取得
    var widPc = browseWid > breakpoint; //PC表示のとき
    var widSp = browseWid <= breakpoint; //SP表示のとき

    var amount = 250; //スクロール量(px)
    var scrollPoint = $(this).scrollTop(); //スクロールした量
    var scrollStartPc = "js-scroll_start_pc"; //PC用
    var scrollStartSp = "js-scroll_start_sp"; //スマホ用

    if (widPc) {
        body.removeClass(scrollStartSp);
        if (scrollPoint > amount) {
            body.addClass(scrollStartPc);
        } else if (scrollPoint == 0) {
            body.removeClass(scrollStartPc);
        }
    } else if (widSp) {
        body.removeClass(scrollStartPc);
        if (scrollPoint > amount) {
            body.addClass(scrollStartSp);
        } else if (scrollPoint == 0) {
            body.removeClass(scrollStartSp);
        }
    }
});

あとは、PCの場合なら「.js-scroll_start_pc」を頭に書いて、各CSSを書いてあげればOK。

閉じタグ

もっといい方法はある気がするけど、現状はこの状態。
今後改善方法を発見し次第取り入れていきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのボタンをクリックした際のイベント処理

目次
記述の仕方(例)
inputについて
thisを関数の引数にすると面白いことができそう
inputを使わず、buttonを使う場合

記述の仕方(例)

  • HTML内で完結させる場合
html
<input type="button" value="ボタン" onclick="console.log(`Hello`)">

実行結果:Hello

  • 外部ファイルに関数を用意する場合
html
<input type="button" value="ボタン" onclick="func()">
js
const func = () => {
    console.log("Hello");
}

実行結果:Hello

  • 関数を複数実行もできる
html
<input type="button" value="ボタン" onclick="func();func2">
js
const func = () => {
    console.log("Hello");
}
const func2 = () => {
    console.log("World");
}

実行結果:Hello World

inputについて

<FORM></FORM>を構成する部品タグ。
typeでボタンやチェックボックス等の形を指定する。
valueでボタン上に表示するテキストを指定。
onclickではクリックされた際のイベント処理について記述する。

thisを関数の引数に指定すると面白いことができそう

html
<input type="button" value="ボタン" onclick="func(this)">
js
let func = (button) => {
    console.log(button.value);
}

実行結果:ボタン
thisは状況によってその中身が変化する変数。
宣言する必要はなく単体で利用できる。
今回はbuttonの属性valueを指定しました。

  • これによりボタンの上に表示されている文字をクリック後に変更することができる
html
<input type="button" value="ボタン" onclick="func(this)">
js
let func = (button) => {
    console.log(button.value = "OK!");
}

実行結果:OK!

inputを使わず、buttonを使う場合

html
<button id="btn">ボタン</button>
js
const x = document.getElementById(`btn`);

x.addEventListener(`click`, func = () => {
    console.log("OK!");
});

実行結果:OK!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者によるプログラミング学習ログ 124日目

100日チャレンジの124日目

Twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。

100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。

124日目は、

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript:負の数を含む数値の配列の順序を sort() メソッドで並び替えるときは sort() メソッドの引数に比較関数を与える

負の数を含んだ数値の配列を昇順に並び替えるときに少しハマったので、解決策を残しておきます。

要約

  • JavaScript における sort() メソッドは配列の中身を一度文字列に変換し、辞書順に順序づけをする。
  • 数値のみの配列を並び替えるときは sort() メソッドに引数として比較関数を与える。

何にハマったのか

例えばこんな配列があったとして、

sortNumbers.js
var ary = [1, -1, 5, -10, -11];

こう並び替えたいとします。

sortNumbers.js
var ary = [-11, -10, -1 , 1, 5];

要は昇順に並び替えたいということです。
しかし、 sort() メソッドを用いて配列の並び替えを試みたところ、

sortNumbers.js
var ary = [1, -1, 5, -10, -11];
console.log(ary.sort());
// => [ -1, -10, -11, 1, 5 ]

昇順に並び替えられませんでした...

なぜ昇順に並び替えられなかったのか?

何故だと考えつつ、MDN web docs の sort() メソッドに関するページを見てみたらこんなことが書いてありました。

配列の要素をin placeでソートします。このソートは stable ではありません(訳注:同じ序列を持つ値の順番が保証されません)。 デフォルトではUnicodeコードポイントの昇順にソートされます。
MDN web docs mozilla : Array.prototype.sort()より引用

つまり、sort() メソッドは、配列の中の要素を一つずつ文字列に変換してから辞書的な順番で並び替えをするということでした。
ということで、先述したように数値の大きさ順にならないことに納得はできます。
では、数値の大きさ順に並び替えるにはどうしたらいいのだろうか?と考える間もなく同じページに一つの解決策が提示されていました。

引数に比較関数を入れる

以下は sort() メソッドの構文です。

array.sort(comparefunction)

上記のように、sort() メソッドは引数として、何をもって並び替えるのか、それを定義する関数を与えることができます。
先ほどはこれを省略していたため、デフォルトの仕組みである文字列変換からの辞書順ソートが適用されていたんですね。
以下に、比較関数についての説明を引用しておきます。

compareFunction (比較関数) が与えられなかった場合、要素はそれぞれの文字列に変換したものを比較して辞書 (あるいは電話帳。数的でない) 順にソートされます。例えば、"80" は辞書順では "9" の前に来ますが、数的なソートでは 9 が 80 の前に来ます。

compareFunction が与えられた場合、配列の要素は比較関数の返り値に基づきソートされます。もし a と b が比較されようとしている要素の場合、

  • compareFunction(a, b) が 0 未満の場合、a を b より小さい添字にソートします。
  • compareFunction(a, b) が 0 を返す場合、a と b は互いに変更せず、他のすべての要素に対してソートします。注意: ECMAScript 標準はこの振る舞いを保証していないため、一部のブラウザ (例えば、遅くとも 2003 年以前のバージョンの Mozilla) はこれを遵守していません。
  • compareFunction(a, b) が 0 より大きい場合、b を a より小さい添字にソートします。
  • compareFunction(a, b) に与えられた引数が同じなら戻り値も同じでなければなりません。もし一貫性のない値を返した場合の挙動は未定義となります。

MDN web docs mozilla : Array.prototype.sort()より引用

つまり、a と b の差をとる関数を書いていけばよいらしい。

比較関数 compare()

とりあえずコードは以下の様になります。

配列の中の要素(数値)を昇順に並び替えるための比較関数

sortNumbers.js
var ary = [1, -1, 5, -10, -11];
var f = function (a, b) {
    return a - b
}
console.log(ary.sort(f));
// => var ary = [-11, -10, -1 , 1, 5];

配列の中の要素(数値)を降順に並び替えるための比較関数

sortNumbers.js
var ary = [1, -1, 5, -10, -11];
var f = function (a, b) {
    return b - a
}
console.log(ary.sort(f));
// => var ary = [5, 1, -1 , -10, -11];

理屈

昇順にしたい場合、

array = [a, b, c, d]

のとき、a,b, b,c, c,d を比較していく。つまり、

array = [2, 3, 4, 1]

のとき、

array = [2, 3, 4, 1] 
・a,b つまり、2,3 を比較
=> 2-3 = -1 なので、順番は変わらない。
・b,c つまり、3,4 を比較
=> 3-4 = -1 なので、また順番は変わらない。
・c,d つまり、4,1 を比較
=> 4-1 = 3 なので、4 と 1 の順番を入れ替える。
=> [2, 3, 1, 4]
...

というようにソートが終わるまで繰り返すということなのかな?

つまり、二つの要素を比較して入れ替えるので降順にしたいなら、b-a とすればよいと。

参考になったページ

teratail: javascript の比較関数がわからない
CPointLAB.: 【javascript】比較関数について

最後に

長くなってしまった...
JavaScript の sort() メソッドについて少しだけ理解が進んだような気がします。
間違いや誤字脱字などありましたらご指摘くださると大変ありがたいです。
ここまで読んでくださりありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript:数値のみを含む配列を sort() メソッドで並び替えるときは sort() メソッドの引数に比較関数を与える

負の数を含んだ数値の配列を昇順に並び替えるときに少しハマったので、解決策を残しておきます。

要約

  • JavaScript における sort() メソッドは配列の中身を一度文字列に変換し、辞書順に順序づけをする。
  • 数値のみの配列を並び替えるときは sort() メソッドに引数として比較関数を与える。

何にハマったのか

例えばこんな配列があったとして、

sortNumbers.js
var ary = [1, -1, 5, -10, -11];

こう並び替えたいとします。

sortNumbers.js
var ary = [-11, -10, -1 , 1, 5];

要は昇順に並び替えたいということです。
しかし、 sort() メソッドを用いて配列の並び替えを試みたところ、

sortNumbers.js
var ary = [1, -1, 5, -10, -11];
console.log(ary.sort());
// => [ -1, -10, -11, 1, 5 ]

昇順に並び替えられませんでした...

なぜ昇順に並び替えられなかったのか?

何故だと考えつつ、MDN web docs の sort() メソッドに関するページを見てみたらこんなことが書いてありました。

配列の要素をin placeでソートします。このソートは stable ではありません(訳注:同じ序列を持つ値の順番が保証されません)。 デフォルトではUnicodeコードポイントの昇順にソートされます。
MDN web docs mozilla : Array.prototype.sort()より引用

つまり、sort() メソッドは、配列の中の要素を一つずつ文字列に変換してから辞書的な順番で並び替えをするということでした。
ということで、先述したように数値の大きさ順にならないことに納得はできます。
では、数値の大きさ順に並び替えるにはどうしたらいいのだろうか?と考える間もなく同じページに一つの解決策が提示されていました。

引数に比較関数を入れる

以下は sort() メソッドの構文です。

array.sort(comparefunction)

上記のように、sort() メソッドは引数として、何をもって並び替えるのか、それを定義する関数を与えることができます。
先ほどはこれを省略していたため、デフォルトの仕組みである文字列変換からの辞書順ソートが適用されていたんですね。
以下に、比較関数についての説明を引用しておきます。

compareFunction (比較関数) が与えられなかった場合、要素はそれぞれの文字列に変換したものを比較して辞書 (あるいは電話帳。数的でない) 順にソートされます。例えば、"80" は辞書順では "9" の前に来ますが、数的なソートでは 9 が 80 の前に来ます。

compareFunction が与えられた場合、配列の要素は比較関数の返り値に基づきソートされます。もし a と b が比較されようとしている要素の場合、

  • compareFunction(a, b) が 0 未満の場合、a を b より小さい添字にソートします。
  • compareFunction(a, b) が 0 を返す場合、a と b は互いに変更せず、他のすべての要素に対してソートします。注意: ECMAScript 標準はこの振る舞いを保証していないため、一部のブラウザ (例えば、遅くとも 2003 年以前のバージョンの Mozilla) はこれを遵守していません。
  • compareFunction(a, b) が 0 より大きい場合、b を a より小さい添字にソートします。
  • compareFunction(a, b) に与えられた引数が同じなら戻り値も同じでなければなりません。もし一貫性のない値を返した場合の挙動は未定義となります。

MDN web docs mozilla : Array.prototype.sort()より引用

つまり、a と b の差をとる関数を書いていけばよいらしい。

比較関数 compare()

とりあえずコードは以下の様になります。

配列の中の要素(数値)を昇順に並び替えるための比較関数

sortNumbers.js
var ary = [1, -1, 5, -10, -11];
var f = function (a, b) {
    return a - b
}
console.log(ary.sort(f));
// => var ary = [-11, -10, -1 , 1, 5];

配列の中の要素(数値)を降順に並び替えるための比較関数

sortNumbers.js
var ary = [1, -1, 5, -10, -11];
var f = function (a, b) {
    return b - a
}
console.log(ary.sort(f));
// => var ary = [5, 1, -1 , -10, -11];

理屈

昇順にしたい場合、

array = [a, b, c, d]

のとき、a,b, b,c, c,d を比較していく。つまり、

array = [2, 3, 4, 1]

のとき、

array = [2, 3, 4, 1] 
・a,b つまり、2,3 を比較
=> 2-3 = -1 なので、順番は変わらない。
・b,c つまり、3,4 を比較
=> 3-4 = -1 なので、また順番は変わらない。
・c,d つまり、4,1 を比較
=> 4-1 = 3 なので、4 と 1 の順番を入れ替える。
=> [2, 3, 1, 4]
...

というようにソートが終わるまで繰り返すということなのかな?

つまり、二つの要素を比較して入れ替えるので降順にしたいなら、b-a とすればよいと。

参考になったページ

teratail: javascript の比較関数がわからない
CPointLAB.: 【javascript】比較関数について

最後に

長くなってしまった...
JavaScript の sort() メソッドについて少しだけ理解が進んだような気がします。
間違いや誤字脱字などありましたらご指摘くださると大変ありがたいです。
ここまで読んでくださりありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RubyMineでJavaScriptのimport文の波括弧前後の空白設定を行う

デフォルトの設定だと、リフォーマットした時に勝手にずれてしまいましたので、コードスタイルの設定をいじります。

下の画像のように、
Preferences
-> Editor
-> Code Style
-> JavaScript
-> Spaces タブ
-> ES6 import/export braces にチェックで解決します。

名称未設定.png

※ RubyMine に限らず、JetBrains IDE であればどれでも同じ設定かもしれません。


参考:Format Space within braces for ES6 import/export – IDEs Support (IntelliJ Platform) | JetBrains

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Oracle Cloud] OCI に自動ログインするbookmarklet

OCI にワンクリックで自動ログインしたいときに使用するbookmarklet

Oracle Cloud Infrastructureにuserid/passwordの入力をスキップして自動ログインしたい場合に使用するbookmarkletです、IDCSでログインします。
複数アカウントを切り替えてloginしたい時に便利。
bookmark自体が流出するとセキュリティリスクになるので気を付けて使いましょう。
パスワードは手入力のままにしておいてもよいかもしれません。

OCIlogin
javascript:(function(d, i, v) {
  location.href = 'https://console.us-ashburn-1.oraclecloud.com/?tenant=<テナント名>&provider=OracleIdentityCloudService';
  d[i]("idcs-signin-basic-signin-form-username")[v] = "<ユーザーID>";
  d[i]("idcs-signin-basic-signin-form-password")[v] = "<パスワード>";
  d[i]("idcs-signin-basic-signin-form-submit").click();
})(document, "getElementById" ,"value");

このJavaScriptをFirefoxブックマークのURL欄に登録しておいて、あとはそのbookmarkをクリックするだけでオラクルクラウドに自動でloginします。

[参考URL] http://neos21.hatenablog.com/entry/2016/02/13/155454

※ bookmarkletを2回クリックしないとログインできないことがある気がする。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptの呼び出し方と行った動作確認

JavaScriptの記述について

JavaScriptはHTML上では

html
<script>
   //ここに記述します
</script>

外部ファイルから出力する場合は

html
<script src="example.js"></script>

そして外部ファイルにexample.jsを用意してそこに記述。

JavaScriptサンプルコードを用いての動作確認

html
document.write(`Hello`);
document.write(`<h1>Hello</h1>`);

実行結果:Hello
実行結果:h1としてHelloを表示してくれる
ページ上に文字を出力。

html
<p id="example">Hello</p>
<script>
    console.log(document.getElementById("example"));
</script>

実行結果:<p id="example">Hello</p>
exampleをIDとして取得し、exampleが含まれているpタグ全体をコンソールに表示。
classの場合はgetElementByClassNameを使う

html
<p id="example">Hello</p>
<script>
    console.log(document.getElementById("example")textContent);
</script>

実行結果:Hello
一つ前の例のテキストだけを取得し、コンソールに表示。

html
<p id="example">Hello</p>
<script>
    console.log(document.getElementById(`example`).innerHTML);
</script>

実行結果:Hello
HTMLで動作するのと一緒の内容でidを取得してくれる

html
<div id="wrap"></div>
<script>
    const box = document.getElementById('wrap');

    //新規にp要素を出力
    box.innerHTML = '<p>こんにちは</p>';
</script>

実行結果:こんにちは
空要素のdivの中に新たにp要素を出力している。

補足事項

  • documentとはWebページを構成しているHTMLソースそのもの
  • innerHTMLとtextContentの違いはinnerHTMLはタグをHTMLとして解釈してくれる。  一方でtextContentはテキストの内容を表示するだけ。 テキストの内容だとわかっていればtextContentを使ったほうがいい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptの記述のし方と行った動作確認

JavaScriptの記述について

JavaScriptはHTML上では

html
<script>
   //ここに記述します
</script>

外部ファイルから出力する場合は

html
<script src="example.js"></script>

そして外部ファイルにexample.jsを用意してそこに記述。

JavaScriptサンプルコードを用いての動作確認

html
document.write(`Hello`);
document.write(`<h1>Hello</h1>`);

実行結果:Hello
実行結果:h1としてHelloを表示してくれる
ページ上に文字を出力。

html
<p id="example">Hello</p>
<script>
    console.log(document.getElementById("example"));
</script>

実行結果:<p id="example">Hello</p>
exampleをIDとして取得し、exampleが含まれているpタグ全体をコンソールに表示。
classの場合はgetElementByClassNameを使う

html
<p id="example">Hello</p>
<script>
    console.log(document.getElementById("example")textContent);
</script>

実行結果:Hello
一つ前の例のテキストだけを取得し、コンソールに表示。

html
<p id="example">Hello</p>
<script>
    console.log(document.getElementById(`example`).innerHTML);
</script>

実行結果:Hello
HTMLで動作するのと一緒の内容でidを取得してくれる

html
<div id="wrap"></div>
<script>
    const box = document.getElementById('wrap');

    //新規にp要素を出力
    box.innerHTML = '<p>こんにちは</p>';
</script>

実行結果:こんにちは
空要素のdivの中に新たにp要素を出力している。

補足事項

  • documentとはWebページを構成しているHTMLソースそのもの
  • innerHTMLとtextContentの違いはinnerHTMLはタグをHTMLとして解釈してくれる。  一方でtextContentはテキストの内容を表示するだけ。 テキストの内容だとわかっていればtextContentを使ったほうがいい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptの記述と行った動作確認

JavaScriptの記述について

JavaScriptはHTML上では

html
<script>
   //ここに記述します
</script>

外部ファイルから出力する場合は

html
<script src="example.js"></script>

そして外部ファイルにexample.jsを用意してそこに記述。

JavaScriptサンプルコードを用いての動作確認

html
document.write(`Hello`);
document.write(`<h1>Hello</h1>`);

実行結果:Hello
実行結果:h1としてHelloを表示してくれる
ページ上に文字を出力。

html
<p id="example">Hello</p>
<script>
    console.log(document.getElementById("example"));
</script>

実行結果:<p id="example">Hello</p>
exampleをIDとして取得し、exampleが含まれているpタグ全体をコンソールに表示。
classの場合はgetElementByClassNameを使う

html
<p id="example">Hello</p>
<script>
    console.log(document.getElementById("example")textContent);
</script>

実行結果:Hello
一つ前の例のテキストだけを取得し、コンソールに表示。

html
<p id="example">Hello</p>
<script>
    console.log(document.getElementById(`example`).innerHTML);
</script>

実行結果:Hello
HTMLで動作するのと一緒の内容でidを取得してくれる

html
<div id="example"></div>
<script>
    const box = document.getElementById('example');
    box.innerHTML = '<p>Hello</p>';
</script>

実行結果:Hello
空要素のdivの中に新たにp要素を出力している。

補足事項

  • documentとはWebページを構成しているHTMLソースそのもの
  • innerHTMLとtextContentの違いはinnerHTMLはタグをHTMLとして解釈してくれる。  一方でtextContentはテキストの内容を表示するだけ。 テキストの内容だとわかっていればtextContentを使ったほうがいい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

webRTCでの多人数接続での不具合

webRTCでP2Pのビデオ通話を制作しているのですが、不具合に直面しております!
詳しい方いらっしゃいましたが、ご教授頂けると幸いです!?

【課題】
2~30人の複数人接続を実現したい。
ルームが複数個あり、画面上でユーザーが移動すると、別のルームに接続させる仕様
課金が必要なAPIなど(Skyway,twilioなど)は極力使いたくない。

【経緯】

完成し、1つのルームあたり2~4人での動作ではうまく動いたのですが、1つのルームで5~12人など 人数が多くなることで 一部の音声が極端に小さくなったり、新規で追加接続されたユーザーのトラック(ビデオカメラ/画面共有 など)が ルーム内の一部のユーザーに届かない 現象が起きました。

エラーが見当たらなかった(見つけられなかった)こと、webRTCは複数人接続に不向きであるという話もあったので、原因がコード側ではなく、webRTCで1つのルームあたりの人数が大きくなることで起きる現象だと仮説を立てました。

UI上のルームごとに、webRTCのルームを作る仕様をやめて、同一ルームに存在するユーザーを感知して、そのユーザーと1対1で即席のピアー接続を毎回作る仕様に変更しました。
具体的には、
-新規ユーザーが自分のルームに入った際に、その人と1対1のルームを制作し、その後、既に[音声・画面共有・ビデオカメラ]をONにしている場合は、そのローカルトラックを 制作したばかりのルームに追加する
-既に複数人がルームに入っている場合、例えばAさん、Bさんがルーム内にいて、Cさんが入った場合、A-B間のルーム、A-C間のルームを制作し、適切なトラックを送る
-ユーザーがルームを出た場合は、その人が入っている1対1のルームを全て削除する
上記のような仕様にしました。

③での構築が 全体的にコードが複雑になり、想定以上に難航しております。
一部は動いているのですが、部屋に入ってもトラックがピアー接続にうまく乗らないことがあったり(乗ったり乗らなかったりする)、UI上のルームで、2人までは上手く動いても3人になった時に動かないケースが出たりしています。

【質問】
⑴ そもそも②で立てた仮説に間違いはないか。
⑵ 仮説が正しいとして、もっとシンプルに構築できるアイディア/方法はないか。
⑶ コードの量が多くどこをシェアすれば良いのかわからなく シェアしていないので、回答が難しくなるかと思いますが、④で現在起きている現象に 何か思い当たる節はないか。以下、コード自体ではないですが、大まかな構成を記載しました。具体的な場所でシェアすべきコードがあれば、シェア可能です。

【大まかなコード内容】
-vue.jsで制作
-firebaseのrealtimeDatabaseで各ユーザーがどのルームに居るかを感知、誰かがルーム移動した際に、自分のルームが関わっていた場合、トリガーを引く
-その際に、まずsocket.ioで新規ソケットを制作し、オファー、アンサー、Candidate制作送信、などのソケットにトリガーを設定。
-自分が移動して、その部屋に既に1人でもユーザーがいる場合同じ準備を行う
-その後、入室した側のユーザーが、既に入っていたユーザーにオファーを送る
-新規のピアー接続を用意し、こちらにも、他ユーザーのトラック追加/削除に対してのリアクション用のトリガーをセットする。
-ピアー接続が上手くいった場合、その後、既にUI上で自分の[カメラ、画面、音声]それぞれのスイッチがONになっていた場合に、そのピアー接続に載せる。
-ピアー接続のトリガーの仕様は、onnegotiationneededを経由して、ontrack、removetrackのアクションを稼働させている。removetrackができるようにontrackの際にsenderをクライアント側で保存させておいて、removetrackではそれを使用する

何卒宜しくお願いいたします!?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UrlFetchApp.fetchの応答3種 タイムアウトがつらい

まとめ

大別して

  • 200 OK
  • エラーステータス(400 500)
  • 接続不能(無効なURL)

がある。
異常系のうちステータスコードの異常はオプションでレスポンスか例外扱いかを選べる

タイムアウトは制御方法がなく、例外処理するしかない。

正常系

const res = UrlFetchApp.fetch(URL)

接続先が問題ないなら気にすることはない

ステータスコード異常

try {
  const res = UrlFetchApp.fetch(URL)
} catch(e) {}

400や500番台が返されると通常は例外として処理される。
ただ、エラーであることをチェックしたい場合もあるので、そういう場合は
muteHttpExceptionsオプションを使う。

const res = UrlFetchApp.fetch(URL, {muteHttpExceptions:true})
res.getResponseCode() != 200

これで応答が帰ってくるかぎりは例外処理で行わなくてよくなる。

利用できないURL

サーバーが死んでいるときなど応答がないとき、この例外エラーが発生する。
これはmuteHttpExceptionsでは制御できない。例外で対処するほかない。

が、問題はそこではなく、応答待ち時間がめっちゃとられることが問題。

タイムアウトの時間の資料は見つからなかったが、私の手元のトリガー実行時間から、
おおよそいちリクエストを大体50秒近く待機する。

トリガーで自動で働いている限りはどれだけ時間を使ってくれても私は困らないが、問題は無料の利用枠の問題である。

GoogleAppsScriptでよくはまる制約まとめ - バグ取りの日々

GASのトリガーの実行時間は一日合計90分。
たとえば、奇特な例として「普段は接続できないがまれに接続できることをチェックしたい」逆死活監視とでもいうような機能を作る場合、
いち実行に50秒だとおよそ
15分に一度の実行が限界になる。
チェックしたいURLが増えると間隔の限界は減り、
二つのURLをチェックするとそれだけで30分に一度しか実行できなくなる。

そして問題はこのタイムアウトまでの時間を操作できないことで、10秒応答がなかったらタイムアウト、などとは設定できない。

Class UrlFetchApp | Apps Script | Google Developers

同期処理なので途中でabortするようなこともできず、
HTTPのヘッダーで操作できないかと考えた結果は前回のとおり
HTTPのリクエストヘッダーでタイムアウトを操作できない - Qiita

トリガー時間は共用なので他のスクリプトにも影響が出てくる困り者。

死んでいるURLを執拗にチェックするシーンは通常ほぼありえないし、トリガー時間はリアルタイムに増減するので異常による浪費の影響も一日で終息する。

普通の人には困らないが、稀有な例として絶賛困っているのである。
解決方法は未だない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptの基本型を等価演算子で比較する

JavaScriptの基本型を等価演算子で比較する時、文字列/論理値はそれぞれ数値に変換したうえで判定される。

文字列 == 数値

// 文字列を数値に変換できる場合は変換し、比較
'1' == 1 // true
'0' == 0 // true
'1' == 0 // false

論理値 == 数値

// trueとfalseはそれぞれ1、0に変換し、比較
true == 1 // true
false == 0 // true
true == 2 // false
false == -1 // false

文字列 == 論理値

// 空文字だけfalseに変換
'' == false // true
'true' == true // false
'false' == 'false' // false

まあES2015以降では、等価演算子はnullチェックぐらいしかもう使い道は無さそうだが。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[HeadlessCMS] contentful でエントリーを取得する色んなクエリ

contentful を触り始めてみました。
Content Delivery API を使ってエントリーを取得するクエリの記述を一部まとめてみました。

logo.png

ブログによくある以下のような記事を取得できます。

  • 記事一覧
  • カテゴリアーカイブ
  • 月別アーカイブ
  • ページネーション
  • 検索一覧

その他詳細は公式を参照してください。

準備

client には Contentful Delivery APIクライアント が入っています。

const contentful = require('contentful')

const client = contentful.createClient({
  space: '<space_id>',
  accessToken: '<content_delivery_api_key>'
});

getEntry('<entry_id>')

エントリーIDを指定して、単一のエントリーを取得します。

client.getEntry("31TNnjawUogHlfGUoMO0M2").then(entry => {
  console.log(entry.fields.title);
  console.log(entry.fields.body);
  console.log(entry.sys.createdAt);
});

このとき fields には Content type に自身で追加したフィールド(タイトル、本文など)が入っています。
sysにはシステム管理のメタデータ(id, revision, createdAtなど)が入っています。

getEntries()

全てのエントリーを取得します。
引数にオプションを指定しない場合には、 entries.items に、記事や人やカテゴリなど、Content type を元にした全てのエントリーが配列で入ります。

client.getEntries().then(entries => {
  console.log(entries.items[0].fields.title);
  console.log(entries.items[1].fields.title);
  console.log(entries.items[2].fields.title);
});

この getEntries() の引数に様々なオプションを含めることができます。

指定した Content type の全てのエントリーを取得

以下はブログ記事の一覧を取得します。

client.getEntries({
  content_type: "blogPost"
}).then(entries => {
  console.log(entries.items); // blogPostのエントリー配列
});

指定したフィールドと一致する全てのエントリーを取得

Memoフィールドに 'hoge' という String を設定している全てのエントリーを取得します。
このように fields を使用した検索をする場合は、 content_type を指定する必要があります。

client.getEntries({
  content_type: "blogPost",
  "fields.memo": "hoge"
}).then(entries => {
  console.log(entries.items);
});

指定したフィールドと一致しない全てのエントリーを取得

[ne]オペレーターを使うと、 'hoge' という String設定していない全てのエントリーを取得します。

client.getEntries({
  content_type: "blogPost",
  "fields.memo[ne]": "hoge"
}).then(entries => {
  console.log(entries.items);
});

List形式のフィールドの検索

Likesフィールド(List形式)に 'banana' が含まれている全てのエントリーを取得します。

client.getEntries({
  content_type: "blogPost",
  "fields.likes": "banana"
}).then(entries => {
  console.log(entries.items);
});

その他のに使えるオペレータはこちら。

オペレータ
[ne] 指定した値が含まれない全てのエントリーを取得
[all] 指定した複数のパラメータが全て含まれている全てのエントリーを取得
[in] 指定した複数のパラメータのどれかが含まれている全てのエントリーを取得
[nin] 指定した複数のパラメータのどれも含まれている全てのエントリーを取得
[exists] フィールドに値が設定されている全てのエントリーを取得(パラメータがtrueの場合)
[exists] フィールドに値が設定されていない全てのエントリーを取得(パラメータがfalseの場合)

日付形式, 数値形式の検索

以下のような範囲を指定するオペレータが使用できます。

オペレータ
[lt] より小さい
[lte] 以下
[gt] より大きい
[gte] 以上

2019年10月のアーカイブスを表示したい場合、以下のようなクエリで取得します。

const year = 2019;
const month = 10;

// 年と月からその月の最終日を返す関数
const getLastDay = (year, month) => {
  const lastDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  const isLeap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);

  if (isLeap && month === 2) {
    return 29;
  } else {
    return lastDays[month - 1];
  }
};

client.getEntries({
  content_type: "blogPost",
  "fields.publishDate[gte]": `${year}-${month}-01`, // その月の初めの日以上
  "fields.publishDate[lte]": `${year}-${month}-${getLastDay(year, month)}` // その月の最終日以内
}).then(entries => {
  console.log(entries.items);
});

全文検索

指定した文字列を含む全てのエントリーを取得できます。

client.getEntries({
  content_type: "blogPost",
  query: "orange"
})
.then(entries => {
  console.log(entries.items);
});

[match]オペレータでフィールドを指定した全文検索も可能です。

client.getEntries({
  content_type: "blogPost",
  "fields.description[match]": "Hello"
})
.then(entries => {
  console.log(entries.items);
});

エントリーの並び替え

orderを使用して、順序を指定できます。
このとき指定パラメータの接頭に - を付けると逆順になります。

client.getEntries({
  content_type: "blogPost",
  order: "-sys.createdAt"
})
.then(entries => {
  console.log(entries.items);
});

指定できるパラメータは、日付や数値やブール値、256字以下の文字列のみです。
複数指定することも可能です。

client.getEntries({
  order: '-sys.createdAt,sys.id'
})

ページネーション(取得数制限とオフセット機能)

limitを使用して、取得数を制限できます。
skip で取得位置のオフセットを指定できます。

ページネーションは以下のようなクエリで実装できます。

const MAX_ENTRY = 20;
const page = 3;

client.getEntries({
  content_type: "blogPost",
  order: "sys.createdAt",
  limit: MAX_ENTRY,
  skip: MAX_ENTRY * (page - 1)
})
.then(entries => {
  console.log(entries.items);
  return {
    entries: entries.items
  };
});

Reference形式の検索

以下は、参照形式であるcategoryフィールドを指定した検索です。
指定する Reference の content type ID もクエリに含める必要があります。

client.getEntries({
  content_type: "blogPost",
  "fields.category.sys.contentType.sys.id": "tag", // ←これも必要
  "fields.category.fields.name": "Game"
})
.then(entries => {
  console.log(entries.items);
  return {
    entries: entries.items
  };
});

おわり

自分のサイトは何年もwordpressで運用してるので、 contentful に乗り換えてみようかと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js用語辞典【未完】

ディレクティブ

メソッドハンドラ

  • 「クリック」や「入力」といったイベントをハンドリングする
  • クリックしたらアラートを出す!、マウスオーバーしたら文字が大きくなるみたいな動きをつけたい時使おう
  • 簡単な「v-on」ディレクティブを使った例

See the Pen pooJzQr by アズマックス (@yuki-azuma) on CodePen.

双方向データバインディング

  • 双方向にデータが同期するデータバインディング
  • そもそも、データバインディングとは、「データと描画を同期する仕組み」
  • Aが変わったら、Bも変わり、Bが変われば、Aも変わるイメージ

v-model.gif

コンポーネント

  • 名前付きの再利用可能な Vue インスタンス
  • 必要なだけ何度でも再利用できる
  • 部品ごとに使われることが多い

    • 例)
    • 画面のヘッダー部分のコンポーネント
    • フッター部分のコンポーネント、
    • サイドメニューのコンポーネント
  • 下図は、画面のパーツ構成をVueファイルの構成に置き換えた時のイメージ図
    コンポーネントの基本_—_Vue_js.png

  • 開発規模が大きくなってきたら、コンポーネントは大活躍する

  • 以下のワードもよく出てくるので、覚えておきたい

    • $emit
    • props
    • ローカルコンポーネント
    • グローバルコンポーネント
  • 参考:Vue.js「コンポーネント」入門」

  • 参考:Vue.jsのコンポーネント入門

  • 参考:Vue.jsをシンプルに理解しよう その2

その他参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CouchDB + PouchDB でPWAを作ろう!

PWA作ってますか?それとも使ってますか?

今、皆さんが見ているQiitaもChrome等ではアドレスバーにinstallアイコンが表示されているので、既に知らず知らずのうちに使っていることでしょう。

先日、私はkanban-board-appというPWAのコードをリリースしました。
動作はこちらで確認できます。
その際に使用したCouchDB + PouchDBの構成について紹介したいと思います。

そもそもPWAって?

乱立するPWA記事で既に見飽きていることでしょうが、念のため説明のリンクを貼っておきたいと思います。
はじめてのプログレッシブウェブアプリ

PWAはオフライン状態でもコンテンツや機能を提供し続けることが必要で、それを実現する技術(API)がService WorkerIndexedDBです。
誤解を恐れず単純化して説明すると、Service Workerは固定のページ、画像、スクリプト等をキャッシュし、IndexedDBは動的コンテンツをキャッシュします。

  • 註:Service Workerは動的コンテンツをローカルマシンから配信することもできる高度な機能を持っています。
  • 註:IndexedDB以外にも、ローカルコンテンツのキャッシュに利用できる機能は存在します (非標準のWeb SQL等)。
  • 註:IndexedDB自体が透過的なキャッシュとして働くわけではありません。リモートのコンテンツを自身で格納する必要があります。

PWAを作ろう!

幸いなことに、Create React App等のツールを使えば、Service Workerに対応したアプリのコードを生成してくれます。serviceWorker.registerを呼ぶように変更するだけです。

src/index.tsx
...


// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA

// serviceWorker.unregister();
serviceWorker.register();
  • 註:public/manifest.jsonも自分のアプリの説明に変更しましょう。

問題は動的コンテンツのキャッシュ

非透過的な方法(自身でリモートから取得、ローカルを更新、リモート取得に失敗したらローカルから取得)でデータを管理するのは非常に苦痛であり、工数も多く掛かるので、リモートとローカルを自動同期して、どこにデータがあるかを意識せずに済む仕組みにしたいと思うのは当然のことと思います。
最近の流れでは、認証含めてFirebaseに丸投げし、この自動同期にはFirestoreFirebase Realtime Database
で済ませてしまうのが、一つのトレンドかと思います。

しかし、様々な理由(ロックインされたくない、サービス化しない、イントラでのみ使う、等々)でFirebaseを使用しないという選択もあり得ます。

CouchDB + PouchDB という選択肢

CouchDBはオープンソースのNoSQLドキュメントデータベースで、すべての操作がREST APIのみで提供されています。
立ち位置としては、MongoDB等と同じですが、MongoDBのようにRDB紛いの複雑な(表)結合操作は行なえません。
JavaScriptによるMap/Reduce関数によってビューを定義することもできますが、Firebase Realtime Databaseのように動的な条件を扱うことはできません。

PouchDBは、JavaScriptのドキュメントデータベースライブラリで、CouchDBの各APIと同等な機能をJavaScript APIとして提供しています。
ブラウザで使用する場合のバックエンドはIndexedDB、Nodeで使用する場合のデフォルトのバックエンドはLevelDBです。
PouchDBのもう1つの特徴は、CouchDBのレプリケーションAPIによってリモートのCouchDBと自動同期できることです。

CouchDBとPouchDBを同期する最低限のコード (PouchDBサイトより引用)

var db = new PouchDB('dbname');

db.put({
  _id: 'dave@gmail.com',
  name: 'David',
  age: 69
});

db.changes().on('change', function() {
  console.log('Ch-Ch-Changes');
});

db.replicate.to('http://example.com/mydb');

これだけのコードで自動同期が実現できます!
あとは、常にローカル側を参照するだけです。

CouchDBの課題

上述しましたが、CouchDBでは動的なビューを提供できず、さらに、ユーザーのアクセス権限をデータベース単位でしか設定できないので、1つのデータベースに複数ユーザーの、個々人の(共有しない)データを格納することができません。
結果として、ユーザー毎にデータベースを作成することになります。
また、複数ユーザー共有のデータがある場合、もう1つ、ユーザーからは読み取り専用のデータベースを個人毎に提供し、バックエンド側から更新することになります。

  • 註:1つのサーバーインスタンスに複数のデータベースを持つことができます。「データベース」と言ってもRDBのテーブル位の粒度のオブジェクトです。

PouchDBの課題

一部のCouchDB互換サーバー(IBM Cloudant)との同期では、同期対象のドキュメントidを指定する必要があります。
以下のように、同期開始前にリモートのid一覧を取得することで対処できます。

https://github.com/shellyln/kanban-board-app/blob/master/src/lib/db.ts

...

const localDocs = await localDb.allDocs({});
const remoteDocs = remoteDb ? await remoteDb.allDocs({}) : null;
const idSet = new Set<string>();
for (const doc of localDocs.rows) {
    idSet.add(doc.id);
}
if (remoteDocs) {
    for (const doc of remoteDocs.rows) {
        idSet.add(doc.id);
    }
}

rep = localDb.sync(remoteDb, {
    live: true,
    retry: true,
    doc_ids: Array.from(idSet.values()),
})

...

さいごに

PWA簡単です!是非作ってみましょう!
(私が一番辛かったのは、各サイズのアイコン作成というのは内緒です)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windy JavaScript API で風や波のアニメーションを地図上に表示する

概要

  • Windy JavaScript API を使って風の流れのアニメーションを Web ページ上に表示する

Windy について

Windy は地図上に風や波のアニメーションを表示している Web サイト。以前は Windyty という名前だった。

公式サイトの Windy: Wind map & weather forecast には十分な機能が揃っている地図を見ることができる。自らカスタマイズ等をする必要がなければこちらを見るだけで十分かもしれない。

Windy JavaScript API と Leaflet

Windy JavaScript API は Leaflet 1.4.x をベースにしたライブラリ。Leaflet の機能に Windy で使われているような地図表示機能を追加している。

Leaflet は Web ページ上に地図を表示することができる JavaScript ライブラリ。

Windy API - Docs

Windy JavaScript API is a simple-to-use library based on Leaflet 1.4.x. It allows you to use everything Leaflet or JavaScript offers, along with the Windy map visualizations we use at Windy.com.

Leaflet - a JavaScript library for interactive maps

Leaflet is the leading open-source JavaScript library for mobile-friendly interactive maps.

Windy JavaScript API を使ったサンプル

Windy JavaScript API を使って風の動きがわかるアニメーション地図をWebページ上に表示してみた。

風の強さによる背景色の色分けができる。

windy-api-01.jpg

背景には雨の強さなどを表示することも可能。

windy-api-02.jpg

波の動きのアニメーションを表示することも可能。
ページ下部のスライダーを動かせば、先の時間の予測情報を見ることもできる。

windy-api-03.jpg

Windy API Key の作成

サンプルコード

サンプルのソースコードを以下に示す。
これだけで、アニメーションや背景を切り替えるためのコントロールも右上に表示される。

JavaScript (script.js)

const options = {
  // Required: Your API key
  key: 'YOUR_API_KEY',

  // Optional: Initial state of the map
  // 名古屋駅を中心に表示
  lat: 35.170736,
  lon: 136.882104,
  zoom: 6,
};

// Initialize Windy API
windyInit(options, windyAPI => {
    const { map } = windyAPI;
    // .map is instance of Leaflet map
});

HTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, shrink-to-fit=no"
    />
    <script src="https://unpkg.com/leaflet@1.4.0/dist/leaflet.js"></script>
    <script src="https://api4.windy.com/assets/libBoot.js"></script>
    <style>
      html, body {
        width: 100%; height: 100%; margin: 0; padding: 0;
      }
      #windy {
        width: 100%; height: 100%; margin: 0; padding: 0;
      }
    </style>
  </head>
  <body>
    <div id="windy"></div>
    <script src="script.js"></script>
  </body>
</html>

いくつかのサンプルコードが公式の GitHub リポジトリにあるので参考になる。
windycom/API: ?Windy API, or Windy Leaflet Plugin, let you put animated weather map into your website and enjoy rich ecosystem of Leaflet library.

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ずんだの1桁足し算問題 Node.js編

ずんだの1桁足し算問題 Ruby編

Node.js で書いたものです(=゚ω゚)ノ

ちなみに, ずんだ成分ゼロです
※ずんだ=ずんだまるさん = https://twitter.com/edamametsubu318

コード

const numberArray = [...Array(9).keys()].map(i => ++i)
// これはなくても動く
let total

numberArray.forEach(i => {
   numberArray.forEach(j => {
      total = (i+j)
      console.log(`${i}+${j}=${"~".repeat(total)} ${total}`)
   })
})

出力

1+1=~~ 2
1+2=~~~ 3
1+3=~~~~ 4
1+4=~~~~~ 5
1+5=~~~~~~ 6
1+6=~~~~~~~ 7
1+7=~~~~~~~~ 8
1+8=~~~~~~~~~ 9
1+9=~~~~~~~~~~ 10
2+1=~~~ 3
2+2=~~~~ 4
2+3=~~~~~ 5
2+4=~~~~~~ 6
2+5=~~~~~~~ 7
2+6=~~~~~~~~ 8
2+7=~~~~~~~~~ 9
2+8=~~~~~~~~~~ 10
2+9=~~~~~~~~~~~ 11
3+1=~~~~ 4
3+2=~~~~~ 5
3+3=~~~~~~ 6
3+4=~~~~~~~ 7
3+5=~~~~~~~~ 8
3+6=~~~~~~~~~ 9
3+7=~~~~~~~~~~ 10
3+8=~~~~~~~~~~~ 11
3+9=~~~~~~~~~~~~ 12
4+1=~~~~~ 5
4+2=~~~~~~ 6
4+3=~~~~~~~ 7
4+4=~~~~~~~~ 8
4+5=~~~~~~~~~ 9
4+6=~~~~~~~~~~ 10
4+7=~~~~~~~~~~~ 11
4+8=~~~~~~~~~~~~ 12
4+9=~~~~~~~~~~~~~ 13
5+1=~~~~~~ 6
5+2=~~~~~~~ 7
5+3=~~~~~~~~ 8
5+4=~~~~~~~~~ 9
5+5=~~~~~~~~~~ 10
5+6=~~~~~~~~~~~ 11
5+7=~~~~~~~~~~~~ 12
5+8=~~~~~~~~~~~~~ 13
5+9=~~~~~~~~~~~~~~ 14
6+1=~~~~~~~ 7
6+2=~~~~~~~~ 8
6+3=~~~~~~~~~ 9
6+4=~~~~~~~~~~ 10
6+5=~~~~~~~~~~~ 11
6+6=~~~~~~~~~~~~ 12
6+7=~~~~~~~~~~~~~ 13
6+8=~~~~~~~~~~~~~~ 14
6+9=~~~~~~~~~~~~~~~ 15
7+1=~~~~~~~~ 8
7+2=~~~~~~~~~ 9
7+3=~~~~~~~~~~ 10
7+4=~~~~~~~~~~~ 11
7+5=~~~~~~~~~~~~ 12
7+6=~~~~~~~~~~~~~ 13
7+7=~~~~~~~~~~~~~~ 14
7+8=~~~~~~~~~~~~~~~ 15
7+9=~~~~~~~~~~~~~~~~ 16
8+1=~~~~~~~~~ 9
8+2=~~~~~~~~~~ 10
8+3=~~~~~~~~~~~ 11
8+4=~~~~~~~~~~~~ 12
8+5=~~~~~~~~~~~~~ 13
8+6=~~~~~~~~~~~~~~ 14
8+7=~~~~~~~~~~~~~~~ 15
8+8=~~~~~~~~~~~~~~~~ 16
8+9=~~~~~~~~~~~~~~~~~ 17
9+1=~~~~~~~~~~ 10
9+2=~~~~~~~~~~~ 11
9+3=~~~~~~~~~~~~ 12
9+4=~~~~~~~~~~~~~ 13
9+5=~~~~~~~~~~~~~~ 14
9+6=~~~~~~~~~~~~~~~ 15
9+7=~~~~~~~~~~~~~~~~ 16
9+8=~~~~~~~~~~~~~~~~~ 17
9+9=~~~~~~~~~~~~~~~~~~ 18

おもろかったし, node というか JavaScript の復習になった.

ではでは(=゚ω゚)ノ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptの正規表現メモ

改行

改行を含めたマッチ

string.match(/([\n\r]|.)*/));

改行を含めないマッチ

string.match(/.*/));

最長一致と最短一致

// 最長一致
string.match(/PATTERN*/)

// 最短一致
string.match(/PATTERN*?/)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

なぜ state を直接変更すると再描画されないのか

意外と state の挙動の理由について知らない人が多いと思ったので、勉強ついでに少しまとめてみようと思います。

state の挙動について

state には以下の特徴があります。

  • state は Object.assign()スプレッド演算子(...Object) として 値を渡さないとうまく変更が反映されない
  • state を直接変更すると再描画が起こらない
  • state を他の変数に入れて、その変数を変更して state に再代入しても再描画が起こらない(または挙動がおかしくなる)

これらの挙動はフレームワーク固有の特徴ではなく Javascript 自体の特徴です!

Javascript の Object の挙動について

まず state の挙動を理解する上で一番理解しなければいけないのは Javascript 自体の挙動についてです。 state は基本的に Object で管理されています。(React の Hooks でも Object で管理する場合は同じです)

この Object がかなり重要です。Objectの特徴は以下の通りです.

  1. Object 同士で比較する場合、メモリを比較するため Object の中身を比較しているわけではない
  2. 値は参照渡しで代入される

これらが Object の特徴であり、理解しなければいけない点です。

まず1の Object 同士で比較する場合、メモリを比較するため Object の中身を比較しているわけではない について実際にコードで見ていきます。

example1.js
const a = {key: 'test'};
const b = {key: 'test'};

/* Object を比較する方法は2通りあります。 */
console.log(a === b); // result: false
console.log(Object.is(a, b)); // result: false

このように Object はメモリの比較によって結果が求められるので変数が異なれば、値が同じでも false になります。

次に2の 値は参照渡しで代入される について見ていきます。

example2.js
const a = {key: 'test'};
const b = a;

b.key = 'qiita';

console.log(a === b); // result: true
console.log(Object.is(a, b)); // result: true
console.log(`a: ${a.key}, b: ${b.key}`); // a: qiita, b: qiita

このように参照渡しで値が渡されるため変数a変数bを比較した場合に、結果がtrueになります。

結論

ここまで見てきた通り、Object は参照渡しによって値が代入されて、メモリごとの比較になるため、いちいち新しい Object を生成しなければ Object の変更が検知されないということになります。
つまり、state を直接変更した際には Object の変更が検知されていないために再描画が行われていない ということになります。
また React ではshallow equalという比較が行われているため、直接 state を変更しても再描画が行われることがありますが、挙動としては正しくないため、stateの変更をするときは新しく Object を生成するということを意識して開発しなければ予期せぬバグを産む可能性があります。

間違いや補足があれば指摘していただけたら嬉しいです。

最後まで読んでいただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cypherで「特定のnodeと同じ繋がりを持つnode」を探す

何がしたかったのか

例えばこんなグラフDBのデータがあったとします

CREATE
(q1:Question{questionId: randomUUID()}),
(q1)<-[:BELONGS_TO]-(a1:Answer{choice:1}),
(q1)<-[:BELONGS_TO]-(a2:Answer{choice:2}),
(q1)<-[:BELONGS_TO]-(a3:Answer{choice:3}),
(q1)<-[:BELONGS_TO]-(a4:Answer{choice:4}),

(q2:Question{questionId: randomUUID()}),
(q2)<-[:BELONGS_TO]-(a5:Answer{choice:1}),
(q2)<-[:BELONGS_TO]-(a6:Answer{choice:2}),
(q2)<-[:BELONGS_TO]-(a7:Answer{choice:3}),
(q2)<-[:BELONGS_TO]-(a8:Answer{choice:4})

4択問題があって、その質問(Question)と回答(Answer)が紐づいています。
とあるユーザが4択問題に回答した時は、こんなクエリを実行します

MATCH
(u:User{userId:'hoge'})

CREATE
(u)-[:CHOSE]->(a1),
(u)-[:CHOSE]->(a6)

これでユーザーhogeさんが、質問1に対して「1」、質問2に対して「2」と答えた状態が保存されます。

ユーザーhogeさんと全問同じ回答のユーザを取得したい

これが今回の目的です。

何も考えずに書いてみる

MATCH
(u:User{userId:'hoge'})-[:CHOSE]->(a:Answer),
(a)<-[:CHOSE]-(users:User)
RETURN
users

これだと「hogeさんと一つでも同じ回答をしたユーザ」を取得してしまいますから、「全問同じ回答のユーザ」ではありませんね。

WHERE ALL を使う

MATCH (u:User{userId:"hoge"})-[:CHOSE]->(a:Answer)
WITH collect(a) as answers
WHERE ALL(answer in answers WHERE (u2:User)-[:CHOSE]-(answer))
return u2

ここでWITHとALL()を使ってみます。

ALL()は、LISTの全要素が与えられた条件を満たす場合にtrueを返します。
それを応用して、ここではhogeさんが選んだAnswerのLISTの全要素に対して[:CHOSE]を貼っているユーザを抽出するというクエリを書いています。

ALL()を使うためにはLIST化する必要があるので、collect(ノード) as answers(LIST)で前処理を施しています。

これで表題の問題は解決です。

もうちょっとクエリの効率を改善する

この状態だとクエリの効率が悪いので、少しだけ改善を試みます。

MATCH (u:User{userId:"1234"})-[:CHOSE]->(a:Answer)
WITH collect(a) as answers
WITH head(answers) as head, tail(answers) as answers
MATCH (u2:User)-[:CHOSE]-(head)
WHERE ALL(answer in answers WHERE (u2)-[:CHOSE]-(answer))
return u2

追加したのは以下2行です

WITH head(answers) as head, tail(answers) as answers
MATCH (u2:User)-[:CHOSE]-(head)

head()は与えられたLISTの最初の要素のみ抽出し、tail()は与えられたLISTの最初の要素以外を抽出します。(なんかセットに使われる事を前提に作られたリスト関数に見えるけど、tailを独立して使う事あるのかな・・・)

要はなんでもいいからhogeさんの回答と同じ回答を1つ持つユーザに、最初に絞っておくことで、一問でも異なる回答をしたユーザを最初に弾いて、後の計算量を減らしています。2択問題だとユーザが絞りきれないのでイマイチかもしれませんが、1〜10のスケールとか、一致する確率が低い問題になるほど効率化が図れるのではないでしょうか。

まとめ

  • all()を使う
  • head/tailを使うと、LISTマッチ系のクエリは少し効率化できる場合がある

以上、apoc使わずintersectionを実装するとこんな感じだよ、という紹介でした

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで配列を一時的に逆順にしたい場合は slice().reverse()

JavaScriptで配列を逆順にするには、reverse() メソッドを使います。

var table = ['a', 'b', 'c'];
console.log(table.reverse());

しかし、このメソッドは配列自体を加工してしまうため、それ以降配列が常に逆順になってしまいます。

表示するときだけ逆順にしたいなどの場合は、別の配列に移してもよいですが次のようにすると手軽です。

console.log(table.slice().reverse());
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GASで電車が遅延・運休したらメールするスクリプトを書いてみた

背景

GASの勉強がてら何か作って見ようと思っていたら 電車が遅延・運休したらLINEに通知するスクリプトを書いてみたという記事が目に入ったので真似してみました。
※構成も真似してます。すいません。

ただ、おじさん未だにガラケーなのでLINEじゃなくてガラケーにメールに通知するだけですが。

事前に用意するもの

  • Googleアカウント

しくみ

基本的には元記事と同じ(鉄道運行情報から情報取得)ですが、テキストで持ってた情報はスプレッドシートに記述することにしました。

というわけでGoogleスプレッドシートで新規作成しておきます。

A列には元記事のSENKU_DB.txtの内容を、B列にはURL_DB.txtの内容をコピペします。

と思ったけど、メールを何通も送られると料金的に困るので

  • スプレッドシートにチェックマークが付いているものだけ対象

ということにします。というわけでA列の左に1列挿入してA1:A22にチェックボックスを挿入しておきます。

spreadsheet.png

というわけで、A列: チェックボックス、B列: 線区名、C列: URL、となっています。

GASを使うので ツール - スクリプトエディタ からスクリプトエディタを開いて、適当な名前で保存しておきます。

実装したスクリプト

遅延のキーワードも真似してます。

実装で変えているのは、

  • 午前二時から四時にはそもそも情報提供されていないので何もしない(GASの制限対策)
  • キーワードの抽出は正規表現で手抜き
  • D列に前回チェックした内容を記憶しておく
  • 前回の内容とスクレイピングした内容が違っていればメール送信

といったところでしょうか。

function range(begin, end) {
  var result = [];
  for (var i = begin; i <= end; ++i) {
    result.push(i);
  }
  return result;
}

function myFunction() {
  try {
    // 2:00~4:00は何もしない
    var hours = new Date().getHours();
    if (2 <= hours && hours < 4) return; 

    var sheet = SpreadsheetApp.getActiveSheet(),
        lastRow = sheet.getLastRow(),
        messages = range(1, lastRow)
    .map(function (row) {
      // チェックマークが付いているところだけ対象
      if (!sheet.getRange(row, 1).isChecked()) return '';
      // C列がURL
      var url = sheet.getRange(row, 3).getDisplayValue(),
          // URLにアクセスして取得した内容から必要な部分だけ抽出(正規表現で手抜き)
          message = (UrlFetchApp.fetch(url).getContentText().match(/<div\s+class="corner_block_row_detail_d".*?>\s*([\s\S]*?)\s*<\/div>/)||[])[1];
      // 何もないときは空文字列
      if (message === '現在、平常通り運転しています。' || message === '情報提供時間は4:00~翌2:00となっています。') message = '';
      // 前回のチェック内容と比較(前回の内容はD列に記録)
      var rng = sheet.getRange(row, 4),
      old = rng.getValue();
      if (old === message) return '';
      rng.setValue(message);
      // 何もないときは通知しない
      if (!message) return '';
      // B列は線区名
      var name = sheet.getRange(row, 2).getDisplayValue();
      return name + ': ' + message + '\n';
    }).join('');
    // すべての線区でなにもなければ通知しない
    if (!messages) return;
    console.log(messages);
    // 宛先はスクリプトのプロパティから取得
    var notifyTo = PropertiesService.getScriptProperties().getProperty('NOTIFY_TO');
    if (notifyTo) {
      GmailApp.createDraft(, '鉄道運行情報', messages).send();
    }
  } finally {
    console.log('end');
  }
}

メール通知はGmailを使って送ります。

メールの宛先は ファイル - プロジェクトのプロパティ から スクリプトのプロパティ を開いて NOTIFY_TO を追加して設定しておきます。

ここまで設定できたら一度実行しておきます。実行 - 関数を実行 - myFunction で実行すると初回は認証やら許可やらを求められますので適当に許可して下さい。

あとは 編集 - 現在のプロジェクトのトリガー からトリガーを追加して5分おきで実行されるようにしておきます。

GASから1日にWEBアクセスしたりメール送信できる回数は制限されているらしいんですが、5分おきなら $12 × (24 - 2) = 264$ でたかが知れてるかなーと。

実行結果

こんな感じのメールが来ます。

使用上の注意点

一つのスクリプトだとたかがしれてますが、調子に乗ってこんなスクリプトをバカスカ作っているとGASの制限を超えてしまっていろいろと動かなくなるらしいです。

https://developers.google.com/apps-script/guides/services/quotas

を見ながら、制限を超えないように調整しましょう。

まとめ

GASがあると自前のサーバーが無くてもこういうことできるんですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

毎年256日目は #プログラマの日 を JavaScript (ES2018) で実装する

毎年256日は #プログラマーの日 という以下のコードを Twitter で見かけました。
もとの開発言語は不明ですが、車載端末でも動くような JavaScript に移植してみます。
async/await と非同期イテレータを使ってみます。

    while (everyYear) {
    if (today().dayNumber == 256) {
        celebrate("Programmers' Day");
        }
    }

https://twitter.com/TRIAD_Japan/status/1172412070769258496 より。

もとのコードの処理を頭の中でシミュレーションすると、
256日目に「Programmers' Day」のお祝いがずっと続いてしまう気がしますので、
どこかでウエイトが入っているはずです。

さらに、256日目以外のことも考慮すれば、while (everyYear) の部分でウエイトが入り、
1日1回だけ while 文の中が実行されると解釈するのが妥当です。
毎日の処理ぽいのに everyYear という変数名?を利用していたもとの命名規則は、
謎ですが、なにか意図があるかと思いますので、そのまま移植することにします。

1. async/await で実装した場合

JavaScript 移植では、最初の while (everyYear) のところがクセモノです。
everyYear を変数でなくて、関数呼び出しに変更してよければ、async/await で実装できます。
もとの while (everyYear)while (await everyYear()) になります。

const sleep = s => new Promise(r => setTimeout(r, s));
const celebrate = m => console.log(m);
const today = () => ({dayNumber: 256}); // TODO
const everyYear = async () => await sleep(1000) || true;
(async () => {

    // ここから
    while (await everyYear()) {
        if (today().dayNumber == 256) {
            celebrate("Programmers' Day");
        }
    }

})();

1年を待つのは辛いので、ここではデモのため、1秒ごとに「Programmers' Day」をお祝いします。
Top-level await が実装されると、(async () => {})(); を省けるようになるはずです。

実行結果:(node v10 と Chrome 77 で動作確認済)

Programmers' Day
Programmers' Day
Programmers' Day

ただし、while (await everyYear()) { の部分については、
動詞 await の目的語が everyYear に読めてしまうので、英語として読み取りにくいです。
読みにくいコードは、安全の敵なので、イマイチです。

2. 非同期イテレータで実装した場合

あるいは、非同期ジェネレータ async function* という呪文と for-await-of 構文を使えば
everyYear を非同期イテレータの変数にできるので、関数呼び出しのカッコ () をはずせます。
もとの while (everyYear)for await (safety of everyYear) のようになります。

const sleep = s => new Promise(r => setTimeout(r, s));
const celebrate = m => console.log(m);
const today = () => ({dayNumber: 256}); // TODO
const everyYear = (async function* () {
    while (true) yield await sleep(1000) || true;
})();
let safety;
(async () => {

    // ここから
    for await (safety of everyYear) {
        if (today().dayNumber == 256) {
            celebrate("Programmers' Day");
        }
    }

})();

for-of 構文では、受ける変数が必要そうなので、safety としてみました。
while (true) yield await sleep(1000) || true; の1行は気持ち良いですが、
非同期ジェネレータ async function* はまだ arrow function で書けなないらしく、そこは残念。

仮に for await safety of everyDay であれば、日々の安全を求めて活動しながら、
256日目だけは Programmers' Day をお祝いするようなエモいニュアンスが込められそうです。


上記の JavaScript 移植版のライセンスは、もとのソースコードのライセンスに従います。

TRI_AD.jpg account.jpg


[余談]
そういえば、https://hack4.jp/relatedinfo/t-shirt/1st/ にある
Hack for Japan の T シャツに記載するコードを検討したときは、みんなでコードレビューして、
いくつかの言語でそのまま動きそうな感じに考慮した記憶があります。

    while (Japan.recovering) {
        we.hack();
    }

事故を防ぐためには、ソースコードはレビューをしてもらうのが大事ですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mobxで深い階層の変更を検知する

mobxで、深い階層のデータをobserveしようと思って、うまくいかないことがあったので調べてみました。

1. mobx.observeは、深い階層のデータを検知しない

意外かもしれませんが、mobx.observeでは直接的には、深い階層のデータの変更を検知することはできません。

たとえば、以下のようなオブジェクトを考えます。

class Sample {
  @observable // @observe.deepと同じ
  array = [];
  @observable // @observe.deepと同じ
  object = {};
}

ここで、以下のようにobserveしても・・・

mobx.observe(sample, "array", change => console.log("observe array"));
mobx.observe(sample, "object", change => console.log("observe object"));
sample.array.push(1);
sample.object.a = 10;

何も表示されません。

observableは、observable.deepと同じはずなのに、
全然deepじゃないじゃん。
そうなんです。

Angulara1.xに存在したwatch deepみたいな挙動を期待してはいけないということです。

2. でも、検知する方法はある

いや、これでは納得いきませんね。
じゃぁ、observable.deepって何なんだよとなります。

そこで、以下のようにcomputedで、array.slice()で、arrayの中身の全コピーを作ってみます。
また、objectについては、Object.valuesで、全データを取得してみます。

class Sample {
  @observable
  array = [];
  @observable
  object = {};
  @observable.ref
  objectRef = {};
  @computed
  get _array() {
    return this.array.slice();
  }
  @computed
  get _object() {
    return Object.values(this.object);
  }
  @computed
  get _objectRef() {
    return Object.values(this.objectRef);
  }
}

const sample = new Sample();
mobx.autorun(() => {
  sample.array;
  console.log("autorun array");
});
mobx.autorun(() => {
  sample._array;
  console.log("autorun _array");
});
mobx.autorun(() => {
  sample.object;
  console.log("autorun object");
});
mobx.autorun(() => {
  sample.objectRef;
  console.log("autorun objectRef");
});
mobx.autorun(() => {
  sample._object;
  console.log("autorun _object");
});
mobx.autorun(() => {
  sample._objectRef;
  console.log("autorun _objectRef");
});
mobx.observe(sample, "array", change => console.log("observe array"));
mobx.observe(sample, "_array", change => console.log("observe _array"));
mobx.observe(sample, "object", change => console.log("observe object"));
mobx.observe(sample, "objectRef", change => console.log("observe objectRef"));
mobx.observe(sample, "_object", change => console.log("observe _object"));
mobx.observe(sample, "_objectRef", change => console.log("observe _objectRef"));

sample.array.push(1);
// autorun _array
// observe _array

sample.object.a = 1;
// observe _object
// observe _object

sample.objectRef.a = 1;
// 何も表示されない

期待どおりの結果が得られました。
ちなみに、computedを使わず、autorunの中で直接、this.array.slice()や、Object.values(this.object)した場合も同じです。
ただ、observeの場合は、computedと併用するしかなさそうです。

3. 結論

たしかに、observable.deepは、深い階層まで検知する仕組みを提供しています。
ただ、observeが、それに対応してないだけです。
autorunとかcomputedで、明示的に深い階層を参照してやれば、深い階層の変更を検知することができます。

さて、上の例では、this.array.slice()や、Object.values(this.object)のような重い操作をして実験しましたが、
通常はこういう操作をする必要はないはずで、
mobxにまかせてしまえば、最適なタイミングで更新処理がかかるはずです。
たとえば、this.array[3]と書けば、配列の3番目が更新されたときだけ、それに依存する処理が走るわけです。

基本的な考え方としては、
1. observeは、深い階層の変更を検知しない
2. observeで、深い階層の検知をしたくなったら、そもそも設計が間違っている可能性があるので、設計を見直そう。
という話ではないかと思います。

個人的には、observe.deepみたいな機能があっても良いのではないかとは思うのですが・・・。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む