- 投稿日:2020-05-26T23:03:04+09:00
【簡単にできる】jQueryでのタブ切り替え
今回、jQueryによるタブの切り替えの方法について、備忘録的に記載していきます。
HTML
まずは、表示させるHTML部分のコードです。
タブ部分のul要素と表示するコンテンツ部分のul要素の2つを作成します。qiita.html<ul class="tabs"> <li class="active">タブ1</li> <li>タブ2</li> <li>タブ3</li> <li>タブ4</li> </ul> <ul class="contents"> <li class="active">サッカー</li> <li>バスケ</li> <li>野球</li> <li>バレー</li> </ul>CSS
次はCSSの記述です。
タブ部はactiveクラスが付与されているタブのみ色を変化させます。
またコンテンツ部は非表示にし、activeクラスが付与されている要素のみを表示する仕様です。qiita.css.tabs{overflow:hidden;} .tabs li{background:#ccc; padding:5px 25px; float:left; margin-right:1px;} .tabs li.active{background:#eee;} .contents li{display:none;} .contents .active {padding:20px; display:block;} ul { list-style: none;}jQuery
最後にjQueryの記述です。
クリックされたタブに対して、タブ部とコンテンツ部の両方にactiveを付与します。qiita.js$('.tab li').click(function() { var index = $('.tabs li').index(this); $('.contents li').removeClass('active'); $('.contents li').eq(index).addClass('active'); $('.tabs li').removeClass('active'); $(this).addClass('active') });今回は、index()メソッドを用いて何番目のタブがクリックされたかを判別し、
その番号と同じ順番にあるコンテンツ部の要素にactiveを付与する処理にしました。
- 投稿日:2020-05-26T22:49:35+09:00
Kinx 要素編 - 演算子オーバーライド
演算子オーバーライド
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。ライブラリ… ではないですが、ライブラリ作成で便利な機能。
今回は演算子オーバーライドです。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
Ruby の何でもオブジェクト方針は一貫した思想という意味で美しいとも思うのだが、
1+1
の意味を変えるのは百害あって一利無しと思います。できても良いとは思うけど。ただし、クラス・オブジェクトに対しての演算子オーバーライドは有益です。ということで、Kinx では クラス・オブジェクトに対してのみ演算子のオーバーライドを明示的にサポート します。
String.+
とかも定義して使えますが、というか標準ライブラリの中で既に使ってますが、オーバーライドしたときの動作保証は いたしません。標準ライブラリで使っている=標準ライブラリの動作が変わる、なので本当に保証できませんので悪しからず...。演算子オーバーライド
演算子オーバーライドとは
オブジェクトに対する演算子の挙動を上書きすること。演算子がクラスに属しているメソッドと考えれば「オーバーライド」となり、クラスに属さないと考えると「オーバーロード」となるイメージですが、ここでは Ruby っぽく演算子はクラス・オブジェクトへのメッセージでありクラスに属しているイメージで、そのクラス・メソッドを上書きする形を表現して「オーバーライド」で統一しておきます。
尚、C++ の演算子オーバーロードは演算子の多重定義です。クラス・メソッドではなく、同じ名前の関数(や演算子)でも、その引数の違いによって呼び出される関数が区別される機能のことです。
基本形
オーバーライド可能な演算子の種類は以下の通り。
==
,!=
,>
,>=
,<
,<=
,<=>
,<<
,>>
,+
,-
,*
,/
,%
,[]
,()
.例として、
+
演算子をオーバーライドしてみましょう。関数名を演算子名の+
とするだけです。他の演算子でも同じ。class Sample(value_) { @isSample = true; @value = value_; public +(rhs) { if (rhs.isSample) { return new Sample(value_ + rhs.value); } return new Sample(value_ + rhs); } }
rhs
として渡されるものは、適宜想定するコンテキストに合わせて場合分けして実装する必要があります。上記のように実装すると、以下のように使えます。var s1 = new Sample(10); var s2 = s1 + 100; s1 += 1100; System.println(s1.value); // => 1110 System.println(s2.value); // => 110
a += b
も内部的にはa = a + b
に展開されるので正しく動作します。尚、オブジェクトに対するメソッド呼び出しなので、以下のようにも書けます。
var s1 = new Sample(10); var s2 = s1.+(100); System.println(s2.value); // => 110基本的に、
[]
演算子と()
演算子以外の右辺値を取る演算子は、同様の動作をします。
[]
演算子
[]
はインデックス要素的なアクセスを許可します。ただし、インデックスには整数(Integer)かオブジェクト、配列しか使えません。実数(Double)は動作しますが引数には整数(Integer)で渡ってきます。文字列は使えません(プロパティ・アクセスと同じであり、無限ループする可能性があるため)。実際に、例えば
Range
には実装されており、以下のようなアクセスが可能です。System.println((2..10)[1]); // => 3 System.println(('b'..'z')[1]); // => 'c'ただし内部で toArray() されるので、イテレーションは最後まで行われた後に応答されます。具体的には以下のように実装されています。
Range(多少異なるがこんな感じ)class Range(start_, end_, excludeEnd_) { ... public [](rhs) { if (!@array) { @array = @toArray(); } return @array[rhs]; } }
[]
演算子もメソッド呼び出し風に書くと以下のようになります。System.println((2..10).[](1)); // => 3 System.println(('b'..'z').[](1)); // => 'c'
()
演算子
()
演算子はオブジェクトに直接作用します。C++ のファンクタ(operator()
を定義したクラス)みたいなものです。例えば以下のようにクラス・インスタンスを関数のように見立てて直接()
演算子を適用できます。class Functor { public ()(...a) { return System.println(a); } } var f = new Functor(); f(1, 2, 3, 4, 5, 6, 7); // => [1, 2, 3, 4, 5, 6, 7]メソッド呼び出し風に書くと以下と同じです。
var f = new Functor(); f.()(1, 2, 3, 4, 5, 6, 7); // => [1, 2, 3, 4, 5, 6, 7]サンプル
スタック
スタック操作を
<<
で行えるクラスStack
を作ってみましょう。<<
で Push します。>>
でポップさせたいですが、引数に左辺値を渡せないので、無理矢理ですが()
演算子で行きます。ちょっと中途半端ですが仕方ない。配列を Push すると末尾に全部追加するようにしておきます。class Stack { var stack_ = []; public <<(rhs) { if (rhs.isArray) { stack_ += rhs; } else { stack_.push(rhs); } } public ()() { return stack_.pop(); } public toString() { return stack_.toString(); } } var s = new Stack(); s << 1; s << 2; s << 3; s << 4; s << [5, 6, 7, 8, 9, 10]; System.println(s); var r = s(); System.println(s); System.println(r);実行してみましょう。
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] [1, 2, 3, 4, 5, 6, 7, 8, 9] 10期待通りですね。
有理数クラス
別のサンプルとして四則演算のみをサポートした有理数クラスを作ってみましょう。符号処理は今回は省略します。基本形は以下の通り。
class Rational(n, d) { @isRational = true; }まず初期化です。Rational オブジェクトのコピーも作れるようにしておきます。また、有理数の演算では最大公約数を求める機会も多いのでそのための private メソッドを用意します。また、確認しやすいように
toString()
メソッドも用意しておきます。class Rational(n, d) { @isRational = true; private gcd(a, b) { if (a < b) { [a, b] = [b, a]; } var r; while ((r = a % b) != 0) { [a, b] = [b, r]; } return b; } private initialize() { if (d.isUndefined && n.isRatioal) { d = n.denominator; n = n.numerator; } var g = gcd(n, d); @numerator = Integer.parseInt(n / g); @denominator = Integer.parseInt(d / g); } public toString() { return "%{@numerator}/%{@denominator}"; } } var r = new Rational(5, 10); System.println("r = ", r); // => r = 1/2では、早速四則演算を定義していきます。
ここではまず
+
演算子の定義です。ただし、r1 + r2
でr1
が破壊されるのは直感的ではないので、新しいオブジェクトを返すようにします。また、直接破壊的に操作する別のメソッドを用意してきます。ついでにオブジェクトのクローンをつくるclone()
メソッドを作って活用しましょう。class Rational(n, d) { @isRational = true; private gcd(a, b) { if (a < b) { [a, b] = [b, a]; } var r; while ((r = a % b) != 0) { [a, b] = [b, r]; } return b; } private initialize() { if (d.isUndefined && n.isRational) { d = n.denominator; n = n.numerator; } var g = gcd(n, d); @numerator = Integer.parseInt(n / g); @denominator = Integer.parseInt(d / g); } public toString() { return "%{@numerator}/%{@denominator}"; } public clone() { return new Rational(this); } public add(rhs) { if (rhs.isInteger) { return this + new Rational(rhs, 1); } else if (rhs.isRational) { var n = @numerator * rhs.denominator + @denominator * rhs.numerator; var d = @denominator * rhs.denominator; var g = gcd(n, d); @numerator = Integer.parseInt(n / g); @denominator = Integer.parseInt(d / g); } else { throw RuntimeException("Unsupported type for rational calculation"); } return this; } public +(rhs) { return @clone().add(rhs); } } var r1 = new Rational(5, 10); var r2 = new Rational(2, 6); var r3 = r1 + r2; var r4 = r1 + 2; System.println("r1 = ", r1); System.println("r2 = ", r2); System.println("r1 + r2 = ", r3); System.println("r1 + 2 = ", r4);
rhs
が Integer の場合、こんなこと(this + new Rational(rhs, 1)
のことね)する必要はないのですが、こんなこともできます、という意味での単なる例です。新たに Rational オブジェクトを作って再度.+()
演算子が呼ばれて正しく計算されるというイメージです。結果は以下のように表示されます。
r1 = 1/2 r2 = 1/3 r1 + r2 = 5/6 r1 + 2 = 5/2では、四則演算全て定義してみましょう。先ほどの無駄っぽいところ(
this + new Rational(rhs, 1)
のことね)も今回は変えておきます。class Rational(n, d) { @isRational = true; private gcd(a, b) { if (a < b) { [a, b] = [b, a]; } var r; while ((r = a % b) != 0) { [a, b] = [b, r]; } return b; } private makeValue(n, d) { var g = gcd(n, d); @numerator = Integer.parseInt(n / g); @denominator = Integer.parseInt(d / g); return this; } private initialize() { if (d.isUndefined && n.isRational) { d = n.denominator; n = n.numerator; } makeValue(n, d); } public toString() { return "%{@numerator}/%{@denominator}"; } public clone() { return new Rational(this); } public add(rhs) { if (rhs.isInteger) { return makeValue(@numerator + @denominator * rhs, @denominator); } else if (rhs.isRational) { return makeValue(@numerator * rhs.denominator + @denominator * rhs.numerator, @denominator * rhs.denominator); } else { throw RuntimeException("Unsupported type for rational calculation"); } } public sub(rhs) { if (rhs.isInteger) { return makeValue(@numerator - @denominator * rhs, @denominator); } else if (rhs.isRational) { return makeValue(@numerator * rhs.denominator - @denominator * rhs.numerator, @denominator * rhs.denominator); } else { throw RuntimeException("Unsupported type for rational calculation"); } } public mul(rhs) { if (rhs.isInteger) { return makeValue(@numerator * rhs, @denominator); } else if (rhs.isRational) { return makeValue(@numerator * rhs.numerator, @denominator * rhs.denominator); } else { throw RuntimeException("Unsupported type for rational calculation"); } } public div(rhs) { if (rhs.isInteger) { return makeValue(@numerator, @denominator * rhs); } else if (rhs.isRational) { return makeValue(@numerator * rhs.denominator, @denominator * rhs.numerator); } else { throw RuntimeException("Unsupported type for rational calculation"); } } public +(rhs) { return @clone().add(rhs); } public -(rhs) { return @clone().sub(rhs); } public *(rhs) { return @clone().mul(rhs); } public /(rhs) { return @clone().div(rhs); } } var r1 = new Rational(5, 10); var r2 = new Rational(2, 6); var r3 = r1 + r2; var r4 = r1 - r2; var r5 = r1 * r2; var r6 = r1 / r2; System.println("r1 = ", r1); System.println("r2 = ", r2); System.println("r1 + r2 = ", r3); System.println("r1 - r2 = ", r4); System.println("r1 * r2 = ", r5); System.println("r1 / r2 = ", r6);結果。
r1 = 1/2 r2 = 1/3 r1 + r2 = 5/6 r1 - r2 = 1/6 r1 * r2 = 1/6 r1 / r2 = 3/2おわりに
上記有理数クラスに符号処理はありませんが、簡単なので省略します。もしかしたらどこかで正式に有理数クラスをサポートするかもしれません。その時は本気出して色々メソッドを定義してみます(以下が参考)。
ではまた次回。
clone()
についての補足
clone()
は通常、上記のようにnew 自分自身のクラス(this)
で定義することが多いですが、以下のようにすると新たに作ったオブジェクトが過去のオブジェクトへの参照を持ち続けてしまうので、新たに作成したオブジェクトが死なない限りその元オブジェクトも GC で解放されないといったことになり、リークする可能性があります。class A(arg_) { @isA = true; var a_; private initialize() { a_ = arg_.isA ? arg_.get() : 0; // arg_ = null が無いと参照を持ち続けてしまう } public get() { return a_; } public clone() { return new A(this); } /* ... */ }上記コメントのように初期化後に
arg_ = null
とすれば OK ですが、それ以外にも、arg_
とa_
を共用させる方法もあります(上記 Rational クラスはそれに近い方法)。例えば以下のような感じ。class A(a_) { @isA = true; private initialize() { a_ = a_.isA ? a_.get() : 0; } public get() { return a_; } public clone() { return new A(this); } /* ... */ }こうすることで、新たなオブジェクトから過去のオブジェクトへの参照が切れるので、しかるべき時にきちんと GC が働くようになります。
では、また。
- 投稿日:2020-05-26T22:26:31+09:00
datepickerのinputにキーボード入力で、/(スラッシュ)や -(ハイフン)でも入力可能にする(yyyymmddなども対応する)
突然クライアントから、「このカレンダーのフォーム手入力できるようにできる?」と聞かれ、
readonly
を外せばすむだろうと思って「問題ないですよ〜」と二つ返事したら、
「よかった、じゃあちゃちゃっとこれよろしく!」と言われて以下のリストが出てきた。以下で入力できるようにお願いします。
- yyyy/mm/dd
- yyyy-mm-dd
- yyyymmdd
- mm/dd
- mm-dd
- mmdd
これ聞き方詐欺だ・・・。
まあいい、やるか。と思ったら案外といろいろ忘れてしまっていたので、
次にまた聞き方詐欺に引っかかったときのために残しておこう(~o~)<input type="text" class="datepicker" onblur="datepickerReplace(event)">function datepickerReplace(e) { const _this = e.target // ターゲット const today = new Date() // 今日 const currentYear = today.getFullYear() // 今年の西暦 const splitString = _this.value.split('') // 入力値を分割 // 記号の有無を確認 Boolean // スラッシュかハイフンがあったらtrueを返す const hasSymbol = splitString.some(string => string === '/' || string === '-') // / を - に変換した文字列を作成 // 1文字ずつ評価してスラッシュがきたらハイフンを返すそれ以外、はそのまま返す // join() で全てを連結する const replaceString = splitString.map(string => string === '/' ? '-' : string).join('') if (splitString.length === 10) { // yyyy-mm-dd yyyy/mm/dd の場合 // 連結された値が無効な日付だったら今日の日付を返す、有効な場合は連結された値を返す _this.value = new Date(replaceString).toString() === "Invalid Date" ? today : replaceString } else if (splitString.length === 8 && !hasSymbol) { // yyyymmdd の場合 // 文字列の最初から4文字を年として、次の2文字を月、最後の2文字を日として定義する const year = replaceString.slice(0, 4); const month = replaceString.slice(4, 6); const day = replaceString.slice(6); // 3つをハイフンでつないで連結 const ymd = `${year}-${month}-${day}` _this.value = new Date(ymd).toString() === "Invalid Date" ? today : ymd } else if (splitString.length >= 3 && splitString.length <= 5 && hasSymbol) { // m-d mm-ddの場合 const spritMonthDay = replaceString.split('-') const monthDayArr = spritMonthDay.map(num => `0${num}`.slice(-2)) const ymd = `${currentYear}-${monthDayArr.join('-')}` _this.value = new Date(ymd).toString() === "Invalid Date" ? today : ymd } else if (splitString.length === 4 && !hasSymbol) { // mmdd const month = replaceString.slice(0, 2); const day = replaceString.slice(2); const ymd = `${currentYear}-${month}-${day}` _this.value = new Date(ymd).toString() === "Invalid Date" ? today : ymd } else { // その他 _this.value = today } }
- 投稿日:2020-05-26T22:19:38+09:00
SPAの文字揃えゲーム作成
背景
過去記事:JavaとJavaScriptでwebブラウザとのソケット通信①
過去に非同期通信を用いてチャットアプリを作成した。
⇒改造してチャットとは異なるアプリケーションを作成。twitterで見かけた『~揃えゲーム』を作成。
個人制作の備忘録。
目的
- ソケット通信で送受信されているデータを取得して編集する
- JavaScriptを用いて受信データの表示方法を学ぶ
実践内容
- クライアントから送信されたデータをサーバーサイドプログラムによって編集
- 編集したデータをクライアントへ送信
- サーバーから送信されたデータ(文字列)を1文字ずつ順番に表示
成果物
- なんでも揃えゲーム
⇒ 送信したデータがランダムに返信されるプログラム言語
サーバープログラム:Java
クライアントプログラム:JavaScriptコード内容
- RandomSocket.java:ソケット通信のためのサーバープログラム
- RandomLogic.java:受信したデータを編集するプログラム
- randomView.html:表示用のHTML
- random.js:ソケット通信および動的表示のためのクライアントプログラム
ソケット通信(サーバー)
RandomSocket.javapackage randomWeb; import java.io.IOException; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint(value = "/random") public class RandomSocket { private static Set<Session> user = new CopyOnWriteArraySet<>(); @OnOpen//クライアントと接続したとき public void onOpen(Session mySession) { System.out.println("connect ID:"+mySession.getId()); user.add(mySession); } @OnMessage//クライアントからデータが送信されたとき public void onMessage(String text , Session mySession) { RandomLogic randomLogic=new RandomLogic(text); System.out.println(text); String random=randomLogic.logic(); for (Session user : user) { user.getAsyncRemote().sendText(random); System.out.println(user.getId()+"番目に"+mySession.getId()+"番目のメッセージを送りました!"); } if(text.equals("bye")) onClose(mySession); } @OnClose//クライアントが切断したとき public void onClose(Session mySession) { System.out.println("disconnect ID:"+mySession.getId()); user.remove(mySession); try { mySession.close(); } catch (IOException e) { System.err.println("エラーが発生しました: " + e); } } }ポイント
@onMessage
でクライアントからデータを受信- RandomLogicインスタンスを作成しデータを渡す
- 変数randomに編集後のデータを受け取り、それをクライアントに送信する
データ編集
RandomLogic.javapackage randomWeb; public class RandomLogic { private String text=""; private String random=""; public RandomLogic(String text) {//コンストラクタ this.text=text; } public String logic() { char[] textArray=text.toCharArray();//textを1文字ずつ配列に分割 char[] textArray2=new char[textArray.length];//分身となる配列を作成 int a=0;//配列の添え字 for(int i=0;i<textArray.length;i++) { a=new java.util.Random().nextInt(textArray.length); textArray2[i]=textArray[a];//分身に本家の1文字をランダムに代入 random+=textArray2[i];//変数randomに順番に文字を追加(for文が終了した時点で1単語分) } while(true) { String text2 = new String(textArray2);//分身の配列を文字列に変換 if(text2.equals(text)) break;//分身の文字列が本家の文字列と等しいならbreak for(int i=0;i<textArray2.length-1;i++) { textArray2[i]=textArray2[i+1];//分身の文字を1文字ずつ前にずらす } a=new java.util.Random().nextInt(textArray2.length); textArray2[textArray2.length-1]=textArray[a];//分身の最後の文字を本家の文字からランダムに選ぶ random+=textArray2[textArray2.length-1];//追加された1文字を変数randomに追加する } return random;//最後にこれまでの文字列を返す } }ポイント
コメントの通り。
やりたいことは文字列を編集してreturnすること。表示用のHTML
randomView.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>ランダムシステム</title> <script type="text/javascript" src="random.js"></script> </head> <body> <div style="width: 500px; height:500px; overflow-y: auto; border: 1px solid #333;" id="show"> </div> <input type="text" size="80" id="msg" name="msg" /> <input type="button" value="送信" onclick="sendMsg();" /> </body> </html>ポイント
特になし。
ソケット通信および動的表示
random.js// WebSocketオブジェクト生成 var wSck= new WebSocket("ws://localhost:8080/randomWeb/random"); //ソケット接続時のアクション wSck.onopen = function() { document.getElementById('show').innerHTML += "接続しました。" + "<br/>"; }; //メッセージを受け取ったときのアクション wSck.onmessage = function(e) { var text=e.data; message(text); }; //時間間隔を空けて表示する var nextText=''; var length='' function message(text){ if (text !== '') { if(nextText==''){ length=text.length; } var c = text.slice(0, 1);//テキストの先頭1文字を取得 document.getElementById('show').innerHTML += c;//1文字を表示 nextText = text.slice(1);//テキストの先頭1文字を削除 setTimeout('message(nextText)', 1);//1ms毎にループ }else{ document.getElementById('show').innerHTML += '<br/>『'+length+'文字目』でそろったよ!<br/>';//最後に改行 } } //メッセージを送信するときのアクション var sendMsg = function(val) { var line = document.getElementById('msg');//入力内容を取得 wSck.send(line.value);//ソケットに送信 console.log(line.value); line.value = "";//内容をクリア };ポイント
- データを受信したとき、
message()
メソッドで1文字ずつ順番に表示する- 初回のみ
length=text.length;
で文字数を代入しておくvar c = text.slice(0, 1)
で先頭1文字を取得し表示するnextText = text.slice(1);
で先頭1文字を削除setTimeout('message(nextText)', 1);
で1ms毎にmessage()
メソッドが実行される ⇒setTimeout()
メソッドでmessage()
メソッドを入れ子構造にすることで無限ループとなる- 全ての文字を表示し終わったら文字数等を表示して終了
実行結果
- ブラウザでHTMLファイルを実行。
- 文字列を入力し送信。(今回は『おぱんつ』)
- 送信した文字列の中から1文字ずつランダムに表示される。
- 元の文字列が揃えば終了。
特に意味はないが、チャットアプリケーションを応用しているため、複数ブラウザで実行すると全てのクライアントにランダムな文字群が表示される。
その他
改善点
・文字列が長すぎると処理に時間がかかりすぎる
完全ランダムなため、文字列が揃う確率は指数関数的に減衰していく。
3~6文字くらいでしか遊べない。・ボックスをオーバーフローすると自動で観測できない
はみ出た分はスクロールで見ることができるが、自動で追ってくれないため手動でスクロールバーを操作しなければならない。
⇒ jQueryのanimateメソッドを使えばうまくスクロールできそうだが、今回は断念。要検討。感想
以前コンソール上で動作する文字揃えゲームを作成したことがあるが、ブラウザで表示するとなると工数がかなり増える。
特に表示方法にこだわるとJavaScriptの記述もかなり必要になってくる。改良すれば適宜更新していく。
- 投稿日:2020-05-26T21:51:33+09:00
Javascriptの for in と for of
今までJavascriptでfor文を書くときは下記の書き方しか知りませんでした。
// これまで使用してたfor文 const arry = [1,2,3,4,5,6,7,8,9,10]; for(let i=0; i<arry.length; i++){ console.log(arry[i]); } // 配列arryの1~10まで表示されるしかし、他にも for in や、 for of の書き方があったんですね。
// for in const arry = [1,2,3,4,5,6,7,8,9,10]; for(let i in arry){ console.log(i); } // 配列arryの添字が表示されるので0~9が表示される// for of const arry = [1,2,3,4,5,6,7,8,9,10]; for(let v of arry){ console.log(v); } // 配列arryの値が表示されるので1~10が表示される配列arryの中身がひらがなの「あいうえお」ならどうなるか。
// for in const arry = ['あ','い','う','え','お']; for(let i in arry){ console.log(i); } // 添字が表示されるので0~4が表示される// for of const arry = ['あ','い','う','え','お']; for(let v of arry){ console.log(v); } // 値が表示されるので あいうえお が表示されるfor in は添字。
for of は値。けっこう使うことになりそうなのでしっかりと覚えるために聞いておきました!
- 投稿日:2020-05-26T20:20:53+09:00
JavaScriptで文字列が小文字・大文字・数字を全て含むかどうか判定する方法について
タイトルにあるように、文字列が半角英小文字・大文字と半角数字を全て含むかどうかを判定するという機会は少なくありません。特に、文字種の多さがパスワードの強さであるという教義の持ち主である場合に顕著です。もちろん長さは16文字以内です。
さて、この判定は一見単純に見えて一筋縄ではいきません。文字列の条件判定といえば正規表現ですが、「全て含む」という条件をきれいに書くのは少し難しいでしょう。そこで、この記事ではこの条件を判定する諸方法について雑に考察します。
愚直に正規表現を使う方法
正規表現では、「ある文字種をひとつ含む」という条件を書くのは簡単です。例えば半角小文字を含むという文字列は
/[a-z]/
という正規表現で判定可能です。これを用いれば、正規表現を3回使うことで上述の条件を判定できます。const ratz = /[a-z]/, rAtZ = /[A-Z]/, r0t9 = /[0-9]/; function isValidPassword(str) { return ratz.test(str) && rAtZ.test(str) && r0t9.test(str); } console.log(isValidPassword("abcABC123")); // true console.log(isValidPassword("password1234")); // false console.log(isValidPassword("*********")); // falseちなみに、正規表現オブジェクトはこのように変数に入れてキャッシュしたほうが高速のようです。オブジェクト作成のコストの差でしょうか。
ただし、
g
フラグ付きの場合は同じオブジェクトにtest
メソッドを複数回走らせると結果が変わるので注意してください。1つの正規表現にまとめる方法
一応、3つの正規表現を一つにまとめることは可能です。ES2018から導入された先読み (lookahead assertion) 機能を用いることで、同じ判定を次のように行うことができます。
const rall = /^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?[0-9])/; function isValidPassword(str) { return rall.test(str); } console.log(isValidPassword("abcABC123")); // true console.log(isValidPassword("password1234")); // false console.log(isValidPassword("*********")); // falseこれを読み解くことができればそこそこの正規表現力があると言えるかもしれません。この正規表現に3回出てくる
(?= )
という構文が先読みを表しています。これは、^
などと同じく位置にマッチする構文です。つまり、(?=中身)
という構文は、その位置から中身
にマッチすることができるという条件を表しています。今回これらは^
の直後に現れていますから、この位置というのは文字列の先頭を表すことになります。つまるところ、この正規表現は「文字列の先頭から
.*?[a-z]
にマッチできる」「文字列の先頭から.*?[A-Z]
にマッチできる」「文字列の先頭から.*?[0-9]
にマッチできる」という3つの条件を全て満たすということを表していることになります。ちなみに、パフォーマンスを比べると、1つにまとめた方が速くなります(筆者のMac上のGoogle Chromeで計測)。具体的には、1つにまとめた方が約1.4倍高速です1。
残念ながら、なぜ1.4倍という性能差があるのかはよく分かりません。というのも、正規表現が1つだろうと3つだろうと、書かれているロジックは変わらないからです。どちらの方式でも「小文字を含む」「大文字を含む」「数字を含む」という3つの事柄を別々に調べている点が同じですから、正規表現が1つでも、普通に解釈すれば3回の走査が行われるはずです。
可能性は主に2つあります。まず
test
の呼び出し回数が少ない点で有利であるという可能性、そして後者の正規表現では何らかの最適化が行われているという可能性です。筆者はパフォーマンスには疎いので残念ながら何が正解なのかはよく分かりません。教えてくださる方を募集しています。正規表現を使わずに1回で走査する
さて、この問題は別に正規表現を使わなくてもできますよね。ということで、愚直に走査してみましょう。
function isValidPassword(str) { let flag = 0; for (let i = 0; i < str.length && flag !== 7; i++) { const code = str.charCodeAt(i); if (0x61 <= code && code <= 0x7a) { flag |= 1; } if (0x41 <= code && code <= 0x5a) { flag |= 2; } if (0x30 <= code && code <= 0x39) { flag |= 4; } } return flag === 7; }これは明らかに文字列を1回走査するだけで判定しています。パフォーマンスを計測すると、正規表現1つの場合に対して約1.1倍高速です。
正規表現の場合に比べてもあまりパフォーマンスが上がっていない点が不思議ですね。やはり正規表現が最適化されているのかもしれません。一応愚直解の底力を見せたいということでビット演算を使った最適化を入れていますが、それが無ければ同じくらいのパフォーマンスです。
1回走査の正規表現
ところで、上記のプログラムでは、変数
flag
は明らかに0〜7の8通りを取ります。図にするとこんな感じです。ここで、上向きの矢印(青)は小文字を読んだときの遷移を、右向きの矢印(黄)は大文字を読んだときの遷移を、そして斜めの矢印(緑)は数字を読んだときの遷移を表しています。よく見ると、これはオートマトンですね。0が始状態で7が受理状態です。先のfor文によるプログラムはこのオートマトンを実装したものであると言えます。
そして、オートマトンは正規表現で表現できることが知られています。それも、プログラミング言語等において拡張された正規表現ではなく、本来の意味での正規表現です。
ということで、このオートマトンを正規表現で表してみましょう。するとこうなります。
function isValidPassword(str) { return /^(?:[^a-zA-Z0-9]*(?:[a-z](?:[^A-Z0-9]*(?:[A-Z](?:[^0-9]*[0-9])|[0-9](?:[^A-Z]*[A-Z])))|[A-Z](?:[^a-z0-9]*(?:[a-z](?:[^0-9]*[0-9])|[0-9](?:[^a-z]*[a-z])))|[0-9](?:[^a-zA-Z]*(?:[a-z](?:[^A-Z]*[A-Z])|[A-Z](?:[^a-z]*[a-z])))))/.test(str) }この正規表現は文字列を1回しか走査しませんから、ちょっと長いものの、先ほどの正規表現よりも高速であることが期待できます。
……と思って計測してみたのですが、一番最初の正規表現×3と変わらない遅さでした。
まとめ
正規表現なんもわからん(完)
参考リンク
1.4倍高速というのは、同じ時間で実行できる回数が1.4倍であるということです。 ↩
- 投稿日:2020-05-26T20:19:08+09:00
jQueryのクリックイベントについて
クリックイベントの取得方法
結論、click()でクリックイベントを取得できます。
JSと比べてめちゃくちゃ楽になりました。
それぞれ違いをみていきましょう。JavaScriptの場合
実際にみていきましょう。
結論から言うと、クリックイベントが起きた場所を探すのにforEach文を使って
順番にみていかないとダメでした。
なので以下のような少々長いコードになります。line.forEach(function(value) { value.addEventListener("click", lineSwitch); });コードが冗長になっています。。。
jQueryの場合
早速コードをみていきましょう。
line.click(lineSwitch)前提としてセレクタをしっかり指定していればこれだけでいいです。
スッキリして読みやすいものになりました。
ただここでクリックされた要素を特定する必要があります。
それにはthisを用います。this
JSでめちゃくちゃ使われるものです。
用途は多岐に広がり、全てを解説するのは膨大な量になるので今回は
このクリックイベントに注目していきます。
関数の中でthisを使うとイベントの発生元となった要素を取得することができます。
なお、jQueryで使う場合には$(this)と指定しなければなりません。jQueryは便利なものが多いです。
使いこなすと間違いなく効率アップ間違いなし!
- 投稿日:2020-05-26T20:10:40+09:00
Puppeteerのpuppeteerクラス
puppeteerとは
他の記事とかでも書いてあると思いますが、一応説明をしておくと、puppeteerはDevToolsプロトコルを使ってChromiumもしくはChromeを管理する高レベルAPIを提供するNodeライブラリです。
今回はこのpuppeteerが面白そうだったので、このライブラリを調べたり使ったりしたことを残していきたいと思います。今回はpuppeteerクラスをやっていきたいと思います。
puppeteerクラス
このクラスはChromiumインスタンスを立ち上げるためのメソッドを提供してくれます。この辺りは設定云々のところなので実証していくところはないです。
メソッドは
connect
、createBrowserFetcher
、defaultArgs
、executablePath
、launch
の5つ。それぞれ説明していきます。connectメソッド
実際のURLを指定するかWebsocketのURLを指定してブラウザインスタンスにつなげるメソッド。
このメソッドは存在しているChromiumインスタンスにのみに接続します。puppeteer.connect(options) options↓ browserWSEndpoint ?string 接続するブラウザwebsocketエンドポイント。 browserURL ?string 形式がhttp://${host}:${port}である接続するブラウザURL。 ignoreHTTPSErrors boolean ナビゲーション中にHTTPSエラーを無視するかどうかを決められます。デフォルトはfalse。 defaultViewport ?Object さまざまなページのviewportを準備します。デフォルトのviewportは800x600。nullはデフォルトのviewportを無効にします。 width number ピクセルでのページのwidth。 height number ピクセルでのページのheight。 deviceScaleFactor number デバイスの(dprとして考えることができる)スケール係数を指定できます。デフォルトは1。 isMobile boolean メタviewportタグを考慮するかどうかを決めることができます。デフォルトはfalse。 hasTouch boolean viewportがタッチイベントをサポートしている場合に指定することができます。デフォルトはfalse。 isLandscape boolean viewportがランドスケープモードである場合に指定することができます。デフォルトはfalse。 slowMo number 指定されたミリ秒単位でPuppeteerオペレーションを遅くします。 transport ConnectionTransport Puppeteerのカスタムトランスポートオブジェクトを指定することができます。このプロパティは実験的なものです。(2020年6月時点) product string 可能な値はchrome、firefox。デフォルトはchrome。createBrowserFetcherメソッド
ChromiumもしくはFirefoxのダウンロードホストやダウンロードフォルダを設定できるメソッド。
puppeteer.createBrowserFetcher([options]) options↓ host string 使用するダウンロードホスト。デフォルトはhttps://storage.googleapis.com。もしproduct(さっきのconnectメソッドで指定するなど)がfirefoxである場合はデフォルトはhttps://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central。 path string ダウンロードフォルダのパス。デフォルトは<root>/.local-chromiumで、<root>はpuppeteerのパッケージルート(大体の場合はnode_modules内のpuppeteerフォルダになってる)。もしproductがfirefoxの場合、デフォルトは<root>/.local-firefox。 platform <"linux"|"mac"|"win32"|"win64"> 現在のプラットフォーム。可能な値はmac、win32、win64、linux。デフォルトは現在のプラットフォーム。 product <"chrome"|"firefox"> 起動するためのproduct。可能な値はchrome、firefox。デフォルトはchrome。defaultArgsメソッド
ブラウザを起動させるときの初期引数を設定するメソッド。
puppeteer.defaultArgs([options]) options↓ headless boolean headlessモードでブラウザを起動するかどうかを設定することができます。デフォルトはdevtoolオプションがtrueである限りはtrue。 args Array<string> ブラウザインスタンスに渡すために追加の引数を設定することができます。Chromiumフラグは参考文献で参照してください。 userDataDir string ユーザーデータディレクトリのパス。 devtools boolean 様々なタブのDevToolパネルを自動でオープンするかどうかを決めることができます。もしこのオプションがtrueなら、headlessオプションはfalseになります。executablePathメソッド
Puppeteerがバンドルされたブラウザを見つけるために期待するパスを取得するメソッド。
launchメソッド
基本的に
connect
メソッドとほとんど変わらないですが、connect
メソッドよりすこしできることが多くなってます。このメソッドはChromiumインスタンスを起動した後に接続をします。puppeteer.launch([options]) options↓ product string 起動するブラウザ。現時点だと、この値はchromeもしくはfirefoxのどちらか。 ignoreHTTPSErrors boolean ナビゲーション中にHTTPSエラーを無視するかどうかを決めることができます。デフォルトはfalse。 headless boolean headlessモードでブラウザを起動するかどうかを設定することができます。デフォルトはdevtoolオプションはtrueである限りはtrue。 executablePath string バンドルされたChromiumの代わりの実行可能なブラウザのパス。もしexecutablePathが相対パスなら、現在の作業ディレクトリを基準にして解決されます。注意:PuppeteerはバンドルされているChromiumでの動作のみが保証されていて、この設定は自己責任での使用です。 slowMo number 指定されたミリ秒単位でPuppeteerオペレーションを遅くします。 defaultViewport ?Object さまざまなページのviewportを準備します。デフォルトのviewportは800x600。nullはデフォルトのviewportを無効にします。 width number ピクセルでのページのwidth。 height number ピクセルでのページのheight。 deviceScaleFactor number デバイスの(dprとして考えることができる)スケール係数を指定できます。デフォルトは1。 isMobile boolean メタviewportタグを考慮するかどうかを決めることができます。デフォルトはfalse。 hasTouch boolean viewportがタッチイベントをサポートしている場合に指定することができます。デフォルトはfalse。 isLandscape boolean viewportがランドスケープモードである場合に指定することができます。デフォルトはfalse。 args Array<string> ブラウザインスタンスに渡すために追加の引数を設定することができます。Chromiumフラグは参考文献で参照してください。 ignoreDefaultArgs boolean|Array<string> もしtrueなら、puppeteer.defaultArgs()を使わないでください。もし配列で定義する場合、指定されたデフォルトの引数を除外します。デフォルトではfalseです。 handleSIGINT boolean Ctrl-Cのブラウザプロセスを閉じます。デフォルトではtrueです。 handleSIGTERM boolean SIGTERMのブラウザプロセスを閉じます。デフォルトではtrueです。 handleSIGHUP boolean SIGHUPのブラウザプロセスを閉じます。デフォルトではtrueです。 timeout number ブラウザインスタンスの起動を待機する最大時間(ミリ秒)。デフォルトは30000(30秒)。タイムアウトを無効にするには、値を0にすること。 dumpio boolean process.stdoutとprocess.stderr内のブラウザプロセスのstdoutとstderrをパイプするかどうかを設定します。デフォルトではfalseです。 userDataDir string ユーザーデータディレクトリのパス。 env Object ブラウザに見ることができる環境変数を指定します。デフォルトではprocess.env。 devtools boolean 様々なタブのDevToolパネルを自動でオープンするかどうかを決めることができます。もしこのオプションがtrueなら、headlessオプションはfalseになります。 pipe boolean WebSocketの代わりのパイプを介してブラウザと接続します。デフォルトではfalseです。 extraPrefsFirefox Object Firefoxに渡すことができる追加のパフォーマンスを設定できます。さいご
次の記事ではBrowserFetcherクラスをやっていきます。おそらく実証なんかもこの辺からだと思います。
参考文献
devtools protocol -- https://chromedevtools.github.io/devtools-protocol/
メタデータエンドポイント -- https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target
Getting Started with Headless Chrome(日本語はバージョンが古い) -- https://developers.google.com/web/updates/2017/04/headless-chrome
Chromiumフラグ -- https://peter.sh/experiments/chromium-command-line-switches/
ユーザーデータディレクトリ -- https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md
- 投稿日:2020-05-26T19:30:06+09:00
【Thymeleaf】th:hrefをJavaScriptで使う
詰まったこと
th:hrefをJavaScriptで生成すると、機能しない。
Thymeleafでは、hrefを使うとき、次のような書き方をすると思います。
<a th:href="@{/hello}">サンプル</a>html上では上手くいきますが、
これを次のようにJavaScriptで生成すると、機能しません。<script type="text/javascript"> target.html('<a th:href="@{/hello}">サンプル</a>'); </script>困った。。。
解決法
①まず、scriptタグを書き換えます。
<script type="text/javascript" th:inline="javascript">②リンクを定義します。
const link = /*[[@{/hello}]]*/'';/helloを飛ばしたいリンクにしてください。
③ ②で定義したリンクに書き換える。
target.html('<a href="' + link + '">サンプル</a>');ここでのポイントは、
<a th:href...とせずに、
<a href...とすることです。
まとめると、
<script type="text/javascript" th:inline="javascript"> const link = /*[[@{/hello}]]*/''; target.html('<a href="' + link + '">サンプル</a>'); </script>パラメーターを付与したい時は?
パラメータを付与したい時、あると思います。
そんな時は、、、const baselink = /*[[@{/samplek}]]*/''; const link = baselink + "?id=" + id; target.html('<a href="' + link + '">サンプル</a>');上手くできました。
以上です。最後まで読んでくださり、ありがとうございました。
参考
- 投稿日:2020-05-26T19:29:54+09:00
【Vue】JestでSVGファイルをコンポーネントとしてテストする方法
はじめに
Jestは最近巷で人気なフロントエンドのテストフレームワークです。
今回は、SVGファイルを含んだVueファイルをJestを利用してテストするために、少し四苦八苦したので共有しようと思い記事にしました!
vue-jestでのsvgファイルコンポーネントのテストについて
Jestを用いたVueコンポーネントのテストを行う際には、
vue-jest
を利用しますが、
vue-jest
はSVGファイルをコンポーネントとして利用する方法をデフォルトで提供していません。そのため、ライブラリを探すと、
jest-transform-stubとjest-svg-transformerとsvg-react-transformerの3つがあるようです。Vueのプロジェクトなのでreactに依存するのはreact必要だしちょっと..と思いつつ
jest-svg-transformer
を早速利用しようとしました。
ですが、こちらもなぜかreactに依存しているみたいでした。。
jest-transform-stub
はSVGファイルをコンポーネントとして用いる場合には、使えそうにはありませんでした。え??どうしようと思いながらたどり着いたのが以下のissueです。
https://github.com/visualfanatic/vue-svg-loader/issues/38
vue-svg-loader
のissueですが、なぜか丁寧にvue-jest
にsvgファイルを読み込む方法を記述してくれてます。デフォルトで対応してよ...。
以下に実装例を記述します。
svgTransFormer.js
はJestがSVGファイルコンポーネントを読み込んだ際にテストを行うための設定ファイルです。svgTransFormer.jsconst vueJest = require('vue-jest/lib/template-compiler'); module.exports = { process(content) { const { render } = vueJest({ content, attrs: { functional: false, }, }); return `module.exports = { render: ${render} }`; }, }Jestの設定は以下の通りです。
"jest": { "moduleFileExtensions": [ "js", "jsx", "vue", "svg" ], "transform": { "^.*\\.vue$": "vue-jest", "^.*\\.svg$": "./svgTransFormer.js", }, "testMatch": [ "<rootDir>/**/__tests__/*.(spec|test).js?(x)" ] }おわりに
今回は以上です。
間違いや何か質問などありましたらコメントお願いします!
- 投稿日:2020-05-26T17:38:45+09:00
jQueryでHTMLを取得する方法
DOMを取得する様々な方法です。個人的にはJSよりjQueryの方が
簡単に取得できると思います。IDセレクタ
html要素のID属性を付与しているセレクタを取得できます。
以下、例です。$("#red")上記の例ではidがredのものを取り出していることになります。
クラスセレクタ
html要素のクラス属性を付与しているセレクタを取得できます。
以下、例です。$(".red")上記の例ではクラスがredのものを取り出していることになります。
要素セレクタ
html要素のセレクタを取得できます。
以下、例です。$("h1")上記の例ではh1の要素の中身を取り出していることになります。
色々取り出しかたがあるけど、$マークをつけ忘れるのを気をつけた方がいいかもしれません。
- 投稿日:2020-05-26T17:33:24+09:00
【備忘録】ブラウザでカメラを使用する
ブラウザからデバイスのカメラを使用する場合。とりあえず記載しているのはカメラ1つの場合で、スマホやタブレットで前面/背面カメラがある場合は未調査。
撮影した画像はAPIに渡す想定で、base64エンコードするまで記載しています。検証環境
- Google Chrome バージョン: 81.0.4044.138
ソースコード
html<video id="video">Video stream not available.</video> <canvas id="canvas"></canvas> <input type="button" onclick="onClickCaptureButton()" value="capture"></input>javascriptconst video = document.getElementById('video'); const canvas = document.getElementById('canvas'); video.addEventListener('canplay', (event) => { canvas.setAttribute('width' , video.videoWidth); canvas.setAttribute('height', video.videoHeight); }, false); navigator.mediaDevices.getUserMedia({video:true, audio:false}) .then((stream) => { video.srcObject = stream; video.play(); }) .catch((err) => { console.log(err); }); function onClickCaptureButton(event) { const ctx = canvas.getContext('2d'); ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight); const base64Image = canvas.toDataURL('image/jpeg', 0.95) // <!-- ここに画像の処理を書く。APIをコールするなり、imgタグを埋め込むなり。。。 --> }チラシの裏(読まなくてもいい余談)
顔認識のAPIを試したくて、過去に作ったカメラを使う処理を引っ張り出してきました。昨今はいろんなAPIが出てきて便利な世の中になりましたね。
- 投稿日:2020-05-26T16:21:58+09:00
React超入門
はじめに
本記事は初めてreactを触る人向けに書いています。
reactってなんだ?どう記述するの?という初歩的な内容をまとめています。※ライフサイクルの説明は長くなるので割愛させていただきました。
以下の記事が秀逸なのでこちらを紹介させていただきます。
https://qiita.com/Julia0709/items/3c3fc8d29fd2e56ed7a9Reactとは
FaceBookによって開発されたフロントエンドフレームワークで、Reactはリアクティブ・プログラミングで、値がどのように伝わっていくかを重視しています。
従来のjsとの違いとして、表示を更新するとき予め用意した値を設定しておけば元の値を変更すると表示も自動的に更新されます。
また、Reactは直接DOM操作するのではなく、プログラム的に仮のDOMを構築してそれを操作します。
これを仮想DOMといいます。この仮想DOMは通常のDOMに比べて軽くて高速です。React開発に必要なもの
- Node.js: JavaScriptの実行環境(必須)
- Create React App: Node.jsに組み込まれたnpxを使ってターミナルで
npx create-react-appフォルダ名
を入力して実行(スターターキットのようなもの。自分で環境構築ができる場合は不要)- React Developer Tools: デバッグ用ツール。Reactを使った開発をするならあった方が良い。(ブラウザの拡張ツール)
※トランスパイラ等はCreate React Appで用意されているのでここでは省略します。
JSX
Reactでは、HTMLのタグを直接JavaScriptのスクリプトに記述する仕組みを持っています。これをJSXといいます。JSXを使用することでようやくReactを使うメリットを実感できると思います。
JSXを使った記述と使わない記述の違いを見ていきます。
ブラウザに表示されているここに表示されます。をHello Worldに表示を変更させます。<div id="root">ここに表示させます。</div>JSXなし
import React from 'react'; //インストールしたreactを使うためにインポートしています。(今回はcreate-react-appで必要な物をまとめてインストールしているので、新しくインストールする必要はありません。) const rootId = document.querySelector('#root'); const element = React.createElement ( 'p', {}, 'Hello World' ); ReactDOM.render(element, rootId);JSX
import React from 'react'; ReactDOM.render(<p>Hello World</p>,document.getElementById('root'));JSXを使用した方が記述量が減り、可読性が上がるのが分かりますね。
JSXを使わない場合以下のReact.createElementという箇所で仮想DOMのエレメントを作成しています。const element = React.createElement ( 'p', {}, 'Hello World' );React.createElementの第一引数にはタグ名、第二引数にはそのエレメントに用意される属性オブジェクト(必要ない場合はからのオブジェクトを指定)、第三引数には作成するエレメントの内部に組み込まれるものを用意します。
まとめると以下のようになります。
React.createElement(タグ名、属性、中に組み込む物);作成した仮想DOMエレメントをReactDOMオブジェクトのrenderメソッドを使いレンダリングします。
JSXを使う場合はシンプルに
pタグ
で表示したいHello Worldを囲い、renderメソッドを使いレンダリングしています。そして、ブラウザにアクセスすると差分を検知し、
divタグ
で囲われたここに表示されます。がHello Worldに書き換わっていると思います。デバッグツールで確認すると以下のように変更されていることが確認できます。これはJSXで記述した方でも同じように表示されるはずです。
<div id="root"><p>Hello World</p></div>どちらの書き方が良いかは一目瞭然ですね。
JSXを使わないと表示するタグをcreateElementで1つずつ作成していかなければなりません。その煩雑さを解消するためにJSXを使うという訳です。
しかし、JSXはbabelによってトランスパイルされることで初めて成り立っています。どのようにbabelが変換しているかを頭でイメージできるかできないかでは習熟度が全然違ってきます。
コンポーネント
Reactではコンポーネント事に管理する分割統治の概念があります。
ここでは基本的なコンポーネントの書き方を書いていきます。//index.jsx import React from 'react'; import ReactDOM from 'react-dom'; import App from '.Components/app'; //Appコンポーネントがある階層を指定してインポートする。 ReactDOM.render(<App />,document.getElementById('root'));//app.js class App extends React.Component { render() { return <p>Hello World</p> } } export default App;//Appコンポーネントを他ファイルでインポートできるようにエクスポートする。上記ではReact.Componentを継承したAppクラスを作って、render()メソッドでJSXをreturnしています。これによりReactDOM.render()から呼び出した際にJSXで出力されます。
このようにコンポーネント化することにより以下のメリットがあります。
- 再利用性が高い
- 保守性が高い
- 管理がしやすい
- アトミックデザインと相性が良い(パーツ・コンポーネント単位で定義していくUIのデザイン手法)
コンポーネントはスクリプト内に複数のコンポーネントを書いて、組み合わせて表示を作成することができます。
また、上記の例では敢えてファイルを複数に分ける想定で書いていますが、これはコンポーネントが複数存在する想定での書き方で、今回のようにテキストを表示させるだけの場合は1ファイルで完結させた方がシンプルで良いでしょう。
状態管理
Reactでは保管した状態を操作するために以下のようなモノが用意されています。
- props 親コンポーネントから渡されたプロパティです。propsはimmutableなデータで、変更ができません。
- state コンポーネントが持っている状態です。stateはmutableなデータで変更することができます。
stateの初期値を受け取るにはthis.stateで値を取り出します。
簡単に説明すると、親コンポーネントで保持しているstateの値を子コンポーネント側でpropsで受け取ります。stateを更新することでコンポーネントの表示を変えたりしています。
以下のようにapp.jsを書き換えます。
//app.js import React, { Component } from 'react'; class App extends Component { constructor(props){ super(props); this.state = { clickCount: 0 }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState((state) => ({ clickCount: state.clickCount + 1 })); } render(){ return( <div> <button onClick={this.handleClick}>ButtonCount</button> <h1>{this.state.clickCount}</h1> </div> ); } } export default App;クリックしたら値が更新され数字が1つずつ増えていきます。
最初に画面に表示されている0の数字はconstructorで設定しており、ステートの値はオブジェクトリテラルとして記述します。これでthis.stateの中から設定した値を取り出して利用することができるようになりました。
初期状態として表示される0はconstructorで設定した0が表示されているということです。そしてButtonCountボタンを押下すると、
<h1>
タグで挟んでいる{this.state.clickCount}
の値が更新されます。今回はapp.jsで完結させていますが、clickCountの値を子コンポーネントに渡して、
子コンポーネントで値を表示するという実装だったとすると以下のようになります。//app.js import React, { Component } from 'react'; import Child from '.Component/child.js'; class App extends Component { constructor(props){ super(props); this.state = { clickCount: 0 }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState((state) => ({ clickCount: state.clickCount + 1 })); } render(){ return( <div> <button onClick={this.handleClick}>ButtonCount</button> <Child clickCount={this.state.clickCount} /> </div> ); } } export default App;//child.js import React, {Component} from 'react'; class Child extends Component { render() { console.log(this.props); return <h1>{this.props.clickCount}</h1> } } export default Child;親コンポーネントの
<Child clickCount={this.state.clickCount} />
箇所で子コンポーネントに値を渡し、子コンポーネントの{this.props.clickCount}
箇所で親コンポーネントから渡された値を受け取り表示しています。
console.log
で実際受け取っている値が見ることができます。おわりに
今回のように小規模開発であれば問題ないのですが、大規模開発になるとコンポーネントも増えて、propsのバケツリレーになってしまいます。
このバケツリレーを避けるためにReduxというReactが扱う状態を管理するフレームワークなどを使うと良いでしょう。
Redux公式
Redux超入門 ←さらっと理解したい人向けに書きました。
- 投稿日:2020-05-26T15:52:24+09:00
【解説しながら】ターミナル上で動作するメモアプリをnodeで作ってみる。Part1
今回の記事は【作成】・【解説】の二部構成になっています。
初学者が理解るように丁寧に解説して行きつつ、完成を目指していきます。
解説は良いから作り方だけ見せてくれやって言う方は、【作成】だけを見ていくことをおすすめします。
細かい説明を知りたい方は【解説】を読んで頂くと良いかと思います。
序盤は解説が多くなりますがご了承ください。
今回は、expressを使わずに実装していきます。? 対象
- nodeを始めたばかりの人・なんとなく触ってみたい人
- ES6.ECMAScriptを基礎的なところまで理解している人
? まずはディレクトリを作成しましょう♪ 【作成】
任意のフォルダに今回はmemoというディレクトリを作ります。
ターミナル% mkdir memo % cd memo % touch app.js上記のコマンドは、メモディレクトリを作成→メモディレクトリを指定→app.jsを作成という流れになってます。
app.jsが動作するか、適当にコードを書いてみたいと思います。
app.jsconsole.log("起動してます")nodeで開くときは
% node app.jsこのように node というコマンドを使って開きます。
しかし、コレだとコードを更新するたびにコマンドを入力しないといけないので、非常に面倒です。
後ほど紹介しますが、nodemonというnpmを使用していきたいと思います。? require関数を使ってみる 【解説】
requireは直訳すると、「必要とする」という意味になります。
この関数は外部ファイルを呼び出すための関数で、nodeファイルの中で自由に使えるようになります。
呼び出すファイルのことを、「モジュール」や「パッケージ」などと呼ぶこともあります。require関数が呼び出せる対象は
1. コアモジュール、ビルトインモジュール(nodeをインストールした時点で自動的に使えるようになっているツール群のこと)
2. 独自に作成したオリジナルファイル(ディレクトリに自分で作ったファイルとか)
3. npmパッケージ(npmで取得してインストールして呼び出す)
この3つが主に呼び出せるものとなります。require関数には、ファイルパス、モジュール名、パッケージ名などを文字列として渡します。
コアモジュール公式
公式ドキュメントは
https://nodejs.org/api/documentation.html
こちらになります。全部英語なので、得意な人は読んでみても良いかもしれません。私は一ミリも読めません。勉強します!? コアモジュールfsを使う 【解説】
今回はfsというコアモジュールを使っていきます。file sytemの頭文字をとってfsだと思います。
また、今回はappendFileというメソッドも使ってみましょう。
appendFileは、指定したファイルが無ければ、ファイルを作成し、次に情報を追加します。app.jsconst fs = require('fs'); //fsを使えるようにrequireする fs.appendFile("greeting.txt","Hello,world",function(err){ //greeting.txtがなければ作成、そのファイルの中にHello,worldという情報を入れる。 if(err){ console.log(err); } });エラーが起こった場合のif文を書いていますがそこはあまり気にしなくていいです。
実行してみましょう。ターミナル% node app.js画面は前回同様に、起動していますよ という文字が出ています。
ではどこに変化が出ているのでしょうか?
エディタを見てみます。
このように、greething.txtファイルができており、中にはHello,worldの文字列が格納されています。
再度、ターミナル% node app.jsと行うと、
このように、新しいファイルはつくられず、文字列だけが追加されているのがわかります。
簡単な記述で、ファイルの作成や情報の追加が出来ることがわかったと思います。?コアモジュールosを使ってみる【解説】
os(オペレーティングシステム)のコアモジュールを使って機能を追加してみます。
パソコンのシステムの基本的な操作や情報取得を行うオブジェクトです。
今回は、
os.userinfo()
というメソッドを使います。
文字通りuserの情報を入手するのが目的です。app.jsconst fs = require('fs'); const os = require('os'); //fs同様 osをrequireする。 let user = os.userInfo(); //user変数に、os.userInfo()というuserの情報を入れる。 console.log(user);ではプログラムを実行してみましょう。
このように、自身のPCの情報などが取得できたかと思います。では実際にosプロパティを使ってみましょう。
先程fsで記入したコードを活用します。app.jsfs.appendFile("greeting.txt","こんにちは!"+ user.username +"さん!ようこそ",function(err){ if(err){ console.log(err); } });node app.jsで実行してみましょう。
すると、
このように、さきほどusernameに入っていた情報が出力されます。オリジナルファイルを扱ってみる【解説】
自身で作成したファイルをrequireさせるにはどうしたら良いでしょうか?
適当にファイルを作ってみます。今回はcontents.jsを作成しました。
その中に、console.log("contentsが稼働しております。")など適当なメッセージログを書いて保存してください。では、requireしてみましょう。
app.js// 省略 constの下に書いてください require("./contents.js"); //引数として渡すのは、モジュール名ではなく、相対パスになります。./は忘れずに書きましょう。保存してnode app.jsで立ち上げると、ターミナル上に、contents.jsで記入したコードが出力されているはずです。
ただ、このやり方は別ファイルの処理を行うだけです。
では、contens.js内で宣言したものをapp.jsで使うにはどうしたら良いでしょうか?
それには、moduleというものを使います。
moduleオブジェクトはrequireと同じようにnodeにおいてグローバルなもので、どこでも使える物となっています。moduleを使う上で、中身を知る必要性があるので、contents.jsにて
contents.jsconsole.log(module);そして、ターミナルでapp.jsを立ち上げてください。
注目してほしいのが、exports:{}という部分になります。
このexportsに付属するものとして定義することで他のファイルから呼び出せるようになります。では、先程書いたconsole.log(module)は削除して、情報をcontens.jsの中に身長の情報定義していきたいと思います。
contents.jsmodule.exports.height = 178;app.jsconst contents = require("./contents.js");//先程requireしたcontents.jsを変数化してあげる。 fs.appendFile("greeting.txt","こんにちは!"+ user.username +"さん!あなたの身長は"+ contents.height +"cmです。"以下コード割愛 //このように、既存のコードに追加してnode app.jsを行ってください。すると、greeting.txtに、こんにちは!〇〇さん!あなたの身長は178cmです。というように出力されていると思います。
関数の場合も同じです。
contents.jsmodule.exports.addNote =(){ console.log("addNote"); return "new note";app.jslet result = contents.addNote(); console.log(result);実行すると、コンソール上にaddNote new note と出力されていることが確認できます。
以上が、require関数による外部ファイルの処理、module.exportsによるファイルの外部化と呼び出しについての解説になります。
? npm init(初期化) install について【作成】
npm initを入力することでnodeに欠かせないnpmパッケージが利用できるようになります。
ターミナルにて% npm initこれは、アプリケーションの初期設定となります。gitに上げるときはgit repositoryに記入したりしてください。
今回は、ここの説明は省略します。とりあえず、入力したら何も考えずに連打してOKです。
すると、ディレクトリにpackage.jsonができています。
中をのぞくと先程ターミナル上で聞かれた内容が書いてあると思います。これでnpmパッケージをインストールする準備ができました。
npmパッケージは2種類のインストール方法が存在します。
1つ目はターミナルから入力していきます。ターミナル% npm install パッケージ名 --saveこうするとpackage.jsonの
"description": "",この中にnpmパッケージが入力されます。
2つ目は、上記のパッケージが格納される場所に直接書くことでインストールできます。
expressを入れてみましょう。(実際は入れなくていいです。)package.json"description": { "express":"*" // "*"は最新版という意味です。 }このようにして、npmパッケージを導入してきます。
nodemon導入【作成】
コレまでの解説を見てきて、
% node app.jsこれを毎回打つのが凄いめんどくさいなと思いませんでしたか?
コードに変更を加えたら自動で更新してくれればいいのに、というニーズを叶えてくれるのが
nodemonというnpmになります。nodeで開発を行う上で必須のnpmとなりますので、今回はPC自体にインストールしたいと思います。
sudo npm install nodemon -g -gはグローバルという意味でパソコン自体にインストールするということです。すると、passwordを聞かれますので、ご自身のPCのパスワードを入力してください。
インストールができたら早速nodemonでファイルを起動してみましょう。
% nodemon app.jsでは、app.jsのconsole.logの文字を適当に変更してみてください。
ターミナル上で自動的に再読み込みされているのがわかると思います!長くなりそうなので、今回は以上となります!
今回は、ほとんど説明ばかりになってしまいました、申し訳ありません。次回のための準備【作成】
app.jsconsole.log("app.js起動中"); const = require("fs"); const contents = require("./contents.js");contents.jsconsole.log("contents.js起動中");こんな感じに、jsファイルの余分なコードを削除した状態から始めます。
? 次回予告
今回の内容を踏まえた上で
次回から本格的にメモアプリを作って行きたいと思います。
- 投稿日:2020-05-26T15:51:30+09:00
JavaScript+AzureFunctionsで簡易アクセス解析をやってみた
背景
静的なWEBサイト(HTML/CSS/JavaScript)で、ちょっとしたアクセス解析っぽいものを実現したくて試してみました。サーバーサイドで処理できる環境を準備したり、アクセス解析ツールを使ったりするのが、正攻法なのかもしれないですが、ローカルネットワーク内で運用されているWEBサイトで、尚且つ、環境まで触れない(いまは触らない)というのもあり、ちょっと何か出来ないかと思ってやってみたものです。
構成
- HTML+CSS
- JavaScritp(Fetch API)
- Azure Functions
- Azure Cosmos DB
流れ
- WEBページの操作(ボタンクリックなど)
- JavaScript(Fetch API)でHTTPリクエスト
- Azure FunctionsのHTTPトリガーで処理が起動
- HTTP通信で受信したデータをAzure Cosmos DBに格納
ソースコード
HTML
<a href="xxxx.html" onclick="javascript:logClick('xxxx')">ああああ</a></li>JavaScript
function logClick(xxxx) { // Create param const yyyy= "hogehoge"; // Create the query param for Azure Functions const params = { xxxx: xxxx, yyyy: yyyy }; const qs = new URLSearchParams(params); // POST to Azure Functions fetch(`https://xxxx.azurewebsites.net/api/HttpTriggerXXXX?${qs}`, { method: "POST", }) .then(response => response.text()) .then(text => { console.log(text); }) .catch(console.error); }Azure Functions
using System.Net; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Logging; using Newtonsoft.Json; public static IActionResult Run(HttpRequest req, out object outputDocument, ILogger log) { string XXXX= req.Query["xxxx"]; string YYYY= req.Query["yyyy"]; if (!string.IsNullOrEmpty(XXXX) && !string.IsNullOrEmpty(YYYY)) { outputDocument = new { XXXX, YYYY }; return (ActionResult)new OkResult(); } else { outputDocument = null; return (ActionResult)new BadRequestResult(); } }まとめ
データベース上にデータが格納されるまで確認できました。
可視化まわりは今後ちまちまやっていこうと思います。
- 投稿日:2020-05-26T15:49:03+09:00
websocket 入門
基本的な構文
var connection = new WebSocket('ws://localhost:8080');4つのイベント処理と2つのメソッド
var connection = new WebSocket('ws://localhost:8080'); //通信が接続されたとき connection.onopen = function(e){}; //エラーが発生したとき connection.onerror = function(e){}; //メッセージを受け取ったとき connection.onmessage = function(e){}; //通信が切断されたとき connection.onclose = function(e){};//データを送信するメソッド connection.send(); //通信を切断するメソッド connection.clse();ボタンクリックでデータを送信する
btn.addEventListener('click',function(e){ var text = document.getElementById('text'); connection.send(text.value); })テキストデータを受信する
connection.onmessage = function(e){ console.log(e.data); connection.close(); }
- 投稿日:2020-05-26T15:47:15+09:00
[JavaScript ]条件分岐(if、switch)
if文
if (条件式) {
処理
}const number = 10; if (number > 8) { console.log(`${number}は8より大きいです`); }else
elseとifの間は半角空ける。
else if(条件式)const number = 10; if (number > 8) { console.log(`${number}は8より大きいです`); } else if (number > 5) { console.log(`${number}は5より大きいです`); } else { console.log(`${number}は5以下です`); }複数条件式
どちらの条件も満たす時true
条件式1 && 条件式2const number = 20; if (number > 10 && number < 100) { console.log(`${number}は10より大きく100より小さいです。`) }どちらかの条件を満たす時true
条件式1 || 条件式2
const number = 8; if (number < 10 || number > 100) { console.log(`${number}は10より小さいか100より大きいです。`) }switch
値によって処理の分岐をする
必ずbreak;で処理を終了するようにする
switch (条件の値) {
case 値1;
処理
case 値2;
処理
default;
処理
}const color = "赤"; switch (color) { case "赤"; console.log("ストップ!"); break; case "黄"; console.log("要注意"); break; case "青"; console.log("進んでよし"); break; default; console.log("値が正しくありません。"); break; }比較演算子
aとbが等しい
a === b
aとbが異なる
a !== b
- 投稿日:2020-05-26T15:37:47+09:00
Node.jsで簡易サーバをつくる
install
$ sudo apt install nodejs npm# 実行 $ node demo.jswebserver using http module
Node.jsはモジュールをrequire()で呼び出す
createServer() メソッドを使ってWebサーバーを構築する
最後にlisten()でポート番号を指定すれば「localhost:8080」にブラウザからアクセスする
createServer()の中身は、最低限の記述としてヘッダー情報とコンテンツを次のように記述する
writeHead()は、responseオブジェクトのメソッドで、ヘッダー情報をレスポンスに書き出す。第1引数にはステータスコードを指定し、第2引数にヘッダー情報を連想配列でまとめたものを指定する。
var http = require('http'); var server = http.createServer(function(request, response){ response.writeHead(200,{'Content-Type': 'text/html; charset=utf-8'}); response.end('<h1><span id="hello">Hello World</span></h1>'); }) server.listen(8080);htmlを表示するサーバ構築
- Node.jsでファイルを扱うためのfsモジュールを使って「index.html」を読み込む
- これを変数htmlに格納し、end()の引数に設定することでブラウザに表示できる
var http = require('http'); var html = require('fs').readFileSync('views/index.html'); var server = http.createServer(function(request,response){ response.writeHead(200,{'Content-Type':'text/html'}); response.end(html); }) server.listen(8080);Expressによるサーバ構築
var express = require('express'); var app = express();createServer()の引数に先ほど作成したExpressのサーバーオブジェクトを設定する。
var server = require('http').createServer(app); app.get("/", function (request, response) { response.sendFile(__dirname + '/views/index.html'); }); server.listen(8000);GETを受信する
ブラウザからWebサイトにアクセスする時はGET通信でサーバーへリクエストを自動的に送信する。
フォームなどのデータを送信する場合はPOST通信で送る。
サーバーに来るリクエストがGET通信であるかどうかを判断するには次のように記述する。
http.createServer(function(request,response){ response.writeHead(200,{'Content-Type':'text/html; charset=utf-8'}); if(request.method == 'GET'){ //処理 } })「request.method」には、サーバーへ送られたリクエストの種類が格納される。
POSTを受信する
ブラウザからWebサイトにアクセスする時はGET通信でサーバーへリクエストを自動的に送信する。
フォームなどのデータを送信する場合はPOST通信で送る。
http.createServer(function(request, response) { response.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'}); if(request.method == 'POST'){ var postData = ''; request.on('data', function(chunk){ postData += chunk; }).on('end',function(){ response.end('あなたが送信したのは'+postData); }) } })POSTの場合は何らかのデータが送信されているのが普通なので、このデータを受けとる処理が必要である。
request.on() の中で関数を記述し、その引数である「chunk」に送信されたデータが格納される。
続けて、on('end')イベント処理内で取得したデータを画面に表示させる。
on というメソッドは、指定のイベント処理を組み込むためのもので、第一引数にイベント名を、第2引数に組み込む処理(関数)をそれぞれ指定する。
var http = require('http'); var server = http.createServer(); server.on('request', doRequest); server.listen(process.env.PORT, process.env.IP); function doRequest(req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.write('Hello World\n'); res.end(); }requestというイベントにdoRequestという関数を割り当てる。
requestというのは、Serverオブジェクトがクライアントからのリクエストを受け取ったときに発生するイベントで「ブラウザからサーバーにアクセスした時のサーバー側の処理」を組み込む。
- 投稿日:2020-05-26T15:05:51+09:00
storyshotsでのスナップショットテスト
スナップショットテスト
現在の画面のスナップショット(キャプチャみたいなもの)を保存しておいて、テストの時にそのスナップショットと今の画面と比較して変更点を出力してくれる。
変更内容を確認して意図せぬ変更に気づくことができる。
正しい変更かそうでないかは自分で確認にすること。流れ
スナップショットは導入した後の初回のテスト時に自動で生成される。
なので初回は比較対象がないので、必ずパスする。
スナップショットが生成した次回以降のテストは比較結果が出る。
変更点が問題なく、新しくスナップショットを取りたいと思ったら、スナップショットのファイルを削除して、再度テストを実施して生成させる。導入
storybookの導入
スナップショットのテストの方法はいくつかあると思うが、今回はstorybookを利用したテストの方法を記載する。
storybookの初期化を実行する。
下を実行することで、storybookのパッケージの追加とstorybookの設定ファイルを生成してくれる。npx -p @storybook/cli sb init
./src/stories
がサンプルファイルとして生成されるので、不必要なら削除する。
また、typescriptを使っているなら、./storybook/main.js
の設定ファイルを変更する。module.exports = { stories: ['../src/**/*.stories.tsx], addons: [ '@storybook/preset-create-react-app', '@storybook/addon-actions', '@storybook/addon-links', ], };Hello world!!と表示するだけのHelloコンポーネントがあるとする。
Hello.stories.tsxを作成してstorybookに登録する。// src/Hello.tsx import React from 'react'; const Hello = () => <div>Hello world!!</div>; export default Hello;// src/Hello.stories.tsx import React from 'react'; import Hello from './Hello'; export default { component: Hello, title: 'Hello', excludeStories: /.*Data$/, }; export const Default = () => <Hello />;
yarn storybook
を実行するとstorybookの画面で確認することができる。storyshotsの導入
スナップショットの実行するパッケージを追加する。
yarn add -D @storybook/addon-storyshots react-test-rendererスナップショットを生成するためのファイルを作成する。
// src/storyshots.test.js import initStoryshots from '@storybook/addon-storyshots'; initStoryshots();
yarn test
を実行したら、./src/__snapshots__/storyshots.test.js.snap
というファイルが生成されたはず。
中身は以下ようにHelloコンポーネントで表示する内容が記載されている。// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Storyshots Hello Default 1`] = ` <div> Hello world!! </div> `;testはこのスナップショットのファイルがない場合はスナップショットの生成、ある場合は比較する。
生成の場合はテストはパスになる。
このあとHelloコンポーネントを変更して、yarn test
を実行したらスナップショットのファイルと比較するのでテストは通らない。コンポーネントを変更して、再度スナップショットを取りたい場合は
__snapshots__
のディレクトリごと削除する。
- 投稿日:2020-05-26T14:56:07+09:00
[Javascript]テンプレートリテラレル
- 投稿日:2020-05-26T14:15:51+09:00
連想配列から配列と一致する要素を抽出
やりたいこと
JavaScriptで連想配列のキーと配列の値を照らし合わせて、検索結果が一致する要素を連想配列から抽出。
サンプルコード
Ajaxなどで取得したjsonを格納した連想配列(オブジェクトデータ)から、必要なデータのみ抽出して処理したい場合などで遭遇するパターン。
for in パターン
JavaScript// 連想配列 var fruits = { "みかん":{"price":"200", "num":"5"}, "いちご":{"price":"400", "num":"1"}, "ぶどう":{"price":"380", "num":"7"} } // 配列(検索ワード) var target = ['みかん', 'ぶどう']; var result = {}; for(key in fruits){ // 連想配列のキーと配列の値が一致するか検索 for(i in target){ if(key == target[i]){ result[key] = fruits[key]; // 連想配列に格納 break; } } } console.log(result);console.log{ ぶどう: {price: "380", num: "7"} みかん: {price: "200", num: "5"} }連想配列(object)には
.push()
メソッドで追加できないので、以下のようにします。arr[キー文字列] = 値 //新規キーと値をセット.filter()パターン
JavaScriptfor(key in fruits){ target.filter(function(data) { if(key == data){ result[key] = fruits[key]; } }) }ちょっと強引な気もしますが、
for in
内を.filter()で書いたパターン。結果は同じです。参考
- 投稿日:2020-05-26T12:18:56+09:00
showModalDialogのように、サブウインドウを表示して戻り値を取得する
やりたいこと
window.showModalDialogのように、サブウインドウを表示して戻り値を取得する。
実装
opener.jsfunction openWindowToCenter(name, url, width, height) { const left = (screen.width - width) / 2; const top = (screen.height - height) / 2; const param = `width=${width}, height=${height}, left=${left}, top=${top}` return window.open(url, name, param); } function lockBody(window) { window.document.body.style.pointerEvents = 'none'; } function unlockBody(window) { window.document.body.style.pointerEvents = 'auto'; } function showDialogAsync(url, width, height) { lockBody(window); const timestamp = Data.now().toString(); const dialogName = `dialog_${timestamp}`; const dialog = openWindowToCenter(dialogName, url, width, height); return new Promise((resolve, reject) => { const intervalId = setInterval(() => { if (dialog.closed) { clearInterval(intervalId); unlockBody(window); resolve(window[dialogName]); } }, 100); }); } (function () { document.getElementById('showDialogButton').onclick = async () => { const returnValue = await showDialogAsync('dialog.html', 100, 100); console.log(returnValue); }; }());dialog.jsfunction returnValueToOpener(value) { window.opener[window.name] = value; window.close(); } (function () { document.getElementById('returnValueButton').onclick = () => { returnValueToOpener('戻り値'); }; }());opener.html<html> <head> <title>呼び出し元ウインドウ</title> </head> <body> <button type="button" id="showDialogButton">サブウインドウを開く</button> <script scr="opener.js"></script> </body> </html>dialog.html<html> <head> <title>呼び出し先ウインドウ</title> </head> <body> <button type="button" id="returnValueButton">呼び出し元に値を返す</button> <script scr="dialog.js"></script> </body> </html>補足
ウインドウを閉じたときのイベントにはwindow.oncloseがあるが、標準仕様に含まれていないため使用しなかった。
- 投稿日:2020-05-26T12:15:34+09:00
jQueryのchangeイベントがIEでは無効になる
課題
radioボタンを使ったinputの動作制御がIEだけうまく行かないのを修正する
原因
IEはinput[type=radio]のchangeイベントがとれない (っぽい)
ソース
$(function(){ // $('input[name="' + hoge + '"]') = radioボタン $('input[name="' + hoge + '"]').change(function(){ concole.log('clicked'); // IE11で出力されない }); });解決
IEはinput[type=radio]のchangeイベントがとれないとわかったので、clickで代用してやればよい。
今回はあえて、IEは'click'、そのほかは'change'と三項演算子で分岐させることにした。var ua = window.navigator.userAgent.toLowerCase(); var isIE = ua.indexOf('msie') >= 0; $(function(){ // $('input[name="' + hoge + '"]') = radioボタン $('input[name="' + hoge + '"]').change(function(){ concole.log('clicked'); // 出力されない }); });こう書けばIEだけは'click'になり、他のブラウザは'change'がイベントが指定される。
........でも、分岐させなくてもいいかな~とも少し思ったり^^;
参考
https://stackoverflow.com/questions/208471/getting-jquery-to-recognise-change-in-ie
- 投稿日:2020-05-26T12:08:54+09:00
アクセストークン等の外部に公開したくないものフロントを側で制御する方法
FIRST PLAN株式会社のフロントエンドエンジニアtakeです。
冷やし中華食べました!
さて、入社後の課題の問題点を解消しました。
以前作成した以下の検索アプリで、アクセストークンがJavaScriptに直書きになってしまっているので、誰でもソースから見れてしまう問題がありました。
それを改善するために調べた知見を共有します。概要
普通にJavaScriptでHTTP通信をしていると、ソースから第三者がアクセストークン等の本来は公開したくない情報にもアクセスできてしまいます。
それを防ぐためには環境変数を使う等色々と方法はあると思いますが、今回はそれをwebpackで手軽に行えるライブラリを使って実装していこうと思います。・dotenv-webpack
https://www.npmjs.com/package/dotenv-webpackコード
設定
まずはdotenv-webpackをインストールします。
npm install dotenv-webpack --save-dev #or yarn add dotenv-webpack --dev次にwebpackの設定ファイル(デフォルトではwebpack.config.js)に以下の記述を追加します。
//新たにモジュールを追加 const Dotenv = require('dotenv-webpack'); module.exports = { //諸々の設定ファイル mode: 'development', devtool: 'source-map', entry: './src/main.js', output: { path: path.resolve(__dirname, '任意のディレクトリ'), }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } ] }, plugins: [ //pluginsでDotenvをインスタンス化 new Dotenv({ systemvars: true }), ] // }後は新たに
.env
ファイルを作成し、ファイル内に非公開情報を記載します。.envACCESS_TOKEN=*********************これで準備は完了です。
後はJavaScriptから参照します。
JavaScript//process.env.hogeでアクセスする。 const token = process.env.ACCESS_TOKEN //.envファイル内のACCESS_TOKENが表示される。 console.log(token);ちなみに現在のディレクトリの中身はこのようになっています。
-rw-rw-r--. 1 centos centos 172 May 25 08:06 babel.config.js #babelやsass-loaderの設定に使うファイル -rw-rw-r--. 1 centos centos 20 May 25 08:06 .browserslistrc #今回作成したクライアント側から参照するアクセストークンが記述されたファイル -rw-rw-r--. 1 centos centos 77 May 25 09:24 .env -rw-rw-r--. 1 centos centos 3674 May 25 10:48 index.html drwxrwxr-x. 361 centos centos 12288 May 25 10:51 node_modules -rw-rw-r--. 1 centos centos 569 May 25 10:49 package.json -rw-rw-r--. 1 centos centos 200826 May 25 10:49 package-lock.json drwxrwxr-x. 2 centos centos 21 May 25 10:22 src -rw-rw-r--. 1 centos centos 489 May 25 10:44 webpack.config.js以上になります。
一応確認のためにbundleされたファイルの中身も見てみましょう。
axios__WEBPACK_IMPORTED_MODULE_5___default.a.get('https://qiita.com/api/v2/items', { headers: { Authorization: "Bearer ".concat(TOKEN) }, params });アクセストークンの部分がキチンと隠されているのが確認できました。
以上になります。
簡単に設定できるので、是非使ってみてください。
- 投稿日:2020-05-26T12:08:54+09:00
アクセストークン等の外部に公開したくないものでフロント側で制御する方法
FIRST PLAN株式会社のフロントエンドエンジニアtakeです。
冷やし中華食べました!
さて、入社後の課題の問題点を解消しました。
以前作成した以下の検索アプリで、アクセストークンがJavaScriptに直書きになってしまっているので、誰でもソースから見れてしまう問題がありました。
それを改善するために調べた知見を共有します。概要
普通にJavaScriptでHTTP通信をしていると、ソースから第三者がアクセストークン等の本来は公開したくない情報にもアクセスできてしまいます。
それを防ぐためには環境変数を使う等色々と方法はあると思いますが、今回はそれをwebpackで手軽に行えるライブラリを使って実装していこうと思います。・dotenv-webpack
https://www.npmjs.com/package/dotenv-webpackコード
設定
まずはdotenv-webpackをインストールします。
npm install dotenv-webpack --save-dev #or yarn add dotenv-webpack --dev次にwebpackの設定ファイル(デフォルトではwebpack.config.js)に以下の記述を追加します。
//新たにモジュールを追加 const Dotenv = require('dotenv-webpack'); module.exports = { //諸々の設定ファイル mode: 'development', devtool: 'source-map', entry: './src/main.js', output: { path: path.resolve(__dirname, '任意のディレクトリ'), }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } ] }, plugins: [ //pluginsでDotenvをインスタンス化 new Dotenv({ systemvars: true }), ] // }後は新たに
.env
ファイルを作成し、ファイル内に非公開情報を記載します。.envACCESS_TOKEN=*********************これで準備は完了です。
後はJavaScriptから参照します。
JavaScript//process.env.hogeでアクセスする。 const token = process.env.ACCESS_TOKEN //.envファイル内のACCESS_TOKENが表示される。 console.log(token);ちなみに現在のディレクトリの中身はこのようになっています。
-rw-rw-r--. 1 centos centos 172 May 25 08:06 babel.config.js #babelやsass-loaderの設定に使うファイル -rw-rw-r--. 1 centos centos 20 May 25 08:06 .browserslistrc #今回作成したクライアント側から参照するアクセストークンが記述されたファイル -rw-rw-r--. 1 centos centos 77 May 25 09:24 .env -rw-rw-r--. 1 centos centos 3674 May 25 10:48 index.html drwxrwxr-x. 361 centos centos 12288 May 25 10:51 node_modules -rw-rw-r--. 1 centos centos 569 May 25 10:49 package.json -rw-rw-r--. 1 centos centos 200826 May 25 10:49 package-lock.json drwxrwxr-x. 2 centos centos 21 May 25 10:22 src -rw-rw-r--. 1 centos centos 489 May 25 10:44 webpack.config.js以上になります。
一応確認のためにbundleされたファイルの中身も見てみましょう。
axios__WEBPACK_IMPORTED_MODULE_5___default.a.get('https://qiita.com/api/v2/items', { headers: { Authorization: "Bearer ".concat(TOKEN) }, params });アクセストークンの部分がキチンと隠されているのが確認できました。
以上になります。
簡単に設定できるので、是非使ってみてください。
- 投稿日:2020-05-26T12:03:22+09:00
【TypeScript/JavaScript】switchを使って変数に値を代入する
const determine = (value: string): string | void => { switch (value) { case 'A': { return 'a' } case 'B': { return 'b' } default: throw new Exception() // it depends break } }const value = determine('A')
- 投稿日:2020-05-26T10:48:14+09:00
jQuery 連続でイベント発生したときに、イベント発生が落ち着いてから実行する
概要
テキストボックスや、スライダーなどで、
.on('input')
などとinputイベントをとると、入力中、ドラッグ中に大量にイベント発火します。軽い処理であればリアルタイムに処理できて便利ですが、重い処理が多重発生するのは避けたい、ただ、操作時にすぐに実行したい、というときのスクリプトです。CodePen
See the Pen Run after many events have fired by Tsuneaki Hasekura (@tsunet111) on CodePen.
イベントは連続で発火していますが、アラートがでるのは操作が終わった後です。
コード
1000msecごとにチェックのカウントを行い、カウントが2になったら実際の処理を実行します。
<h1>Run after many events have fired</h1> <p>Event fire count : <span id="txt_count">0</span></p> <p> <input type="range" min="0" max="100" /> </p> <p> <input type="text" /> </p>var num = 0; var waitingTimer,waitCount; var waitingFlg = false; var waitMsec = 1000; var waitNum = 2; function wait_start(){ waitCount = 0; waitingFlg = true; waitingTimer = setTimeout(wait_countup,waitMsec); } function wait_countup(){ if(waitingTimer){ clearTimeout(waitingTimer); } if(waitingFlg){ waitCount++; if(waitCount==waitNum){ waitingFlg = false; do_action(); }else{ waitingTimer = setTimeout(wait_countup,waitMsec); } } } function do_action(){ alert('do action'); } $(function(){ $('input').on('input',function(){ num++; $('#txt_count').html(num); wait_start(); }) });
- 投稿日:2020-05-26T10:24:00+09:00
TensorFlow.jsのfacemeshで顔向き推定を試してみた その2
これはTensorFlow.jsのfacemeshの3D座標から顔向き推定を実装した内容。
前回はOpenCVのSolvePnPとRodriguesで回転行列(rotation matrix)を求めました。
今回はアプローチを変えてfacemeshの3D座標から直接回転行列を求めます。コード解説
行列演算はTensorFlow.jsを利用しました。
TensorFlow.jsでは演算子をサポートしていないのでとても見辛いです。
TensorFlow.js API参考までに
- div .. 割り算
- sub .. 引き算
- mul .. 掛け算
SolvePnPでは既知の2Dと3D座標と、物体を撮影したときの既知のカメラ座標を用いましたが、今回は顔の適当な3D座標と輪郭の端の4点と目の位置から求めました。
ここでは、顔の適当な3D座標(元コードでは全ての座標、今回は適当に選択した座標:輪郭のみ)と輪郭の端の4点と目の位置の引数からスケールを取得し、中心を求めています。let fecePoints = tf.tensor(faces); let eye1 = tf.tensor1d(rightEye); let eye2 = tf.tensor1d(leftEye); let scales = fecePoints.div(tf.norm(eye1.sub(eye2))).mul(0.06); let centered = scales.sub(scales.mean(axis=0)); let c00 = centered.slice(0,1).as1D(); let c09 = centered.slice(9,1).as1D(); let c18 = centered.slice(18,1).as1D(); let c27 = centered.slice(27,1).as1D();3x3のrotation matrixを求めます。
let rotate0 = c18.sub(c00).div(tf.norm(c18.sub(c00))); let rotate1 = c09.sub(c27).div(tf.norm(c09.sub(c27))); let rotate = tf.concat([rotate0, rotate1]).arraySync(); let m00 = rotate[0]; let m01 = rotate[1]; let m02 = rotate[2]; let m10 = rotate[3]; let m11 = rotate[4]; let m12 = rotate[5]; // cross product let m20 = m01*m12 - m02*m11; let m21 = m02*m10 - m00*m12; let m22 = m00*m11 - m01*m10;rotation matrixが解れば、Rotation Matrix to Euler Anglesで変換します。
製作物
顔向き推定のオンラインデモを公開しました。良ければお試しください。
— o2 (@mb_otsu) May 26, 2020
あと、今回の実装について簡単にまとめました。https://t.co/OXEvQENpCmhttps://t.co/jnKMpUnFIS#facemesh #TensorFlow pic.twitter.com/ZjgDfwzocgオンラインデモ
https://infocom-tpo.github.io/facevrm/v0.2/
おわりに
facemeshの三次元座標で顔の向きを推定しました。
二次元座標から求めていた時と比べ、AIで三次元座標が解ることで、簡単に顔の向きが推定出来るようになりました。
ここ最近一気に変化したようで数年前の記事が既に古い情報となっていました。
今後も、定期的に情報を更新していければと思います。
- 投稿日:2020-05-26T01:02:55+09:00
【JavaScript】イベント全部ログ
取り急ぎ
HTMLの各要素の各イベント実行時に開発者ツールのコンソールにログを出力します。
ログが大量に出るのでご注意ください。
ページ解析用javascriptvar logger = function(s){ return function(){console.log(s);}; } document.querySelectorAll("*").forEach(function(el){ var evs = getEventListeners(el); for (var k in evs) { el.addEventListener(k, logger(el.tagName+"\t"+k.toString())); } })
- 投稿日:2020-05-26T00:44:58+09:00
C#でFormにBingマップを表示する
はじめに
Windows Formsに触れたことがある人なら、きっと誰もがマップをFormに埋め込みたいと思うでしょう。
しかしWindows Formsには、残念ながらマップを表示するコントロールはありません。
しかし、CefSharpとBing Maps APIを組み合わせればそのような機能を実現できます。
今回はそのやり方を紹介します。HTMLファイルを作る
仕組みとしては、Bing Mapsを埋め込んだHTMLファイルをCefSharpで表示する形となります。
本当はGoogle Maps APIを使いたかったのですが、クレジットカード情報を登録する必要があるみたいで面倒だったので、今回はあきらめました。Bing Maps APIを使うにはMicrosoftのアカウントが必要です。持っていない方は登録しましょう。
持っている方はBing Maps APIのページにアクセスし、「Sign in」ボタンを押してサインインします。
サインインできたら、上のメニューの「My account」から「My keys」をクリックします。
「Create key」という画面が表示されたら、必要事項を記入し「Create」をクリックします。
するとキーの一覧が表示されるので、「Show key」を押せばキーが表示されます。
このキーは後で使うのでコピーしておいてください。それではHTMLを記述していきます。
<!DOCTYPE html> <html> <head> <title>Bing Maps API</title> <meta charset="utf-8" /> <script type='text/javascript' src='http://www.bing.com/api/maps/mapcontrol?callback=GetMap&key=[キーを指定]&setLang=ja' async defer></script> <script type='text/javascript'> var map; function GetMap() { map = mapStart(35, 140, 10); } // Mapを初期化 // 緯度, 経度, 拡大率(1~20) function mapStart(lat, lon, num) { return new Microsoft.Maps.Map('#myMap', { center: new Microsoft.Maps.Location(lat, lon), // 表示モード(road:普通, aerial:航空写真) mapTypeId: Microsoft.Maps.MapTypeId.aerial, zoom: num }); } // ピンを立てる(緯度, 経度) function mapPushpin(lat, lon) { const location = new Microsoft.Maps.Location(lat, lon); const pin = new Microsoft.Maps.Pushpin(location, { color: "red", // ピンの色 draggable: true, // ドラッグできるか }); map.entities.push(pin); } </script> </head> <body style="margin:0;"> <div id="myMap" style="position:relative;width:100%;height:100%;"></div> </body> </html>[キーを指定]のところに先ほど取得したキーを指定してください。
これをC:\Maps.htmlとして保存します。CefSharpの導入
次に、Bingマップを表示するアプリのプロジェクトを作成しておきましょう。
作成したら、このプロジェクトに「CefSharp」というパッケージを導入します。
CefSharpはChromiumのC#向け実装です。「HTMLファイルを表示したいならWebBrowserコントロールを使えばいいのでは?」
と思うかもしれませんが、WebBrowserコントロールは内部に古いIEのエンジンを使っているので、エラーが頻発してまともに使えません。今回は、NuGetでCefSharp.WinFormsをインストールします。
インストールが完了したら、プロジェクトのフォルダにある設定ファイル(csprojファイル)をテキストエディタで開きます。
以下のように、<CefSharpAnyCpuSupport>true</CefSharpAnyCpuSupport>をPropertyGroupタグの中に追加して保存します。
次に、App.configファイルを開き、configurationタグの中に
<runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="x86"/> </assemblyBinding> </runtime>これでCefSharpの導入は完了です。
Bingマップを表示するプログラム
それでは、プログラムを作っていきます。
以下のようにPanelやステータスバーなどを置きます。
CefSharpはツールボックスに表示できないので、Panelの内部に合わせて表示するようにします。
以下が今回のプログラムです。
using System; using System.Windows.Forms; using CefSharp; using CefSharp.WinForms; namespace BingMapsSample { public partial class Form1 : Form { string maphtml = @"C:\Maps.html"; // さっきのHTMLファイル ChromiumWebBrowser cefBrowser; CefSettings settings; public Form1() { InitializeComponent(); button1.Enabled = false; toolStripStatusLabel1.Text = "読み込み中..."; settings = new CefSettings(); // レンダリングを最適化(これをやらないとバグる) settings.SetOffScreenRenderingBestPerformanceArgs(); Cef.Initialize(settings); cefBrowser = new ChromiumWebBrowser(maphtml); cefBrowser.LoadingStateChanged += CefBrowser_LoadingStateChanged; // Panelに合わせて表示 panel1.Controls.Add(cefBrowser); cefBrowser.Dock = DockStyle.Fill; } private void CefBrowser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) { if(!e.IsLoading) { // 読み込み完了時 Invoke((MethodInvoker)delegate { toolStripStatusLabel1.Text = "読み込み完了"; button1.Enabled = true; }); } } private void button1_Click(object sender, EventArgs e) { // Javascriptの実行 cefBrowser.ExecuteScriptAsync("map = mapStart(" + textBox1.Text + "," + textBox2.Text + "," + "10);"); cefBrowser.ExecuteScriptAsync("mapPushpin(" + textBox1.Text + "," + textBox2.Text + ");"); } } }button1_Click内ではExecuteScriptAsyncメソッドを使って、HTMLファイルで定義した関数を呼び出しています。
実行するとこのようにマップが表示されます。
緯度と経度を入力して「移動」を押せば
このようにマークが付きます。おわりに
以上、C#でFormにBingマップを表示する方法でした。
回りくどい方法でしたが、意外とちゃんと動くので是非試してみてください。参考文献