20200526のJavaScriptに関する記事は30件です。

【簡単にできる】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を付与する処理にしました。

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

Kinx 要素編 - 演算子オーバーライド

演算子オーバーライド

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。ライブラリ… ではないですが、ライブラリ作成で便利な機能。

今回は演算子オーバーライドです。

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 + r2r1 が破壊されるのは直感的ではないので、新しいオブジェクトを返すようにします。また、直接破壊的に操作する別のメソッドを用意してきます。ついでにオブジェクトのクローンをつくる 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 が働くようになります。

では、また。

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

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
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SPAの文字揃えゲーム作成

背景

過去記事:JavaとJavaScriptでwebブラウザとのソケット通信①

過去に非同期通信を用いてチャットアプリを作成した。
⇒改造してチャットとは異なるアプリケーションを作成。

twitterで見かけた『~揃えゲーム』を作成。
個人制作の備忘録。
無題.jpg

目的

  • ソケット通信で送受信されているデータを取得して編集する
  • JavaScriptを用いて受信データの表示方法を学ぶ

実践内容

  1. クライアントから送信されたデータをサーバーサイドプログラムによって編集
  2. 編集したデータをクライアントへ送信
  3. サーバーから送信されたデータ(文字列)を1文字ずつ順番に表示

成果物

  1. なんでも揃えゲーム
    ⇒ 送信したデータがランダムに返信されるプログラム

言語

サーバープログラム:Java
クライアントプログラム:JavaScript

コード内容

  1. RandomSocket.java:ソケット通信のためのサーバープログラム
  2. RandomLogic.java:受信したデータを編集するプログラム
  3. randomView.html:表示用のHTML
  4. random.js:ソケット通信および動的表示のためのクライアントプログラム

ソケット通信(サーバー)

RandomSocket.java
package 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);
        }
    }

}

ポイント

  1. @onMessageでクライアントからデータを受信
  2. RandomLogicインスタンスを作成しデータを渡す
  3. 変数randomに編集後のデータを受け取り、それをクライアントに送信する

データ編集

RandomLogic.java
package 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 = "";//内容をクリア
};

ポイント

  1. データを受信したとき、message()メソッドで1文字ずつ順番に表示する
  2. 初回のみlength=text.length;で文字数を代入しておく
  3. var c = text.slice(0, 1)で先頭1文字を取得し表示する
  4. nextText = text.slice(1);で先頭1文字を削除
  5. setTimeout('message(nextText)', 1);で1ms毎にmessage()メソッドが実行される ⇒ setTimeout()メソッドでmessage()メソッドを入れ子構造にすることで無限ループとなる
  6. 全ての文字を表示し終わったら文字数等を表示して終了

実行結果

  1. ブラウザでHTMLファイルを実行。
  2. 文字列を入力し送信。(今回は『おぱんつ』)
  3. 送信した文字列の中から1文字ずつランダムに表示される。
  4. 元の文字列が揃えば終了。

特に意味はないが、チャットアプリケーションを応用しているため、複数ブラウザで実行すると全てのクライアントにランダムな文字群が表示される。

ランダムシステム.gif

その他

改善点

・文字列が長すぎると処理に時間がかかりすぎる
完全ランダムなため、文字列が揃う確率は指数関数的に減衰していく。
3~6文字くらいでしか遊べない。

・ボックスをオーバーフローすると自動で観測できない
はみ出た分はスクロールで見ることができるが、自動で追ってくれないため手動でスクロールバーを操作しなければならない。
⇒ jQueryのanimateメソッドを使えばうまくスクロールできそうだが、今回は断念。要検討。

感想

以前コンソール上で動作する文字揃えゲームを作成したことがあるが、ブラウザで表示するとなると工数がかなり増える。
特に表示方法にこだわるとJavaScriptの記述もかなり必要になってくる。

改良すれば適宜更新していく。

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

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 は値。

けっこう使うことになりそうなのでしっかりと覚えるために聞いておきました!

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

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通りを取ります。図にするとこんな感じです。ここで、上向きの矢印(青)は小文字を読んだときの遷移を、右向きの矢印(黄)は大文字を読んだときの遷移を、そして斜めの矢印(緑)は数字を読んだときの遷移を表しています。

automaton.png

よく見ると、これはオートマトンですね。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. 1.4倍高速というのは、同じ時間で実行できる回数が1.4倍であるということです。 

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

jQueryのクリックイベントについて

クリックイベントの取得方法

結論、click()でクリックイベントを取得できます。
JSと比べてめちゃくちゃ楽になりました。
それぞれ違いをみていきましょう。

JavaScriptの場合

