20210403のJavaScriptに関する記事は26件です。

SPAって何?

Reactを勉強しててSPAという言葉よく聞くようになり、なんとなくの理解だった為改めて文字に起こしてアウトプットしていきたいと思います!

一番下に参考にした記事を載せておきます!

SPAってなに?

SPAとはSingle Page Application(シングルページアプリケーション)のことです。
簡潔にまとめると、必要な部分だけ更新して、必要ないところはそのままの表示にすることです!

今までのwebアプリケーションの仕組みは、何かアクション(クリックなど)をすると、
①サーバーにリクエスト
②サーバー側でHTMLを生成
③②で生成されたHTMLを受け取り、ブラウザで描画する
流れでした。

従来の仕組みだと不要なデータまで更新される為、表示するまでに時間がかかっていました。

そこでSPAの登場!!

①ユーザーがアクションを起こす
②①のアクションに必要なものだけをサーバーにリクエスト
③帰ってきたデータをJSで処理してHTMLで表示する
流れに変わりました!

SPAの仕組みを利用することでユーザーのサービス利用時間が長くなる=購入確率などが高くなる=会社の売り上げに貢献!!←少し飛躍しすぎました。。。

だから今SPAへの温度感が非常に高まってるとか。。。

その他メリット

他のメリットデメリットについては他記事でも乗ってるので興味のある方はそちらを参考にしてみてください!
ここではざっくり説明していきます

①よりリットなweb表現ができる

ReactやVue.jsなどのライブラリやフレームワークが誕生したことによって、簡単に様々な物が実装可能なり、いろんなUIを表現することができるようになりました!

②ネイティブアプリの代用ができる

ここに関しては自分自身が体験してない為、あまり実感が湧きませんがSPAを導入することで
ネイティブアプリで実装されてたことがSPAでも表現することが可能とのことです!!←いずれここも体験してみたいです!

最後に

どうやらSPAは何かを実装する上でデファクトスタンダードそう。これからもSPAを意識しながら学習に取り組んでいきたいと思います!!

参考記事
https://rara-world.com/spa-single_page-merit/
https://www.oro.com/ja/technology/001/

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

leaflet.jsで地図に円を描画

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="css/style.css">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
    integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
    crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
    integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
    crossorigin=""></script>
    <title>Leaflet</title>
</head>
<body>
    <div id="map"></div>
    <script src="js/leaflet/map.js"></script>
</body>
</html>
css/style.css
#map {
    height: 98vh;
}
js/map.js
/// <reference path="typings/index.d.ts" />
var map = L.map('map').setView([35, 135], 16);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
L.circle([35, 135], {
    color: 'red',
    fillColor: 'red',
    fillOpacity: 0.5,
    radius: 500
}).addTo(map);

スクリーンショット 2021-04-03 22.31.35.png

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

leaflet.jsで地図にmarkerを置く

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="css/style.css">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
    integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
    crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
    integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
    crossorigin=""></script>
    <title>Leaflet</title>
</head>
<body>
    <div id="map"></div>
    <script src="js/leaflet/map.js"></script>
</body>
</html>
css/style.css
#map {
    height: 98vh;
}
js/map.js
/// <reference path="typings/index.d.ts" />
var map = L.map('map').setView([35, 135], 16);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
L.marker([35, 135]).addTo(map)

スクリーンショット 2021-04-03 22.28.35.png

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

SATソルバ―で遊んでみた(8 Queens/数独)

はじめに

Kinx で Package Manager を作ろうと思い、バージョン依存性チェックを SAT ソルバ―にやらせようと思いました。しかし複数バージョン共存できる方が良いのでは、と、バージョン依存性チェックいらないかも、と思い始めた今日この頃。結局作ったけど使わないかも、な感じになってます。

とはいえ、せっかく実装したのでいくつか遊んでみました。8 Queens と数独を解いてみます。

ちなみに Kinx とは↓コレです。

また、最後に(これもせっかくなので)バージョン依存性チェックプログラムを載せておきます。使わないかもですが。

SAT ソルバ―とは

ご注意: SAT ソルバーは v1.0.0 のリリース版には含まれていません。最新の master ブランチに含まれています。

充足可能性問題を解く機械(プログラム)。Wikipedia が詳しいのでご参考。Kinx では picosat をバックエンドに使いました。

簡単に言うと、$(x_1 \lor x_2) \land (x_1 \lor \lnot x_2) \land (\lnot x_1 \lor \lnot x_2)$ みたいな論理式があって、この式全体が真となる各変数の真偽値の組合せを見つけるというものです。例えばこの例の場合、$x_1 = $ 真、$x_2 = $ 偽とすれば全体が真となるのでこの組合せは解の一つです。

SAT ソルバ―を使う、というのは、与えられた問題を上記のような論理式で表し、SAT ソルバーに解かせ、出てきた結果を解釈する、ということを行うこと、意味します。

ちなみに、論理式の形は $(A \lor B \lor \ldots) \land (X \lor Y \lor \ldots) \land ...$ と OR でつながった式を AND でつなげた式として表現します。また、ある命題を解く際に、$A \Rightarrow B$ ($A$ ならば $B$)の条件を考えるシーンがよくありますが、これは論理式として同一の解となる $\lnot A \lor B$ で置き換えて考えます。

Kinx で使うには、以下のように using します。また、ライブラリは Satisfiablity クラスで、インスタンス化しておきましょう。

using SatisfiablitySolver;
var s = new Satisfiablity();

8 Queens を解いてみた

8 Queens を解いてみましょう。8 Queens に関しても Wikipedia が詳しいのでご参考。

SAT ソルバ―を使うということは、8 Queens の問題を論理式で表すということです。どう表せばよいでしょう。

SAT ソルバーでは、各変数を整数で管理します。そして、変数 1 が真の場合は +1、偽の場合は -1 として符号で判断できるようにします。従って、全ての状態を変数として番号を付け、ある変数(番号)が真(プラス)の時に他の変数(番号)がどうなるのかを論理式で表すようにします。こんな感じです。

  • 変数
    • すべてのマスを表す変数を仮定する。8 x 8 マスなので、64 個の変数がある。
    • 変数が真の場合は Queen が存在するものとする。
  • 条件(制約)
    • 行(列でも良い)の中に少なくとも 1 つは Queen が存在する。
    • ある $(i, j)$ 座標に Queen があるとした場合、その上下左右、ナナメ全てに Queen は存在しない。

この時、例えば最初の条件として 1 行目を考えます。1行目を表す変数を $x_1$, $x_2$, $x_3$, $\ldots$, $x_8$ とすると、$(x_1 \lor x_2 \lor x_3 \lor x_4 \lor x_5 \lor x_6 \lor x_7 \lor x_8)$ となります。2 ~ 8 行目も同じように条件が書けます。

プログラム的には、これを配列で表して、[1, 2, 3, 4, 5, 6, 7, 8] と扱います。全部真なのでプラスです。この 1 つの OR の式を節(Clause)と言います。複数の節を作って条件を絞っていきます。

まず、分かりやすくするために、ある $(i, j)$ の変数の番号を返す関数を作っておきましょう。ここで N は 8 です。+1 するのは、変数は 1 始まりだからです。

const N = 8;

function getVarNum(i, j) {
    return (i + j * N) + 1;
}

では各行に少なくとも 1 つは Queen が存在する、というのを書くと以下のようになります。これで [1, 2, 3, 4, 5, 6, 7, 8][57, 58, 59, 60, 61, 62, 63, 64] までの 8 つの節が登録されます。

for (var jx = 0; jx < N; ++jx) {
    s.addClause(N.times().map { => getVarNum(_1, jx) });
}

次の条件「ある $(i, j)$ 座標に Queen があるとした場合、その上下左右、ナナメ全てに Queen は存在しない。」はどう表しましょう。これは、$A \Rightarrow B$ の形で個別に指定していきます。例えば、「$(0, 0)$ にあれば $(1, 0)$ には存在しない」は変数で表すと、$x_1 \Rightarrow \lnot x_2$ なので、$\lnot x_1 \lor \lnot x_2$ です。つまり、節としては [-1, -2] になります。すべてのマスで上下左右、ナナメそれぞれ同じように節を追加していきます。

ところで、実は $x_1 \Rightarrow \lnot x_2$ は $x_2 \Rightarrow \lnot x_1$ と同じことを表しています。なぜなら OR は交換可能なのでどちらも $\lnot x_1 \lor \lnot x_2$ だからです。ということで、左上から順にスキャンする場合、あるマス $(i, j)$ の条件を調べる際は既に設定した節と同じものを設定する必要はありません。つまり、右下左下、だけをスキャンして節を登録すればよいのです。

これをプログラムにすると、以下のようになります。これをすべてのマスで呼びます。

function addClauses(s, i, j) {
    const ijQ = getVarNum(i, j);

    /* right */
    for (var ix = i + 1; ix < N; ++ix) {
        s.addClause([-ijQ, -getVarNum(ix, j)]);
    }

    /* down */
    for (var jx = j + 1; jx < N; ++jx) {
        s.addClause([-ijQ, -getVarNum(i, jx)]);
    }

    /* right-down */
    for (var ix = i + 1, jx = j + 1; ix < N && jx < N; ++ix, ++jx) {
        s.addClause([-ijQ, -getVarNum(ix, jx)]);
    }

    /* left-down */
    for (var ix = i - 1, jx = j + 1; ix >= 0 && jx < N; --ix, ++jx) {
        s.addClause([-ijQ, -getVarNum(ix, jx)]);
    }
}

for (var ix = 0; ix < N; ++ix) {
    for (var jx = 0; jx < N; ++jx) {
        addClauses(s, ix, jx);
    }
}

さて、ここまで準備できたら SAT ソルバーから解を求めます。Satisfiablity クラスはイテレータブルにしてあるので、以下のように for-in で回せます。

function display(e) {
    System.print("\nSolution %d\n" % ++solution);
    var board = [];
    for (var ijQ = 0, l = e.length(); ijQ < l; ++ijQ) {
        var i = ijQ % N;
        var j = Integer.parseInt(ijQ / N);
        board[i][j] = e[ijQ] > 0;
    }
    for (var jx = 0; jx < N; ++jx) {
        for (var ix = 0; ix < N; ++ix) {
            System.print(board[ix][jx] ? " Q" : " .");
        }
        System.println("");
    }
}

for (var e in s) {
    display(e);
}

実行すると、無事 92 個の解が表示されました。

$ ./kinx examples/sat_nqueens.kx

Solution 1
 . . . . . . . Q
 . Q . . . . . .
 . . . . Q . . .
 . . Q . . . . .
 Q . . . . . . .
 . . . . . . Q .
 . . . Q . . . .
 . . . . . Q . .

Solution 2
 . . . Q . . . .
 . . . . . . . Q
 . . . . Q . . .
 . . Q . . . . .
 Q . . . . . . .
 . . . . . . Q .
 . Q . . . . . .
 . . . . . Q . .

/* 省略 */

Solution 92
 . . . Q . . . .
 . . . . . . Q .
 . . . . Q . . .
 . Q . . . . . .
 . . . . . Q . .
 Q . . . . . . .
 . . Q . . . . .
 . . . . . . . Q

数独を解いてみた

次に数独を解いてみます。数独に関してもまた Wikipedia が詳しいのでご参考。

数独は変数の数が増えます。縦横に加えてマスごとに 1 ~ 9 までの数値を取るといった奥行的な(立体的な)イメージを持つ必要がありますが、考え方は一緒です。

各マスが 1 ~ 9 までの数値を取るので、マスごとに 9 個の状態を持たせます。つまり、左上のマスが 1 であるとき、2 であるとき、... と全て異なる変数として扱います。例えば、$R \times R$ の数独において $(i, j)$ が $v$ であるときの変数の番号を $(i + j \times R) \times R + v$ として定義します。$v$ が 1 から始まるので 1 加える必要はありません。例えば、変数 1 が真であれば、左上のマスは 1 です。変数 9 が真であれば、左上のマスは 9 であることを表します。変数 10 はマスが移って、左上の 1 つ右(左から 2 番目)のマスが 1 ということになります。

