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

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で続きを読む

JavaScriptでFizzBuzz問題作ってみた

JavaScriptの練習としてFizzBuzz問題を作ってみました。

FizzBuzzとは?

Fizz Buzz(フィズ・バズ、Bizz BuzzやBuzzとも呼ばれる)は英語圏で長距離ドライブ中や飲み会の時に行われる言葉遊びである。

出典: Wikipedia
 
 

実装したいこと(課題)

①Fizzに入力された値の倍数とBuzzに入力された値の倍数を小さい順で交互に表示する。
②FizzとBuzzの公倍数の時に”FizzBuzz”と表示させる。
③表示させるのは二桁の値まで。
④入力された値が整数値以外(文字列、小数点、空白)の時に、”整数値を入力して下さい”のエラー文章を表示させる。

完成イメージ

スクリーンショット 2020-11-27 18.23.54.png

スクリーンショット 2020-11-27 18.31.38.png

完成コード

index.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>FizzBuzz</title>
</head>

<body>
    <h1>FizzBuzz問題</h1>
    FizzNum:<input type="text" id="fizzNum" placeholder="整数値を入力してください" value="">
    <br>
    BuzzNum:<input type="text" id="buzzNum" placeholder="整数値を入力してください" value="">
    <br>
    <button type="button" id="output">実行</button>
    <h3>【出力】</h3>

    <ul id="fizzbuzzList"></ul>

    <script src="main.js"></script>
</body>

</html>

 
 

style.css
ul {
  padding-left: 10px;
}

li {
  list-style: none;
}

input {
  width: 160px;
}

 
 

main.js
const button = document.getElementById("output");
const fizzbuzzList = document.getElementById("fizzbuzzList");

const listAdd = (a) => {
  const li = document.createElement("li");
  li.innerHTML = a;
  fizzbuzzList.appendChild(li);
};

button.addEventListener("click", () => {
  const fizzNum = Number(document.getElementById("fizzNum").value);
  const buzzNum = Number(document.getElementById("buzzNum").value);

  if (
    Number.isInteger(fizzNum) &&
    Number.isInteger(buzzNum) &&
    fizzNum > 0 &&
    buzzNum > 0
  ) {
    for (i = 1; i < 100; i++) {
      if (i % fizzNum === 0 && i % buzzNum === 0) {
        listAdd("FizzBuzz" + i);
      } else if (i % fizzNum === 0) {
        listAdd("Fizz" + i);
      } else if (i % buzzNum === 0) {
        listAdd("Buzz" + i);
      }
    }
  } else {
    listAdd("整数値を入力してください");
  }
});

コードの解説

JavaScriptのコードを復習も兼ねて自分なりに解説していこうと思います。
 

main.js
const button = document.getElementById("output"); 
const fizzbuzzList = document.getElementById("fizzbuzzList");

定数buttonにクリックイベント発火用のDOM要素を取得して代入し、
定数fizzbuzzListにFizzBuzzを表示させる為のul要素を取得し代入
 

main.js
const listAdd = (a) => {
  const li = document.createElement("li");
  li.innerHTML = a;
  fizzbuzzList.appendChild(li);
};

定数listAddにli要素を作成する為の関数を代入。
関数呼び出し時に引数を受け取る為、listAdd = (a) =>のカッコの中に受け取る為の値 a を入力。

document.createElement("li")でli要素を生成し変数liに代入。

生成したli要素にli.innerHTML = a;で引数として渡されてきた値をセット。

appendChild(li)で生成したli要素をfizzbuzzListに追加。
 
 

