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

Railsでページ遷移後にJavaScriptが実行されない問題の解消法

こんにちは、とくめいチャットサービス「ネコチャ」運営者のアカネヤ(@ToshioAkaneya)です。

Railsでページ遷移後にJavaScriptが実行されない問題について解説します。

Railsでページ遷移後にJavaScriptが実行されない問題の解消法

以下の行を削除します。

application.js
//= require turbolinks

turbolinksは初心者にとっては余計な悩みを増やすだけですので、削除するのが良いでしょう。

はてなブックマーク・Pocketはこちらから

はてなブックマークに追加
Pocketに追加

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

ワイ「いうても型なんて面倒くさいだけやろ?」

※登場人物等は全てフィクションです。

再就職したワイ

社長「やめ太郎くん」

ワイ「はい」

社長「今うちで開発中のブラウザゲームがあるんやけど」
社長「そのゲームの中に登場するジュースの自動販売機をプログラミングしてくれへんか」

ワイ「ええで」

社長「言語はTypeScriptや」

ワイ「・・・ええで(震え声)」

タイプスクリプト is 何

ワイ「なんやTypeScriptて」
ワイ「JavaScriptの打ち間違いか?」
ワイ「きっとそうやな」
ワイ「誰かがタイプミスで仕様書にTypeScriptって打ち込んでもうたんや」
ワイ「いわばタイポスクリプトや」

ハリー先輩「ちゃうで

ハリー先輩「知らん言葉が出てきたからって変な妄想で自己解決したらアカンで」
ハリー先輩「なにがタイポスクリプトや」
ハリー先輩「TypeScriptは静的型付けが出来るAltJSや」

ワイ「ほえー、静的なのあるJavaScriptでっか」

型があるメリット

ワイ「型があるとなんか嬉しいんでっか?」

ハリー先輩「けっこう嬉しいで」
ハリー先輩「例えば、引数として数値しか認めない関数とかを作れるんや」
ハリー先輩「あと返り値の型も指定できるで」

ハリー先輩「つまり関数や変数の使い方に関するルールを自分で決められるんや」
ハリー先輩「この関数は数値を受け取って文字列を返さなあきまへんよ
ハリー先輩「みたいなルールや」
ハリー先輩「そんで、その通りに実装せえへんとJSに変換してくれへんねん」

ハリー先輩「その関数が受け取るべき引数返すべき値・・・」
ハリー先輩「それがコード上に明記されることになるから、可読性も爆上がりや」
ハリー先輩「あとからプロジェクトに参加したメンバーも助かるやろ」

ワイ「ワイみたいなザコーダーが関数の使い方を間違えてウンコードを生産してしまうのを防いでくれるわけでんな」

ハリー先輩「せや」

さらに

ハリー先輩「自作の型も定義できるで」

ワイ「型を自作・・・」
ワイ「そもそも型を作りたいとか思ったことないですわ」
ワイ「型が作れると何が嬉しいんでっか」

ハリー先輩「めっちゃ嬉しいで
ハリー先輩「例えばやな」
ハリー先輩「ジュースの自販機の例やと・・・」

type Coin = 10 | 50 | 100 | 500;

ハリー先輩「こうや」

ハリー先輩「コインいうのは10円玉、50円玉、100円玉、500円玉の事ですよ
ハリー先輩「っちゅう意味や」
ハリー先輩「ジュースの自販機には1円玉や5円玉は入れられへんからな」

ハリー先輩「そんで次はこうや」

type Bill = 1000;

ハリー先輩「これは」
ハリー先輩「紙幣(Bill)いうのは千円札の事ですよ、いうことや。」
ハリー先輩「ジュースの自販機には二千円札、五千円札、一万円札は入らへんからな」

ハリー先輩「さらにこうや」

type Money = Coin | Bill;

ハリー先輩「お金いうのは、硬貨と紙幣のことですよ、と」

ハリー先輩「こうやって、硬貨として受け取るべき値紙幣として受け取るべき値・・・」
ハリー先輩「そして自販機に投入されるお金としてあるべき値
ハリー先輩「そんなんを自分で定義できるんや」

ハリー先輩「そうすることで、Money型の引数しか受け取らへん関数とかも作ることが出来るわけや」
ハリー先輩「↓こんな感じや」

// 引数 money には Money型の値しか渡せない
function insertMoney (money: Money) { 
    /* ・・・・ */
}

insertMoney(5); // 5はMoney型と一致しないのでコンパイルエラー!

ハリー先輩「変数や関数のあるべき使い方自分で定義して」
ハリー先輩「その通りに実装せんと前に進めへん」
ハリー先輩「これだけでちょっとしたテスト駆動開発みたいになるやん?」

ワイ「ほんまや・・・」

ハリー先輩「場合にもよるけど」
ハリー先輩「仕様として求められとる要件を」
ハリー先輩「割とそのまんまの感じでコードにできたりするから」
ハリー先輩「訳が分からなくなりにくいっちゅうのもメリットやな」

type Coin = 10 | 50 | 100 | 500;
type Bill = 1000;
type Money = Coin | Bill;

ワイ「たしかに↑これとか一目瞭然ですわ・・・」

ハリー先輩「TypeScriptをブラウザ上で気軽に試せるサイトもあるから、まずは触ってみいや」

ワイ「さっそくやってみますわ!」

Let's TypeScript

ワイ「ポチポチポチ・・・っと」
ワイ「なんや、おかしいな」
ワイ「ハリー先輩」
ワイ「このブラウザ上のエディタ、なんかオカシイでっせ」
ワイ「エラーばっかり吐きよる」

ハリー先輩「いや、ちゃうな」
ハリー先輩「ここ、moneyのスペルがmoonyになっとるで」
ハリー先輩「ムーニーて」
ハリー先輩「紙オムツやないかい」

ワイ「いや、パンツタイプもありまっせ」

ハリー先輩「どうでもええわ
ハリー先輩「お前が一番タイポスクリプトしとるやないかい」

〜おしまい〜

オススメ記事

TypeScriptの型演習
TypeScriptの型演習(解答・解説編)

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

go言語でhtmlから取得したデータをjson形式で保存する

Goの勉強録

昨日あたりからGoに対するエンジニアの成長曲線の傾きが正に大きい部分に入ったのでは?と思うくらいGoを書くのが開発環境構築したてのころと比べて苦じゃ無くなりました。以前はエラーが出ると何処エラーしてるんやとか、うーんわからんみたいな感情があったのですが、結構早くエラーにならないように記述する事ができるようになりました。(潜在的なバグなどはわかりませんけれど。。)
昨日JavaScriptから送られて来るリクエストからデータを取得してコンソールに出力することができ、取得したこのデータを煮るなり焼くなりと言ったのでまずは手始めに、JSON形式で外部ファイルに保存しようと思いました。
あっさりJSONファイルにデータを保存することができたのでブログに残しておきます。

ちなみに過去のGo勉強録記事は下から飛べるようにしています。

過去の記事

本題

go言語はPHPやruby on rails、Node.js(サーバサイド)と言った言語と同じようにサーバサイドのプログラムを記述することができます。サーバサイドのプログラムを記述できるということは、サーバから必要なデータを引っ張ってきたり、データを書き込んだりするのが主な動作になります。
そこで今回はデータを書き込むという如何にもサーバサイドの動作を実装しました。

ちなみにワークスペースの階層は以下のようになっています。Dataの中にdata.jsonが作られるようにします。(新幹線で作業をしたのでAngularのフレームワークをHTMLの中にぶっこんでいます。)
昨日のものを少しカスタマイズした感じですね。

workspace/
  ├ Data/ 
  │
  ├ HTML/
  │    ├ index.html
  │    ├ test.js
  │    ├ Angular.min.js
  │
  ├ POST/
  │    ├ Process.go
  │
  ├ Host.go

ソースコード

Host.go
サーバを立てて、来るリクエストに対してデータを取得し、jsonに書き込むという処理が記述されています。

Host.go
package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"

    "./Post"
)

func main() {
    HostingServer()
}

// Json形式でデータを保存するようにHandleを定義
func SaveJson() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // ファイルの生成
        file, err := os.Create("./Data/data.json")
        if err != nil {
            fmt.Println("ファイルの生成に失敗しました")
        }
        defer file.Close()
        // Process.goからGetDataを呼び出し、リクエストのデータをData形式で取得する
        var data = Post.GetData(w, r)
        // 出力用にData形式のdataをjson形式にencode
        encodedjson, _ := json.Marshal(&data)
        // 正常に生成されたファイルに書き込み
        file.Write(([]byte)(string(encodedjson)))
        // エンコードされたデータをコンソールに表示
        fmt.Println(string(encodedjson))
    })
}

