20201122のJavaScriptに関する記事は24件です。

CLARANSをJavaScriptで実装した

はじめに

色々な機械学習処理をブラウザ上で試せるサイトを作った」中で実装したモデルの解説の七回目です。

今回はPAMおよびCLARANSの実装について解説します。

デモはこちらから。(TaskをClusteringにして、ModelのCLARANSを選択)
実際のコードはclarans.jsにあります。

なお、可視化部分については一切触れません。

概説

朱鷺の杜Wikiのアルゴリズムをコードに起こしただけです。

※PAMは認証モジュールのことではありませんのでご注意ください。

PAM(Partitioning Around Medoids)

最初にランダムにセントロイドを選択します。
そのセントロイドの中から一つをガリガリと入れ替え、最善のコストになる場所を見つけます。
fit関数一回の呼び出に対して、最善の入れ替えを各セントロイドで一回ずつ行います。

コストはセントロイドとそのカテゴリに属するデータとの距離の総和とし、それもガリガリと計算しています。
k-medois法と同じようなものですが、それとは異なり全パターンをチェックすることになります。

なんとなく間違っているような気がしているため、GUI側からは使用できないようにしています。

CLARA(Clustering LARge Applications)

イメージがいまいちつかめていないため、実装していません。

CLARANS(Clustering Large Applications based on RANdomized Search)

隣接した分割は、一つのデータだけカテゴリを変更した状態と考えます。

本質的なアルゴリズムは単純です。
全データ中から一つデータを選択し、そのカテゴリをランダムに変更してコストを計算、そしてコストが小さくなれば採用する、ということを繰り返します。
この繰り返しに対して、numlocalmaxneighborという細かい条件を付けているだけです。

使用するコストは、カテゴリに属するデータの重心と各データとの距離の総和とします。

コード

PAM

this._centroidsには各クラスタのセントロイドとするデータのインデックスが設定されます。

class PAM {
    // http://ibisforest.org/index.php?CLARANS
    constructor(k) {
        this._k = k
    }

    _distance(a, b) {
        return Math.sqrt(a.reduce((acc, v, i) => acc + (v - b[i]) ** 2, 0));
    }

    _cost(centroids) {
        const c = centroids.map(v => this._x[v])
        const n = this._x.length
        let cost = 0;
        for (let i = 0; i < n; i++) {
            const category = argmin(c, v => this._distance(this._x[i], v))
            cost += this._distance(this._x[i], c[category])
        }
        return cost
    }

    init(datas) {
        this._x = datas;
        const idx = []
        for (let i = 0; i < this._x.length; idx.push(i++));
        shuffle(idx)
        this._centroids = idx.slice(0, this._k);
    }

    fit() {
        const n = this._x.length
        let init_cost = this._cost(this._centroids)
        for (let k = 0; k < this._k; k++) {
            let min_cost = Infinity;
            let min_idx = -1;
            for (let i = 0; i < n; i++) {
                if (this._centroids.some(c => c === i)) {
                    continue
                }
                const new_c = this._centroids.concat()
                new_c[k] = i
                const new_cost = this._cost(new_c);
                if (new_cost < min_cost) {
                    min_cost = new_cost
                    min_idx = i
                }
            }
            if (min_cost < init_cost) {
                this._centroids[k] = min_idx
                init_cost = min_cost
            }
        }
    }

    predict() {
        const c = this._centroids.map(v => this._x[v])
        return this._x.map(x => {
            return argmin(c, v => this._distance(x, v));
        });
    }
}

CLARANS

this._categoriesには各データのクラスタ番号が設定されます。

class CLARANS {
    // http://ibisforest.org/index.php?CLARANS
    constructor(k) {
        this._k = k
    }

    _distance(a, b) {
        return Math.sqrt(a.reduce((acc, v, i) => acc + (v - b[i]) ** 2, 0));
    }

    _cost(categories) {
        const n = this._x.length
        const centroids = []
        const count = []
        for (let i = 0; i < n; i++) {
            const cat = categories[i]
            if (!centroids[cat]) {
                centroids[cat] = this._x[i].concat()
                count[cat] = 1
            } else {
                for (let k = 0; k < this._x[i].length; k++) {
                    centroids[cat][k] += this._x[i][k]
                }
                count[cat]++
            }
        }
        for (let i = 0; i < centroids.length; i++) {
            for (let k = 0; k < centroids[i].length; k++) {
                centroids[i][k] /= count[i]
            }
        }
        let cost = 0;
        for (let i = 0; i < n; i++) {
            cost += this._distance(this._x[i], centroids[categories[i]])
        }
        return cost;
    }

    init(datas) {
        this._x = datas;
        this._categories = [];
        for (let i = 0; i < this._x.length; i++) {
            this._categories[i] = Math.floor(Math.random() * this._k);
        }
    }

    fit(numlocal, maxneighbor) {
        const n = this._x.length
        const categories = this._categories;
        let i = 1;
        let mincost = Infinity
        while (i <= numlocal) {
            let j = 1
            let cur_cost = this._cost(categories)
            while (j <= maxneighbor) {
                const swap = Math.floor(Math.random() * n)
                const cur_cat = categories[swap]
                const new_cat = Math.floor(Math.random() * (this._k - 1));
                categories[swap] = (new_cat >= cur_cat) ? new_cat + 1 : new_cat;
                const new_cost = this._cost(categories)
                if (new_cost < cur_cost) {
                    j = 1;
                    cur_cost = new_cost
                    continue
                }
                j++
                categories[swap] = cur_cat
            }
            if (cur_cost < mincost) {
                mincost = cur_cost
            }
            i++
        }
    }

    predict() {
        return this._categories
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ES2020の仕様には、こんなのが追加されました!

はじめに

Ecma International で定められている ES2020 について2020年6月にリリースされたので改めてまとめたいと思います。
仕様についてはこちらに記載されています。

Optional Chaining

Optional Chaining は、 ? を使って、 nullundefined の要素にも安全にアクセスできる仕組みです。

const obj = {
  a: 1,
  b: {
    ba: 21
    bb: 22
  }
}
console.log(obj.b?.ba) 
// => 21
console.log(obj.b?.bc) 
// => undefined
console.log(obj.c?.ca) 
// => undefined
console.log(obj.d?.da?.daa?.daaa) 
// => undefined

このように要素がない場合は、エラーになることなく、 undefined が返されます。

Nullish coalescing Operator

Nullish coalescing Operator は、 ?? を使って、変数が null の場合の値を指定することができます。

const getValue = (val) => val || 'default'
const getValue2 = (val) => val ?? 'default'

console.log(getValue(''))
// => 'default'
console.log(getValue(0))
// => 'default'

console.log(getValue2(''))
// => ''
console.log(getValue2(0))
// => 0

console.log(getValue2('hoge'))
// => 'hoge' 
console.log(getValue2(null)) 
// => 'default'

このように、 valfalse 判定される場合には、 'default' が返されます。

Dynamic import

Dynamic import は、

module.js
export const hoge = "hoge!!"
import("./module.js").then(module => {
  console.log(module.hoge)
 // => hoge!!
})

setTimeout(async () => {
  const { hoge } = await import("./module.js")
  console.log(hoge)
  // => hoge!!
}, 1000)

このように Dynamic import では、 Promise の形でモジュールを読み込むことができます。
なので、使いたい時だけモジュール非同期で import して使用するということも可能です。

Promise.allSettled

Promise.allSettled は、複数の Promise を扱うことができます。 Promise.all と違い、複数のうちどれか1つが reject されても他の Promise は問題なく実行されます。

const promiseList = [
  Promise.resolve("ok"),
  Promise.resolve("ok"),
  Promise.reject("ng"),
  Promise.resolve("ok")
]

Promise.all(promiseList).then(
  resolve => console.log(`resolve: ${resolve}`),
  reject => console.log(`reject: ${reject}`)
) 
// => reject: ng

Promise.allSettled(promiseList).then(
  resolveList => {
    console.log("resolve")
    for (const resolve of resolveList) {
      console.log(resolve)
    }
  },
  reject => {
    console.log("reject")
    console.log(reject)
  }
) 
// => resolve
// => { status: 'fulfilled', value: 'ok' }
// => { status: 'fulfilled', value: 'ok' }
// => { status: 'rejected', reason: 'ng' }
// => { status: 'fulfilled', value: 'ok' }

String#matchAll

String#matchAll は、対象文字列について、正規表現で一致したものをイテレータで返します。

const text = "Test String";
const regex = /t/g;
for (const match of text.matchAll(regex)) {
  console.log(match)
}
// => [ 't', index: 3, input: 'Test String', groups: undefined ]
// => [ 't', index: 6, input: 'Test String', groups: undefined ]

このように、 regex にマッチしたものをイテレータで回すことができるので便利です。

globalThis

globalThis は、ウェブブラウザでもNode.jsもグローバルオブジェクトを参照できるオブジェクトです。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title></title>
  </head>
  <body>
    <script>
      console.log(globalThis) // Window が出力されます
    </script>
  </body>
</html>
console.log(globalThis); // Global が出力されます

このようにウェブブラウザ・Node.jsで共通のグローバルオブジェクトを参照できます。

BigInt

BigInt は、 Number より大きな整数 2^53 以上の整数を扱えるオブジェクトです。

number を使うと

console.log(Number.MAX_SAFE_INTEGER) 
// => 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1) 
// => 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2) 
// => 9007199254740992
//   (9007199254740993ではない)

このように誤差が生じてしまいます。

BigInt を使うことでこのような値も正しく扱うことができます。 BigInt は、数値に n を追加することで使用することができます。

console.log(BigInt(Number.MAX_SAFE_INTEGER) + 2n) 
// => 9007199254740993

Well defined for-in order

従来の ECMAScript の使用では、 for-in の順序は保証されていませんでしたが、順序が固定されるようになりました。

const data = { name: "hoge", value: 100, text: "hoge" }

for (const key in data) {
  console.log(`${key}: ${data[key]}`)
}
// => name: hoge
// => value: 100
// => text: hoge

Module Namespace Exports

Module Namespace Exports は、 import してきたものをそのまま export することができます。

import * as utils from './utils.js'
export { utils }

これと同じことが以下のように書くことができます。

export * as utils from './utils.js'

import.meta

import.meta を使うことで、 import したモジュールのメタ情報にアクセスできます。

<script type="module" src="module.js"></script>
console.log(import.meta)
// => { url: "file:///home/user/module.js" }

さいごに

いかがだったでしょうか?
いろんな便利な機能が追加されたように思います。
それぞれ駆使して、より良いソースコードをかけるようになりましょう!

参考

https://ics.media/entry/200128/
https://shisama.hatenablog.com/entry/2019/12/01/080000#ES2020
https://www.freecodecamp.org/news/javascript-new-features-es2020/

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

airbnbのページ模写(途中経過)~完成(?)した事にした 先に進むために!

お久しぶりです。やりたいことがころころ変わる魔物です。
途中経過などは→ギタリストの魔物のツイッターで随時報告してます!

"#魔物の努力n日目"っていうハッシュタグでnの位置に数字入れると過去の見れます!
※まだ1~5くらいしかないけど! (2020/11/21時点)

何をしているか

多少タグの知識はあったんですが、
web系の勉強してたら1度は目にしたことあるんじゃないかっていう
有益なツイートをたくさんしてくださるマナブさんのロードマップを参考にしています。

・Twitter(@manabubannai)
・YouTube マナブ 登録者数約49万人!!
・運営されてるサイト→マナブログ(参考にしているロードマップのリンクです)
※ブクマ推奨

実際にやったこと

上からザーッと読み進め
最初は

・はじめてのHTML(24本)
・はじめてのCSS(34本)

これを2日かけて視聴しました。
ちゃんとその通りに動くかエディタにコードを書いて手を動かしながら。
1本1本は5分前後と短いのですが、一気見しようとするとボリュームがすごいです・・

CSSの終わりらへんはpaddingとかmarginとか色々出てきて苦痛でしたのでとりあえず

動画を見るだけ見て実際扱うときにまた見直して理解できればいいや!!

って感じで進めました。

現在どこまで進んだか

今のところコピペは文字だけをテキストとしてとってくる
以外は一切やっておらず進められていることに気分が高まっています。。

Bootstrapの4系、レイアウトはflexの理解が浅かったのでGridレイアウトを使用しています。
JavaScriptはまだファイルだけ状態なので中身はありません。

簡単に環境説明

・HTML5
・Bootstrap4系
・Gridレイアウト
・CSS3
・JavaScript

分かる人から見ればまだまだ見づらいかもしれないけどコードはこんな感じ

index.html
 <!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <!--BootstrapのCSSの読み込み-->
        <link rel="stylesheet" href="..\no1\BS\bootstrap-4.5.3-dist\css\bootstrap.min.css">

        <!--jQueryの読み込み
        Bootstrapのjsを使うためにはjQueryが必要なので先に読み込む必要がある-->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

        <!--Bootstrapのjsの読み込み-->
        <script src="..\no1\BS\bootstrap-4.5.3-dist\js\bootstrap.min.js"></script>

        <!--自作jsの読み込み-->
        <script src="index.js"></script>

        <link rel="stylesheet" type="text/css" href="style.css">
        <title>練習サイト</title>
    </head>

    <body>
        <div ID="container-1">
            <div class="f-item">

                <h1><a href="index.html">Airbnbにお部屋を<br>掲載しよう</a></h1>

                活気に満ちたホストコミュニティに参加し、<br>
                記憶に残るとっておきの滞在を旅行者に提供<br>
                しながら、好きなことを追求するための収入を<br>
                得ましょう。<br>

                <input type="button" class="btn btn-danger btn-sm" value="はじめる">

            </div>
            <!--container-1の中のコンテナ-->
            <div class="f-item02">test</div>

        </div>
        <hr color="black">
            製作途中・・
        <hr color="black">

        <div class=midashi><h1>ホスティングの流れを確認しよう</h1></div>
        <hr color="black">


        <div class=midashi><h1>城からコンドミニアムまで、ゲストはあらゆる宿泊先を予約します</h1></div>
        <hr color="black">

        <div class=midashi><h1>Airbnbはみなさまの安全を最優先に考えております</h1></div>

        <div ID="container-2">
            <div class="f-item2">
            <h2>ホスト向けの保険と補償</h2>
                万が一の事故の際にホストのみなさまをお守りするため、最大US$1,000,000の物損補償および最大US$1,000,000の賠償責任保険がAirbnbの全予約に自動付帯されます。

                <br><br>
                <a href="#">ホストを守るAirbnbの取り組み</a>
            </div>

            <div class="f-item2">
            <h2>新型コロナウイルスに関する安全ガイドラインとサポート</h2>
                コミュニティのみなさまの健康を守るため、Airbnbは専門家と連携し、メンバー全員のための安全対策とホスト向けの清掃スタンダードを作成しました。

                <br><br>
                <a href="#">清掃スタンダードのご案内</a>
            </div>

            <div class="f-item2">
            <h2>すべてのゲストに適用される要件</h2>
                ホストのみなさまにご安心いただくため、Airbnbでは、予約前にゲストに身分証明書の提示を求め、過去のホストによるゲストのレビューを閲覧できるようにしています。Airbnbの新しいゲスト基準ポリシーは、これまで以上に高い行動基準をゲストに求めるものです。

                <br><br>
                <a href="#">安心してホスティングしていただくためのAirbnbの取り組み</a>

            </div>
        </div>

    <div id="container">

            <div id="item-a">
                <p>予想ホスティング収入額をチェック</p>
                <p><h1>Fukuokaでホスティングして、<br>ひと月あたり最大<span style="color: rgb(182, 41, 41);">¥ 58,441</span>の収入を得ましょう</h1></p>
                <a href="#">予想ホスティング収入額の計算方法</a>
            </div>

            <div id="item-b">
                <p>予想ホスティング収入額を更新するにはお部屋についてさらに詳しくお聞かせください</p>
                <input type="text"  class="inText" value="所在地"><br>
                <select name="room" class="room" value="まるまる">
                    <optgroup label="まるまる貸し切り">
                            <option style="color: black;" value="まるまる貸し切り">
                                まるまる貸し切り<br>
                                この文から下に表示させたい(改行)
                            </option>

                            <option>
                                個室

                            <option>
                                シェアルーム

                            </option>

                        </optgroup>
                </select>

                <hr color="black">
                ここに新しく作る

                <hr color="black">


                <input type="text"  class="inText" value="まるまる貸切"><br>
                <input type="text"  class="inText" value="ゲスト4人"><br><br>

                <p>ゲストが利用できる特別なスペースがあればお知らせください</p>
                <input type="button" class="btn btn-dark btn" value="リスティングの掲載をはじめる">
            </div>         

    </div>    

    <p>ここから下は機能的な実験のテスト領域です</p>




        Airbnbがサポートいたします
        24時間365日対応のカスタマーサポート
        リスティングの設定からゲストに関する懸念まで、Airbnbのグローバルチームが電話、メール、チャットですべてのステップをサポートします。
        成功に役立つさまざまなツール
        Airbnbのツールを活用すれば、的確な料金設定、予約管理、ゲストとのメッセージの送受信、支払いの受け取り、ホスティング収入の追跡などが簡単に行えます。
        カスタマイズされたお役立ち情報
        地域の旅行トレンド情報をお伝えするとともに、高評価レビューを獲得し、ビジネスを成長させるための改善策を提案します。
        学びとトレーニング
        リソースセンターでアドバイスを見つけたり、無料オンラインセミナーでホスティングの基礎を学んだり、コミュニティセンターでホスト仲間とつながったりしましょう。
        ホスティングをはじめる準備はできましたか?
        ご自身のホスティングスタイルに合ったリスティングを作成しましょう。お部屋を輝かせるヒントをご紹介します。手続きは今すぐはじめていつでも中断できます。

        はじめる
        企業情報
        Airbnbのご利用方法
        ニュースルーム
        Airbnb Plus
        Airbnb Luxe
        HotelTonight
        Airbnbビジネスプログラム
        オリンピック
        採用情報
        コミュニティ
        ダイバーシティ&ビロンギング
        アクセシビリティ対応
        Airbnbアソシエイト
        お友達を招待
        ホスト
        お部屋を掲載
        オンライン体験をホストする
        体験をホストする
        責任あるホスティング
        オープンホーム
        リソースセンター
        コミュニティセンター
        サポート
        Airbnbの新型コロナウイルスに対する取り組み
        ヘルプセンター
        キャンセルオプション
        近隣コミュニティサポート
        信頼&安全
        言語を選択
        日本語 (JP)
        通貨を選択
        ¥
        JPY
        Airbnb Global Services Limited
        観光庁長官(01)第S0001号(2018年6月15日-2023年6月14日)
        © 2020 Airbnb, Inc. All rights reserved·プライバシー·利用規約·サイトマップ·企業情報

    </body>
</html>

style.css
@charset "utf-8";

a:link{
    color: #000;
    text-decoration: underline;
}

a:visited{
    color: #000;
}

h1{
   text-align: left;
}

h2{
    padding: 10px;
    background-color: lightslategray;
}

.midashi{

    background-color: white;
}

#container-1{
    display: grid;
    grid-template-columns: 1fr 1fr;
    background-color: lightblue;
}

.f-item{
    display: grid;
    text-align: left;
}

.f-item02{
    background-color: red;
}

#container-2{
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    background-color:white;
}


