- 投稿日:2019-07-19T23:01:10+09:00
jsのflatMapの使い道を考える
flatMapをいまだに一度も使っていないので、使い道を調べた。A, Bはあまり直感的ではなく、Cはfilterとmapで書いたほうが
[]で囲まなくていいためわかりやすい気がする。関数型言語力が足りないのか?// A. 二次元配列を生成してからflattenする // bodyの孫要素のリストを取得 Array.from(document.body.children) .flatMap(x => Array.from(x.children)); // document内で使われているすべてのclassのリストを取得 Array.from(document.all) .flatMap(x => Array.from(x.classList)) .filter((x, i, a) => a.indexOf(x) === i); // B. 前後に挿入可能なmapとして使う `# Title 行1 行2 行3`.split("\n") .flatMap(v => { if (v.startsWith("#")) { return ["<h1>", v, "</h1>"] } return ["<p>", v, "</p>"] }); // -> ["<h1>", "# Title", "</h1>", "<p>", "行1", "</p>", "<p>", "行2", "</p>", "<p>", "行3", "</p>"] // C. 値を返さないでもよいmapとして使う [-20, 10, 0, 100, -80] .flatMap(v => { if (v <= 0) { return [] } else { return [v * v] } }) // -> [100, 1000]
- 投稿日:2019-07-19T22:32:50+09:00
jsのsplit()で区切り文字を残す
let str = 'a1:b2:c3:d4:e5:f6::'; console.log(str.split(':')); //普通 // ["a1", "b2", "c3", "d4", "e5", "f6", "", ""] console.log(str.split(/[1-9]/g));//正規表現 //["a", ":b", ":c", ":d", ":e", ":f", "::"] console.log(str.split(/(:)/g));//区切り文字も一つの要素として分割する //["a1", ":", "b2", ":", "c3", ":", "d4", ":", "e5", ":", "f6", ":", "", ":", ""] console.log(str.split(/(?<=:)/g));//区切り文字を直前の要素に含める //["a1:", "b2:", "c3:", "d4:", "e5:", "f6:", ":"] console.log(str.split(/(?=:)/g));//区切り文字を直後の要素に含める //["a1", ":b2", ":c3", ":d4", ":e5", ":f6", ":", ":"]
- 投稿日:2019-07-19T22:22:35+09:00
JavaScriptのスプレッド構文(...)ことはじめ
スプレッド構文とは
ドットを3つ使用した構文(
...)で、公式の回答では関数呼び出しでは 0 個以上の引数として、Array リテラルでは 0 個以上の要素として、Object リテラルでは 0 個以上の key-value のペアとして、Array や String などの iterable オブジェクトをその場で展開します。
長くてわかりにくいですが、スプレッド構文を使える場所が下記のようになっています。
- 関数呼び出し
function(...hoge)- array
[...hoge]- オブジェクト
{ ...hoge }iterableオブジェクトって?
iterableは繰り返し可能なという意味の単語です。
繰り返しの処理(for)で扱えるオブジェクトかどうかということだと認識しています。
for文の中で使われるiはiteraterのiだという話は聞きますね。そしてそのようなオブジェクトを展開してくれる構文がスプレッド構文だそうです。
具体例
先ほどの公式の例にログを追加したものです。
これでnumbersが展開されて、それぞれの引数x,y,zに入っていることがわかります。function sum(x, y, z) { console.log(x) console.log(y) console.log(z) return x + y + z; } const numbers = [1, 2, 3]; console.log(sum(...numbers)); // 1, 2, 3, 6便利な使い方
便利だから色々と使えますが、ここら辺の使い方を知っておくと便利だなと思いました。
html要素を取得して配列として扱う
odeList や HtmlCollection を配列のように操作する
このコードでは、特定のクラスの要素を取得して、それをスプレッド構文で展開して、配列として扱っています。
そのためそのままfilterメソッドがしようできて、取得したクラスの要素一覧から、特定の要素のみを抜き出すということができます。const elems = document.getElementByClassName('checkboxClassName'); const filtered = [...elems].filter(el => el.checked && el.classList.contains('foo')); // こうもかける const filtered = [...document.getElementByClassName('checkboxClassName')].filter(el => el.checked && el.classList.contains('foo'));連番の作成
ちょうど少し前にスプレッド構文を使用して連番を作成する記事がありました。
JavaScriptで[ 0, 1, 2, 3, 4 ]のような連番の配列を生成する方法スプレッド構文を使わない場合はどのように書くか?
apply
Function.prototype.apply()を使うと同じようなことができるそう。
apply() メソッドは与えられた this 参照値と、配列 (もしくは配列風のオブジェクト) の形で与えられた引数を用いて関数を呼び出します。
console.log(sum.apply(null, numbers)); // スプレッド構文を使った方がシンプル console.log(sum(...numbers));参考
使い所については、この記事わかりやすかったです。
【JavaScript】スプレッド演算子の便利な使い方まとめ
- 投稿日:2019-07-19T21:40:45+09:00
円の面積を公式を使わずして点を数えて求める
公式を使わなくたって、図形描いて点の数を数えたら面積になるんじゃない?っていうお話です。今回は円の面積を求めてみようと思います。
公式による円の面積の求め方
円の面積は、 円周率 * 半径**2 の公式で求めることができます。 円周率 を 3.14 、半径を 100 とするなら、 3.14 * 100**2 = 31400 で 31400 と求めることができます。
点の数を数えて求める?どうするの?
黒色キャンパス内に白色の円を描いて塗りつぶした後で、キャンパス内の全ピクセルから白色であるピクセルの数を数えます。
半径 1000 なら、 2000x2000 のキャンパスを用意して 4000000 、つまり 4 百万のピクセルをしらみつぶしにチェックして白色であるピクセルの数を数えます。
正確かどうかは、どんだけ正確な円を描くことができて、それに対して十分な解像度があるかといった環境にめちゃくちゃ依存してしまうという、なかなかスリリングな話となります。
じゃあやってみよう、環境はこれ
実行環境は以下のとおりです。 JavaScript の実行環境としてウェブブラウザじゃなくて Node.js を利用したのはタイムアウトとかを気にしなくて良いし、ファイル出力とかも楽かなっと思ったからです、
- Lubuntu 16.04(64bit)
- Memory 4G
- Node.js v10.16.0
- node-canvas 2.5.0
- libcairo2-dev 1.14.6-1
Node.js で canvas を使えるようにするために node-canvas をインストールします。 node-canvas は cairo を使用しているので、 cairo 関係のライブラリを事前にインストールしておきます。
node-canvasのインストールsudo apt-get install libcairo2-dev libjpeg-dev libgif-dev npm install -g canvasプログラムはこれ
以下がプログラムです。キャンパスに半径 1000 の白色の円を描いてキャンパス内の白色のピクセル数を数えています。
円を描いてピクセル数を数える'use strict' /* sudo apt-get install libcairo2-dev libjpeg-dev libgif-dev npm install -g canvas */ let radius = 1000; if (process.argv.length>2) radius = parseInt(process.argv[2]); const { createCanvas, Canvas } = require('canvas'); let canvas = createCanvas(radius*2, radius*2); let ctx = canvas.getContext('2d',{ pixelFormat: 'A8' }); //8bitグレースケール //図形を描画 ctx.antialias = 'none'; ctx.fillStyle = 'rgb(255,255,255)'; ctx.beginPath(); ctx.arc(radius, radius, radius, 0, 2 * Math.PI); ctx.fill(); //集計 let countArea = 0; for(let x=0;x<radius*2;x+=1000) { for(let y=0;y<radius*2;y+=1000) { let image = ctx.getImageData(x,y,1000,1000).data; for(let i=0;i<image.length;i++) if (image[i]==255) countArea++; } } //比較のため公式を使って面積を計算 let formulaArea = Math.floor(Math.PI*radius**2); //結果を表示 console.log(`radius = ${radius}`); console.log(`countArea = ${countArea}`); console.log(`formulaArea = ${formulaArea}`); console.log(`absolute_err = ${Math.abs(countArea-formulaArea)}`); console.log(`relative_err = ${Math.abs((countArea-formulaArea)/formulaArea)}`); //試しに算出した結果から逆算して円周率を計算してみる let pi = countArea/radius**2; console.log(`pi = ${pi}`);さぁ実行してみよう!
さぁて実行です! 2000x2000 のキャンバス内をピクセルを数えることになるので、速度的にどうかなって思っていたのですが、一瞬で終了しました。さすがです。
で、気になるその結果は…、以下のとおりです。
実行結果$ time node circle.js radius = 1000 countArea = 3141577 formulaArea = 3141592 absolute_err = 15 relative_err = 0.0000047746492860944385 pi = 3.141577 real 0m0.164s user 0m0.140s sys 0m0.024sおー!なんということでしょう!公式で求めたのが 3141592 で、ピクセルの数を数えた結果が 3141577 !想像以上の良い結果です!
さらに試しに逆算した円周率も四捨五入したらなんと 3.14 に! node-canvas というか cairo がそこまで正確な円を描いているとは想像もしていなかったので、かなりの驚きです。
半径を大きくすると正確になる?
もしかして半径をもっと大きくすればもう少し正確になるかと思い、半径を 10000 にして計算してみました。…ちょこっと精度があがりました。
半径10000で計算$ time node circle.js 10000 radius = 10000 countArea = 314158637 formulaArea = 314159265 absolute_err = 628 relative_err = 0.0000019989860875183802 pi = 3.14158637 real 0m1.739s user 0m1.424s sys 0m0.336sもうちょっと調子に乗ってみる
思った以上に良い結果が出たので、調子にのって、もうちょっと複雑な図形でも試してみようと思います。
ターゲットは?
「面積の求め方(第3回)~葉っぱ型図形の面積 | 学びの場.com」の問題 3 は以下のような図形の黒色の面積を求めるというものです。その面積をピクセル数を数えて求めてみます。ホームページには計算の仕方や答えも書いてあるので比較しやすそうです。
プログラムはこれ
でもってプログラムは以下のとおりです。ホームページでは 10cmx10cm でしたが、プログラムでは、 1000x1000 のサイズで描画して集計して、それを最後に 10000 で割ることで求めています。
問題3'use strict' /* sudo apt-get install libcairo2-dev libjpeg-dev libgif-dev npm install -g canvas */ //https://www.manabinoba.com/math/6520.html let radius = 500; if (process.argv.length>2) radius = parseInt(process.argv[2]); const { createCanvas, Canvas } = require('canvas'); let canvas = createCanvas(radius*2, radius*2); //let ctx = canvas.getContext('2d'); let ctx = canvas.getContext('2d',{ pixelFormat: 'A8' }); //8bitグレースケール //図形の描画 ctx.antialias = 'none'; ctx.globalCompositeOperation = "xor"; ctx.fillStyle = "rgb(255,255,255)"; ctx.beginPath(); ctx.arc(0, radius, radius, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(radius, radius*2, radius, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(0, radius*2, radius*2, 0, Math.PI*2); ctx.fill(); //集計 let countArea = 0; for(let x=0;x<radius*2;x+=1000) { for(let y=0;y<radius*2;y+=1000) { let image = ctx.getImageData(x,y,1000,1000).data; for(let i=0;i<image.length;i++) if (image[i]==255) countArea++; } } countArea/=(radius*2/10)**2; //比較のため公式を使って面積を計算 let formulaArea = 10*10 * ((10*10*Math.PI/4 - 10*10/2) * 2 / 100) / 2; //結果を表示 console.log(`radius = ${radius}`); console.log(`countArea = ${countArea}`); console.log(`formulaArea = ${formulaArea}`); console.log(`absolute_err = ${Math.abs(countArea-formulaArea)}`); console.log(`relative_err = ${Math.abs((countArea-formulaArea)/formulaArea)}`); //確認用 const fs = require('fs'); const out = fs.createWriteStream("fig3.png"); const stream = canvas.createPNGStream(); stream.pipe(out); out.on('finish', () => console.log('The PNG file was created.'));さぁ実行してみよう!
では実行!…あいかわらずあっけないくらい一瞬で終わってしまいました。で、で、気になる結果はというと…、 じゃじゃん 28.5389 となりました!小数点以下第2位で四捨五入すれば 28.5 となり、ホームページでの答えである 28.5cm² と一緒になりました!おー!
結果$ time node fig3.js radius = 500 countArea = 28.5389 formulaArea = 28.539816339744835 absolute_err = 0.0009163397448332944 relative_err = 0.00003210741561630831 The PNG file was created. real 0m0.173s user 0m0.152s sys 0m0.020s結局どう?
私個人的には使える!って思うのですが、実行する環境によって結果が変わっちゃいますし、「点の数を数えました」よりも「公式を使いました」の方が信頼されるかもしれないので努力は報われないかもしれないです。意味がないって言われちゃうかもしれないです。
でも、楽しかったので、ま、私的にはいいかなって、自己満足に浸ることができたんだし。ということでめでたしめでたし、です。
- 投稿日:2019-07-19T21:39:09+09:00
水汲み問題(water jug problem)をPython、Javascriptで実装してみた
動機
研究室内の課題で水汲み問題を解く問題が出たので始めにPyhtonで書き、Javascriptの勉強中だったのでJavascriptでも書いてWebサイト風にアレンジしてみました。これをサーバーで公開するのもなぁと思ったのでコピペしてブラウザで開いてみてください。
Pythonのコード
warer_jug.pyimport queue class Bottle: def __init__(self,one,two): self.one = 0 #一個目のボトル self.two = 0 #二個目のボトル self.one_max = one self.two_max = two def put(self,now_one,now_two,num,past_list): #oneのボトルを水で満たす copy = past_list.copy() if num ==1: now_one = self.one_max elif num ==2: now_two = self.two_max for i in range(len(copy)): if copy[i] != [now_one,now_two]: if (i== len(copy)-1): copy.append([now_one,now_two]) queue.put(copy) break else: break def pour(self,now_one,now_two,num,past_list): #numから一方のボトルに水を移す copy = past_list.copy() if num==1: #oneからtwo now_two += now_one if now_two >= self.two_max: #あふれる場合,全部は入れない場合 now_one = now_two - self.two_max now_two = self.two_max else: now_one = 0 elif num ==2: #twoからone now_one += now_two if now_one >= self.one_max: #あふれる場合,全部は入れない場合 now_two = now_one - self.one_max now_one = self.one_max else: now_two = 0 for i in range(len(copy)): if copy[i] != [now_one,now_two]: if (i== len(copy)-1): copy.append([now_one,now_two]) queue.put(copy) break else: break def away(self,now_one,now_two,num,past_list): #水を捨てる copy = past_list.copy() if num==1: now_one = 0 elif num==2: now_two = 0 for i in range(len(copy)): if copy[i] != [now_one,now_two]: if (i== len(copy)-1): copy.append([now_one,now_two]) queue.put(copy) break else: break past_op = [] queue = queue.Queue() num_one = int(input("Bottle1? ")) num_two = int(input("Bottle2? ")) num_Want = int(input("How much do you want? ")) bottle = Bottle(num_one,num_two) bottle.one = bottle.one_max past_op.append([bottle.one,bottle.two]) queue.put(past_op) while (True): get_queue = queue.get() now = get_queue[-1] bottle.one,bottle.two = now[0],now[1] if (bottle.one == num_Want or bottle.two == num_Want): for i in get_queue: print(i) break if bottle.one > 0 and num_one > bottle.one: bottle.put(bottle.one,bottle.two,1,get_queue) bottle.pour(bottle.one,bottle.two,1,get_queue) #oneからtwo bottle.away(bottle.one,bottle.two,1,get_queue) #oneを捨てる if bottle.one == 0: bottle.put(bottle.one,bottle.two,1,get_queue) if bottle.one == num_one: bottle.pour(bottle.one,bottle.two,1,get_queue) #oneからtwo bottle.away(bottle.one,bottle.two,1,get_queue) if bottle.two > 0 and num_two > bottle.two: bottle.put(bottle.one,bottle.two,2,get_queue) bottle.pour(bottle.one,bottle.two,2,get_queue) #oneからtwo bottle.away(bottle.one,bottle.two,2,get_queue) #oneを捨てる if bottle.two == 0: bottle.put(bottle.one,bottle.two,2,get_queue) if bottle.two == num_two: bottle.pour(bottle.one,bottle.two,2,get_queue) #oneからtwo bottle.away(bottle.one,bottle.two,2,get_queue) if(queue.empty()): print("無理です") breakはい、めちゃくちゃ長いです。頑張ればもうちょい短くなると思います。
やってることはBottleクラスでボトル1と2の入っている水量とサイズを定義し、水を入れる関数と水を移す関数、水を捨てる関数を定義します。
あとはキューに今までの水の推移を表したリストを入れていき、ボトル1か2が目的の水量になったらその推移を表したリストを表示して終了です。
もし不可能な組み合わせ(例:6Lと3Lの容器で4Lを測るなど)をしたらいずれキューが空になるのでそうなったら無理と表示して終わりです。Javascript(+HTML+CSS)のコード
water_jug.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>水量を測る</title> <h1>水を測ってみよう!</h1> <style type="text/css"> body{ text-align: center; } #table{ position: relative; } #table table{ position:absolute; left: 50%; } </style> <script> var queue = []; var past_op = []; function Bottle(one,two){ this.one = 0; this.two = 0; this.one_max = one; this.two_max = two; } function show(list_water){ var table = document.getElementById("table"); var string = "<table><tr><th>ボトル1</th><th>ボトル2</th></tr><br>"; for(var i = 0; i<= list_water.length-1; i++){ string += "<tr><td>"+list_water[i][0]+"</td><td>"+list_water[i][1]+"</td></tr><br>"; } string +="</table><br>"; table.innertHTML = string; } function calc_water(myForm) { var bottle = new Bottle(Number(myform.Bottle1.value),Number(myform.Bottle2.value)); bottle.one = bottle.one_max; past_op.push([bottle.one,bottle.two]) queue.push(past_op) while (1){ var get_queue = queue.shift() var now = get_queue[get_queue.length -1] bottle.one = now[0]; bottle.two = now[1]; if (bottle.one == Number(myform.Want_Water.value) || bottle.two == Number(myform.Want_Water.value)){ for (var i=0; i < get_queue.length; i++){ console.log(get_queue[i]); } show(get_queue); break; } if (bottle.one > 0 && bottle.one_max > bottle.one){ bottle.put(bottle.one,bottle.two,1,get_queue); bottle.pour(bottle.one,bottle.two,1,get_queue); //oneからtwo bottle.away(bottle.one,bottle.two,1,get_queue); //oneを捨てる } if (bottle.one == 0){ bottle.put(bottle.one,bottle.two,1,get_queue); } if (bottle.one == bottle.one_max){ bottle.pour(bottle.one,bottle.two,1,get_queue); //oneからtwo bottle.away(bottle.one,bottle.two,1,get_queue); } if (bottle.two > 0 && bottle.two_max > bottle.two){ bottle.put(bottle.one,bottle.two,2,get_queue); bottle.pour(bottle.one,bottle.two,2,get_queue); //oneからtwo bottle.away(bottle.one,bottle.two,2,get_queue); //oneを捨てる } if (bottle.two == 0){ bottle.put(bottle.one,bottle.two,2,get_queue); } if (bottle.two == bottle.two_max){ bottle.pour(bottle.one,bottle.two,2,get_queue); //oneからtwo bottle.away(bottle.one,bottle.two,2,get_queue); } if (queue.length == 0){ cant(); break; } } } Bottle.prototype.put = function(now_one,now_two,num,past_list){ var copy = Array.from(past_list); if (num ==1){ now_one = this.one_max; } else if (num ==2){ now_two = this.two_max; } for (var i=0;i< copy.length;i++){ if (past_list[i].toString() != [now_one,now_two].toString()){ if (i==copy.length-1){ copy.push([now_one,now_two]); queue.push(copy); break; } } else{ break; } } } Bottle.prototype.pour = function(now_one,now_two,num,past_list){ var copy = Array.from(past_list) if (num==1) { now_two += now_one; if (now_two >= this.two_max){ //あふれる場合,全部は入れない場合 now_one = now_two - this.two_max; now_two = this.two_max; } else { now_one = 0; } } else if (num ==2){ //twoからone now_one += now_two; if (now_one >= this.one_max){//あふれる場合,全部は入れない場合 now_two = now_one - this.one_max; now_one = this.one_max; } else{ now_two = 0; } } for (var i=0;i< copy.length;i++){ if (past_list[i].toString() != [now_one,now_two].toString()){ if (i==copy.length-1){ copy.push([now_one,now_two]); queue.push(copy); break; } } else{ break; } } } Bottle.prototype.away = function(now_one,now_two,num,past_list){ //水を捨てる var copy = Array.from(past_list) if (num==1){ now_one = 0; } else if (num==2){ now_two = 0; } for (var i=0;i< copy.length;i++){ if (past_list[i].toString() != [now_one,now_two].toString()){ if (i==copy.length-1){ copy.push([now_one,now_two]); queue.push(copy); break; } } else{ break; } } } </script> </head> <body> <form id="myform" name="myform"><p> Bottle1の大きさ:<input type="text" name="Bottle1"><br> Bottle2の大きさ:<input type="text" name="Bottle2"><br> 欲しい水の量:<input type="text" name="Want_Water"><br> <input type="button" value="計算" name="calc" onclick="calc_water(this.form)"><br> </p></form> <div id = "table"> </div> </body> </html>javascriptは勉強して1週間でクソザコナメクジなのでもっといいコードが書けるかもしれません
やってることはpythonとほとんど同じです。解が見つかったら表を作成するHTMLコードを作成し表示するって感じです。
質問またはこうした方がいいってのがあったら気軽にコメントしてください
- 投稿日:2019-07-19T21:38:58+09:00
絶対パス、ルート相対パス、相対パスが混在するHTMLをなんとかする(暫定処置)
前書き
サイト制作および保守運用対応の最中で、現場より以下のような相談をいただきました。
「Aタグにテストドメインへのリンクと、本番ドメインへのリンクが混在しているのを何とかできないか?」
ん・・・どういうこと?
確認したところ、静的ページのHTML内で、
次のような実装が行われている場所がチラホラあるようです。<a href="https://honban_web_site.xxxxx.yyyyy.zzzzz/aaaaa.html">Aページ</a> <a href="./aaaaa.html">Bページ</a> <a href="/aaaaa.html">Cページ</a>パスの書き方が混在しています。これだと確かにリンクが混在してしまいますね。
制作者やその時の状況によって書き方が異なっていた、という状態のようです。
(動的生成しているページはそのあたりを考慮しているので、上記事象は起きていません。)パスの書き方は、どこかで統一するよう舵きりしていただきたいですが、
それとは別アプローチで暫定対処できる方法は無いものかと考えてみました。何がしたいのか
テストドメイン上でのアクセス時限定で、
「テストドメインへのリンクと、本番ドメインへのリンクが混在している」
のを、HTMLは直接修正せずに暫定対処したい。なお、想定されるリンクの種類と修正対応は次の通り。
Aタグ内で定義されたリンクの種類と修正対応
パス ドメイン 詳細 修正対応 絶対パス 本番ドメイン 末尾がファイル名 本番ドメインをテストドメインに書き換える 絶対パス 本番ドメイン 末尾が半角スラッシュ 本番ドメインをテストドメインに書き換える 絶対パス 本番ドメイン 末尾がドメイン末尾文字 本番ドメインをテストドメインに書き換える 絶対パス 外部ドメイン 考慮しない 何もしない ルート相対パス アクセス中のドメイン 半角スラッシュとファイル名 テストドメインの絶対パスに書き換える 相対パス アクセス中のドメイン 基点が同じ階層 (ファイル名) テストドメインの絶対パスに書き換える 相対パス アクセス中のドメイン 基点が同じ階層 (./) テストドメインの絶対パスに書き換える 相対パス アクセス中のドメイン 基点が上の階層 (../) テストドメインの絶対パスに書き換える やったこと
テストドメイン上でのアクセス時限定で、
A タグ の href を「テスト環境ドメイン」を示す絶対パスに書き換える処理を、
読み込ませるようにしてみました。構成
sample.html (該当のHTMLファイルです) ┗ jquery-x.x.x.min.js (jQueryファイルです) ┗ domainchk.js (ホスト名がテスト環境の場合のみ「href_replace.js」を読み込む) ┗ href_replace.js (A タグ の href を「テスト環境ドメイン」に書き換える処理)まずはHTMLの構成。
上述の「リンクの種類と修正対応」を参考に、現場でみたHTMLのサンプルを準備しました。
HTML内では、絶対パス、ルート相対パス、相対パスが、混在した状態になっています。sample.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="https://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>HOST書き換えテスト</title> <meta http-equiv="Content-Style-Type" content="text/css" /> <meta http-equiv="Content-Script-Type" content="text/javascript" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <script type="text/javascript" src="/js/jquery-x.x.x.min.js"></script> <script type="text/javascript" src="/js/domainchk.js"></script> </head> <body> <b>テストドメインでアクセスした時だけ A タグ のリンクURLを「テストドメインの絶対パス」に書き換えます</b> <hr> <a href="https://honban-xxxxx.yyyyy.zzzzz/index.html">絶対パス 本番ドメイン 末尾がファイル名</a><br> <a href="https://honban-xxxxx.yyyyy.zzzzz/">絶対パス 本番ドメイン 末尾が半角スラッシュ)</a><br> <a href="https://honban-xxxxx.yyyyy.zzzzz">絶対パス 本番ドメイン 末尾がドメイン末尾文字</a><br> <a href="https://www.google.co.jp">絶対パス 外部ドメイン</a><br> <a href="/index.html">ルート相対パス(/index.html)</a><br> <a href="index.html">相対パス 基点が同じ階層 (ファイル名)(index.html)</a><br> <a href="./index.html">相対パス 基点が同じ階層 (./)(./index.html)</a><br> <a href="../index.html">相対パス 基点が上の階層 (../)(../index.html)</a> </body> </html>上記HTML内から、以下のjsを読み込ませました。
実際には、各ページが共通で読み込むHTMLファイル上でjsを読みこませることで、
ファイルごとでjs読み込みをせずに済むよう処置しています。domainchk.js
$(function() { var domain_now = $(location).attr('protocol') + '//' + $(location).attr('host') + '/'; if (domain_now.match('/test.')) { $.getScript(domain_now + "/js/href_replace.js"); } });domainchk.js の処理概要
$(location)を使うと、現在アクセスしているページURL等の情報を取得できます。
Location - Web API | MDN - Mozilla
https://developer.mozilla.org/ja/docs/Web/API/Locationdomain_now には、現在アクセスしているページURLが入ってきます。
var domain_now = $(location).attr('protocol') + '//' + $(location).attr('host') + '/';次に、現在アクセスしているページURLに、
「テストドメインを示す文字が含まれるか」を確認してから、
別のjsファイルに書いた「hrefの書き換え処理」を読み込ませます。
(テストドメインを直書きすると世に存在が割れてしまうので、URLの部分一致による判断で済ませています。)これらは、本番へリリースした時に本対応を動作させないための処置となります。
if (domain_now.match('/test.')) { $.getScript(domain_now + "/js/href_replace.js"); }そして「hrefの書き換え処理」は、以下のとおりです。
href_replace.js
$(function() { $('a').each(function() { var obj = jQuery(this); var link = obj.attr("href"); var url_now = $(location).attr('href'); var domain_now = $(location).attr('protocol') + '//' + $(location).attr('host') + '/'; var origin_url = 'https://honban-xxxxx.yyyyy.zzzzz'; // 絶対パス の対処 if (link.startsWith('http')) { // パス内に本番URLが含まれる if (link.startsWith(domain_org)) { // 本番URLの後に「/」があるかないか if (link.slice(domain_org.length, domain_org.length + 1) == '/') { obj.attr("href", link.replace(domain_org + '/', domain_now)); } else { obj.attr("href", link.replace(domain_org, domain_now)); } } return true; } // ルート相対パス (/) の対処 if (link.startsWith('/')) { obj.attr("href", domain_now + link.replace('/', '')); return true; } // 相対パス 基点が同じ階層 (ファイル名 or ./) および 上の階層 (../) の対処 obj.attr("href", new URL(link, url_now).href); }); });href_replace.jsの処理概要
まず、該当ページ内のAタグを精査していきます。
$('a').each(function() {次に現在アクセスしているページURL等の情報を取得や、本番URLの定義など。
(呼び出し前の処理で済ませた方が良かったかもしれません。)var obj = jQuery(this); var link = obj.attr("href"); var url_now = $(location).attr('href'); var domain_now = $(location).attr('protocol') + '//' + $(location).attr('host') + '/'; var origin_url = 'https://honban-xxxxx.yyyyy.zzzzz';続いて、絶対パスの対処。
パス内に本番ドメインが含まれる場合のみ、
現在アクセスしているドメイン(テストドメインと同義)に書き換えます。なお、外部ドメインは書き換えることなくそのままとしています。
// 絶対パス の対処 if (link.startsWith('http')) { // パス内に本番URLが含まれる if (link.startsWith(domain_org)) { // 本番URLの後に「/」があるかないか if (link.slice(domain_org.length, domain_org.length + 1) == '/') { obj.attr("href", link.replace(domain_org + '/', domain_now)); } else { obj.attr("href", link.replace(domain_org, domain_now)); } } return true; }続いて、ルート相対パスの対処。
こちらはリンクの先端に、現在アクセスしているドメイン(テストドメインと同義)を追記すればOK。
そのまま絶対パスとして成立するはずです。// ルート相対パス (/) の対処 if (link.startsWith('/')) { obj.attr("href", domain_now + link.replace('/', '')); return true; }最後に、相対パスの対処。上記のどこにも該当しなかったものを処置対象としています。
URL()を使うと、相対パスを絶対パスに置換することができます。
URL() - Web API | MDN - Mozilla
https://developer.mozilla.org/ja/docs/Web/API/URL/URL第1引数に相対パスを、第2引数に絶対パスのベースになるURL、
を指定すれば、絶対パスに変換してくれるスグレモノです。// 相対パス 基点が同じ階層 (ファイル名 or ./) および 上の階層 (../) の対処 obj.attr("href", new URL(link, url_now).href);これで、とりあえず完成。
実際に使ってみた感想
本番環境へ影響を与えない、かつ、HTML上のパスに手を入れることなく、
Aタグのリンクをテストドメインへ向けることができます。パスの書き方が乱立している環境では、暫定処置としては中々役立つのではないでしょうか。
本当にやりたかったこと
以上は自分なりに空気を読んだ暫定処置でした。
本当にやりたかったことは「Aタグを全て絶対パスかルート相対パスに書き換える」です。
(だってね、バラバラじゃわかりにくいし管理でけへry)
ただ、制作者サイドにはそれなりの都合や理由があるわけです。
それゆえに、責任範囲外の場所で見つかった課題とどう向き合うかはいつも考えさせられるものです。まぁ勉強がてら、今度そういう処理をPythonで書いてみようかな。
おまけ:HTML上のリンクはどう書くべきかの所感
適当にググった所感なのですが、なんだか色々と意見が割れている模様。
「絶対パス派」「ルート相対パス派」「相対パス派」とそれぞれに筋だった見解があるようです。その上で、私なりの意見は「絶対パス」が良いかなと考えています。
理由
Google Search Console で絶対パスが推奨されている
https://support.google.com/webmasters/answer/35156?hl=ja上記では、
「可能な場合は、相対リンクではなく絶対リンクを使用します(たとえば、サイト内の別のページにリンクするときに、mypage.html だけではなく https://www.example.com/mypage.html とします)」
と書かれているようです。推奨の理由までは書かれていないようですが、
推察するに「相対パス」でリンク切れ(制作側のヒューマンエラー)が起きやすいことを、
Google側が体感しているから、なのかもしれません。単にGoogleがそう言っているから、ではなくて、
その背景にあるものを考えると「絶対パス」がベターなのかなと考えるようになりました。
(上記を知るまでは「ルート相対パス」が良いかなと思ってました。)まとめ
HTML上のパスはなるべく統一したルールで書いてもらいたいなぁと思った次第
- 投稿日:2019-07-19T20:45:53+09:00
Firebase の Realtime Databaseにてデータを降順で並び替える方法
Firebase の Realtime Database を使用した際に orderByChild では昇順でしか並び替えることができず困ったため、見つけた解決方法を投稿しようと思います。まだまだ初心者のため表現方法等は誤っているかもしれませんが、下記コードは一度実行し動作確認しております。
使用環境
- AWS Cloud9
- Firebase Realtime DatabaseRealtime Database のデータ構造
コード
HTML
Firebase の設定等の部分は省略します
<button id="order">昇順</button> <button id="reverse">降順</button> <button id="reset">リセット</button> <div id="list"></div>JavaScript(データ出力部分)
const createDiv = function(firebaseData) { // 取得データに下記処理を実行 firebaseData.on("child_added", function(snapshot) { // "name","count"の値を代入 const getName = snapshot.child("name").val(); const getCount = snapshot.child("count").val(); // 新しい div 要素を作成 const newDiv = document.createElement("div"); // 新しい div の内容を作成 const divContent = document.createTextNode(getName + " : " + getCount); // 上記で作成した内容を div 要素に追加 newDiv.appendChild(divContent); // 内容を追加した div を list IDの中に追加 document.getElementById('list').appendChild(newDiv); }); };なお、HTMLのデータ出力に関してはこちらを参考にしております。
JavaScript(昇順)
document.getElementById("order").onclick = function() { // Firebaseよりデータを取得 const firebaseData = firebase .database() .ref("list") .orderByChild("count"); // 取得したデータを使い、出力する createDiv(firebaseData); };となりますが、こちらをアルファベット順にしようとする場合、count(Realtime Database の値) の値が降順になるようにしなければなりません。
そこで下記のように、JavaScript(降順)
document.getElementById("reverse").onclick = function() { // Firebaseよりデータを取得 const firebaseData = firebase .database() .ref("list") // + 値の場合昇順になってしまうので、 - をつける .orderByChild("-count"); // 取得したデータを使い、出力する createDiv(firebaseData); };内容はほとんど変更がありませんが、一カ所Firebaseよりデータを取得する際に、 マイナス(-) を count(Realtime Database の値) に入れています。
これにより先ほどとはデータを取得する順序が変更され、
- name : AAA count : 3(-3)
- name : BBB count : 2(-2)
- name : CCC count : 1(-1) とcount(Realtime Database の値) が降順で出力されるようになりました。
JavaScript(全体)
/* jshint curly:true, debug:true */ const createDiv = function(firebaseData) { // 取得データに下記処理を実行 firebaseData.on("child_added", function(snapshot) { // "name","count"の値を代入 const getName = snapshot.child("name").val(); const getCount = snapshot.child("count").val(); // 新しい div 要素を作成 const newDiv = document.createElement("div"); // 新しい div の内容を作成 const divContent = document.createTextNode(getName + " : " + getCount); // 上記で作成した内容を div 要素に追加 newDiv.appendChild(divContent); // 内容を追加した div を list IDの中に追加 document.getElementById('list').appendChild(newDiv); }); }; // 昇順 document.getElementById("order").onclick = function() { // Firebaseよりデータを取得 const firebaseData = firebase .database() .ref("list") .orderByChild("count"); // 取得したデータを使い、出力する createDiv(firebaseData); }; // 降順 document.getElementById("reverse").onclick = function() { // Firebaseよりデータを取得 const firebaseData = firebase .database() .ref("list") // + 値の場合昇順になってしまうので、 - をつける .orderByChild("-count"); // 取得したデータを使い、出力する createDiv(firebaseData); }; // リセット document.getElementById("reset").onclick = function() { document.getElementById("list").innerHTML = ""; };追記
こちら以外の解決方法やコードの書き方等ございましたら、勉強のために是非お教えいただければと思います。この投稿が、少しでも悩んでいる誰かの助けになれば幸いです。
- 投稿日:2019-07-19T20:40:01+09:00
TypeScriptでプロパティの初期値をnullにしたりしなかったりの使い分け
ふと意識した途端、クラスをnewしたときにプロパティへ何を初期値として入れるべきなのかが段々分からなくなってきました。(Stackoverflowで質問立てたけどうまく英語が通じなかった?)
自分の中で考えられうる実装のバリエーションを分類した上で、どう使い分けるべきなのか現状の考えをメモします。
A. 初期値は
nullだよ派
nullとのユニオン型を定義し、コンストラクタ内でnullをセットする実装。
- メリット
- 意味のある値がセットされている状態か、そうでないかが区別できる
- 懸念
- プロパティを使用する際、
nullの場合の分岐も実装しなければならないclass MyEntity { public name: string | null; public num: number | null; public date: Date | null; public messages: string[] | null; public subEntity: SubEntity | null; constructor() { this.name = null; this.num = null; this.date = null; this.messages = null; this.subEntity = null; } }B. 初期値は
undefinedだよ派
undefinedとのユニオン型を定義し、意図的にundefinedのままにしておく実装。
- メリット
- 意味のある値がセットされている状態か、そうでないかが区別できる
- 懸念
- プロパティを使用する際、
undefinedの場合の分岐も実装しなければならない- 値が
undefinedのとき、本当に存在しないプロパティなのか、値がセットされていないだけなのかが分かりにくそうclass MyEntity { public name: string | undefined; public num: number | undefined; public date: Date | undefined; public messages: string[] | undefined; public subEntity: SubEntity | undefined; constructor() { } }C. 初期値は空っぽい値だよ派
空っぽい値を明示的にコンストラクタ内でセットする実装。
- メリット
- ユニオン型がいらないので、型定義がシンプル
nullやundefinedのケースを想定した分岐が不要- 懸念
- 値がセットされていないのか、セットした値がたまたまデフォルト値と同じなのかが判別できない
(配列が初期値のままなのか、セットした値が実際にゼロ件なのかなど)- 文字列型と配列以外の型では、本当の意味で「空っぽい値」を定義できない(特に日付型)
class MyEntity { public name: string; public num: number; public messages: string[]; public subEntity: SubEntity; constructor() { this.name = ''; this.num = 0; // 0が空っぽい値というのはかなり強引…… this.messages = []; this.subEntity = new SubEntity(); } }D. 意味のあるデフォルト値をセットしちゃうよ派
明示的に意味のあるデフォルト値をコンストラクタ内でセットしてしまう実装。
- メリット
- 型定義がシンプル
nullやundefinedの状態を想定せずに実装できる- 前提が整っていれば、初期状態と値がセットされた状態を区別できる
- 懸念
- 定義したデフォルト値が偶然セットした値と一致していまう可能性がある
- デフォルト値のせいで無意味にメモリを消費するのでは
- 設計の難易度が余計に増すのでは
class MyEntity { public name: string; public num: number; public date: Date; constructor() { this.name = 'not set'; // 初期値は'not set'だという仕様にしてしまう this.num = -9999; // 初期値は-9999だという仕様にしてしまう this.date = new Date(1889, 0); // 初期値は1889年1月1日だという仕様にしてしまう } }E. 絶対にコンストラクタ引数からセットするよ派
コンストラクタの引数から値をとらせることを強制する実装。
- メリット
- 型定義がシンプル
- 初期値に悩む必要がない
nullやundefinedの状態を想定せずに実装できる- 懸念
- 「まだ具体的な値は分からないけどとりあえずインスタンスを生成したい」という場合に使いにくい
- 初期値をどうするのかという観点では、問題を一つ上のレイヤーへ先送りしただけ
class MyEntity { constructor( public name: string; public num: number; public date: Date; public messages: string[]; public subEntity: SubEntity; ) { } } const entity = new MyEntity('hoge', 1, new Date(), ['message1', 'message2'], new SubEntity());よく選ぶ実装パターンと選ばない実装パターン、その理由
よく選ぶ実装パターン
実際は、プロパティの型に応じてよく使うパターンが分かれている印象です。
筆者の経験上妥当なことが多い実装パターンを挙げます。
- 文字列型
- 空文字列
''を初期値する場合が多い。要件によってはnullにすることもある- 数値型
nullを初期値する場合が多い。要件によっては0にすることもある- 日付型
nullを初期値とする場合が多い- 配列型
- ゼロ件の配列
[]をセットすることが多い。要件によってはnullにすることもある- カスタム型
- その場でインスタンスをnewすることが多い。要件によっては
nullにすることもある一般化はできていません。
要件に応じ、プロパティ値が初期状態かどうかを判定したい場合は初期値を
nullにする設計に寄せる。
そうでなければ空っぽい値をとる設計に寄せる、というのは言えるかもしれません。// よく選ぶ実装パターンの例 class MyEntity { public name: string; public num: number | null; public date: Date | null; public messages: string[]; public subEntity: SubEntity; constructor(param: Partial<MyEntity>) { this.name = ''; this.num = null; this.date = null; this.messages = []; this.subEntity = new SubEntity(); Object.assign(this, param); } } // インスタンス生成時にパラメータオブジェクトから値を渡す使い方が好き const entity = new MyEntity({ name: 'Who bar', num: 0, date: new Date(1955, 1, 24), messages: ['who', 'bar'] });滅多にやらないパターン
逆に、以下の3パターンを好んで選ぶことはほとんどありません。
- B. 初期値は
undefinedだよ派
- 個人的には
undefinedを意図的に初期値とすることにあまりメリットが感じられません。nullの方が意図が感じられます- D. 意味のあるデフォルト値をセットしちゃう派
- 「なぜそれがデフォルト値であるべきなのか」という根拠を考えるのが難しく、それなら
nullや空値の方がマシに感じます- たとえば入力フォームで最初からデフォルトの値を表示させておきたいなどのケースでは、初期化時のパラメータから拾うか、インスタンス生成後に値をセットすることで対応させる方が自然に感じます。
- E. 絶対にコンストラクタ引数からセットするよ派
- インスタンス生成時に常に具体的な値が必要となるので不便な印象があります
- 投稿日:2019-07-19T20:40:01+09:00
プロパティの初期値をnullにしたりしなかったりの使い分け
ふと意識した途端、クラスをnewしたときにプロパティへ何を初期値として入れるべきなのかが段々分からなくなってきました。
自分の中で考えられうる実装のバリエーションを分類した上で、どう使い分けるべきなのか現状の考えをメモします。
A. 初期値は
nullだよ派
nullとのユニオン型を定義し、コンストラクタ内でnullをセットする実装。
- メリット
- 意味のある値がセットされている状態か、そうでないかが区別できる
- 懸念
- プロパティを使用する際、
nullの場合の分岐も実装しなければならないclass MyEntity { public name: string | null; public num: number | null; public date: Date | null; public messages: string[] | null; public subEntity: SubEntity | null; constructor() { this.name = null; this.num = null; this.date = null; this.messages = null; this.subEntity = null; } }B. 初期値は
undefinedだよ派
undefinedとのユニオン型を定義し、意図的にundefinedのままにしておく実装。
- メリット
- 意味のある値がセットされている状態か、そうでないかが区別できる
- 懸念
- プロパティを使用する際、
undefinedの場合の分岐も実装しなければならない- 値が
undefinedのとき、本当に存在しないプロパティなのか、値がセットされていないだけなのかが分かりにくそうclass MyEntity { public name: string | undefined; public num: number | undefined; public date: Date | undefined; public messages: string[] | undefined; public subEntity: SubEntity | undefined; constructor() { } }C. 初期値は空っぽい値だよ派
空っぽい値を明示的にコンストラクタ内でセットする実装。
- メリット
- ユニオン型がいらないので、型定義がシンプル
nullやundefinedのケースを想定した分岐が不要- 懸念
- 値がセットされていないのか、セットした値がたまたまデフォルト値と同じなのかが判別できない
(配列が初期値のままなのか、セットした値が実際にゼロ件なのかなど)- 文字列型と配列以外の型では、本当の意味で「空っぽい値」を定義できない(特に日付型)
class MyEntity { public name: string; public num: number; public messages: string[]; public subEntity: SubEntity; constructor() { this.name = ''; this.num = 0; // 0が空っぽい値というのはかなり強引…… this.messages = []; this.subEntity = new SubEntity(); } }D. 意味のあるデフォルト値をセットしちゃうよ派
明示的に意味のあるデフォルト値をコンストラクタ内でセットしてしまう実装。
- メリット
- 型定義がシンプル
nullやundefinedの状態を想定せずに実装できる- 前提が整っていれば、初期状態と値がセットされた状態を区別できる
- 懸念
- 定義したデフォルト値が偶然セットした値と一致していまう可能性がある
- デフォルト値のせいで無意味にメモリを消費するのでは
- 設計の難易度が余計に増すのでは
class MyEntity { public name: string; public num: number; public date: Date; constructor() { this.name = 'not set'; // 初期値は'not set'だという仕様にしてしまう this.num = -9999; // 初期値は-9999だという仕様にしてしまう this.date = new Date(1889, 0); // 初期値は1889年1月1日だという仕様にしてしまう } }E. 絶対にコンストラクタ引数からセットするよ派
コンストラクタの引数から値をとらせることを強制する実装。
- メリット
- 型定義がシンプル
- 初期値に悩む必要がない
nullやundefinedの状態を想定せずに実装できる- 懸念
- 「まだ具体的な値は分からないけどとりあえずインスタンスを生成したい」という場合に使いにくい
- 初期値をどうするのかという観点では、問題を一つ上のレイヤーへ先送りしただけ
class MyEntity { constructor( public name: string, public num: number, public date: Date, public messages: string[], public subEntity: SubEntity ) { } } const entity = new MyEntity('hoge', 1, new Date(), ['message1', 'message2'], new SubEntity());よく選ぶ実装パターンと選ばない実装パターン、その理由
よく選ぶ実装パターン
実際は、プロパティの型に応じてよく使うパターンが分かれている印象です。
筆者の経験上妥当なことが多い実装パターンを挙げます。
- 文字列型
- 空文字列
''を初期値する場合が多い。要件によってはnullにすることもある- 数値型
nullを初期値する場合が多い。要件によっては0にすることもある- 日付型
nullを初期値とする場合が多い- 配列型
- ゼロ件の配列
[]をセットすることが多い。要件によってはnullにすることもある- カスタム型
- その場でインスタンスをnewすることが多い。要件によっては
nullにすることもある一般化はできていません。
要件に応じ、プロパティ値が初期状態かどうかを判定したい場合は初期値を
nullにする設計に寄せる。
そうでなければ空っぽい値をとる設計に寄せる、というのは言えるかもしれません。// よく選ぶ実装パターンの例 class MyEntity { public name: string; public num: number | null; public date: Date | null; public messages: string[]; public subEntity: SubEntity; constructor(param: Partial<MyEntity> = {}) { this.name = ''; this.num = null; this.date = null; this.messages = []; this.subEntity = new SubEntity(); Object.assign(this, param); } } // インスタンス生成時にパラメータオブジェクトから値を渡す使い方が好き const entity = new MyEntity({ name: 'Who bar', num: 0, date: new Date(1955, 1, 24), messages: ['who', 'bar'] });滅多にやらないパターン
逆に、以下の3パターンを好んで選ぶことはほとんどありません。
- B. 初期値は
undefinedだよ派
- 個人的には
undefinedを意図的に初期値とすることにあまりメリットが感じられません。nullの方が意図が感じられます- D. 意味のあるデフォルト値をセットしちゃう派
- 「なぜそれがデフォルト値であるべきなのか」という根拠を考えるのが難しく、それなら
nullや空値の方がマシに感じます- たとえば入力フォームで最初からデフォルトの値を表示させておきたいなどのケースでは、初期化時のパラメータから拾うか、インスタンス生成後に値をセットすることで対応させる方が自然に感じます。
- E. 絶対にコンストラクタ引数からセットするよ派
- インスタンス生成時に常に具体的な値が必要となるので不便な印象があります
- 投稿日:2019-07-19T20:14:33+09:00
Prettierの文字数制限の確認
Prettierの
--print-widthは上限値だと思ってたのだけど、改行を嫌って極端に大きい数値を設定すると、逆に改行したいときにも纏められる(改行が消える)という学び。$ npx prettier for_test/test.js --write --print-width=800-import { - CollectionDashboard, - DashboardPlaceholder -} from "../components/collections/collection-dashboard/main"; +import { CollectionDashboard, DashboardPlaceholder } from '../components/collections/collection-dashboard/main';qちなみに俺は
120以上に設定したことがなかったので、気付かなかった。基本的にはdefaultの80であまり困らない勢。正直、Prettierに対しては雑に書いたコードが勝手に整形される以上の感想を持ったことがないので、「どうしてもone-linerにしたい」勢のストレスを理解してなかった。是非もないよネ!(レビューしたくないなとは思ったが)
Prettierに仕事をさせない
In prettier, print width is not a rule, it’s actually an input into the algorithm used to print code. Due to this it is impossible to turn off. You can set it to a very high number (not recommended) or use // prettier-ignore comments to disable prettier on a chunk of code.
https://github.com/prettier/prettier/issues/3468
printWidthはPrettierにとって必要な値で、ルールではない。
無効にすることは不可能なので、極端に高い値を指定するか(望まない整形になりやすくなるが)、// pretteir-ignoreを使うべし。gitignore syntaxで.prettierignoreを置くこともできる。// prettier-ignore @@ -17,42 +17,51 @@ import { } from "../components/collections/collection-dashboard/main"; // several lines -import { - CollectionDashboard, - DashboardPlaceholder -} from "../components/collections/collection-dashboard/main"; +import { CollectionDashboard, DashboardPlaceholder } from '../components/collections/collection-dashboard/main';
// prettier-ignore付けると、いい感じに該当箇所だけ無視してくれる(JSの場合、next-lineとかか範囲指定とかしなくても巧くいく風)。大抵の人は1行が長くなりすぎないように努力するので、大半のコードは80文字以下になる。エッジケースの対応でいちいち設定を変えてるとルールが意味を為さなくなるので、コメントで対応したほうが良いでしょう。つーかそれで極端に長い数値を指定された結果、普通のコードもone-linerになるようになって「Prettierはクソ」て苦情が来た訳ですが。知らんがな。
小技
What Prettier is not concerned aboutにもあるけど、
+と引用符で繋げていく系の文字列は整形対象になると細かく切られてしまうので、template literalに換えると良い。// long html strings -var quote_and_plus = '<div id="' + widgetContentID + '" style="' + widgetContentStyles + '">' + '</div>'; +var quote_and_plus = + '<div id="' + + widgetContentID + + '" style="' + + widgetContentStyles + + '">' + + '</div>'; var template_literal = `<div id="${widgetContentID}" style="${widgetContentStyles}"></div>`;エディタにエラーを出さない
変換されること自体はいいけど、書いてるときにエラー出されると気が散る、て意見もあり。
Prettier自体はエラー出さないので、eslint-plugin-prettierとか咬ましてる場合の、ESLintの警告ですね。なんで
rules指定すれば制御できます。"es6": true }, "rules": { - "prettier/prettier": "error" + "prettier/prettier": "warn" } }この記事は Corne Cherry で書きました。
- 投稿日:2019-07-19T19:40:35+09:00
JavaScriptでハッシュ関数(SHA256)を使う
はじめに
JavaScriptでハッシュ関数を使いたくて、いろいろな記事を見ていたのですが、node.jsでインポートして使うのが多かった印象でした。
しっかりとは私も理解していないのですが、JaveScriptでハッシュ関数(sha256)を使うことができたので、それを記事にしました。
ハッシュ値をWebページに出力まで
まずこのページへ飛んで、ダウンロードしてください。→jsSHA
そしてダウンロードしてきたフォルダを解凍するとjsSHA-masterというフォルダが手に入ると思います。
そのフォルダ内にあるsrcフォルダに入ると、「sha256.js」というファイルがあるので、それをこれから作る、「index.html」「main.js」ファイルと同じフォルダに置いてください。あとは以下のようにコードを書けばハッシュ化できるはずです。
index.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>sha256</title> </head> <body> <script type="text/javascript" src="sha256.js"></script> <script type="text/javascript" src="main.js"></script> <div id="test"></div> </body> </html>main.jswindow.onload=init_time; var shaObj = new jsSHA("SHA-256","TEXT"); var text = "ハッシュ関数"; function init_time(){ shaObj.update(text); document.getElementById("test").innerHTML=shaObj.getHash("HEX"); }index.htmlをブラウザで起動すると、上図のように表示されるはずです。
ここではハッシュ関数という文字列をハッシュ化しています。
- 投稿日:2019-07-19T17:41:29+09:00
進捗がなかったらメンターが現れるATOM
はじめに
進捗がなかったらメンターが現れて、僕たちを鼓舞してくれるATOMパッケージを作りました。
https://atom.io/packages/atom-mentor
https://github.com/iwamatsu0430/atom-mentorインスパイア元
進捗がなかったらメンターが現れるエディター#駆け出しエンジニアと繋がりたい pic.twitter.com/QGkOMIuTgp
— Nino (@d151005) 2019年6月28日https://www.youtube.com/watch?v=ZXsQAXx_ao0 (※音量注意)
何ができるのか
コードを書かずに一定時間経過すると、大音量で「Just Do It!!!」と叫ぶおじさんの動画を表示します。
動画の外側をクリックすれば消えますが、コードを書かずに一定時間経過するとまた表示します。
以下の設定を任意に変更する事もできます。
- メンターが出てくるまでの時間(デフォルト:60秒)
- 表示動画(デフォルト:インスパイア元)
技術解説
ATOMパッケージの作り方
パッと雛形を作れるコマンドがあります。(
Generate Package)作り方自体は公式ブログが詳しいです。
起動時に自動的に読み込まれるようにする
雛形のままだと
my-package:toggleといったコマンドを実行しないとパッケージが初期化されません。
メンターを呼び出すための操作が必要だとサボってしまう事があるかもしれません。それではせっかくのメンターも効果半減でしょう。
なのでATOM起動時にメンターが自動でスタンバイしてくれるようにします。
my-package:toggleを実行するとpackage.jsonのmainで指定されたスクリプトからactivateメソッドが実行されます。
package.jsonにはactivationCommandsという項目があるのですが、これを削除する事でactivateメソッドが起動時に自動で実行されるようになります。エディターイベント監視
ATOMにおける進捗とはなにか?今回はこれを「コードを書いているか」としました。
なのでエディタで何かしら編集したときのイベントをキャッチする必要があります。今回はWorkspace APIのobserveTextEditorsメソッドを利用します。
ここで得られるTextEditorクラスのonDidChangeを使う事で「コードを書いているか」を検知できます。atom.workspace.observeTextEditors(editor => { editor.getBuffer().onDidChange(() => { // コードを書いたと判定 }); });YouTubeの呼び出し
メンターにどのように現れてもらうかについては悩みどころでしたが、手っ取り早くYouTubeから来てもらう事としました。
YouTubeでは「共有」ボタンから埋め込み用のiframeコードを得ることができます。これをちょっといじって自動再生できるようにします。この記事を参考にしました。
<iframe src="https://www.youtube.com/embed/ZXsQAXx_ao0?controls=0&autoplay=1&loop=1&playlist=ZXsQAXx_ao0&muted=1" frameborder="0" allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"></iframe>設定を自由に変更できるようにする
メンターは60秒進捗がないと現れるようにしてみましたが、人によっては「もっと厳しくしてほしい!」と思われるかもしれません。また、字幕付きの動画じゃないと「何言ってるかわからない!」という事があるかもしれません。
なのでユーザがパラメータを自由に設定できるようにします。ATOMパッケージでは
package.jsonでmainに指定されているスクリプトがconfigという項目をエクスポートする事でユーザーが編集可能な設定を定義できます。(詳しくはドキュメント参照)config: { video: { title: "Video ID", description: "Set YouTube ID", type: "string", default: "ZXsQAXx_ao0", order: 1, }, showSeconds: { title: "Show Seconds", description: "Waiting time for your mentoring", type: "integer", default: 60, order: 2, }, },設定の値を使いたいときは以下のように呼び出せば大丈夫です。
atom.config.get('my-package.showSeconds')今後について
今回は楽をするためにYouTubeを利用しましたが、インスパイア元のようにメンターとエディターが調和する姿を目指したいと考えています。なので以下の2機能は開発したい気持ちはあります。
- 透過(クロマキー)
- この記事が参考になりそう
- 字幕表示
- 字幕がないとインパクトがあまりない事に気づいた
- 嘘字幕で遊べそう
おわりに
思ったより邪魔なので作ってて楽しかったです。
メンターはアメとムチの使い分けが重要であると学びました。ちなみに
VSCode版もあるそうです
https://marketplace.visualstudio.com/items?itemName=dyuji.justdoitextension
- 投稿日:2019-07-19T16:59:43+09:00
JavaScriptでstring->numberへのcastは2種類あるので使い分ける
- Number()
- parseInt
の2種類ある
挙動に違いは以下
Number()とparseInt()の違いconsole.log(Number("1234")); // 1234 console.log(parseInt("1234")); // 1234 console.log(Number("asdf")); // Nan console.log(parseInt("asdf")); // Nan ここまでは同じ結果 console.log(Number("")); // 0 空の文字列のとき0 console.log(parseInt("")); // Nan からの文字列のときNan例えば
JSON.stringifyでこれらを変換する場面で結構な違いになるconst obj = {foo: parseInt("")}; console.log(JSON.stringify(obj)); // {"foo": null} obj.foo = Number(""); console.log(JSON.stringify(obj)); // {"foo": 0}自分のイメージ的には
""をnumberにcastしたときはnull値になって欲しい気がするので、基本的にはparseInt()を使ったほうが意図しない変換結果になることを防げる気がするまた、
parseIntは以外と名前の通りいろいろやってくれるconsole.log(parseInt("20px")); // 20 いい感じにparseしてくれる console.log(parseInt("Jackson5")); // NaN でも後方にある場合はだめ console.log(Number("20px")); // Nan // 基数指定できるのもparseInt console.log(parseInt("0x100", 16)); // 256参考
- 投稿日:2019-07-19T16:47:34+09:00
【Rails on Rails】.envの環境変数をjsファイル内で利用する方法
はじめに
Rails内で環境変数を.envファイル内に書いておくと、ENV["環境変数名"]で利用することが出来ます。
しかし、erbファイル内では利用することが出来ますが、jsファイルで利用することが出来ません。
そこで、jsファイル内で環境変数を利用する方法を解説します。
gemのインストール
以下の2つのgemをインストールします。
Gemfileに以下の2つのgemを記入し、
Gemfilegem 'gon' gem 'dotenv-rails'bundle installします。
bundle install.envファイルの作成作成
jsファイル内で利用したい、環境変数を.envファイルに記入します。
.envKEY = "xxx".gitignoreファイルを編集
念の為、GithubなどにAPI KEYなどの環境変数が公開されていように設定します。
.envに以下の1行を追加します。.gitignore/.envapplication.html.erbの編集
application.html.erbのhead内に以下の1行を追加します。
application.html.erb<%= include_gon %>
【注意】javascriptより上に書くこと
コントローラーの編集
環境変数を利用したいコントローラーのメソッドに追記。
xxx_controller.erbdef xxx gon.xxx_key = ENV['KEY'] end
KEYは.envに記述したもの
jsファイルの編集
xxx.jsconst KEY = gon.xxx_key;// 環境変数これでjsファイル内で環境変数を利用することが出来ます。
参考リンク
- 投稿日:2019-07-19T16:06:36+09:00
[Rails]C3.js × gonでチャートを表示する
できるだけ簡単に、かつクオリティの高いチャートを表示したい・・・。
そんなあなたにはC3.jsがオススメです。
また、データベースやコントローラーにあるデータを表示したい場合はgemgonを使うと便利です。今回は、C3.jsとgonを使った以下のような棒グラフの描画方法を紹介します。
環境
バージョン
$ ruby -v ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin18] $ rails -v Rails 5.2.3導入gem
gonを導入します。gem 'gon' gem 'haml-rails'ビューにはhamlを採用しています。
ライブラリとgonの読み込み
C3.jsはD3.jsのラッパーライブラリーなのでC3.jsと一緒にD3.jsを読み込みます。
今回はCDNを使って読み込むことにしました。(もちろん、gemとして読み込むこともできます。RailsでC3.jsを使う)
また、gonを適用するためにheadタグの中で以下のように追記します。application.html.haml# jsファイルよりも前に読み込む = Gon::Base.render_data %link{ href: "https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.7/c3.css", rel: "stylesheet"} %script{src: "https://d3js.org/d3.v4.min.js", type: "text/javascript"} %script{src: "https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.7/c3.js", type: "text/javascript"}ビュー
チャートを挿入したい要素にIDを指定しておきます。
html.haml.container #bar / ここにチャートを挿入コントローラー
gon.◯◯という変数に値を代入することでjsファイルで呼び出すことができます。controller.rbgon.x = ["x","07/13","07/14","07/15","07/16","07/17","07/18","07/19"] gon.cash = ["現金",2000000,1000000,1000000,1000000,1000000,1000000,1000000] gon.crypto = ["仮想通貨",8000000,9000000,10000000,8000000,6000000,4000000,2000000]わかりやすくするため値を直接代入していますが、
コントローラーでDBの情報を取得してjsに変数を渡したいときに利用すると便利です。jsファイル
同様に
gon.◯◯とすることでコントローラで定義した変数を取り出すことができます。window.onload = function() { // 棒グラフを挿入 var barGraph = c3.generate({ bindto: '#bar', // 挿入する要素のIDを指定(デフォルトは#chart) data: { x : 'x', columns: [ // コントローラーで定義した変数の呼び出し gon.x, //x軸のメモリに表示する文字列 gon.cash, // 値1 gon.crypto, // 値2 ], groups: [ // 値1と値2を連結させた棒グラフを表示 ['現金', '仮想通貨'] ], type: 'bar' // チャートタイプを指定(今回は棒グラフ) }, axis: { x: { type: 'category' // x軸のメモリ(今回は日付)を読み込むために必要 }, y: { label: { // y軸のラベルを追加 text: '価格 / 円', position: 'outer-middle' }, tick: { // 3桁ずつ「,」を挿入 format: d3.format(",") } } }, // チャートの余白を指定できる padding: { top: 10, bottom: 0 } }); }指定できるチャートタイプやその他設定は公式リファレンスがわかりやすいです。C3.js
gonの仕組み
application.html.hamlのheadタグの中で
Gon::Base.render_dataを記述した場所がどう表示されているかを確認してみます。html<script> //<![CDATA[ window.gon={}; gon.x=["x","07/13","07/14","07/15","07/16","07/17","07/18","07/19"]; gon.cash=["現金",2000000,1000000,1000000,1000000,1000000,1000000,1000000]; gon.crypto=["仮想通貨",8000000,9000000,10000000,8000000,6000000,4000000,2000000]; //]]> </script>
window.gon={}とすることでgonというグローバル変数を定義し、空のオブジェクトを代入して初期化しています。(グローバル変数はwindowオブジェクトのプロパティです)
そして、コントローラーで指定した値をgonのプロパティとして代入しています(windowは省略可能)。
これにより、jsファイルで変数を読み込むことが可能になります。参考
- 投稿日:2019-07-19T15:40:53+09:00
Clipkitでユーザーがログイン中か判定する方法
はじめに
以下のJSを
<script>タグで囲んでテンプレートに記述、またはファイルとしてアップロード・テンプレートでの読み込みでユーザのログイン判定ができるようになりました。Clipkitのバージョン9.4.0以降より実装可能です。実装
login.jsvar request = new XMLHttpRequest(); request.open('GET', '/users/me.json', true); request.onload = function() { if (request.status >= 200 && request.status < 400) { var data = JSON.parse(request.responseText); if (data.user) { // ユーザがログイン中の場合に行いたい処理 } } }; request.send();サンプルコード
Clipkitの標準テーマClassicにて、以下のサンプルコードでログイン中にヘッダーの「マイページ」をログイン判定で表示変更しています。ログインしていない場合は「無料会員登録」、ログインしている場合は、「マイページ(ユーザー名)」と表示しています。ユーザー名は
data.user.screen_nameで取得できます。sample.jsvar request = new XMLHttpRequest(); request.open('GET', '/users/me.json', true); request.onload = function() { if (request.status >= 200 && request.status < 400) { var data = JSON.parse(request.responseText); var myPage = $("ul.nav.navbar-nav.navbar-right li a"); if (data.user) { myPage.text("マイページ" + "(" + data.user.screen_name + ")"); } else { myPage.text("無料会員登録"); } } }; request.send();以下の例は、ユーザーネームが「Banri」となっているので、ログイン中は「マイページ(Banri)」と表示されています。
実装前
実装後・ログイン前
実装後・ログイン時
- 投稿日:2019-07-19T15:05:21+09:00
inputとbuttonで作るボタンの違い
はじめに
画面を作っていくなかで、inputタグを利用したボタンの大きさを変更できず、代わりにbuttonを利用したことがあったため、以下の2つの違いを自分なりに書いてみた。
<input type="button" value="ボタン"> <button type="button">ボタン</button>実際に使ってみる
まず、以下のように配列から乱数を生成し取り出した値をURLに埋め込んだものを画面に表示してみます。
<script type="text/javascript"> const number = [11111, 22222, 33333, 44444, 55555]; randNum = 11111; randNum = number[Math.floor(Math.random() * number.length)]; function transition(randNum) { document.location.href = `https://www.xxxxx.com/vvvvv/${randNum}/`; }; </script>1. inputでの書き方
<input type="button" value="ボタン" style="width:200px;height:50px" onClick={transition(randNum)}/>2. buttonでの書き方
<button type="button" value="button" style="width:200px;height:50px" onClick={transition(randNum)}>ボタン</button>3. 表示結果
結論としては、どちらの書き方でも問題なく画面推移するボタンを作ることができた。
しかし、inputの方はボタンの横幅を200pxにすることはできたが、縦幅をデフォルト値から変更することができなかった。
4. 結論
CSSを触らずにサイズ等を変更したい場合はbuttonタグを利用する。
場合によって臨機応変に使い分けていく必要がありそう。
- 投稿日:2019-07-19T15:03:30+09:00
【JavaScript】要素の取得方法(getElement、querySelector)
はじめに
JavaScriptにおいて、HTMLの要素を取得するには様々な方法があります!
今回はその要素の取得方法に関してまとめました!要素を1つだけ取得する
document.getElementById()
document.getElementById()は引数にID名を入れることで、そのIDの要素を取得することができます。
同じID名が複数存在する場合には最初の要素のみを取得します。
以下が簡単な例です。
htmlのid='sample'のdiv要素を、jsファイルにて取得して、定数sampleに代入しています。test.html<div id='sample'></div>test.jsconst sample = document.getElementById('sample')document.getElementsByClassName()
先ほどの
document.getElementById()ではID名から要素を取得しましたが、
document.getElementsByClassName()ではクラス名を指定して要素を取得できます。
以下が例です。test.html<div class='sample'></div>test.jsconst sample = document.getElementsByClassName('sample')document.querySelector()
こちらも引数にID名やクラス名を入れることで、要素を取得することができます!
先ほどのgetElementById()やgetElementsByClassName()では、それぞれID名かクラス名どちらかしか指定できませんでした。
しかしdocument.querySelector()ではID名もクラス名も両方指定できます!
しかし例えばID名sampleを指定したい場合、先ほどのdocument.getElementById()とは違って、引数には#sampleと書かなければなりません。
クラス名の場合も同様で.sampleと書きます!
CSSでセレクタを記述する際と同じ書き方です。
なぜかというと、document.querySelector()は、IDだけではなく、クラス名も指定して要素を取得することが出来てしまうからです。
document.getElementById()と同じように引数内を'sample'と書いてしまうと、ID名を指しているのかクラス名を指しているのかわかりません。
以下が例です。test.jsconst sample = document.querySelector('#sample')要素を複数取得する
document.querySelectorAll()
これまでご紹介したものは全て、要素を一つのみ取得するプロパティでした!
document.querySelectorAll()を使用すると、引数で指定したID名、もしくはクラス名と一致する全ての要素を
取得することができます。
以下が例です。test.html<div class="hoge"></div> <div class="foo"></div> <div class="hoge"></div>test.jsconst hoge = document.querySelectorAll('.hoge')3つ並んでいるdiv要素のうち、1番目と3番目の要素が取得できます。
余談①
今回は全て、documentから要素を探しました。
(document.querySelectorやdocument.getElementsByClassName等)
しかし、documentではなくelementを指定して要素を取得することもできます。
以下が例です。test.html<ul class="hoge"> <li class="foo"></li> <li></li> <li></li> </ul>test.jsconst sample = hoge.getElementsByClassName('foo')上記の例では、hogeクラスの中にある、fooクラスの要素を取得しています。
余談②
特定のIDもしくはクラス名を指定して、一つの要素を取得したい場合。
getElementByとquerySelectorどちらを使用するべきなのでしょうか。
基本どちらでも良いのですが、
getElementByの方がquerySelectorよりも速度が早いらしいです。
でも人がわからないレベルらしい。。
少しでも早くしたいって方はgetElementByを使った方が良いかも??さいごに
今回はID名やクラス名を指定しての取得の仕方のみ解説しましたが、他にもタグ名を指定して取得する方法などがあります。
以下の記事がとても詳しいので、深掘りしたい方にはおすすめです!
https://qiita.com/amamamaou/items/25e8b4e1b41c8d3211f4
- 投稿日:2019-07-19T14:49:52+09:00
Google Tag Manager でコンバージョンに至るまでの経過時間を Google Analytics にイベント送信する方法
はじめに
Google Analytics のコンバージョン設定をしている方はいると思います。 コンバージョンに至るまでのファネル内でどの程度時間がかかったか という指標もコンバージョン率やユーザー体験に影響があるため、経過時間を知りたいというニーズはあると思います。
Google Tag Manager (GTM) を使うと、ソースコードを逐一いじらずに色んな解析ができます。今回 Google Tag Manager を使って Google Analytics に経過時間をイベント送信してみたので、備忘録としてまとめておきます。
集計イメージ
以下のように Google Analytics 上でコンバージョンに至るまでの秒数が記録されます。期間指定で施策前後の平均値を比べたり、セカンダリディメンジョンで詳細な分析をしたりできます。
方針
今回、ソースコードは変更せず、全て Google Tag Manager で完結させたかったので、ユーザー行動に沿って以下のように設定しました。
- 時間計測を開始したいタイミングで GTM のカスタム変数に現在時刻を記録
- コンバージョン時にカスタム JavaScript を使って、現在時刻と 1 で設定した時刻との差分をイベント送信
設定方法
タグとトリガーと変数をそれぞれ 2 個ずつ作成します。まず変数から説明します。
変数の設定
Order Start Timestamp と Order Duration という変数を設定しています(今回申込画面にかかる時間を測定したので Order 系の文字列があるのは気にしないでください)。
Order Start Timestamp は データレイヤー変数 にしてデフォルト値を 0 としておきます。この値に時間を代入するのはトリガーで行います(後述します)。
Order Duration は カスタム JavaScript にして Order Start Timestamp との差分を計算して返します。秒変換するために 1000 で割りました。
function () { var now = new Date().getTime(); var diff = parseInt((now - {{Order Start Timestamp}}) / 1000); return diff; }Order Duration をイベント送信時にセットします。
トリガーの設定
次にトリガーの設定について説明します。
トリガーには時間計測開始のタイミングと、コンバージョンのタイミングの 2 つを設定します。これはサービスによって異なる設定になるはずなので詳細は説明しません。
ちなみに SPA で特定のページアクセスをトリガーとしたい場合には、トリガータイプにページビュー系のものを選ばずに 履歴の変更 を選ぶ必要があるようです。
タグの設定
最後にタグの設定について説明します。
時間の計測を開始するタイミングとコンバージョンのタイミングの 2 つにタグを設定します。
時間の計測を開始するタイミングのタグ設定を設定します。 タグの種類を カスタム HTML にして script タグ内で dataLayer 変数に値を設定しています。変数は上の手順で作った Order Start Timestamp を使います。トリガーは上の手順で作った時間計測開始のトリガーを選択します。<script> /** * New Order Timestamp 変数に新規申し込みページにアクセスしたタイムスタンプを記録 * 申込み完了時にこのタイムスタンプとの差分を送信するために利用 **/ dataLayer.push({'Order Start Timestamp': new Date().getTime() }); </script>コンバージョンのタイミングのタグを設定します。 Google アナリティクスのイベントを選択し、値に上の手順で作った Order Duration を選択します。
以上で設定完了です。プレビューで状態確認して問題なければ公開しましょう。
まとめ
カスタム HTML やカスタム変数、カスタム JavaScript などをうまく使うことで、GTM だけで管理できる変数を定義して、特定のタイミングで作成・更新・取得などができることが分かりました。一見コードで書かないと難しそうな処理も、一度 GTM でできるか検討してみると良さそうです。
- 投稿日:2019-07-19T12:01:18+09:00
JavaScriptで[ 0, 1, 2, 3, 4 ]のような連番の配列を生成する方法
JavaScriptで
[ 0, 1, 2, 3, 4 ]のような連番の配列を生成する方法です。[...Array(5)].map((_, i) => i) //=> [ 0, 1, 2, 3, 4 ]spread operator
...と、arrow function=>を使っているためES6移行で動作します。解説
上のコードは3つのパートに分けられます。
パート1:
Array(5)これは要素数が5つ(=スロットが5つあるだけ)の空っぽの配列を作る。
undenfinedが5つ入るわけでない。Array(5) //=> [ <5 empty items> ]パート2:
[...Array(5)]の[... ]の部分要素数が5つある配列をspread operatorにかけて、
undefined5つの配列に変形している。[...Array(5)] //=> [ undefined, undefined, undefined, undefined, undefined ]パート3:
.map((_, i) => i)
mapメソッドでundefined5つの配列を、インデックス番号が値になった配列に変形している。[ undefined, undefined, undefined, undefined, undefined ].map((_, i) => i)応用:
[ 1, 2, 3, 4, 5 ]のように1始まりにしたい場合[...Array(5)].map((_, i) => i + 1) //=> [ 1, 2, 3, 4, 5 ] // ^^^ mapのコールバック関数で+1の演算をすればよい
- 投稿日:2019-07-19T09:17:52+09:00
[Vue.js] オリジナルコンポーネントをnpmに公開する方法
環境
この記事を書くのに使った環境は以下の通りです。
$ npm -version 6.9.0 $ vue --version 3.8.3Vue CLI のインストール
Vue CLIをグローバルインストールします。
$ npm install -g vue-cli ~~中略~~ + @vue/cli@3.8.3 added 877 packages from 554 contributors in 213.92sこの記事で紹介しているnpmへの公開方法は、Vue CLI 3以上のバージョンが必要なので、イントールされたバージョンを確認してください。もし2.x系のVue CLIがインストールされている場合、一度アンインストールしてからインストールしてください。
公開するコンポーネントのVueプロジェクトを作成
Vueのプロジェクトを作成するフォルダに移動します。
$ cd path/to/dir次に、プロジェクトを作成します。コマンド入力後、質問形式で設定を聞かれるので答えます。
$ vue create <component-name> Vue CLI v3.8.3 ? Please pick a preset: default (babel, eslint) ? Pick the package manager to use when installing dependencies: NPMプロジェクトの作成が完了したら、とりあえず動かしてみましょう。
$ cd <component-name> $ npm install $ npm run serveブラウザで
http://localhost:8080にアクセスします。フォルダ構成
この記事では、最終的に以下のようなフォルダ構成になるように、コンポーネントおよび必要ファイルを作成していきます。
├── src │ ├── components │ │ └── MyComponent.vue //(1)公開するコンポーネント │ ├── library-main.js //(2)使われる時に自動でvue.useする為のwrapper ├── package.json //(3) プロジェクトの情報 ~~中略~~
vue createでプロジェクトを作成すると、上で書いた必要なファイル以外にも、色々とファイルが作成されますが、消す必要はなく、ほっておいても大丈夫です。インストールラッパーを作成する
コンポーネント使用時に自動的に
Vue.use()を行うインストールラッパーを作成します。以下はVueの公式サイトに掲載されているサンプルコードです。基本的にそのまま使えますが、(1)(2)の部分だけは、公開するコンポーネント名に置き換えてください。library-main.js// コンポーネントのインポート import component from './components/MyComponent.vue'; //(1)Vueのファイル名に変更 // Vue.use() によって実行される install 関数を定義 export function install(Vue) { if (install.installed) return; install.installed = true; Vue.component('MyComponent', component); //(2)コンポーネント名を変更 } // Vue.use() のためのモジュール定義を作成 // Create module definition for Vue.use() const plugin = { install, }; // vue が見つかった場合に自動インストールする (ブラウザで <script> タグを用いた場合等) let GlobalVue = null; if (typeof window !== 'undefined') { GlobalVue = window.Vue; } else if (typeof global !== 'undefined') { GlobalVue = global.Vue; } if (GlobalVue) { GlobalVue.use(plugin); } // (npm/webpack 等で) モジュールとして利用させるためコンポーネントを export する export default component;公開するコンポーネントを実装する
npmに公開するコンポーネントを実装します。公開するからと言って変わった点はなく、普通にコンポーネントを実装していきます。
MyComponent.vue<template> ~~コンポーネントのテンプレート~~ </template> <script> export default { ~~コンポーネントの実装~~ } </script> <style scoped> ~~コンポーネントのスタイル~~ </sytle>package.jsonを編集
{ "name": "my-component", //(1)npmに公開するコンポーネント名 "version": "0.1.0", "private": false, //(2)npmに公開する為、falseに変更 "main": "dist/my-component.common.js", //(3)エントリポイント "unpkg": "./dist/my-component.umd.min.js", //(4)unpkg公開用 "jsdelivr": "./dist/my-component.umd.min.js", //(5)jsdelivr公開用 "typings": "dist/types/index.d.ts", "license": "MIT", //(6)ライセンス "author": "<作成者>", //(7)作成者 "files": [ "dist" ], //(8)npmに公開するフォルダやファイル "keywords": ["Vue", "Vue.js", "xxxx", "xxxx"], //(9)パッケージマネージャー用のキーワード "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint", "build-bundle": "vue-cli-service build --target lib --name my-component ./src/library-main.js" //(10)npmに公開するライブラリ生成用のコマンド }, "dependencies": { "core-js": "^2.6.5", "vue": "^2.6.10" }, ~~中略~~ }npmのサインアップ
npmのアカウントを持っていない場合は、以下のURLからアカウントを登録して、サインアップします。
npmにログイン
以下のコマンドを入力して、npmにログインします。コマンドを入力すると、ユーザ名/パスワード/メールアドレスを聞かれますので、サインアップ時に登録した内容を打ち込みます。
$ npm loginビルドをする
package.jsonに追加したライブラリ生成用のコマンドを入力して、ビルドを行います。
$ npm run build-bundlenpm publishで公開する
以下のコマンドで、npmにコンポーネントを公開します。
$ npm publishhttps://www.npmjs.com/にアクセスにして、自分が公開したコンポーネントが検索できるか確認してみましょう。
README.md を書こう
テンプレートで作成された
README.mdをnpmに公開すると、下のように寂しい感じになっているので、セットアップ方法やAPIなどの説明を書くようにしましょう。変更した内容を公開する
バグ修正・新機能追加・README.mdの修正など、作成したコンポーネントを変更した場合、
npm publishで再度公開します。npmでは同じバージョンでpublishするとエラーになるので、バージョンを上げてからnpm publishをする必要があります。バージョンを上げる方法には、以下の2つのやり方があります。
[1] npm versionコマンドを使う
npmには、package.jsonのバージョンを上げるコマンドが容易されています。
npm version [option]
[option]には以下を指定します。指定するオプションによって、バージョンの上がり方が違います。
option 実行後のバージョン major 2.0.0 minor 1.1.0 patch 1.0.1 premajor 2.0.0-0 preminor 1.1.0-0 prepatch 1.0.1-0 prerelease 1.0.1-0 [2] package.jsonを直接編集する
上のコマンドでバージョンを上げるのは必須ではない為、package.jsonのversionを手動で書き換えてもOKです。
package.json{ "version": "0.1.1", }npmに公開したコンポーネントの使い方
npmに公開すれば、あとは普通にnpmのコマンドで、コンポーネントがインストールできます。
コンポーネントのインストール
npm i my-component --saveコンポーネントを使う
importにコンポーネントとCSSを入れてあげると、作成したコンポーネントが使えます。
<template> <my-component/> </template> <script> import MyComponent from 'my-component' import 'my-component/dist/my-component.css' export default { components: { MyComponent } } </script>さいごに
Vue.jsでオリジナルコンポーネントをnpmに公開する方法と、公開した後に、実際にコンポーネントを使う手順を紹介してきました。VUE-CLI3を簡単に公開できて楽です。
- 投稿日:2019-07-19T07:27:13+09:00
予習 Vue 3.x vue-function-api の 書き方一覧 with TypeScript
最近のVue.jsの話題といえば、次期メジャーバージョンアップのVue3.xで採用が予定され、現在RFCとなっているvue-function-api ですね。親しみがある今のオブジェクトベースのAPIとはかなり違うので賛否両論あるようですが、実際に使ってみるとTypeScriptで書きやすく個人的には好みです。
この記事ではTypeScriptでのvue-function-apiの書き方を紹介します。(随時追加予定)
自分もまだまだ勉強段階なので間違ってる箇所あれば、気軽にコメントで指摘お願いします。※ サンプルコードでは、明示的に型アノテーションつけていますが、実際には自動的に型推論される箇所が多いです。
※ Vue3.x では従来のオブジェクトベースのAPIはサポートされるので、バージョンアップの際に置き換えが必ず必要というわけではないです。vue-function-apiの導入
vue-function-apiはpluginとして提供されているので、vue2.x でも使用可能です。
https://github.com/vuejs/vue-function-apiパッケージの追加
$ yarn add vue-function-apipluginの設定
main.tsimport Vue from 'vue' import { plugin } from 'vue-function-api' Vue.use(plugin)以下にvue-function-api & typescript の練習用に作ったTodoアプリがあるので、実際の使い方はそちら参考にしてください。
https://github.com/kawamataryo/sandbox-vue-functional-apicomponetの作成 -
createComponent(),setup()vueのSFCの基本単位となるcomponentの作成は、
createComponent<T>()を使います。
そして慣れ親しんだvueのリアクティブなデータや、メソッド定義はcreateComponent<T>()の引数オブジェクト内でsetup()メソッドを定義して記述します。
また、template側で使う関数、変数はsetup()内で変数に代入し、オブジェクトとしてreturnする必要があります。従来
export default { methods: { greet: function() { return "hello world" } } }vue-function-api(TypeScript)
export default createComponent({ setup() { const greet: string = () => { return "hello world"; }; return { greet }; } });型定義
declare type ComponentOptionsWithSetup<Props> = Omit<ComponentOptions<Vue>, 'props' | 'setup'> & { props?: Props; setup?: (this: undefined, props: { [K in keyof Props]: Props[K]; }, context: Context) => object | null | undefined | void; }; export declare function createComponent<Props>(compOpions: ComponentOptionsWithSetup<Props>): ComponentOptions<Vue>;リアクティブなデータの保持 - value()
オブジェクトベースのAPIでの
data(){return {}}に相当するのがvalue()です。
vue-function-apiではオブジェクトでまとめず要素ごとに個別に定義します。そしてTypeScriptでは、Wrapper<T>を使って型定義します。
また、template側で使う変数は関数と同様setup()内で変数に代入し、オブジェクトとしてreturnする必要があります。従来
export default { data() { return { name: "taro", address: { postCode: 1234, city: "tokyo" } }; } };vue-function-api(TypeScript)
type Address = { postCode: number; city: string; }; export default createComponent({ setup() { const name: Wrapper<string> = value("taro"); const address: Wrapper<Address> = value({ postCode: 1234, city: "tokyo" }); return { name, address }; } });型定義
export declare function value<T>(value: T): Wrapper<T>;算出プロパティ -
computedリアクティブな依存関係にもとづきキャッシュされる算出プロパティは、
computed()関数で定義します。従来
export default { data() { return { message: "Hello world"; } }, computed: { reversedMessage: function () { return this.message.split("").reverse().join("") } } }vue-function-api(TypeScript)
export default createComponent({ setup() { const message: Wrapper<string> = value("hello"); const reverseMessage: Wrapper<string> = computed(() => { return message.value.split("").reverse().join("") }); return { message, reverseMessage }; } });型定義
export declare function computed<T>(getter: () => T, setter?: (x: T) => void): Wrapper<T>;親から子への値の受け渡し -
props親から子コンポーネントに値を受け渡す
propsは、`createComponentのジェネリクスとしてporpsの型を渡して、引数オブジェクトのpropsのプロパティの値として文字列配列をanyにupキャストした上で型定義が必要です。
型定義見ると、ジェネリクスと Mapped typesを使っていて難解ですね。従来
export default { props: { name: String, age: Number }, methods: { userProfile: function { return `名前: ${name}, 年齢: ${age}` } } }vue-function-api(TypeScript)
type Props = { name: string; age: string; }; export default createComponent<Props>({ props: (["name", "age"] as any) as PropType<Props>, setup(props) { const userProfile = (): string => { return `名前: ${props.name}, 年齢: ${props.age}` } return { userProfile }; } });型定義
export declare type PropType<T> = T; declare type ComponentOptionsWithSetup<Props> = Omit<ComponentOptions<Vue>, 'props' | 'setup'> & { props?: Props; setup?: (this: undefined, props: { [K in keyof Props]: Props[K]; }, context: Context) => object | null | undefined | void; };context関数 -
emit,refs,slot...子から親へのデータ通信の際のイベント発火に使うemitなどのインスタンスメソッドは、
setup()メソッドが第2引数で受け取る、contextオブジェクトに定義されています。それを従来と同様の書き方で使えば大丈夫です。
例 emit従来
export default { methods: { emitGreet: function() { this.$emit('greet', 'Hello') } } }vue-function-api(TypeScript)
export default createComponent({ setup(props, context: Context) { const emitGreet = () => { context.emit("greet", 'Hello'); }; } });型定義
export interface Context { readonly parent: Vue; readonly root: Vue; readonly refs: { [key: string]: Vue | Element | Vue[] | Element[]; }; readonly slots: { [key: string]: VNode[] | undefined; }; readonly attrs: Record<string, string>; emit(event: string, ...args: any[]): void; }変更の監視 -
watch()カスタムウォッチャの
watch()の定義は、setup()メソッド内のwatch関数で定義します。
value側で適切に型定義をしていれば、特に型アノテーションは必要なく、型推論されます。従来
export default { data() { return { count: 0 } }, watch: { count: function (newVal, oldVal) { if (newVal > oldVal) { console.log("カウントアップ"); } else { console.log("カウントダウン"); } } }, }vue-function-api(TypeScript)
export default createComponent<Props>({ setup() { const count: Wrapper<number> = () => value(0); watch(count, (newVal, oldVal) => { if (newVal.value > oldVal.value) { console.log("カウントアップ"); } else { console.log("カウントダウン"); } }); return { count }; } });型定義
declare type watcherCallBack<T> = (newVal: T, oldVal: T) => void; declare type watchedValue<T> = Wrapper<T> | (() => T); declare type FlushMode = 'pre' | 'post' | 'sync'; interface WatcherOption { lazy: boolean; deep: boolean; flush: FlushMode; } export declare function watch<T>(source: watchedValue<T>, cb: watcherCallBack<T>, options?: Partial<WatcherOption>): () => void; export declare function watch<T>(source: Array<watchedValue<T>>, cb: watcherCallBack<T[]>, options?: Partial<WatcherOption>): () => void;Lifecycle Hooks
vueのライフサイクルに合わせて処理を行いたい場合に使うライフサイクルフックは、
setup()メソッド内で関数として呼び出します。
処理はコールバックで渡します。従来
export default { created: function() { // 何らかの処理 }, mouted: function() { // 何らかの処理 }, beforeDestroy: funciton() { // 何らかの処理 } }vue-function-api(TypeScript)
export default createComponent({ setup() { onCreated(() => { //何らかの処理} }) onMounted(() => { //何らかの処理} }) onBeforeDestroy(() => { //何らかの処理} }) } });型定義
export declare const onCreated: (callback: Function) => void; export declare const onBeforeMount: (callback: Function) => void; export declare const onMounted: (callback: Function) => void; export declare const onBeforeUpdate: (callback: Function) => void; export declare const onUpdated: (callback: Function) => void; export declare const onActivated: (callback: Function) => void; export declare const onDeactivated: (callback: Function) => void; export declare const onBeforeDestroy: (callback: Function) => void; export declare const onDestroyed: (callback: Function) => void; export declare const onErrorCaptured: (callback: Function) => void; export declare const onUnmounted: (callback: Function) => void;参考
- 投稿日:2019-07-19T07:11:01+09:00
【Programming News】Qiitaまとめ記事 July 18, 2019 Vol.4
筆者が昨日2019/7/18(木)に気になったQiitaの記事をまとめました。昨日のまとめ記事はこちら。
Java
Python
- Beginner
- Tips
- K近傍法
Node.js
Nuxt.js
Swift
Laravel
MySQL
- Beginner
Git
- Beginner
Visual Studio
AWS
- Beginner
- Tips
- Cognito
- S3
- EC2
- Elasticsearch
Docker
Google Apps Script
- Tips
Develop
Raspberry
- Tips
- Apps
UML
- PlantUML
Go言語
- Beginner
- Tips
R言語
awk
LaTex
Redmine
- Tips
更新情報
- 投稿日:2019-07-19T01:32:38+09:00
JavaScript FullCalendar demo timeZone確認
JavaScript の FullCalendar の demo をもとに timeZone を確認
demo は以下より取得
https://fullcalendar.io/docs/getting-started<!DOCTYPE html> <html> <head> <meta charset='utf-8' /> <link href='../packages/core/main.css' rel='stylesheet' /> <link href='../packages/daygrid/main.css' rel='stylesheet' /> <script src='../packages/core/main.js'></script> <script src='../packages/interaction/main.js'></script> <script src='../packages/daygrid/main.js'></script> <script> document.addEventListener('DOMContentLoaded', function() { var calendarEl = document.getElementById('calendar'); var calendar = new FullCalendar.Calendar(calendarEl, { plugins: [ 'interaction', 'dayGrid' ], defaultDate: '2019-06-12', editable: true, eventLimit: true, // allow "more" link when too many events <!-- timeZone: 'local', --> timeZone: 'UTC', events: [ { title: 'All Day Event', start: '2019-06-01T23:00:00Z', end: '2019-06-05T15:59:59Z', }, { title: 'Long Event', start: '2019-06-07', end: '2019-06-10' }, { groupId: 999, title: 'Repeating Event', start: '2019-06-09T16:00:00' }, { groupId: 999, title: 'Repeating Event', start: '2019-06-16T16:00:00' }, { title: 'Conference', start: '2019-06-11', end: '2019-06-13' }, { title: 'Meeting', start: '2019-06-12T10:30:00', end: '2019-06-12T12:30:00' }, { title: 'Lunch', start: '2019-06-12T12:00:00' }, { title: 'Meeting', start: '2019-06-12T14:30:00' }, { title: 'Happy Hour', start: '2019-06-12T17:30:00' }, { title: 'Dinner', start: '2019-06-12T20:00:00' }, { title: 'Birthday Party', start: '2019-06-13T07:00:00' }, { title: 'Click for Google', start: '2019-06-23T07:00:00Z', end: '2019-06-27T07:00:00Z' } ] }); calendar.render(); }); </script> <style> body { margin: 40px 10px; padding: 0; font-family: Arial, Helvetica Neue, Helvetica, sans-serif; font-size: 14px; } #calendar { max-width: 900px; margin: 0 auto; } </style> </head> <body> <div id='calendar'></div> </body> </html>
- 投稿日:2019-07-19T00:24:23+09:00
ネストしすぎの()を何とかしたい
@Yametaro さんのエントリでこんなのを見た。
【あるある】こんなコードは嫌だ【JavaScript】
https://qiita.com/Yametaro/items/6a7ab15e5b1764bdd383その中の「その4: 関数ネストし過ぎ」が何とかならないかと思って考えてみた。
const [val0, val1, val2] = ["a", "b", "c"]; let name="xxx"; const f0 = (val)=>val+val0; const f1 = (val)=>val+val1; const f2 = (val)=>val+val2;これについて「f0で処理した結果をf1で処理した結果をf2で処理した結果をコンソール出力」というのを考えると普通こんな風になる。
// ordinary console.log(f2(f1(f0(name))));うん、見にくい。
そこで知恵を絞ってこんな風にしてみた。
// using custom object const applier = (val)=>{ const o={ value : val, apply : (unary)=>{ o.value=unary(o.value); return o; } }; return o; }; console.log(applier(name).apply(f0).apply(f1).apply(f2).value);もうちょい短くしてみる。
// using custom object 2 const applier2 = (val, o={ value : val, apply : (unary)=>{ o.value=unary(o.value); return o; } })=> o; console.log(applier2(name).apply(f0).apply(f1).apply(f2).value);と、ここまで考えたところで「配列に突っ込んであれこれすりゃいいんじゃね?」と気づいた。
// using array console.log( [name].map(item=>f0(item)) .map(item=>f1(item)) .map(item=>f2(item)) .reduce(accumulator=>accumulator) );どっとはらい。
- 投稿日:2019-07-19T00:04:36+09:00
p5.jsをWebページの背景に設定する方法
p5.jsとは?
p5.jsとはデジタルアート等をつくることのできるProcessingというプログラミング言語のjavascript版ライブラリです。
javascriptで使うことができるので、Webページに組み込むことが可能です。公式サイト
https://p5js.org/OpenProcessing(いっぱい作例が載っているサイト)
https://www.openprocessing.org/browsep5.jsをWebページの背景に設定する方法
ダウンロード
まずはp5.jsのDownload(http://p5js.org/download/)からp5.js completeをダウンロードしてください。今回はその中のp5.min.jsとp5.dom.min.jsを使います。
準備
以下の説明ではフォルダ,ファイルの構成は以下のようになっているものとします。
projectFolder/
├ index.html
├ css/
| └style.css
├ js/
| └ sketch.js
└ library/
├ p5.min.js
└ p5.dom.min.js今回sketch.jsのサンプルとしてOpenProcessingにアップされているこちらの作品を使用させていただきます。
ページの上中央の</>をクリックするとコードを見ることができるので、sketch.jsにコピペしてください。HTML
HTMLでp5.jsを使うためには
<head>内に以下のように記述します。<head> <link rel="stylesheet" href="css/style.css"> <!-- ここからp5.jsのための記述 --> <script src="library/p5.min.js"></script> <script src="library/p5.dom.min.js"></script> <script src="js/sketch.js"></script> </head>テストのためにHTMLファイル全体は以下のようにしておきます。
index.html<!doctype html> <head> <link rel="stylesheet" href="css/style.css"> <script src="library/p5.min.js"></script> <script src="library/p5.dom.min.js"></script> <script src="js/sketch.js"></script> <title>Hello, world!</title> </head> <body style="color: #FFF"> <br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A<br>A </body> </html>現時点ではスクロールすると下の方にこんな感じのものが動いていると思います。
CSS, javascript
ここからCSSとjavascriptを記述していきますが、ページをスクロールしても背景を固定したい場合と、背景ごとスクロールしたい場合で記述が変わります。
背景を固定したい場合
上のgifのようにスクロールしても背景を固定しておきたい場合はCSSを以下のように記述します。style.css#defaultCanvas0{ position: fixed; /*canvasを固定*/ top: 0; /*canvasを上に固定*/ }sketch.jsは以下のように変更してください。
sketch.js//省略 var noiseScale = 800; var canvas; /* * ウィンドウの大きさが変更されたときに背景が再度描画されるように * 元々setup()にあった処理をここに移動した */ function canvasSetup(){ background(21, 8, 50); for(var i = 0; i < nums; i++){ particles_a[i] = new Particle(random(0, width),random(0,height)); particles_b[i] = new Particle(random(0, width),random(0,height)); particles_c[i] = new Particle(random(0, width),random(0,height)); } } function windowResized(){ resizeCanvas(windowWidth, windowHeight); canvasSetup(); } function setup() { canvas = createCanvas(windowWidth, windowHeight);//ブラウザのウィンドウサイズに合わせてcanvas作成 canvas.style('z-index','-1');//canvasを後ろに移動する。 canvasSetup(); } function draw(){ //省略 }背景もスクロールしたい場合
上のgifのように背景もスクロールする場合はCSSは記述の必要は特にありません。sketch.jsは以下のように変更してください。
sketch.js//省略 var noiseScale = 800; var canvas; /* * ウィンドウの大きさが変更されたときに背景が再度描画されるように * 元々setup()にあった処理をここに移動した */ function canvasSetup(){ background(21, 8, 50); for(var i = 0; i < nums; i++){ particles_a[i] = new Particle(random(0, width),random(0,height)); particles_b[i] = new Particle(random(0, width),random(0,height)); particles_c[i] = new Particle(random(0, width),random(0,height)); } } function windowResized(){ resizeCanvas(document.documentElement.scrollWidth,document.documentElement.scrollHeight); canvasSetup(); } function setup() { //ページのサイズに合わせてcanvasを作成 canvas = createCanvas(document.documentElement.scrollWidth,document.documentElement.scrollHeight); canvas.position(0,0);//canvasをページの原点に固定 canvas.style('z-index','-1');//canvasを後ろに移動する。 canvasSetup(); } function draw(){ //省略 }これでp5.jsを背景に設定することができました。
おしゃれなWebページを作りましょう!
参考
[動画]Q&A #6: p5.js Sketch as Background
https://www.youtube.com/watch?v=OIfEHD3KqCg





























