20200728のJavaScriptに関する記事は23件です。

D3.js y軸の方向を変える

d3.jsでy軸を表示すると思っていた軸と上下が逆さまだったため、改善方法をメモ。

つまずいたコード

以下、適当なデータを使って棒グラフをつくっていきます。すでに何かしらのコードを書いていて早く修正したい人は読み飛ばしても大丈夫です(最後のまとめにコードがあります)。

棒グラフとy軸の表示(デフォルト)

とにもかくにもsvg領域を作成します。

bar.js
var width = 640;
var height = 240;

var svg = d3.select("#bar")
    .append("svg")
    .attr("width",width)
    .attr("height",height);

次に適当なデータを作成してスケールを作成します。

bar.js
var xlabel = d3.range(1,20,1);
var ylabel = [150,145,167,149,152,159,174,163,158];
var xoffset = 30;    //y軸の値が隠れないようグラフ全体を少しずらす
var xScale = d3.scale.ordinal()
    .domain(d3.range(xlabel.length))
    .rangeRoundBands([0,width]);

var rangemin = 10;
var rangemax = 200;
var yScale = d3.scale.linear()
    .domain([Math.min(...ylabel),Math.max(...ylabel)])
    .range([rangemin,rangemax]);

var yAxis = d3.svg.axis()
    .scale(yScale)
    .orient("left");

棒グラフを生成して作成したy軸も描画します。

bar.js
var bar_width = 10;  //棒の幅
// グラフの生成
svg.selectAll("rect")
    .data(ylabel)
    .enter()
    .append("rect")
    .attr("x",function(d,i){
        return xScale(i)+xoffset;
    })
    .attr("y",function(d){
        return height-yScale(d);
    })
    .attr("width",bar_width)
    .attr("height",function(d){
        return yScale(d);
    })

// 軸の表示
svg.append("g")
    .attr("class","yaxis")
    .attr("transform","translate("+xoffset+","+(height-yScale(Math.max(...ylabel)))+")")
    .call(yAxis);
});

また、少し本筋とはそれますが、生まれたてのy軸はあまり綺麗ではないのでyaxisクラスを指定してCSSで整えておきます。

style.css
.yaxis path,
.yaxis line {
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
}
.yaxis text {
    font-size: 11px;
}

棒グラフとy軸の表示結果(デフォルト)

コードを実行すると以下のようなグラフが生成されます。145という値がいちばん上に来てしまっていて棒グラフと矛盾しています。
スクリーンショット 2020-07-28 20.00.42.png

棒グラフ描画の仕組み

以上のコードで修正するために注目すべき点はグラフの表示
部分です。軸方向を変えたときに何をやっているのか理解するために必要です。

bar.js
svg.selectAll("rect")
    .data(ylabel)
    .enter()
    .append("rect")
    .attr("x",function(d,i){
        return xScale(i)+xoffset;
    })
    .attr("y",function(d){
        return height-yScale(d);
    })
    .attr("width",bar_width)
    .attr("height",function(d){
        return yScale(d);
    })

height-yScale(d)を返しているのはsvg領域の左上が原点なため、原点から高さheight-yScaled(d)の位置で棒の描画をはじめたいからです。次の図ではheight-yScaled(d)の値はオレンジ色の部分になります。

図1.png

height-yScaled(d)から描画を始めることで、オレンジ色の部分の「土台」がつくられます。すると、我々から見ると普通のグラフに見えるわけです。

図2.png

改善方法

y軸が上下反対になってしまう問題を解決するには以下のことを行う必要があります。
- スケールレンジのminとmaxを入れ替える
- y軸の位置を調整する

先程のコードであれば以下のようになります。これによってy軸の向きは反対になります。

bar.js
var yScale = d3.scale.linear()
    .domain([Math.min(...ylabel),Math.max(...ylabel)])
    .range([rangemax,rangemin]); //! maxとminを入れ替えた

しかしscaleが変更されたことによってスケーリングされた値も、さきほどとはあべこべの値になってしまいます。グラフを見ると以下のような状態です。

スクリーンショット 2020-07-28 20.29.50.png

軸方向は改善されましたが、y軸の位置とグラフの値が変わってしまいました。この2点を改善します。

bar.js
// グラフの生成
svg.selectAll("rect")
    .data(ylabel)
    .enter()
    .append("rect")
    .attr("x",function(d,i){
        return xScale(i)+xoffset;
    })
    .attr("y",function(d){
        //! height-yScale(d)から変更
        //! ylabelの最小値がrangemaxにスケーリングされるのでheightを変える
        return yScale(Math.min(...ylabel))-yScale(d);
    })
    .attr("width",bar_width)
    .attr("height",function(d){
        return yScale(d);
    })

// 軸の表示
svg.append("g")
    .attr("class","yaxis")
    .attr("transform","translate("+xoffset+",0)") //! y方向への並行移動が必要なくなったので0にした 
    .call(yAxis);
});

以上の修正で次のようなグラフが得られるようになります。

スクリーンショット 2020-07-28 21.39.45.png

修正される理屈ですが、下図のようにあべこべになったデータも新たに上限となったyScale(Math.min(...ylabel))から引く必要があります(以前のコードではデフォルトの上限であるheightから引いていた)。すると目的の表示が得られるようになります。
図3.png

まとめ

コードの変更をまとめておきます

BEFORE:

bar.js
var width = 640;
var height = 240;

var svg = d3.select("#bar")
    .append("svg")
    .attr("width",width)
    .attr("height",height);

var xlabel = d3.range(1,20,1);
var ylabel = [150,145,167,149,152,159,174,163,158];
var xoffset = 30;    //y軸の値が隠れないようグラフ全体を少しずらす
var xScale = d3.scale.ordinal()
    .domain(d3.range(xlabel.length))
    .rangeRoundBands([0,width]);

var rangemin = 10;
var rangemax = 200;
var yScale = d3.scale.linear()
    .domain([Math.min(...ylabel),Math.max(...ylabel)])
    .range([rangemin,rangemax]);

var yAxis = d3.svg.axis()
    .scale(yScale)
    .orient("left");

var bar_width = 10;  //棒の幅
// グラフの生成
svg.selectAll("rect")
    .data(ylabel)
    .enter()
    .append("rect")
    .attr("x",function(d,i){
        return xScale(i)+xoffset;
    })
    .attr("y",function(d){
        return height-yScale(d);
    })
    .attr("width",bar_width)
    .attr("height",function(d){
        return yScale(d);
    })

// 軸の表示
svg.append("g")
    .attr("class","yaxis")
    .attr("transform","translate("+xoffset+","+(height-yScale(Math.max(...ylabel)))+")")
    .call(yAxis);
});

AFTER:

bar.js
var width = 640;
var height = 240;

var svg = d3.select("#bar")
    .append("svg")
    .attr("width",width)
    .attr("height",height);

var xlabel = d3.range(1,20,1);
var ylabel = [150,145,167,149,152,159,174,163,158];
var xoffset = 30;    //y軸の値が隠れないようグラフ全体を少しずらす
var xScale = d3.scale.ordinal()
    .domain(d3.range(xlabel.length))
    .rangeRoundBands([0,width]);

var rangemin = 10;
var rangemax = 200;
var yScale = d3.scale.linear()
    .domain([Math.min(...ylabel),Math.max(...ylabel)])
    .range([rangemax,rangemin]); //! maxとminを入れ替えた

var yAxis = d3.svg.axis()
    .scale(yScale)
    .orient("left");

var bar_width = 10;  //棒の幅
// グラフの生成
svg.selectAll("rect")
    .data(ylabel)
    .enter()
    .append("rect")
    .attr("x",function(d,i){
        return xScale(i)+xoffset;
    })
    .attr("y",function(d){
        //! height-yScale(d)から変更
        //! ylabelの最小値がrangemaxにスケーリングされるのでheightを変える
        return yScale(Math.min(...ylabel))-yScale(d);
    })
    .attr("width",bar_width)
    .attr("height",function(d){
        return yScale(d);
    })

// 軸の表示
svg.append("g")
    .attr("class","yaxis")
    .attr("transform","translate("+xoffset+",0)") //! y方向への並行移動が必要なくなったので0にした 
    .call(yAxis);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kintoneからGoogleMapsAPIを呼び出して、緯度・経度から住所を取得してみた(リバースジオコーディング)

やってみたこと

kintoneアプリに緯度と経度を入力してレコードを保存すると、GoogleMapsAPIを呼び出して、「住所」フィールドに取得した住所をセットするアプリを作ってみました。

準備1:kintoneアプリの作成

まずはkintoneアプリを作成しましょう。

フィールドの種類 フィールド名 フィールドコード
数値 緯度 緯度
数値 経度 経度
文字列(1行) 住所 住所

準備2:APIキーの取得