#container{
    grid-template-columns: 1fr 1fr;
    width: 100%;
    display: grid;
    padding: 50px;
    margin: 0px ;
    background-color: rgb(231, 203, 203);
}

#item-a{
    grid-template-rows: 100px 50px;
}

#item-b{

    margin: 0px 0px 0px 0px;
    padding: 50px 100px 50px 100px;
    border-radius: 15px;
    grid-row: 1/3;
    grid-column: 2/4 ;
    background-color: white;
}

.room{
    width: 100%;
}

.room > h1{
    color: black;
}

.inText{
    border-radius: 2px;
    color: rgba(119, 119, 119, .7);
    width: 100%;
}


.box{
     margin-left: auto;
}

.btn{
    margin: 10px 0px 10px 0px;
    padding: 10px 15px 10px 15px;

}

.btn-sm{
    margin:10px 80% 10px 10px;
}

.btn-dark{

    background-color: rgb(33, 34, 34);
    width: 100%;

}


.btn:hover{
    color: #000;
}

div{
    margin: 5% 5% 5% 5%;
    ;
}


body{
    font-family: "UD Digi Kyokasho NK-B";
    background-color: aliceblue;
}



レンタルサーバーと公開HP

練習成果を公開することを目的としてるだけなので

無料のサーバーとWordpress

を使用します。

サーバー:xrea free 現時点だと1年後に更新しないなら無料らしい 1年もあれば充分!!
Wordpress:(WordPressのリンク)

xrea freeの中でwordpressはすぐインストールできました。

2020/11/22 時刻21:24 現在

なんとかパーツごとにphp化してxreaのサーバー側にファイルアップロードして
公開できました!

ベーシック認証かけてます!
passwordは【mamonokun】です!
http://www.mamonogt.shop/nptest/

正直functions.phpとかphpの組み込み方はほぼ理解できなかったのでコピペした(^_-)-☆!!

完璧ではないけど1から公開までは結構大変だった!!!!

次は何するかわからないけどひとまず模写はこれでひと段落!

ここからはメモ書き!!

header.php
<!--CSSとBootstrapの読み込みはこれをそのままネットからコピペしたら読み込んだ-->
<link rel="stylesheet" href="<?php bloginfo('stylesheet_url'); echo '?' . filemtime( get_stylesheet_directory() . '/style.css'); ?>" type="text/css" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"   type="text/css" media="all" >
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TECH CAMP (エンジニア転職)7週目の学習内容の振り返り

みなさんおつかれさまです。今週やっとテックキャンプの最終課題が終わったので、明日から自分のポートフォリオの作成に入ろうと思います。ではでは、振り返りをやっていこうかと思います。

11/16~11/22までの学習進捗
・商品詳細表示機能の導入
・商品情報編集機能の導入
・商品削除機能の導入
・商品購入機能の導入

正直いって詳細表示、編集、削除機能に関しては、そんなに難しくなかったですが、購入機能は結構な工数が必要になり、時間は結構かかりました。いつもならそれぞれの項目でどんなことをやったのかを書いているのですが、フリマアプリの商品情報に関する機能はそこまで難しくない上、文章として残しておけるほどの情報量を思い出せないので割愛しておきます(笑)。

・購入機能の導入
まず始めに、大まかに手順が必要だったのかを箇条書きで書くと、
・テーブルの作成(購入履歴と配送先の情報をDBに記録)
・フォームオブジェクトパターンの実装
・決済機能の導入
・テストコードの実装
・購入機能実装によるビューファイルの編集
という感じですね

最初にやるテーブル作成では、2つのテーブル(購入履歴と配送先)が必要で、それらについての実装をしなければいけませんでした。これに関しては、先週に書いたER図やread.meの記述を参照しながらやったのでそこまでの時間はかからなかったですね。ただ次にやるフォームオブジェクトパターンの実装、自分の中ではこれが一番難しかったですね。

フォームオブジェクトパターンとは一つのフォームから複数テーブルへ情報に送信・保存を行うための手法で、一つのフォームから一つのテーブルに情報を保存する場合とは異なる記述をしなければいけません。やはり最大の違いは、MVCのやりとりの流れが違う点です。通常、一つのフォームから一つのテーブルに情報を保存する場合、コントローラーにストロングパラメーターを設定するモデルやDBに通す前に制限をかけるとおもうのですが、フォームオブジェクトパターンでは、取得する情報を新しく作成したモデル内に列挙し、どの情報をどのテーブルに割り振るのか振り分けを(モデル内で)行うという流れでデータベースに情報が送信・保存されます。その考え方があまり理解できなくて実装に時間をかけてしまいました。

決済機能の導入では、Pay.jpというカード決済代行サービス利用し、セキュリティ面に配慮しながら実装を進めなければいけませんでした。カード情報をそのまま送信しては第三者の介入でカード情報が抜き取られる可能性があるため、トークン化というものを行い、情報が漏洩しても悪用できない仕様にします。概念はわかるのですが、javascriptをいじる必要があるので面倒でした(苦手意識)。

テストコードの実装は今までの内容とやるべきことは変わらないものだったので時間はかかりましたが、難しくはありませんでした。

最後のビューファイルの編集は、商品の購入記録の有無で売り切れと表示したり、購入ボタンをなくしたりするものです。データの有無を判定するメソッド(nil?メソッド)を知らなかったのでこれも時間をかけてしまう作業でした。

土日のうちにカリキュラム外でやったこともいろいろあるのですが、それは一段落がついてから記事にしていこうと思います。来週は自作ポートフォリオについて進捗報告をしていこうと思います。
それでは!

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

【Nuxt.js】middleware(ミドルウェア)についてまとめ

middlewareとは

・ページレンダリングが行われる前に、独自の関数を実行できる。
・ユーザー認証が済んでいない状態で特定のページにアクセスした場合のリダイレクト処理などに使用される。

pagesから呼ぶ

midllewareディレクトリ及びpagesディレクトリ配下に以下のように作成

|--middleware
   |--page1.js
   |--page2.js
   |--page3.js
|--pages
   |--page1.vue
   |--page2.vue
   |--page3.vue

middlewareが呼ばれた際に実行させる関数をexport defaultで返す。

コンソールにmiddleware page名と表示させておく。

middoleware/page1.js
export default () => {
  console.log('middleware page1');
}

middoleware/page2.js
export default () => {
  console.log('middleware page2');
}

middoleware/page3.js
export default () => {
  console.log('middleware page3');
}