実際にみていきましょう。
結論から言うと、クリックイベントが起きた場所を探すのにforEach文を使って
順番にみていかないとダメ
でした。
なので以下のような少々長いコードになります。

line.forEach(function(value) {
  value.addEventListener("click", lineSwitch);
});

コードが冗長になっています。。。

jQueryの場合

早速コードをみていきましょう。

line.click(lineSwitch)

前提としてセレクタをしっかり指定していればこれだけでいいです。
スッキリして読みやすいものになりました。
ただここでクリックされた要素を特定する必要があります。
それにはthisを用います。

this

JSでめちゃくちゃ使われるものです。
用途は多岐に広がり、全てを解説するのは膨大な量になるので今回は
このクリックイベントに注目していきます。
関数の中でthisを使うとイベントの発生元となった要素を取得することができます。
なお、jQueryで使う場合には$(this)と指定しなければなりません。

jQueryは便利なものが多いです。
使いこなすと間違いなく効率アップ間違いなし!

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

Puppeteerのpuppeteerクラス

puppeteerとは

他の記事とかでも書いてあると思いますが、一応説明をしておくと、puppeteerはDevToolsプロトコルを使ってChromiumもしくはChromeを管理する高レベルAPIを提供するNodeライブラリです。

今回はこのpuppeteerが面白そうだったので、このライブラリを調べたり使ったりしたことを残していきたいと思います。今回はpuppeteerクラスをやっていきたいと思います。

puppeteerクラス

このクラスはChromiumインスタンスを立ち上げるためのメソッドを提供してくれます。この辺りは設定云々のところなので実証していくところはないです。

メソッドはconnectcreateBrowserFetcherdefaultArgsexecutablePathlaunchの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

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

【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>');

上手くできました。

以上です。最後まで読んでくださり、ありがとうございました。

参考

Thymeleaf: Use a link with 'th:href' in Javascript

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

【Vue】JestでSVGファイルをコンポーネントとしてテストする方法

はじめに

Jestは最近巷で人気なフロントエンドのテストフレームワークです。

今回は、SVGファイルを含んだVueファイルをJestを利用してテストするために、少し四苦八苦したので共有しようと思い記事にしました!

vue-jestでのsvgファイルコンポーネントのテストについて

Jestを用いたVueコンポーネントのテストを行う際には、vue-jestを利用しますが、
vue-jestはSVGファイルをコンポーネントとして利用する方法をデフォルトで提供していません。

そのため、ライブラリを探すと、
jest-transform-stubjest-svg-transformersvg-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.js
const 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)"
    ]
}

おわりに

今回は以上です。
間違いや何か質問などありましたらコメントお願いします!

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

jQueryでHTMLを取得する方法

DOMを取得する様々な方法です。個人的にはJSよりjQueryの方が
簡単に取得できると思います。

IDセレクタ

html要素のID属性を付与しているセレクタを取得できます。
以下、例です。

$("#red")

上記の例ではidがredのものを取り出していることになります。

クラスセレクタ

html要素のクラス属性を付与しているセレクタを取得できます。
以下、例です。

$(".red")

上記の例ではクラスがredのものを取り出していることになります。

要素セレクタ

html要素のセレクタを取得できます。
以下、例です。

$("h1")

上記の例ではh1の要素の中身を取り出していることになります。

色々取り出しかたがあるけど、$マークをつけ忘れるのを気をつけた方がいいかもしれません。

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

【備忘録】ブラウザでカメラを使用する

ブラウザからデバイスのカメラを使用する場合。とりあえず記載しているのはカメラ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>
javascript
const 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が出てきて便利な世の中になりましたね。

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

React超入門

はじめに

本記事は初めてreactを触る人向けに書いています。
reactってなんだ?どう記述するの?という初歩的な内容をまとめています。

※ライフサイクルの説明は長くなるので割愛させていただきました。
以下の記事が秀逸なのでこちらを紹介させていただきます。
https://qiita.com/Julia0709/items/3c3fc8d29fd2e56ed7a9

Reactとは

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の中から設定した値を取り出して利用することができるようになりました。
初期状態として表示される0constructorで設定した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超入門 ←さらっと理解したい人向けに書きました。

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

【解説しながら】ターミナル上で動作するメモアプリをnodeで作ってみる。Part1

今回の記事は【作成】・【解説】の二部構成になっています。
初学者が理解るように丁寧に解説して行きつつ、完成を目指していきます。
解説は良いから作り方だけ見せてくれやって言う方は、【作成】だけを見ていくことをおすすめします。
細かい説明を知りたい方は【解説】を読んで頂くと良いかと思います。
序盤は解説が多くなりますがご了承ください。
今回は、expressを使わずに実装していきます。