// サーバを立てる処理
func HostingServer() {
    // Handlerの追加
    SaveJson()
    dir, _ := os.Getwd()
    // サーバを立てる為にstripprefixを設定
    fileserver := http.FileServer(http.Dir(dir + "/HTML/"))
    stripprefix := http.StripPrefix("/datatest/", fileserver)
    http.Handle("/datatest/", stripprefix)
    // serve :8080
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Process.go
リクエストのデータをData形式で取得し、それを返す処理をしています。

Process.go
package Post

import (
    "encoding/json"
    "fmt"
    "net/http"
    "reflect"
)

type Data struct {
    Name   string
    Age    int
    Gender string
}

func GetData(w http.ResponseWriter, r *http.Request) Data {
    // parse
    r.ParseForm()
    var data = Data{}
    decoder := json.NewDecoder(r.Body)
    decoder.Decode(&data)
    fmt.Println(reflect.TypeOf(data))
    return data
}

index.html
表示用のスクリプト

index.html
<!DOCTYPE html>
<html ng-app="idx">
    <head>
        <title>FileIO</title>
        <script src="./Angular.min.js"></script>
        <script src="./test.js"></script>
    </head>
    <body ng-controller="form">
        <input type="text" ng-model="name">
        <br />
        <input type="number" ng-model="age">
        <br />
        男:<input type="radio" ng-model="gender" value="man">
        <br />
        女:<input type="radio" ng-model="gender" value="woman">
        <br />
        <input type="button" ng-click="req()" value="送信">
    </body>
</html>

test.js
index.htmlでボタンを押された時に呼び出すイベントリスナーを定義しています。

test.js
var idx = angular.module("idx",[])

idx.controller("form",function($scope,$http){
    $scope.req = function(){
        // 送信用のデータ
        data = {
            name: $scope.name,
            age: $scope.age,
            gender: $scope.gender
        }
        // POSTでhttp通信
        $http({
            method: 'POST',
            url: '../Host.go',
            contentType: 'application/JSON',
            data: data
        }).then(function DoneCallback(res){
          console.log("通信成功");
          console.log(res)
        },function failCallback(res){
            console.log("通信失敗");
        });
    }
})

go run Host.goを実行し、http://localhost:8080/datatest/にアクセスすると
簡易的な入力フォームができています。
そこに値を入力しボタンを押すとData/data.jsonが生成され、中身は

data.json
{"Name":"test","Age":20,"Gender":"woman"}

とJSON形式で保存されます。

まとめ

go側で無事データをJSON形式で保存する事ができました。(ようやく面白くなってきたぞ(-ω- )o)
次はファイルの読み込みなどを書くと思います。後々DBなどの接続や、並列処理と言った所の記事がかけていけたらなと思っています!(勉強することは山積みですな)
まだまだ、go初心者なので間違っている部分があれば、気軽にコメントを描いてください!

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

プログラミング学習記録17〜変数はletでおーけー〜

今日やったこと

  • Udemyの「Web開発入門完全攻略コース - プログラミングをはじめて学び創れる人へ!未経験から現場で使える開発スキルを習得!」のセクション9の89~103

今日はjavascriptの基礎を学びました。

以下、パートごとのメモです。

セクション9javaScript入門

89.イントロダクション-javascript入門-

セクション9の概要。

90.Javascriptの歴史

1995年に生まれた言語だと知りました。

この講座ではES3、ES5を学習します。

91.JavaScriptの開発環境構築

開発環境はgoogle chromeとatomのみ。

92.はじめてのjavascript

Hello World!を出力しました。

ディベロッパーツールのconsoleから出力されていることを確認しました。

93.JavaScriptのエラーを自力で解決するための方法

ディベロッパーツールのコンソールを使って、エラーメッセージを表示する方法について学びました。

エラーメッセージはMDNのリファレンスにまとめられています。

JavaScript エラーリファレンス(https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Errors)

94.コメント

コメントアウトのやり方についての動画です。

HTMLやCSS同様に、atomエディタの場合はcommandキー+/キーで簡単にコメントアウトすることができます。

95.演算

四則演算と余りの計算方法についてです。
これはProgateで散々やったので大丈夫でした。

96.変数

Progateではlet=“”で変数を指定したが、動画ではvar x=“”で指定していました。

「この違いはなんなんだ?」と疑問に思い、調べたらQiitaの過去記事に答えが載っていました。

letとvarの違い(https://qiita.com/y-temp4/items/289686fbdde896d22b5e)

一言で言うと、変数のスコープがブロックスコープか関数スコープかの違いです。

ES6からはletが使えるため、今後はvarではなく好んでletが使われるようになるかと思います。

この講座ではES3、ES5なのでvarで定義していますが、ES6の場合はProgateでやったようにletで定義する方が好ましいようです。

変数の記述法、ルールで細かい違いはありましたが、概念に大きな違いはないので、そういうものなんだな〜という感じで進んでいっていいと思います。

97.インクリメントとデクリメント

インクリメントとは、値に1を足すこと。
x=x+1
x+=1
x++
というように3種類の表記法があります。

デクリメントとは、値から1を引くこと。
x=x-1
x-=1
x--
というようにインクリメント同様に、3種類の表記法があります。

98.条件分岐

条件分岐の概要です。

99.条件分岐if/else

If、elseに関する動画です。
ここはProgateをしっかりやってれば流し見で大丈夫です。

100.条件分岐if/if else/else

If、else if、elseに関する動画です。
ここもProgateをしっかりやっている場合は流し見で大丈夫です。

101.条件分岐switch

Switchに関する動画です。
ここもProgateをしっかりやっている場合は流し見で大丈夫です。

102.演習:テーマパークの入場料計算

年齢によって異なる入場料を出力するプログラムを作る演習です。

103.演習回答:テーマパークの入場料計算

102の回答です。無事正解することができました。

If、else If、elseが理解できてれば特に問題なくできる課題です。



ProgateではES6を学んでいたので、今回ES5を学んでみて一部違いに戸惑う部分もありましたが、大まかな概念は一緒だと思いました。

明日からも引き続きプログラミング学習頑張ります。

おわり

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

JavaScriptで無限ループを書くと逮捕される

元ネタ

NHKニュースより
https://www3.nhk.or.jp/lnews/kobe/20190304/2020003239.html
クリックすると同じ画面が表示され、消えなくなる不正なプログラムのアドレスをインターネットの掲示板に書き込んだとして、13歳の女子中学生が兵庫県警に補導されました。

JavaScriptで無限ループを書くと逮捕される

怖い時代になりました:innocent:
無限ループのバグは重い罪みたいです。新人プログラマは注意しないと警察のお世話になってしまいますね・・・

例えば勉強がてらFizzBuzzを実装したとします

新人プログラマ「繰り返しはいろいろ書き方があるみたいだけどまずはwhileを使ってみよう」

function FizzBuzz(){
    while(i<20){
        if (i%15 == 0){
            alert("FizzBuzz");
        }
        else if (i%3 == 0){
            alert("Fizz");
        }
        else if (i%5 == 0){
            alert("Buzz");
        }
        else {
            alert(i);
        }
    }
}

新人プログラマ「よし多分実装できたぞ!WEBページに乗せてみよう」

<html>
<head>
<script>
function FizzBuzz(){
    var i = 0
    while(i<20){
        if (i%15 == 0){
            alert("FizzBuzz");
        }
        else if (i%3 == 0){
            alert("Fizz");
        }
        else if (i%5 == 0){
            alert("Buzz");
        }
        else {
            alert(i);
        }
    }
}
</script>
</head>
<body onload=FizzBuzz()>
</body>
</html>

新人プログラマ「えーと。htmlのbodyでonloadで呼び出すようにして・・・WEBに公開っと」

数日後・・・

警察「悪質な不正ソフトウェアを作り配信しましたね?署までご同行ください」

・・・

・・・

この新人プログラマはWEBページ上で繰り返しalertを出したことで逮捕されてしまいました

ソースコードをよく見るとwhile文の中で i が変化することがないですから
無限ループしてしまいそうです。

これは重い罪ですね・・・無限ループは不正なプログラムですから・・・

一回でもテスト実行していればこんな事故は防げただけに非常に残念です
1とにかく「e」を無限に書いていても、もしかしたら逮捕されてしまうかもしれません・・・・

おや、誰か来たようだ

 

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

サクラエディタで数独を解く

環境

サクラエディタ Ver. 2.2.0.1
Windows 10

数独ソルバーマクロ

動機は謎。

前に普通にJavaScriptで書いたのをサクラエディタのマクロで使えるJScriptに移植。
JScriptだと文字列の添え字アクセス出来ないんだろうか…?(charAt()で代用した)

長いので、実用の項目をクリップしてスキップ。

sudoku_solver.js
function idxToBlock(c, r) {
  return (r / 3 | 0) * 3 + (c / 3 | 0);
};

function check(arr) {
    var chist = [], rhist = [], bhist = [];
    for (var i = 0; i < 9; ++i) {
        //chist[i] = new Array(9).fill(0);
        //rhist[i] = new Array(9).fill(0);
        //bhist[i] = new Array(9).fill(0);
        chist[i] = new Array(9);
        rhist[i] = new Array(9);
        bhist[i] = new Array(9);
        for (var j = 0; j < 9; ++j) {
            chist[i][j] = 0;
            bhist[i][j] = 0;
            rhist[i][j] = 0;
        }
    }
    var maxcnt = 0;
    for (var r = 0; r < 9; ++r) {
        for (var c = 0; c < 9; ++c) {
            var val = arr[r * 9 + c] - 1;
            if (val >= 0) {
                maxcnt = Math.max(++chist[c][val], maxcnt);
                maxcnt = Math.max(++rhist[r][val], maxcnt);
                maxcnt = Math.max(++bhist[idxToBlock(c, r)][val], maxcnt);
            }
        }
    }
    var ret = {
        chist: chist,
        rhist: rhist,
        bhist: bhist,
        valid: (maxcnt >= 2) ? false : true
    };
    return ret;
};

function solve(inArr){
  var Q = [], ans = null;
  Q.push(inArr.concat());
  while ( Q.length > 0 ) {
    var arr = Q.pop(), i = 0;
    if ( false == check(arr).valid ){
      continue;
    }
    for (i = 0; i < 9 * 9; ++i) {
      if ( 0 === arr[i] ) {
        for (var j = 1; j <= 9; ++j) {
          arr[i] = j;
          Q.push(arr.concat());
        }
        break;
      }
    }
    if ( 9 * 9 === i ) {
      ans = arr;
      break;
    }
  }
  if( ans ){
    return ans;
  }
  else {
    return null;
  }
}

// ここからエディタマクロ
var text = Editor.GetSelectedString(0);
lines = text.split("\n");

var arr = [];
for( var i = 0; i < lines.length && i < 9; ++i ) {
  for( var j = 0; j < lines[i].length && j < 9; ++j ) {
    var num = parseInt(lines[i].charAt(j));
    if( "?" === lines[i].charAt(j) ){
      arr[ i * 9 + j ] = 0;
    }
    else if( 0 <= num && num <= 9 ){
      arr[ i * 9 + j ] = num;
    }
    else{
      arr[ i * 9 + j ] = 0;
    }
  }
}

var ans = solve(arr);

var outText = "";
for(var i = 0; i < 9; ++i){
  for(var j = 0; j < 9; ++j){
    outText += ans[ i * 9 + j ]
  }
  outText += "\n"
}

Editor.InsText( outText );

実用

以下のように数独の問題を入力し文字列選択する。
(コードを見ればわかるが、parseIntで0に変換される文字列なら"?"じゃなくても良い。"_"とかでも良い。)

image.png

マクロ実行。(「共通設定」→「マクロ」から登録でも良い)

image.png

10秒ぐらい掛かるので待つ。

image.png

自動で問題が解ける。

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

alert無限ループの件に便乗して警察を試してみる

例のalertの無限ループの件のパロディです。

内容

解説

while (0.1 + 0.2 === 0.3) {
   alert('何回閉じても無駄ですよ~');
}

分かっている方も多いと思いますが、一応無実を証明するために説明を。

0.1 + 0.2 === 0.3は数学的には常に真なので、一見無限にalertを表示し続けるかのように見えます。しかし、JavaScriptの数値はIEEE 754 64bit浮動小数点数なので、0.1 + 0.2 === 0.30000000000000004であり、無限ループどころか一度もalertしません。

ところが、条件式に0.2 + 0.2 === 0.4とうっかり書いてしまった途端、無限alertになって警察に逮捕されてしまいます(誇張表現)。

結論

最近のブラウザでは、アラートが表示されていてもタブを閉じられるものがほとんどですが、警察が未だにIEを使っている可能性を考慮して、alertを使う時は十分に気を付けましょう。

バグでalert無限ループに陥ってしまうと、警察が手錠を持って押しかけて来るかもしれません。

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

Sass map eachを使って色を指定する

以外と知らない人が多そうなので出力します。
各要素に応じて色を定義している場合があると思います。
例えば信号の状態を表現する以下のようなcssがあります。

.signal-stop {
  color: #dd0000;
}

.signal-caution {
  color: #f4e542;
}

.signal-go {
  color: #5ce053;
}

これをmapを使うと以下のように記述することができます。

$map: (stop: #dd0000, caution: #f4e542, go: #5ce053);

@each $state, $color in $map {
    signal-#{$state} {
        color: $color;
    }
}

例えば、これに信号が停電するような場合を追加する場合は

$map: (stop: #dd0000, caution: #f4e542, go: #5ce053, blackout: #000000 );

とすれば済みそうです。実際のコードでは状態が8つあったので大分スッキリさせることができました。

https://sass-lang.com/documentation/file.SASS_REFERENCE.html#each-multi-assign
https://sass-lang.com/documentation/file.SASS_REFERENCE.html#lists
https://www.sassmeister.com/

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

webpack ES2018記述でIE11対応 そしてjavascriptファイル分けてコンパイル

webpackに取り組みの理由

ずっと後回しにしておりましたが「webpack」を取り入れないと、javascirptの記述方法に遅れを取ってしまうなと感じてきました。

始めないとと思った理由

  • 「アロー関数」「async/await」「スプレッド演算子」などを使いたい
  • vue.js単一ファイルコンポーネント、Typescriptを将来的には使いたい、そのためのコンパイルするための練習
  • ie11がなかなか消えてくれないから対応しなければならず、そのためのBABELとpolyfill使いたい

Screenshot(1).png

まだIE11人口は13%もいらっしゃいました:joy_cat:
IE11と一緒に新しい時代に向かっていきたいと思いました。

【参考サイト】→https://webrage.jp/techblog/pc_browser_share/

webpackでやりたいこと

野望はたくさんありますが、あれもこれもと手を伸ばさないで、まずは最低限の実現を目指します。

  • Babelする
  • polyfillに対応させて、IE11に対応するようにする。
  • 各ページに合わせて、javascriptファイルを分けてコンパイルする。(例トップページはfrontpage.js、アバウトページはabout.jsとコンパイルされるようにする)

※今回やらないこと、vueファイルのコンパイル・sassファイルをcssにコンパイル
※nodeをインストールしていること、そしてnpmも使えること前提で進みます。

ファイル準備

最終的なファイル郡

index.html
package.json
package-lock.json(npmインストールしたときに生成されます)
webpack.config.js
| - about - index.html
| - src - frontpage.js
- about.js
- partsフォルダ(自身で設定したclass等のパーツ)

| - node-module(npmインストールしたときに生成されます)
| - dist(コンパイルしたときに生成されます)

package.json

package.json
{
  "scripts": {
    "build": "webpack",
    "watch": "webpack -w"
  },
  "devDependencies": {
    "@babel/core": "^7.2.2",
    "@babel/polyfill": "^7.2.5",
    "@babel/preset-env": "^7.3.1",
    "babel-loader": "^8.0.5",
    "webpack": "^4.29.0",
    "webpack-cli": "^3.2.1"
  },
  "private": true,
  "dependencies": {
    "axios": "^0.18.0",
    "lodash": "^4.17.11"
  }
}

※「axios」「lodash」はよく使うので入れております、お好みで:relaxed:

webpack.config.js

jsファイルを分けたいためにページ毎にエントリーポイントを設けております。
今回はfrontpage.jsとabout.jsだけですが、ページが増えてきた際にはentryオブジェクトに追加をしていってください。

webpack.config.js
module.exports = {
  // 公開時には[production]
  // 制作段階では[development]
  mode: 'development',
  output: {
    path: __dirname + '/dist/',
    filename: '[name]'
  },
  entry: {
    //各ページ毎に記述を追記していく
    'frontpage.js': './src/frontpage.js',
    'about.js': './src/about.js',
  },
  module: {
    rules: [
      {
        // 拡張子 .js の場合
        test: /\.js$/,
        use: [
          {
            // Babel を利用する
            loader: 'babel-loader',
            // Babel のオプションを指定する
            options: {
              presets: [
                // プリセットを指定することで、ES2018 を ES5 に変換
                '@babel/preset-env',
              ]
            }
          }
        ]
      }
    ]
  }
};

★★★やることはこれだけ★★★

上記の説明を読みたくないという人はここから

私のgithubのページ
github

①このgithubページを目的のディレクトリでcloneします。
②package.jsonに記述されているライブラリをインストールするために
npm installしましょう。

③サイト制作時はjsファイルを更新する度にコンパイルをさせます。
npm run watch
※この際webpack.config.jsのmodeがdevelopmentであることを確認しましょう。

④デプロイ(本番公開)するときのファイル生成はnpm run build
※この際webpack.config.jsのmodeがproductionであることを確認しましょう。

感想

npmの使い方に戸惑ったり、webpack.configの記述方法に手間をとってしまいました。
また少しずつ勉強していきます。

そして、webpackが使えるようになってからjavascriptライフが充実しております、
私のようにずっと導入を避けていた人もこの記事を参考に始めていただければ嬉しい限りです。

参考サイトと賛辞

初めて学ぶ上で参考にした記事を載せておきます。
大変に参考になりました、記事を執筆された方にお礼を申し上げたいと思います。

最新版で学ぶwebpack 4入門 - Babel 7でES2018環境- ICS MEDIA
ES2015(ES6)+webpack+babel-loaderで開発環境を構築
Webpack (v4) で複数のJavaScriptファイル、CSSファイルを分けてビルド

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

TypeScriptでDate型に曜日を返すメソッドを追加した with GAS

概要

clasp使ってGASをtsで書いていたのですが、Date型に日本語の曜日を返却するメソッドがなかったので、拡張メソッド(厳密にはモンキーパッチ)で実装してみました

そもそも拡張メソッド(モンキーパッチ)とは?

拡張メソッドはC#、Swift、Kotlinなどで使える、既存のクラスのメソッドを拡張する機能です。内部的にはstaticメソッドとしてコンパイルされます。

モンキーパッチも既存のクラスのメソッドを拡張するという意味では変わりませんが、クラス(jsの場合はprototype)に直接メソッドを追加している点が違います。

Effective JavaScriptにやみくもなモンキーパッチを避けると書いてあったので使うのを避けていましたが、今回はほぼ趣味で書いてるようなもんだし規模が小さいし別にいいかな…って思ったので実装してみました

実装

type Week = "日" | "月" | "火" | "水" | "木" | "金" | "土"

const WEEKS: Array<Week> = ["日", "月", "火", "水", "木", "金", "土"]

interface Date {
  getWeek(): Week
}

Date.prototype.getWeek = function() {
  return WEEKS[this.getDay()]
}

jsならば以下で動きます

const WEEKS = ["日", "月", "火", "水", "木", "金", "土"]

Date.prototype.getWeek = function() {
  return WEEKS[this.getDay()]
}

解説

以下のように、既存のクラスをinterfaceとして書くことで、クラスにメソッドの情報を登録することができます。

interface Date {
  getWeek(): Week
}

ただしこの状態だとjsとしての実装がないため、Date.prototypegetWeekを代入してやります。

type Week = "日" | "月" | "火" | "水" | "木" | "金" | "土"

const WEEKS: Array<Week> = ["日", "月", "火", "水", "木", "金", "土"]

Date.prototype.getWeek = function() {
  return WEEKS[this.getDay()]
}

これでスプレッドシートのGAS書くときに多少は楽できるかなーって思います

余談

てかGASでもモンキーパッチできるんだね?

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

alert無限ループは不正プログラムとして逮捕されるらしいので警察にゴールドバッハ予想を証明してもらおう

これは何?

これは以下のツイートに触発されて作ったn回目のアラートダイアログで2n+2を二つの素数の和で表せるかを全探索して表せた場合もう一度アラートダイアログを表示するJavaScriptプログラムです

つまりどういうこと?

経緯

エンジニアの方ならわかると思いますが、今日(3月5日)の朝から、Twitterなどで、不正プログラムを書き込んだ疑いで女子中学生が補導され、男性2人が書類送検されるという方針だというニュースがトレンド入りし、話題になりました。(確認したら今もトレンド入りしてるようです。ある程度パーソナライズされてるらしいので全員がそう見えるかは定かではありませんが)
ソース:不正プログラム書き込み疑い補導|NHK 兵庫県のニュース

動画や文脈から推測すると、これは再帰的にalertを実行する処理で、簡単に再現するとwhile(true) alert('無限ループだよ!')のような感じになります。

仮に今回警察が無限ループだから不正プログラムだと判断した仮定すると、警察が無限なのか有限なのか検証してくれると解釈ができ、未だに証明されてないゴールドバッハの予想(後述)を元にalertを表示したら警察がゴールドバッハの予想を証明してくれるのではないかというのが上のツイートの趣旨です

ゴールドバッハの予想とは

ゴールドバッハの予想とは全ての 2 よりも大きな偶数は2つの素数の和として表すことができる。という予想です。(ウィキペディアより)
つまり2より大きな偶数、4, 6, 8, 10...は2つの素数の和として表せるというまだ証明されていない予想です。
理解しやすいように何個か例を載せておきます。

 4 = 2 + 2
 6 = 3 + 3
 8 = 3 + 5
10 = 7 + 3 = 5 + 5
12 = 5 + 7

出てくる2, 3, 5, 7は全部素数なのがわかると思います。これを実装して警察がこんにちはしてきたら、それはつまり警察がゴールドバッハの予想を無限だと認めたということなので、日本の警察が認めているのだからゴールドバッハの予想は真だということができます(できません)

それから今回は警察にゴールドバッハ予想を証明してもらいますが、無限か有限化の形に落とし込めればどんな未解決問題でも警察に証明させることができるので、他にもいろいろ実装して数学会に貢献しましょう!

ソース

長いしクソコードなのでたたみます(クリックで開く)

index.js

function getPrimes(n) {
  var sieve = [], i, j, primes = []
  for (i = 2; i <= n; ++i) {
    if (!sieve[i]) {
      primes.push(i)
      for (j = i << 1; j <= n; j += i) sieve[j] = true
    }
  }
  return primes
}

function goldbach(initial) {
  var list = getPrimes(initial)
  while (list.length > 0) {
    var item = list.shift()
    if (item * 2 === initial) return item + " + " + item
    var index = list.indexOf(initial - item)
    if  (index !== -1) {
      var itemPair = list.splice(index, 1)[0]
      return item + " + " + itemPair
    }
  }
}

function main(n = 1) {
  var initial = 2 * n + 2
  var result = goldbach(initial)
  if (result && initial < 100) {
    console.log(initial + ' = ' + result)
    main(n + 1)
  }
}

main(1)

div.js

(function main(n) {
  var result = (function () {
    var arr=[2];for(i=3;i<n;i+=2){for(no=1,j=2;j<i;j++)i%j==0&&(no=0);no&&arr.push(i)}
    while (arr.length > 0) {
      var item = arr.shift()
      if (item * 2 == n) return item + " + " + item
      var index = arr.indexOf(n - item)
      if  (~index) {
        var itemPair = arr.splice(index, 1)[0]
        return item + " + " + itemPair
      }
    }
  })()
  if (result && n < 100) {
    console.log(n + " = " + result)
    main(n + 2)
  }
})(4)

minified.js

!function r(n){var f=function(){var r=[2];for(i=3;i<n;i+=2){for(no=1,j=2;j<i;j++)i%j==0&&(no=0);no&&r.push(i)}for(;r.length>0;){var f=r.shift();if(2*f==n)return f+" + "+f;var o=r.indexOf(n-f);if(~o)return f+" + "+r.splice(o,1)[0]}}();f&&(alert(n+" = "+f),r(n+2))}(4);

それぞれのファイルの説明

index.js

  • 最初にコピペしながら雑に作ったもの

dev.js

  • index.jsをできるだけ圧縮したもの
  • (1ツイートに収めようとして頑張ったけど挫折した残骸)
  • (まぁブックマークレットとして使うなら短いほうがいいよね?ね?)

minified.js

  • dev.jsから10回の制限を無くし
  • console.logをalertに置き換え
  • さらにminifyしたもの

更に短いもの

  • @Yuki_jukjisさんがさらに圧縮してくれました
  • このスレッドにdev.js相当のものや、minified.js相当のものがあるので短いほうがいい方はぜひ使ってみてください

使い方

index.js

  • index.jsを読み込めばmain(1)で実行できます
  • (for文ではなく再帰で実行してるので引数の1は重要です)
  • 開発しやすいようにconsole.log出力、10回のループ制限があります
  • カスタマイズもしやすいと思うのでいろいろ活用できると思います()

dev.js

  • こんな無理やり圧縮したクソコード忘れて..

minified.js

  • ブラウザで実行するのに特化したバージョンです。
  • やっぱり短いほうがいいですよね?
  • ブックマークレットなどにいかがでしょうか?
  • 他の2つと違ってループ回数制限もないのでちゃんとゴールドバッハの予想が検証できるね!

あとがき

これらは全部、自己責任で使ってくださいね! これを作るきっかけにもなった、再帰alertスクリプトのアドレスを掲示板に書き込んだ女子中学生や男性が補導、書類送検される方針だということを忘れないでね!!

※この投稿は当初警察に目をつけられるのが怖くてQiitaのガイドラインに反するような気がしてGistの方に投稿しましたが、意外と評価が良く考え直してみたらQiitaに時事ネタが上がることもよくあり、この逮捕の問題点をより多くの人に認識してもらうことも重要だと思ったので、Gistを加筆編集して投稿しています。
オリジナルバージョンを確認したい、より古いリビジョンを確認したい人はこちらからどうぞ。
JavaScript Alert Recursion with Goldbach's conjecture
※Gistのほうはこの記事のリンクを追加したらこっちをメインに更新していきます

この逮捕の問題点がなるなる人や僕に興味がある人は僕のTwitter @yuta0381を見てみてもいいかも知れません。直近では警察、法律の問題視するツイートやリツイートなどをしています。ただしあくまで個人の意見なので過信と鵜呑みは禁物です。異論は歓迎ですがなるべく建設的な議論をしましょう!
※注意 この件に特化したアカウントでもないので、関係ないツイートも普通にしますのでそれが不快な人は見ないほうがいいかも知れません

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

2つ素数alert無限ループは不正プログラムとして逮捕されるらしいので警察にゴールドバッハ予想を証明してもらおう

これは何?

これは以下のツイートに触発されて作ったn回目のアラートダイアログで2n+2を二つの素数の和で表せるかを全探索して表せた場合もう一度アラートダイアログを表示するJavaScriptプログラムです

つまりどういうこと?

経緯

エンジニアの方ならわかると思いますが、今日(3月5日)の朝から、Twitterなどで、不正プログラムを書き込んだ疑いで女子中学生が補導され、男性2人が書類送検されるという方針だというニュースがトレンド入りし、話題になりました。(確認したら今もトレンド入りしてるようです。ある程度パーソナライズされてるらしいので全員がそう見えるかは定かではありませんが)
ソース:不正プログラム書き込み疑い補導|NHK 兵庫県のニュース

動画や文脈から推測すると、これは再帰的にalertを実行する処理で、簡単に再現するとwhile(true) alert('無限ループだよ!')のような感じになります。

仮に今回警察が無限ループだから不正プログラムだと判断した仮定すると、警察が無限なのか有限なのか検証してくれると解釈ができ、未だに証明されてないゴールドバッハの予想(後述)を元にalertを表示したら警察がゴールドバッハの予想を証明してくれるのではないかというのが上のツイートの趣旨です

ゴールドバッハの予想とは

ゴールドバッハの予想とは全ての 2 よりも大きな偶数は2つの素数の和として表すことができる。という予想です。(ウィキペディアより)
つまり2より大きな偶数、4, 6, 8, 10...は2つの素数の和として表せるというまだ証明されていない予想です。
理解しやすいように何個か例を載せておきます。

 4 = 2 + 2
 6 = 3 + 3
 8 = 3 + 5
10 = 7 + 3 = 5 + 5
12 = 5 + 7

出てくる2, 3, 5, 7は全部素数なのがわかると思います。これを実装して警察がこんにちはしてきたら、それはつまり警察がゴールドバッハの予想を無限だと認めたということなので、日本の警察が認めているのだからゴールドバッハの予想は真だということができます(できません)

それから今回は警察にゴールドバッハ予想を証明してもらいますが、無限か有限化の形に落とし込めればどんな未解決問題でも警察に証明させることができるので、他にもいろいろ実装して数学会に貢献しましょう!

ソース

長いしクソコードなのでたたみます(クリックで開く)

index.js

function getPrimes(n) {
  var sieve = [], i, j, primes = []
  for (i = 2; i <= n; ++i) {
    if (!sieve[i]) {
      primes.push(i)
      for (j = i << 1; j <= n; j += i) sieve[j] = true
    }
  }
  return primes
}

function goldbach(initial) {
  var list = getPrimes(initial)
  while (list.length > 0) {
    var item = list.shift()
    if (item * 2 === initial) return item + " + " + item
    var index = list.indexOf(initial - item)
    if  (index !== -1) {
      var itemPair = list.splice(index, 1)[0]
      return item + " + " + itemPair
    }
  }
}

function main(n = 1) {
  var initial = 2 * n + 2
  var result = goldbach(initial)
  if (result && initial < 100) {
    console.log(initial + ' = ' + result)
    main(n + 1)
  }
}

main(1)

div.js

(function main(n) {
  var result = (function () {
    var arr=[2];for(i=3;i<n;i+=2){for(no=1,j=2;j<i;j++)i%j==0&&(no=0);no&&arr.push(i)}
    while (arr.length > 0) {
      var item = arr.shift()
      if (item * 2 == n) return item + " + " + item
      var index = arr.indexOf(n - item)
      if  (~index) {
        var itemPair = arr.splice(index, 1)[0]
        return item + " + " + itemPair
      }
    }
  })()
  if (result && n < 100) {
    console.log(n + " = " + result)
    main(n + 2)
  }
})(4)

minified.js

!function r(n){var f=function(){var r=[2];for(i=3;i<n;i+=2){for(no=1,j=2;j<i;j++)i%j==0&&(no=0);no&&r.push(i)}for(;r.length>0;){var f=r.shift();if(2*f==n)return f+" + "+f;var o=r.indexOf(n-f);if(~o)return f+" + "+r.splice(o,1)[0]}}();f&&(alert(n+" = "+f),r(n+2))}(4);

それぞれのファイルの説明

index.js

  • 最初にコピペしながら雑に作ったもの

dev.js

  • index.jsをできるだけ圧縮したもの
  • (1ツイートに収めようとして頑張ったけど挫折した残骸)
  • (まぁブックマークレットとして使うなら短いほうがいいよね?ね?)

minified.js

  • dev.jsから10回の制限を無くし
  • console.logをalertに置き換え
  • さらにminifyしたもの

更に短いもの

  • @Yuki_jukjisさんがさらに圧縮してくれました
  • このスレッドにdev.js相当のものや、minified.js相当のものがあるので短いほうがいい方はぜひ使ってみてください

使い方

index.js

  • index.jsを読み込めばmain(1)で実行できます
  • (for文ではなく再帰で実行してるので引数の1は重要です)
  • 開発しやすいようにconsole.log出力、10回のループ制限があります
  • カスタマイズもしやすいと思うのでいろいろ活用できると思います()

dev.js

  • こんな無理やり圧縮したクソコード忘れて..

minified.js

  • ブラウザで実行するのに特化したバージョンです。
  • やっぱり短いほうがいいですよね?
  • ブックマークレットなどにいかがでしょうか?
  • 他の2つと違ってループ回数制限もないのでちゃんとゴールドバッハの予想が検証できるね!

あとがき

これらは全部、自己責任で使ってくださいね! これを作るきっかけにもなった、再帰alertスクリプトのアドレスを掲示板に書き込んだ女子中学生や男性が補導、書類送検される方針だということを忘れないでね!!

※この投稿は当初警察に目をつけられるのが怖くてQiitaのガイドラインに反するような気がしてGistの方に投稿しましたが、意外と評価が良く考え直してみたらQiitaに時事ネタが上がることもよくあり、この逮捕の問題点をより多くの人に認識してもらうことも重要だと思ったので、Gistを加筆編集して投稿しています。
オリジナルバージョンを確認したい、より古いリビジョンを確認したい人はこちらからどうぞ。
JavaScript Alert Recursion with Goldbach's conjecture
※Gistのほうはこの記事のリンクを追加したらこっちをメインに更新していきます

この逮捕の問題点がなるなる人や僕に興味がある人は僕のTwitter @yuta0381を見てみてもいいかも知れません。直近では警察、法律の問題視するツイートやリツイートなどをしています。ただしあくまで個人の意見なので過信と鵜呑みは禁物です。異論は歓迎ですがなるべく建設的な議論をしましょう!
※注意 この件に特価したアカウントでもないので、関係ないツイートも普通にしますのでそれが不快な人は見ないほうがいいかも知れません

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

オリジナルKdB(科目検索)を作ってみよう -番外3- GCP Cloud Functions編

これまでの記事

スクリーンショット 2019-02-22 16.11.47.png

この記事を読む前に

ある特定の人にしかわからない単語が出現する可能性が高いです。あらかじめご了承ください。

この記事での開発環境

  • MacOS 10.14.3
  • Visual Studio Code 1.31.1
  • Node.js LTS 10.15.1 (GCP Cloud Functionsでは6.x)

はじめに

「オリジナルKdB(科目検索)を作ってみよう」の番外編です。これまではExpress.jsは使わず、サーバレスなシステムを作っていきましょう。シリーズでやっていたようにExpress.jsでエンヤコラはやらずに、関数を作るだけで簡単にサービスを作ることができます。

前提条件

  • GCP(Google)のアカウントを持っていること
  • node.jsの開発環境があること

AWS Lambda用の関数を作成

まずはじめにGCP Cloud FunctionsではNode.js(ver 6.x)のみがサポートされています。他の言語で描きたい場合はAWS LambdaやIBMCloud, Azure等の利用をオススメします。(なお、GCP Cloud FunctionsではPython3.7やGo1.11がベータ版として提供されています。)

特に複雑な処理は行わないのでGoogleCloud-SDKを使わずに作れます。
それではindex.jsを作りましょう。

index.js
// GCPでは「exports.〇〇」とすることで好きな命名ができる。
// AWS Lambdaの時みたいに (event,callback) => ... もできるが
// 簡単な関数では (req, res) => ... の方が簡潔で早い
exports.csvparse = (req, res) => {

    // リクエストのクエリ文字列から「?search=〇〇」の値を変数に格納
    const str = req.query.search

    // -番外1- で作ったJSONデータの読み込み
    const json = require("./data")

    // 正規表現オブジェクトを作成、filterで検索
    const reg = new RegExp( str )
    const filterCourse = json.subjects.filter( (value) => {
        return value.name.match(reg)
    })

    // データ転送量削減のために最初の5件のみにする
    const resJson = {
        subjects: filterCourse.splice(0, 5)
    }

    // HTTPステータス200(success) でレスポンスを返す
    res.status(200).send( JSON.stringify(resJson) )
};

※GCP Cloud Functionsではデータ転送量が多くなるほど、実行時間が長くなるほどお金がかかります。そのためresponseを5件に絞り、JSON.stringfy()のコストとデータ転送量を削減しています。

補足

ぱっと見でわかると思いますが、Express.jsの時に書いたコードと大きく変わることなく記述できていることがわかると思います。これはシンプルなHTTPレスポンス処理を採用しているからです。細かい部分ではExpress.jsと記述方法が異なりますので注意してください(req.queryの部分とか)

lambda関数のローカル実行テスト

Googleの方で提供されているエミュレータを使います。
こちらを参考にしました → https://cloud.google.com/functions/docs/emulator

※エミュレートできる環境はNode.js 6.xのみ。PythonやGo, Node.js 8.xはエミュレートできません。

まずはエミュレータをダウンロードします。

$ npm install -g @google-cloud/functions-emulator
$ functions --version   # 入っていることを確認
1.0.0-beta.5

そしたらエミュレータを起動します

$ functions start

これで起動しました。
ちなみにエミュレータを終了するときはfunctions stopと入力すればOKです

次はエミュレータ環境にデプロイします。

$ functions deploy csvparse --trigger-http

# 何かしら表示される。
# 「Function csvparse deployed.」と表示されていればOK

それではテストしてちゃんとレスポンスが返ってくるか確認します。
今回はクエリ文字列を直接入力できないので、以下のようにペイロードとして渡します。

$ functions call csvparse --data='{"query":{"search":"生物"}}'

ちゃんと検索結果が表示されたらOKです。
Zipファイルに圧縮してアップロードの準備をしましょう。

$ zip -r test.zip index.js data.js node_modules

GCP Cloud Functions の準備

GCPのアカウントを作ったら、GCPのコンソールページにいきます。

スクリーンショット 2019-03-05 15.53.19.png
スクリーンショット 2019-03-05 15.53.47.png
スクリーンショット 2019-03-05 16.06.39.png
スクリーンショット 2019-03-05 16.11.21.png

無事にリンクが発行できたのでリンクの最後に「?search=〇〇」を追加してアクセスしてみましょう!

APIが完成した!

スクリーンショット 2019-03-03 17.12.57.png

APIのURLにクエリ文字列を指定して実行すると実際にレスポンスが返ることがわかります。
AWS Lambdaよりも少し簡単に実装できましたね!

GCPもgcloudというCLIがあり、そちらを使えばコマンドライン上で全ての処理が可能です。興味のある方はぜひ使ってみてください!

なお、今回もAPIをオープンアクセス可能な状態で作成しましたが、API-Keyを発行するなり、ユーザ認証をするなど、何かしらのセキュリティを設けたほうが確実で安全です。ここでの説明は割愛させていただきますが最低限のマナーなので気をつけたほうがいいです。

参考記事↓

質問等はコメントでお願いします!
ありがとうございました!

今回のあとがき

こんなに簡単ならAWS Lambdaを使わなくていいのでは...?
いえいえ、そんな簡単な話じゃないんです。確かにGCPの操作は簡単で便利ですが、設定の細かさで言ったらAWSに軍配があがるケースが多いです。細かい設定をすることで本当の意味での最適化を行うことができるのでコストカットには最適でしょう。

でもやっぱFirebaseとかSearchAPIとかGoogleMapsAPIとかとか。Googleにしかない強みがこれでもかというくらいあるんですよね。やっっぱGoogleはつよい。

それでもクラウドも適材適所ってことです。いろんなサービスを使って仲良くしていきましょう。

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

なんとなくVue.jsのUIフレームワークを紹介する

概要

僕が今まで使ってきたVue.jsのUIフレームワークを紹介していきたいと思います。

Vuetify

image.png

難易度 ★★★☆☆
ドキュメントの読みやすさ ★★☆☆☆
コンポーネントの数 ★★★★★
汎用性 ★★★★★
おすすめ度 ★★★★☆

感想

コンポーネントも多いし、デザインも綺麗なのでおすすめのフレームワークです。
でもドキュメントがすごく読みにくいです、何度読んでもVuetifyのグリッドレイアウトはよく分かりません。

Bootstrap-vue

image.png

難易度 ★☆☆☆☆
ドキュメントの読みやすさ ★★★★★
コンポーネントの数 ★★☆☆☆
汎用性 ★★★☆☆
おすすめ度 ★★★★★

感想

Bootstrap4というCSSフレームワークをVue.js向けに拡張したフレームワークになります。
Bootstrap-vueは初めてUIフレームワークに触る人におすすめです。
ドキュメントも比較的読みやすいし、何よりBootstrapのクラス(マージンとかパディングとか)をそのまま流用できるのがいいです。
何でもできるというタイプのフレームワークではないのですが、絶妙なシンプルさが気に入っています。

Buefy

image.png

難易度 ★★☆☆☆
ドキュメントの読みやすさ ★★★★★
コンポーネントの数 ★★☆☆☆
汎用性 ★★☆☆☆
おすすめ度 ★★★★☆

感想

BulmaというCSSフレームワークをVue.js向けに拡張したフレームワークになります。
BuefyとBootstrap-vueは結構似たUIフレームワークだと思っています
このフレームワークでよく躓くのはBulmaの仕様の方なので最終的にBulmaのドキュメントばっかり見るようになります。
なんとなくBootstrap-vueのほうが使いやすかったです。

Quasar Framework

image.png

難易度 ★★★★★
ドキュメントの読みやすさ ★★★☆☆
コンポーネントの数 ★★★★★
汎用性 ★★★★★
おすすめ度 ★★★☆☆

感想

一番何でもできるUIフレームワークだと思います。
コンポーネントの数も多いしドキュメントも充実していますが、使いこなすには時間がかかると思います。
難易度が高いのでおすすめ度は低いです。

Onsen UI 2

image.png

難易度 ★★☆☆☆
ドキュメントの読みやすさ ★★☆☆☆
コンポーネントの数 ★☆☆☆☆
汎用性 ★☆☆☆☆
おすすめ度 ★☆☆☆☆

感想

モバイルアプリケーション(cordovaやmonaca)向けのUIフレームワークですね。
個人的にはおすすめはしません。
AngularやReactにも対応させているせいか、なんか無理やりな感じのするフレームワークワークですね。
ドキュメントも読みにくいし、コンポーネントも器用貧乏でかゆいところに手がとどかない感じです。

Framework7

image.png

難易度 ★★☆☆☆
ドキュメントの読みやすさ ★★☆☆☆
コンポーネントの数 ★★★☆☆
汎用性 ★★☆☆☆
おすすめ度 ★★☆☆☆

感想

こちらもモバイルアプリケーション向けのUIフレームワークですね。
Onsen UI 2よりは多少マシです。
このフレームワークもAngularやReactにも対応させているせいか、なんか無理やりな感じがします。
ドキュメントは頑張れば読めます。
あとルーティングがVue-Routerが使えません、その点でマイナスです。

Element

image.png

難易度 ★☆☆☆☆
ドキュメントの読みやすさ ★★★★☆
コンポーネントの数 ★★★☆☆
汎用性 ★★☆☆☆
おすすめ度 ★★★☆☆

感想

Elementは昔からあるUIフレームワークですね。
ですがモバイル対応が微妙とのことですぐに使うのやめちゃいました。
ドキュメントも読みやすいし、デザインも綺麗なのでまた使ってみたいですね。

総評

如何だったでしょうか?
てめぇその評価はおかしぃだろうがYO!
みたいな意見もあるでしょうが多めに見てください〜。

今回紹介したもの以外もVue materialやMint UIなどなどたくさんありますので使う機会があったら更新したいと思います。

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

chart.jsで積み上げ横棒グラフを描く

はじめに

項目数が多いから縦グラフじゃなくて横グラフで描きたいってときありますよね。

方法

1.データの用意

データがなくちゃ始まらない。

   $datas = array(
    '太郎' => array (
            '国語' => 81
            , '数学' => 79
            , '理科' => 66
            , '社会' => 72
            , '英語' => 89
        ),
    '次郎' => array (
            '国語' => 60
            , '数学' => 68
            , '理科' => 50
            , '社会' => 49
            , '英語' => 91
        ),
    '三郎' => array (
            '国語' => 81
            , '数学' => 65
            , '理科' => 98
            , '社会' => 81
            , '英語' => 66
        )
   );

2.データのこねくり回し

chart.jsに渡す用のデータを作成します。

//変数の初期化
$labels   = '';
$japanese = '';
$math     = '';
$science  = '';
$social   = '';
$english  = '';

//データを文字列に
foreach ($datas as $name => $values){
    $labels   = sprintf('%s"%s", ', $labels, $name);
    $japanese = sprintf('%s%s, ', $japanese, $values['国語']);
    $math     = sprintf('%s%s, ', $math,     $values['数学']);
    $science  = sprintf('%s%s, ', $science,  $values['理科']);
    $social   = sprintf('%s%s, ', $social,   $values['社会']);
    $english  = sprintf('%s%s, ', $english,  $values['英語']);
}

//いらない部分を削除
$labels   = mb_substr($labels, 0, -2);
$japanese = mb_substr($japanese, 0, -2);
$math     = mb_substr($math, 0, -2);
$science  = mb_substr($science, 0, -2);
$social   = mb_substr($social, 0, -2);
$english  = mb_substr($english, 0, -2);

中身は、
labels = "太郎", "次郎", "三郎"
math = 81, 60, 81
こんな感じの文字列が入っています。

もうちょっと綺麗にしたいですね♨

3.HTML

<!DOCTYPE html>
<html>
   <head>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
      <title>得点グラフ</title>
   </head>
   <body>
      <div class = "graph_box">
         <canvas id="score" class = "chart"></canvas>
      </div>
   </body>
</html>

canvasタグの間にグラフが入ります。

4.chart.js

<script>
//ロス回数
var ctx = document.getElementById("score");
var myChartSt = new Chart(ctx, {
    type: "horizontalBar"      //barで縦グラフ
    , data: {
        labels:  [<?php echo $labels;?>]
        , datasets: [
            {
                label: '国語'
                , borderWidth: 1
                , backgroundColor: "red"
                , borderColor: "red"
                , data: [<?php echo $japanese;?>]
            }
            , {
                label: '数学'
                , borderWidth: 1
                , backgroundColor: 'orange'
                , borderColor: 'orange'
                , data: [<?php echo $math;?>]
            }
            , {
                label: '理科'
                , borderWidth: 1
                , backgroundColor: 'green'
                , borderColor: 'green'
                , data: [<?php echo $science;?>]
            }
            , {
                label: '社会'
                , borderWidth: 1
                , backgroundColor: 'skyblue'
                , borderColor: 'skyblue'
                , data: [<?php echo $social;?>]
            }
            ,{
                label: '英語'
                , borderWidth: 1
                , backgroundColor: 'black'
                , borderColor: 'black'
                , data: [<?php echo $english;?>]
            }
        ]
    }
    , options: {
        title: {
            display: true
            , text: 'みんなの点数'        //グラフの見出し
            , padding: 3
            , fontSize: 20
        }
        , scales: {
            yAxes: [
                { 
                    stacked: true              //積み上げ棒グラフの設定
                    , xbarThickness: 16        //棒グラフの幅
                    , scaleLabel: {            // 軸ラベル
                        display: true          // 表示設定
                        , labelString: '名前'  // ラベル
                        , fontSize: 16         // フォントサイズ
                    }
                }
            ]
            , xAxes: [
                {
                    stacked: true               //積み上げ棒グラフにする設定
                    , scaleLabel: {             // 軸ラベル
                        display: false          // 表示設定
                        , labelString: '総得点'  // ラベル
                        , fontSize: 16          // フォントサイズ
                    }
                }
            ]
        }
        , legend: {
            labels: {
                boxWidth:30
                , padding:20        //凡例の各要素間の距離
            }
            , display: true
        }
        , tooltips: {
            mode: "label"
        }
    }
});
</script>

積み立て横棒グラフにするには、
・type: "horizontalBar"
・yAxes:[{stacked: true}]
とする。

結果

キャプチャ.PNG

まとめ

縦グラフのときと、x,yが逆になるので注意。

下に全ソースを貼っておきます

aaa.php
<?php
$datas = array(
    '太郎' => array (
            '国語' => 81
            , '数学' => 79
            , '理科' => 66
            , '社会' => 72
            , '英語' => 89
        ),
    '次郎' => array (
            '国語' => 60
            , '数学' => 68
            , '理科' => 50
            , '社会' => 49
            , '英語' => 91
        ),
    '三郎' => array (
            '国語' => 81
            , '数学' => 65
            , '理科' => 98
            , '社会' => 81
            , '英語' => 66
        )
);

//変数の初期化
$labels = '';
$japanese = '';
$math = '';
$science = '';
$social = '';
$english = '';

//データを文字列に
foreach ($datas as $name => $values){
    $labels = sprintf('%s"%s", ', $labels, $name);
    $japanese = sprintf('%s%s, ', $japanese, $values['国語']);
    $math = sprintf('%s%s, ', $math, $values['数学']);
    $science = sprintf('%s%s, ', $science, $values['理科']);
    $social = sprintf('%s%s, ', $social, $values['社会']);
    $english = sprintf('%s%s, ', $english, $values['英語']);
}

//いらない部分を削除
$labels = mb_substr($labels, 0, -2);
$japanese = mb_substr($japanese, 0, -2);
$math = mb_substr($math, 0, -2);
$science = mb_substr($science, 0, -2);
$social = mb_substr($social, 0, -2);
$english = mb_substr($english, 0, -2);
?>
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
<title>得点グラフ</title>
</head>
<body>
    <div class = "graph_box">
        <canvas id="score" class = "chart"></canvas>
    </div>
</body>
</html>
<script>
//ロス回数
var ctx = document.getElementById("score");
var myChartSt = new Chart(ctx, {
    type: "horizontalBar"      //barで縦グラフ
    , data: {
        labels:  [<?php echo $labels;?>]
        , datasets: [
            {
                label: '国語'
                , borderWidth: 1
                , backgroundColor: "red"
                , borderColor: "red"
                , data: [<?php echo $japanese;?>]
            }
            , {
                label: '数学'
                , borderWidth: 1
                , backgroundColor: 'orange'
                , borderColor: 'orange'
                , data: [<?php echo $math;?>]
            }
            , {
                label: '理科'
                , borderWidth: 1
                , backgroundColor: 'green'
                , borderColor: 'green'
                , data: [<?php echo $science;?>]
            }
            , {
                label: '社会'
                , borderWidth: 1
                , backgroundColor: 'skyblue'
                , borderColor: 'skyblue'
                , data: [<?php echo $social;?>]
            }
            ,{
                label: '英語'
                , borderWidth: 1
                , backgroundColor: 'black'
                , borderColor: 'black'
                , data: [<?php echo $english;?>]
            }
        ]
    }
    , options: {
        title: {
            display: true
            , text: 'みんなの点数'        //グラフの見出し
            , padding: 3
            , fontSize: 20
        }
        , scales: {
            yAxes: [
                { 
                    stacked: true              //積み上げ棒グラフの設定
                    , xbarThickness: 16        //棒グラフの幅
                    , scaleLabel: {            // 軸ラベル
                        display: true          // 表示設定
                        , labelString: '名前'  // ラベル
                        , fontSize: 16         // フォントサイズ
                    }
                }
            ]
            , xAxes: [
                {
                    stacked: true               //積み上げ棒グラフにする設定
                    , scaleLabel: {             // 軸ラベル
                        display: false          // 表示設定
                        , labelString: '総得点'  // ラベル
                        , fontSize: 16          // フォントサイズ
                    }
                }
            ]
        }
        , legend: {
            labels: {
                boxWidth:30
                , padding:20        //凡例の各要素間の距離
            }
            , display: true
        }
        , tooltips: {
            mode: "label"
        }
    }
});
</script>

あとがき

案外まとめられてる記事がなかったので、備忘録です。
というか、これ使いやすいし、わかりやすいからそんなにまとめる必要もないかもしれない。

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

Nuxt.jsでグローバルSass変数を使いたいんじゃ

概要

Nuxt.js使ってるときにsassのグローバル変数を使う方法を紹介します。

@nuxtjs/style-resourcesを使う

@nuxtjs/style-resourcesを使うと簡単です。

npm
npm i @nuxtjs/style-resources

yarn
yarn add @nuxtjs/style-resources

nuxt.config.jsを編集します。今回はsassなのでこんな感じ

export default {
  modules: [
    '@nuxtjs/style-resources',
  ],

  styleResources: {
    scss: ['@/assets/style/_variables.scss']
  }
}

scssファイル内にグローバル変数を書いていきます。

assets/style/_variables.scss
$primary: #3f51b5;
$secondary: #b0bec5;
$accent: #8c9eff;
$error: #b71c1c;

コンポーネント内で使います

<style lang="scss" scoped>
.button {
  color: $primary;
}
</style>

いいゾ~これ

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

量子ゲート回路っぽいタイトル描きたくない?

image(1).png

●ここでつくれます

昨日の帰りの電車で思いついたものをつくってみました.量子ゲート回路っぽいタイトルのpng画像が作成できます.
思ったより量子ゲートっぽくないとかそんなこと言わないでください.

遊び方

こちらの動画を見ていただけると一発でわかると思います.

こんなかんじです

スクリーンショット 2019-03-05 15.30.37.png

  • ソースコードはこちら
  • PC,フルスクリーン表示推奨です
  • Internet Explorerはdownload属性に対応していないため,画像の保存ができません(詳しくはこちら)

作品集

つくってみたものをいくつか載せます.
チュートリアルビデオで作成したもの↓
image (2).png
真面目に量子ゲート描くとこんなかんじです↓
image.png
これかわいくて好きです↓
image (1).png
難しい漢字もこんなにポップに↓
image (3).png
一色縛りもなかなかかわいい↓
image (5).png

なかなか楽しいです.いろんな使い方ができそう.

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

How to 速度改善 ー原因調査編ー

前提

速度改善にこれから取り組もうと思っている方向けに書きました。
すでに速度改善に取り組んでいる方からするとちょっと基礎すぎるかもしれませんが、自分の勉強メモとしてアウトプットして残そうと思った次第です。

前回の記事はこちらになります。

How to 速度改善 ー計測・知識編ー

ご興味ある方はどうぞ。

原因の調査

前回の記事でスコアのキャプチャを載せましたが、Page Speed Insightsの低スコアの原因については添付の画像の中に色々と書かれています。

スクリーンショット 2019-02-28 15.29.59.png

8個の指摘がありますので順番に見ていきましょう。

1. レンダリングを妨げるリソースの除外

これはレンダリングを阻害しているファイルたちがいるよ、ということですね。
今回でいうとこんな感じで沢山のファイルがその対象となっていました。

qiita.png

ここの部分の解決策としては後ほど詳しく示しますが、指摘しているファイルたちを遅延読み込みする、必要なリソースはインラインでHTMLに書く必要がありそうです。

2. オフスクリーン画像の遅延読み込み

これは、そのページのファーストビューで読み込みする必要がない画像は遅延読み込みしましょう、という指摘ですね。
見えていない所の画像については、いちいち読みこむ必要ないので、それでページの読み込み速度をあげようということです。

こんな感じで沢山指摘されていました。

qiita2.png

これについても後ほど一つの解決策を共有します。

3. 次世代フォーマットでの画像の配信

画像といえばjpeg,pngあたりがすぐに思い浮かぶと思いますが
googleさんによると

JPEG 2000、JPEG XR、およびWebPは、以前のJPEGおよびPNGに比べて優れた圧縮および品質特性を持つ画像フォーマットです

とのことです。
要するにその形式のものを使ってみてはどうですか?ということですね。

それぞれの画像形式についてCan I useで調べてみました。
JPEG 2000
JPEG XR
Web P

それぞれ対応しているブラウザが違いますね。
上記の形式でほぼ全てのブラウザに対して対応できそうですが、ユーザーエージェントを判断して出し分けたりしないといけないんでしょうか・・・。大変だ。。

4. 効率的な画像フォーマット

これについては、適切な画像のファイルサイズでサーバーに格納しましょう、という感じの指摘です。
サイズが大きすぎる画像があると指摘されます。
8KBの画像でも、減らせるデータ量がまだあると指摘されています。笑
qiita3.png

5. 使用していないCSSの遅延読み込み

対象のページで使われていないCSSについてはあとで読み込むような仕組みにしましょう。ということですね。

critical css

critical cssとは、ページをロードするために必要とされるcssのことをさします。
一般に、ページロードは重要なCSSでのみブロックされるべき、という考えがあるので、それをまずはロードして、それ以外は遅延評価をして、あとで読み込みましょう、ということです。

6. 適切なサイズの画像

これについては言わずもがな、画像のサイズを圧縮しましょうという話ですね。

7. Javascriptの最適化

これも言わずもがなですが、javascriptの空白とか諸々削除して最小化してね、ということですね。
それができていないファイルがいくつかあるのか・・・。

8. サーバー応答時間の短縮

これはサーバーの応答時間を改善してくださいね、という指示になります。
TTFB(Time To First Byte)はブラウザがWebサーバーにリクエストを送信してから、Webサーバーが最初の1バイトの応答をブラウザに返すまでの時間です。
無駄なクエリが走っていたり、何かのファイルの読み込みに時間がかかっていたりすると応答時間の遅延が起こる模様です。

原因の調査その2

page speed insightsの中身をもう少し詳しく見ていきましょう。
スクリーンショット 2019-03-05 10.47.58.png

1. 静的なアセットと効率的なキャッシュポリシーの配信

キャッシュの有効期間を設定して、静的なアセットをキャッシュヒットさせるようにしましょう、ということですね。毎回HTTPリクエストを投げて画像を待つというのは無駄ですし。
それにしても40個以上のファイルが対象になっているようで、修正のしがいがありますねぇ・・・。

2. メインスレッド処理の最小化

メインスレッド処理については下の画像にあるように、かなりJavascriptが邪魔をしているようですね。
スクリーンショット 2019-03-05 14.02.18.png

3. Javascriptの実行にかかる時間の低減

Javascriptの解析、コンパイル、実行にかかる時間の短縮をしてください、という内容ですね。
こちらについては上記の「メインスレッド処理の最小化」とも密接に関係していそうです。

4. 過大なDOMサイズの回避

ページに含まれるDOMノード数が1500を超えない、というのが推奨されている値のようです。
DOMノードとは、DOMツリーの中にある要素のことをさします。例えばpタグ、aタグ、imgタグなどがそれに該当します。これの個数を押さえましょうということですね。

5. クリティカルなリクエストの深さの最小化

こちらのGoogleのページによると

クリティカル リクエスト チェーンは、クリティカル レンダリング パス(CRP)の最適化技術のコンセプトの 1 つです。 CRP を考慮してリソースに優先度を設定し、読み込む順序を指定すると、ブラウザでより早くページが読み込まれるようになります。
とのことです。まあ、要は必要なリソースだけを読み込みましょう。ということですね。

これらの問題を解決していくための案

さて、これである程度スコアが低い理由を把握することができました。色々と指摘されていますが

大まかに

①画像のサイズが適切でない
②レンダリングをブロックしている要素が多数ある
③キャッシュなどを設定していないのでHTTPリクエストの数が多い
④ファーストビューに必要な情報以外の情報も多数読み込んでしまっている

といった問題があり、それらが原因でPage Speed Insightsのスコアが落ちてしまっていることがわかりました。

それぞれの解決策として

ImageOptimImage Compressorなどで画像のサイズを適切なものにする。できればその作業を何度も行わなくても良いように、画像をアップロードしたら自動的に画像サイズを圧縮してくれるような仕組みを作る。

② Javascript、CSSについては遅延読み込みをする。
③ ブラウザキャッシュの設定をちゃんとやる。←
④ ファーストビューに対して必要な要素を割り出し、必要なものを読み込む。

といったことがあげられます。

具体的には

  • 先ほどのImageOptimImage Compressorの利用
  • Gruntなどを使い画像圧縮を自動化
  • imgixのようなCDNサービスを使って画像を配信する

  • Javascriptについてはasync defer属性を使い非同期で読み込む
  • CSSについてはpreloadを利用する

  • 社内のプロダクトなので公開はできないのですが、どうやら対象の画像ファイルたちがキャッシュの対象になっていない模様。。対象に入るように設定する

  • ファーストビューのに必要なCSSのインライン化、読み込む必要のないjsファイルは読み込まないようにする

といった対策をとることを検討することにしました!

次回は

How to 速度改善 ー実装編①ー を書きたいと思います。ようやく実装。。

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

【React】超ざっくり入門(Hooks)

概要

React16.8から新しい機能としてHooksが導入されました。
以下の順序でHooksとは何かざっくり入門していきましょう。

Hooksとは?

Hooksとは従来classコンポーネントでしか使用できなかったstate等の機能をfunctionalコンポーネント(関数コンポーネント)でも使用可能にする機能です。

この記事では以下の3種類のhookを紹介します。

  • (1)State Hook:statesetStateを関数コンポーネント内で使用可能にする。
  • (2)Effect Hook:データ取得やDOM変更といったside effectsを関数コンポーネント内で使用可能にする。
  • (3)Custom Hooks:共通のロジックをコンポーネント間での再利用を可能にする。

Hooksの規則

Hooksを使用する際には以下の規則を守りましょう。

  • (1) Hooksは必ずコンポーネント中の一番上(トップレベル)に定義する。(ループ、条件分岐、ネストされた関数の中で定義してはいけません。)
  • (2) HooksはReactの関数コンポーネントの中だけで呼ぶ。(通常のJavascriptの関数から使用してはいけません。)

State Hook

上記で記した様に、statesetStateを関数コンポーネント内で行える様にします。
具体的にはuseStateと呼ばれるhookを使用します。

useStateの使用方法を従来のclassコンポーネントと比較して確認していきます。

従来のclassコンポーネント

以下の例では主に2つの事を行なっています。

  • (1)stateで初期値に0を持つcountを定義する。
  • (2)setStateでボタンを押すたびにcount1を足している。

output2

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>{this.state.count} 回クリックしました。</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          クリックしてください。
        </button>
      </div>
    );
  }
}