では早速先ほどと同様に、変数の番号を返す関数から定義してみましょう。$R \times R$ の数独です。

function getVarNum(i, j, v) {
    return ((i + j * R) * R) + v;
}

次に制約条件です。「ミニ区画」と言ってますが、$9 \times 9$ の数独の場合、その中に 9 つ存在する $3 \times 3$ の領域のことを意味しています。この 3 を $N$ とします。つまり、$R = N^{2}$ です。

  • あるマス $(i, j)$ には 1 ~ 9 のいずれかの数値が入る。
  • あるマス $(i, j)$ が $v$ であるとき、同じ $(i, j)$ のマスには $v$ 以外の数値は存在しない。
  • あるマス $(i, j)$ が $v$ であるとき、同じ行に $v$ は存在しない。
  • あるマス $(i, j)$ が $v$ であるとき、同じ列に $v$ は存在しない。
  • あるマス $(i, j)$ が $v$ であるとき、そのマスを含むミニ区画に $v$ は存在しない。

これをプログラムで書くと、次のようになります。

function addClauses(s, i, j, v) {
    const ijv = getVarNum(i, j, v);

    // There is at most one number in each cell. (Cell - uniqueness)
    for (var vi = 1; vi <= R; ++vi) {
        if (v != vi) {
            s.addClause([-ijv, -getVarNum(i, j, vi)]);
        }
    }

    // Each number appears at most once in each row. (Row - uniqueness)
    for (var ix = i + 1; ix < R; ++ix) {
        s.addClause([-ijv, -getVarNum(ix, j, v)]);
    }

    // Each number appears at most once in each column. (Column - uniqueness)
    for (var jx = j + 1; jx < R; ++jx) {
        s.addClause([-ijv, -getVarNum(i, jx, v)]);
    }

    var si = Integer.parseInt(i / N) * N;  // i as a left top in sub square.
    var sj = Integer.parseInt(j / N) * N;  // j as a left top in sub square.
    var ei = si + N;
    var ej = sj + N;
    // Each number appears at most once in each block. (Block - uniqueness)
    for (var ix = si; ix < ei; ++ix) {
        for (var jx = sj; jx < ej; ++jx) {
            const ijv2 = getVarNum(ix, jx, v);
            if (ijv2 > ijv) {
                s.addClause([-ijv, -ijv2]);
            }
        }
    }
}

for (var j = 0; j < R; ++j) {
    for (var i = 0; i < R; ++i) {
        const ijv = ((i + j * R) * R);
        var v = test[j][i];
        if (v == 0) {
            // There is at least one number in each cell. (Cell - definedness)
            s.addClause(R.times().map { => ijv + (_1 + 1) });
        } else {
            s.addClause([ ijv + v ]);
        }
        R.times().each { => addClauses(s, i, j, (_1 + 1)) };
    }
}

実際に $9 \times 9$ の数独を解いてみましょう。

+----------+----------+----------+
|  8  .  . |  .  .  . |  .  .  . |
|  .  .  3 |  6  .  . |  .  .  . |
|  .  7  . |  .  9  . |  2  .  . |
+----------+----------+----------+
|  .  5  . |  .  .  7 |  .  .  . |
|  .  .  . |  .  4  5 |  7  .  . |
|  .  .  . |  1  .  . |  .  3  . |
+----------+----------+----------+
|  .  .  1 |  .  .  . |  .  6  8 |
|  .  .  8 |  5  .  . |  .  1  . |
|  .  9  . |  .  .  . |  4  .  . |
+----------+----------+----------+

+----------+----------+----------+
|  8  1  2 |  7  5  3 |  6  4  9 |
|  9  4  3 |  6  8  2 |  1  7  5 |
|  6  7  5 |  4  9  1 |  2  8  3 |
+----------+----------+----------+
|  1  5  4 |  2  3  7 |  8  9  6 |
|  3  6  9 |  8  4  5 |  7  2  1 |
|  2  8  7 |  1  6  9 |  5  3  4 |
+----------+----------+----------+
|  5  2  1 |  9  7  4 |  3  6  8 |
|  4  3  8 |  5  2  6 |  9  1  7 |
|  7  9  6 |  3  1  8 |  4  5  2 |
+----------+----------+----------+

解けました。

ところが、これでもいいのですが、実は $25 \times 25$ の数独で返ってきません。実は冗長であっても節を増やしたほうが余計な条件を枝刈りして速く解を得ることができたりします。ここでは、以下の条件を追加してみます。

  • 各数値は、少なくとも 1 つは各行に存在する。
  • 各数値は、少なくとも 1 つは各列に存在する。

プログラムで書くとこんな感じです。

for (var v = 1; v <= R; ++v) {
    // Each number appears at least once in each row. (Row - definedness)
    for (var j = 0; j < R; ++j) {
        s.addClause(R.times().map { => getVarNum(_1, j, v) });
    }
    // Each number appears at least once in each column. (Column - definedness)
    for (var i = 0; i < R; ++i) {
        s.addClause(R.times().map { => getVarNum(i, _1, v) });
    }
}

これを加えると、$25 \times 25$ の数独も 1 秒未満で答えが得られました!

+----------------+----------------+----------------+----------------+----------------+
|  . 12  .  .  . |  .  .  .  .  . |  .  .  .  9  . |  .  . 15  .  . | 22  .  .  .  . |
|  .  .  .  .  . |  .  9  . 19  . |  .  . 10 11  . |  .  .  .  .  . |  .  .  .  .  . |
|  .  4  . 22  . |  .  .  .  .  . |  .  .  .  .  . |  .  . 12  .  . | 20 15  1  .  . |
| 16  1 20 15  . |  .  .  .  .  . |  .  .  .  .  . | 14  .  4  . 22 | 12 25  .  .  . |
|  .  .  .  .  . |  .  7  2 11  . | 23  . 19  8  . |  .  .  . 13  . |  .  .  .  .  . |
+----------------+----------------+----------------+----------------+----------------+
| 13  .  8  .  2 |  .  .  .  .  . |  .  .  7 23  6 |  .  9  . 19 11 |  .  .  .  .  . |
|  .  .  .  . 23 |  .  .  .  . 16 |  .  .  .  .  . |  .  .  .  .  . |  1  .  .  .  . |
|  7  .  .  . 10 |  3  .  .  .  . |  .  .  9 19  . |  . 13  . 23  . |  .  .  .  5  . |
|  .  .  .  .  . | 15  .  .  . 22 |  .  .  .  .  . |  .  .  .  .  . | 25 20  .  .  . |
|  .  .  .  .  . | 12  . 14  1 25 |  .  .  .  .  . |  .  .  3  .  . | 16  4 15  .  . |
+----------------+----------------+----------------+----------------+----------------+
|  .  .  .  .  . |  . 19  9  .  . |  .  . 13  7  . |  .  .  .  5  . |  .  .  . 23 10 |
|  . 22  . 25 17 |  .  .  .  .  . |  .  .  .  .  . | 12  . 20  .  . |  .  .  .  .  . |
|  . 20 12 16  . |  .  .  .  .  . |  .  .  .  . 14 | 15 22  1  . 25 |  .  .  .  .  . |
|  . 15  .  .  . |  . 11  .  .  . |  .  .  .  .  . |  .  . 16  .  . |  .  .  .  9  . |
|  .  .  .  1  . |  . 10  . 23  . |  .  .  .  . 18 |  .  .  .  .  . |  .  .  .  .  8 |
+----------------+----------------+----------------+----------------+----------------+
| 10  .  .  .  8 |  . 13  .  5  . |  .  .  .  .  . |  . 19  . 11 23 |  .  .  .  6  . |
|  .  .  . 17  7 |  .  .  .  .  . |  .  .  .  .  1 |  .  .  .  .  . |  4 22  .  .  . |
|  .  .  .  . 11 |  . 23  .  .  . |  .  .  .  . 20 |  .  .  .  2  . | 14  .  .  .  . |
| 19  . 23  .  5 |  .  8  .  9  . |  . 21  .  .  . |  . 10  .  7  . |  .  .  .  .  . |
|  .  3  .  .  . |  .  .  .  .  . | 25  4  .  . 12 |  .  .  .  .  . | 15  1 16  .  . |
+----------------+----------------+----------------+----------------+----------------+
|  .  .  .  .  . |  .  .  .  . 15 |  . 12  .  . 25 |  1  . 22  .  . |  3  .  .  .  . |
| 23  .  .  . 19 |  .  2  .  .  . |  .  .  .  .  . |  .  .  . 10  . |  .  .  .  7 11 |
|  .  .  . 18  . |  .  .  .  .  . |  . 20  .  .  . |  .  .  .  .  . |  .  .  .  .  . |
|  .  .  .  .  . |  .  .  .  .  4 | 14 15  .  . 22 |  .  .  .  .  . |  .  .  . 10  . |
| 11  .  .  .  9 |  .  .  .  .  . |  .  .  .  .  . |  .  .  .  .  . |  .  .  . 19  . |
+----------------+----------------+----------------+----------------+----------------+

+----------------+----------------+----------------+----------------+----------------+
|  8 12 11 10 18 | 14 25  4 16 24 | 20 17  1  9 21 | 19  5 15  6  2 | 22 23  7  3 13 |
|  2 23 13  7 25 | 22  9 15 19 20 | 12 24 10 11  4 | 17 16 18  1  3 |  8  6  5 14 21 |
|  9  4 19 22 21 | 13  3 18 17  5 | 16  6 25 14  7 | 23 11 12  8 10 | 20 15  1 24  2 |
| 16  1 20 15 24 | 10 21  6  8 23 |  5  2 18  3 13 | 14  7  4  9 22 | 12 25 11 17 19 |
| 14  5 17  6  3 |  1  7  2 11 12 | 23 22 19  8 15 | 21 25 24 13 20 | 10  9 18 16  4 |
+----------------+----------------+----------------+----------------+----------------+
| 13 25  8  4  2 | 18 20 21 24 10 | 15  1  7 23  6 | 16  9  5 19 11 | 17  3 14 22 12 |
| 22 24  3 21 23 |  9 17 19 13 16 | 18 14 20 25  5 |  6 12  2 15  4 |  1 10  8 11  7 |
|  7 14 15 12 10 |  3  4  8  2 11 | 22 16  9 19 17 | 20 13 25 23  1 |  6 24 21  5 18 |
| 17 11 16  5  1 | 15  6 23  7 22 |  3 13  4 12  8 | 18 21 10 14 24 | 25 20 19  2  9 |
|  6  9 18 19 20 | 12  5 14  1 25 |  2 10 21 24 11 |  7 17  3 22  8 | 16  4 15 13 23 |
+----------------+----------------+----------------+----------------+----------------+
|  3  8  2 11  4 | 20 19  9 15 18 |  1 25 13  7 16 | 24  6 14  5 17 | 21 12 22 23 10 |
|  5 22 10 25 17 |  2  1  3  4 13 |  8 11  6 21 19 | 12 23 20 18  9 |  7 14 24 15 16 |
| 18 20 12 16 13 |  8 24  5  6  7 |  9 23 17 10 14 | 15 22  1 21 25 | 19 11  2  4  3 |
| 24 15  7 23 14 | 21 11 25 12 17 |  4  5 22 20  2 | 10  8 16  3 19 | 13 18  6  9  1 |
| 21 19  9  1  6 | 16 10 22 23 14 | 24  3 12 15 18 | 11  2  7  4 13 |  5 17 20 25  8 |
+----------------+----------------+----------------+----------------+----------------+
| 10 16  1 14  8 | 25 13 12  5  2 | 17 18 15 22 24 |  4 19 21 11 23 |  9  7  3  6 20 |
| 12  2 25 17  7 |  6 15 24 18 21 | 10 19 11 13  1 |  3 14  9 20 16 |  4 22 23  8  5 |
| 15 21  4 13 11 | 17 23 16 22  3 |  7  9  8  5 20 | 25  1  6  2 12 | 14 19 10 18 24 |
| 19 18 23 24  5 |  4  8 20  9  1 |  6 21 14 16  3 | 22 10 17  7 15 | 11  2 13 12 25 |
| 20  3  6  9 22 | 11 14  7 10 19 | 25  4 23  2 12 | 13 18  8 24  5 | 15  1 16 21 17 |
+----------------+----------------+----------------+----------------+----------------+
|  4 10  5  8 16 | 23 18 11 21 15 | 19 12  2  6 25 |  1 24 22 17  7 |  3 13  9 20 14 |
| 23 17 22 20 19 | 24  2  1 25  6 | 21  8  3  4  9 |  5 15 13 10 14 | 18 16 12  7 11 |
| 25 13 21 18 15 |  7 12 10 14  9 | 11 20 24 17 23 |  8  3 19 16  6 |  2  5  4  1 22 |
|  1  7 24  2 12 | 19 16 13  3  4 | 14 15  5 18 22 |  9 20 11 25 21 | 23  8 17 10  6 |
| 11  6 14  3  9 |  5 22 17 20  8 | 13  7 16  1 10 |  2  4 23 12 18 | 24 21 25 19 15 |
+----------------+----------------+----------------+----------------+----------------+