? 対象

  • nodeを始めたばかりの人・なんとなく触ってみたい人
  • ES6.ECMAScriptを基礎的なところまで理解している人

? まずはディレクトリを作成しましょう♪ 【作成】

任意のフォルダに今回はmemoというディレクトリを作ります。

ターミナル
% mkdir memo  
% cd memo
% touch app.js

上記のコマンドは、メモディレクトリを作成→メモディレクトリを指定→app.jsを作成という流れになってます。

app.jsが動作するか、適当にコードを書いてみたいと思います。

app.js
console.log("起動してます")

下の写真の様になっていれば、成功です。
スクリーンショット 2020-05-25 21.31.25.png

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.js
const 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

画面は前回同様に、起動していますよ という文字が出ています。
ではどこに変化が出ているのでしょうか?
エディタを見てみます。
スクリーンショット 2020-05-26 11.42.13.png

このように、greething.txtファイルができており、中にはHello,worldの文字列が格納されています。
再度、

ターミナル
% node app.js

と行うと、スクリーンショット 2020-05-26 11.43.44.png
このように、新しいファイルはつくられず、文字列だけが追加されているのがわかります。
簡単な記述で、ファイルの作成や情報の追加が出来ることがわかったと思います。

?コアモジュールosを使ってみる【解説】

os(オペレーティングシステム)のコアモジュールを使って機能を追加してみます。
パソコンのシステムの基本的な操作や情報取得を行うオブジェクトです。
今回は、
os.userinfo()
というメソッドを使います。
文字通りuserの情報を入手するのが目的です。

app.js
const fs = require('fs');
const os = require('os'); //fs同様 osをrequireする。

let user = os.userInfo(); //user変数に、os.userInfo()というuserの情報を入れる。
console.log(user);

ではプログラムを実行してみましょう。
スクリーンショット 2020-05-26 11.58.37.png
このように、自身のPCの情報などが取得できたかと思います。

では実際にosプロパティを使ってみましょう。
先程fsで記入したコードを活用します。

app.js
fs.appendFile("greeting.txt","こんにちは!"+ user.username +"さん!ようこそ",function(err){
  if(err){
    console.log(err);
}
});

node app.jsで実行してみましょう。
すると、
スクリーンショット 2020-05-26 12.06.17.png
このように、さきほど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.js
console.log(module); 

そして、ターミナルでapp.jsを立ち上げてください。
スクリーンショット 2020-05-26 12.52.37.png
注目してほしいのが、exports:{}という部分になります。
このexportsに付属するものとして定義することで他のファイルから呼び出せるようになります。

では、先程書いたconsole.log(module)は削除して、情報をcontens.jsの中に身長の情報定義していきたいと思います。

contents.js
module.exports.height = 178;
app.js
const contents = require("./contents.js");//先程requireしたcontents.jsを変数化してあげる。

fs.appendFile("greeting.txt","こんにちは!"+ user.username +"さん!あなたの身長は"+ contents.height +"cmです。"以下コード割愛
//このように、既存のコードに追加してnode app.jsを行ってください。

すると、greeting.txtに、こんにちは!〇〇さん!あなたの身長は178cmです。というように出力されていると思います。

関数の場合も同じです。

contents.js
module.exports.addNote =(){
console.log("addNote");
return "new note";
app.js
let result = contents.addNote();
console.log(result);

実行すると、コンソール上にaddNote new note と出力されていることが確認できます。

以上が、require関数による外部ファイルの処理、module.exportsによるファイルの外部化と呼び出しについての解説になります。

? npm init(初期化) install について【作成】

npm initを入力することでnodeに欠かせないnpmパッケージが利用できるようになります。
ターミナルにて

% npm init

と入力すると下の画像のように色々と聞かれます。
スクリーンショット 2020-05-26 15.20.55.png

これは、アプリケーションの初期設定となります。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.js
console.log("app.js起動中");
const = require("fs");
const contents = require("./contents.js");
contents.js
console.log("contents.js起動中");

こんな感じに、jsファイルの余分なコードを削除した状態から始めます。

? 次回予告

今回の内容を踏まえた上で
次回から本格的にメモアプリを作って行きたいと思います。

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

JavaScript+AzureFunctionsで簡易アクセス解析をやってみた

背景

静的なWEBサイト(HTML/CSS/JavaScript)で、ちょっとしたアクセス解析っぽいものを実現したくて試してみました。サーバーサイドで処理できる環境を準備したり、アクセス解析ツールを使ったりするのが、正攻法なのかもしれないですが、ローカルネットワーク内で運用されているWEBサイトで、尚且つ、環境まで触れない(いまは触らない)というのもあり、ちょっと何か出来ないかと思ってやってみたものです。

構成

  • HTML+CSS
  • JavaScritp(Fetch API)
  • Azure Functions
  • Azure Cosmos DB

image.png

流れ

  1. WEBページの操作(ボタンクリックなど)
  2. JavaScript(Fetch API)でHTTPリクエスト
  3. Azure FunctionsのHTTPトリガーで処理が起動
  4. 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();
    }
}