StateHook

上記と全く同じ事をuseStateを使用して行います。

まずuseStateをインポートしましょう。

import React, { useState } from  'react';

useStateでは従来のstatesetStateを同時に設定します。
以下の3点に注目して以下のコードを見てみましょう。

  • (1) countがclassコンポーネントでいうthis.state={count:0}の部分。
  • (2) setCountがclassコンポーネントでいうsetStateの部分。
  • (3) useState()に渡されている0countの初期値。
import React, { useState } from  'react';

function Counter() {
  const [count, setCount] = useState(0)
}

また以下の2点を踏まえて残りのコードを完成させます。

  • (1) stateを直接参照できる。
  • (2) setCountを使用してstateを更新できる。
import React, { useState } from  'react';

function Counter() {
  const [count, setCount] = useState(0)

  return(
    <div>
      // (1) stateを直接参照できる。
      <p>{count} 回クリックしました</p>

      // (2) setCountを使用してstateを更新できる。
      <button onClick={() => setCount(count + 1)}> クリックしてください。</button> 
    </div>
  )
}

export default Counter;

ちなみに以下の様にして複数のstateを定義していきます。

import React, { useState } from  'react';

function Counter() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('')

  return(
    <div>
      <p>{count} 回クリックしました</p>
      <button onClick={() => setCount(count + 1)}> クリックしてください。</button>
      <p>私の名前は{name}</p>
      <button onClick={() => setName('テスト太郎')}>名前表示</button>
    </div>
  )
}