Google Maps Platformに記載の手順で、GoogleMapsのAPIキーを取得しましょう。
3つのポイントがありますので、ご注意ください。

  • Maps JavaScript APIだけでなく、Geocoding APIも有効にする必要がある。
  • アプリケーションの制限を「HTTPリファラー(ウェブサイト)」に設定して、APIキーを盗用されないようにする。
  • Google Cloud Platform Consoleの設定にはタイムラグがある。設定が反映されないと思ったら1時間ほど待つと良い。

プログラム

  • 5行目の「APIキー」の部分は、準備2で取得したAPIキーに書き換えてください。
  • 24行目でPromiseを使用しているのは、eventをreturnしてレコードを更新するためです。
  • 31行目で「results[0]」を使用しているのは、Geocoder.geocode()は複数の結果を一致度が高い順に返すという仕様のためです。
JavaScript
(function () {
    "use strict";

    // API キー
    var api_key = "APIキー";

    // ヘッダに要素を追加
    var head = document.getElementsByTagName("head")[0];
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = "https://maps.googleapis.com/maps/api/js?key=" + api_key;
    head.appendChild(script);

    // レコード追加・編集画面の保存実行前イベント
    kintone.events.on(["app.record.edit.submit", "app.record.create.submit"], function (event) {
        // 緯度・経度の指定
        var request = {
            "location": {
                "lat": parseFloat(event.record.緯度.value),
                "lng": parseFloat(event.record.経度.value)
            }
        };

        return new Promise(function (resolve, reject) {
            // リバースジオコーディングの実行
            var geocoder = new google.maps.Geocoder();
            geocoder.geocode(request, function (results, status) {
                if (status === "OK") {
                    if (results[0]) {
                        // 住所を取得できた
                        event.record.住所.value = results[0].formatted_address;
                    } else {
                        // 住所を取得できなかった
                        event.error = "住所を取得できませんでした。";
                    };
                } else {
                    // エラーが発生した
                    event.error = "エラーが発生しました。:" + status;
                };
                resolve(event);
            });
        });
    });
})();

動作確認

緯度と経度を入力してから「保存」ボタンを押すと・・・
image1.png

住所を取得し、レコードが保存されます。
image2.png

参考にしたWebサイト

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

JavaScriptでラジオボタンの情報を取得する

「addEventListener()」を使った、チェックによるイベント処理の方法です。
「addEventListener()」とは、さまざまなイベント処理を実行することができるメソッドです。

  • 入力がされた
  • クリックがされた
  • ページが読み込まれた

などが発生した場合に「addEventListener()」を使って処理を実行できます。

書き方

対象要素.addEventListener( 種類, 関数, false )

■第1引数にイベントの種類を指定

■第2引数に関数を指定

■第3引数にイベント伝搬の方式を「true / false」で指定
通常はfalseを指定しておく。

ラジオボタンが押されたらvalueの情報を取得する

html
<form id="CheckBtn">
    <input type="radio" name="group" value="ラジオボタン1" checked> ラジオボタン1
    <input type="radio" name="group" value="ラジオボタン2"> ラジオボタン2
    <input type="radio" name="group" value="ラジオボタン3"> ラジオボタン3
</form>
javascript
const CheckBtn = document.getElementById('CheckBtn');
CheckBtn.addEventListener('change', function () {
  console.log(CheckBtn.group.value);
});

まとめ

ラジオボタンでサイトの装飾を変化させたときのコードです。

よく見るドロワーやアコーディオンメニューなどの機能も「addEventListener()」で作れるので、覚えておいても良いかも。

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

高卒工場勤務が未経験から転職するまで(Prologue)

自己紹介

はじめまして!高校卒業後→地元の工場へ就職→現在転職のため学習中
はい。こんな感じで何にも学歴も良いわけでもなく何も取り柄がないわけですが、
転職を考えております。技術で勝負するしかないのです。

Qiitaになぜ投稿し始めたか。

これは単純にアウトプットする場所が欲しかったからです。
これから少しづつアウトプットしていきます。

とりあえず触ってみた技術、言語

HTML,CSS,JavaScript,Python,Django,Vue.js
とざっくりこのあたりを触りましたが、エラーを吐かれすぎて挫折仕掛けていました。

これからどうしていくのか

エラーを吐かれて解決できずに止まることがモチベーションが下がり気味
スクールに通うか迷いましたが、闇が深すぎて断念
様々な人に聞いたところMENTAを使ってみてはどうかということで、
メンターさんについてもらうことにしました。
これからはもう少し効率的に進めていけたらと思います。

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

再帰処理で求める順列(nPr)【JavaScript】

この記事について

プログラミングクイズを解くにあたり、再帰処理を使うとスラッと解けそうな気がする問題がいくつかありました。
そのなかでも「順列」は特に使いこなせるようになりたかったので調べて記事にまとめました。

以下の説明をしています。
- 順列とは何か
- 順列を出力するサンプルコード

以下は説明しません。
- 公式 $nPr=\frac{n!}{(n-r)!}$を使った順列総パターン数の計算方法

順列とは何か

そもそも順列ってなんでしょうか?

結論

順列(nPr)は、「n個の物の中からr個を取り出して一列に並べたもの」です。
nPrPpermutationの略で、そのまま「順列」という意味です。
例えば3匹の動物から2匹を取り出して一列に並べると、以下のような順列になります。

image

組み合わせ(nCr)との違いに注意

注意しなければならないのは、「1:ねずみ」「2:うし」「1:うし」「2:ねずみ」異なるものだという点です。
image
上記を「同じ」とみなす考え方は「組み合わせ(nCr)」といいます。
この2つはよく混同されますが別物なので気をつけてください。

nCrについては以下の記事がわかりやすかったです。
5分で分かる!確率統計「nCr」の計算方法

順列の洗い出し方

どのように並べれば順列を取得のか、例を上げて解説します。

4匹から2匹選ぶ例

4匹の動物ねずみ?うし?とら?うさぎ?から2匹選んで並べるとき、以下の手順で並べるとパターンを網羅できます。

  1. [ねずみ]と[それ以外の動物]を並べる
  2. [うし]と[それ以外の動物]を並べる
  3. [とら]と[それ以外の動物]を並べる
  4. [うさぎ]と[それ以外の動物]を並べる
  ?
  - [ねずみ]と
      - [うし]
      - [とら]
      - [うさぎ]

  ?
  - [うし]と
      - [ねずみ]
      - [とら]
      - [うさぎ]

  ?
  - [とら]と
      - [ねずみ]
      - [うし]
      - [うさぎ]

  ?
  - [うさぎ]と
      - [ねずみ]
      - [うし]
      - [とら]

4匹の動物それぞれに3匹の動物を並べています。
よって 4 × 3 = 12 通り の順列ができあがります。

4匹から3匹選ぶ例

4匹の動物ねずみ?うし?とら?うさぎ?から3匹選んで並べるとき、以下の手順で並べるとパターンを網羅できます。

  1. [ねずみ]と[うし]と[それ以外の動物]を並べる
  2. [ねずみ]と[とら]と[それ以外の動物]を並べる
  3. [ねずみ]と[うさぎ]]と[それ以外の動物]を並べる
  4. [うし]と[ねずみ]と[それ以外の動物]を並べる
  5. [うし]と[とら]と[それ以外の動物]を並べる
  6. [うし]と[うさぎ]]と[それ以外の動物]を並べる
  7. [とら]と[ねずみ]と[それ以外の動物]を並べる
  8. [とら]と[うし]と[それ以外の動物]を並べる
  9. [とら]と[うさぎ]]と[それ以外の動物]を並べる
  10. [うさぎ]と[ねずみ]と[それ以外の動物]を並べる
  11. [うさぎ]と[うし]と[それ以外の動物]を並べる
  12. [うさぎ]と[とら]と[それ以外の動物]を並べる
  ?
  - [ねずみ]と
      - [うし]と
          - [とら]
          - [うさぎ]
      - [とら]と
          - [うし]
          - [うさぎ]
      - [うさぎ]と
          - [うし]
          - [とら]

  ?
  - [うし]と
      - [ねずみ]と
          - [とら]
          - [うさぎ]
      - [とら]と
          - [ねずみ]
          - [うさぎ]
      - [うさぎ]と
          - [ねずみ]
          - [とら]

  ?
  - [とら]と
      - [ねずみ]と
          - [うし]
          - [うさぎ]
      - [うし]と
          - [ねずみ]
          - [うさぎ]
      - [うさぎ]と
          - [ねずみ]
          - [うし]

  ?
  - [うさぎ]と
      - [ねずみ]と
          - [うし]
          - [とら]
      - [うし]と
          - [ねずみ]
          - [とら]
      - [とら]と
          - [ねずみ]
          - [うし]

4匹の動物それぞれに3匹の動物を並べています。
さらにそこから2匹を並べたパターンを列挙しています。
よって 4 × 3 × 2 = 24通り の順列ができあがります。

