- 投稿日:2020-07-28T21:43:41+09:00
D3.js y軸の方向を変える
d3.jsでy軸を表示すると思っていた軸と上下が逆さまだったため、改善方法をメモ。
つまずいたコード
以下、適当なデータを使って棒グラフをつくっていきます。すでに何かしらのコードを書いていて早く修正したい人は読み飛ばしても大丈夫です(最後のまとめにコードがあります)。
棒グラフとy軸の表示(デフォルト)
とにもかくにもsvg領域を作成します。
bar.jsvar width = 640; var height = 240; var svg = d3.select("#bar") .append("svg") .attr("width",width) .attr("height",height);次に適当なデータを作成してスケールを作成します。
bar.jsvar 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.jsvar 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という値がいちばん上に来てしまっていて棒グラフと矛盾しています。
棒グラフ描画の仕組み
以上のコードで修正するために注目すべき点はグラフの表示
部分です。軸方向を変えたときに何をやっているのか理解するために必要です。bar.jssvg.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)の値はオレンジ色の部分になります。
height-yScaled(d)から描画を始めることで、オレンジ色の部分の「土台」がつくられます。すると、我々から見ると普通のグラフに見えるわけです。
改善方法
y軸が上下反対になってしまう問題を解決するには以下のことを行う必要があります。
- スケールレンジのminとmaxを入れ替える
- y軸の位置を調整する先程のコードであれば以下のようになります。これによってy軸の向きは反対になります。
bar.jsvar yScale = d3.scale.linear() .domain([Math.min(...ylabel),Math.max(...ylabel)]) .range([rangemax,rangemin]); //! maxとminを入れ替えたしかしscaleが変更されたことによってスケーリングされた値も、さきほどとはあべこべの値になってしまいます。グラフを見ると以下のような状態です。
軸方向は改善されましたが、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); });以上の修正で次のようなグラフが得られるようになります。
修正される理屈ですが、下図のようにあべこべになったデータも新たに上限となったyScale(Math.min(...ylabel))から引く必要があります(以前のコードではデフォルトの上限であるheightから引いていた)。すると目的の表示が得られるようになります。
まとめ
コードの変更をまとめておきます
BEFORE:
bar.jsvar 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.jsvar 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);
- 投稿日:2020-07-28T21:29:38+09:00
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); }); }); }); })();動作確認
参考にしたWebサイト
- 投稿日:2020-07-28T21:17:26+09:00
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>javascriptconst CheckBtn = document.getElementById('CheckBtn'); CheckBtn.addEventListener('change', function () { console.log(CheckBtn.group.value); });まとめ
ラジオボタンでサイトの装飾を変化させたときのコードです。
よく見るドロワーやアコーディオンメニューなどの機能も「addEventListener()」で作れるので、覚えておいても良いかも。
- 投稿日:2020-07-28T20:46:51+09:00
高卒工場勤務が未経験から転職するまで(Prologue)
自己紹介
はじめまして!高校卒業後→地元の工場へ就職→現在転職のため学習中
はい。こんな感じで何にも学歴も良いわけでもなく何も取り柄がないわけですが、
転職を考えております。技術で勝負するしかないのです。Qiitaになぜ投稿し始めたか。
これは単純にアウトプットする場所が欲しかったからです。
これから少しづつアウトプットしていきます。とりあえず触ってみた技術、言語
HTML,CSS,JavaScript,Python,Django,Vue.js
とざっくりこのあたりを触りましたが、エラーを吐かれすぎて挫折仕掛けていました。これからどうしていくのか
エラーを吐かれて解決できずに止まることがモチベーションが下がり気味
スクールに通うか迷いましたが、闇が深すぎて断念
様々な人に聞いたところMENTAを使ってみてはどうかということで、
メンターさんについてもらうことにしました。
これからはもう少し効率的に進めていけたらと思います。
- 投稿日:2020-07-28T20:40:27+09:00
再帰処理で求める順列(nPr)【JavaScript】
この記事について
プログラミングクイズを解くにあたり、再帰処理を使うとスラッと解けそうな気がする問題がいくつかありました。
そのなかでも「順列」は特に使いこなせるようになりたかったので調べて記事にまとめました。以下の説明をしています。
- 順列とは何か
- 順列を出力するサンプルコード以下は説明しません。
- 公式 $nPr=\frac{n!}{(n-r)!}$を使った順列総パターン数の計算方法順列とは何か
そもそも順列ってなんでしょうか?
結論
順列(
nPr
)は、「n個の物の中からr個を取り出して一列に並べたもの」です。
nPr
のP
はpermutation
の略で、そのまま「順列」という意味です。
例えば3匹の動物から2匹を取り出して一列に並べると、以下のような順列になります。組み合わせ(
nCr
)との違いに注意注意しなければならないのは、
「1:ねずみ」「2:うし」
と「1:うし」「2:ねずみ」
は異なるものだという点です。
上記を「同じ」とみなす考え方は「組み合わせ(nCr
)」といいます。
この2つはよく混同されますが別物なので気をつけてください。nCrについては以下の記事がわかりやすかったです。
5分で分かる!確率統計「nCr」の計算方法順列の洗い出し方
どのように並べれば順列を取得のか、例を上げて解説します。
4匹から2匹選ぶ例
4匹の動物
ねずみ?
、うし?
、とら?
、うさぎ?
から2匹選んで並べるとき、以下の手順で並べるとパターンを網羅できます。
- [ねずみ]と[それ以外の動物]を並べる
- [うし]と[それ以外の動物]を並べる
- [とら]と[それ以外の動物]を並べる
- [うさぎ]と[それ以外の動物]を並べる
? - [ねずみ]と - [うし] - [とら] - [うさぎ] ? - [うし]と - [ねずみ] - [とら] - [うさぎ] ? - [とら]と - [ねずみ] - [うし] - [うさぎ] ? - [うさぎ]と - [ねずみ] - [うし] - [とら]4匹の動物それぞれに3匹の動物を並べています。
よって4 × 3 = 12 通り
の順列ができあがります。4匹から3匹選ぶ例
4匹の動物
ねずみ?
、うし?
、とら?
、うさぎ?
から3匹選んで並べるとき、以下の手順で並べるとパターンを網羅できます。
- [ねずみ]と[うし]と[それ以外の動物]を並べる
- [ねずみ]と[とら]と[それ以外の動物]を並べる
- [ねずみ]と[うさぎ]]と[それ以外の動物]を並べる
- [うし]と[ねずみ]と[それ以外の動物]を並べる
- [うし]と[とら]と[それ以外の動物]を並べる
- [うし]と[うさぎ]]と[それ以外の動物]を並べる
- [とら]と[ねずみ]と[それ以外の動物]を並べる
- [とら]と[うし]と[それ以外の動物]を並べる
- [とら]と[うさぎ]]と[それ以外の動物]を並べる
- [うさぎ]と[ねずみ]と[それ以外の動物]を並べる
- [うさぎ]と[うし]と[それ以外の動物]を並べる
- [うさぎ]と[とら]と[それ以外の動物]を並べる
? - [ねずみ]と - [うし]と - [とら] - [うさぎ] - [とら]と - [うし] - [うさぎ] - [うさぎ]と - [うし] - [とら] ? - [うし]と - [ねずみ]と - [とら] - [うさぎ] - [とら]と - [ねずみ] - [うさぎ] - [うさぎ]と - [ねずみ] - [とら] ? - [とら]と - [ねずみ]と - [うし] - [うさぎ] - [うし]と - [ねずみ] - [うさぎ] - [うさぎ]と - [ねずみ] - [うし] ? - [うさぎ]と - [ねずみ]と - [うし] - [とら] - [うし]と - [ねずみ] - [とら] - [とら]と - [ねずみ] - [うし]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)!}$)を使って求める方法もありますが、これはコードの説明から話が逸れすぎるので割愛します。サンプルコード
紹介した順列の羅列方法を元にコードを書いてみます。
関数
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
になるまで何度も繰り返し再帰呼び出しを行うことで、最終的に必要な配列を出力することができます。まとめ
順列(
nPr
)は、「n個の物の中からr個を取り出して一列に並べたもの」です。
r回数分再帰処理を繰り返すことで答えを導くことができます。再帰処理を使えば、倍々に選択肢が増えていくような処理(チェスのコマを置く場所や移動経路の探索など)が書けるようになると思います。
例題はたくさんあるので、他の処理もいつか記事にしてみたいと思います。ちなみにとてもかわいい動物の素材はこちらにお借りしました。
イラストレイン
- 投稿日:2020-07-28T18:50:48+09:00
[Rails][Turbolinks] inline Javascript が実行されない 解決法
Javascript周り の取り扱いが難しい
Turbolinks
。起こったこと
ページ毎に任意の
Javascript
を実行したいが
普通に書いたら以下エラー。ページ遷移でも同様。
ChromeDeveloperConsoleRefused 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_tagconfig/initializers/content_security_policy.rbRails.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) endscript タグに nonce をつける
some_page.html.slim= javascript_tag, nonce: true | console.log('JS here');Turbolinks の
Event
に合わせて、nonce を入れ替える。application.jsdocument.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
- 投稿日:2020-07-28T18:36:14+09:00
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())}`); }); };
- 投稿日:2020-07-28T18:17:08+09:00
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.jsvar 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関連
qiita:すっきり書きたい JavaScriptの条件分岐
【jQuery入門】removeClass()でクラスを削除する方法まとめ!
getElementsByClassNameで指定したクラスを全部消す
エラー関連
Uncaught Type Error: Cannot read property 'columns' of undefinedについて
今後の構想
・計算結果をリストに追加していく機能
(複数の計算があったとき用のメモ代わり)
RECORDボタン押してリストに追加する。・計算結果リストの編集ができる機能
(作れそうなら作ってみる)リストに追加する機能自体は、自分の記事からコピペして、今のコードに合うように書き換える。
リスト自体の配置やスタイルをどう当てるかが課題。
計算結果リストの編集機能は、主に削除さえできればと思っている。
- 投稿日:2020-07-28T18:04:21+09:00
fadeIn()を使うには先に非表示を
- 投稿日:2020-07-28T17:32:24+09:00
画像が読み込まれたか監視するjavascriptをnpmに公開しました
やりたかったこと
「シンプルjavascriptで画像の読み込みを監視し、
読み込み終わったらイベントを発火する」
という画像読み込みの監視クラス使い方
npm
https://www.npmjs.com/package/load-image.js$ npm install load-image.jsimport 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; }
- 投稿日:2020-07-28T17:05:27+09:00
【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
- 投稿日:2020-07-28T16:33:45+09:00
Pdf.jsで"The cMap url must be specified"のエラーが出たときの解決法
散々探しても中々情報が出てきませんでしたが、
以下のサイトに解決法がありました。
PDF.jsで日本語を表示させるのに苦労した話普通にエラーメッセージでググると
pdfjsLib.cMapUrl = "~/cmaps/";
pdfjsLib.cMapPacked = true;みたいなワンライナーが出てくるかと思いますが、
私の場合はこれでは解決しませんでした。解決法は以下
main.jspdfjsLib.getDocument({ url: '<PDFへのurlやpath>', // モジュール内にあるcmapsディレクトリーへのpathを指定してください // 末尾のスラッシュ'/'まで必須です cMapUrl: './node_modules/pdfjs-dist/cmaps/', cMapPacked: true, })*htmlのscriptダグからpdf.jsを読み込んで使用する場合、変数名「PDJFS」は既にDeprecatedで動きません。
代わりに「pdfjsLib」を使いましょう。
- 投稿日:2020-07-28T15:06:24+09:00
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
- 投稿日:2020-07-28T14:58:56+09:00
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を返す
- 投稿日:2020-07-28T12:24:05+09:00
Kindle Library の蔵書をAlgoliaで検索する
これは何
Kindleの蔵書一覧を全文検索サービスのAlgoliaで検索可能にするスクリプトです。
こちらの記事を参考に、蔵書一覧をCSV出力する代わりに Algolia でインデックスし、検索可能にしてみます。InstantSearch.jsでフロントエンドを作ると簡単なWebアプリケーションにもなります(サンプル)。
前提条件
あらかじめAlgoliaアカウントを作成し、API Keyを取得する必要があります。
アカウントの取得についてはこちらを参考に。
今回必要なのはインデックスを作成する権限及びレコードを追加する権限となるのため、editSettings と addObject 権限に絞った API キーをあらかじめ発行しておくと良いです。Algolia Dashboard からの操作だと以下のように新規 API キー作成時に ACL を指定することができます。ACL など API キーに関する詳細はこちらの記事にまとまっています。
なお、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 で指定したインデックスを選択。検索欄にクエリを入力すると検索結果が表示される
その他
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
- 投稿日:2020-07-28T10:30:48+09:00
[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
obj.aaa?.bbb.ccc
で、aaa?.
は undefined について未判定。.bbb
の時点で?.
未判定のため、後続の.ccc
がundefined
判定される。.ccc
は undefined なのでエラーになる。Nullish ?? Coalescing
??
の第二項の式は、第一項がundefined
かnull
ではない場合には評価されない。第二項は実行されない。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.)
- 投稿日:2020-07-28T10:30:48+09:00
[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.bbb?.ccc.ddd
で、bbb?.
は undefined と判定される。.ccc
以降は実行されず、直ちに undefined が返る。.ddd
は実行されないので、エラーにならない。obj.aaa // {} obj.aaa?.bbb // undefined obj.aaa?.bbb.ccc // Uncaught TypeError: // Cannot read property 'ccc' of undefined
obj.aaa?.bbb.ccc
で、aaa?.
は undefined ではないため、?.bbb
が実行される。.bbb
の後ろに?.
がないので、後続の.ccc
が実行される。.bbb
が undefined なので.ccc
は エラーになる。Nullish ?? Coalescing
??
の第二項の式は、第一項がundefined
かnull
ではない場合には評価されない。第二項は実行されない。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.)
- 投稿日:2020-07-28T10:27:07+09:00
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
- 投稿日:2020-07-28T07:07:33+09:00
「親子」のお小遣いで考えるVueのEmitの理解
なぜVueのEmit処理はややこしいのか。自分で理解したときのコツを残しておく。
* TypeScriptで記述しています。VueのEmitで必要になる処理
例えば、何かクリックのようなイベントがあったときを考えよう。
単に親から子供のデータにアクセスしたいだけのに、親側では以下のようなコードが必要になる。
- データにアクセスするための関数を用意する。
- 例:
changeParentValueTo
のような関数。- その関数を
@click=changeParentValueTo
でトリガーさせる。一方で、子側では以下のようなコードが必要になる。
@click=onClick
のようにクリック時に発火する関数を定義する
- これはまだ自然なのでわかりやすい。
@Emit
でデコレートされたpublic click
関数をオーバーライドする。onClick
の中で、click
関数に、渡したい値を引数として入れる。理解のコツ
上で見たとおり、やりたいことは単純なのにも関わらず実装はわりと面倒になる。素直に、
<子コンポーネント>.<変数>
のような形でアクセスできたらもっと簡単なのに、と思ったことはないだろうか。しかし
<子コンポーネント>.<変数>
という形で子供の変数にアクセスすることは、あまり良い方法とはいえない。これを理解するためには、「親側はなるべく子供の変数をダイレクトに反映させたくない」という原則を腹落ちしておく必要がある。
以下ではこの原則を理解するポイントと、この原則に沿ったコードの読み方について述べておく。
親側はなるべく子供の変数をダイレクトに反映させたくないという原則
ここでは暫定的に「親」「子」という意味を本来の意味(人間の「親子」というように使うときの意味)としてあえて曖昧にとらえ、親が子供に、作業量に応じたお小遣いをあげるという状況を考えよう。
この状況において、例えば以下のようにコードを書いたとしよう。
vue.jspaymentToChild = Child.workLoad * 100 //子供の作業量から計算。比例係数100 sendMoney(paymentChild) //自分の口座から送金実行このコードは、シンプルで読みやすいがちょっと恐ろしいコードでもある。2つの理由がある。
変な値が入ってきたときの挙動そのまま入ってしまう
例えば、子どものworkLoadがなぜかnullになってしまうとこのロジックは破綻してしまう。口座の変化に気が付きづらい
子供のワークロードに変化があったとしても、親側ではその変化が一切記述されていないので、このコードのデバッグは難しいものになるだろう。ということで、自然とこれらのデメリットをカバーするようなスクリプトを書こうということになる。それぞれのデメリットに対応するようにコードを書くと、
値をラップする関数を書く
直接的に代入するのではなく、変な値が入ってきたときに備えてのtry-catchとかを書いておく。変化を親がコントロールできるようにする
値の変化を子のコンポーネントのイベントで管理するのではなく、親のコンポーネントで管理できるようにする。というようになる。
...と、ここまで書けば気がついた方もいらっしゃると思うが、実はこれらをわざわざ自分で書かなくても良いというのが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処理はややこしいが、「なるべく子供の変数をダイレクトに反映させたくない」という原則を理解していれば、ある程度素直に認めることができるだろう。
- 投稿日:2020-07-28T02:12:12+09:00
Chrome ExtensionのbackgroundからWebAssemblyを呼び出す
ソース付き最小構成の記事が見当たらなかったので追加。
chrome拡張の background 内から WebAssembly を実行するサンプルを纏めました。
フォルダ構成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
を選択すれば、インストール完了です。
実行
インスールした直後に、自動的に
background.js
が実行され、そこから rustlib.rs
の関数greet()
が呼び出されます。src/crate/src/lib.rs#[wasm_bindgen] pub fn greet() -> String { alert("Hello, from Rust!"); "from_Rust!".to_string() }まず、インストール直後に、
alert()
が実行され、ポップアップが表示されたと思います。
次に、「background.html」をクリックし、 console でログを見てみましょう。
表示されたログから、
background.js
で、 Rust-apigreet()
を呼出し、戻値を受け取り表示している事が確認出来ます。※
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
- 投稿日:2020-07-28T01:26:17+09:00
react-scrollでトップページに戻るアニメーションを実装してみた
はじめに
今回、よくサイトでページ下部に固定表示されている「ページトップへ戻る」という機能をreactで実装する機会があったのでアウトプットとしてこの記事を残します。
アニメーションの調査
ページトップに戻るアニメーションを実装しようとした際に最初に思い浮かんだのは、jQueryでの実装でした。
しかし、そもそもjQueryはライブラリのサイズが大きくて古いということだったので他のreactに対応しているアニメーションパッケージを調査することにしました。
そこで見つけたのがreact-scrollというパッケージ。
react-scrollは様々なfunctionがパッケージに含まれていて、scrollToTopやscrollToBottomなどの関数で簡単にページ上下へのスクロールが実装できます。react-scroll
・https://www.npmjs.com/package/react-scroll完成形
アニメーションの挙動はこんな感じです。
実装方法
①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自体)がまだまだだなと感じたのでまだまだ勉強が必要です笑
- 投稿日:2020-07-28T01:21:00+09:00
ウェブページを作るにあたって
ウェブページを書く前に。
ウェブページを作るにあたっては、個別サイトか企業サイトかで中の作りが変わってくるので作りたい方に必要な要素などを書き出してから進めるといい。
ホームページの種類は従来型とブログ型(CMS)に大きく分けられる。
従来型
メリット
自己紹介や会社概要などの時系列を持たないコンテンツの配信には従来型のホームページが適している。
デメリット
一から作成していく必要があるので、コンテンツを追加しようとすると手間がかかってしまう。
ブログ型(CSM)
メリット
日々更新するような日記やニュースなどはブログを使った配信の方が適している。本文を簡単に投稿するだけで簡単にページを作成することができる。
デメリット
常に新しいコンテンツが優先してしまうので、古い記事の大事な内容が読者の目に止まりにくくなってしまう。
最近では両方のメリットを兼ね備えたページもある
最近では従来型とブログ型のメリットを生かして、自己紹介や会社案内、お問い合わせなどは常に表示されるようにして、ニュースやお知らせなどは投稿するだけで新しい記事になっていくものもある。
- 投稿日:2020-07-28T00:38:12+09:00
フロントエンド初心者が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以下の画像のようなページに遷移できるはず。
index.js
、index.css
とApp.js
に変化を加えてみる別のものを表示してみる。
index.js
を以下のように書き換える。App.jsimport 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.cssbody { 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.jsimport 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
とすれば、以下のように表が表示されるはず。これからもいろいろ他のものも触っていきたい。
参考