export default Counter;

Effect Hook

上記で記した通り、データ取得やDOM変更といったside effectsを関数コンポーネント内で使用可能にします。

従来のclassコンポーネントではcomponentDidMountcomponentDidUpdatecomponentWillUnmount等の中でデータ取得やDOM変更を行なっていましたが、これをuseEffectと呼ばれるhookを使用し、関数コンポーネントでも使用可能にします。

こちらもuseEffectの使用方法を従来のclassコンポーネントと比較して確認していきます。

従来のclassコンポーネント

従来はcomponentDidMountはマウントされた直後に1回だけ呼ばれ、componentDidUpdateはコンポーネントが更新された後に呼ばれ、ここでデータの取得等を行いました。

output3

import React from  'react';

class Effect extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    console.log('マウントされました。')
  }

  componentDidUpdate() {
    console.log('アップデートされました。')
  }

  render() {
    return (
      <div>
        <h1>{this.state.count} 回クリックしました。</h1>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          クリックしてください。
        </button>
      </div>
    );
  }
}

EffectHook

useEffectは従来のcomponentDidMountcomponentDidUpdatecomponentWillUnmountの3つを組み合わせた様なもので、基本的にrenderされる毎に呼ばれます。(first renderを含む。)
hooksを使用した開発ではここでside effectsを実行します。