余談: 順列のパターンの総数

上記で説明したように、順列は並べる数(r)が増えれば増えるほどパターンの総数も増えていきます。
その総数を知りたい場合は「nのr回分の階乗」を求めればOKです。
階乗はその数から1ずつ引いた数を掛け合わせた数のことで、たとえば5の階乗5×4×3×2×1です。

  • 5種類から3つ選んで並べる(5の3回分の階乗)
    $ _{5}P_{3} = 5\times 4 \times3 = 60$ 通り

  • 6種類から4つ選んで並べる(6の4回分の階乗)
    $ _{6}P_{4} = 6\times 5 \times 4 \times 3 = 360$ 通り

  • 5種類から1つ選んで並べる(5の1回分の階乗)
    $ _{5}P_{1} = 5$ 通り

  • 3種類から3つ選んで並べる(3の3回分の階乗)
    $ _{3}P_{3} = 3\times 2 \times1 = 6$ 通り

上記のイメージを持っておくとコードの理解がしやすくなると思います?
きちんとした公式($nPr=\frac{n!}{(n-r)!}$)を使って求める方法もありますが、これはコードの説明から話が逸れすぎるので割愛します。

参考:【高校数学】1から分かる順列と組み合わせの違い

サンプルコード

紹介した順列の羅列方法を元にコードを書いてみます。

関数

const getPermutation = (types, r) => {
  const result = [];

  // r が 1 の場合
  if (r === 1) {
    for (let i = 0; i < types.length; i++) {
      result[i] = [types[i]];
    }
    return result;
  }

  // r が 2以上の場合
  for (let i = 0; i < types.length; i++) {
    const parts = types.slice(0);
    parts.splice(i, 1)[0];
    const row = getPermutation(parts, r - 1);
    for (let j = 0; j < row.length; j++) {
      result.push([types[i]].concat(row[j]));
    }
  }

  return result;
};

参考: 順列・組み合わせ のサンプルコード JS [permutation] [combination]

4匹から1匹選ぶ例

実行結果

const result = getPermutation(['ねずみ', 'うし', 'とら', 'うさぎ'], 1);
console.log(JSON.stringify(result));

// 実行結果
// [
//   ['ねずみ'],
//   ['うし'],
//   ['とら'],
//   ['うさぎ']
// ]

解説

rが1だった場合は、以下の条件分岐に当てはまり、ただただ要素を1つずつ別の配列にしたものが返却されます。

  // r が 1 の場合
  if (r === 1) {
    for (let i = 0; i < types.length; i++) {
      result[i] = [types[i]];
    }
    return result;
  }

4匹から2匹選ぶ例

実行結果

// 実行
const result = getPermutation(['ねずみ', 'うし', 'とら', 'うさぎ'], 2);
console.log(result);

// 実行結果
// [
//   ['ねずみ', 'うし'],
//   ['ねずみ', 'とら'],
//   ['ねずみ', 'うさぎ'],
//   ['うし', 'ねずみ'],
//   ['うし', 'とら'],
//   ['うし', 'うさぎ'],
//   ['とら', 'ねずみ'],
//   ['とら', 'うし'],
//   ['とら', 'うさぎ'],
//   ['うさぎ', 'ねずみ'],
//   ['うさぎ', 'うし'],
//   ['うさぎ', 'とら'],
// ];

解説