各pageではそれぞれのpage名に対応したmiddlewareを呼ぶ。

page/page1.vue
<template>
  <div>
    page1
  </div>
</template>

<script>
export default {
  middleware: [
    ['page1']
  ]
}
</script>

page/page2.vue
<template>
  <div>
    page2
  </div>
</template>

<script>
export default {
  middleware: [
    ['page2']
  ]
}
</script>

page/page3.vue
<template>
  <div>
    page3
  </div>
</template>

<script>
export default {
  middleware: [
    ['page3']
  ]
}
</script>


各pageへのリンクをlayoutsに作っておきます。

layouts/default.vue
<template>
  <div>
    <hr>
    <NuxtLink to="/page1">page1</NuxtLink>
    <NuxtLink to="/page2">page2</NuxtLink>
    <NuxtLink to="/page3">page3</NuxtLink>
    <hr>
    <Nuxt />
  </div>
</template>

結果
demo

layoutsから呼ぶ

middllewareディレクトリ配下にlayouts.jsを追加します。

middoleware/layouts.js
export default () => {
  console.log('layouts')
}
layouts/default.vue
<template>
  <div>
    <hr>
    <NuxtLink to="/page1">page1</NuxtLink>
    <NuxtLink to="/page2">page2</NuxtLink>
    <NuxtLink to="/page3">page3</NuxtLink>
    <hr>
    <Nuxt />
  </div>
</template>

<script>
export default {
  middleware: ['layouts']
}
</script>

結果

demo

layoutsから呼ぶことで全てのページにアクセスした際に共通の処理を実行できます。

nuxt.config.jsから呼ぶ

middllewareディレクトリ配下config.jsを追加します。

middoleware/config.js
export default () => {
  console.log('nuxt.config.js');
}
nuxt.config.js
export default {

//・・・省略

  router: {
    middleware: [
      'config'
    ]
  }

//・・・省略

}

結果

nuxt.config.jsから呼ぶとlayoutsから呼ぶのと同じで全てのページで共通の処理として呼ばれますが、その実行順に注意です。

スクリーンショット 2020-11-22 14.13.45.png

nuxt.config.jslayoutspagesの順で呼ばれるのがわかります。

contextオブジェクト

middlewareは第一引数にcontextオブジェクトを取ります。

contextは様々なデータを含んでいます。詳細はこちら

最後にcontextを使って簡単に認証、リダイレクト処理を作ります。

storestatestatusを用意し、
初期値をtrueとします。
mutationsの呼び出しでtruefalseを切り替える様にします。

store/index.js
export const state = () => ({
  status: true
})

export const mutations = {
  changeStatus(state){
    state.status = !state.status;
  }
}

pages/page1truefalseを切り替えるボタンを用意します。

page/page1.vue
<template>
  <div>
    page1
    <button @click="$store.commit('changeStatus')">{{$store.state.status}}</button>
  </div>
</template>

・ボタン(state.status)がfalseの時にはpage3に入れない。
・ボタン(state.status)がtrueの時にはpage2に入れない。

といった仕様にします。

middllewareディレクトリ配下auth.jsを追加します。
nuxt.config.jsから呼びます。

middoleware/auth.js
export default ({store, route, redirect }) => {
  if (store.state.status && route.path === '/page3') {
    console.log('true時はpage3へ行けません。page1に戻ります。');
    redirect('/page1');
  } else if (!store.state.status && route.path === '/page2') {
    console.log('false時はpage2へ行けません。page1に戻ります。');
    redirect('/page1');
  }
}

nuxt.config.js
//・・・省略

  router: {
    middleware: [
      'auth'
    ]
  }

//・・・省略

}

結果

demo


以上です。
ここまで見て頂きありがとうございました!

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

【Nuxt.js】middleware(ミドルウェア)について【まとめ】

middlewareとは

・ページレンダリングが行われる前に、独自の関数を実行できる。
・ユーザー認証が済んでいない状態で特定のページにアクセスした場合のリダイレクト処理などに使用される。

pagesから呼ぶ

midllewareディレクトリ及びpagesディレクトリ配下に以下のように作成

|--middleware
   |--page1.js
   |--page2.js
   |--page3.js
|--pages
   |--page1.vue
   |--page2.vue
   |--page3.vue

middlewareが呼ばれた際に実行させる関数をexport defaultで返す。

コンソールにmiddleware page名と表示させておく。

middoleware/page1.js
export default () => {
  console.log('middleware page1');
}

middoleware/page2.js
export default () => {
  console.log('middleware page2');
}

middoleware/page3.js
export default () => {
  console.log('middleware page3');
}


各pageではそれぞれのpage名に対応したmiddlewareを呼ぶ。

page/page1.vue
<template>
  <div>
    page1
  </div>
</template>

<script>
export default {
  middleware: [
    ['page1']
  ]
}
</script>

page/page2.vue
<template>
  <div>
    page2
  </div>
</template>

<script>
export default {
  middleware: [
    ['page2']
  ]
}
</script>

page/page3.vue
<template>
  <div>
    page3
  </div>
</template>

<script>
export default {
  middleware: [
    ['page3']
  ]
}
</script>


各pageへのリンクをlayoutsに作っておきます。

layouts/default.vue
<template>
  <div>
    <hr>
    <NuxtLink to="/page1">page1</NuxtLink>
    <NuxtLink to="/page2">page2</NuxtLink>
    <NuxtLink to="/page3">page3</NuxtLink>
    <hr>
    <Nuxt />
  </div>
</template>

結果
demo

layoutsから呼ぶ

middllewareディレクトリ配下にlayouts.jsを追加します。

middoleware/layouts.js
export default () => {
  console.log('layouts')
}
layouts/default.vue
<template>
  <div>
    <hr>
    <NuxtLink to="/page1">page1</NuxtLink>
    <NuxtLink to="/page2">page2</NuxtLink>
    <NuxtLink to="/page3">page3</NuxtLink>
    <hr>
    <Nuxt />
  </div>
</template>

<script>
export default {
  middleware: ['layouts']
}
</script>

結果

demo

layoutsから呼ぶことで全てのページにアクセスした際に共通の処理を実行できます。

nuxt.config.jsから呼ぶ

middllewareディレクトリ配下config.jsを追加します。

middoleware/config.js
export default () => {
  console.log('nuxt.config.js');
}
nuxt.config.js
export default {

//・・・省略

  router: {
    middleware: [
      'config'
    ]
  }

//・・・省略

}

結果

nuxt.config.jsから呼ぶとlayoutsから呼ぶのと同じで全てのページで共通の処理として呼ばれますが、その実行順に注意です。

スクリーンショット 2020-11-22 14.13.45.png

nuxt.config.jslayoutspagesの順で呼ばれるのがわかります。

contextオブジェクト

middlewareは第一引数にcontextオブジェクトを取ります。

contextは様々なデータを含んでいます。詳細はこちら

最後にcontextを使って簡単に認証、リダイレクト処理を作ります。

storestatestatusを用意し、
初期値をtrueとします。
mutationsの呼び出しでtruefalseを切り替える様にします。

store/index.js
export const state = () => ({
  status: true
})

export const mutations = {
  changeStatus(state){
    state.status = !state.status;
  }
}

pages/page1truefalseを切り替えるボタンを用意します。

page/page1.vue
<template>
  <div>
    page1
    <button @click="$store.commit('changeStatus')">{{$store.state.status}}</button>
  </div>
</template>

・ボタン(state.status)がfalseの時にはpage3に入れない。
・ボタン(state.status)がtrueの時にはpage2に入れない。

といった仕様にします。

middllewareディレクトリ配下auth.jsを追加します。
nuxt.config.jsから呼びます。

middoleware/auth.js
export default ({store, route, redirect }) => {
  if (store.state.status && route.path === '/page3') {
    console.log('true時はpage3へ行けません。page1に戻ります。');
    redirect('/page1');
  } else if (!store.state.status && route.path === '/page2') {
    console.log('false時はpage2へ行けません。page1に戻ります。');
    redirect('/page1');
  }
}

nuxt.config.js
//・・・省略

  router: {
    middleware: [
      'auth'
    ]
  }

//・・・省略

}

結果

demo


以上です。
ここまで見て頂きありがとうございました!

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

Node.js バージョン管理nodenvを使ってみる

nodeのバージョン管理についてnというものを使ってみたが、
今回CentOs環境で別のnodenvを使ってみることにしました。

バージョン管理には他にも、
nodebrewがありますが、nodenvだとディレクトリごとにバージョンが変更できるみたいです
※ ついでに、win機はNodistというのがあるみたいです

anyenv インストール

anyenv」というツールを用いてインストールします

[vagrant@vagrant ~]$ git clone https://github.com/riywo/anyenv ~/.anyenv

パスを通して設定します

[vagrant@vagrant ~]$ echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.bash_profile
[vagrant@vagrant ~]$ echo 'eval "$(anyenv init -)"' >> ~/.bash_profile
[vagrant@vagrant ~]$ exec $SHELL -l

こちらを実行すると以下のようにでました。

ANYENV_DEFINITION_ROOT(/home/vagrant/.config/anyenv/anyenv-install) doesn't exist. You can initialize it by:
> anyenv install --init

指示通り初期をおこないます

anyenv install --init

nodenv インストール

anyenvを使用し、nodenvをインストールします

anyenv install nodenv
exec $SHELL -l

以下のコマンドを実行できたら成功です

nodenv

バージョンを切り替え

欲しいnode.jsのバージョンを以下のコマンドでインストールします

nodenv install 12.19.0

切り替えます

nodenv local 12.19.0

参考URL
https://qiita.com/tonkotsuboy_com/items/5322d226b6783d25b5df

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

Hurry Coderと魔法の数字

登場人物

  • ハリー先輩・・・忙しい先輩。「ハリー・コーダー」と呼ばれている。
  • ワイ・・・・・・ワイ

とあるシステム開発会社にて

ハリー先輩「なあ、やめ太郎」

ワイ「なんでっか、ハリー先輩?」

ハリー先輩「去年お前が構築したマンガ販売サイトあるやろ?」
ハリー先輩「その修正作業を今やってるんやけど」

ワイ「あー、あの案件、ハリー先輩が引き継いでくれたんでっか」
ワイ「おおきにです」

ハリー先輩「それはええねんけど、ちょっと分からへんことがあんねん」

ワイ「なんでっか?」

ハリー先輩「あのな・・・」

const campaignMessage = (campaignStatus: number): string => {
  switch (campaignStatus) {
    case 0:
      return "もうすぐコミック半額キャンペーンが始まるよ!"

    case 1:
      return "キャンペーン開催中!対象コミックスはこちら!"

    case 2:
      return "次回キャンペーンもお楽しみにね!"

    default:
      return "エラーが発生しました。サイト管理者にお問い合わせください。"
  }
}

ハリー先輩「↑この関数についてやねん」
ハリー先輩「campaignStatusという引数の値によって」
ハリー先輩「画面に表示するメッセージを出し分けてるみたいなんやけど」

ワイ「はい」

ハリー先輩「このcampaignStatusの数値、何なん?」
ハリー先輩「キャンペーンの状態を表してるとは思うんやけど」
ハリー先輩「0とか1とか2って、何なん?」

ワイ「ああ、それなら・・・」

0 -> キャンペーン開催前
1 -> キャンペーン開催中
2 -> キャンペーン終了後

ワイ「↑こういう意味ですわ」

ハリー先輩「ああ、そうなんか」
ハリー先輩「初めてこのソースを見る人は、数値だけだとよう分からんから」
ハリー先輩「一応コメントとか書いといてや」

ワイ「何を言うてはるんですか」
ワイ「ちゃんと社内Wikiのどっかに書いてありまっせ」

ハリー先輩「どっかて」
ハリー先輩「どこがちゃんとやねん」
ハリー先輩「貴様も把握しとらんやないかい」

ワイ「ぐぬぬ」

ハリー先輩「ええか?」
ハリー先輩「こういう数値はマジックナンバーいうねん」

ワイ「マ、マジックナンバーでっか?」

マジックナンバーとは

ハリー先輩「マジックナンバーいうんはな・・・」

「ん?campaignStatus === 0・・・?」
「なんやこの0って」
「何を表しとるかよう分からんけど、プログラムはちゃんと動いとるな」
「なんか魔法の数字みたいやな」

ハリー先輩「↑ってことや」

ワイ「上手いこと言うたもんですな」
ワイ「ほな、どう書けばええんですか」

ハリー先輩「せっかくTypeScriptを導入してんねやから」
ハリー先輩「enum型でも使えばええやろ1

enum CampaignStatus {
  BeforeStart = 0,
  IsStarted = 1,
  IsEnd = 2
}

ハリー先輩「↑こうや」

ワイ「へえ」
ワイ「それぞれの数値に名前を付けてやった感じでっか」

ハリー先輩「せや」
ハリー先輩「そんで、関数の方は・・・」