output4

import React, { useState, useEffect } from 'react'

function Effect() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    console.log('レンダーされました。')
  })

  return(
    <div>
      <h1>{count} 回クリックしました</h1>
      <button onClick={() => setCount(count + 1)}> クリックしてください。</button>
    </div>
  );
}

Custom Hooks

上記で記載した通り、共通のロジックをコンポーネント間での再利用を可能にします。

以下の2つのコンポーネントがあるとします。

  • (1) 受け取ったid1だったらログイン中ですを返す、status.js
  • (2) 受け取ったid1だったらおかえりなさいを返すmessage.js
status.js
export default function Status(props){
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const handleStateChange = (id) => {
    if(id === 1){
      setIsLoggedIn(true)
    }
    else{
      setIsLoggedIn(false)
    }
  }

  useEffect(() => {
    handleStateChange(props.user.id)
  })

 const status = isLoggedIn ? 'ログイン中' : 'サインアップ'

  return (
    <>
      <h1>Status: {status}</h1>
    </>
  )
}
message.js
export default function Message(props){
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const handleStateChange = (id) => {
    if(id === 1){
      setIsLoggedIn(true)
    }
    else{
      setIsLoggedIn(false)
    }
  }

  useEffect(() => {
    handleStateChange(props.user.id)
  })

 const message = isLoggedIn ? 'おかえりなさい' : 'おかえりなさい'

  return (
    <>
      <h1>Message: {message}</h1>
    </>
  )
}

ここの共通したロジック部分をcustom Hookを使用する事で、以下の様に抜き出す事ができます。
抜き出したロジックのコンポーネントはuseから始まる慣習があります。今回はuseLoginと名付けました。

useLogin
import { useState, useEffect } from 'react';

export default function useLogin(userId){
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  // コンポーネント間で再利用したいロジック
  const handleStateChange = (id) => {
    if(id === 1){
      setIsLoggedIn(true)
    }
    else{
      setIsLoggedIn(false)
    }
  }

  // side effectsを実行する。
  useEffect(() => {
    handleStateChange(userId)
  })

  return isLoggedIn;
}

useLoginを使用する事でstatus.jsmessage.jsをよりスッキリ書くことができます。

user.js
import React from 'react';
import useLogin from './useLogin';

export default function Status(props){
  const status = useLogin(props.user.id) ? 'ログイン中' : 'サインアップ'
  return (
    <>
      <h1>Status: {status}</h1>
    </>
  )
}
message.js
import React from 'react';
import useLogin from './useLogin';

export default function Message(props){
  const message = useLogin(props.user.id) ? 'おかえりなさい' : 'サインアップしてください'
  return (
    <>
      <h1>Message: {message}</h1>
    </>
  )
}

custom hooksでは他にも色々な事ができるので、詳細は公式ドキュメントで確認ください。

参照

hooksの説明としては本当にざっくりでしたので、詳細は公式ドキュメントで確認ください。

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

【☠️危険なプログラム☠️】"何回閉じても無駄ですよ〜"と無限にアラートを出すサイトのコードを添削してみた。

☠️元ネタ☠️

不正プログラム書き込み疑い補導|NHK 兵庫県のニュース

まず該当のURLからソースを取ってくる

curl http://n41050z.web.fc2.com/burakura.html > burakura.html
burakura.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>無題</title>
<SCRIPT LANGUAGE="JavaScript">
for ( ; ; ) {
window.alert(" ∧_∧ ババババ\n( ・ω・)=つ≡つ\n(っ ≡つ=つ\n`/  )\n(ノΠU\n何回閉じても無駄ですよ~ww\nm9(^Д^)プギャー!!\n byソル (@0_Infinity_)")
}
</SCRIPT>
</head>
<body />
</html><script type="text/javascript"><!--
var fc2footerparam = 'charset=' + (document.charset ? document.charset : document.characterSet) + '&url=' + document.location + '&service=0&r=' + Math.floor(Math.random()*99999999999);
var fc2footertag = '<' + 'script src="//vip.chps-api.fc2.com/apis/footer/?' + fc2footerparam + '" charset="UTF-8"><' + '/script>';
document.write(fc2footertag);
//--></script>
<!-- FC2, inc.-->
<img src="//media.fc2.com/counter_img.php?id=50" style="visibility:hidden" alt="inserted by FC2 system" width="0" height="0">
<!-- FC2, inc.-->

</html>から以下はFC2によって自動的に付与されるものだと思われるからそれを除いた以下のソースを添削していく。

burakura.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>無題</title>
<SCRIPT LANGUAGE="JavaScript">
for ( ; ; ) {
window.alert(" ∧_∧ ババババ\n( ・ω・)=つ≡つ\n(っ ≡つ=つ\n`/  )\n(ノΠU\n何回閉じても無駄ですよ~ww\nm9(^Д^)プギャー!!\n byソル (@0_Infinity_)")
}
</SCRIPT>
</head>
<body />
</html>

添削

DOCTYPE宣言

HTMLやXHTMLのどのバージョンで作成されているかを宣言するため最初の行に<!DOCTYPE html>を追記するべき。

<html><!DOCTYPE html>
<html>
...

script内のlanguage属性

script内のlanguage属性はobsoleteなので削除したほうがいい。

<SCRIPT LANGUAGE="JavaScript">
...
</SCRIPT><SCRIPT>
...
</SCRIPT>

<body />

これだとSelf-closing構文になってしまう。
bodyは空の要素ではないため(コンテンツを持つ要素であるため)、開始タグの<body>と解釈されてしまう。

そのために以下のように修正するとよい。

<body/><body></body>

参考:

lang属性

<html>にはlang属性をつけることが推奨される。

<html><html lang="ja">

メタタグ

HTML5では上記のままで問題ない。
charset属性を使用すれば以下のよう短くかける。

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="UTF-8">

参考: <meta>-HTML5タグリファレンス

SCRIPTタグ

XHTML1.0では大文字(UPPER CASE)でかくべきだが、HTML4.01以降から区別されない。

問題はないが、他のタグと統一性を保つために小文字で描くべき。

<SCRIPT>
...
</SCRIPT><script>
...
</script>

参考: タグは大文字・小文字どちらで書くべき? - タグHTML

for(;;)

for(;;) ...でも無限ループになるが、可読性のためにwhile(true) ...を使った方がいい。