関数が実行されると、まず['ねずみ','うし','とら','うさぎ']を順に処理するためのループを開始します。
(以降はループ1回目(i = 0)の前提で説明します。)

 for (let i = 0; i < n.length; i++) {

次に、「parts」という変数に [ 'うし', 'とら', 'うさぎ' ]を代入しています。

const parts = types.slice(0);
parts.splice(i, 1)[0];
// [ 'うし', 'とら', 'うさぎ' ]

slice(0)は値コピーのためにつけています。 (参考:JavaScriptで配列のコピー(値渡し))
splice()は要素を一部削除する関数です。 (参考:MDN -Array.prototype.splice()-)


次に、自分自身の関数を実行します。

const row = getPermutation(parts, r - 1);

このように、自分で自分自身の関数を呼ぶことを再帰呼び出しといいます。
(参考:再帰関数を学ぶと、どんな世界が広がるか)

再起呼び出しをする際の引数は以下の通りです。

第一引数 : parts( ['うし', 'とら', 'うさぎ'])
第二引数 : 1

処理の流れは前章で説明した通りで、r = 1 だった場合の処理が実行されています。
戻り値は以下のようになっています。

// lowの中身
// [
//   ['うし'],
//   ['とら'],
//   ['うさぎ']
// ]

最後に、concat()[ねずみ]['うし'],['とら'],['うさぎ']をそれぞれ結合し、result配列に格納しています。

for (let j = 0; j < row.length; j++) {
      result.push([types[i]].concat(row[j]));
    }

ここでresult配列に格納された値は以下の通りです。

[
  [ 'ねずみ', 'うし' ],
  [ 'ねずみ', 'とら' ],
  [ 'ねずみ', 'うさぎ' ],
]

上記の動きを、ネズミ以外の動物にも順次繰り返すことで結果が得られます。

4匹から3匹選ぶ例

実行結果

const result = getPermutation(['ねずみ', 'うし', 'とら', 'うさぎ'], 3);
console.log(result);

// 実行結果
// [
//   ['ねずみ', 'うし', 'とら'],
//   ['ねずみ', 'うし', 'うさぎ'],
//   ['ねずみ', 'とら', 'うし'],
//   ['ねずみ', 'とら', 'うさぎ'],
//   ['ねずみ', 'うさぎ', 'うし'],
//   ['ねずみ', 'うさぎ', 'とら'],
//   ['うし', 'ねずみ', 'とら'],
//   ['うし', 'ねずみ', 'うさぎ'],
//   ['うし', 'とら', 'ねずみ'],
//   ['うし', 'とら', 'うさぎ'],
//   ['うし', 'うさぎ', 'ねずみ'],
//   ['うし', 'うさぎ', 'とら'],
//   ['とら', 'ねずみ', 'うし'],
//   ['とら', 'ねずみ', 'うさぎ'],
//   ['とら', 'うし', 'ねずみ'],
//   ['とら', 'うし', 'うさぎ'],
//   ['とら', 'うさぎ', 'ねずみ'],
//   ['とら', 'うさぎ', 'うし'],
//   ['うさぎ', 'ねずみ', 'うし'],
//   ['うさぎ', 'ねずみ', 'とら'],
//   ['うさぎ', 'うし', 'ねずみ'],
//   ['うさぎ', 'うし', 'とら'],
//   ['うさぎ', 'とら', 'ねずみ'],
//   ['うさぎ', 'とら', 'うし'],
// ];

解説

最初の処理は$_{4}P_{2}$の処理と同じです。
まず['ねずみ','うし','とら','うさぎ']を対象にするループを開始し、parts配列([ 'うし', 'とら', 'うさぎ' ])を作成します。

  for (let i = 0; i < n.length; i++) {
    const parts = types.slice(0);
    parts.splice(i, 1)[0];
    // [ 'うし', 'とら', 'うさぎ' ]


次に、自分自身の関数を実行します。

const row = getPermutation(parts, r - 1);

再起呼び出しをする際の引数は以下の通りです。

第一引数 : parts( ['うし','とら', 'うさぎ'])
第二引数 : 2

今回は第二引数が2になるので、 r が 2 以上の場合に該当し、さらに再帰処理を行います。

'うし''それ以外' などのペアになる順列を取得します。

  for (let i = 0; i < n.length; i++) {
    const parts = types.slice(0);
    parts.splice(i, 1)[0];
    // [ 'とら', 'うさぎ' ]

const row = getPermutation(['とら', 'うさぎ'], 1);

rowには以下の結果が格納されています。

//[
//   [ 'うし', 'とら' ],
//   [ 'うし', 'うさぎ' ],
//   [ 'とら', 'うし' ],
//   [ 'とら', 'うさぎ' ],
//   [ 'うさぎ', 'うし' ],
//   [ 'うさぎ', 'とら' ]
// ]

最後に、['ねずみ']と、row配列の要素['うし']['とら']などを結合し、result配列に格納しています。

  for (let i = 0; i < n.length; i++) {
    const parts = types.slice(0);
    parts.splice(i, 1)[0];
    // 実行結果
    // [
    //   ['ねずみ', 'うし', 'とら'],
    //   ['ねずみ', 'うし', 'うさぎ'],
    //   ['ねずみ', 'とら', 'うし'],

    // ...(略)

上記サイクルを終えて'ねずみ'から始まる全ての順列を洗い出したら、以降同じ手順で'うし''とら'から始まる順列を取得します。
このようにr === 1になるまで何度も繰り返し再帰呼び出しを行うことで、最終的に必要な配列を出力することができます。

image

まとめ

順列(nPr)は、「n個の物の中からr個を取り出して一列に並べたもの」です。
r回数分再帰処理を繰り返すことで答えを導くことができます。

再帰処理を使えば、倍々に選択肢が増えていくような処理(チェスのコマを置く場所や移動経路の探索など)が書けるようになると思います。
例題はたくさんあるので、他の処理もいつか記事にしてみたいと思います。

ちなみにとてもかわいい動物の素材はこちらにお借りしました。
イラストレイン

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

[Rails][Turbolinks] inline Javascript が実行されない 解決法

Javascript周り の取り扱いが難しい Turbolinks

起こったこと

ページ毎に任意のJavascript を実行したいが
普通に書いたら以下エラー。

ページ遷移でも同様。

ChromeDeveloperConsole
Refused to execute inline script because it violates the following Content Security Policy directive: 
"script-src 'self' https: 'unsafe-inline' 'nonce-UiVx2CiP0HHN9jOOSEG43g=='". 
Note that 'unsafe-inline' is ignored if either a hash or nonce value is present in the source list.

Content-Security-Policy (CSP) (Cross Site Scripting (XSS) や data injection 攻撃を防ぐための HTTP の仕様)のために
ブラウザでの Javascript 実行が制限されている。

https://qiita.com/tearoom6/items/30e3aacaa432860d4b36

解決法

Rails 側で nonce を発行し、Javascript を認証する。
(HTTPヘッダに追加)

views/layouts/head.html.slim
= csp_meta_tag
config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy_nonce_generator = -> (request) do
  # use the same csp nonce for turbolinks requests
  if request.env['HTTP_TURBOLINKS_REFERRER'].present?
    request.env['HTTP_X_TURBOLINKS_NONCE']
  else
    SecureRandom.base64(16)
  end

script タグに nonce をつける

some_page.html.slim
= javascript_tag, nonce: true
 | console.log('JS here');

Turbolinks のEvent に合わせて、nonce を入れ替える。

application.js
document.addEventListener('turbolinks:request-start', function(event) {
  var nonceTag = document.querySelector("meta[name='csp-nonce']");
  if (nonceTag) event.data.xhr.setRequestHeader('X-Turbolinks-Nonce', nonceTag.content);
});
document.addEventListener('turbolinks:before-cache', function() {
  Array.prototype.forEach.call(document.querySelectorAll('script[nonce]'), function(element) {
    if (element.nonce) element.setAttribute('nonce', element.nonce);
  });
});

Turbolinks Event list

https://github.com/turbolinks/turbolinks#full-list-of-events

参考

https://github.com/turbolinks/turbolinks/issues/430

https://discuss.rubyonrails.org/t/turbolinks-broken-by-default-with-a-secure-csp/74790

https://github.com/thredded/thredded/pull/797/commits/beac77f9c7ac0c880afac3302f752157f2945afe

https://stackoverflow.com/questions/57044770/what-is-csp-meta-tag-in-rails-5-2-3-and-how-does-it-work

https://y-yagi.hatenablog.com/entry/2018/02/24/070139

https://site-builder.wiki/posts/1050

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

JavaScript で wait

何をしたいの

JavaScript で wait をしたい。段階的に何かを表示するとかの演出に使うかもしれない。
setTimeout の入れ子とか、再帰的に書くのとかを回避するとか、Exponential Backoff したかったりする場合とかにも使える。

どう書くの

const wait = (msec)=>{
  return new Promise((resolve)=>{
    setTimeout(()=>{resolve(msec)}, msec);
  });
};

どう使うの

素直に Promise を利用して次のように記述できる。

const waitBehavior1 = () => {
  const before = Number(new Date());
  wait(1250).then((waitLength) => {
    const after = Number(new Date());
    console.log(after - before);
  });
};

ループで回したりするのには不便。

const LOOP_REQUEST_COUNT = 3;
const waitBehavior2 = (loopCount) => {
  if(loopCount) {
    wait(1250).then((waitLength) => {
      console.log(`${LOOP_REQUEST_COUNT  - loopCount + 1}週目`);
      waitBehavior2(loopCount - 1);
    });
  } else {
    console.log('おしまい');
  }
};
waitBehavior2(LOOP_REQUEST_COUNT);

入れ子にしないで使う

本題。async と await に頼るといい感じに書ける。

const waitBehavior3 = async () => { // async を入れないとだめ
  const before = Number(new Date());
  await wait(1250); // ここも await を入れないとだめ
  const after = Number(new Date());
  console.log(after - before);
};

waitBehavior3();

これならループも簡単

const waitBehavior5 = async () => { // async を入れないとだめ
  const before = Number(new Date());
  for(let i = 0; i < 3; i++) {
    await wait(1250); // ここも await を入れないとだめ
  }
  const after = Number(new Date());
  console.log(after - before);
};

waitBehavior5();

ただ、以下のような使い方はできないので注意。

// ほぼ同時に ぱんだ、うさぎ、こあらが console.log される
waitBehavior6 = () => {
  ['ぱんだ', 'うさぎ', 'こあら'].forEach(async (animal)=>{
    await wait(1250);
    console.log(`${animal} ${Number(new Date())}`);
  });
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascriptを使って簡単な計算機を作るpart6 入門者向け

計算機を作る

今回作る機能

ボタン切り替え機能

1.演算子入力をボタン切り替え機能を使って、計算する。
2.演算子入力した後で、別の演算子を入力するとその演算子を使った計算に切り替わるようにする。
3.CLEARボタン押すことで、リセットできる。

今回は、押した演算子ボタンの計算行いたかったので計算の関数を多少改変した。

今回の記事では、どのボタンを押したか分かりやすく、ボタンの背景色をピンクにしている。

苦戦したのは、CLEARボタンを押すことで演算子ボタンを初期化する機能。

getElementsByClassNameで指定したクラスを全部消す

上記のサイトで解決することができた。

該当するコード

caluculate.js
//ボタン切り替え機能の関数
var btns = document.getElementsByClassName('symbol-btn');
for (var i = btns.length - 1; i >= 0; i--) {
  btnAction(btns[i],i);
}

function btnAction(btnDOM, btnId) {
  btnDOM.addEventListener('click', function() {
    this.classList.add('active');

    for(var i = btns.length - 1; i >= 0; i--) {
      if(btnId !== i) {
        if(btns[i].classList.contains('active')) {
          btns[i].classList.remove('active');
        }
      }
    }
  });
}

//計算の関数
var calc = data => {
  if (flag === 0 && data !== "=") {
    flag = 1;

    var formula = total + operator + currentValue;
    total = eval(formula);

    operator = data;
    currentValue = '';
    show.textContent = total;
    console.log(operator); //確認用
  } else if (flag === 1 && data === "=") {
    var formula = total + operator + total;
    total = limitNum(eval(formula));

    currentValue = "";
    show.textContent = total;
  } else if (data === "=") {
    flag = 1;

    var formula = total + operator + currentValue;
    total = limitNum(eval(formula));

    currentValue = "";
    show.textContent = total;
  } else {
    operator = data; //演算子入力を変更できる
    console.log(operator);
  }
};
caluculate.js
//CLEARボタンを押すことでクラスを取り除く関数
function resetBackColor() {
  [].forEach.call(btns, function(e) {
    e.classList.remove('active');
  });
}

var clear = document.getElementById('clear-btn')
clear.addEventListener('click', () => {
  resetBackColor();
});

完成物

See the Pen QWyRvjr by ライム (@raimumk2) on CodePen.

サンプルコード

HTML
caluculate.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="css/caluculate.css">
  <title>計算機</title>
</head>
<body>
  <div class="caluculate">
    <div class="wrapper">
      <div id="number-text">0</div>
    </div>
    <div class="other-btns">
      <button id="clear-btn">CLEAR</button>
      <button id="record-btn" onclick="record()">RECORD</button>
    </div>
    <div id="btns">
      <div id="num-btns">
        <button onclick="number(7)">7</button>
        <button onclick="number(8)">8</button>
        <button onclick="number(9)">9</button>
        <button onclick="number(4)">4</button>
        <button onclick="number(5)">5</button>
        <button onclick="number(6)">6</button>
        <button onclick="number(1)">1</button>
        <button onclick="number(2)">2</button>
        <button onclick="number(3)">3</button>
        <button id="zero" onclick="zero(0)">0</button>
        <button name="point" onclick="point('.')">.</button>
        <button id="equal" onclick="calc('=')">=</button>
      </div>

      <div id="symbol-btns">
        <button class="symbol-btn" onclick="calc('/')">/</button>
        <button class="symbol-btn" onclick="calc('*')">*</button>
        <button class="symbol-btn" onclick="calc('-')">-</button>
        <button class="symbol-btn" onclick="calc('+')">+</button>
      </div>
    </div>
  </div>

  <script src="js/caluculate.js"></script>
</body>
</html>

CSS
caluculate.css
* {
  margin: 0;
  padding: 0;
}

.caluculate {
  margin: 100px auto;
}

.wrapper {
  width: 300px;
  margin: 0 auto;
}

.wrapper > #number-text {
  width: 285px;
  height: 54px;
  line-height: 54px;
  margin-left: 5px;
  margin-bottom: 5px;
  font-size: 48px;
  border: 1px solid black;
  /* 右から左へ入力するためのスタイル */
  text-align: right;
}

.other-btns {
  margin: 0 auto;
  width: 300px;
}

#clear-btn {
  width: 135px;
  margin-left: 5px;
  font-size: 24px;
  /* 上下のズレを直すためのスタイル */
  vertical-align: middle;
}

#record-btn {
  width: 135px;
  font-size: 24px;
  margin-left: 13px;
  /* 上下のズレを直すためのスタイル */
  vertical-align: middle;
}

#btns {
  width: 300px;
  display: flex;
  margin: auto;
}

button {
  width: 65px;
  height: 57px;
}

#num-btns {
  margin: 5px;
}