const campaignMessage = (campaignStatus: CampaignStatus): string => {
  switch (campaignStatus) {
    case CampaignStatus.BeforeStart: // <- 変更
      return "もうすぐコミック半額キャンペーンが始まるよ!"

    case CampaignStatus.IsStarted: // <- 変更
      return "キャンペーン開催中!対象コミックスはこちら!"

    case CampaignStatus.IsEnd: // <- 変更
      return "次回キャンペーンもお楽しみにね!"

    default:
      return "エラーが発生しました。サイト管理者にお問い合わせください。"
  }
}

ハリー先輩「↑こうや」

ワイ「あー、確かに」
ワイ「さっきまでのコードは・・・」

0だったら」
"もうすぐコミック半額キャンペーンが始まるよ!"って表示してや!」

ワイ「↑こんな感じでしたけど」

CampaignStatus.BeforeStartだったら」
"もうすぐコミック半額キャンペーンが始まるよ!"って表示してや!」

ワイ「↑こっちの方が説明的で良いかもしれまへんな」
ワイ「初めてこのコードを読んだ人でも分かりそうですわ・・・」

ハリー先輩「せやろ」
ハリー先輩「『リーダブルコード2でいう説明変数みたいなもんや」

ワイ「なるほどー」

「この数値は、こんな意味でっせ!」

ワイ「ってことを命名で表現するわけでんな」

まとめ

  • マジックナンバーやめよう
  • 説明変数を使おう
  • enumは、名前と数値の対応表みたいに使える3

ワイ「ってことでんな!」

ハリー先輩「そんなとこや!」

ワイ「ハリー先輩、今日も勉強になりましたわ!」

〜おしまい〜


  1. 型安全性的には、readonlyなオブジェクトから生成したUnion型を使用するほうが良いかもです。 

  2. 有名な本です。 

  3. 今回の場合、数値を書かなくても同じように動きます。また、文字列も指定できます。 

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

アコーディオンメニュー(haml,scss,javascript)

はじめに

タイトルについて記事にしました。
この記事で得る内容は以下の通りです。

・ CSSの基礎知識が増える
・ アコーディオンメニューの作り方について

例えば、こんな画面があったとします

スクリーンショット 2020-11-21 13.41.00.png

手順

*説明に必要な所だけ記述しています

① アコーディオンメニューをクリックしたら、表示する場所を予め記述します
→ 今回、アコーディオンメニューが%aだとすると、.answer配下の%p fuga

index.haml
.wrapper
  .wrapper__text
    %i.far.fa-envelope-open
    %h2 よくある質問
  .wrapper__question
    %a
      %i.far.fa-question-circle
      %p hoge?
    .answer
      %p fuga

② ①で記述した要素をアコーディオンメニュークリック後に表示させたいので、display: none;で隠しておきます

index.scss
.answer {
  display: none;
}

③ アコーディオンメニューをクリックしたら.activeクラスをつけると想定して、display: block;を指定します

index.scss
.answer.active {
  display: block;
}

④ hamlにJavaScriptファイルを読み込む記述と、accordion.jsファイルを新規作成します

index.html
= javascript_pack_tag 'accordion'

⑤ accordion.jsに処理を記述します

accordion.js
const accordionMenu = document.querySelectorAll(".wrapper__question > a"); // アコーディオンメニュー発火部分をCSSのセレクタで要素を取得する変数

for (let i = 0; i < accordionMenu.length; i++) { // querySelectcorAllで要素を配列で取得して、変数"accordionMenu"の数だけ処理をfor文で繰り返す
  accordionMenu[i].addEventListener("click", function () {
    this.nextElementSibling.classList.toggle("active"); // イベントが起こっている要素の次のクラスを切り替える
  });
}

%aタグにhref属性があり、クリック後にページがリロードしてしまう場合は、以下のように記述します

accordion.js
const accordionMenu = document.querySelectorAll(".wrapper__question > a");

for (let i = 0; i < accordionMenu.length; i++) {
  accordionMenu[i].addEventListener("click", function (e) { // ファンクションに引数"e"(名前は自由)を持たせタグのイベントを取得する
    e.preventDefault(); // hrefのイベントをキャンセルする
    this.nextElementSibling.classList.toggle("active");
  });
}

アコーディオンメニューの機能自体はこれで完成していますが、右端の矢印を回転させる処理を記述します

次に矢印.gif

⑦ accordion.js及び、index.scssを以下のように記述します

accordion.js
const accordionMenu = document.querySelectorAll(".wrapper__question > a");

for (let i = 0; i < accordionMenu.length; i++) {
  accordionMenu[i].addEventListener("click", function () {
    this.classList.toggle("active"); // クリックした要素(a)自体に"active"クラスを切り替える
    this.nextElementSibling.classList.toggle("active");
  });
}
index.scss
    a.active::after {
      transform: rotate(180deg);
    }

これでアコーディオンメニューをクリックしたら、矢印がクルクル回転する様になりました

矢印がくるくる.gif

参考(今回のコード)

index.haml
.wrapper
  .wrapper__text
    %i.far.fa-envelope-open
    %h2 よくある質問
  .wrapper__question
    %a
      %i.far.fa-question-circle
      %p hoge?
    .answer
      %p fuga
= javascript_pack_tag 'accordion'
index.scss
.wrapper {
  text-align: center;
  padding: 4rem 0;
  letter-spacing: 2px;
  &__text {
    i {
      font-size: 1.5rem;
      margin-right: -5px;
      vertical-align: 3px;
    }
    h2 {
      font-size: 1.75rem;
      font-weight: bold;
      display: inline-block;
    }
  }
  &__question {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding-top: 3rem;
    a {
      font-size: 1.25rem;
      font-weight: bold;
      border: 1px solid #ddd;
      padding: 1rem 1.5rem;
      width: 70%;
      margin-bottom: 0.3rem;
      text-align: left;
      i {
        color: #016ea9;
        margin-right: -10px;
      }
      p {
        display: inline-block;
      }
    }
    a::after {
      content: "\02228";
      color: #ccc;
      float: right;
    }
    a:hover > p {
      text-decoration: underline;
    }
    .answer {
      width: 70%;
      text-align: left;
      padding: 1rem 1rem 2rem;
      display: none;
      p {
        font-weight: bold;
        color: #333;
      }
    }
    .answer.active {
      display: block;
    }
    a.active::after {
      transform: rotate(180deg);
    }
  }
}
accordion.js
const accordionMenu = document.querySelectorAll(".wrapper__question > a");

for (let i = 0; i < accordionMenu.length; i++) {
  accordionMenu[i].addEventListener("click", function () {
    this.classList.toggle("active");
    this.nextElementSibling.classList.toggle("active");
  });
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

モバイルアプリのアイコン画像用意する面倒くさくない?自動化しちゃおう!

PhotoShopでいちいち何十回も解像度を変える作業めんどくさ・・・

AndroidとiPhone合わせるとこのくらいの画像が必要になる。

b1d2c8bfdad903f18ac9aa668706984e.png

しかもiPhoneの画像は20@2xみたいな語尾を付けているからややこしい。

だからPhotoshopのスクリプトで自動化してしまおう

まず出力したいPSDのレイヤーは全てスマートオブジェクトにして下さい。←これ重要

これを忘れると出力された画像がボケるから気をつけて!
レイヤーを右クリック→スマートオブジェクトに変換
Inked4ec9e66d5b55a3bcdc164078007e697a_LI.jpg

全部出来たらこんな感じで変なマークが追加される

Inked7cc6c02a13d6c064c7f25102f02f795f_LI.jpg

スクリプトファイルダウンロード

https://drive.google.com/file/d/19dk19n3V2ciiC1BXWAHbdFAilxHzvqO_/view?usp=sharing
↑からスクリプトファイルをダウンロードしたら

PhotoShopのディレクトリを探してPhotoshop\Presets\Scripts下に配置

僕の場合は↓だった
C:\Program Files\Adobe\Adobe Photoshop 2021\Presets\Scripts

実行

念の為Photoshopでは何も開いていない状態からスタートする事をおすすめする。

メニュー→ファイル→スクリプト→MakePngsForMobileを選択

11f4fc7cd9cc70a2fbf8e2ee64994346.png

ファイル選択ダイアログが出現するので実行したいPSDを選択してOKを押す

Inkedb5816321b57c782742b12e66e15e1bdc_LI.jpg

PSDとPNGが同ディレクトリに出力されていれば成功!

47910029a03e66f30f7c9685c0912825.png

もしこのスクリプトが既存であったり、エラー、不具合等あれば報告して下さい!

色々雑でしたがqiita初投稿なので多めに見てやって下さい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

モバイルアプリのアイコン画像用意するの面倒くさくない?自動化しちゃおう!

PhotoShopでいちいち何十回も解像度を変える作業めんどくさ・・・

AndroidとiPhone合わせるとこのくらいの画像が必要になる。

b1d2c8bfdad903f18ac9aa668706984e.png

しかもiPhoneの画像は20@2xみたいな語尾を付けているからややこしい。

だからPhotoshopのスクリプトで自動化してしまおう

まず出力したいPSDのレイヤーは全てスマートオブジェクトにして下さい。←これ重要

これを忘れると出力された画像がボケるから気をつけて!
レイヤーを右クリック→スマートオブジェクトに変換
Inked4ec9e66d5b55a3bcdc164078007e697a_LI.jpg

全部出来たらこんな感じで変なマークが追加される

Inked7cc6c02a13d6c064c7f25102f02f795f_LI.jpg

スクリプトファイルダウンロード

https://drive.google.com/file/d/19dk19n3V2ciiC1BXWAHbdFAilxHzvqO_/view?usp=sharing
↑からスクリプトファイルをダウンロードしたら

PhotoShopのディレクトリを探してPhotoshop\Presets\Scripts下に配置

僕の場合は↓だった
C:\Program Files\Adobe\Adobe Photoshop 2021\Presets\Scripts

実行

念の為Photoshopでは何も開いていない状態からスタートする事をおすすめする。

メニュー→ファイル→スクリプト→MakePngsForMobileを選択

11f4fc7cd9cc70a2fbf8e2ee64994346.png

ファイル選択ダイアログが出現するので実行したいPSDを選択してOKを押す

Inkedb5816321b57c782742b12e66e15e1bdc_LI.jpg

PSDとPNGが同ディレクトリに出力されていれば成功!

47910029a03e66f30f7c9685c0912825.png

もしこのスクリプトが既存であったり、エラー、不具合等あれば報告して下さい!

色々雑でしたがqiita初投稿なので多めに見てやって下さい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScriptの基本的な型と利用例

本記事の概要

業務でTypeScriptを利用する機会が出てきそうなので勉強した備忘録として書いてます。
随時追加していきます。
私自身が駆け出しエンジニアですので、間違いなどあればご指摘いただければありがたいです。

TypeScriptとは何か

TypeScriptとはJavaScriptに型を設けたMicroSoft社が開発した言語です。
Javascriptと違って変数、配列、関数に型を設けることによって、予期せぬ値の混入を防いだりエラーにいち早く気付く事が出来たりします。

基本的な型の書き方

基本的な変数の型指定は以下のようになります。難しくないですね。

let 変数名 : 型の種類 = 値;

boolean型

boolean型はtrue/falseの2つの真偽値をとるデータ型です。
if文やwhile文など制御分の構造文などで挙動をコントロールする際に大切な型となります。
宣言した変数の後に: booleanと書きます。

boolean.ts
let isBoolean: boolean = ture;
console.log({ isBoolean });  //true

上記のコードだと、当然ですがtrueと表示されます。
また、以下のように変換して利用する事もできます。

console.log(isBoolean.toString());  //'true'/'false'になる

console.log(Number(isBoolean));  //1, 0になる

number型

TypeScriptには数値を扱う型としてnumber型bigint型があります。ただ、bigint型は利用機会が少ないようなので今回は割愛します。数値を扱う場合はとりあえずnumber型という認識でまずは良さそうです。
前置きが長くなりましたが、以下がnumber型を指定したソースコードです。

number.ts
let year: number = 2000;  
let age: number = 0x1b;

console.log(year);  //2000
console.log(age);  //27

上記のように16進数でも利用する事ができます。

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

最近朝起きると喉がカラカラになっているので、いい感じの湿度を保つ加湿器コントローラーを2時間で作る

朝起きると喉が痛い

最近、冬になって空気が乾燥してきていますね。
夜寝るときは気にならないのですが、朝起きると口から鼻から粘膜が乾燥してしまいます。

我が家にも超音波式の加湿器があるのですが、つまみで噴出量を調整するタイプのもので、

  • 量が少ないと加湿しきれず、朝喉がカラカラになり、
  • 量が多すぎると加湿は良いが、床がびちょびちょになり、

ちょうどつまみの回し具合を探し当てるのが大変です。

本記事では、加湿器の電源を自動でON/OFFさせることで、いい感じの湿度にしてくれるコントローラーを実働2時間で作成していきます。
(※材料は前もって用意しておいてください)

睡眠時の適切な湿度は50%~60%

布団の西川さんによると、寝室の湿度は50%~60%が丁度良いみたいです[1]。
今回はこの範囲を狙っていきましょう。

動作イメージ

用意する材料

  • obniz Board
  • 温湿度センサー SHT31
  • リモコンコンセント OCR-05W
  • 赤外線LED
  • 抵抗
    • 5Ωのもの
    • セットで100円くらい
    • 秋月で買えます(ページ見つからなかった)
  • 加湿器
    • 電源をつけたり消したりしても継続して動くもの
    • ホームセンターで購入
  • その他あると良いもの
    • ブレッドボード
    • オスオスのピン or ジャンパワイヤ

加湿器コントローラーを作っていく

obnizと湿度センサー&赤外線LEDを接続する

プログラムを書く

基本動作としては、湿度を取ってきて、赤外線LEDから信号を送るだけのシンプルなプログラムです。
1分間隔で動作するようにループさせています。
(追記:@il9437さんのアドバイスにより改良。ありがとうございます。)

const Obniz = require('obniz');
const { on, off } = require('./signal');

const callback = async () => {
    const obniz = new Obniz(process.env.OBNIZ_ID);
    obniz.connect(); // obnizに接続
    await obniz.connectWait();
    const sensor = obniz.wired("SHT31", { vcc: 0, sda: 1, scl: 2, adr: 3, gnd: 4, addressmode: 5 });
    const irLed = obniz.wired('InfraredLED', { anode: 6, cathode: 11 });

    const { temperature, humidity } = await sensor.getAllWait();
    console.log(new Date(), temperature, humidity);

    if (humidity < 50) { // 湿度に応じてOCR-05WをON/OFF
        irLed.send(on);
    } else if (humidity > 60) {
        irLed.send(off);
    }

    obniz.close(); // 繰り返し実行するので1回毎に切断する
};

setInterval(callback, process.env.INTERVAL || 60 * 1000); // 1分毎に実行する

ソースコードはGitHubにアップしています。
https://github.com/tmitsuoka0423/obniz-auto-humidifier

SHT31を使用して、上記の写真通りピンを接続すれば、そのまま使えるはずです。
ピンの位置を変える場合は、プログラムを修正してください。

実行する

以下のコマンドで動作します。
OBNIZ_IDは、obniz Boardの画面に表示されている4桁-4桁の数字を入れてください。

$ npx cross-env OBNIZ_ID=xxxx-xxxx node index.js

使ってみてわかったこと:ON/OFFさせる条件には幅を持たせた方が良い

最初は、

  • if 湿度 < 55% → 加湿器ON
  • else → 加湿器OFF

で制御していたのですが、湿度が55%を行ったり来たりするたびに加湿器がON/OFFされ、気になり寝られず、すぐに以下の条件にプログラムを修正しました。

  • if 湿度 < 50% → 加湿器ON
  • else if 湿度 > 60% → 加湿器OFF

こうすることで、湿度が50%未満になったら加湿器がONになり60%を超えるまでOFFになりません。(逆も然り。)
気にならないレベルになりました。使ってみるって大事ですね。

まとめ

実働2時間くらいで作りましたが、割と満足いくものができました。
寝てる間は加湿器を調整することはできないので、自動で制御させるのが良いですね。

参考文献

  1. 良い睡眠の条件 - https://www.nishikawa1566.com/company/laboratory/topics/03/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テストツールjestのインストール〜実行の流れ

jestインストール〜テスト実行まで

参照先:https://github.com/facebook/jest

①jestをインストール

npm install --save-dev jest

②基本設定ファイルの作成

1.node_modules/.binに対してinitで作成。

./node_modules/.bin/jest --init

2.基本的にEnter連打でok

The following questions will help Jest to create a suitable configuration for your project

✔ Would you like to use Jest when running "test" script in "package.json"? … yes
✔ Would you like to use Typescript for the configuration file? … no
✔ Choose the test environment that will be used for testing › node
✔ Do you want Jest to add coverage reports? … no
✔ Which provider should be used to instrument code for coverage? › v8
✔ Automatically clear mock calls and instances between every test? … no

✏️  Modified /Users/masahiro/Documents/react-router-test/project/package.json

📝  Configuration file created at /Users/masahiro/Documents/react-router-test/project/jest.config.js

③babelのインストール

1.import,exportをテスト環境で使用する場合、babelのインストールが必要。
下記コマンドでインストールする

npm install --save-dev babel-jest @babel/core @babel/preset-env

2.{}package.jsonの"devDependencies"に下記が追加されていればok

 "devDependencies": {
    "@babel/core": "^7.12.7",
    "@babel/preset-env": "^7.12.7",
    "babel-jest": "^26.6.3",
    "jest": "^26.6.3"
  }
}