for ( ; ; ) {

while (true) {

AA文字列

AAを1行で書いているために可読性が低い。
+で連結したほうが見やすい。

window.alert(" ∧_∧ ババババ\n( ・ω・)=つ≡つ\n(っ ≡つ=つ\n`/  )\n(ノΠU\n何回閉じても無駄ですよ~ww\nm9(^Д^)プギャー!!\n byソル (@0_Infinity_)")

window.alert(
    " ∧_∧ ババババ\n" +
    "( ・ω・)=つ≡つ\n" +
    "(っ ≡つ=つ\n" +
    "`/  )\n" +
    "(ノΠU\n" +
    "何回閉じても無駄ですよ~ww\n" +
    "m9(^Д^)プギャー!!\n" +
    " byソル (@0_Infinity_)"
)

インデント

インデントをつけた方がいい。

添削結果

brakura.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>無題</title>
    <script>
      while (true) {
        window.alert(
          " ∧_∧ ババババ\n" +
          "( ・ω・)=つ≡つ\n" +
          "(っ ≡つ=つ\n" +
          "`/  )\n" +
          "(ノΠU\n" +
          "何回閉じても無駄ですよ~ww\n" +
          "m9(^Д^)プギャー!!\n" +
          " byソル (@0_Infinity_)"
        );
      }
    </script>
  </head>
  <body></body>
</html>

ちなみに

以下だけでも動く。

hoge.html
<script>
  while (true) {
    window.alert(
      " ∧_∧ ババババ\n" +
        "( ・ω・)=つ≡つ\n" +
        "(っ ≡つ=つ\n" +
        "`/  )\n" +
        "(ノΠU\n" +
        "何回閉じても無駄ですよ~ww\n" +
        "m9(^Д^)プギャー!!\n" +
        " byソル (@0_Infinity_)"
    );
  }
</script>

余談

なんで無限アラートのURL貼っただけで書類送検になるんですかねぇ...
ウェブ開発してるときにバグ発生したら捕まる世界になるのかな:thinking:

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

"何回閉じても無駄ですよ〜"と無限にアラートを出すサイトのコードを添削してみた。

元ネタ

不正プログラム書き込み疑い補導|NHK 兵庫県のニュース

まず該当のURLからソースを取ってくる

curl http://n41050z.web.fc2.com/burakura.html > burakura.html
burakura.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>無題</title>
<SCRIPT LANGUAGE="JavaScript">
for ( ; ; ) {
window.alert(" ∧_∧ ババババ\n( ・ω・)=つ≡つ\n(っ ≡つ=つ\n`/  )\n(ノΠU\n何回閉じても無駄ですよ~ww\nm9(^Д^)プギャー!!\n byソル (@0_Infinity_)")
}
</SCRIPT>
</head>
<body />
</html><script type="text/javascript"><!--
var fc2footerparam = 'charset=' + (document.charset ? document.charset : document.characterSet) + '&url=' + document.location + '&service=0&r=' + Math.floor(Math.random()*99999999999);
var fc2footertag = '<' + 'script src="//vip.chps-api.fc2.com/apis/footer/?' + fc2footerparam + '" charset="UTF-8"><' + '/script>';
document.write(fc2footertag);
//--></script>
<!-- FC2, inc.-->
<img src="//media.fc2.com/counter_img.php?id=50" style="visibility:hidden" alt="inserted by FC2 system" width="0" height="0">
<!-- FC2, inc.-->

</html>から以下はFC2によって自動的に付与されるものだと思われるからそれを除いた以下のソースを添削していく。

burakura.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>無題</title>
<SCRIPT LANGUAGE="JavaScript">
for ( ; ; ) {
window.alert(" ∧_∧ ババババ\n( ・ω・)=つ≡つ\n(っ ≡つ=つ\n`/  )\n(ノΠU\n何回閉じても無駄ですよ~ww\nm9(^Д^)プギャー!!\n byソル (@0_Infinity_)")
}
</SCRIPT>
</head>
<body />
</html>

添削

DOCTYPE宣言

HTMLやXHTMLのどのバージョンで作成されているかを宣言するため最初の行に<!DOCTYPE html>を追記するべき。

<html><!DOCTYPE html>
<html>
...

script内のlanguage属性

script内のlanguage属性はobsoleteなので削除したほうがいい。

<SCRIPT LANGUAGE="JavaScript">
...
</SCRIPT><SCRIPT>
...
</SCRIPT>

<body />

これだとSelf-closing構文になってしまう。
bodyは空の要素ではないため(コンテンツを持つ要素であるため)、開始タグの<body>と解釈されてしまう。

そのために以下のように修正するとよい。

<body/><body></body>

参考:
- HTML 5.2: 8. The HTML syntax
- html - Are (non-void) self-closing tags valid in HTML5? - Stack Overflow

lang属性

にはlang属性をつけることが推奨される。

<html><html lang="ja">

メタタグ

HTML5では上記のままで問題ない。
charset属性を使用すれば以下のよう短くかける。

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="UTF-8">

参考: -HTML5タグリファレンス

SCRIPTタグ

XHTML1.0では大文字(UPPER CASE)でかくべきだが、HTML4.01以降から区別されない。

問題はないが、他のタグと統一性を保つために小文字で描くべき。

<SCRIPT>
...
</SCRIPT><script>
...
</script>

参考: タグは大文字・小文字どちらで書くべき? - タグHTML

for(;;)

for(;;) ...でも無限ループになるが、可読性のためにwhile(true) ...を使った方がいい。

for ( ; ; ) {

while (true) {

AA文字列

AAを1行で書いているために可読性が低い。
+で連結したほうが見やすい。

window.alert(" ∧_∧ ババババ\n( ・ω・)=つ≡つ\n(っ ≡つ=つ\n`/  )\n(ノΠU\n何回閉じても無駄ですよ~ww\nm9(^Д^)プギャー!!\n byソル (@0_Infinity_)")



window.alert(
    " ∧_∧ ババババ\n" +
    "( ・ω・)=つ≡つ\n" +
    "(っ ≡つ=つ\n" +
    "`/  )\n" +
    "(ノΠU\n" +
    "何回閉じても無駄ですよ~ww\n" +
    "m9(^Д^)プギャー!!\n" +
    " byソル (@0_Infinity_)"
)

インデント

インデントをつけた方がいい。

添削結果

brakura.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>無題</title>
    <script>
      while (true) {
        window.alert(
          " ∧_∧ ババババ\n" +
          "( ・ω・)=つ≡つ\n" +
          "(っ ≡つ=つ\n" +
          "`/  )\n" +
          "(ノΠU\n" +
          "何回閉じても無駄ですよ~ww\n" +
          "m9(^Д^)プギャー!!\n" +
          " byソル (@0_Infinity_)"
        );
      }
    </script>
  </head>
  <body></body>
</html>

余談

なんで無限アラートのURL貼っただけで書類送検になるんですかねぇ...
ウェブ開発してるときにバグ発生したら捕まる世界になるのかな:thinking:

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

Lambda×puppeteerでクロールしようとしたらゾンビプロセスの沼に浸かった記録(解決済)

headless chromeを使ってサーバーレスでいい感じにクローリングするプログラムを作ろうとしたら沼に浸かりました。
うまいこと動くテンプレートをgithubで公開しています。
https://github.com/inatomitk/lambda-puppeteer-template

環境

  • puppeteer ※headless chromeのNode.jsのAPI
  • AWS Lambda
  • Node.js

沼りポイント

headless chromeのバイナリがLambdaにアップロードできない

Lambdaには一つのfunctionあたり50MBまでのファイルしかアップロードできないという制限があります。
普通の処理ならその制限を気にする必要もない(まず、そんな大きいファイルにならない)です。が、headless chromeはサイズが大きい(50MB弱くらい)なので、その他諸々のパッケージを含めるとアップロードできない可能性が高いです。

なので、Lambdaのlayerを使用しました。layerを使用することで関連するパッケージを関数とは別でアップロード・使用することが可能です。
serverlessというLambdaにアップロードするための便利なツールがlayerにも対応しています。

ちなみに、serverlessでアップロードする際に、素直にserverless deployをすると毎回layerもアップロードされるのであまりよろしくないです。

$ serverless deploy function -f [関数名]

と実行すると、該当の関数だけアップロードされます。

puppeteerが動かない

様々な記事を参考にさせていただいたのですが、バージョンの相性のせいなのか、うまくheadless chromeが起動せずうまく動きませんでした。

https://github.com/RafalWilinski/serverless-puppeteer-layers
試行錯誤を重ね、遂に、↑を参考に構築したところ、無事クローラーが動きました。

だが、しかし、、、

ゾンビプロセスが溜まりクラッシュする

数回の実行ではまったく問題なかったのですが、短い時間に連続で何度も実行するとクラッシュしてしまう。。
メモリを観察すると、実行毎にメモリが圧迫されていたことがわかりました。そして限度を超えるとクラッシュする。

そこでプロセスを出力してみると、ゾンビプロセス(メモリ0のプロセス)が実行の度に溜まっていることがわかりました。

USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
486 989 0.0 0.0 0 0 ? Z 07:56 0:00 [headless_shell] <defunct>
486 937 0.0 0.0 0 0 ? Z 07:50 0:00 [headless_shell] <defunct>
486 888 0.0 0.0 0 0 ? Z 07:50 0:00 [headless_shell] <defunct>
486 840 0.0 0.0 0 0 ? Z 07:50 0:00 [headless_shell] <defunct>
486 793 0.0 0.0 0 0 ? Z 07:50 0:00 [headless_shell] <defunct>
486 748 0.0 0.0 0 0 ? Z 07:49 0:00 [headless_shell] <defunct>
486 694 0.0 0.0 0 0 ? Z 07:49 0:00 [headless_shell] <defunct>
486 645 0.0 0.0 0 0 ? Z 07:49 0:00 [headless_shell] <defunct>
486 62 0.0 0.0 0 0 ? Z 07:45 0:00 [headless_shell] <defunct>
486 596 0.0 0.0 0 0 ? Z 07:49 0:00 [headless_shell] <defunct>
486 548 0.0 0.0 0 0 ? Z 07:49 0:00 [headless_shell] <defunct>
486 500 0.0 0.0 0 0 ? Z 07:48 0:00 [headless_shell] <defunct>
486 451 0.0 0.0 0 0 ? Z 07:48 0:00 [headless_shell] <defunct>

※「あれ?Lambdaってサーバレスだから毎回プロセスを破棄するんじゃないの?」と思ってましたが、そんなことはないようです。
https://qiita.com/koshigoe/items/afa3368352020660a220

ここからスーパー試行錯誤しました。
一定時間待ったり、クローラ実行用のchild processを作ってchild process毎killしたり、あらゆる手でプロセスの終了を試みました。

が、解決しなかった

※該当のissueも存在します。執筆時点でまだopenになっている。
https://github.com/GoogleChrome/puppeteer/issues/1825

バージョンのせいなのか、人によってkillするだけで上手くいったと報告してる人もいるのが難しいところでした。
私の場合は、puppeteer.launch時点で孤立したゾンビプロセスが生成され、消せなかった。。
ライブラリ依存の問題であろうことはわかってたので、Lambdaを辞めるという選択肢も含めて諦めかけていました。
もういっそこの際レポジトリにコントリビュートしてやろうかなと思っていたところ、意外な形で解決しました。

headless chromeの新バージョンが出た

https://github.com/alixaxel/chrome-aws-lambda
最新のheadless chromeに追従してくれるこのライブラリをベースにlaunch部分を書き直したら動きました。

今回のトラブルを活かして↑のレポジトリをlayerにしてserverlessで使えるようにテンプレート化したレポジトリを公開しています。
https://github.com/inatomitk/lambda-puppeteer-template

結論

バージョンアップは偉大

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

p5.jsの開発環境をwebpack4を使って整える

p5.jsの開発環境の選択肢としては、OpenProcessingなどのweb上のエディタを使うか、自前で用意するかどちらかになると思います。自前で用意する場合、p5-managerという便利なライブラリがあり、基本的にはこのp5-managerを使って開発環境を整えれば、自分のエディタ上で開発できる上に、watch機能もあるのでソースを編集したらすぐにホットリロードされてかなり便利です。

Webサイトに組み込んでいきたい

このp5-managerの環境でもwebサイトを作れると思いますが、cssやes6でクラスを使って開発していくには物足りません。そこで、webpackとes6で開発できる環境を整えるために良さそうなのがこちらのGitHubにありました。クローンして使ってみたところ、問題なく使えたので、しばらくこれを使って開発していて、いざGitHubにアップしてみたら、GitHub上で以下の警告を受けました。

スクリーンショット 2019-03-05 11.02.58.png

セキュリティの致命的な脆弱性が見つかったという警告ですが、どこが問題だったかというと、webpackのバージョンが古すぎるのが原因だそうです。

スクリーンショット 2019-03-05 11.03.37.png

webpack4に対応した環境を試してみる

webpackについては概念程度の知識しかなかったので、これを機に勉強がてら最新バージョンのwebpack4に対応した環境を試してみました。構築の際に参考にしたサイトは本家のドキュメントです。そのほか、webpackについては様々な方の記事を参考にしました。

脆弱性のあったのリポジトリのwebpack.config.jsの構成を見ながら何が必要かを洗い出していくと、構成的には以下のような構成で、オプションにある項目をwebpackのドキュメントから参照してconfigファイルを作りました。

オプション 自分なりの解釈
entry 変換対象のjsファイル
output 変換したデータの出力先。webpackコマンドで出力される。
module jsモジュール以外の処理やそのほかの依存依存関係を含む処理。ここではstyle-loaderなどのモジュールを使ってscssの処理をすぐに適用するように設定
devServer 開発用の設定。ポート番号と、watchするディレクトリの指定。
plugins js以外のファイルの変換などを行う設定
assetsのデータのコピーとhtmlの書き出し。htmlはbundle.jsが読み込まれた状態で出力される。

エラーに悩まされる

webpackのサイトの設定通りにconfigに書いていっても、エラーに遭遇してなかなか開発環境が整えられませんでした。とくに、pluginsの設定で

Tapable.plugin is deprecated. Use new API on `.hooks` instead

こういうエラーが出たり

compilation.mainTemplate.applyPluginsWaterfall is not a function
    at /var/www/myproject/node_modules/html-webpack-plugin/lib/compiler.js:81:

こういうエラーが出たりで、なぜ本家と同じ設定なのにできないのだ。。。と意気消沈してましたが、html-webpack-plugincopy-webpack-pluginのバージョンを最新にしたらあっさり解消されました。脆弱性があったリポジトリのnpmパッケージそのまま使っていたのがアダになりました。。。

なんとか完成

まだまだ理解に苦しむところもありますが、なんとかwebpack4で開発できる環境が整えられました。これでp5.jsを使ってアニメーションを作っていけます。

GitHubに置いておくので良ければどうぞ。
p5.jsだけでなく、そのままCanvasのアニメーションを扱うコードも入っていますので、p5.jsとcanvascontextを使ったアニメーションも同じcanvas上で両立して使えます。

output2.gif

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

[編集中]Puppeteerが遅い場合 〜対象ページでの接続速度の違い〜

はじめに

編集中で解説がまだ記載できておりませんが、先にコードだけ共有しておきます。

今回のコード

接続時間の検証コード
const puppeteer = require('puppeteer');

(async() => {
    const url = [
        'https://www.google.com/',
        'https://www.yahoo.co.jp/',
        'https://qiita.com/',
        'https://github.com/',
        'https://www.rakuten.co.jp/',
    ];

    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    for (let i = 0; i < 5; i++){
        console.log(url[i] + 'に接続中')
        console.time('接続までの時間');
        await page.goto(url[i]); // ページへ
        console.timeEnd('接続までの時間');
    }


// puppeteerを終了
await browser.close();
})();
実行結果(1回目)
https://www.google.com/に接続中
接続までの時間: 1190.734ms

https://www.yahoo.co.jp/に接続中
接続までの時間: 1360.209ms

https://qiita.com/に接続中
接続までの時間: 4163.477ms

https://github.com/に接続中
接続までの時間: 2957.095ms

https://www.rakuten.co.jp/に接続中
接続までの時間: 20221.243ms
実行結果(2回目)
// https://www.google.com/に接続中
// 接続までの時間: 1194.058ms

// https://www.yahoo.co.jp/に接続中
// 接続までの時間: 2463.839ms

// https://qiita.com/に接続中
// 接続までの時間: 1958.890ms

// https://github.com/に接続中
// 接続までの時間: 2816.664ms

// https://www.rakuten.co.jp/に接続中
// 接続までの時間: 20071.951ms
実行結果(3回目)
// https://www.google.com/に接続中
// 接続までの時間: 1171.473ms

// https://www.yahoo.co.jp/に接続中
// 接続までの時間: 1387.476ms

// https://qiita.com/に接続中
// 接続までの時間: 2444.999ms

// https://github.com/に接続中
// 接続までの時間: 2725.922ms

// https://www.rakuten.co.jp/に接続中
// 接続までの時間: 19709.615ms

考察

楽天が圧倒的に遅い。
平均値で見ると、おそらくgoogleの接続時間の10倍ほどかかっている。

ヘッドレスブラウザとはいえ、情報量の重いページは接続が遅いと思われる。
(編集中)

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

prettier でコミット前にフォーマットする

prettier lint-staged husky を使ってgit commit に合わせてJavaScriptのコードを整形します。

必要なパッケージをインストールする

npm install --save-dev prettier lint-staged husky

commitに連動させる

huskylint-stagedの設定をpackage.jsonにします。

package.json
{
  "devDependencies": {
    "husky": "^1.3.1",
    "lint-staged": "^8.1.5",
    "prettier": "^1.16.4"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,jsx}": [
      "prettier --write",
      "git add"
    ]
  }
}

prettierの整形ルールを作る

prettierの整形ルールを.prettierrc.jsに追加します。

prettierrc.js
module.exports = {
  printWidth    : 120,
  tabWidth      : 2,
  useTabs       : false,
  semi          : true,
  singleQuote   : true,
  trailingComma : 'es5',
  bracketSpacing: true,
  arrowParens   : 'avoid',
};

オプションの一覧: prettier.io/docs/en/options.html

おわり。

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

Amazonパンチアウトを利用する購買アプリケーション

概要

Eコマースアプリケーションを開発するための標準にcXMLがあります。その中にパンチアウトという仕組みがあり、AribaやAmazonなどがその機能を使ってオンラインカタログと注文の仕組みを提供しています。

この投稿ではパンチアウトを利用して物品を購買するアプリケーションの例を紹介します。ここではパンチアウト機能によってカタログや注文の仕組みを提供するサーバーをパンチアウトサイトと呼びます。

購買アプリケーションはパンチアウトサイトに対してPunchOutSetupRequestをPOSTします。リクエストを受け取ったパンチアウトサイトはPunchOutSetRequestに設定されたBuyerCookieを使って、ユーザー毎のカタログとショッピングカートを準備し、そのカタログにアクセスするURLをPunchOutSetupResponseに設定して返信します。

BuyerCookieは各購買アプリケーションがパンチアウトセッション毎に一意になるように生成する文字列です。UUIDなどを用いることが多いです。

購買アプリケーションはフレームかウィンドウを開いて、その中でPunchOutSetupResponseに設定されたURLにアクセスしてパンチアウトサイトが準備したカタログを表示し、ユーザーはその中で購入物品をショッピングカートに追加します。

ユーザーがパンチアウトサイト上でショッピングカートをチェックアウト(購入物品の確定)を行うと、パンチアウトサイトは購買アプリケーションにショッピングカートの内容を入れたPunchOutOrderMessageをPOSTします。

PunchOutOrderMessageを受け取った購買アプリケーションはPunchOutOrderMessageからショッピングカートの中身を自アプリケーションの購入品目リストに転記してパンチアウトサイトの画面を閉じます。一般的な購買アプリケーションでは、この後、購入品目リストを転記した購入申請書類をワークフローシステムなどに送り、決裁が行われた後にAmazonにOrderRequestを送信して発注処理が行われます。

オンラインカタログとしてパンチアウトを利用するシーケンス

buyer_catalog-server.png

購買アプリケーションを実行した様子

パンチアウトサイトからPunchOutSetupResponseを受信してカタログ画面を表示した様子

buyer-01.png

パンチアウトサイト上でショッピングカートに購入物品を追加した様子

buyer-02.png

パンチアウトサイト上でチェックアウト後、PunchOutOrderMessageを受け取って自アプリケーションに購入物品を取り込んだ様子

buyer-03.png

アプリケーションを動かしている様子の動画 (YouTube)

https://www.youtube.com/watch?v=v7nwgebzbXQ

アプリケーションの構成

Amazonはビジネスアカウント向けにこのパンチアウトの仕組みを提供していますが、ビジネスアカウントを取得せずに試作したかったので、PunchOutSetupRequest/PunchOutSetupResponseとPunchOutOrderMessageを処理するカタログサーバーを作って実験しました。

なるべく簡単に実験ができるように、購買アプリケーションとカタログサーバーは、MEANスタックの上で開発・実行しました。

肝の部分のコード

Catalog-ServerのPunchOutSetupRequestを受けてPunchOutSetupResponseを返信する処理
/**
 * Expressのルーター
 * PunchOutSetupRequestのPOSTデータを受け取ってPunchOutSetupResponseを返信する
 **/
router.post('/setup', (req, res) => {
    var data = req.body; 

    const punchoutRequest = punchoutSetupRequestFromData(data);

    console.log('Got PunchOutSetupRequest:');
    console.log(JSON.stringify(punchoutRequest));

    punchoutSetupResponse(punchoutRequest, res);
});

/**
 * PunchOutSetupRequestをパースしてJSON形式に変換する
 **/
function punchoutSetupRequestFromData(data) {
    const root = ...;
        /* cXMLのルートエレメントをdataから取得 */
    const header = ...;
        /* cXMLのHeaderを取得 */
    const fromIdentity = ...;処理
        /* HeaderからFrom.Credential.Identityを取得 */
    const toIdentity = ...;
        /* HeaderからTo.Credential.Identityを取得*/
    const senderIdentity = ...;
        /* HeaderからSender.Credential.Identityを取得*/;
    const senderSharedSecret = ...;
        /* Sender.Credential.IdentityのSharedSecret (アプリケーションの認証に使われる);
    const userAgent = ...;
        /* HeaderからSender.UserAgentを取得 */;
    const punchout = ...;
        /* PunchOutSetupRequestの本体 */;
    const cookie = ...;
        /* PunchOutSetupRequestからBuyerCookieを取得 (パンチアウトセッションのIDになる)*/
    const formPostURL = ...;
        /* PunchOutOrderMessageをPOSTするURL */;

    var poRequest = {
        payloadId: root.attributes.payloadID,
        timestamp: root.attributes.timestamp,
        from: fromIdentity.elements[0].text,
        to: toIdentity.elements[0].text,
        sender: senderIdentity.elements[0].text,
        sharedSecret: senderSharedSecret.elements[0].text,
        userAgent: userAgent.elements[0].text,
        operation: punchout.attributes.operation,
        buyerCookie: cookie.elements[0].text,
        browserFormPost: formPostURL.elements[0].text
    };

    return poRequest;
}

/**
 * PunchOutSetupResponseを返信する関数
 **/
function punchoutSetupResponse(punchoutRequest, res) {
    /*
        データベースにこのパンチアウトセッション用のショッピングカートを作成する。

        punchoutRequest.buyerCookieをカートのキーとして使用する。

        punchoutRequest.operationには新規作成や編集などのモードが設定されているので、
        実際のシステムではこれに応じた振る舞いをする。

        punchoutRequest.browserFormPostには購買アプリケーション側の
        PunchOutOrderMessage POSTの受け口のURLが入っている。
    */
    /*
        CatalogServiceはMongoDBにショッピングカートを作成する処理を行う。
    */
    CatalogService.prepareShoppingCart(punchoutRequest.buyerCookie, punchoutRequest.operation, punchoutRequest.browserFormPost).then( (result) => {
        /*
            カタログページのURLを生成
        */
        const url = 'http://localhost:5500/catalog/' + punchoutRequest.buyerCookie;

        const timestamp = moment().format();
        /*
            PunchOutSetupResponseのテンプレートを読み込み、timestampと
            StartPage.URLを差し替え、その結果を購買アプリケーションに返信する。
        */
        fs.readFile('routes/resources/PunchoutSetupResponse.xml', 'utf8', (err, data) => {
            if(err) {
                res.send(err);
            } else {
                let responseXml = data.replace('{timestamp}', timestamp).replace('{url}', url);
                console.log(responseXml);
                res.header('Content-Type', 'text/xml');
                res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
                res.header('Pragma', 'no-cache');
                res.send(responseXml);
            }
        });
    }).catch ( (err) => {
        console.log(err);
        res.send(err);        
    });
}

購買アプリケーション側でPunchOutOrderMessageを受け取った後の処理
function _get_element(data, elementName) {
    return data.elements.find(e => e.name === elementName);
}

function _get_elements(data, elementName) {
    return data.elements.filter(e => e.name === elementName);
}

const PunchoutHandler = {

    /* 
        PunchOutOrderMessageをMongoDBに保存する。
    */
    save: (json) => {
        let content = {};

        const cxml = _get_element(json, 'cXML');
        content.payloadID = cxml.attributes.payloadID;
        content.timestamp = cxml.attributes.timestamp;

        const header = _get_element(cxml, 'Header');
        content.from = _get_element(_get_element(_get_element(header, 'From'), 'Credential'), 'Identity').elements[0].text;
        content.to = _get_element(_get_element(_get_element(header, 'To'), 'Credential'), 'Identity').elements[0].text;
        content.sender = _get_element(_get_element(_get_element(header, 'Sender'), 'Credential'), 'Identity').elements[0].text;
        content.userAgent = _get_element(_get_element(header, 'Sender'), 'UserAgent').elements[0].text;

        const message = _get_element(_get_element(cxml, 'Message'), 'PunchOutOrderMessage');
        content.buyerCookie = _get_element(message, 'BuyerCookie').elements[0].text;

        const messageHeader = _get_element(message, 'PunchOutOrderMessageHeader');
        content.currency = _get_element(_get_element(messageHeader, 'Total'), 'Money').attributes.currency;
        content.total = parseInt(_get_element(_get_element(messageHeader, 'Total'), 'Money').elements[0].text);

        /*
            パンチアウトサイトでショッピングカートに入れた物品
        */
        content.itemIns = [];
        const itemElements = _get_elements(message, 'ItemIn');
        itemElements.forEach(elem => {
            let item = {};
            item.quantity = parseInt(elem.attributes.quantity);
            item.supplierPartID = _get_element(_get_element(elem, 'ItemID'), 'SupplierPartID').elements[0].text;
            const detail = _get_element(elem, 'ItemDetail');
            item.currency = _get_element(_get_element(detail, 'UnitPrice'), 'Money').attributes.currency;
            item.unitPrice = parseInt(_get_element(_get_element(detail, 'UnitPrice'), 'Money').elements[0].text);
            item.description = _get_element(detail, 'Description').elements[0].text;
            item.unitOfMeasure = _get_element(detail, 'UnitOfMeasure').elements[0].text;
            item.classificationDomain = _get_element(detail, 'Classification').attributes.domain;
            item.classification = _get_element(detail, 'Classification').elements[0].text;
            item.manufacturerPartID = _get_element(detail, 'ManufacturerPartID').elements[0].text;
            item.manufacturerName = _get_element(detail, 'ManufacturerName').elements[0].text;
            content.itemIns.push(item);
        });
        content.updated = true;

        return new Promise((resolve, reject) => {
            /*
                MongoDBに保存。
            */
            MongoClient.connect(config.db.url, config.db.options, (err, client) => {
                if (err) {
                    reject(err);
                }
                const dbo = client.db('buyer');
                dbo.collection('punchout_contents').findOneAndDelete({ buyerCookie: content.buyerCookie }, (err, result) => {
                    dbo.collection('punchout_contents').insertOne(content, (err) => {
                        if (err) {
                            client.close();
                            reject(err);
                        } else {
                            console.log('Punchout Response saved.');
                            client.close();
                            resolve(content);
                        }
                    });
                });
            });
        });
    },

   /*
       購買画面に表示する際にデータベースに保存済のショッピングカートの中身を取り出す処理。
   */
   consume: (buyerCookie) => {
        return new Promise((resolve, reject) => {
            MongoClient.connect(config.db.url, config.db.options, (err, client) => {
                if (err) {
                    reject(err);
                }
                const dbo = client.db('buyer');
                dbo.collection('punchout_contents').find({ buyerCookie: buyerCookie }).toArray( (err, result) => {
                    if(err) {
                        client.close();
                        reject(err);
                    } else {
                        dbo.collection('punchout_contents').findOneAndUpdate({buyerCookie: buyerCookie}, {$set: {updated: false}}, (err, r) => {
                            client.close();
                            if(err) {
                                console.log(err);
                            }
                            resolve(result[0]);
                        });
                    }
                });
            });
        });
    },

    ....
    ....
};

module.exports = PunchoutHandler;

雑感

多くのネット通販サイトがこのパンチアウトに対応してくれれば、中継サーバーや購買アプリケーションを開発して安価なサービスを提供できそうです。クラウド型の会計ソフトと連携するなどすれば中小企業の仕入れや販売に関する情報管理や業務を効率化できるように思います。元々cXMLはAribaなどの大企業相手のB2Bの仕組みを標準化するためのものでしたが、企業の大小を問わずに有効な仕組みではないかと思います。自営業者の一人として、経理処理や販売管理の面倒くささは切実な悩みです。同じ悩みを共有している方も多いと思います。Amazonや楽天などのネット販売サービスに登録している事業者さんたちや会計ソフトを提供している企業のみなさんには、こういうシステムへの関心を持っていただけたら嬉しいですし、何らかの形で日本の中小企業を応援できればと思っています。

最近、このようなアプリケーションの開発やドキュメント作成にはVisual Studio Codeを使っています。大変快適に作業ができる素晴らしいソフトウェアだと思います。

参考資料

  • Amazon Punchout FAQ
  • cXML.org PunchOutSetup/PunchOutOrderなどのcXML文書のフォーマットはここで知ることができます。

このような記事をブログに投稿していますので、お時間のある方は覗いてみてください。
(これが本業ではありません)

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

Javascript オブジェクトの浅い/深いコピー

Javascript オブジェクトの浅い/深いコピー

概要

Javascript でオブジェクトをコピーしたい場合、Object.assign()メソッドが有効です。Object.assign()メソッドは元々は2つのオブジェクトを合成するためのメソッドですが、コピーする目的でも使えます。Object.assign()メソッドはいわゆる浅いコピーとなっていて、注意が必要です。浅いコピーで何が問題になるのかを説明します。

浅いコピーの問題点

ゲーム制作をしていて、出現するモンスターの特徴をJavascript のオブジェクトで管理しているとしましょう。モンスターの名前はスライム、色は黒です。モンスターには武器を複数持たせることができて、今回はダメージが100の剣を装備しています。

const monster = {
  name: "スライム",
  color: "青", 
  weapons:[
    {name:"剣", damage:100}
  ]
}

figure1.png

ゲーム制作ではリソースの問題で同じモンスターの絵柄を使い回すことがあると思います。先程のスライムをコピーして闇スライムを作りましょう。

const darkMonster = Object.assign({},monster);
darkMonster.name = "闇スライム";
darkMonster.color = "黒";

console.log(monster);
// {"name":"スライム","color":"青","weapons":[{"name":"剣","damage":100}]}
console.log(darkMonster);
// {"name":"闇スライム","color":"黒","weapons":[{"name":"剣","damage":100}]}

figure2.png

良い感じですね。
闇スライムはなんとなく魔法を使いこなせそうなので、魔法の杖を装備するという設定にしましょう。

darkMonster.weapons[0] = {name:"魔法の杖", damage: 200};

console.log(monster);
// {"name":"スライム","color":"青","weapons":[{"name":"魔法の杖","damage":200}]}
console.log(darkMonster);
// {"name":"闇スライム","color":"黒","weapons":[{"name":"魔法の杖","damage":200}]}

figure2.png

ここで問題が起きました!
闇スライムの武器を魔法の杖に変更したら、なんとコピー元の通常スライムの武器まで変更されてしまいました。Object.assign()メソッドは浅いコピーです。浅いコピーでは、オブジェクトが直接保有する値しかコピーされません。nameプロパティは文字列、colorプロパティも文字列なのでそのままコピーされ問題はありませんが、weaponsプロパティが持っているのは配列への参照です。monsterオブジェクトのweaponsプロパティ(配列への参照)がコピーされて、darkMonsterのweaponsプロパティになりました。実際は両者のweaponsプロパティも同じメモリの箇所を参照しています。なので、闇スライムのweaponsプロパティの参照先の値を変更しようとすると、そこは通常スライムのweaponsプロパティが参照している場所と同じです。なので、通常スライムのweasponsプロパティも変更されてしまったのです。

深いコピー

オブジェクトが保有する値、その値が保有する値… とオブジェクトの深い階層まできっちりコピーするには別の方法があります。深いコピーをするためにはJSON.stringify()メソッドと、JSON.parse()メソッドを上手く使います。JSON.stringify()は、javasctiptのオブジェクトをJSON文字列に変換するメソッドで、JSON.parse()はその逆です。

// 深いコピー
const darkMonster = JSON.parse(JSON.stringify(monster));

darkMonster.name = "闇スライム";
darkMonster.color = "黒";
darkMonster.weapons[0] = {name:"魔法の杖", damage: 200};

console.log(monster);
// {"name":"スライム","color":"青","weapons":[{"name":"剣","damage":100}]}
console.log(darkMonster);
// {"name":"闇スライム","color":"黒","weapons":[{"name":"魔法の杖","damage":200}]}

figure4.png

lodashで深いコピー

lodash は、便利な関数を集めたライブラリです。実際にJavascript のプロジェクトにはよく導入されている気がします。lodash で深いコピーをするには以下のようになります。

// 深いコピー
const darkMonster = _.cloneDeep(monster);

まとめ

  • Object.assign()は浅いコピー(直接保有の値までしかコピーされない)
  • 深いコピーをするにはJSON.stringify()JSON.parce()を上手く使う
  • 深いコピーをするにはlodashを使う
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】超ざっくり入門(Hooks)

概要

React16.8から新しい機能としてHooksが導入されました。
以下の順序でHooksとは何かざっくり入門していきましょう。

Hooksとは?

Hooksとは従来classコンポーネントでしか使用できなかったstate等の機能をfunctionalコンポーネントでも使用可能にするReactに導入された新しい機能です。

この記事では以下の2種類のhookを紹介します。

  • (1)State Hook:statesetStateをfunctionalコンポーネント内で使用可能にする。
  • (2)Effect Hook:データ取得やDOM変更といったside effectsをfunctionalコンポーネント内で使用可能にする。

Hooksの規則

Hooksを使用する際には以下の規則を守りましょう。

  • (1) Hooksは必ずfunctionalコンポーネント中の一番上(トップレベル)に定義する。(ループ、条件分岐、ネストされた関数の中で定義してはいけません。)
  • (2) HooksはReactのfunctionalコンポーネントの中だけで呼ぶ。(通常のJavascriptの関数から使用してはいけません。)

State Hook

上記で記した様に、statesetStateをfunctionalコンポーネント内で行える様にします。
具体的にはuseStateと呼ばれるhookを使用します。

useStateの使用方法を従来のclassコンポーネントと比較して確認していきます。

従来のclassコンポーネント

以下の例では主に2つの事を行なっています。

  • (1)stateで初期値に0を持つcountを定義する。
  • (2)setStateでボタンを押すたびにcount1を足している。

output2

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>{this.state.count} 回クリックしました。</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          クリックしてください。
        </button>
      </div>
    );
  }
}