尚、サンプルプログラムでは答えに色を付けて、元々あった数値と区別できるようにしています。こんな感じです。

image.png

バージョン依存性チェックを作ってみた

さて、当初の目的の本命です。使わないかもしれませんが。

製品(プロダクト)とバージョンの組合せを変数にして、問題を解かせて組合せとして真となるかを判断するのは一緒です。番号付けが登録順で対処する感じですが、まぁ考え方は一緒です。ただ、バージョンチェックに関していうとこれまでと違うところがあります。それは 偽となったときに何が悪かったのか を教えてあげないといけないのです。どの版数とどの版数がコンフリクトしているのか、と。

これは、これまでのやり方だけではできません。ここでは picosat の機能を使って実現しました。picosat には MUS(Minimally Unsatisfiable Subformulas)というものを見つける仕組みがあり、これを使います。

バージョンチェックには特別に用意した VersionSatisfiablity クラスを使います。

var vs = new VersionSatisfiablity();

製品 X のバージョン 0.0.1 がターゲットです。製品 A, B, Z はそれぞれバージョン 0.0.1 と 0.0.2 が存在します。

var X = vs.addProduct("X")
    .addVersion("0.0.1", true);
var A = vs.addProduct("A")
    .addVersion("0.0.1")
    .addVersion("0.0.2");
var B = vs.addProduct("B")
    .addVersion("0.0.1")
    .addVersion("0.0.2");
var Z = vs.addProduct("Z")
    .addVersion("0.0.1")
    .addVersion("0.0.2");

ここで、製品 A, B, Z の依存関係を定義します。ここでは単純に同じバージョンで依存する感じで A と B が Z に依存するものとします。

A("0.0.1").dependsOn(Z("0.0.1"));
A("0.0.2").dependsOn(Z("0.0.2"));

B("0.0.1").dependsOn(Z("0.0.1"));
B("0.0.2").dependsOn(Z("0.0.2"));

ここで、X が A と B に依存し、どちらも同じ版数に依存していれば問題無いですね。やってみましょう。

X("0.0.1").dependsOn(A("0.0.2"));
X("0.0.1").dependsOn(B("0.0.2"));
tryit();

ちなみに tryit() は結果を出すものですが、その定義は重要ではないので後程お見せします。今回は結果だけ。ちゃんと定義した依存関係の版数で問題無し、という結果になりました。

1: [{
    "index": 1,
    "isFix": 1,
    "name": "X",
    "version": "0.0.1"
}, {
    "index": 3,
    "isFix": null,
    "name": "A",
    "version": "0.0.2"
}, {
    "index": 5,
    "isFix": null,
    "name": "B",
    "version": "0.0.2"
}, {
    "index": 7,
    "isFix": null,
    "name": "Z",
    "version": "0.0.2"
}]

条件を変えて、B の 0.0.1 に依存するようにしてみましょう。

X("0.0.1").dependsOn(A("0.0.2"));
X("0.0.1").dependsOn(B("0.0.1"));
tryit();

こうなりました。

Unsatisfiable - Conflict here
- X is v0.0.1
- Z is v0.0.1 => Z is NOT v0.0.2
- A is v0.0.2 => Z is v0.0.2
- B is v0.0.1 => Z is v0.0.1
- X is v0.0.1 => A is v0.0.2
- X is v0.0.1 => B is v0.0.1

これは、以下の組合せが満たせない、と伝えています。今回はほぼほぼ全部出てしまってますが、条件が複雑になると満たせなかった条件だけが出力されます。

  • X が 0.0.1 であること
  • Z の 0.0.1 と 0.0.2 が排他であること
  • A が 0.0.2 のとき Z が 0.0.2 であること
  • B が 0.0.1 のとき Z が 0.0.1 であること
  • X が 0.0.1 のとき A が 0.0.2 であること
  • X が 0.0.1 のとき B が 0.0.1 であること

おわりに

せっかく SAT ソルバー入れてみたのでどこかで使えるといいな。パズルを解かせるのはちょっと面白かった。

そして本題の Package Manager はどうやって実現しようか考え中です。

P.S. tryit() の定義

function msg(a) {
    if (a.not) {
        return ("%1% is NOT v%2%" % a.value.name % a.value.version).format();
    } else {
        return ("%1% is v%2%" % a.value.name % a.value.version).format();
    }
}
function error(item) {
    switch (item.length()) {
    when 1:
        System.println("- ", msg(item[0]));
    when 2:
        item[0].not = !item[0].not; // (!A || B) means (A => B)
        System.println("- ", msg(item[0]), " => ", msg(item[1]));
    else:
        System.println("- ", item.map { => msg(_1) }.join(', or\n  '));
    }
}

function tryit() {
    var count = 0;
    for (var e in vs) {
        System.println("%d: " % ++count, e.toJsonString(true));
    }
    if (count == 0) {
        System.println("Unsatisfiable - Conflict here");
        vs.getConflict().each { => error(_1) };
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptでマイクから生データを取り出す

はじめに

マイクで取得したデータを、数値の配列にしたいだけなのに、苦労した話です。

調べるとScriptProcessorNodeを使ったものがいくつか出てきますが、非推奨っぽい。
AudioWorkletで代用してね、という話もあるが、これは扱いにくそう。
MediaRecorderからWav形式に変換できる方法もあるようだが、そこからうまいこと取り出すのも大変。
もっと簡単にできる方法があると思う。

結論

  1. MediaRecorderでMediaStreamからデータを取得し適当な音声ファイル形式に出力
  2. FileReaderにより音声ファイルをArrayBuffer形式に変換
  3. AudioContext.decodeAudioDataによりArrayBufferをAudioBuffer形式に変換
  4. AudioBufferからgetChannelDataなどにより生データの取得

※取得される値の詳細はAudioBufferを参照してください。WAV形式で保管される数値とは異なります。
※コードは後述

調べたこと

ScriptProcessorNode

次の方の記事に書いてあるような方法になります。

ただmozillaによると、本機能は廃止対象とのことです。なので長期使用を考える場合は、これを使わない方法が必要となります。

AudioWorklet

ScriptProcessorNodeの代替を調べると、AudioWorkletが出てきます。

次の方の記事が詳しいです。

む、難しいです。また、ファイル数が増えることや、実装行数が増えることもちょっといただけません。
私はただ、音声データの配列が欲しいだけなのです。

MediaRecorder

やはりこれで何とかしないと、とは思うのですが、うまく検索できませんでした。

Audio要素

Audio要素で再生するのは簡単ですね。
ふむ、ではここに格納されたデータを取得してうまいことできる方法は、と思ったのですが、見つけられませんでした。

decodeAudioData

音声ファイルをAudioBufferに変換するものだそうです。
音声ファイルはMediaRecorderで生成できるので、あとはAudioBufferから生データを取り出せれば、できます。

まとめ

手順は以下の通りです。

まず、MediaDevicesからMediaStreamを取得します。

let audioStream = null
navigator.mediaDevices.getUserMedia({audio: true}).then(stream => {
    audioStream = stream
})

次に、MediaRecorderを作成します。
そしてEventListenerでデータの取得・変換を行うようにします。

chunks = []

const mediaRecorder = new MediaRecorder(audioStream, {
    mimeType: 'audio/webm'
})
mediaRecorder.addEventListener('dataavailable', e => {
    if (e.data.size > 0) {
        chunks.push(e.data);
    }
})
mediaRecorder.addEventListener('stop', e => {
    const blob = new Blob(chunks)
    const reader = new FileReader()
    reader.readAsArrayBuffer(blob)
    reader.onload = () => {
        audioCtx.decodeAudioData(reader.result).then(buf => {
            const b = buf.getChannelData(0)
            console.log(b)
        })
    }
})

あとは適切なタイミングでMediaRecorderを開始・停止するだけでになります。

mediaRecorder.start()
// some process
mediaRecorder.stop()

完成コード

おまけ

そもそもなぜ必要だったかというと、

でマイクからの音声をデータとして扱えるようにするためです。
よかったら見てもらえると嬉しいです。

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

jsx,tsxでEmmetを使用する方法

はじめに

ReactでEmmetが使えたら便利だな〜って思って調べたら利用出来たのでご紹介します。
VScodeだと素の状態でもEmmetがインストールされているのでおすすめです。

方法

方法はsetting.jsonを弄るだけ。
command + , (Windows: Ctrl + ,)で設定を開きます。
検索にsetting.jsonと入れてsetting.jsonを編集をクリック

その後setting.json

setting.json
{
  "emmet.triggerExpansionOnTab": true,
  "emmet.includeLanguages": {
        "javascript": "javascriptreact",
        "typescript": "typescriptreact",
    },
}

を入れればtsx,jsxでEmmetが使用できます。

さいごに

Emmetを愛していたのでEmmetが使用できない環境は苦痛でしたが解決しました。
Emmetを使い慣れてる人にはおすすめです。

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

leaflet.jsで地図を描画

R/leafletを使ってたけど、
leaflet.jsを使った方が使えるプラグインも多そうなのでリハビリ。

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-widtho, initial-scale=1.0">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
    <title>Leaflet</title>
</head>
<body>
    <div id="map" style="height: 98vh;"></div>
    <script>
        var map = L.map('map').setView([35, 135], 16);
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        }).addTo(map);
    </script>
</body>
</html>

スクリーンショット 2021-04-03 20.37.39.png

ファイルを分けて書くと下記のような感じ。

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-widtho, initial-scale=1.0">
    <link rel="stylesheet" href="css/style.css">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
    <title>Leaflet</title>
</head>
<body>
    <div id="map"></div>
    <script src="js/map.js"></script>
</body>
</html>
css/style.css
#map {
    height: 98vh;
}

jsフォルダで下記を実行し、

typings install dt~leaflet --global --save

js/map.jsに/// <reference path="typings/index.d.ts" />を追記するとVisual Studio Codeでのleaflet.jsの実装が楽。

js/map.js
/// <reference path="typings/index.d.ts" />

var map = L.map('map').setView([35, 135], 16);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

色々と思い出せない。

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

leaflet.jsでOpenStreetMapを描画

R/leafletを使ってたけど、leaflet.jsの方が使えるプラグインも多いので練習。

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="css/style.css">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
    integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
    crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
    integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
    crossorigin=""></script>
    <title>Leaflet</title>
</head>
<body>
    <div id="map"></div>
    <script src="js/map.js"></script>
</body>
</html>

スクリーンショット 2021-04-03 22.24.27.png

ファイルを分けて書くと下記のような感じ。

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-widtho, initial-scale=1.0">
    <link rel="stylesheet" href="css/style.css">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
    <title>Leaflet</title>
</head>
<body>
    <div id="map"></div>
    <script src="js/map.js"></script>
</body>
</html>
css/style.css
#map {
    height: 98vh;
}
js/map.js
/// <reference path="typings/index.d.ts" />
var map = L.map('map').setView([35, 135], 16);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

map.jsのあるフォルダで下記を実行し、

typings install dt~leaflet --global --save

map.jsに/// <reference path="typings/index.d.ts" />を追記するとVisual Studio Codeでのleaflet.jsの実装が楽になる。

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

HTMLで絵を書く

Image.png

上の画像のようなものが作れます。

index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <title></title>
  <meta charset="utf-8" />