④基本ファイルの作成

1.babel.config.jsファイルを作成し、下記のコードを追加

module.exports = {
  presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
};

⑤testフォルダの作成

1.__test__フォルダの作成し、適当なコードを入力

import QuizFetcher from '../../../src/data_fetchers/QuizFetcher';

describe('QuizFetcherのテスト', () => {
  it('クラスチェック', () => {
    console.log('@@@@@@');
  });
});

⑥テスト実行

1.npm testを実行

npm test

2.下記の表示が出ればok

 PASS  __tests__/src/data_fetchers/QuizFetcher.js
  QuizFetcherのテスト
    ✓ クラスチェック (11 ms)

  console.log
    @@@@@@

      at Object.<anonymous> (__tests__/src/data_fetchers/QuizFetcher.js:5:13)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.775 s, estimated 1 s
Ran all test suites.

以上になります。

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

JavaScriptで勉強時間を伸ばすボタン作ってみた!

休憩したくなったら押すだけです!

永遠と「休憩」がでなければ、ひたすら勉強できますよ笑

Screen Recording 2020-11-22 at 03.46 PM.gif

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>GAME</title>
    <link rel="stylesheet" href="css/styles.css">
  </head>
  <body>
    <h1>押してね!</h1>
    <div id="btn">?</div>
    <script src="js/main.js"></script>
  </body>
</html>
styles.css
h1 {
  text-align: center;
}

body {
  background: #efefef;
}

#btn {
  width: 200px;
  height: 200px;
  background: #ef454a;
  border-radius: 30%;
  margin: 30px auto;
  text-align: center;
  line-height: 200px;
  color: white;
  font-weight: bold;
  font-size: 42px;
  cursor: pointer;
  box-shadow: 0 10px 0 #d1483e;
  user-select: none;
}

#btn:hover {
  opacity: 0.9;
}

#btn:active {
  box-shadow: 0 5px 0 #d1483e;
  margin-top: 40px;
}
main.js
'use strict';

{
  const btn = document.getElementById('btn');
 
  btn.addEventListener('click', () => {
    const n = Math.floor(Math.random() * 3);

    switch (n) {
      case 0:
        btn.textContent = '休憩';
        break;
      case 1:
        btn.textContent = 'もう10分';
        break;
      case 2:
        btn.textContent = 'もう15分';
        break;
    }
  })
}

JavaScriptほんと楽しい(^^)

参考

JavaScriptでおみくじを作ろう

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

誰が使うかわからないけど、膝のレントゲン写真を送ったら、その膝がどの程度膝が痛んでいるか教えてくれるラインbotを作ってみた。

はじめに

突然ですが、最近、膝痛くないですか?
階段降りる時や立ち上がる時など、特に痛くないですか??
気づいたら、足がO脚になっていないですか???

もしかしたらそれ、変形性膝関節症かもしれません。

*「変形性膝関節症って何?」という方は、僕が書いたこちらの記事をぜひお読みください。
変形性膝関節症とは:その治療法・進行予防について

自分や家族の膝のレントゲン写真を持っている人がどれほどいるのか甚だ疑問ですが、膝の痛みに悩んでいる人の一助になればと作成しました。

注意)このボットはあくまで参考程度に作成したもので、正確な診断ツールではありません! 
   最終的な診断については、おかかりいただいた先生にお伺いください。

開発環境の下準備

1) VScodeのインストール
VScodeのインストールついては、googleなどで他の記事を検索してください。
2) node.jsとnpmのインストール
Macでの環境作りは、こちらの別の記事にまとめてあります。
参考にしてください。
Macにnode.js,npmのインストール方法

開発環境の準備

1) kneepainというフォルダを作成
2) VSCodeで上記フォルダを開き、フォルダ内にkneeOA.jsを作成
3) VSCode内でターミナルを開き、フォルダをnpm管理できるように初期化

terminalコマンド
$ npm init -y

4) フォルダ内にラインボット用のパッケージをnpmでインストール

terminalコマンド
$ npm i @line/bot-sdk express

システムの概要

スクリーンショット 2020-11-22 11.30.45.png

AIメーカーによる画像認識AIの作成

こちらの記事を参考に、画像認識AIを作成しました。
画像認識AIを使ったLINE BOTの作り方

kneeOA.jsのコード

kneeOA.js
'use strict';

const lineAccessToken = '作成したBOTのチャネルアクセストークン';
const lineSecret = '作成したBOTのチャネルシークレット';
const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;
const config = {
    channelSecret: lineSecret,
    channelAccessToken: lineAccessToken
};
const userID = '作成したBOTのチャネルユーザーID';

// AIメーカーで作成したAIの定数
const aimakerClient = require('request');
const aimakerModelId = AIメーカーで作成したAIモデルのIDを入力;
const aimakerApiKey = 'AIメーカーで作成したAIモデルのAPIキーを入力';
// 返信用定数
const normalrtAdvice = '正常の右膝です。';
const normalltAdvice = '正常の左膝です。';
const kl1rtAdvice = '関節軟骨が痛んでいる可能性のある右膝です。';
const kl1ltAdvice = '関節軟骨が痛んでいる可能性のある左膝です。';
const kl2rtAdvice = '関節裂隙が軽度狭小化した右膝です。';
const kl2ltAdvice = '関節裂隙が軽度狭小化した左膝です。';
const kl3rtAdvice = '関節裂隙が高度狭小化した右膝です。';
const kl3ltAdvice = '関節裂隙が高度狭小化した左膝です。';
const kl4rtAdvice = '関節裂隙が無くなってしまった右膝です。';
const kl4ltAdvice = '関節裂隙が無くなってしまった左膝です。';
const OAImportant = '変形性膝関節症の進行には、膝関節周囲の筋力の衰えることによる、膝関節の不安定性が影響を与えます。';
const OAImportant2 = '膝関節の安定には四頭筋の筋力upが重要です。適切な運動を心がけましょう。';

const app = express();
app.post('/webhook', line.middleware(config), (req, res) =>
{
    Promise
        .all(req.body.events.map(handleEvent))
        .then((result) => res.json(result));
});

const client = new line.Client(config);

function handleEvent(event)
{
    if (event.type !== 'message' || event.message.type !== 'image')
    {
        return Promise.resolve(null);
    };

    const getImageOptions = {
        url: `https://api.line.me/v2/bot/message/${event.message.id}/content`,
        method: 'get',
        headers: {
            'Authorization': 'Bearer ' + lineAccessToken,
        },
        encoding: null
    };

    let resultMessage = '';
    resultMessage = '解析中です。';
    aimakerClient(getImageOptions, function (error, response, body)
    {
        if (!error && response.statusCode == 200)
        {
            console.log('成功');
            //console.log(body);
            const buffer = new Buffer.from(body);
            const base64String = buffer.toString('base64');
            imageRecognition(base64String, event.source.userId);
        } else
        {
            console.log(error);
            resultMessage = restartMessage;
        }
    });

    return client.replyMessage(event.replyToken, [{
        type: 'text',
        text: resultMessage
    }, {
        type: 'text',
        text: OAImportant
    }]);
};

function imageRecognition(base64, userId)
{ // Aiメーカー関数
    let message = '';
    aimakerClient.post({ // AiメーカーAPI接続
        uri: "https://aimaker.io/image/classification/api",
        headers: {
            "Content-type": "application/x-www-form-urlencoded",
        },
        timeout: 20000,
        form: {
            id: aimakerModelId,
            apikey: aimakerApiKey,
            base64: base64
        }
    }, function (error, response, body)
    {
        if (error)
        {
            console.log('imegerecエラー');
            console.log(error);
            message = restartMessage;
        } else
        {
            console.log(body);
            var imageScores = JSON.parse(body);
            var labels = imageScores.labels.sort(function (a, b)
            {
                if (a.score > b.score) return -1;
                if (a.score < b.score) return 1;
                return 0;
            });
            console.log(imageScores.labels);
            if (labels[0].label && labels[0].score)
            {
                switch (labels[0].label)
                {
                    case 'Normal:Rt':
                        message = 'この膝は「' + labels[0].label + '」です。' + normalrtAdvice;
                        break;
                    case 'Normal:Lt':
                        message = 'この膝は「' + labels[0].label + '」です。' + normalltAdvice;
                        break;
                    case 'KL1:Rt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl1rtAdvice;
                        break;
                    case 'KL1:Lt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl1ltAdvice;
                        break;
                    case 'KL2:Rt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl2rtAdvice;
                        break;
                    case 'KL2:Lt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl2ltAdvice;
                        break;
                    case 'KL3:Rt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl3rtAdvice;
                        break;
                    case 'KL3:Lt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl3ltAdvice;
                        break;
                    case 'KL4:Rt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl4rtAdvice;
                        break;
                    case 'KL4:Lt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl4ltAdvice;
                        break;
                    default:
                        message = 'もう一度、画像を送ってください。';
                        break;
                }
            }
            console.log('アゲイン');
            client.pushMessage(userId, [{
                type: 'text',
                text: message
            }, {
                type: 'text',
                text: OAImportant2
            }]).then();
        };
    });
};