まとめ

データベース上にデータが格納されるまで確認できました。
可視化まわりは今後ちまちまやっていこうと思います。

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

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();
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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 && 条件式2

const 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

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

Node.jsで簡易サーバをつくる

install

$ sudo apt install nodejs npm
# 実行
$ node demo.js

webserver 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オブジェクトがクライアントからのリクエストを受け取ったときに発生するイベントで「ブラウザからサーバーにアクセスした時のサーバー側の処理」を組み込む。

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

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__のディレクトリごと削除する。

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

[Javascript]テンプレートリテラレル

説明

コンソールに表示する時に ${変数} で文字をつなげる。

#注意点
この書き方をする時はバックコーテーション「`」でくくる。

const name = "田中";

console.log(`こんにちは、${name}さん`);

出力結果
こんにちは、田中さん

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

連想配列から配列と一致する要素を抽出

やりたいこと

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()パターン

JavaScript
for(key in fruits){
    target.filter(function(data) {
        if(key == data){
            result[key] = fruits[key];
        }
    })
}

ちょっと強引な気もしますが、for in内を.filter()で書いたパターン。結果は同じです。

参考

https://www.sejuku.net/blog/27965

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

showModalDialogのように、サブウインドウを表示して戻り値を取得する

やりたいこと

window.showModalDialogのように、サブウインドウを表示して戻り値を取得する。

実装

opener.js
function 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.js
function 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があるが、標準仕様に含まれていないため使用しなかった。

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

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

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

アクセストークン等の外部に公開したくないものフロントを側で制御する方法

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ファイルを作成し、ファイル内に非公開情報を記載します。

.env
ACCESS_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
});

アクセストークンの部分がキチンと隠されているのが確認できました。

以上になります。
簡単に設定できるので、是非使ってみてください。

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

アクセストークン等の外部に公開したくないものでフロント側で制御する方法

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ファイルを作成し、ファイル内に非公開情報を記載します。

.env
ACCESS_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
});

アクセストークンの部分がキチンと隠されているのが確認できました。

以上になります。
簡単に設定できるので、是非使ってみてください。

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

【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')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

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

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で変換します。

製作物

オンラインデモ

https://infocom-tpo.github.io/facevrm/v0.2/

おわりに

facemeshの三次元座標で顔の向きを推定しました。
二次元座標から求めていた時と比べ、AIで三次元座標が解ることで、簡単に顔の向きが推定出来るようになりました。
ここ最近一気に変化したようで数年前の記事が既に古い情報となっていました。
今後も、定期的に情報を更新していければと思います。

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

【JavaScript】イベント全部ログ

取り急ぎ
HTMLの各要素の各イベント実行時に開発者ツールのコンソールにログを出力します。
ログが大量に出るのでご注意ください。
ページ解析用

javascript
var 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()));
  }
})

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

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をインストールします。
スクリーンショット (52).png

インストールが完了したら、プロジェクトのフォルダにある設定ファイル(csprojファイル)をテキストエディタで開きます。
以下のように、<CefSharpAnyCpuSupport>true</CefSharpAnyCpuSupport>をPropertyGroupタグの中に追加して保存します。
スクリーンショット (55).png

次に、App.configファイルを開き、configurationタグの中に

<runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <probing privatePath="x86"/>
    </assemblyBinding>
</runtime>

を追加して保存します。
スクリーンショット (53).png

これでCefSharpの導入は完了です。

Bingマップを表示するプログラム

それでは、プログラムを作っていきます。
以下のようにPanelやステータスバーなどを置きます。
CefSharpはツールボックスに表示できないので、Panelの内部に合わせて表示するようにします。
スクリーンショット (54).png

以下が今回のプログラムです。

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ファイルで定義した関数を呼び出しています。
実行するとこのようにマップが表示されます。
スクリーンショット (56).png
緯度と経度を入力して「移動」を押せば
スクリーンショット (57).png
このようにマークが付きます。

おわりに

以上、C#でFormにBingマップを表示する方法でした。
回りくどい方法でしたが、意外とちゃんと動くので是非試してみてください。

参考文献

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