#num-btns > button {
  margin-bottom: 5px;
  font-size: 24px;
}

#symbol-btns {
  height: 228px;
  display: flex;
  flex-direction: column;
  display: inline-block;
  margin-top: 5px;
}

#symbol-btns > .symbol-btn {
  margin-bottom: 5px;
  height: 57px;
  font-size: 24px;
  text-align: center;
}

.active {
  background-color: pink;
}

Javascript
caluculate.js
var show = document.getElementById('number-text');
var total = ''; //合計と演算子
var operator = '+'; //演算子
var currentValue = ''; //現在の値
var flag = 0; //ボタンが押されたあとに効かなくする※flag(フラグ)を立てる。最初は0を代入。

//数字入力の関数
var number = data => { //押されたボタンの値 data()をアロー関数にしている
  if (currentValue.length <= 8) {
    flag = 0;
    currentValue += data;
    show.textContent = currentValue;
    console.log(show.textContent); //確認用
  }
};

//0についての関数
var zero = data => {
  if(currentValue === '0') {
    return; //最初の文字が0のとき0を押せなくする
  } else if (currentValue.length <= 8){
    flag = 0;
    currentValue += data;
    show.textContent = currentValue;
    console.log(show.textContent);
  }
}

//小数点(.)の関数
var point = data => {
  if (currentValue === '') {
    return; //最初に小数点を押せなくする
  } else if (!currentValue.includes('.')) {
    currentValue += data;
    show.textContent = currentValue;
  }
}

//計算の関数
var calc = data => {
  if (flag === 0 && data !== "=") {
    flag = 1;

    var formula = total + operator + currentValue;
    total = eval(formula);

    operator = data;
    currentValue = '';
    show.textContent = total;
    console.log(operator); //確認用
  } else if (flag === 1 && data === "=") {
    var formula = total + operator + total;
    total = limitNum(eval(formula));

    currentValue = "";
    show.textContent = total;
  } else if (data === "=") {
    flag = 1;

    var formula = total + operator + currentValue;
    total = limitNum(eval(formula));

    currentValue = "";
    show.textContent = total;
  } else {
    operator = data; //演算子入力を変更できる
    console.log(operator);
  }
};

//ボタン切り替え機能の関数

var btns = document.getElementsByClassName('symbol-btn');
for (var i = btns.length - 1; i >= 0; i--) {
  btnAction(btns[i],i);
}

function btnAction(btnDOM, btnId) {
  btnDOM.addEventListener('click', function() {
    this.classList.add('active');

    for(var i = btns.length - 1; i >= 0; i--) {
      if(btnId !== i) {
        if(btns[i].classList.contains('active')) {
          btns[i].classList.remove('active');
        }
      }
    }
  });
}

//小数点以下の桁数を揃える関数
function limitNum(num) {
  return Math.round(num*10000000)/10000000;
}

//CLEARボタンの関数
function reset() {
  operator = '+';
  total = '';
  currentValue = '';
  flag = 0;
  show.textContent = '0';
};

function resetBackColor() {
  [].forEach.call(btns, function(e) {
    e.classList.remove('active');
  });
}

var clear = document.getElementById('clear-btn')
clear.addEventListener('click', () => {
  reset();
  resetBackColor();
});

参考サイト

js関連

jQuery日本語リファレンス

qiita:jQuery classの追加・削除

qiita:すっきり書きたい JavaScriptの条件分岐

qiita:JS イベントまとめ

【jQuery入門】removeClass()でクラスを削除する方法まとめ!

getElementsByClassNameで指定したクラスを全部消す

エラー関連

Uncaught Type Error: Cannot read property 'columns' of undefinedについて

今後の構想

・計算結果をリストに追加していく機能
(複数の計算があったとき用のメモ代わり)
RECORDボタン押してリストに追加する。

・計算結果リストの編集ができる機能
(作れそうなら作ってみる)

リストに追加する機能自体は、自分の記事からコピペして、今のコードに合うように書き換える。

リスト自体の配置やスタイルをどう当てるかが課題。

計算結果リストの編集機能は、主に削除さえできればと思っている。

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

fadeIn()を使うには先に非表示を

jQuery fadeIn()を使えない?

動的に追加した要素をフェードインさせたい。
fadeIn()は要素が非表示になっている状態でないと使えない。
これを忘れててフェードインできないできない、と悩みました。

解決策は2パターン。

  • パターン1
    CSSで対象要素を非表示display:none;にしておく。

  • パターン2
    hide()で一度非表示にする。

今回は動的に要素を追加するためパターン2を採用。

$('body').append(`<div><img src="${src}"></div>`);
$('div').hide().fadeIn();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像が読み込まれたか監視するjavascriptをnpmに公開しました

 やりたかったこと

「シンプルjavascriptで画像の読み込みを監視し、
読み込み終わったらイベントを発火する」
という画像読み込みの監視クラス

使い方

npm
https://www.npmjs.com/package/load-image.js

$ npm install load-image.js
import loadImage from 'load-image.js';

const PATH_IMG = "/assets/img/";
let callbackEvent = () => {
    // fadein event
}
new loadImage({
    targetImageUrl : [
        PATH_IMG + `mv/background.jpg`,
        PATH_IMG + `mv/title.png`,
        PATH_IMG + `mv/image.png`,
    ],
    callback: callbackEvent
});

中身

var img = new Image();
で画像要素を作り、そこにロードイベントを追加して、
カウンターを回すだけのシンプルな作り。
念の為エラー吐いたときもカウンター回して、全部ロード完了orエラー吐いた状態で
コールバックが発生するようにした。