app.listen(PORT);
console.log(`Server running at ${PORT}`);

Herokuへのデプロイ

node.jsが動作する無料のクラウドサーバーであるHerokuにデプロイします。

下準備

1) Heroku CLIのインストール
https://devcenter.heroku.com/articles/heroku-cli

2) Herokuアカウントの作成
https://signup.heroku.com/

デプロイ

ターミナルで下記コマンドを実行します。

terminalコマンド
$ heroku login

実行するとブラウザが自動的に起動するので、ログインします。
すると、ターミナル上でもログインが完了します。

その後、package.jsonの"scripts"の中に、"start"を下記のように追記します。

package.json
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node kneeOA.js"
  },

Heroku内にデプロイ先のディレクトリを作成します。
下記の順でターミナルにコマンドを入力します。

terminalコマンド
$ git init
terminalコマンド
$ heroku create

Heroku内にデプロイ先のディレクトリが作成されると、ターミナルに下記のように表示されます。

terminal
Creating app... done, ⬢ デプロイ先の名前
https://デプロイ先の名前.com/ | https://git.heroku.com/デプロイ先の名前.git

このデプロイ先ディレクトリに、作成したディレクトリを入れていきます。
下記の順でターミナルにコマンドを入力します。

terminalコマンド
$ git add .
terminalコマンド
$ git commit -m 'init'
terminalコマンド
$ git push heroku master

最終的に下記のように表示されれば完了です。

terminal
remote:        https://デプロイ先の名前.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/デプロイ先の名前.git
 * [new branch]      master -> master

これでデプロイ完了です。

ファイルを更新する時

まず、更新するファイルが入ったディレクトリがherokuのデプロイ先のディレクトリと紐づいているかを確認します。

terminalコマンド
$ git remote -v

紐づいている場合

紐づいている場合は、ターミナルに下記のように表記されます。

terminal
heroku https://git.heroku.com/heroku上のデプロイ先の名前 (fetch)
heroku https://git.heroku.com/heroku上のデプロイ先の名前 (push)

紐づいていない場合

紐づいていない場合は、ターミナルに下記のように表記されます。

terminal
fatal: not a git repository (or any of the parent directories): .git

その場合は、ターミナルに下記の順に入力し、空のディレクトリを作成し、heroku上のデプロイしたいディレクトリと紐付けを行います。

terminalコマンド
$ git init //空のディレクトリを作成
terminalコマンド
$ heroku git:remote -a heroku上のデプロイしたいディレクトリ名 
  //herokuのデプロイ先と先ほど作成した空のtディレクトリを紐付け

その上で、下記の順にコマンドを入力し、ファイルを更新します。

terminalコマンド
$ git add .
terminalコマンド
$ git commit -m 'init'
terminalコマンド
$ git push heroku master

LINE Developersからbot作成

1) LINE Developersにアクセスし、LINEアカウントでログイン
2) プロバイダー作成
3) 新規チャンネルの作成
4) チャネルアクセストークンとチャネルシークレットの取得
1)〜4)までは下記を参考に進みました。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest
5) webhookの設定
Herokuから取得したデプロイ先のURLを入力。
/webhookをつけることを忘れずに。
スクリーンショット 2020-11-22 12.20.39.png

完成したラインbot

完成したラインbotのQRコードがこちらです。
スクリーンショット 2020-11-22 14.48.54.png

ちなみに動作はこんな感じになります。


繰り返しになりますが、こちらは診断ツールではありませんので、ご注意ください!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

誰が使うかわからないけど、膝のレントゲン写真を送ったら、その膝がどの程度痛んでいるのか教えてくれるラインbotを作ってみた。

はじめに

突然ですが、最近、膝痛くないですか?
階段降りる時や立ち上がる時など、特に痛くないですか??
気づいたら、足がO脚になっていないですか???

もしかしたらそれ、変形性膝関節症かもしれません。

*「変形性膝関節症って何?」という方は、僕が書いたこちらの記事をぜひお読みください。
変形性膝関節症とは:その治療法・進行予防について

自分や家族の膝のレントゲン写真を持っている人がどれほどいるのか甚だ疑問ですが、膝の痛みに悩んでいる人の一助になればと作成しました。

注意)このボットはあくまで参考程度に作成したもので、正確な診断ツールではありません! 
   最終的な診断については、おかかりいただいた先生にお伺いください。

開発環境の下準備

1) VScodeのインストール
VScodeのインストールついては、googleなどで他の記事を検索してください。
2) node.jsとnpmのインストール
Macでの環境作りは、こちらの別の記事にまとめてあります。
参考にしてください。
Macにnode.js,npmのインストール方法

開発環境の準備

1) kneepainというフォルダを作成
2) VSCodeで上記フォルダを開き、フォルダ内にkneeOA.jsを作成
3) VSCode内でターミナルを開き、フォルダをnpm管理できるように初期化

terminalコマンド
$ npm init -y

4) フォルダ内にラインボット用のパッケージをnpmでインストール

terminalコマンド
$ npm i @line/bot-sdk express

システムの概要

スクリーンショット 2020-11-22 11.30.45.png

AIメーカーによる画像認識AIの作成

こちらの記事を参考に、画像認識AIを作成しました。
画像認識AIを使ったLINE BOTの作り方

kneeOA.jsのコード

kneeOA.js
'use strict';

const lineAccessToken = '作成したBOTのチャネルアクセストークン';
const lineSecret = '作成したBOTのチャネルシークレット';
const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;
const config = {
    channelSecret: lineSecret,
    channelAccessToken: lineAccessToken
};
const userID = '作成したBOTのチャネルユーザーID';

// AIメーカーで作成したAIの定数
const aimakerClient = require('request');
const aimakerModelId = AIメーカーで作成したAIモデルのIDを入力;
const aimakerApiKey = 'AIメーカーで作成したAIモデルのAPIキーを入力';
// 返信用定数
const normalrtAdvice = '正常の右膝です。';
const normalltAdvice = '正常の左膝です。';
const kl1rtAdvice = '関節軟骨が痛んでいる可能性のある右膝です。';
const kl1ltAdvice = '関節軟骨が痛んでいる可能性のある左膝です。';
const kl2rtAdvice = '関節裂隙が軽度狭小化した右膝です。';
const kl2ltAdvice = '関節裂隙が軽度狭小化した左膝です。';
const kl3rtAdvice = '関節裂隙が高度狭小化した右膝です。';
const kl3ltAdvice = '関節裂隙が高度狭小化した左膝です。';
const kl4rtAdvice = '関節裂隙が無くなってしまった右膝です。';
const kl4ltAdvice = '関節裂隙が無くなってしまった左膝です。';
const OAImportant = '変形性膝関節症の進行には、膝関節周囲の筋力の衰えることによる、膝関節の不安定性が影響を与えます。';
const OAImportant2 = '膝関節の安定には四頭筋の筋力upが重要です。適切な運動を心がけましょう。';

const app = express();
app.post('/webhook', line.middleware(config), (req, res) =>
{
    Promise
        .all(req.body.events.map(handleEvent))
        .then((result) => res.json(result));
});

const client = new line.Client(config);

function handleEvent(event)
{
    if (event.type !== 'message' || event.message.type !== 'image')
    {
        return Promise.resolve(null);
    };

    const getImageOptions = {
        url: `https://api.line.me/v2/bot/message/${event.message.id}/content`,
        method: 'get',
        headers: {
            'Authorization': 'Bearer ' + lineAccessToken,
        },
        encoding: null
    };

    let resultMessage = '';
    resultMessage = '解析中です。';
    aimakerClient(getImageOptions, function (error, response, body)
    {
        if (!error && response.statusCode == 200)
        {
            console.log('成功');
            //console.log(body);
            const buffer = new Buffer.from(body);
            const base64String = buffer.toString('base64');
            imageRecognition(base64String, event.source.userId);
        } else
        {
            console.log(error);
            resultMessage = restartMessage;
        }
    });

    return client.replyMessage(event.replyToken, [{
        type: 'text',
        text: resultMessage
    }, {
        type: 'text',
        text: OAImportant
    }]);
};

function imageRecognition(base64, userId)
{ // Aiメーカー関数
    let message = '';
    aimakerClient.post({ // AiメーカーAPI接続
        uri: "https://aimaker.io/image/classification/api",
        headers: {
            "Content-type": "application/x-www-form-urlencoded",
        },
        timeout: 20000,
        form: {
            id: aimakerModelId,
            apikey: aimakerApiKey,
            base64: base64
        }
    }, function (error, response, body)
    {
        if (error)
        {
            console.log('imegerecエラー');
            console.log(error);
            message = restartMessage;
        } else
        {
            console.log(body);
            var imageScores = JSON.parse(body);
            var labels = imageScores.labels.sort(function (a, b)
            {
                if (a.score > b.score) return -1;
                if (a.score < b.score) return 1;
                return 0;
            });
            console.log(imageScores.labels);
            if (labels[0].label && labels[0].score)
            {
                switch (labels[0].label)
                {
                    case 'Normal:Rt':
                        message = 'この膝は「' + labels[0].label + '」です。' + normalrtAdvice;
                        break;
                    case 'Normal:Lt':
                        message = 'この膝は「' + labels[0].label + '」です。' + normalltAdvice;
                        break;
                    case 'KL1:Rt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl1rtAdvice;
                        break;
                    case 'KL1:Lt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl1ltAdvice;
                        break;
                    case 'KL2:Rt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl2rtAdvice;
                        break;
                    case 'KL2:Lt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl2ltAdvice;
                        break;
                    case 'KL3:Rt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl3rtAdvice;
                        break;
                    case 'KL3:Lt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl3ltAdvice;
                        break;
                    case 'KL4:Rt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl4rtAdvice;
                        break;
                    case 'KL4:Lt':
                        message = 'この膝は「' + labels[0].label + '」です。' + kl4ltAdvice;
                        break;
                    default:
                        message = 'もう一度、画像を送ってください。';
                        break;
                }
            }
            console.log('アゲイン');
            client.pushMessage(userId, [{
                type: 'text',
                text: message
            }, {
                type: 'text',
                text: OAImportant2
            }]).then();
        };
    });
};

app.listen(PORT);
console.log(`Server running at ${PORT}`);

Herokuへのデプロイ

node.jsが動作する無料のクラウドサーバーであるHerokuにデプロイします。

下準備

1) Heroku CLIのインストール
https://devcenter.heroku.com/articles/heroku-cli

2) Herokuアカウントの作成
https://signup.heroku.com/

デプロイ

ターミナルで下記コマンドを実行します。

terminalコマンド
$ heroku login

実行するとブラウザが自動的に起動するので、ログインします。
すると、ターミナル上でもログインが完了します。

その後、package.jsonの"scripts"の中に、"start"を下記のように追記します。

package.json
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node kneeOA.js"
  },

Heroku内にデプロイ先のディレクトリを作成します。
下記の順でターミナルにコマンドを入力します。

terminalコマンド
$ git init
terminalコマンド
$ heroku create

Heroku内にデプロイ先のディレクトリが作成されると、ターミナルに下記のように表示されます。

terminal
Creating app... done, ⬢ デプロイ先の名前
https://デプロイ先の名前.com/ | https://git.heroku.com/デプロイ先の名前.git

このデプロイ先ディレクトリに、作成したディレクトリを入れていきます。
下記の順でターミナルにコマンドを入力します。

terminalコマンド
$ git add .
terminalコマンド
$ git commit -m 'init'
terminalコマンド
$ git push heroku master

最終的に下記のように表示されれば完了です。

terminal
remote:        https://デプロイ先の名前.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/デプロイ先の名前.git
 * [new branch]      master -> master

これでデプロイ完了です。

ファイルを更新する時

まず、更新するファイルが入ったディレクトリがherokuのデプロイ先のディレクトリと紐づいているかを確認します。

terminalコマンド
$ git remote -v

紐づいている場合

紐づいている場合は、ターミナルに下記のように表記されます。