</head>
<body style="background-color:#D0D0D0;">
  <div class="parent">
    <div class="bg">
      <canvas id="SimpleCanvas" width="640" height="640" style="background-color:#FFFFFF;"></canvas>
      <canvas id="SimpleCanvas-layer2" width="120" height="120" style="background-color:rgba(255,0,0,0);;"></canvas>
      <canvas id="SimpleCanvas-layerFont1" width="120" height="120" style="background-color:rgba(0,0,0,0);;"></canvas>
    </div>
  </div>
</body>
<link rel="stylesheet" href="./layout.css" type="text/css">
<script type="text/javascript" src="./draw.js"></script>
</html>
layout.cs
.parent {
    width: 720px;
    height: 0px;
    position: relative; /*relativeに設定*/
    padding: 0em 0em;
    margin:0;
    z-index: 0;
}
.l1 {
    top: 20px;
    left: 0px;
    width: 720px; 
    padding: 0em 0em;
    position: relative; 
    z-index: 2;
}
.bg {
    top: 0px;   /*動的に変更されるので適当*/
    left: 0px;  /*動的に変更されるので適当*/
    width: 400px; /*撮影枠サイズ*/ 
    height: 60px; /*撮影枠サイズ*/
    position: relative; /*absoluteに設定*/
    padding: 0em 0em;
    z-index: 1;
}

.bg canvas {
    position: absolute;
}
draw.js
function draw_1Pattern(step){
  console.log(width,height,pixels.length,width*height*4)
  // ピクセル単位で操作できる
  var cnt = 0
  for (var x = 0; x < width; ++x) {
    for (var y = 0; y < height; ++y) {
      var base = (y * width + x) * 4;
      if((Math.floor(x/step))%2 == 0){
        pixels[base + 0] = 38
        pixels[base + 1] = 31
        pixels[base + 2] = 135
        pixels[base + 3] = 255

      }
      else{
        pixels[base + 0] = 0
        pixels[base + 1] = 133
        pixels[base + 2] = 84
        pixels[base + 3] = 255

      }
    }
  }
}

var canvas = document.getElementById('SimpleCanvas');
canvas.width = screen.width*0.9
canvas.height = screen.height*0.8
var context = canvas.getContext('2d');

// キャンバス全体のピクセル情報を取得
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
var width = imageData.width, height = imageData.height;
var pixels = imageData.data;  // ピクセル配列:RGBA4要素で1ピクセル
//console.log(pixels)
var step = 160//ボーダーの横幅ピクセル
draw_1Pattern(step)
//console.log(pixels)
// 変更した内容をキャンバスに書き戻す
context.putImageData(imageData, 0, 0);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ローディング画面実装方法

ページを読み込むときにローディング画面が欲しくなりますね。
ローディング画面実装の備忘録をサンプルコード付きでまとめました。

1.ページ読み込み時にはローディング画面を表示
2.DOM読み込みが終了するとローディング画面を消す

サンプルコード

<html lang="ja" class="is-loading">

<div class="c-loader">
    <div class="c-loader__bg"></div>
    <div class="c-loading__inner">
        <div class="c-loader__icon"></div>
    </div>
</div>
.c-loader {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 100;
  text-align: center;
  pointer-events: none;
}

.c-loader__bg {
  display: block;
  position: absolute;
  top: 0;
  right: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  -webkit-transition: 0.5s cubic-bezier(0.215, 0.61, 0.355, 1) 0s;
  transition: 0.5s cubic-bezier(0.215, 0.61, 0.355, 1) 0s;
  -webkit-transition-property: opacity;
  transition-property: opacity;
  background-color: #fff;
}

html.is-loading .c-loader__bg {
  opacity: 1;
}

.c-loader__icon {
  position: relative;
  z-index: 1;
  width: 24px;
  height: 24px;
  margin: -4% auto 0;
  -webkit-animation: 1s loading linear infinite;
  animation: 1s loading linear infinite;
  border: 1px solid #f5f5f5;
  border-top: 1px solid #000;
  border-radius: 50%;
  opacity: 0;
  -webkit-transition: 0.5s cubic-bezier(0.215, 0.61, 0.355, 1) 0s;
  transition: 0.5s cubic-bezier(0.215, 0.61, 0.355, 1) 0s;
  -webkit-transition-property: opacity;
  transition-property: opacity;
}

html.is-loading .c-loader__icon {
  opacity: 1;
}