main.js
button.addEventListener("click", () => {
  const fizzNum = Number(document.getElementById("fizzNum").value);
  const buzzNum = Number(document.getElementById("buzzNum").value);

addEventListener("click", ()=> { でクリックイベント発動。
input要素に入力された値をそれぞれfizzNumとbuzzNumに代入。
.valueで入力された値を取得することができる。
ここでNumberオブジェクトを使って入力された値を文字列から数値化させておかないとif文での条件分岐の時に苦労する。

main.js
  if (
    Number.isInteger(fizzNum) &&
    Number.isInteger(buzzNum) &&
    fizzNum > 0 &&
    buzzNum > 0
  ) 

Number.isInteger(値)で渡された値が整数か判定します。
渡された値が整数であればtrue、そうでなければfalseが返ってきます。

そして、fizzNum > 0 && buzzNum > 0 とすることで
渡された値が1以上の自然数の場合はtrueを返すという条件分岐になります。

※一般的に0は自然数には含まれないので注意。

こちらの条件分岐では
fizzNumとbuzzNumに渡された値が整数値"かつ"1以上の自然数の場合、次のfor文の処理をします。
 
 

main.js
//変数iに1を代入しiが100より小さい間以下の処理を繰り返す。1ループごとにiを1ずつ増やす
    for (i = 1; i < 100; i++) {

//iを入力されたfizzNumで割った値のあまりが0の場合かつbuzzNumで割ったあまりが0の場合
      if (i % fizzNum === 0 && i % buzzNum === 0) {

//関数listAddに引数として()内の文字列を渡す
        listAdd("FizzBuzz" + i);

//iを入力されたfizzNumで割ったあまりの数が0の場合
      } else if (i % fizzNum === 0) {

//関数listAddに引数として()内の文字列を渡す
        listAdd("Fizz" + i);

//iを入力されたbuzzNumで割ったあまりの数が0の場合
      } else if (i % buzzNum === 0) {

//関数listAddに引数として()内の文字列を渡す
        listAdd("Buzz" + i);
      }
    }
//上記のどの条件にも引っかからなかった場合以下の処理を実行
  } else {
    listAdd("整数値を入力してください");
  }

最後の部分投げやりになってしまいましたが、ざっくりとこんな感じです(笑)

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

始めてwebRTCに挑戦した結果…爆散した(後編)

webRTCに挑戦した結果爆散した…

こちらの記事は前編の続きです!ぜひ前編を見てからこの記事を読んでやってください。
全編

とにかくReactでカメラを起動させないと…

前回とりあえずシンプルなJavaScriptで書いたコードをまんまReactで書いたんですけど…
あれ〜カメラ動かんやんけ〜てなことになりましたね!

あ、本日の記事は少しおまけをつけときました。

あの後親切なお方からアドバイスをもらいました…

どうやら前回の記事のときはJSXで作ったいわゆる仮想DOMを取得するときにdocument.getElementById('video');で取得しようとしていました。しかし、ここをdocument.querySelector('video');とすることでうまくいきました。

コード全体は前回の記事を遡ってくださいとおもったのですが、一応それだとめんどいはアホと言われかねないので書いときます。

Camera.js
class Camera extends Component {
    render() {
        const media = document.getElementsByClassName('video');
        navigator.mediaDevices.getUserMedia({vide:true; audio:false;})
        .then (function(stream) {
            media.srcObject = stream;
        })
        .catch (function(err){
            console.error ('mediaDevices.getUserMedia() error:',err);
            return;
        })
        return (
            <div>
                <h1>Webcam</h1> /* なんとなくタイトルをつけてるだけです */
                <video className="video" autoplay />
            </div>
        )
    };
}

ちなみに今回はメディアをしているするときに
navigator.mediaDevices.getUserMedia()でメディアを指定していますがここはnavigator.getUserMediaでも動きます。

しかし、これはMDNでは非推奨の書き方になりますので今回は前者の書き方を使用しました。なんで非推奨なのかはよくわかってません…ごめんなさい…では今後この記事を読んだ人に悪影響を与えかねんので調べました。
メディア周りをいじるときnavigator.getUserMedia()は動くブラウザと動かないブラウザが混在しておりブラウザが変わったときに挙動が異なってしまうということが起きます。これはnavigator.mediaDevices.getUserMedia()を導入したことによってこれに対応するためにブラウザがいろいろ対応しているのですが、その際に今まで使用していたnavigator.getUserMedia()はもういらん!といって非対応にしてしまったらしいです。よって開発者側は全てのブラウザに対応させるためにnavigator.mediaDevices.getUserMedia()を用いることが推奨されてます。
参照:MDN mozilla

へ〜便利な外部ライブラリ(react-webcam)かぁ〜

調べていくとどうやらReactさんでカメラを動かすにはReactWebCamという外部ライブラリを利用するのが一番楽らしいdesu!
というわけなのでとりあえずReactWebCamをプロジェクト内にインストールします。

$ npm install react-webcam

これでReactWebCamを利用できるようになりました。
でもどうやって使うんやろ〜?

react-webcamを使ってみよう!

先ほどプロジェクトにライブラリをインストールしたのでこれから実際に動かしてみます。
まぁ詳しい使い方はGitHubにあるので読みましょう!
するとどうやらいつも通りimportして使えばい良いとのことなので時をもど…
何はともあれ先程のコードは一旦全てコメントアウト!
最悪消しても良いです…

Camera.js
import React,{ Component } from 'react';
import Webcam from 'react-webcam';

class Camera extends Component {
    render(){

        // cameraSize
        const videoConstraints = {
          width: 1280,
          height:720,
        };
        // 使用メディアの指定
        const capture = {
            facingMode: {
                exact: "environment" // リアカメラの利用、もしフロントカメラを使いたい場合はuserにする
            }
        };

        return(
            <h1>WebcamApp</h1>
            <Webcam
                audio={false}
                videoConstraints={videoConstraints}
                video={capture} />
        )
    };
}
export default Camera;

今回はとりあえずカメラが起動できて、きちんとキャプチャしてくれれば良いのでaudioはfalseにしてあります。
もし、音声を扱いたい場合はaudioをtrueにしてナンジャカンジャをいじいじしてあげましょう!
ちなみに今回はキャプチャしたものを表示するためのサイズを指定していますが指定しなくてもよしなにサイズを設定して表示してくれるみたいです。
でも自分の望んだサイズで表示されるとは限らない(というか大半の場合は表示されないと思う)のできちんと表示サイズを指定しましょう。
何はともあれとりあえずこれを表示してみようか…
これをApp.jsにインポートして一つのページとしてルーティングさせましょう。

おまけ

今後ページを少しづつ増やしていくのでreact-routerでルーティングさせていく準備もやってしまおう!
ちなみにルーティングの記事はまたどこかで書きますがとりあえず今回はプロジェクトの中で以下を実行しておいてください

$ npm install react-router-dom

そして、App.jsを編集します。

App.js
import React from 'react';
import { BrowserRouter as Router,Switch, Route } from 'react-router-dom';

// pageComponents
import Home from './pages/Home';
import Camera from './pages/Camera';

function App() {
    return (
        <div className = "App">
            <Router>
                <Switch>
                    <Route exact path = "/" component={Home} />
                    <Route path = "Camera" component={Camera} />
                </Switch>
            </Router>
        </div>
    );
}
export default App;

そうしますととりあえずルーティングは終わり。
起動してみます。

$ npm start

これを実行するとlocalhostのおそらく3000番ポートが開かれると思います。
もしすでにlocalhost3000番が使われていたら、別ポートで開きますか?というようなメッセージがターミナル上に出てきますのでyを押してエンターで続けてください。

やった〜かめらがようやっと起動しました〜〜〜〜〜
カメラを起動させるのにどれだけ時間をかけるつもりだよ…

さてこのあとは…

ここまででやっとスタートラインに立ちました…
ここからメディアをいじって何かしらサービスを作っていきたいと思います。
しかし、私非常に疲れてしまいましたのでその記事はまた次回にするとしましょう〜

最後に

今回はライブラリを使わない方法と使う方法二つをご紹介しましたが、改めてライブラリというものの偉大さを切実に実感しましたね〜
だってぽちぽち、インポート!終了!ですからね!
サービスでどのような機能を作るかをよく考えてそれを実装できそうなライブラリがあるなら積極的に使っていきたいな〜と思ういい機会でした。
また何かアドバイス等ございましたらコメント欄にお願いします!

余談?かな今回はクラスコンポーネントを利用していますがもちろんファンクショナルコンポーネントでもちゃんと動きます!動作確認済みです。

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

始めてwebRTCに挑戦した結果…爆散した(前編)

初めに

この記事はweb開発初心者を前提として書いております。(筆者の体験談)
「馬鹿かこいつ!こんな基礎中の基礎も知らずにwebやるとか頭おかしんじゃね〜の!出直してこい!」とか思わず生暖かい目で見守ってやってください。
そして、僕と同じ状況のあなた!心配しないで!あなたよりポンコツはいくらでもいます!
現に今目の前の記事を書いてる人も相当のポンコツです!

webカメ使って楽しいことしたい!

とある大学の授業で何かしら作品作ってって言われたからとりあえず私未踏の地WebRTCに挑戦したろ!
今までカメラとか一度も使ったことないけど多分videoタグ当たりをJSで軽くいじるんやろ〜
余裕余裕…じゃない!ん?
何をどうすればいいかよくわからん!
というわけでWebRTC初心者の私が勉強しながらも頑張った証を残そうと思います。
私と同じでWebRTCやってみたいけどよくわからないという人の助けになればいいな〜

ひとまずWebRTCを行うためには…?

まぁweb開発で困ったことあったらみんなご存知のMDNを見れば大体解決できるのですが、私はそもそも何をしていいかわからないからどのドキュメントを読めばいいかわからない(何がわからないのかわからない!)という状態だったのでとりあえずGitHubに溢れかえっているWebRTCのコードを読みあさってみることにしました。すると!どうやらMediaツール周りのリクエストや制約云々をいじればいけるということがわかってきました!

Mediaツール周りのリクエスト

まぁ考えてみればwebカメラいじるんだからそりゃそこらへんいじるわな!
というわけでここまででわかったことはNavigatorというObjectでgetUserMediaにて使用するメディアツールの情報を呼び出すことから始めましょう!

DocumentObjectModel

DocumentObjectModel、通称DOMですね!
これは簡単に言えばJavaScriptからHTMLのhタグやらpタグやらに接続しいじくり回すためのオブジェクトモデルです。
より正確に知りたいという方ははいでましたMDNさんを参照してください!
そしてこのDOMというものはツリー構造をようしており、このツリーの最小の単位をNodeと言います。
このツリーのNodeに対して干渉していくことでイベントを起こすことができます。
また、このイベントというものはNodeに対してイベントハンドラーというものを割り当てて起こすことができます。
なぜ今このDOMが出てきたのか…
それはこのあとDOMを動かしながらでないと私たち(ユーザ)がみている画面をJavaScriptから動かすことができないからです。ちなみに今回はWebRTCということなのでwebカメラを動かします。そのための表示フィールドとでも言っておきましょうかそれをこのDOMを用いて作成し、表示していきましょう。

ちょこっとWebcamを起動してみましょ

ちょっとここまでコードが出てこなかったので、「なんだこの記事ふざけやがって〜!」と思った方ここからコードを少し書きますよ〜

それでは実際に作っていきます。
とりあえずシンプルなJavaScriptでやってみよう!
webでカメラを利用して何かをするときは基本的にはhtmlのvideoタグをいじいじしながら開発をしていくことが多いみたいです。
ちなみに、videoタグはこれだけではなくwebに動画を埋め込む際にも利用されたりと、メディアをいじるときにはよく出てくるものらしい…
というわけで今回はvideoタグをHTMLで用意し、JavaScriptにvideoタグの要素を取得しDOMを操作していこうと思います。

sample.html
<body>
    <h1>Sample Camera App</h1>
    <video id = "video" autoplay></video>
    <script type = "javascript">
        const media = document.getElementById('video');  // これは皆さんご存知HTMLのvideoタグのidを指定してDOM要素を取得する手順です。

        navigator.mediaDevices.getUserMedia({video:true, audio: false})  // 今回は写れば良しということでマイクはOFFで行きます
        .then (function(stream) {
            media.srcObject = stream;
        })
        .catch (function(err){
            console.error('mediaDevices.getUserMedia() error:',err);

            return;
        })
    </script>
</body>

これでうまくいくはずです…!
うまくいった!
スクリーンショット 2020-11-18 14.10.07.png
まぁこないな感じでやれますわ〜

しかし今のご時世フレームワークを用いてユーザ体験の向上やサーバサイドレンダリングやら静的サイトジェネレーターやらを行うのが当たり前と言われておりますが(筆者の周りでわ…)応用編として今回は私くしが実際にお勉強中のReactでやってみようと思います。
そしてあわよくば今現在大学の課題で作っているカメラに写っている人の数を検出して人数を表示するというところにまで持っていきたい!
とまぁ自分語りはここら辺にしてやっていきましょう〜
これは完全に僕の好みというかReact使う人たちは大体そうだと思いますけど、今回はCameraコンポーネントを作成しApp.jsにimportして作ろうと思います。

Camera.js
import React,{ Component } from 'react';

class Camera extends Component {
    render() {
        const media = document.getElementsByClassName('video');
        navigator.mediaDevices.getUserMedia({vide:true; audio:false;})
        .then (function(stream) {
            media.srcObject = stream;
        })
        .catch (function(err){
            console.error ('mediaDevices.getUserMedia() error:',err);
            return;
        })
        return (
            <div>
                <h1>Webcam</h1> /* なんとなくタイトルをつけてるだけです */
                <video className="video" autoplay />
            </div>
        )
    };
}
export default Camera;

とりあえずこれでカメラを起動して画面にリアルタイムで表示してくれるはず…
なんですが、おや?
webカメラは起動したけど画面に表示してくれない…
何かエラーは出ているのかな〜
あれ⁉︎
エラーもない!
どうしよう!
Reactわけわから〜ん!
いやJavaScriptなのか?
なんだよこれ!
もうWebやめる!

[というわけで次回へ…]

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

Node.jsのバージョン管理で苦労したお話…

バージョンが上がった!さぁ更新しよう!

私maresukeはwebフロントが好きなのでよくJavaScript(React)を使った開発をするんですが、まぁパッケージ管理はnode.jsさんにお任せ!っていう感じなんですね〜
しかしあるときからやたらGitHubさんから警告がくるんですよね…!
もしかして垢BAN!かと思ったら、nodeのバージョンがあまりよろしくないものを使っているだけでした…!
nodeのバージョンって新しくなったり、安定版が変更されていたりしますが、僕いい子ちゃんじゃないからいつも更新を後回しにしちゃって〜気付いたら僕が使ってるバージョンは遥か昔江戸時代ぐらいのものになっているんです…
こら〜そこのあなた〜「このアンポンタンの頭はチンパンジーか」とか思っちゃダメですよ〜
しかし、このバージョン管理は非常に大事です!サービスの脆弱性につながってしまうので、nodeのバージョンはこまめにチェックしましょう!

と前置きはこのくらいにして、早速に本題に入らなくては!

実際にバージョンアップしてみよう!

実際に僕が当時直面した時の手順でやっていきます。
今回は私がmacOSユーザーなので基本はmacOSベースに進めていきますが、Windowsも対応していきます。ただしWindowsはUbuntu上での環境構築を想定しています。もしこの記事のやり方でやろうと思われている方がいましたらUbuntuのインストールから行ってください。
Ubuntu公式にUbuntuのセットアップ手順など全て記載してあるので参照しみてください。

今現在の状態を確認してみよう

そんなこんなで怒られるのは嫌だし、サービスが大変なことになるのも嫌なのでnodeのバージョンを変更することにしました。ちなみに今まで使っていたものはv12.12.0でした。もし僕と同じでnodeのバージョンで困っている方も確認してみてください。確認方法は…

$ node -v
# もしくは
$ node --version

こちらのどちらかのコマンドを実行して貰えば今現在自分のローカル環境のnodeのバージョンが表示されます。
ちなみにnodeがインストールされていなければエラーが出てきます。

では安定版をインストールします

今回はanyenvnodenvを用いてやっていきます。

まだanyenv,nodenvが入っていなかった場合

まずはanyenvをインストールします。このanyenvはいろんな言語の開発環境をコマンドを二つ三つぐらい実行するだけでできてしまうという超便利なセットアップツールなんです。ぜひ入れましょう!
そしてanyenvを用いてnodenvというnode.jsのバージョンを管理できるツールをインストールし、nodenvでバージョンを上げたり下げたりします。
ちなみに今回はすでにNode.jsが入っているという設定ですが、ここから読めばNode.jsが入っていない人もNoe.jsの環境構築を楽々できちゃいます。

  • anyenvの設定
// macOS
$ brew install anyenv

// Windows
$ git clone https://github.com/anyenv/anyenv ~/.anyenv
$ echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.bashrc
$~/.anyenv/bin/anyenv init

以上のコマンド実行すると以下のようなコメントが出てきます

// macOS
# Load anyenv automatically by adding
# the following to ~/.zshrc:

eval "$(anyenv init -)"

// Windows
# Load anyenv automatically by adding
# the following to ~/.bash_profile:

eval "$(anyenv init -)"

どうやらeval "$(anyenv init -)"の一文を.zshrcに記述してくださいとのことですので記述します。
以下要注意事項
ちなみに2行目の最後の辺の~/.~rcの部分はお使いのshellによって異なります。zshならzsh、bashならbash、fishは確認ができてませんがおそらくfishと出ているでしょう…
以降~rcや~_profileのような表記があればその都度自身の使用しているshellに変更してお考えください!

では記述して行きます!

// macOS
vi ~/.zshrc

// Windows
vi ~/.bash_profile

さっきの一文(eval "$(anyenv init -)")を記述して:wqで保存しましょう。
できましたら一度Shellを再起動して次に以下のコマンドを実行します。
おそらくsource .zshrcでもいけるんじゃないかな〜とおもいます。

// macOS
echo 'eval "$(anyenv init -)"' >> ~/.zshrc
exec $SHELL -l

// Windows
echo 'eval "$(anyenv init -)"' >> ~/.bashrc
exec $SHELL -l

これでanyenvのインストールは完了です!
試しに

$ anyenv -v
// もしくは
$ anyenv --version

を実行してください。きちんとインストールできていればインストールされたバージョンが表示されます。
さぁ〜ここまでやってきましたがやっと本番です!
Node.jsをいじりますよ〜
まずはNode.jsをインストール、アップデート、ダウングレードするためのnodenvをインストールしましょう!

  • nodenvの設定

先ほどインストールしたanyenvnodenvをインストールします。

$ anyenv install nodenv

実行がうまくいくとこうなります

// これより上は長ったらしい文章が出てきやがるので省略
Install nodenv succeeded!
Please reload your profile (exec $SHELL -l) or open a new session.

とまぁ再起動しいやと言われるので素直に従いましょう。
そしてお次は

$ $ exec $SHELL -l

を実行してみましょう!
特にエラーが出なかったらきちんとnodenvがインストールされているはずです…
不安なので確認します

$ nodenv -v
// もしくは
$ nodenv --versions

を実行します…

nodenv 1.4.0+3.631d0b6

よかった〜ちゃんと入ってました〜

  • Node.jsのインストール
    さて、ここまでちょっと時間がかかってしまいましたが最終段階だ〜 node.jsのインストール及びアップグレード、ダウングレードは以降nodenvを用いてぽちぽちするだけで終わります。やった〜これで今後楽にバージョン管理ができるぞ〜やっフォ〜い
    え〜ごほん!お見苦しいところをお見せしました。 ではまずはNode.jsをインストールします。が、その前に今現在インストール可能なバージョンがなんなのかを確認しましょう。
$ nodenv install list

すると今現在インストール可能なnode.jsのバージョンが全て表示されます。
今回も実行結果を載せたいのですが実行結果が多すぎて載せると記事がとんでもないことになりそうなので割愛します。

  • さぁてnode.jsの安定版をインストールしていきましょう!
    今現在インストール可能で最新のnode.jsはv15.2.1になります。しかしこれは未だ動作の安定が未確認もしくは改良途中、対応中のためお勧めはしません。動作安定版として一番お勧めされるのはv14.15.1になります。 てなわけで今回(この記事の執筆時2020/11/22)は安定版のv14.15.1をインストールしていきます。
$ nodenv install 14.15.1

これでインストールは完了!
あとはローカルに反映させれば終わりです!

$ nodenv global 14.15.1

よし!
ここまで全てが順調に終わってる!
俺って天才なのでは!
JS完全に理解した!
確認確認!

$ node -v
v12.12.0


誰だ!貴様!
ふぇ〜
なんじゃこれ〜どないなことになってんねん!
僕なんも悪いことしてないんだぞ!
よくわからなかったけど調べていくうちにバージョンの更新がうまくいかない時によくある問題としてnodeのpathが悪さをしてる場合があるらしいのでnodeのpathがnodenvとつながっているかを確認しましょう。

$ which node

で調べれるらしい。whichコマンドはpathを調べるコマンドなので今後何かしらpathを調べることがよくあると思うので覚えときましょう。
ほとんどの場合はつながっておらずuser/local/bin/nodeになってることが多いそうです。調べてる間僕と同じこのようなpathになっている人がほとんどでした。
ということなのでこのpathを殲滅します!
くらえ!

$ sudo rm -rf user/local/bin/node
// 注意 rm -rf の利用は気をつけましょう問答無用で全てを消し去ります。取り返しがつかなくなる!危険!

これで邪魔者はいなくなった!

$ node -v
14.15.1

やった〜!
みたかv12.12.0
いや、失礼ですねちゃんとお礼を言わなくては!
今までありがとう12.12.0

以上!これにて更新終了

すでにnodenvがある方

こちらの方は凄〜〜〜〜く簡単です!
なぜならこうです。

$ nodenv install 14.15.1
$ nodenv global 14.15.1

終わり…
つまり、更新したいバージョンをインストールし、それを反映させるだけなのでコマンドとしては二つで終わりでダス。
もし問題があった場合はやはりpathの問題が多いみたいです。
なので焦らず急がずpathを治してあげて終了です。

そういえば…

なんで今回はnodenvを使ったの?
調べるとnodebrewってのもあるよね?
こんな質問が飛んできそうですね〜
実は僕も詳しくはよくわかっていはいないんですが、はっきりと言えるのはNode.jsのバージョン管理が一番楽!ということです。
本来Node.jsのバージョン管理はディレクトリ(サービス)ごとというのが暗黙の了解というか、当たり前なのです。したがってhogeというディレクトリではv1を使ってるがfugaというディレクトリではv2を使う、このようなことが頻繁に起きます。その場合nodebrewではいちいちコマンドを実行してnodeのバージョンを切り替えなくてはなりません。しかしnodenvさんはすごくてですね〜ディレクトリを移動しただけで自動的にバージョンを変更してくれるんですよ〜すごくないですか⁉︎多分調べればもっと出てくるんでしょうが、僕はこの程度のことしか知らないので許してください。

どうでした?

web大好きマンならこのくらい当たり前かもしれませんが、意外とバージョン管理でてこずる人って多いと思うんですよ〜
なので今回の僕の経験が誰かの役に立ってくれればと…

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

【JavaScript】プリミティブ型とオブジェクト型を理解したい

概要

この記事では、JavaScriptのデータ型について理解していきます。JavaScriptにおいて、型の理解は非常に大事なので、しっかり理解しておきたい部分です。

では、いきましょう!

データ型とは?

そもそもデータ型とはなんなのでしょうか?調べてみると次のような記述がありました。

プログラミングにおけるデータ型あるいは単に型は、値の種類を示し分類分けするラベルである。データタイプともいう。
引用:Wikipedia

つまり、簡単にいうと値のカテゴリー分けのことになります。例えば、「10」だったら、数値型のように、各プログラミング言語で決まっています。

もちろん、JavaScriptにも型は存在します

JavaScriptは型を持っている

「JavaScriptにも型は存在する」と聞いて、「JavaScriptって型がない言語なんじゃないの?」って思っている方がいるかもしれません。これは、間違いです。

「型がある・ない」という表現は、誤解を招きやすいためあまり使わないほうが良いです。

JavaScriptには型が存在します。ここをまず押さえておいてください。

JavaScriptは動的型付け言語である

なぜ、JavaScriptが型がない言語であるか誤解されやすいのでしょうか?

それは、JavaScriptが「動的型付け言語」であるためです。

「動的型付け言語」とは、変数や関数の引数・戻り値と言った値の型が、プログラムの実行時の値によって動的に変化する言語です。
JavaScriptで変数を宣言する際は、変数名だけを決めますよね。

動的型付け言語と対をなす性質を持った言語を「静的型付け言語」と呼びます。

静的型付け言語」では、変数や関数の引数・戻り値と言った値の型をあらかじめ決めておかなければいけません。
例えば、TypeScriptでは、変数を宣言する際に変数名だけではなく、その変数にはどのような型の値が入るのかを指定しておきます。

//JavaScriptによる変数の宣言
let a = 10;

a = 'Messi';
//TypeScriptによる変数の宣言
let b: number = 10;

b = 'Messi';
//コンパイルエラーになる

JavaScriptの例では、letで変数aを定義し、10を代入しています。その後、aMessiに書き換えてもエラーにはなりません。
一方、TypeScriptの例では、letで変数bを定義する際に、bの型を数値型に指定しています。そのため、文字列型である'Messi'に値を更新すると、エラーが表示されます。

JavaScriptは「動的型付け言語」であり、「動的型付け言語」では値の型をあらかじめ決めないという性質から、JavaScriptには型がないという誤解をされやすいのです。

プリミティブ型とオブジェクト型

ここまでで、JavaScriptにも型があるということを理解していただけたと思います。

では、JavaScriptのデータ型はどのように分類されているのでしょうか?

結論から言いますと、JavaScriptのデータ型は「プリミティブ型」と「オブジェクト型」の2種類に大別されます。

それぞれについて説明します。

簡単にいうと、「プリミティブ型」とは、オブジェクトではないもの、「オブジェクト型」とは、プリミティブ値でないもののことです。

すみません、ちゃんと説明します。

プリミティブ型(基本型)」は、真偽値や数値などの基本的な型のことです。プリミティブ型の値は、一度作成したらその値を変更することができません。(この特性をイミュータブルと呼ぶ)

一方、「オブジェクト型(複合型)」は、複数のプリミティブ型の値またはオブジェクトからなる集合体です。オブジェクトは、一度作成した後も、その値自体を変更することができます。(この特性をミュータブルと呼ぶ)

JavaScriptのデータ型は、このような定義で分類されています。

プリミティブ型とリテラル

プリミティブ型は、さらに以下の7種類に分けられます。

  • Boolean型
  • Number型
  • BigInt型
  • String型
  • Symbol型
  • Null型
  • Undefined型

typeof演算子を使うことでデータ型を確認することができます。

console.log(typeof "Messi"); 
console.log(typeof true); 
出力結果
"string"
"boolean"

また、それぞれの型を定義するには「リテラル(Literal)」を使います。「リテラル」とは、プログラム上で数値や文字列など、データ型の値を直接記述できるように構文として定義されたものです。
Symbol型とUndefined型以外のプリミティブ型には、リテラルが用意されています。

では、それぞれの型とそのリテラルについて説明していきます。

Boolean型

Boolean型とは、turefalseの2つの真偽値を扱うデータ型です。
Boolean型には、truefalseの真偽値リテラルがあります。

Number型

Number型とは、数値を扱うためのデータ型です。JavaScriptのNumber型では、整数も少数も同じNumber型として扱われます。扱うことのできる最大値は253–1です。
Number型には、10-7などの数値リテラルと1.45.4e3などの浮動小数点リテラルがあります。

BigInt型

BigInt型とは、Number型で取り扱うことのできない大きな数値を扱うためのデータ型です。ES2020で追加されました。Number型との互換性はなく、相互に代入や計算・等値比較などは行うことができません。
200nなどのように数字の後ろにnをつけて表現する数値リテラルがあります。

String型

テキストを表す連続した文字である文字列を扱うためのデータ型です。
"Messi"のように、'もしくは"で囲んだ範囲が文字列リテラルとなります。`を用いることで、改行を含む複数行テキストや${}による式の展開画家のなテンプレートリテラルになります。

Symbol型

Symbol型とは、「シンボル値」という固有の識別子を表現する値で、任意の不変な値のデータ型です。ES2015で追加されました。
Symbol()関数を呼び出すことで同的に生成されます。同じシンボル値を後から生成することはできません。オブジェクトのプロパティキーとして使用することができます。
Symbol型は、リテラル表現を持ちません。

Null型

Null型とは、値が存在しないことを意味するデータ型です。
nullリテラルであるnullは、プリミティブ値nullを返します。

ただしここで注意点があります。typeof演算子でnullのデータ型を調べるとobjectとなります。

console.log(typeof null);
出力結果
"object"

これは、残念なことにJavaScriptの歴史的経緯からなるバグです。

Undefined型

Undefined型とは、値が未定義であることを意味するデータ型です。プリミティブ値undefinedは、宣言のみが行われた変数やオブジェクトないの存在しないプロパティへのアクセスに割り当てられます。JavaScriptでは、nullundefinedは明確に区別されています。

ここで注意が必要なのは、undefinedはリテラルではないということです。undefinedは、ただのグローバル変数で、undefinedという値を持っているだけです。

以上が、プリミティブ型の分類になります。

オブジェクト型とリテラル

オブジェクト型には以下のものが含まれます。

  • プリミティブ型以外のデータ
  • オブジェクト
  • 配列
  • 関数
  • 正規表現
  • Data        など...

基本的にプリミティブ型でないものは、全てオブジェクト型です。

console.log(typeof ["Messi"]);
console.log(typeof { name: "Messi" });
console.log(typeof function() {});
出力結果
"object"
"object"
"function"

このように、プリミティブ型と同じようにtypeof演算子で分類することができます。しかし、配列とオブジェクトがどちらもobjectと判定されているように、全てのオブジェクトの種類を判定することはできません。
そのため、基本的にtypeof演算子は、プリミティブ型かオブジェクト型かを判別するために使われます。(nullのバグには注意しましょう)

オブジェクト型にもリテラルを持つものがあります。

  • オブジェクトリテラル
  • 配列リテラル
  • 正規表現リテラル

それぞれについて説明していきます。

オブジェクトリテラル

オブジェクトリテラル{key: value }の形式で書かれます。{}は空のオブジェクトを示します。obje[key]もしくはobj.keyとすることで、任意のプロパティ値にアクセスすることができます。ただし、プロパティ名が識別子として利用できないプロパティ名は、obj.keyで取り出すことができません。

また、オブジェクトリテラルは、Objectオブジェクトのインスタンスとして生成されます。

//どちらでも定義することができる
const obj1 = {};
const obj2 = newObject();
const obj3 = {name: "Messi"};

console.log(obj1, obj2 ,obj3);
出力結果
{} {} {name: "Messi"}

基本的には、オブジェクトリテラルはオブジェクトの作成と同時に中身を定義できるので、Objectオブジェクトからオブジェクトを作成することはありません。

以下の配列や正規表現もこのオブジェクトが元になっています。

広義のオブジェクトと狭義のオブジェクト

ここで広義のオブジェクトと狭義のオブジェクトについて説明しておきます。
先ほどから、オブジェクト型だとか、オブジェクトとか一貫性がなく使っているなと思われた方がいるかもしれません。

JavaScriptでは、一般的に連想配列と呼ばれるものもオブジェクトと呼ぶため、オブジェクト型と混同してわかりにくくなっています。ほんとに紛らわしいですよね。
基本的に、JavaScriptの書籍や記事はここの違いについて触れませんので、文脈から推論するしかありません。

それぞれについて簡単に説明すると

  • 広義のオブジェクト→最終的な継承元がObjectオブジェクトであるもの
  • 狭義のオブジェクト→一般的に「連想配列」と呼ばれるキーとそれに対応するプロパティの集まり

と言えます。

配列リテラル

配列リテラルとは[1, 2, 3]の形式で書かれます。[]は、空の配列を示します。arr[n]という構文で、n-1番目の要素にアクセスすることができます。

また、Arrayオブジェクトのインスタンスとして生成されます。

const array1 = [];
const array2 = new Array();
const array3 = [1, 2, 3];
const array4 = new Array(1, 2, 3);

console.log(array1, array2 ,array3, array4);
出力結果
[] [] [1, 2, 3] [1, 2, 3]

オブジェクトと同じような理由から、基本的にインスタンスで生成するということはありません。

正規表現リテラル

正規表現リテラルは、/pattern/igの形式で書かれます。正規表現パターンでの特殊文字の使い方については、ここでは割愛しますが、他の言語とほとんど同じです。

また、RegExpオブジェクトのインスタンスとして生成されます。


以上が、プリミティブ型とオブジェクト型の分類になります。

プリミティブ値のリテラルでメソッドが使える理由

プリミティブ型の値は、インスタンスメソッドを持たないはずですが、普通の文字列でもメソッドを実行することができてしまいます。

これは、なぜなのでしょうか?

実は、nullundefinedを除く全てのプリミティブ型には、それらの値を抱合する「ラッパーオブジェクト」が存在します。例えば、string型ならString、number型ならNumberがラッパーオブジェクトになります。ラッパーオブジェクトは、new演算子と対応するコンストラクタ関数を利用して作成することができます。

//ラッパーオブジェクトを作成
const str = new String("Messi");

console.log(typeof str);
console.log(str.length);
出力結果
"object"
5

lengthは、文字の長さを返すメソッドです。strのデータ型はオブジェクト型なので、メソッドを問題なく使うことができます。

JavaScriptでは、明示的にラッパーオブジェクトを記述せずとも、プリミティブ型のデータに対してオブジェクトのように参照する仕組みがあります。
そのため以下のようにプリミティブ型のデータに対してもメソッドを使うことができます。

//プリミティブ型の変数strを定義
const str = "Messi":

console.log(typeof str);
console.log(str.length);
出力結果
"string"
5

データ型は、プリミティブ型のString型ですが、メソッドが問題なく使用できます。lengthが実行できるのは、自動的に変換されたStringオブジェクトのインスタンスメソッドを実行しているからです。

まとめ

今回は、JavaScriptのプリミティブ型とオブジェクト型についてまとめてみました。

最後まで読んでいただきありがとうございました。ではまた。

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

Javascriptの非同期処理を制御する方法

はじめに

今回はJavascriptの非同期処理について解説していきます。

最初に非同期処理とはという解説をした後に、Promiseを使って制御する方法とasync/awaitを使って制御する方法を解説します。

頑張っていきましょう。

非同期処理とは

そもそも非同期処理とは一体なんでしょうか。

以下のサイトに分かりやすい図があったので拝借しました。

I-26-2. 非同期処理と同期処理の実装パターンと特徴

image.png

非同期処理を端的に言うなら、あるタスクが実行しているときに、他のタスクが別の処理を実行できる方式ということができます。

プログラムを例にして、詳しく解説していきます。

普通、プログラムを実行すると、コードを上から順に一行ずつ実行していきます。

これは普段から当たり前のように使っている同期処理と呼ばれるものです。

しかし、サーバーと通信を行った時などは、この同期処理とは違った挙動をします。

具体的には、サーバーと通信を行う(fetchなど)行を実行したあと、その終了を待たずに次の行が実行されることになります。

例えば、fetchを使ってサーバーからデータを取ってきた後に、そのデータを出力するというプログラムを書いたとします。

その時、非同期処理によりfetchが完了する前に次のコードが実行されるため、データが出力できない(undefainedになる)です。

以下のコードで、非同期処理を制御せずにfetchメソッドを用いてgithubからユーザーIDを取得して、出力してみましょう。ちなみに、外部からメソッドをimportするためESModuleであることをpackage.json内に示しておきましょう。

package.json
{"type": "module"}
import fetch from 'node-fetch';

const getGitUsername = () => {
    const url = 'https://api.github.com/users/hangi4343'

    fetch(url).then(res => res.json())
    .then(json => {
        console.log('非同期処理成功')
        return json.login
    }).catch(error => {
        console.error('非同期処理失敗', error)
        return null
    })
}

const message = 'GitのユーザIDは'
const username = getGitUsername()
console.log(message + username)

GitのユーザIDはundefined
非同期処理成功

const username = getGitUsername()の行でgetGitUsername関数の完了を待たずに、その下のconsole.log(message + username)が実行されるためusernameがundefainedになってしまいます。

fetchの使い方についてはこちらの記事を参考にして下さい。

簡単に解説すると、fetchが返すResponseオブジェクトを最初のthenで受け取り、その中でres.json()をreturnしています。その後、それをまたthen受け取り、メッセージを出力した後にjson.loginがreturnされます。また、この処理内でエラーが起きると、そのエラーをcatchで受け取り、その後の処理を実行します。

このコードが上手く実行されるために、const username = getGitUsername()の非同期処理を行う処理を、完了まで待ってから次のコードを実行することが考えられます。

この非同期処理の完了を待つために、Promiseやasync/awaitが用いられます。

Promiseで非同期処理を制御

それでは、非同期処理を制御していきましょう。

以下の記事を参考にしました。

Promiseについて0から勉強してみた

僕なりのPromise入門

Promiseはresolveとrejectの2つの関数を引数に取ります。

  • resolve : 処理が成功したときのメッセージを表示する関数
  • reject : 処理が失敗したときのメッセージを表示する関数
import fetch from 'node-fetch';

const getGitUsername = () => {
    return new Promise((resolve, reject) => {
        const url = 'https://api.github.com/users/hangi4343'

        fetch(url).then(res => res.json())
            .then(json => {
                console.log('非同期処理成功')
                return resolve(json.login)
            }).catch(error => {
                console.error('非同期処理失敗', error)
                return reject("reject")
            })
    })
}

const message = 'GitのユーザIDは'
getGitUsername().then(username => {
    console.log(message + username)
})

非同期処理成功
GitのユーザIDはhangi4343

非同期処理を制御することができました。

コードについて解説します。

Promiseは、resolveとrejectの2つの引数を持ちます。

getGitUsernameを実行すると、Promiseオブジェクトがreturnされます。

このPromiseオブジェクトに対してthenを用いると、そのthenの引数にPromiseオブジェクトのresolveでreturnされた値が代入されます。

もう少し詳しく解説します。Promiseのコンストラクタの中でresolveが呼ばれると、Promiseはresolveの状態になります。thenはPromiseの状態がresolveになったときに呼ばれるので、結果的にthenの引数にresolveでreturnされた値が代入されることになります。

この場合では、getGitUsername().then(username)=>の部分で、returnで帰ってきたPromiseオブジェクトに対してthenを実行し、resolve(json.login)json.loginがthenの引数であるusernameに代入されます。

今回のコードでは示していませんが、getGitUsername.then.catch(reject => console.log(reject))のようにすると、Promiseのコンストラクタの中でrejectが呼ばれたときに、Promiseがrejectの状態になり、catch内の処理が実行されます(catchはPromiseがrejectの状態のときに呼ばれるため)。

Promiseはresoveとrejectとpendingの三つの状態を持ちます。returnしたPromiseオブジェクトに対してthenを実行すると、Promiseオブジェクトがresolveの状態になるまで待ってからthen以降の処理が実行されます。

また、catchを実行するとPromiseオブジェクトがrejectの状態になるまで待ってからcatch以降の処理が実行されます。

また、このthenやcatchの処理は非同期処理になるため、resolveやrejectが返される前にその次のコードが実行され、resolveやrejectが返ってきて初めて実行されます。

async/awaitを使って非同期処理を制御

今度は、async/awaitを使って非同期処理を制御してみましょう。

こちらの記事を参考にしました。

Promiseが分かれば簡単!async, await

以下のコードです。

import fetch from 'node-fetch';

const getGitUsername = async () => {
    const message = 'GitのユーザーIDは';
    const url = 'https://api.github.com/users/hangi4343'

    const json = await fetch(url)
        .then(res => {

            console.log('非同期処理成功')
            return res.json()
        }).catch(error => {
            console.error('非同期処理失敗', error)
            return null
        })
    console.log(message + json.login)
}
getGitUsername()
console.log('end')

end
非同期処理成功
GitのユーザーIDはhangi4343

それではコードの解説です。

asyncは、関数の前に書きます。このasyncにより、その関数の中でawaitが使えるようになります。

awaitは端的にいうと、非同期処理を同期処理のように書けるようにしてくれるものという風に説明されます。

今回はconst json = await fetch(url)の部分でawaitを使っています。awaitにより、fetch(url)の部分で値がreturnするまで待つことができます。

そのため、値が返ってきたあとにconsole.log(message + json.login)の部分のコードが実行されます。

しかし、awaitはあくまでPromiseを簡単に扱えるようにしたものであり、内部的にはawaitの右側の式を同期的に実行した後、resolveしたpromiseを生成し、awaitの左側および下のコード全てをthenの引数の関数として実行しています。

つまり、この場合においてはconsole.log(message + json.login)の部分がPromiseにおけるthenの後に実行されるものであり、console.log('end')の部分はthenの後の処理ではありません。

つまり、getGitUsername()の実行の完了を待たずに、console.log('end')は実行されます。

終わりに

今回の記事はここまでになります。

お疲れさまでした。

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