StateHook

上記と全く同じ事をuseStateを使用して行います。

まずuseStateをインポートしましょう。

import React, { useState } from  'react';

useStateでは従来のstate(ここではcount)とsetStateを同時に定義します。
以下の2点に注目して以下のコードを見てみましょう。

  • (1) 以下のsetCountがclassコンポーネントでいうsetStateの部分。
  • (2) useState()に渡されている0countの初期値。
import React, { useState } from  'react';

function Counter() {
  const [count, setCount] = useState(0)
}

また以下の2点を踏まえて残りのコードを完成させます。

  • (1) stateを直接参照できる。
  • (2) setCountを使用してstateを更新できる。
import React, { useState } from  'react';

function Counter() {
  const [count, setCount] = useState(0)

  return(
    <div>
      // (1) stateを直接参照できる。
      <p>{count} 回クリックしました</p>

      // (2) setCountを使用してstateを更新できる。
      <button onClick={() => setCount(count + 1)}> クリックしてください。</button> 
    </div>
  )
}

export default Counter;

ちなみに以下の様にして複数のstateを定義していきます。

import React, { useState } from  'react';

function Counter() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('')

  return(
    <div>
      <p>{count} 回クリックしました</p>
      <button onClick={() => setCount(count + 1)}> クリックしてください。</button>
      <p>私の名前は{name}</p>
      <button onClick={() => setName('テスト太郎')}>名前表示</button>
    </div>
  )
}