loadimage(target_img) {
  var img = new Image();
  img.addEventListener("load", () => {
    this.counter();
  }, false);
  img.addEventListener("error", () => {
    this.counter();
  }, false);
  img.src = target_img;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Nuxt.js】Nuxt文法編:asyncData

? この記事はWP専用です
https://wp.me/pc9NHC-ut

前置き

非同期通信で使うasyncData
まとめました??‍♀️

・非同期通信とは
・Promiseとは
・asyncDataの役割と書き方(Promiseとasync/await)

分かるところは飛ばしてくださいね ✈️

非同期処理とは

こちらのツイートが分かりやすいかと思います?

https://twitter.com/ichikun0000/status/1283909871310499840?s=20

結果を待たずに
すぐに処理ができるということです?‍♀️?

温めた商品が手元にくる前に
会計を先に済ませることができる
というわけです!

これが同期処理なら
温めてる間はずっと店員さんが
レンジの前にいて、
温め終わってからでしか会計できません。

コードでいうなら…
⬇️の例でご覧ください??

Promise

Promiseとは

? 続きはWPでご覧ください?
https://wp.me/pc9NHC-ut

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

Pdf.jsで"The cMap url must be specified"のエラーが出たときの解決法

散々探しても中々情報が出てきませんでしたが、
以下のサイトに解決法がありました。
PDF.jsで日本語を表示させるのに苦労した話

普通にエラーメッセージでググると
pdfjsLib.cMapUrl = "~/cmaps/";
pdfjsLib.cMapPacked = true;

みたいなワンライナーが出てくるかと思いますが、
私の場合はこれでは解決しませんでした。

解決法は以下

main.js
pdfjsLib.getDocument({  
  url: '<PDFへのurlやpath>',  
  // モジュール内にあるcmapsディレクトリーへのpathを指定してください  
  // 末尾のスラッシュ'/'まで必須です  
  cMapUrl: './node_modules/pdfjs-dist/cmaps/',  
  cMapPacked: true,  
})  

*htmlのscriptダグからpdf.jsを読み込んで使用する場合、変数名「PDJFS」は既にDeprecatedで動きません。
代わりに「pdfjsLib」を使いましょう。

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

DataTablesの特定行全体をリンクにする

概要

DataTableに関してはこちらをご覧ください
DataTableは簡単にリッチなテーブルを作れるとあって使っている人も多いのではと思います
今回、こちらのテーブルの行をクリックした時に、そのデータに対応した詳細画面に飛ばしたいという要望が来たので、その時の解決策をメモしておきます

解決策

DataTableライブラリ上で行をカスタマイズするには

'createdRow': function( row, data, dataIndex ) {
  // 任意のコードを記述
 // $(row)にて行を構成するHTML要素にアクセス可能
},

でできるみたいです
createdRow以外にもcreatedCellもあるみたいですね

行全体をクリック可能にするためにはtr要素にclick時の挙動を記述して、カーソルが行の上に来た時に変化できるようにCSSを追加しておけば良さそうです

$('#table1').DataTable({

  ...

  // このajaxの返り値でhoge_idをもらっておく
  // 以下のライブラリを使うとcontrollerのコーディングが楽になる
  // https://github.com/yajra/laravel-datatables
  ajax: {
    type: 'GET',
    url: '/path/to/api',
    data: {
      ...
    }
  },

  // 行を選択した時にurfFormに飛ぶ
  'createdRow': function( row, data, dataIndex ) {
    // data-hrefにURLを追加してから、クリック時にそちらへ遷移させる
    var urlForm = "/path/to/link/" + data.hoge_id;
    $(row).attr('data-href', urlForm)
      // カーソールオーバー時の見た目を変化
      .css("cursor", "pointer")
      // クリックしたら遷移
      .click(function(e) {
        window.location = $(e.target).closest('tr').data('href');
    });
  },

  ...

});

reference

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

CORS

書籍のアウトプットとして

CORS

異なるオリジンとの通信を可能にする。

Access-Control-Allow-Origin

クロスオリジンからの読み出しを居するための仕掛け。
アクセスされる側がHTTPレスポンスヘッダとして出力する。

シンプルなリクエスト

特定の条件を見たすシンプルなリクエストの場合、XMLHttpRequestで異なるオリジンにHTTPリクエストを送ることが許可になしに可能。

http://example.jsからhttp://example.comへのXMLHttptRequestを許可する場合は
http://example.comが以下のようにHTTPレスポンスヘッダを送信する。

Access-Control-Allow-Origin:http://example.js

プリフライトリクエスト

シンプルナリクエストでないクロスサイトオリジンアクセスでは、ブラウザはプリフライトリクエストというHTTPリクエストを送信する。

このリクエストを受けたサーバは以下のヘッダに応答する必要がある。

揚州の種類 リクエスト レスポンス
メソッドに対する許可 Access-Control-Request-Method Access-Control-Allow-Method
ヘッダに対する許可 Access-Control-Request-Headers Access-Control-Allow-Headers
オリジンに対する許可 Orijin Access-Control-Allow-Orijin

プリフライトリクエストの受け取り

if ($_SERVER['REQUEST_METHOD'] === "OPTIONS") {
  //プリフライトリクエスト受取時の処理
}elseif ($_SERVER['REQUEST_METHOD'] === "POST") {
  //ポスト処理
}

プリフラを受け取って、ヘッダに返答したら次は正常のPOSTが送信されるから条件付を記載する。
その際にheader('Access-Control-Allow-Origin: URL');
を忘れないように。

認証情報を含むリクエスト

デフォルトではクロスサイトオリジンに対するリクエストにはHTTP認証やクッキーなどの認証に用いられるリクエストヘッダは自動的に動診されない。

XMLHttpRequestのプロパティにwithCredentialsをセットする必要がある。

withCredentialsをセットする

req.open('GET', URL);
req.withCredentials = true;

withCredentialsをtrueにするとAccess-Control-Allow-Credentials: true'というレスポンスヘッダを返す必要がある。

まとめ

クッキー等認証用ヘッダを用いるクロスオリジンリクエストは書きを満たす必要がある。

  • withCredentialsをtrueにする
  • レスポンスヘッダとしてAccess-Control-Allow-Credentials: trueを返す
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kindle Library の蔵書をAlgoliaで検索する

これは何

Kindleの蔵書一覧を全文検索サービスのAlgoliaで検索可能にするスクリプトです。
こちらの記事を参考に、蔵書一覧をCSV出力する代わりに Algolia でインデックスし、検索可能にしてみます。

InstantSearch.jsでフロントエンドを作ると簡単なWebアプリケーションにもなります(サンプル)。

anime.gif

前提条件

あらかじめAlgoliaアカウントを作成し、API Keyを取得する必要があります。
アカウントの取得についてはこちらを参考に。
今回必要なのはインデックスを作成する権限及びレコードを追加する権限となるのため、editSettings と addObject 権限に絞った API キーをあらかじめ発行しておくと良いです。

Algolia Dashboard からの操作だと以下のように新規 API キー作成時に ACL を指定することができます。ACL など API キーに関する詳細はこちらの記事にまとまっています。

api_key.png

なお、Algoliaのフリープランでは10000レコード・10000検索リクエストまで無料なので、個人使用の範囲であればほぼ問題ないかと思います(ライブラリが10000冊を超える方ってどのくらいいるんだろう・・・)。

やり方

  • Kindle Cloud Readerにアクセス。本が大量にあると読み込みが終わるまで時間がかかるためしばらく待つ

  • F12を押して、デベロッパーツールを立ち上げる

  • Consoleタブに移動して、以下のコードの各変数を環境に合わせて変更し実行する。appIdとapiKeyはAlgoliaアカウント情報から取得したものを、indexNameは任意のものを指定する

let appId = 'YOUR_ALGOLIA_APP_ID'
let apiKey = 'YOUR_ALGOLIA_API_KEY_WITH_WRITE_PERMISSION'
let indexName = 'kindleLibrary'
  • 以下のコードを実行しインデックスを作成する
createAlgoliaIndex = function (appId, apiKey, indexName) {
    const url = `https://${appId}-dsn.algolia.net/1/indexes/${indexName}/settings`
    const headers = {
        'X-Algolia-API-Key': apiKey,
        'X-Algolia-Application-Id': appId
    }

    let req = new Request(url, {
        method: 'PUT',
        body: JSON.stringify({
            searchableAttributes: ["title", "authors", "purchaseDate"],
            customRanking: ["desc(purchaseDate)"],
            attributesForFaceting: ["searchable(authors)"]
        }),
        headers: headers
    })

    fetch(req)
        .then(response => {
            if (response.status === 200) {
                console.log(response.json());
            } else {
                throw new Error('Something went wrong on api server!' + response.json());
            }
        })
        .then(response => {
            console.debug(response);
        }).catch(error => {
            console.error(error);
        });
}

createAlgoliaIndex(appId, apiKey, indexName)
  • 以下のコードを実行しkindle libraryのデータをAlogliaに登録する
sendKindleToAlgolia = function (appId, apiKey, indexName) {
    let db = openDatabase('K4W', '3', 'thedatabase', 1024 * 1024);

    const url = `https://${appId}-dsn.algolia.net/1/indexes/${indexName}/batch`
    const headers = {
        'X-Algolia-API-Key': apiKey,
        'X-Algolia-Application-Id': appId
    }

    db.transaction(function (tx) {
        tx.executeSql('SELECT * FROM bookdata order by title;', [], function (tx, results) {
            const len = results.rows.length;
            let batch = []

            for (i = 0; i < len; i++) {
                let result = results.rows.item(i);
                let asin = result.asin;
                let title = result.title;
                let authors = JSON.parse(result.authors);
                let purchaseDate = new Date(result.purchaseDate).toLocaleDateString();
                let productUrl = `https://www.amazon.co.jp/dp/${asin}`
                let productImageUrl = `https://images-na.ssl-images-amazon.com/images/P/${asin}.09.MZZZZZZZ.jpg`

                // Remove double quotes and CRLF from title
                title = title.replace(/"/g, '');
                title = title.replace(/\n|\r\n|\r/g, '');

                // Add record to batch
                let record = {
                    asin: asin,
                    title: title,
                    authors: authors,
                    purchaseDate: purchaseDate,
                    productUrl: productUrl,
                    productImageUrl: productImageUrl
                }
                batch.push({
                    action: 'addObject',
                    body: record
                })
            }
            let req = new Request(url, {
                method: 'POST',
                body: JSON.stringify({
                    requests: batch
                }),
                headers: headers
            })

            fetch(req)
                .then(response => {
                    if (response.status === 200) {
                        console.log(response.json());
                    } else {
                        throw new Error('Something went wrong on api server!' + response.json());
                    }
                })
                .then(response => {
                    console.debug(response);
                }).catch(error => {
                    console.error(error);
                });

        });
    });
};

sendKindleToAlgolia(appId, apiKey, indexName);
  • Algolia dashboard 画面に移動しデータがインデックスされていることを確認する。メニューから 'indices' をクリックし、indexName で指定したインデックスを選択。検索欄にクエリを入力すると検索結果が表示される

dashboard.png

その他

Aloglia Dashboardにログインするのがめんどくさい場合はWeb UIライブラリのInstantSearch.jsで比較的簡単にWebアプリにしてしまうこともできます -> 試しに作ってみました。ソースをコピペして AppId, APIキー、インデックス名を書き換えれば動くはずです。

参考:
Getting Started | Building Search UI | Guide | Algolia Documentation
Vue InstantSearchを使って検索機能を実現する | shinodogg.com
Vue InstantSearch (Algolia) を使った検索機能の実装 | by Kazuki Yonemoto | Medium

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

[JavaScript] Optional?.Chaining と Nullish ?? Coalescing

概要

  • JavaScript の新しい文法 ?.?? について調べた結果の忘備録です。
  • 随時追加されます。

Optional?.Chaining

  • ?. により undefined と判定された後は、後続の .?. にする必要がない。
後続の?.は不要
const obj = {
    aaa: {}
}

obj.aaa.bbb             // undefined
obj.aaa.bbb?.ccc.ddd    // undefined (no error)

obj.aaa             // {}
obj.aaa?.bbb        // undefined
obj.aaa?.bbb.ccc    // Uncaught TypeError: 
                    // Cannot read property 'ccc' of undefined
  1. obj.aaa?.bbb.ccc で、 aaa?. は undefined について未判定。
  2. .bbb の時点で ?. 未判定のため、後続の .cccundefined 判定される。
  3. .ccc は undefined なのでエラーになる。

Nullish ?? Coalescing

  • ?? の第二項の式は、第一項が undefinednull ではない場合には評価されない。
第二項は実行されない。
function func () {
  console.log('Done second operand.')
  return 2
}

1 ?? func()            // 1 (no message)
false ?? func()        // false (no message)
'' ?? func()           // '' (no message)
NaN ?? func()          // NaN (no message)

undefined ?? func()    // 2 (message: Done second operand.)
null ?? func()         // 2 (message: Done second operand.)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript] Optional?.Chaining と Nullish ?? Coalescing の忘備録

概要

  • JavaScript の新しい文法 ?.?? について調べた結果の忘備録です。
  • 随時追加されます。

Optional?.Chaining

  • ?. により undefined と判定された後は、後続の .?. にする必要がない。
後続の?.は不要
const obj = {
    aaa: {}
}

obj.aaa.bbb             // undefined
obj.aaa.bbb?.ccc.ddd    // undefined (no error)
  1. obj.aaa.bbb?.ccc.ddd で、bbb?. は undefined と判定される。
  2. .ccc 以降は実行されず、直ちに undefined が返る。
  3. .ddd は実行されないので、エラーにならない。
obj.aaa             // {}
obj.aaa?.bbb        // undefined
obj.aaa?.bbb.ccc    // Uncaught TypeError: 
                    // Cannot read property 'ccc' of undefined
  1. obj.aaa?.bbb.ccc で、 aaa?. は undefined ではないため、?.bbb が実行される。
  2. .bbb の後ろに ?. がないので、後続の .ccc が実行される。
  3. .bbb が undefined なので .ccc は エラーになる。

Nullish ?? Coalescing

  • ?? の第二項の式は、第一項が undefinednull ではない場合には評価されない。
第二項は実行されない。
function func () {
  console.log('Done second operand.')
  return 2
}

1 ?? func()            // 1 (no message)
false ?? func()        // false (no message)
'' ?? func()           // '' (no message)
NaN ?? func()          // NaN (no message)

undefined ?? func()    // 2 (message: Done second operand.)
null ?? func()         // 2 (message: Done second operand.)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascriptではif(undefined&&undefined.undefined){}が許される

JavaScriptで効率的にifを書きたい

JavaScriptではメソッド含め、全てがオブジェクトなのでa.b.cみたいなのを頻繁に見かけます。
しかし、他の言語ではa.bが存在しないのにa.b.cを参照すれば例外が吐かれるか、Segmentation Faultになりますよね。JavaScriptはとりあえず何があっても動くことを優先に作られている(私見)な言語なので、この辺が割と柔軟です。
論理積演算子で左側だけで成立していれば右側は問われません。どういうことかというと、

if(undefined&&undefined.undefined) {
} //ok
console.log(!!(undefined&&undefined.undefined)) //false

&&演算子で繋げている場合、左側がfalseなので右側が例外を吐かれるような状態であっても問題なくfalseを返します。

ただし、左側がtrueな場合や論理和の時は例外を吐きます。

if(true&&undefined.undefined) {
} //TypeError
console.log(!!(undefined||undefined.undefined)) //TypeError
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「親子」のお小遣いで考えるVueのEmitの理解

なぜVueのEmit処理はややこしいのか。自分で理解したときのコツを残しておく。
* TypeScriptで記述しています。

VueのEmitで必要になる処理

例えば、何かクリックのようなイベントがあったときを考えよう。

単に親から子供のデータにアクセスしたいだけのに、親側では以下のようなコードが必要になる。

  • データにアクセスするための関数を用意する。
    • 例:changeParentValueToのような関数。
  • その関数を@click=changeParentValueToでトリガーさせる。

一方で、子側では以下のようなコードが必要になる。

  • @click=onClickのようにクリック時に発火する関数を定義する
    • これはまだ自然なのでわかりやすい。
  • @Emitでデコレートされたpublic click関数をオーバーライドする。
  • onClickの中で、click関数に、渡したい値を引数として入れる。

理解のコツ

上で見たとおり、やりたいことは単純なのにも関わらず実装はわりと面倒になる。素直に、<子コンポーネント>.<変数>のような形でアクセスできたらもっと簡単なのに、と思ったことはないだろうか。

しかし<子コンポーネント>.<変数>という形で子供の変数にアクセスすることは、あまり良い方法とはいえない。

これを理解するためには、「親側はなるべく子供の変数をダイレクトに反映させたくない」という原則を腹落ちしておく必要がある。

以下ではこの原則を理解するポイントと、この原則に沿ったコードの読み方について述べておく。

親側はなるべく子供の変数をダイレクトに反映させたくないという原則

ここでは暫定的に「親」「子」という意味を本来の意味(人間の「親子」というように使うときの意味)としてあえて曖昧にとらえ、親が子供に、作業量に応じたお小遣いをあげるという状況を考えよう。

この状況において、例えば以下のようにコードを書いたとしよう。

vue.js
paymentToChild = Child.workLoad * 100 //子供の作業量から計算。比例係数100
sendMoney(paymentChild) //自分の口座から送金実行

このコードは、シンプルで読みやすいがちょっと恐ろしいコードでもある。2つの理由がある。

  1. 変な値が入ってきたときの挙動そのまま入ってしまう
    例えば、子どものworkLoadがなぜかnullになってしまうとこのロジックは破綻してしまう。

  2. 口座の変化に気が付きづらい
    子供のワークロードに変化があったとしても、親側ではその変化が一切記述されていないので、このコードのデバッグは難しいものになるだろう。

ということで、自然とこれらのデメリットをカバーするようなスクリプトを書こうということになる。それぞれのデメリットに対応するようにコードを書くと、

  1. 値をラップする関数を書く
    直接的に代入するのではなく、変な値が入ってきたときに備えてのtry-catchとかを書いておく。

  2. 変化を親がコントロールできるようにする
    値の変化を子のコンポーネントのイベントで管理するのではなく、親のコンポーネントで管理できるようにする。

というようになる。

...と、ここまで書けば気がついた方もいらっしゃると思うが、実はこれらをわざわざ自分で書かなくても良いというのがVueにおける(親側の)Emit処理なのだ。

具体的にみていこう。

子供の「作業」が押される回数countに相当し、親が子供に払う金額がresultに相当するとして考えてもらいたい。

コメントはコードの中に記してゆく。

親コンポーネント

<template>
    <div>
        <!--クリックしたら「値を返す関数」を呼ぶ-->
        <MyButton @click="changeParentValueTo"></MyButton>
        <br>
        子コンポーネントでの計算結果×100 : {{result}}</div>
</template>

<script lang="ts">
    import {Component, Vue} from 'vue-property-decorator';
    import MyButton from "@/components/MyButton.vue";

    @Component({
        components: {
            MyButton,
        },
    })
    export default class Home extends Vue {
        private result = 0; // 画面に表示したい、最終計算結果

        // ほしい値を返す関数
        // なるべく、簡単にアクセスできないようにしてある。
        // (result = MyBotton.count*100とは記述できない。理由は下記のとおり。)
        public changeParentValueTo(emittedValue: number) {
            // このように子の変数を直接覗けないようにすることで、2つのメリットが生じる。
            // 1. 自然に(このような)関数を一度挟むようになるので制御しやすい(try-catchで変な値を抑えるとか)
            // 2. 1.とほぼ同義だが、値の変化を検知しやすい。(もしこの関数がないと、子のコードを覗かないと変化のトリガーが調べられない)
            this.result = emittedValue; // 子コンポーネントとつながる変数emittedValueをcountに格納し、値を変える
        }
    }
</script>

子コンポーネント(MyButton.vue)

<template>
    <button @click="onClick">MyButton</button>
</template>

<script lang="ts">
    import {Component, Emit, Prop, Vue} from "vue-property-decorator";

    @Component
    export default class MyButton extends Vue {
        private count = 0;

        @Emit() // こうすることで、click関数を呼べば親に通知されるようになる。
        public click(valueToEmit: number) 
            // 引数を定義すると、そこに入れられた値は通知の際に親へゆく
            console.log(`emitted ${valueToEmit} from child`)
        }

        public onClick() {
            this.count++;
            // @EmitのついたClickの引数に親へ渡す変数を入れると親へデータが行く。
            this.click(this.count); 
        }
    }
</script>

ということで、VueのEmit処理はややこしいが、「なるべく子供の変数をダイレクトに反映させたくない」という原則を理解していれば、ある程度素直に認めることができるだろう。

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

Chrome ExtensionのbackgroundからWebAssemblyを呼び出す

ソース付き最小構成の記事が見当たらなかったので追加。

chrome拡張の background 内から WebAssembly を実行するサンプルを纏めました。

githubはコチラ

フォルダ構成
execwasm
 └ src
   └ crate    // rust
     └ src
       └ lib.rs
       └ utils.rs
     └ Cargo.toml
   └ img
     └ kani_128.png
   └ js
     └ background.html
   └ manifest.json
 └ package.json
 └ webpack.config.js

使い方

ビルド

 $ npm install
 $ npm run build

ビルドすると、 dist フォルダが生成され、フォルダ内に chrome拡張機能インストール時に必要な manifest.json が格納されています。

Chrome Extension に登録

Chromeのメニューから [その他のツール] > [拡張機能] を選択し、Chromeをデベロッパーモードに切り替えてから、 パッケージ化されていない拡張機能を読み込む ボタンを押してフォルダ dist を選択すれば、インストール完了です。
スクリーンショット 2020-07-28 1.55.46.png

実行

インスールした直後に、自動的に background.js が実行され、そこから rust lib.rs の関数 greet() が呼び出されます。

src/crate/src/lib.rs
#[wasm_bindgen]
pub fn greet() -> String {
    alert("Hello, from Rust!");
    "from_Rust!".to_string()
}

まず、インストール直後に、alert() が実行され、ポップアップが表示されたと思います。
スクリーンショット 2020-07-28 2.02.58.png

次に、「background.html」をクリックし、 console でログを見てみましょう。
スクリーンショット 2020-07-28 2.04.47.png

表示されたログから、 background.js で、 Rust-api greet() を呼出し、戻値を受け取り表示している事が確認出来ます。

rust ソースコード lib.rs の関数 greet() の最終行 "from_Rust!".to_string() が関数の戻り値( String 型)です。

ポイント

webpack で @wasm-tool/wasm-pack-plugin を使用している為、 npm run build コマンド一発で rust のビルドも自動的に行われます。
manifest には、 "content_security_policy" の設定が必要です。これを定義する事で、background で実行できる様になります。

その他

参考サイト
https://stackoverflow.com/questions/48523118/wasm-module-compile-error-in-chrome-extension

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

react-scrollでトップページに戻るアニメーションを実装してみた

はじめに

今回、よくサイトでページ下部に固定表示されている「ページトップへ戻る」という機能をreactで実装する機会があったのでアウトプットとしてこの記事を残します。

アニメーションの調査

ページトップに戻るアニメーションを実装しようとした際に最初に思い浮かんだのは、jQueryでの実装でした。
しかし、そもそもjQueryはライブラリのサイズが大きくて古いということだったので他のreactに対応しているアニメーションパッケージを調査することにしました。
そこで見つけたのがreact-scrollというパッケージ。
react-scrollは様々なfunctionがパッケージに含まれていて、scrollToTopやscrollToBottomなどの関数で簡単にページ上下へのスクロールが実装できます。

react-scroll
https://www.npmjs.com/package/react-scroll

完成形

アニメーションの挙動はこんな感じです。

画面収録 2020-07-28 0.53.43.mov.gif

実装方法

①react-scrollをインストール

②react-scrollからanimateScroll as scrollをimportする

③functionとしてscrollToTopを定義する

④ボタンをクリックした時にイベントが発火するように
onClick={scrollToTop} を記述。

実際に書いたコードがこちら↓

/**
 * そのページの先頭に戻る
 */

export const BackTop: React.FC<Props> = ({ onClick }: Props) => {
  return (
    <div className="bg-gray-100">
      <div className="container mx-auto py-4 max-w-screen-md">
        <div className="px-20">
          <TextButton
            text={'⬆ このページの先頭へ'}
            onClick={onClick}
            buttonClassName="w-full"
            textClassName=" text-gray-400 font-bold"
          />
        </div>
      </div>
    </div>
  );
};

これはまだコンポーネントの状態でonClick={scrollToTop}でボタンをクリックしたした際のアニメーションが実装されていません。実際に今回作成したBackTopコンポーネントを使うページでfunctionとしてscrollToTopを定義するとアニメーションが実装できます。

※また今回はTextButtonのコンポーネントを使用しています

まとめ

functionとしてscrollToTopを定義してonclickで発火させるだけで意外と簡単に実装できました。
react-scrollの他にもscroll用パッケージが何個ありましたが、僕はこれが一番シンプルで簡単に実装できると思いました。
またreactに関する知識(JavaScript自体)がまだまだだなと感じたのでまだまだ勉強が必要です笑

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

ウェブページを作るにあたって

ウェブページを書く前に。

ウェブページを作るにあたっては、個別サイトか企業サイトかで中の作りが変わってくるので作りたい方に必要な要素などを書き出してから進めるといい。

ホームページの種類は従来型とブログ型(CMS)に大きく分けられる。

従来型

メリット

自己紹介や会社概要などの時系列を持たないコンテンツの配信には従来型のホームページが適している。

デメリット

一から作成していく必要があるので、コンテンツを追加しようとすると手間がかかってしまう。

ブログ型(CSM)

メリット

日々更新するような日記やニュースなどはブログを使った配信の方が適している。本文を簡単に投稿するだけで簡単にページを作成することができる。

デメリット

常に新しいコンテンツが優先してしまうので、古い記事の大事な内容が読者の目に止まりにくくなってしまう。

最近では両方のメリットを兼ね備えたページもある

最近では従来型とブログ型のメリットを生かして、自己紹介や会社案内、お問い合わせなどは常に表示されるようにして、ニュースやお知らせなどは投稿するだけで新しい記事になっていくものもある。

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

フロントエンド初心者がReactを触ってみた

環境

Mac OS Catalina

プロジェクトの作成

create-react-appで作成できる

// プロジェクトを作成したいディレクトリで
$ npx create-react-app react-example-app

// いろいろパッケージがダウンロードされる

プロジェクトのディレクトリへ移動

$ cd react-example-app
$ npm start

(一部略)
Compiled successfully!

You can now view react_app_practice in the browser.

  Local:            http://localhost:3000

以下の画像のようなページに遷移できるはず。

Screen Shot 2020-07-27 at 23.31.43.png

index.jsindex.cssApp.jsに変化を加えてみる

別のものを表示してみる。index.jsを以下のように書き換える。

App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

function Moneybook() {
  return (
    <div>
      <h1>
        小遣い帳
      </h1>
      <table className="book">
        <thead>
          <tr>
            <th>
              日付
            </th>
            <th>
              項目
            </th>
            <th>
              入金
            </th>
            <th>
              出勤
            </th>
          </tr>
        </thead>
        <tbody>
          <tr><td>1/1</td><td>お年玉</td><td>10000</td><td></td></tr>
          <tr><td>1/3</td><td>ケーキ</td><td></td><td>500</td></tr>
          <tr><td>2/1</td><td>お小遣い</td><td>3000</td><td></td></tr>
          <tr><td>2/5</td><td>漫画</td><td></td><td>600</td></tr>
        </tbody>
      </table>

    </div>
  );
}

export default Moneybook;

index.cssも書き換える

index.css
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

.book {
  border-collapse: collapse;
}

.book th, .book td {
  border: solid 1px;
  padding: 1px 15px;
}

最後にindex.jsも書き換える

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import MoneyBook from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
  <MoneyBook />,
  document.getElementById('root')
);

// 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();

これで npm startとすれば、以下のように表が表示されるはず。

Screen Shot 2020-07-28 at 0.36.37.png

これからもいろいろ他のものも触っていきたい。

参考

作りながら学ぶReact入門

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