terminal
heroku https://git.heroku.com/heroku上のデプロイ先の名前 (fetch)
heroku https://git.heroku.com/heroku上のデプロイ先の名前 (push)

紐づいていない場合

紐づいていない場合は、ターミナルに下記のように表記されます。

terminal
fatal: not a git repository (or any of the parent directories): .git

その場合は、ターミナルに下記の順に入力し、空のディレクトリを作成し、heroku上のデプロイしたいディレクトリと紐付けを行います。

terminalコマンド
$ git init //空のディレクトリを作成
terminalコマンド
$ heroku git:remote -a heroku上のデプロイしたいディレクトリ名 
  //herokuのデプロイ先と先ほど作成した空のtディレクトリを紐付け

その上で、下記の順にコマンドを入力し、ファイルを更新します。

terminalコマンド
$ git add .
terminalコマンド
$ git commit -m 'init'
terminalコマンド
$ git push heroku master

LINE Developersからbot作成

1) LINE Developersにアクセスし、LINEアカウントでログイン
2) プロバイダー作成
3) 新規チャンネルの作成
4) チャネルアクセストークンとチャネルシークレットの取得
1)〜4)までは下記を参考に進みました。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest
5) webhookの設定
Herokuから取得したデプロイ先のURLを入力。
/webhookをつけることを忘れずに。
スクリーンショット 2020-11-22 12.20.39.png

完成したラインbot

完成したラインbotのQRコードがこちらです。
スクリーンショット 2020-11-22 14.48.54.png

ちなみに動作はこんな感じになります。


繰り返しになりますが、こちらは診断ツールではありませんので、ご注意ください!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GitHub】リポジトリをクローン後に行うコマンド

前提・状況

  • Ruby on Rails 6
  • GitHubを使ってクローンした時
  • 他の人がマイグレーションファイルを修正した時
  • 他の人がGemのインストールを行った時

コマンド

ローカルリポジトリで作業する際、VSコードに書かれている文字そのものは、クローンした時やプルした時に反映されるが、それを取り巻くデータベースや、Gemfile・JavaScriptの依存関係は、コマンド実行しないとエラー出ることを学んだので覚書。

(1)自分のデータベースへ取り込み
terminal
% rails db:create
(2)マイグレーションファイルを元に、テーブル作成
terminal
% rails db:migrate
(3)Gemfile.lockに書かれた内容をインストール
  • 自身が作っているRailsに入れたGemのバージョンとクローンのバージョンを合わせている。
  • bundle install後にエラーが出る場合は、Gemfile.lockファイルを削除してから再度実行。
terminal
% bundle install
(4)yarn.lockに書かれた内容をインストール
  • 自身が作っているyarnファイルとクローンしたバージョンを合わせている。

  • jsファイルとcssファイルなどを依存関係を考慮した関係でyarn.lockに書かれている内容をインストールしている。

  • yarn install後にエラーが出る場合には、yarn upgradeを行い、最新のバージョンに更新する。

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

(JSメモ)IE用にXmlHttpRequestを用いた、async/await・Promise未使用の順序保証複数GET/POST用関数

作るに至った経緯は以下の記事に纏めています。

IEなどにおけるXMLHttpRequestで、複数リクエストをawaitするPromise.all的な処理を行いたい。

本体

function REQUEST_ALL(c, p) {
    var r = p.map(function(u) {
        var x = new XMLHttpRequest;
        x.addEventListener('loadend', function() {
            r.length == 1 + (p++) && c(r.map(function(v) {
                return {
                    url: v.responseURL,
                    status: v.status,
                    data: {
                        text: v.responseText || null,
                        json: (function(t) {
                            try {
                                return JSON.parse(t)
                            } catch (z) {
                                return null
                            }
                        }
                        )(v.responseText)
                    }
                }
            }))
        }),
        x.open(u.type, u.path, true, u.user, u.password),
        x.send(u.type !== 'GET' ? (function(y) {
            var f = new FormData;
            Object.keys(y).forEach(function(k) {
                Array.isArray(y[k]) ? y[k].forEach(function(v) {
                    f.append(k + '[]', String(v))
                }) : f.append(k, String(y[k]))
            });
            return f
        }
        )(u.param || {}) : null);
        return x
    })
      , p = 0
}

縮小版

function REQUEST_ALL(c,p){var r=p.map(function(u){var x=new XMLHttpRequest;x.addEventListener('loadend',function(){r.length==1+(p++)&&c(r.map(function(v){return{url:v.responseURL,status:v.status,data:{text:v.responseText||null,json:(function(t){try{return JSON.parse(t)}catch(z){return null}})(v.responseText)}}}))}),x.open(u.type,u.path,!0,u.user,u.password),x.send(u.type!=='GET'?(function(y){var f=new FormData;Object.keys(y).forEach(function(k){Array.isArray(y[k])?y[k].forEach(function(v){f.append(k+'[]',String(v))}):f.append(k,String(y[k]))});return f})(u.param||{}):null);return x}),p=0}

使い方

第一引数がコールバック関数、第二引数がリクエスト情報の配列です。

REQUEST_ALL(function(a) {
    console.log(a);
}, [
    {type: 'GET', path: 'a.json'},
    {type: 'GET', path: 'b.json', user: 'guest_user', password: 'guest_credential'},
    {type: 'POST', path: 'x.php', param: {a: 1, b: [1, 2, 3]}}
])

なお、コールバック関数の返り値は以下のようになっています。

[{...}, {...}, {...}]

↓↓↓

[
    {
        data: {
            text: "<responseText>",
            json: {...}// responseTextがJSONでパースできないならnull
        },
        status: 200,//400系などの時も
        url: "http://<...>/a.json"
    },
    {...},
    {...}
]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScrpitにおけるAND条件とOR条件について

JavaScriptにおけるfalsyな値

falsyな値とは、Booleanで変換した際にfalseが返る値

  • false
  • null
  • 0
  • 0n(bigint)
  • ""
  • undefined
  • NaN
ex.js
let a = 0;
console.log(Boolean(a));

//false

JavaScriptにおけるtruthyな値

truthyな値とは、Booleanで変換した際にtrueが返る値

  • falsy以外

変数に値が設定するか確認する場合。

if (a) {
  console.log("set")
  }

この場合、変数aがnullや""の場合、setが出力されないため判定できる。
ただし、0 が格納されていた場合は、falseと判定されるので、setは出力されない。

AND条件の動き

左の式から判定していき、falsyな値が出現した場合、返す。(以後の式は実行されない)
全てtruthyな場合は最後の式を返す。

const a = 1;
const b = 2;
const c = 0;

console.log(a === 1 && b === 3);
//a === 1 => true
//b === 0 => false
//出力:false
console.log(a === 1 && b === 2);
//a === 1 => true
//b === 2 => true
//出力:true
console.log(a && b);
//a  => true
//b  => true
//出力:2
console.log(a && c && b);
//a  => true
//c  => falsyな値
//出力:0

OR条件の動き

AND条件と逆。
左の式から判定していき、truthyな値が出現した場合、返す(以後の式は実行されない)。
全てfalsyな場合は最後の式を返す。

const a = 1;
const b = 2;
const c = 0;

console.log(a === 1 || b === 3);
//a === 1 => true
//出力:true
console.log(a === 5 || b === 5);
//a === 1 => false
//b === 2 => false
//出力:false
console.log(a && b);
//a  => truthy
//出力:1
console.log(c && a && b);
//c  => falsy
//a  => truthy
//出力:1

ちなみに

let a = "a";

a && console.log("b");

//bが出力される。

let a = 0;

a && console.log("b");

//bは出力されない。(aがfalsyであるため、console.logは実行されない。)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スクール3週目 アプリ作成

1〜2週目で基礎カリキュラムを終え、3週目からは応用カリキュラムへと入りました。
新型コロナの影響で自粛ムードが続きますが、コロナの不安も忘れるぐらい集中して勉強できているのは受験生以来だなとつくづく思いました。

社会人になってからも勉強は大切なことを身にしみました。プログラミングを短期集中で学ぶことで理解度を高められると思うので、この良い学習習慣を継続させていきたいと思います。

【学習内容】
・質問フォーマットの活用
・検索力、読みやすいコード
・JavaScript、jQuery基礎
・Git、GitHub
・SQL

質問フォーマットの活用 :camera_with_flash:
まず応用カリキュラムに入るにあたって、メンターへの質問方法が変わりました。
これまではわからないことがあればエラー内容をそのまま教えてもらったり、ミスしている部分を修正してもらえたのですが、これからは検索して調べ、そこから仮説を立てて実践、それでもわからなければメンターへその内容を踏まえ質問をする、という形式です。

個人的には、これまでもそのように質問していましたが3週目に入りメンターへの質問の質が高まってきたと実感しています。

検索力、読みやすいコード :computer:
エンジニアに必要な検索力を身につけよう、ということで検索の際のキーワード選びや目的の情報が載っているサイトの選び方、あとは期間指定で新しい記事を探す方法を学びました。この能力はどれだけ数をこなしていくかが大事だと思います。今の時代は知識力も大切ですが検索力も必須だなと思います。

あとは読みやすいコード・良いコードを書く、という意識をつけることを学びました。
命名規則、レイアウト、コメント、リファクタリング。やはりアプリケーションは自分だけで作るわけでなく共同開発をするもので、さらに後で見返してもすぐに理解が出来るように、という観点からも大事な部分だと思いました。このあたりは、リーダブルコードを読んで理解を深めていこうと思います。

JavaScript、jQuery基礎 :pencil2:
Webアプリケーションを作る上で必要不可欠な言語。基礎的な部分のみですが難しく感じました。また、これまでの学習内容よりもさらっと流れていく印象でした。これは実際に実装していく中で覚えていこうと思います。jQueryはグーグルマップで使用されていることを知りました。画面が動くということは裏でこのような技術が使われているんだ!と思うと知的好奇心で学習意欲をより一層、高まりました。

Git、GitHub :scissors:
共同開発していく上で必要なツールで、これからは課題を作成しながらメンターへ都度GitHubを用いて確認してもらいます。データの動きがイメージ出来るようになるまでは扱いが難しい。こればかりは数をこなしていくうちに頭の中で流れを理解していく必要があるなと思いました。また、初めてLGTMをもらった時の感動は忘れられないぐらい嬉しかったです。

SQL :bulb:
基礎カリキュラムでもRailsでアプリを作る際はMySQLを使用していました。ではそのSQLとは?データベースとは?からその構造の操作を学習しました。多少Excelに近い印象はあったので、入りやすかったですね。レコードは行、カラムは列、まずは一つずつ覚えていこうと思います。

振り返り・感想 :triangular_flag_on_post:
3週目はこれまで以上にインプットする量が多く、一つ覚えたと思ったら一つ忘れてしまうことの連続でした。それは、つまり理解したつもりになっている落とし穴にハマっているとも言えます。解決策は、何でもかんでもメモするのではなく、要点だけを抑え、覚えているうちにアウトプットをすることだと思います。アウトプットをすることで同期の人からフィードバックをもらえますし、理解しきれていないところをお互いに聞きあうこともできます。

はじめの頃は一時間おきに、なぜアウトプットをする必要があるのか不明でしたが、3週目に入り早く誰かにアウトプットしたいと思えるまでになりました。引き続き、良い習慣は取り入れ継続させていければと思います。

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

Next.jsでログインフラグによるリダイレクト機能を実装してみた

前書き

Next.jsで自前のログイン機能を作成しようとして情報集めに苦労したのでその備忘録。

前提として、認証処理一式を実装できるライブラリは色々あります。但しSNSとの連携を前提としたモノしかなかった。(僕調べ)

  • 個人学習向けの簡単なログイン機能を実装したい
  • 自前の認証処理を実装したい

みたいな方々には、もし良ければこんな実装もあるよっていう参考にしていただければ幸いです。

使用技術

  • Next.js(メイン)
    • axios:お馴染み、API叩く用に使用
    • nookies:Cookie制御用の外部ライブラリ
  • バックエンドAPI(なんらかのログイン成功フラグが返せればなんでも良い、自分はSpring Bootを使いました)

機能要件

大要件

  • ログイン済みか否かで遷移させる画面を制御する

小要件

  • ログイン済みフラグの取得のために、ログイン画面からIDとパスワードを入力してログイン用バックエンドAPIを叩く(固定値設定して等価判定しているだけの簡単なモノ)
  • ログイン済みフラグをクライアント側で保持(Cookieへ)
  • Cookieにログイン済みフラグがなければ、遷移時にログイン画面へリダイレクトさせる

実装

ポイント

  • _app.tsx(Jsなら_app.jsx) で画面制御処理を実装→全画面のコンポーネントで呼ばれるため

コード

  • ログイン画面コンポーネント
import Head from 'next/head';
import axios from '@/common/module/customAxios';
import { useState } from 'react';
import { Container, Button, Form, Image } from 'react-bootstrap';
import { setCookie } from 'nookies';
import { useRouter } from 'next/router';