@-webkit-keyframes loading {
  from {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  to {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}

@keyframes loading {
  from {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  to {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
var isLoading = false;
$(function() {
    setTimeout(function(){
        $('html').removeClass('is-loading');
    },300)
});
$(window).on('load', function() {
    if (isLoading==false) {
        loadingInit();
    }
});
function loadingInit() {
    function loading() {
        $('html').addClass('is-loaded');
    }
    loading();
}

DOMが読み込まれたタイミングで0.3秒後にis-loadingというクラスを解除、すべて読み込みが終わるとis-loadedというクラスがつくようにしています。

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

ブックマークレットで Markdown 用のリンクテキストを自動生成

またまた、バッチシリーズです。

今回は Markdown 用のリンクテキストを自動生成するブックマークレットを作成したのでご紹介いたします。

最近、手動で Markdown や Textile で参考サイトのリンクを作るのが面倒になってきたので自動化したら、(個人的には)面倒ではなくなりました。
※ただし、Mac の Safari と Chrome しか動作確認していないので、ご注意ください。

仕様

例:Yahoo のサイトでブックマークレットを実行した場合、以下のテキストがクリップボードにコピーされる

[Yahoo! JAPAN](https://www.yahoo.co.jp/)

ブックマークレットを実行したら、開いているサイトの Markdown 用リンクテキストをクリップボードにコピーする。
失敗時は alert or prompt で通知する。

あとは良しなにペーストしてお使いください。

使い方

まずはブックマークレット作成

  1. ブラウザでブックマークを追加する(空でも何でも良いです)
  2. 以下に記載してある 圧縮後 (Markdown_Link) の JavaScript をコピーする
  3. 追加したブックマークの URL に JavaScript をそのままペーストして保存する

参考サイト

ブックマークレットを実行

  1. リンクテキストを作りたいサイトをブラウザで表示する
  2. ブラウザのお気に入りからブックマークレットを選択する
  3. ブックマークレットが実行され、リンクテキストがクリップボードにコピーされる
  4. あとは Markdown テキストにペーストしてお使いください

JavaScript (Markdown_Link)

(
    () => {
// ---------------------------------------------------------------
const pageTitle = document.title;
const url = location.href;
if (pageTitle && url) {
    // link text (Markdown)
    const text = '[' + pageTitle + '](' + url + ')';

    // textarea
    const ta = document.createElement('textarea');
    ta.textContent = text;
    document.body.appendChild(ta);

    // select text
    ta.select();

    // copy to clipboard
    if (!document.execCommand('copy')) {
        prompt('コピー失敗', text)();
    }

    // remove textarea
    ta.parentNode.removeChild(ta);
} else {
    alert('テキスト取得失敗');
}
// ---------------------------------------------------------------
    }
)();

圧縮後 (Markdown_Link)

javascript:(()=>{const pageTitle=document.title;const url=location.href;if(pageTitle&&url){const text='['+pageTitle+']('+url+')';const ta=document.createElement('textarea');ta.textContent=text;document.body.appendChild(ta);ta.select();if(!document.execCommand('copy')){prompt('コピー失敗',text)()} ta.parentNode.removeChild(ta)}else{alert('テキスト取得失敗')}})()

実行結果

[Yahoo! JAPAN](https://www.yahoo.co.jp/)

JavaScript (Textile_Link)

Redmine をお使いの方は Textile 版も用意しましたので、こちらをお使いください。

(
    () => {
// ---------------------------------------------------------------
const pageTitle = document.title;
const url = location.href;
if (pageTitle && url) {
    // link text (Textile)
    const text = '\"' + pageTitle + '\":' + url;

    // textarea
    const ta = document.createElement('textarea');
    ta.textContent = text;
    document.body.appendChild(ta);

    // select text
    ta.select();

    // copy to clipboard
    if (!document.execCommand('copy')) {
        prompt('コピー失敗', text)();
    }

    // remove textarea
    ta.parentNode.removeChild(ta);
}
// ---------------------------------------------------------------
    }
)();

圧縮後 (Textile_Link)

javascript:(()=>{const pageTitle=document.title;const url=location.href;if(pageTitle&&url){const text='\"'+pageTitle+'\":'+url;const ta=document.createElement('textarea');ta.textContent=text;document.body.appendChild(ta);ta.select();if(!document.execCommand('copy')){prompt('コピー失敗',text)()} ta.parentNode.removeChild(ta)}})()

実行結果

"Yahoo! JAPAN":https://www.yahoo.co.jp/

参考サイト

注意事項

Document.execCommand()廃止

  • Document.execCommand() は廃止されたので、いつまで使えるのか分かりません
  • ブラウザで使えなくなった時のためにエラー時は手動コピーしやすいように prompt で表示させてます

参考サイト

Chrome の「新しいタブ」画面でブックマークレットが動かない

  • テスト実行時はどこかのWebサイトを表示している状態でブックマークレットを実行して下さい

参考サイト

ドロップした仕様

元々はキーボードから手を離さずに実行出来るようにしたかったのですが、以下のやり方では Mac 版 Chrome で実行しても XML が表示されるだけだったので今回は諦めました。

手順

  1. 適当なショートカット(.webloc)を作成する
  2. webloc の URL にブックマークレットを入れて保存する
  3. Commnad + space で Spotlight を表示
  4. Spotlight でショートカット(.webloc)を検索して、ブックマークレットを実行
  5. [NG] Chrome で XML が表示されるだけ

参考サイト

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

配列

配列(要素を取得)

let fruits = ['もも','りんご','バナナ','みかん','ぶどう'];

//1番目の要素
console.log(fruits[0]);  //fruits[0]:もも

//4番目の要素
console.log(fruits[3]); //fruits[3]:みかん

//6番目の要素(空っぽ)
console.log(fruits[6]); //fruits[5]:undefined

indexOf

  • 配列データに存在する場合にその場所を「インデックス番号」で取得
let fruits = ['もも','りんご','バナナ','みかん','ぶどう'];
let fruits2 = fruits.indexOf('りんご');
console.log(fruits2);//1

push

  • 配列要素の追加
let fruits = ['もも','りんご','バナナ','みかん','ぶどう'];
fruits.push ('梨');
console.log(fruits);//(6) ["もも", "りんご", "バナナ", "みかん", "ぶどう", "梨"]

delete

  • 要素の削除
let fruits = ['もも','りんご','バナナ','みかん','ぶどう'];
delete fruits[2];
console.log(fruits);//(5) ["もも", "りんご", empty, "みかん", "ぶどう"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Puppeteer(パペティア)でcsvファイルをダウンロードする

概要

ずっとやりたかったPuppeteerでファイルダウンロードをやってみた。
世の中いろんなやり方が書かれていて混乱したが、やってみたら簡単にできたので共有。

参考 - 公式issue
https://github.com/puppeteer/puppeteer/issues/299

環境

  • Ubuntu18.04
  • Node.js v14.15.1
  • Puppeteer 8.0.0

ソース

意外と普通にできました!
何か罠が待っている気もしなくもなく。

const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require("path");

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    const width = 1980;
    const height = 1080;
    await page.setViewport({ width, height });
    await page.goto('https://portal.local/');
    // gotoの後は、waitしなくてよさげ

    // ファイルダウンロード
    const downloadPath = path.resolve('/hoge/fuga/'); // ここは絶対に絶対パス
    await page._client.send('Page.setDownloadBehavior', {
        behavior: 'allow',
        downloadPath: downloadPath 
    });

    // csvダウンロードボタンクリック
    await page.click('.csvDownload');
    await page.waitForTimeout(5000);

    await browser.close();
})();

参考

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

【WebIOPi】スマホでサーボモーターを制御!③

スマホでサーボモータを制御!

今回は、スマホでサーボモータを制御するシリーズの第3回目です。

Raspberry Piを用いて簡単にIoTを作成できるライブラリ、WebIOPiを使用し、スマホからサーボモーターを制御します。

これまでの記事は以下を参考にしてください。
1. WebIOPiのインストール
2. HTML/CSSファイルの作成
3. JavaScriptファイルの作成

サーボモーター

サーボモーターを動作させるPythonスクリプトを作成します。

サーボモーターは、パルスの周期に応じて回転します。それぞれの仕様によりますが、制御パルスは1.0ms ~ 2.4msの範囲だと思います。

ちなみに、SG90というサーボモータは、0.5ms~2.4msだそうです。

サーボモーターを動作させるために、pigpioというライブラリを使用します。

pigpioインストール

サーボモータを動作させるときに、安定したパルスを生成できるように、pigpioというライブラリを使用します。
Raspberry Piのターミナルで以下のコマンドを打ちます。

sudo apt install pigpio

pigpioを使用する場合は、以下のコマンドで起動(デーモン)します。

sudo pigpiod

Pythonスクリプト作成

まず、全体は以下のようになります。

pyfile.py
import webiopi
import pigpio

webiopi.setDebug()      # WebIOPiのデバッグをセット
pi = pigpio.pi()

# サーボモーターへのパルス出力ピンを指定
SV_1 = 12       # SERVO1
SV_2 = 19       # SERVO2

"""以下、サーボ動作"""

# SERVO1
@webiopi.macro
def GET1(val):
    value1 = int(val)
    pi.set_servo_pulsewidth(SV_1, value1)
    webiopi.debug(value1)


# SERVO2
@webiopi.macro
def GET2(val):
    value2 = int(val)
    pi.set_servo_pulsewidth(SV_2, value2)
    webiopi.debug(value2)

WebIOPiでは、デバッグを行う際、webiopi.setDebug()という記述をします。
そして、webiopi.debug(表示したいパラメータ)と記述することでデバッグの際、そのパラメータの値を確認することができます。

webiopi.macro

ところで、JavaScriptで作成したwebiopi.callMacro()関数を覚えていますか?

webiopi().callMacro('GET1',value1)

この第一引数は、実行したいPython関数、ここではdef GET1(val):です。
また、JavaScriptより引数valを受け取ります。互いの関係は下の写真を参考にしてください。
webiopi4.png

ここで、webiopiで実行するPython関数には、関数名の上に@webiopi.macroという記述が必須です!

あとは、受け取った値を数値型に変換し、pigpioによりサーボを動作させます。

最後に

次回が最終回です。

WebIOPiを使用してサーバーを起動させます。

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

switch文

用途

  • ある式や値が複数の値を取りうる
  • それぞれの値に応じて処理を変えたい
  • 式と値が一致した処理を実行する

構文

switch {
    case 値1:
        //式の結果が値1と等しいときに実行する処理
        break;
    case 値2:
        //式の結果が値2と等しいときに実行する処理
        break;
    case 値3:
        //式の結果が値3と等しいときに実行する処理
        break;
    default:
        //式の結果がどれにも該当しないときの処理
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

QiitaやGoogleなどで使える、クオートで囲うBookmarklet作ってみた

クオートで囲うの面倒くさい

選択して"shift+@で囲ってくれるエディターの便利機能に体が慣れてしまった。
誰か作ってるだろうけど、練習用に作ってみた。

使い方

  • コピーしてブックマークのURL欄に貼り付け、編集画面でそれをクリック。
  • カーソルが十字になる。
  • 文字を選択して`とか"とか{とか押すだけ。詳しくはソース参照。
bookmarklet
javascript:
/* eslint semi: "error"*/
(function() {
  var sandwich = (p1, p2) => {
    if (p2 === undefined) {
      p2 = p1.slice(p1.length / 2);
      p1 = p1.slice(0, p1.length / 2);
    }
    var textarea = document.activeElement;
    var pos_start = textarea.selectionStart;
    var pos_end = textarea.selectionEnd;
    var val = textarea.value;
    var range = val.slice(pos_start, pos_end);
    var beforeNode = val.slice(0, pos_start);
    var afterNode = val.slice(pos_end);
    var insertNode = p1 + range + p2;
    textarea.value = beforeNode + insertNode + afterNode;
    textarea.setSelectionRange(pos_start + p1.length, pos_end + p1.length);
  };
  var handleKeydown = (ev) => {
    var el = ev.target;
    if (!['TEXTAREA', 'INPUT'].includes(el.tagName)) return;
    if (el.selectionStart === el.selectionEnd) return;
    var pd = () => ev.preventDefault() || true;

    if (ev.key === '`' && ev.ctrlKey) return pd() && sandwich('\n```\n', '\n```\n');
    if (ev.key === '`') return pd() && sandwich('``');
    if (ev.key === '{') return pd() && sandwich('{}');
    if (ev.key === '[') return pd() && sandwich('[]');
    if (ev.key === '(') return pd() && sandwich('()');
    if (ev.key === '~') return pd() && sandwich('~~~~');
    if (ev.key === '*') return pd() && sandwich('****');
    if (ev.key === '_') return pd() && sandwich('__');
    if (ev.key === '"') return pd() && sandwich('""');
    if (ev.key === '\'') return pd() && sandwich('\'\'');
    if (ev.key === '>') return pd() && sandwich('> ', '');
    if (ev.key === 'L' && ev.ctrlKey) return pd() && sandwich('[](', ')');
  };

  document.addEventListener('keydown', handleKeydown, {once: !true});
  document.title = '@' + document.title;
  var style = document.createElement('style');
  style.textContent = 'textarea,input{ cursor: crosshair}';
  document.body.appendChild(style);
})();

作ってみて分かったこと

  • 書き換えた瞬間、編集履歴が消える。
    • ctrl+zで戻れない
    • ブラウザIDEとかは戻れるし、別の方法があるんだろうなあ。
  • Qiitaでは、書き換えの直後はプレビューに反映されない。
    • ev.preventDefault()してるからかな。
  • 日本語入力中だと、なんかおかしい。
  • 総合的に、そんなに便利じゃない。
  • ```ctrlcontrolのコンビネーション。
    • alt系にしたかったけど。Macでev.keyが不明になり諦める。
  • Qiitaにドラッグアンドドロップで使えるリンクを作れなかった。
    • markdownのリンク書式にコードを改行消して入力してみたが、括弧の認識がずれて無理だった。
  • Bookmarklet Builder が let const に対応してなかった。
  • Bookmarkletはセミコロン必須
  • 動作してるかどうか分かる何かが必要。alertは邪魔くさい。
    • カーソル変化は良いと思った。bodyでなくtextareaに。
  • vscodeでjavascript:(123)て書いても怒られない。
    • ラベルはまだ良くわからない。

参考にさせてもらったページ

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

条件分岐

if文

if (条件式){
処理内容1 //条件式を満たす場合にのみ実行する
}
else{
処理内容2 //条件式を満たさない場合にのみ実行される
}
const score = 70;
if (60 > score ){
    console.log ("不合格");
} else if (60 <= score && score < 80){
    console.log ("おしい!");
} else if (80 <= score){
    console.log("合格");
}

論理演算子

  • && …かつ
  • || …または
  • ! …〜でない(否定)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【WebIOPi】スマホでサーボモーターを制御!②

スマホでサーボモータを制御!

今回は、スマホでサーボモータを制御するシリーズの第2回目です。

Raspberry Piを用いて簡単にIoTを作成できるライブラリ、WebIOPiを使用し、スマホからサーボモーターを制御します。

WebIOPiのインストールはこちらの記事を参考にしてください。

まだHTMLファイルを作成していない方は、こちらの記事も参考にしてください。

WebIOPiとJavaScript

今回は、前回作成したHTMLファイルにJavaScriptを追加します。

WebIOPiでは、JavaScriptを使用することで、Pythonとの連携が可能になります。

それでは作成していきます!

JavaScriptの実装

WebIOPiの公式ドキュメントには様々な機能が紹介されています(公式ドキュメント:機能紹介)。

その中でも、今回はWebiopi.callMacro()という関数を使用します。

この関数は、JavaScriptから特定のPython関数を実行することができます。

例えば、Webページ上のボタンが押されたら、PythonのLEDを光らせる関数を実行することも可能になります!
webiopi3.png

Webiopi.callMacro()について

WebIOPi.callMacro(macro, [args[, callback]])

Call a macro on the server.
(string) macro : name of the macro to call
(string) arg (optional) : array containing arguments
(function) callback (optional) : function called when result received from the server

WebIOPi公式ドキュメントより

引数には以下を指定します。

  • 1つ目:実行したいPython関数名
  • 2つ目:Python関数に渡す引数(スライドバーの値など)
  • 3つ目:Python関数が実行された後に実行するJavaScriptの関数

また、HTMLにJavaScriptを埋め込む際、headタグに

controller.html
<head>
...

<script type="text/javascript" src="webiopi.js"></script>
</head>

を忘れないよう、記述してください。

コード

controller.js
// 前半
var current1 = document.getElementById("myRange1");    // SERVO1 pin12
var output1 = document.getElementById("out1");
output1.innerHTML = current1.value;

var current2 = document.getElementById("myRange2");    // SERVO2 pin19
var output2 = document.getElementById("out2");
output2.innerHTML = current2.value;

// 後半
function func1(){
    var value1 = current1.value;
    output1.innerHTML = value1;
    webiopi().callMacro('GET1',value1);
}
function func2(){
    var value2 = current2.value;
    output2.innerHTML = value2;
    webiopi().callMacro('GET2',value2);
}

まず、前半はページにアクセスがあったとき、スライダの現在の値を取得して表示するプログラムです。

後半でwebiopi.callMacro関数を使用します。

function func1(){
    var value1 = current1.value;
    output1.innerHTML = value1;
    webiopi().callMacro('GET1',value1);
}

まず、2行目では現在のスライダの値を取得し、それをoutput1(idがout1の場所)に表示します。
そして、スライダの値をGET1というPython関数に渡します。

HTML全体

これまでのHTML/JavaScript全体は次のようになります。

controller.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <title>Servo Controller</title>
    <script type="text/javascript" src="webiopi.js"></script>
    <link rel="stylesheet" type="text/css" href="controller.css">
  </head>
  <body>
    <div align="center">
        <tr>
          <td>
            <table>
              <tbody>
                <tr>
                  <!--SERVO 1-->
                  <td id="name1">SERVO 1</td>                  
                  <td class="SS1">                  
                    <div class="slidecontainer1">
                      <input type="range" min="1000" max="2500" value="2000" step="10" class="slider" id="myRange1" oninput="func1()">
                    </div>  
                  </td>
                  <td id="out1"></td>
                  <!--SERVO 2-->
                  <td id="name2">SERVO 2</td>
                  <td class="SS2">                  
                    <div class="slidecontainer2"> 
                      <input type="range" min="1000" max="2500" value="2000" step="10" class="slider" id="myRange2" oninput="func2()">                                             
                    </div>  
                  </td>              
                  <td id="out2"></td>    
                </tr>
              </tbody>
            </table>
          </td>
        </tr>
    </div>
  </body>
  <script>
    var current1 = document.getElementById("myRange1");    //SERVO1 pin12
    var output1 = document.getElementById("out1");
    output1.innerHTML = current1.value;

    var current2 = document.getElementById("myRange2");    //SERVO2 pin19
    var output2 = document.getElementById("out2");
    output2.innerHTML = current2.value;

    function func1(){
        var value1 = current1.value;
        output1.innerHTML = value1;
        webiopi().callMacro('GET1',value1);
    }
    function func2(){
        var value2 = current2.value;
        output2.innerHTML = value2;
        webiopi().callMacro('GET2',value2);
    }

  </script>
</html>

最後に

次回、スライダの値に応じてサーボモーターを動かすPythonプログラムを作成していきます。

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

【Kintoneカスタマイズテンプレート】日付フィールドAの◯日後の日付を日付フィールドBへ登録する

目次

・はじめに
・カスタマイズ概要
・テンプレート
・変数説明
・おわりに

はじめに

プログラミング初心者の方や、コードをいちいち書きたくないという方の為に、4つの変数の値だけ変更すれば即使えるテンプレートを作りました!
JSEditに以下のコードをコピー&ペーストするだけでOKです。

カスタマイズ概要

from(日付フィールドA)から14日後をto(日付フィールドB)へ反映させる。
尚且、to(日付フィールドB)は手動で更新も可能。
①〜④までの変数の値を利用する用途やフィールドコードに合わせて変更してお使い下さい。

テンプレート

// #処理概要
// from(日付フィールド)から14日後をto(日付フィールド)に反映する。to(日付)は手動で更新も可能。
// #変数
//  ①reflectionDays: 加算する日数を指定する
//   ②bA: 日数を加算する場合は"add"(◯日後を表示させたい)
//      日数を減算する場合は"subtract"(◯日前を表示させたい)
//  ③from: 計算の元となるフィールド(フィールドコード)
//  ④to: 14日後を反映するフィールド(フィールドコード)

(function() {
  'use strict';
  const reflectionDays = 14;
  const bA = 'add';
  const conf = [
    {
      from: '作業依頼日',
      to: '提出希望日'
    },
//  2ペアで処理を行いたい場合は以下も記入
    {
      from: '作業依頼日_3',
      to: '提出希望日_4'
    }
  ];

// 値の変更の元となるフィールド(n)
  let targetFields = [];
  conf.forEach(field=>{
    targetFields.push(field.from);
  })

  const targetEvents = createEvents(targetFields)
  function createEvents(fields){
    var eventLists = [
      //#対応イベント
      // レコード追加画面 表示後
      // レコード追加画面 表示後 モバイル版
      'app.record.create.show',
      'mobile.app.record.create.show',
    ];
    fields.forEach(field=>{
      // #対応イベント
      // レコード一覧 インライン編集のフィールド値変更
      // レコード編集画面 フィールド値変更
      // レコード編集画面 フィールド値変更 モバイル版
      // レコード追加画面 フィールド値変更
      // レコード追加画面 フィールド値変更 モバイル版
      eventLists.push(`app.record.index.edit.change.${field}`);
      eventLists.push(`app.record.edit.change.${field}`);
      eventLists.push(`mobile.app.record.edit.change.${field}`);
      eventLists.push(`app.record.create.change.${field}`);
      eventLists.push(`mobile.app.record.create.change.${field}`);
    })
    return eventLists;
  }
  kintone.events.on(targetEvents, function(event) {
    // イベントタイプを配列に分解
    const eventTypeArray = event.type.split(".");
    console.log(eventTypeArray);
    const eventType = eventTypeArray[eventTypeArray.length -2];
    // フィールドの変更イベントか判定
    if(eventType == 'change') {
      // エベントタイプの一番後ろの文字(変更されたフィールド)を代入
      const changeField = eventTypeArray[eventTypeArray.length -1];
      // 変更元フィールドに対しての更新先フィールドをconfの中から探して代入
      const targetSetting = conf.find(setting => setting.from === changeField).to;
      // 計算処理
      let result = moment(event.record[changeField].value)[bA](reflectionDays, 'd').format('YYYY-MM-DD');
      // 計算結果を反映先フィールドへ更新
      event.record[targetSetting]['value'] = result;
      console.log('変更イベントの場合の計算結果:' + result);
    // その他のイベントの場合
    }else{
      // fromからtoへそれぞれ計算結果を反映する
      conf.forEach(fields => {
        // 計算処理
        let result = moment(event.record[fields.from].value)[bA](reflectionDays, 'd').format('YYYY-MM-DD');
        event.record[fields.to].value = result;
        console.log('変更イベント以外の場合の計算結果:' + result);
      })  
    }
    return event;
  });
})();

おわりに

この記事を上げた理由は以下になります。
1. Kintoneのアプリ開発をしていると同じようなカスタマイズ案件に出会うことがあり、その度に、毎回調べてコードを書くのが面倒。(自分の為)
2. 変数だけ変えるだけで使えるようにテンプレート化してしまえば、コードを書けない方でも使えるじゃん! と。

コードは書けないけどKintoneの標準機能だけでは満足出来ない!という方の為になれば幸いです。
これからも、変数の値を変えるだけで使えるカスタマイズテンプレートを随時投稿していきたいと思います。
最後まで見てくださりありがとうございます!

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

javascript基本メモ

初めに

JavaScriptをもう一度基礎から勉強した基本構文メモです。
自分用なので誤字脱字があるかもしれません。
今後色々追加していく予定です。

変数

変数とは繰り返し使う値を格納しておく入れ物
変数を定義することを「変数を宣言する」と言う

//変数の宣言
let a = 0;
const b = 0;
var c = 2;

//出力
console.log(a);
console.log(b);

//letかconstを使う
let(ES6~)
const(ES6~)
var(非推奨)

関数

関数とは一連の手続き(処理)を実行する文の集まり
関数に渡す変数が「引数」
関数の呼び出し元に返す値が「戻り値」

//関数の宣言
function fn(引数1, 引数2){
  return 戻り値;
}

//関数の呼び出し
fn(実引数1, 実引数2);

オブジェクト

オブジェクトとは名前(プロパティまたはキー)と値(バリュー)をペアで管理するもの

名前:値
名前:関数名
名前:オブジェクト

let obj = {
  property: 'hello',
  property: function(){
    },
  property: {
    d: 'Bye'
    }
}

//プロパティへのアクセス方法
//ドット記法
obj.name

//ブランケット記法
obj['name']

//例
let obj = {
    prop1: 'value1',
    prop2: 'value2',
    prop3: function(){
      console.log('value3')
    },
    prop4: {
      prop5: 'value5'
    }
}

obj.prop3();
console.log(obj.prop4);
console.log(obj.prop4.prop5);

//値を追加することもできる
obj.prop6 = 'value6';
console.log(obj['prop6'])

JavaScriptとは

・ECMAScriptの仕様に基づいて実装されているプログラミング言語
・環境によって使える機能が変わってくる
・universal JavaScript は様々な環境で動くように作られたJavaScript
・ブラウザとJSエンジンで違うが一番シェアがあるのはV8
・JavaScriptからweb APIsを通してブラウザを操作する
・APIとはアプリケーションプログラミングインターフェースの略でソースから別のアプリケーションを動かす仕組みのこと

実行環境

コードが実行されるまで

・JSエンジンによって生成されるコード内のどこからでもアクセスできるオブジェクト
・JavaScript実行前にはグローバルオブジェクトとthisが準備される
・ブラウザのグローバルオブジェクトはwindowオブジェクトとなる

実行コンテキスト

コードを実行する際の文脈・状況
(コードが実行されているとき、どのような状況で実行されているか)

グローバルコンテキスト(ファイル直下で実行するもの)

・実行中のコンテキスト内の変数・関数
・グローバルオブジェクト
・this
が使用可能

関数コンテキスト(関数内で実行されるもの)

・実行中のコンテキスト内の変数・関数
・argument
・super
・this
・外部変数

コールスタック

Javascriptエンジンが保持している為、実行中のコードがたどってきたコンテキストをたしかめることができる
このようなスタックの仕組みを「後入れ先だし」LIFO(Last In ,First Out)という

ホイスティング(宣言の巻き上げとも言う)
コンテキスト内で宣言した変数や関数の定義をコード実行前にメモリーに配置すること

//実行される
a();

function a() {
    console.log('hogehoge')
}

//変数の場合

var b = 0;
console.log(b);//出力される


console.log(c);//出力されない
var c = 0;

関数宣言の仕方
関数と関数式では挙動の違いがある

//これだとaを出力しようとするとエラーがでる
a();

const a = function(){
    console.log(c);
    let c = 1;
}

スコープ

スコープとは実行中のコードから値と式が参照できる範囲

windowオブジェクト=グローバルスコープ
一般的にはスクリプトスコープもグローバルスコープと呼ばれる

関数スコープとブロックスコープ

関数宣言はブロックスコープは無視されてしまう

function a() {
  //関数スコープ
}

{
 //ブロックスコープjavascriptでは{}の中と言う意味
 変数の宣言で使えるのは
    let , const

}

レキシカルスコープ

レキシカルとはプログラミングではどこになにがかかれているかという意味
実行中のコードから見た外部スコープのこと

レキシカルスコープとはコードを書く場所によって参照できる変数が変わるスコープのこと
コードを記述した時点で決定するため「静的スコープ」とも言う

let a = 2;

function fn1(){
  let b = 1;
  function fn2(){
    let c = 3;
  }
  fn2();
}
fn1();

スコープチェーン

スコープが複数階層で、連なっている状態

//スコープが複数階層になっている場合一番内側の変数から探しにいく

let a = 2;

function fn1(){
  let a = 1;
  function fn2(){
    let a = 3;
    console.log(a);
  }
  fn2();
}
fn1();

>>3

クロージャー

レキシカルスコープの変数を関数が使用している状態

function incrementFactory(){

    let num = 0;

    function increment(){
        num = num + 1;
        console.log(num);
    }

    return increment;
}

const increment = incrementFactory;

increment();
increment();
increment();
increment();

クロージャー(動的)

function addNumberFactory(num){
    function addNumber(value){
      return num + value;
    }
    return addNumber;
}

const add5 = addNumberFactory(5);
const add10 = addNumberFactory(10);
const result = add10(10);
console.log(result);

>>20

即時関数(IIFE)

関数定義と同時に一度だけ実行される関数
実行結果が呼び出し元に返却される

function a() {
  console.log('called');
}

a();

let c = (function(){
  console.log('called');
  return 0;
})

console.log(c);

let b = function(){
  console.log('called');
}();

変数

厳格な等価性と抽象的な等価性

//厳格な等価性(型の比較あり)
a === b

//抽象的な等価性(型の比較なし)
a == b

falsyとtruthy

falsyな値とはBooleanで真偽値に変換した場合falseになる値の事

falsyな値
・false
・null
・0
・0n
・undefined
・NaN
・""

truthyな値
・上記以外

AND条件OR条件

//記述方法
a && b
a || b


//値の初期化を簡略化できる
function hello(name){
  name = name || 'Tom';
  console.log('Hello' + name);
}

hello('Bob')

let name;

name && hello(name);

関数とオブジェクト

プリミティブ型とオブジェクト

・データ型ではプリミティブ型とオブジェクトが存在する
・オブジェクトは参照を名前付きでしている入れ物

・プリミティブ型は値の比較
・オブジェクトは参照の比較

【プリミティブ型】
String, Null, Number, Symbol, Boolean, Biglnt, Undefined

【オブジェクト】
Object

関数

関数は「実行可能」なオブジェクトである

prop「属性」

function a(){
  console.log('hello');
}

a.prop = 0;
a.method = function(){
  console.log('method');
}

a();
a.method();
console.log(a.prop);

>>hello
>>method
>>0

コールバック関数

他の関数に引数として渡される関数

//関数はオブジェクトとして扱える、関数を引数として渡せる

function hello(){
  console.log('hello');
}

function fn(cb){
  cd();
}

fn(hello);

>>hello

this

呼び出し元のオブジェクトへ参照を保持するキーワード

オブジェクトのメソッドとして実行された場合
'this'→呼び出し元オブジェクト

関数として実行された場合
'this'→グローバルオブジェクト

const person = {
  name: 'Tom',
  hello: function(){
    console.log('Hello' + this.name)
  }
}
person.hello();

>>Hello
>>Tom

bindと'this'

bindによって'this'や引数を固定した新しい関数を作成
→bindによるthisの束縛

function a(name){
    console.log('hello' + name);
}

const b = a.bind(null, 'Tim')
b();

call,applyと'this'

bind→'this'や引数の参照先を変更、使用時点で実行しない
call,apply→'this'や引数の参照先を変更、同時に関数を実行する

function a(name, name1){
    console.log('hello' + name + ' ' + name1)
}

const tim = {name: 'Tim'};

const b = a.bind(tim);

b();

//配列の場合には
a.apply('tim', ['Tim', 'Bob']);
//変数が独立している場合には
a.call(tim, 'Tim', 'Bob');

アロー関数

無名関数を記述しやすくした省略記法
アロー関数はthis,argments,new,prototypeの値を保持できない

const b = name => 'hello ' + name;
console.log(b('Tom'))

>>hello Tom

コンストラクター関数

新しくオブジェクトを生成するための雛形となる関数

newでオブジェクトを生成することを「インスタンス化」
newで生成したオブジェクトを「インスタンス」

function Person(name, age){
    this.name = name;
    this.age = age;
}

const bob = new Person('Bob', 18)
const tom = new Person('Tom', 65)
const sun = new Person('sun', 34)

prototype

・オブジェクトに存在する特別なプロパティ
・コンストラクター関数と合わせて使用

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.hello = function(){
    console.log('hello' + this.name)
}

const bob = new Person('Bob', 18)
const tom = new Person('Tom', 65)
const sun = new Person('sun', 34)

bob.hello();
tom.hello();

new演算子

コンストラクター関数からインスタンスを作成するために使用する演算子

//コンストラクター関数の戻り値がオブジェクト
function F(a, b){
    this.a = a;
    this.b = b;
    return {};
}

F.prototype.c = function() {}
//残余引数構文で、不定数の引数を配列として表す
function newOpe(c, ...args){
    console.log(args);
}

const insta = newOpe(F ,1 ,2);
console.log(insta);

>>(2) [1, 2]

//コンストラクター関数の戻り値がオブジェクト以外
function F(a, b){
    this.a = a;
    this.b = b;
}

F.prototype.c = function() {}

function newOpe(c, ...args){
    const _this = Object.create(c.prototype);
    const result = c.apply(_this, args);
    console.log(result, _this);

    return _this;
}

const insta = newOpe(F ,1 ,2);
console.log(insta);

>>undefined F {a: 1, b: 2}

instanceof

どのコンストラクターから生成されたオブジェクトかを確認する

//Fがコンストラクターとして定義

function F(a, b){
    this.a = a;
    this.b = b;
    // return {a: 1};
}

F.prototype.c = function(){}

const instance = new F(1, 2);
console.log(instance instanceof F)

>>true

クラス

コンストラクター関数をクラス記述でかけるようにしたもの

class Person {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    hello(){
        console.log('hello' + this.name)
    }
}

const bob = new Person('Bob', 23);
console.log(bob);


>>Person {name: "Bob", age: 23}

クラス継承

他のクラスのプロパティーとメソッドを継承すること

class Person {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    hello(){
        console.log('hello' + this.name)
    }
}

class Japanese extends Person {
    constructor(name, age, gender){
        super(name, age);
        this.gender = gender;
    }
    hello(){
        console.log('konnitiwa' + this.name);
    }
    bye(){
        console.log('sayonara' + this.name);
    }
}

const bob = new Japanese('Bob', 23, 'men');
console.log(bob);


>>Japanese {name: "Bob", age: 23, gender: "men"}

スーパー

継承元の関数を呼び出すためのキーワード

反復処理

ループ文

for(let i = 0; i < 10; i++){
    console.log(i);
}

let i = 0;
while(i < 10){
    console.log(i);
    i++;
}

演算子と優先順位

演算子とは・・・値(オペランド)を元に処理を行い、結果を返す記号のこと

演算子の優先順位
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

let a = 1 + 2 * 3;
console.log(a);

>>7

let b = (1 + 2)* 3;
console.log(b);

>>9

ループ文とブロックスコープ

ループを使ったブロックスコープの場合には、宣言したループごとに全く異なるメモリー空間に参照を保持する

for(let i =0; i < 5; i++){
  var j = i * 2;
  setTimeout(function(){
    console.log(j);
  }, 1000);

}
>> 8

配列とループ文

const array = [1, 2, 3, 4, 5];

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

let v, i = 0;
while(v = array[i++]){
  console.log(v);
}

for...inと列挙可能性

「for...in」・・・列挙可能プロティーに対して順不同で反復処理を実行する
プロトタイプチェーン内も列挙対象となる→Object.hasOwnProperty()
Symbolで定義したプロパティは「for...in」で列挙対象にならない

const obj = {
  prop1: 'value1',
  prop2: 'value2',
  prop3: 'value3'
}

Object.prototype.method = function(){}
//enumerable: falseにすると列挙対象から外れる
Object.defineProperty(Object.prototype, 'method',{
  enumerable: false
});

for(let key in obj){
  console.log(key, obj[key]);
}


//Symbolで定義した場合
const s = Symbol();
const obj = {
  prop1: 'value1',
  prop2: 'value2',
  prop3: 'value3',
  [s]: 'value4'
}

Object.prototype.method = function(){}
//enumerable: falseにすると列挙対象から外れる
Object.defineProperty(Object.prototype, 'method',{
  enumerable: false
});

for(let key in obj){
  console.log(key, obj[key]);
}

for...ofと反復可能性

イテレーターを持つオブジェクトの反復操作を行う
「イテレーター」とは反復操作を行う際に使用するオブジェクト

オブジェクトに格納されているイテレーターの挙動に依存する

const array = ['a', 'b', 'c'];

for(let v of array){
  console.log(v)
}

MapとSet

「MapとSet」・・・データを管理するための入れ物
コレクションとも呼ぶ

//Map
const map = new Map();
const key1 = {};

map.set(key1, 'value1');
console.log(map.get(key1))

const key2 = function(){}
map.set(key2, 'value2')
console.log(map.get(key2))

let key3;
map.set(key3 =0, 'value3')
console.log(map.get(key3))

map.delete(key3);
console.log(map.get(0))

for(const[k,v] of map){
  console.log(k)
}

//Set
const s = new Set();
s.add(key1);
s.add(key2);
s.add(key3);
s.delete(key3);

console.log(s.has(key2))

for(let k of s){
  console.log(k)
}

参考

【JS】初級者から中級者になるためのJavaScriptメカニズム
【JavaScript&CSS】ガチで学びたい人のためのWEB開発徹底実践(フロントエンド編)

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

【WebIOPi】スマホでサーボモーターを制御!

スマホでサーボモーターを制御

今回はRaspberry Piを使用して、スマホからサーボモーターを制御します。
WebIOPiというライブラリを使用します。インストールは、こちらの記事を参考にしてください。

今回作成するのはこんなイメージです。

Webページ上のスライダを動かすと、サーボモーターが動作します!

system.png

これから作成していくのは、

  • スライダを表示するHTML
  • スライダの値を取得するJavaScript
  • サーボモーターを動作させるPythonスクリプト

です。WebIOPiは、スライダの値を取得するJavaScriptとサーボモータを動作させるPython橋渡しのような役割を果たします。

それでは作成していきましょう!

HTML作成

HTMLファイルを作成していきます。今回は、サーボを2つ使用するため、スライダも2つ用意します。
WebIOPiを使用する際は、headタグ内に

<script type="text/javascript" src="webiopi.js"></script>

が必要です。忘れないようにしてください。
以下、スクリプトです。inputタグでtype=rangeとすることで、スライドバーが挿入されます。

controller.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <title>Servo Controller</title>
    <script type="text/javascript" src="webiopi.js"></script>
  </head>
  <body>
    <div align="center">
        <tr>
          <td>
            <table>
              <tbody>
                <tr>
                  <!--SERVO 1-->
                  <td id="name1">SERVO 1</td>                  
                  <td class="SS1">                  
                    <div class="slidecontainer1">
                      <input type="range" min="1000" max="2500" value="2000" step="10" class="slider" id="myRange1" oninput="func1()">
                    </div>  
                  </td>
                  <td id="out1"></td>
                  <!--SERVO 2-->
                  <td id="name2">SERVO 2</td>
                  <td class="SS2">                  
                    <div class="slidecontainer2"> 
                      <input type="range" min="1000" max="2500" value="2000" step="10" class="slider" id="myRange2" oninput="func2()">                                             
                    </div>  
                  </td>              
                  <td id="out2"></td>    
                </tr>
              </tbody>
            </table>
          </td>
        </tr>
    </div>
  </body>
</html>

現状のコードをブラウザで確認すると、こんな感じだと思います。
webiopi1.png

ただし、ブラウザの種類(Safari, Chrome, IEなどなど)によって、スライダの形は変わります。

後ほど、JavaScriptの実装を行うと、<td id="out1"></td>部分にスライダの値が表示されます。

CSS作成

現状のままのスライドバーだと、どこか味気ないのでアレンジしていきます。

CSSスクリプトを作成することで、HTMLをデコレーションすることができます。

今回はこんなスライドバーを作成したいと思います。
webiopi2.png

デフォルトのスライドバーよりも、スライド部分が長く、取手も大きくなっています。

それでは作成していきます。

まず、どのブラウザでも同じようなスライダが表示されるよう設定します。
SafariChrome, FireFoxでは同様のスライダが表示されると思います。他のブラウザはわかりません...

controller.css
#myRange1, #myRange2{
    -webkit-appearance:none;
    background:#182005;
    height:10px;
    width: 30%;
    border-radius:8px;
}
input[type=range]::-webkit-slider-thumb{
    -webkit-appearance:none;
    background:hsl(182, 90%, 61%);
    height:50px;
    width:20px;
    border-radius:40%;
    border: 1px solid rgb(66, 32, 129);
}
input[type=range]::-ms-tooltip{
    display:none;
}
input[type=range]::-moz-range-track{
    height:0;
}
input[type=range]::-moz-range-thumb{
    background:hsl(182, 90%, 61%);
    height:50px;
    width:20px;
    border-radius:40%;
    border: 1px solid rgb(66, 32, 129);
}

次に、スライダのサイズ、位置を調整していきます。

#myRange1{
    position:absolute;
    top:20%;
    transform:scale(2,2);
    right:50%;
}
#out1{
    transform:scale(2,2);
    position:absolute;
    top:20%;
    right:20%;
}

#myRange2{
    position:absolute;
    top:50%;
    right:50%;
    transform:scale(2,2);
    overscroll-behavior-y :none;
}
#out2{
    transform:scale(2,2);
    position:absolute;
    top:50%;
    right:20%;
} 

HTMLでCSSを読み込む

HTMLから作成したCSSファイルを読み込むよう、記述します。

HTMLとCSSを同じディレクトリに置き、HTMLのheadタグ内に以下を記述します。

controller.html
<head>
...

<link rel="stylesheet" type="text/css" href="controller.css">
</head>

全体

これまでのHTML/CSS全体は次のようになります。

controller.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <title>Servo Controller</title>
    <script type="text/javascript" src="webiopi.js"></script>
    <link rel="stylesheet" type="text/css" href="controller.css">
  </head>
  <body>
    <div align="center">
        <tr>
          <td>
            <table>
              <tbody>
                <tr>
                  <!--SERVO 1-->
                  <td id="name1">SERVO 1</td>                  
                  <td class="SS1">                  
                    <div class="slidecontainer1">
                      <input type="range" min="1000" max="2500" value="2000" step="10" class="slider" id="myRange1" oninput="func1()">
                    </div>  
                  </td>
                  <td id="out1"></td>
                  <!--SERVO 2-->
                  <td id="name2">SERVO 2</td>
                  <td class="SS2">                  
                    <div class="slidecontainer2"> 
                      <input type="range" min="1000" max="2500" value="2000" step="10" class="slider" id="myRange2" oninput="func2()">                                             
                    </div>  
                  </td>              
                  <td id="out2"></td>    
                </tr>
              </tbody>
            </table>
          </td>
        </tr>
    </div>
  </body>
</html>
controller.css
body{
    margin:0;
    overscroll-behavior-y: none;
}

#myRange1, #myRange2{
    -webkit-appearance:none;
    background:#182005;
    height:10px;
    width: 30%;
    border-radius:8px;
}
input[type=range]::-webkit-slider-thumb{
    -webkit-appearance:none;
    background:hsl(182, 90%, 61%);
    height:50px;
    width:20px;
    border-radius:40%;
    border: 1px solid rgb(66, 32, 129);
}
input[type=range]::-ms-tooltip{
    display:none;
}
input[type=range]::-moz-range-track{
    height:0;
}
input[type=range]::-moz-range-thumb{
    background:hsl(182, 90%, 61%);
    height:50px;
    width:20px;
    border-radius:40%;
    border: 1px solid rgb(66, 32, 129);
}

#myRange1{
    position:absolute;
    top:20%;
    transform:scale(2,2);
    right:50%;
}
#out1{
    transform:scale(2,2);
    position:absolute;
    top:20%;
    right:20%;
}

#myRange2{
    position:absolute;
    top:50%;
    right:50%;
    transform:scale(2,2);
    overscroll-behavior-y :none;
}
#out2{
    transform:scale(2,2);
    position:absolute;
    top:50%;
    right:20%;
} 

最後に

今回は、WebIOPiでサーボを動作させるためのHTML/CSSファイルを作成しました。

次回は、JavaScriptを実装していきます。

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

Proxyについて

Proxyとは

プロパティーの操作のために独自の処理を追加するためのオブジェクト。
値の変更を検知して、独自の処理を追加する。
targetの処理に割り込んで別の処理をさせることができる仕組みのこと。

const target = { a: 0 };
const handler = {
  set: function(target, prop, value, receiver) {
    console.log(`set: ${prop},${value}`);
    target[prop] = value;
  },
  get: function(target, prop, receiver) {
    console.log(`get: ${prop}`);
    return target[prop];
  },
  deleteProperty: function(target, prop) {
    console.log(`del: ${prop}`);
    delete target[prop];
  }
}

const pxy = new Proxy(target, handler);
pxy.a = 1;
pxy.a;
delete pxy.a;

target →対象のオブジェクト
handler →ターゲットに対する操作を表すオブジェクト
handlerはトラップと呼ばれる。
handlerとは、トラップの処理を上書きするもので各トラップに対して対応するhandlerが用意されてる。
上記のようにProxy経由で変更を加えることでpxy.a=1によって、変更があったことによりsetが呼び出される。
pxy.aによってgetが呼び出される。
deleteによって、deletePropertyが呼び出されることになる。

Reflect

Reflect オブジェクトは JavaScript エンジンが内部で使用している汎用的な関数(内部メソッド)を格納しているオブジェクト。

class Men {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}
const m1 = Reflect.construct(Men, ['taro', 25])
console.log(m1);
console.log(Reflect.has(m1, 'name'));

.constructを使用すると、new 演算子によるインスタンス生成と同等のことを、関数の記述形式で行うことができる。
.has() メソッドを使用すると、in 演算子によるプロパティの判定と同等のことを、関数の記述形式で行うことができる。

const cat = {
  na: 'Bob',
  _hello: function () {
    console.log(`hello ${this.na}`);
  }
}

const cat2 = {
  na: 'Suke',
  _hello: function () {
    console.log(`hello ${this.na}`);
  },
  get hello() {
    return this._hello();
  },
}
Reflect.get(cat2, 'hello', cat);

.get() メソッドは第3引数 receiver を指定することもできる。第1引数 targetObj と第2引数 prop で指定したオブジェクトのプロパティの値 targetObj[prop] がゲッターのとき、そのゲッターの処理内の this を第3引数で指定することができる。

ProxyとReflect

const targetObj = { 
  a: 0,
  get value() {
    return this.b;
  }
}

const handler = {
  get: function(target, prop, receiver) {
    console.log(`get: ${prop}`);
    if(target.hasOwnProperty(prop)) {
      return Reflect.get(target, prop, receiver);
    } else {
      return 'none';
    }
  }
}

const pxy = new Proxy(targetObj, handler);
console.log(pxy.value);

Proxyのhandlerから渡ってくる引数を、Reflectの引数として渡すことができる。
上記の場合だと、bに対してgetトラップを呼んでいるが、bのpropは存在しないので、noneが戻り値となる。

基礎の定着は欠かせないものなので今後も基礎のインプットはしっかりしていこうと思います。ありがとうございました。

参考

https://qiita.com/irico/items/86a03db80bb081f59519
https://www.udemy.com/course/javascript-essence/
https://hidekazu-blog.com/javascript-reflect/

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

Reactで複数要素に動的にクリック→スクロールする(createRef, useRef)

Reactでクリック→スクロールする方法について

Reactで『クリック→該当部分までスクロール』を行う場合、以下のようにcreateRefを使用することがありました。

App.tsx
const ref = createRef<HTMLDivElement>()

ここで定義したrefをスクロールさせたい箇所に渡して、

App.tsx
const handleJump = useCallback(() => {
  ref!.current!.scrollIntoView({ behavior: "smooth" })
}, [ref])

クリック時にスクロールするよう関数を定義する。すると以下のようにクリック→スクロールができます。

sample.gif

ソースコードは以下を参照いただければ幸いです。
CodeSandbox

ですが、これだとスクロール箇所が増える度に、ユニークなrefを定義していく必要があるので、
複数の要素に動的にスクロールさせることを検討しました。

結論

以下がソースコードです。
CodeSandbox

App.tsx
type Item = {
  title: string;
  background: string;
  service: string;
  otherContent?: boolean;
};

const items: Item[] = [
  { title: "コンテンツ1", background: "skyblue", service: "サービス1" },
  { title: "コンテンツ2", background: "yellow", service: "サービス2" },
  { title: "コンテンツ3", background: "green", service: "サービス3", otherContent: true }
];

↑のような配列のデータがある場合、

App.tsx
  const pageRef = useRef(items.map(() => createRef<HTMLDivElement>()));

  const scrollToView = (id: number) => {
    pageRef.current[id]!.current!.scrollIntoView({ behavior: "smooth" });
  };

↑のコードのように
- mapを使ってrefの配列を作り
- 関数(scrollToView)の引数にはidを受け取る

とすることでrefを何度も定義せずに動的にクリック→スクロールをさせることができました!

wiita.gif

参考

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

documentとHTMLElementにgetElementByXPath定義する

ブラウザのコンソールでのちょっとした作業用。探せば色々見つかるのだけど、 documentに対してだけではなく、どのHTMLElementに対しても実行できるところまでまとまってるのはあまりないようだったので。

(() => {

  const __getElementByXPath = function(path, root) {
    const result = document.evaluate(path, root, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
    return result.snapshotLength > 0 ? result.snapshotItem(0) : null
  }
  const __getElementsByXPath = function(path, root) {
    const array = []
    const result = document.evaluate(path, root, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (let i = 0; i < result.snapshotLength; i++) {
      array.push(result.snapshotItem(i));
    }
    return array;
  }

  document.getElementByXPath = function(path) {
    return __getElementByXPath(path, this)
  }
  document.getElementsByXPath = function(path) {
    return __getElementsByXPath(path, this)
  }
  HTMLElement.prototype.getElementByXPath = function(path) {
    return __getElementByXPath(path, this)
  }
  HTMLElement.prototype.getElementsByXPath = function(path) {
    return __getElementsByXPath(path, this)
  }

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

GASで別のHTMLファイルをインクルードする

はじめに

「画面の共通部分を部品化して、複数の画面から使いたい」ということはよくあると思います。
これまで経験したプロジェクトでも include という関数を作ってそのようなことを実現してきていましたが、インクルード先ではスクリプトレットが使えない、など制限があるものでした。
もっと使いやすくならないかといろいろ試した結果を書いておきます。

対象者

  • 基本的な JavaScript が読める方
  • GAS で Web アプリを作ったことがある方

いきなり結論

試行錯誤の結果、これでやりたかった以下のことができました。

  • インクルード先の HTML でもスクリプトレットを使える
  • インクルードのネスト(インクルード先でインクルード可能)
  • インクルード先にパラメーターを渡せる
Code.gs
/**
 * HTMLをインクルードする.
 *
 * @param {string} filename HTMLファイル名
 * @param {Object} params インクルード先に渡すパラメーター
 */
function include(filename, params) {
  const template = HtmlService.createTemplateFromFile(filename);
  if (params) {
    for (const key in params) {
      template[key] = params[key];
    }
  }
  return template.evaluate().getContent();
}

動作確認

:arrow_up:include 関数を動作確認します。
以下のように複数の HTML を作りました。

index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <?!= include('css'); ?>
  </head>
  <body class="index">
    <h2>this is index.</h2>
    <?!= include('sub1'); ?>
    <?!= include('sub2', { a:1, b:2 }); ?>
  </body>
</html>
css.html
<style>
div {
  margin: 5px;
  padding: 5px;
}
.index { background-color: #ffffff; }  /* 白 */
.calc  { background-color: #99ffff; }  /* 水色 */
.sub1  { background-color: #99ff99; }  /* 緑 */
.sub2  { background-color: #ffff99; }  /* 黄色 */
.sub2_1 { background-color: #9999ff; } /* 紫 */
</style>
calc.html
<div class="calc">
  <h4>this is calc.</h4>
  <?= a ?> + <?= b ?> = <?= a + b ?>
</div>
sub1.html
<div class="sub1">
  <h3>this is sub1.</h3>
<?
const a = 'A';
const b = 'B';
?>
  <?= a + b ?>
</div>
sub2.html
<div class="sub2">
  <h3>this is sub2.</h3>
  <?!= include('calc', { a:a, b:b }); ?>
  <?!= include('sub2_1', { a:a*2, b:b*2 }); ?>
</div>
sub2_1.html
<div class="sub2_1">
  <h3>this is sub2_1.</h3>
  <?!= include('calc', { a:a, b:b }); ?>
</div>

HTML どうしの関係は以下のようになっています。

  • index.html
    • css.html
    • sub1.html
    • sub2.html
      • calc.html
      • sub2_1.html
        • calc.html

つづけて Code.gsdoGet を追加します。

Code.gs
function doGet(e) {
  return HtmlService.createTemplateFromFile('index').evaluate();
}

最後に作成した Web アプリにアクセスすると以下のように表示され、期待通りの動作が確認できました。

3.png

所感

  • GAS で HTML を扱うクラスには、HtmlOutputHtmlTemplate があり、色々と機能を持っているようなのでもっと深堀りしてみると面白そうです
  • doGet 関数は HtmlOutput を返す必要があるようなので、最初は return HtmlService.createHtmlOutputFromFile('index'); と書いていましたが、これではインクルードが期待通り動きませんでした。
    • 一度 HtmlTemplate 経由で HtmlOutput を生成することに意味がある模様
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsでの標準入力の書き方をまとめてみた

初めに

最近になって、あらためて Node.jsでatCoderやLeetCodeでアルゴリズム問題を解いてみようと思ったのですが、Node.jsの標準入力の書き方は調べてみると色々あってどれがいいんだろうと少し迷ったので、いい機会なのでまとめてみました。

Node.js(標準入力)のパターン

1. /dev/stdinreadFileSyncを利用して読み込む

input1.js
var input = require("fs").readFileSync("/dev/stdin", "utf8");
console.log(input);

標準入力を表す特殊ファイル(/dev/stdin)をreadFileSyncで読み込みます。

メリット : 記述がかなりシンプル
デメリット: /dev/stdinはUNIX系のOSでしか利用できないので、Windowsでは利用できない

2. process.stdinreadlineモジュールを利用して読む

input2.js
process.stdin.setEncoding("utf8");

var lines = []; 
var reader = require("readline").createInterface({
  input: process.stdin,
});

reader.on("line", (line) => {
  //改行ごとに"line"イベントが発火される
  lines.push(line); //ここで、lines配列に、標準入力から渡されたデータを入れる
});
reader.on("close", () => {
  //標準入力のストリームが終了すると呼ばれる
  console.log(lines); 
});

改行単位でReadable streamを処理できるreadlineモジュールを利用してデータを読み込みます。

メリット : 改行単位で読み込めるので、1行ごとで処理を行う場合や一度に処理するにはインプットが大きすぎる場合には便利。
デメリット: 改行ごとの処理と読み込み完了後の処理に分かれるため、記述量が多い。

3. process.stdinfor await...ofを利用して読む

input3.js
(async () => {
  const buffers = [];
  for await (const chunk of process.stdin) buffers.push(chunk);
  const buffer = Buffer.concat(buffers);
  const text = buffer.toString();
  console.log(text);
})()

標準入力を非同期のイテラブルなオブジェクトとしてループで処理します。

メリット : 分割して読み込みされるので、一度に処理しきれないインプットを扱う場合には便利。
デメリット: 文字列への変換が必要なので、やや記述量が多い。またデータが分割される場所をコントロールしにくい。

おわりに

Windowsを利用していない場合やインプットが多くない場合は1の方法を利用して、それ以外のケースでは必要に応じて2または3の方法で記述するのが良さそうだなと思いました。

それでは素敵なNode.jsライフを。

参考リンク

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