export default Counter;

Effect Hook

上記で記した通り、データ取得やDOM変更といったside effectsをfunctionalfunctionコンポーネント内で使用可能にします。

従来のclassコンポーネントではcomponentDidMountcomponentDidUpdatecomponentWillUnmount等の中でデータ取得やDOM変更を行なっていましたが、これをuseEffectと呼ばれるhookを使用し、functionalコンポーネントでも使用可能にします。

こちらもuseEffectの使用方法を従来のclassコンポーネントと比較して確認していきます。

従来のclassコンポーネント

従来はcomponentDidMountはマウントされた直後に1回だけ呼ばれ、componentDidUpdateはコンポーネントが更新された後に呼ばれ、ここでデータの取得等を行いました。

output3

import React from  'react';

class Effect extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    console.log('マウントされました。')
  }

  componentDidUpdate() {
    console.log('アップデートされました。')
  }

  render() {
    return (
      <div>
        <h1>{this.state.count} 回クリックしました。</h1>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          クリックしてください。
        </button>
      </div>
    );
  }
}

EffectHook

useEffectは従来のcomponentDidMountcomponentDidUpdatecomponentWillUnmountの3つを組み合わせた様なもので、基本的にrenderされる毎に呼ばれます。(first renderを含む。)
hooksを使用した開発ではここでside effectsを実行します。

output4

import React, { useState, useEffect } from 'react'

function Effect() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    console.log('レンダーされました。')
  })

  return(
    <div>
      <h1>{count} 回クリックしました</h1>
      <button onClick={() => setCount(count + 1)}> クリックしてください。</button>
    </div>
  );
}

参照

hooksの説明としては本当にざっくりでしたので、詳細は公式ドキュメントで確認ください。

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

"何回閉じても無駄ですよ〜"と無限にアラートを出す10の方法

掲示板に書き込むと補導または書類送検されるので気をつけてください

while

これは定番ですね。

while(1) alert('何回閉じても無駄ですよ〜')

for

for(;;) alert('何回閉じても無駄ですよ〜')

setInterval

setInterval(() => alert('何回閉じても無駄ですよ〜'))

alertだけでよければ、以下でも実行できます。

setInterval(alert)

setTimeout

function f() {
  alert('何回閉じても無駄ですよ〜')
  setTimeout(f)
}
setTimeout(f)

setImmediate

function f() {
  alert('何回閉じても無駄ですよ〜')
  setImmediate(f)
}
setImmediate(f)

requestAnimationFrame

function f() {
  alert('何回閉じても無駄ですよ〜')
  requestAnimationFrame(f)
}
requestAnimationFrame(f)

requestIdleCallback

function callback() {
  alert('何回閉じても無駄ですよ〜')
  requestIdleCallback(callback)
}
requestIdleCallback(callback)

再帰関数(Recursive function)

function f() {
  alert('何回閉じても無駄ですよ〜')
  f()
}
f()

内部DSL(Internal Domain Specific Language)

import('./何回閉じても無駄ですよ.js')
  .then(({何回閉じても無駄ですよ}) => 何回閉じても無駄ですよ())

See the Pen 何回閉じても無駄ですよ〜 by John Doe (@04) on CodePen.

TBD... 編集リクエスト待ってます

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

昨日の復習をやってみた『そして伝説に…』

今回作成したのはタイトルにある通り、「昨日の復習をやってみた『そして伝説に…』」です!
インスタンスやコンストラクタやprototypeなど前回の記事でちょっぴり勉強したので、それを面白おかしく使ってみたら覚えるかな?って事で試してみました!

以下様々なGASの記事を紹介してます!
https://bzbot.work/

紹介記事

今回紹介している記事は以下です!
https://bzbot.work/2019/03/05/gas-legend/

こだわり抜いたこのサムネイル画像!(笑)

image.png

作りたいもの

人をオブジェクトのコンストラクタとして昨日は勉強したので、『ヒューマン』を型として作ってみようと思いました。
まず、それぞれのJOBごとに必要な情報をまとめてみました。
画像データは以下サイトにて、お借りしました。ありがとうございます!
https://hiyokoyarou.com/tag/%E6%AD%A6%E5%99%A8/

person1

名前:フォースに好かれたチョン
JOB:フォース
HP:15
MP:10
こうげきりょく:15
ぼうぎょりょく:8
かしこさ:8
性格:おっちょこちょい
紹介
月に旅行に行った際に買ってきた温泉のおみやげ。
振るたびに『ボォオン』と音がなる。
暗い夜道を照らすのにちょうどいい。
=IMAGE(“http://drive.google.com/uc?export=view&id=1MDtpx5sHbcwB0q6j_oKVUWWXRgcTkWPQ”)

person2

名前:花の剣士チュン
JOB:剣士
HP:12
MP:15
こうげきりょく:10
ぼうぎょりょく:7
かしこさ:12
性格:おっとり
紹介
盾を使った行動がメイン。主に盾で殴る事が得意。盾以外で得意な事と言ったら花魔法を使える事。花魔法で相手を乗り物酔いにさせる事ができる。しかし、相手が乗り物酔いをしない場合は効果がない。
=IMAGE(“http://drive.google.com/uc?export=view&id=18CccewciUSEB5vwgKHr_o67qAA26OCQX”)

person3

名前:スナイパーチェン
JOB:銃
HP:14
MP:6
こうげきりょく:15
ぼうぎょりょく:8
かしこさ:12
性格:しっかりもの
紹介
こう見えて目が悪く、射撃の命中率は5割がいいところ。しかし、あまりに外しすぎると目に頼ることをやめ、目を閉じたまま寝てしまう。寝る事で消費した体力と精神的ダメージは回復するが、反省はしない。
=IMAGE(“http://drive.google.com/uc?export=view&id=1tQyFkcam9pNw0hU0t78OTd1EtMyL3ett”)

person4

名前:大魔導士ツン
JOB:賢者
HP:30
MP:50
こうげきりょく:10
ぼうぎょりょく:10
かしこさ:70
性格:うっかりもの
紹介
ぼーっと空を眺める事が好きな大魔導士ツン。しかし戦闘が始まれば人が変わったように戦う姿はまるで魔人と変わらない。
この大陸にその名を知らぬものはいないと言われている。
=IMAGE(“http://drive.google.com/uc?export=view&id=1htSnToHtJp9kd8ryM1xlSNzZAu9EFODZ”)

person5

名前:勇者見習いテン
JOB:勇者見習い
HP:13
MP:8
こうげきりょく:13
ぼうぎょりょく:12
かしこさ:10
性格:おちょうしもの
紹介
勇者のお兄ちゃんに憧れを抱く勇者見習いのテン。一生懸命お兄ちゃんの背中を追うが、彼は腹違いの子。勇者の血を引いていない事を知らない。しかし、彼は努力の天才で後に英雄となる。
=IMAGE(“http://drive.google.com/uc?export=view&id=1Rv7bibyCSxE5kxRySZRES23MyZzK4IU_”)

person6

名前:勇者アルスト
JOB:勇者
HP:50
MP:20
こうげきりょく:50
ぼうぎょりょく:25
かしこさ:20
性格:やさしい
紹介
ある日、大魔王ゾーマゾを倒すため旅に出る事になった勇者アルスト。光の弾を追い求め東の地を目指している。
彼はこの世の全ての希望であり、光である。
=IMAGE(“http://drive.google.com/uc?export=view&id=1tDWLsohVcgbKCsRBy0PoJIB6x2KYP79g”)

※IMAGE関数でGoogleDriveに入っているデータを呼び出してスプレッドシート上に表示します

実行画面

スプレッドシートのセル面積を小さくして、ドット絵っぽくしてみました!(笑)しかし、これが動くとかではないので、残念なのですが『▶︎ぼうけんをはじめる』をクリックすると名前を聞かれます。
image.png

クリックすると以下ウィンドウが立ち上がります。
image.png

名前を入力すると、JOBを選択する画面が表示されます。
ここに表示されている6つのJOBを選択して、入力フォームに入れます。
image.png

名前を入力すると、JOBを選択する画面が表示されます。
ここに表示されている6つのJOBを選択して、入力フォームに入れます。
image.png

コード

コードの概要についてはビジボットを参照してください
https://bzbot.work

GAS
function onOpen(){

  var yName = Browser.inputBox('あなたの名前は?');
  if (yName !== 'cancel'){
     var job = Browser.inputBox('こんにちは、' + yName + 'さん。\\n\\nこれからCHIKIN QUESTの世界に出発するために、あなたの代わりにプレーをするキャラクターを選択します。\\nJOBを以下から選択してください。\\n\\nフォース 剣士 銃 賢者 勇者見習い 勇者');
     selectJob(job);
  }
}  

var Person = function(psn){
  name = psn[0][1];
  job = psn[1][1]
  hp = psn[2][1];
  mp = psn[3][1];
  atc = psn[4][1];
  def = psn[5][1];
  int = psn[6][1];
  per = psn[7][1];
  url = psn[8][1];
  desc = psn[9][1];
};

Person.prototype.msg = function(){
Logger.log(name);
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sh1 = ss.setActiveSheet(ss.getSheetByName("スタート"));
  var sh2 = ss.setActiveSheet(ss.getSheetByName("キャラ"));
  sh2.getRange(1,2).setValue(name);
  sh2.getRange(3,2).setValue(hp);
  sh2.getRange(4,2).setValue(mp);
  sh2.getRange(5,2).setValue(atc);
  sh2.getRange(6,2).setValue(def);
  sh2.getRange(7,2).setValue(int);
  sh2.getRange(8,2).setValue(per);
  sh2.getRange(2,2).setValue(url);
  sh2.getRange(9,2).setValue(desc);  
};


function selectJob(job){
Logger.log(job);
  if(job === 'フォース'){
    var personArr = [
    ['name','フォースに好かれたチョン'],
    ['job','フォース'],
    ['hp',15],
    ['mp',10],
    ['atc',15],
    ['def',8],
    ['int',8],
    ['per','おっちょこちょい'],
    ['url','=IMAGE("http://drive.google.com/uc?export=view&amp;id=1MDtpx5sHbcwB0q6j_oKVUWWXRgcTkWPQ")'],
    ['desc','ある日、大魔王ゾーマゾを倒すため旅に出る事になった勇者アルスト。光の弾を追い求め東の地を目指している。彼はこの世の全ての希望であり、光である。']
    ];

}else if(job === '剣士'){
    var personArr = [
    ['name','花の剣士チュン'],
    ['job','剣士'],
    ['hp',12],
    ['mp',15],
    ['atc',10],
    ['def',7],
    ['int',12],
    ['per','おっとり'],
    ['url','=IMAGE("http://drive.google.com/uc?export=view&amp;id=18CccewciUSEB5vwgKHr_o67qAA26OCQX")'],
    ['desc','盾を使った行動がメイン。主に盾で殴る事が得意。盾以外で得意な事と言ったら花魔法を使える事。花魔法で相手を乗り物酔いにさせる事ができる。しかし、相手が乗り物酔いをしない場合は効果がない。']
    ];

  }else if(job === '銃'){
    var personArr = [
    ['name','スナイパーチェン'],
    ['job','銃'],
    ['hp',14],
    ['mp',6],
    ['atc',15],
    ['def',8],
    ['int',12],
    ['per','しっかりもの'],
    ['url','=IMAGE("http://drive.google.com/uc?export=view&amp;id=1tQyFkcam9pNw0hU0t78OTd1EtMyL3ett")'],
    ['desc','こう見えて目が悪く、射撃の命中率は5割がいいところ。しかし、あまりに外しすぎると目に頼ることをやめ、目を閉じたまま寝てしまう。寝る事で消費した体力と精神的ダメージは回復するが、反省はしない。']];

  }else if(job === '賢者'){
    var personArr = [
    ['name','大魔導士ツン'],
    ['job','賢者'],
    ['hp',30],
    ['mp',50],
    ['atc',10],
    ['def',10],
    ['int',70],
    ['per','おっちょこちょい'],
    ['url','=IMAGE("http://drive.google.com/uc?export=view&amp;id=1htSnToHtJp9kd8ryM1xlSNzZAu9EFODZ")'],
    ['desc','ぼーっと空を眺める事が好きな大魔導士ツン。しかし戦闘が始まれば人が変わったように戦う姿はまるで魔人と変わらない。この大陸にその名を知らぬものはいないと言われている。']];

  }else if(job === '勇者見習い'){
    var personArr = [
    ['name','勇者見習いテン'],
    ['job','勇者見習い'],
    ['hp',13],
    ['mp',8],
    ['atc',13],
    ['def',12],
    ['int',10],
    ['per','おちょうしもの'],
    ['url','=IMAGE("http://drive.google.com/uc?export=view&amp;id=1Rv7bibyCSxE5kxRySZRES23MyZzK4IU_")'],
    ['desc','勇者のお兄ちゃんに憧れを抱く勇者見習いのテン。一生懸命お兄ちゃんの背中を追うが、彼は腹違いの子。勇者の血を引いていない事を知らない。しかし、彼は努力の天才で後に英雄となる。']];

  }else if(job === '勇者'){
    var personArr = [
    ['name','勇者アルスト'],
    ['job','勇者'],
    ['hp',50],
    ['mp',20],
    ['atc',50],
    ['def',25],
    ['int',20],
    ['per','やさしい'],
    ['url','=IMAGE("http://drive.google.com/uc?export=view&amp;id=1tDWLsohVcgbKCsRBy0PoJIB6x2KYP79g")'],
    ['desc','ある日、大魔王ゾーマゾを倒すため旅に出る事になった勇者アルスト。光の弾を追い求め東の地を目指している。彼はこの世の全ての希望であり、光である。']];
    }
  var person = new Person(personArr);
  Logger.log(person.msg());
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む