interface ILogin {
  userName: string;
  password: string;
}

const initialPayload: ILogin = {
  userName: '',
  password: '',
};

const Login = () => {
  const router = useRouter();

  const [payload, setPayload] = useState<ILogin>(initialPayload);

  const handleChange = (e) => {
    setPayload({
      ...payload,
      [e.target.name]: e.target.value,
    });
  };

  const onClickLogin = () => {
    axios
      .post('/api/login', payload)
      .then((res) => {
        // ログインフラグをクッキーへ、「auth」というキーで登録
        setCookie(null, 'auth', 'true', {
          maxAge: 30 * 24 * 60 * 60,
          path: '/',
        });
        router.push('/');
      })
      .catch((e) => {
        console.log('認証エラー');
      });
  };
  return (
    <Container>
      <Head>
        <title>ログイン画面例</title>
      </Head>
      <div className='login-container'>
        <Image
          src='https://placehold.jp/150x150.png'
          roundedCircle
          style={{ marginBottom: '20px' }}
        />
        <Form.Control
          type='text'
          placeholder='User Name'
          name='userName'
          value={payload.userName}
          onChange={handleChange}></Form.Control>
        <Form.Control
          type='password'
          placeholder='Password'
          name='password'
          value={payload.password}
          onChange={handleChange}></Form.Control>
        <Button variant='info' type='button' onClick={onClickLogin}>
          Login
        </Button>
      </div>
    </Container>
  );
};

export default Login;

※イメージこんな画面(若干自前CSS足しているので上記コピーだけでは再現されません)

  • _app.tsx(メイン)
import { NextPageContext } from 'next';
import { AppProps } from 'next/app';
import { parseCookies } from 'nookies';
import { useEffect } from 'react';
import { useRouter } from 'next/router';

const MyApp = ({ Component, pageProps }: AppProps, ctx: NextPageContext) => {
  const router = useRouter();
  const cookies = parseCookies(ctx);

  // 第二引数に空配列を指定してマウント・アンマウント毎(CSRでの各画面遷移時)に呼ばれるようにする
  useEffect(() => {
    // CSR用認証チェック

    router.beforePopState(({ url, as, options }) => {
      // ログイン画面とエラー画面遷移時のみ認証チェックを行わない
      if (url !== '/login' && url !== '/_error') {
        if (typeof cookies.auth === 'undefined') {
          // CSR用リダイレクト処理
          window.location.href = '/login';
          return false;
        }
      }
      return true;
    });
  }, []);

  const component =
    typeof pageProps === 'undefined' ? null : <Component {...pageProps} />;

  return component;
};

MyApp.getInitialProps = async (appContext: any) => {
  // SSR用認証チェック

  const cookies = parseCookies(appContext.ctx);
  // ログイン画面とエラー画面遷移時のみ認証チェックを行わない
  if (
    appContext.ctx.pathname !== '/login' &&
    appContext.ctx.pathname !== '/_error'
  ) {
    if (typeof cookies.auth === 'undefined') {
     // SSR or CSRを判定
      const isServer = typeof window === 'undefined';
      if (isServer) {
        console.log('in ServerSide');
        appContext.ctx.res.statusCode = 302;
        appContext.ctx.res.setHeader('Location', '/login');
        return {};
      } else {
        console.log('in ClientSide');
      }
    }
  }
  return {
    pageProps: {
      ...(appContext.Component.getInitialProps
        ? await appContext.Component.getInitialProps(appContext.ctx)
        : {}),
      pathname: appContext.ctx.pathname,
    },
  };
};

export default MyApp;

後書き

Next(Nuxtも)はSSRとCSRそれぞれを考慮する必要があるからめんどい、、けど良い勉強になった。
Nextはほぼ英語記事しかないので日本語の参考記事も増えたらいいなあ(やっぱり日本ではNuxt・・・?)

参考

※リダイレクト周りはほぼ英語記事参考にしましたが散り散りで集められませんでしたすみません・・。

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

【JavaScript】非同期処理の書き方 async/await編

【JavaScript】非同期処理の書き方 Promise編に引き続き、JavaScriptでの非同期処理について書きます。
今回はPromiseをより簡潔に書けるようにES2017で導入されたasync/awaitについて書きます

asyncとは何か?

非同期関数を定義する関数定義です。
async function で非同期関数を定義できます。
async function は Promise インスタンスを返却します。

async function sample(){
return 'Hello World!!';
}

上記の例で定義した sample は、Promiseで書くと以下の様になります。

function sample() {
  return new Promise((resolve, reject) => {
    resolve('Hello World!!');
  });
}

asyncの利用例

async function で定義された関数内でreturnされると、そのreturnの返り値で Promise.resolveもしくはreject が実行される為、.then()や.catch()でreturnの値を受け取ることができます。

async function resolveSample() {
          return "sample was resolved!";
        }
        async function rejectSample() {
          // reject を呼ぶ
          return Promise.reject(new Error("エラーです..."));
        }
        async function throwSample() {
          throw new Error("エラーが発生");
        }

        resolveSample().then((value) => {
          console.log(value); 
        });
        // 実行結果 =>sample was resolved!

        rejectSample().catch((error) => {
          console.log(error.message);
        });
        // 実行結果 => エラーです...

        // catch() で例外処理を行える
        throwSample().catch((error) => {
          console.log(error.message);
        }); // => エラーが発生

awaitとは何か?

async function内でPromiseの結果(resolve、reject)が返されるまで処理を一時停止する演算子のことです。awaitはasync function で利用することが出来ます。

async/awaitを使って非同期処理を書く

【JavaScript】非同期処理の書き方 Promise編で書いた以下の処理を実際にasync/awaitを使って書いていきます。
Screen Recording 2020-11-15 at 10.23.33.mov.gif

以下は前回↑の処理をasync/awaitを使わないで書いた処理です。

///(async/awaitを使わない処理)
            const getRelated = publisher =>{
                return new Promise((resolve, reject) =>{
                    setTimeout(pub =>{
                        const recipe = { title: 'Ramen', publisher: 'Tony' }
                        resolve(`${pub}: ${recipe.title}`);
                    }, 2000, publisher);
                });
            };

            const getIDs = new Promise((resolve, reject) =>{
                setTimeout(() =>{
                    resolve([523, 883, 473, 974]);
                }, 1500);
            });

            const getRecipe = recId =>{
                return new Promise((resolve, reject) =>{
                    setTimeout(ID =>{
                        const recipe = { title: 'Udon', publisher: 'Taro' };
                        resolve(`${ID}: ${recipe.title}`)
                    }, 2000, recId);
                });
            };

            getIDs
            .then(IDs => {
                console.log(IDs);
                return getRecipe(IDs[2]);
            })
            .then(recipe => {
                console.log(recipe);
                return getRelated('Tony');
            })
            .then(recipe => {
                console.log(recipe);
            })

次はasync/awaitを使って書いた処理です。

///(async/awaitを使った処理)
        const getRelated = (publisher) => {
          return new Promise((resolve, reject) => {
            setTimeout(
              (pub) => {
                const recipe = { title: "Ramen", publisher: "Tony" };
                resolve(`${pub}: ${recipe.title}`);
              },
              2000,
              publisher
            );
          });
        };

        const getIDs = new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve([523, 883, 473, 974]);
          }, 1500);
        });

        const getRecipe = (recID) => {
          return new Promise((resolve, reject) => {
            setTimeout(
              (IDs) => {
                const recipe = { title: "Udon", publisher: "Taro" };
                resolve(`${IDs}: ${recipe.title}`);
              },
              2000,
              recID
            );
          });
        };
async function getRecipesAW() {
          const IDs = await getIDs;
          console.log(IDs);
          const recipe = await getRecipe(IDs[2]);
          console.log(recipe);
          const related = await getRelated("Tony");
          console.log(related);

          return recipe;
        }
        getRecipesAW();

async/awaitを使うことでコードを簡潔に書くことが出来ます。

参考

MDN web docs 非同期関数

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

Next.jsまとめ

Next.jsを使ってブログ作ったが色々とだめすぎたので調べたことをまとめて作るために書く記事。
間違ってたりするかもだけど忘備録だからあんまり怒らないで。。。
てことで始めます。

Node.jsのインストール

Next.jsはNode.jsのReactのフレームワークみたいなやつなので色々入れます。
まずはNode.js
基本的にNode.jsの公式サイトからダウンロードすれば問題ない。
これは普通にexeとかからインストールしよう。
Node.js
バカでもこれくらいはできるはず

作業するディレクトリを作る

ディレクトリ内にNext.jsとReactをインストールしていくのでディレクトリを作成しましょう。
デスクトップとかにドメイン名のディレクトリとか作ったらいいんじゃないのかな
そんでcd 作ったディレクトリ名みたいなのをコマンドプロンプトとかで実行してディレクトリ移動してね

node_modulesを設定する

npm init -y

これを実行したらpackage.jsonってファイルができると思います。
それが設定ファイルみたいなのになります

ReactとNext.jsのインストール

npm install --save react react-dom next

これを実行したらReactとNext.jsがインストールされます。
簡単ですね^^

package.jsonに設定を記述する

設定を記述するとかなんかむずそうなこと言ってますが開発用のサーバーを起動する設定するだけです

package.json
{
  "name": "re-nixo-blog",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

package.jsonの中身はこんな感じになっているとおもうのでscriptsの中身を変更します
"test": "echo \"Error: no test specified\" && exit 1"
を削除して

"dev": "next",
"build": "next build",
"export": "next export",
"start": "next start -p 8010"

に変更します

トップページを作成する

next.jsのページはpagesというディレクトリにJavaScriptファイルを作成して作成します。
pagesというディレクトリをpackages.jsonと同じディレクトリに作成します。
そして、pagesの中にindex.jsというJavaScriptファイルを作成します。
これがトップページです。
トップページのコードを書いていきます。

pages/index.js
import Head from "next/head"
import Link from "next/link"

export default function Index() {
    var index = (
        <html>
            <Head>
                <meta charSet="UTF-8"/>
                <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0"/>
                <title>トップページ</title>
            </Head>
            <body>
                <h1>HelloWorld</h1>
            </body>
            <style jsx>{`
                * {
                    margin: 0;
                    padding: 0;
                    box-sizing: border-box;
                }
                h1 {
                    text-align: center;
                }
            `}</style>
        </html>
    )
    return index
}

これをindex.jsにコピペして、

npm run dev

を実行して、localhost:3000にアクセスするとHelloWorldと表示されると思います。

静的ファイルを配置する

今回は画像を読み込ませます。
Next.jsはpublicまたはsrcという名前のディレクトリに静的ファイルを配置します。
どちらでも動きますが、publicのほうがなんかいいね!
ということで静的ファイルを配置します。
package.jsonと同じディレクトリにpublicというディレクトリを作成します。
そこに画像とかを配置してください。
そして、index.jsbodyにhtmlと同じように書きます。
image.pngというファイル名の画像の場合は

<img src="/image.png" width="100%" height="auto"/>

のように記述します。
すると読み込まれます。

サーバーで公開する方法

本当に苦戦しました。
npm run exportでエクスポートしたものを公開したらアクセスはできるものの直URLでアクセスすると404になったり再読込すると404になったりするのでサーバーでnpm run buildをしたあとにnpm run startをし、リバースプロキシを使用し、公開することに成功しました。
えー、ずっと苦戦し、いろんな海外サイトを飛び回っていたら現在朝の6:31分です。
本当に疲れました。
なので忘れないように書いておきます。

まず、サーバーにプロジェクトファイルをアップロードし、サーバーでも

npm install --save react react-dom next

を実行します。
そして、npm run buildをし、screenを開きログアウトしても閉じないようにし、npm run startをします。
すると、localhost:8010でサーバーが開かれます。
8010というポートはpackage.jsonに書いた

"start": "next start -p 8010"

の8010です。ここを変えることでポートを変更することができます。
ポート8010で開いた、という体で進めます。
ポート開放が必要なのかわかりませんが一応しておきます。
ルーター側でもしておいたほうがいいかも

sudo ufw allow 8010
sudo ufw reload

そして、VirtualHostの設定に、

virtual-host.conf
<VirtualHost *:80>
    LoadModule ssl_module modules/mod_ssl.so
    LoadModule proxy_module modules/mod_proxy.so
    ServerName ドメイン名

    ProxyRequests Off
    ProxyPass / http://localhost:8010/
    ProxyPassReverse / http://localhost:8010/
</VirtualHost>

のように記述します。
これだけだとエラーが出ます。(4時間苦戦した)
なのでこの記事の通り色々有効化します。
https://stackoverflow.com/questions/23931987/apache-proxy-no-protocol-handler-was-valid

sudo a2enmod ssl
sudo a2enmod proxy
sudo a2enmod proxy_balancer
sudo a2enmod proxy_http

するとアクセスができるようになり、SSRも有効だと思います。
表示が早いね!

本当に疲れました。
ではまた。。。

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