20200114のJavaScriptに関する記事は28件です。

document.querySelectorAll('#need_click')[0].click();でid="need_click"の要素をクリックできる【JavaScript】

コード

index.html
<button id="need_click"></button>
click.js
document.querySelectorAll('#need_click')[0].click();

解説

document.querySelectorAll('#need_click')

これで、need_clickというidを持つ要素を取得。
querySelectorAllの中の要素一つを取りたいので、[0]で0番目を取得。

.click();

これがクリックするメソッドなので、先ほど取得したものに対して実行。

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

JavaScriptでif文の条件式に変数だけを記述するやつ

よく忘れるのでメモ

JavaScriptでは 0, "", undefined, null は false になる

以下のようなよくあるif文。存在チェックなどに使われています。
この場合、例えばhogeがundefinedだとif文の中に入ります。

if(!hoge) {
  ・・・
}

JavaScriptでは以下の値はfalseとみなされます。
- 空文字("")
- 数値の0
- NaN
- null
- undefined

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

【Youtube Player API】埋め込む動画を動的に変更する

動作

こんな感じです。
テキストボックスにurlを入力するたびに、埋め込まれたYoutubeの動画が変更されます。
youtube.gif

コード

// ここがiframeに置き換わる
<div id="ytplayer"></div>
<input type="text" id="url" style="width: 600px">

<script>
  // IFrame API Playerを読み込む
  const tag = document.createElement('script');
  tag.src = "https://www.youtube.com/player_api";
  const firstScriptTag = document.getElementsByTagName('script')[0];
  firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

  // APIコードが読み込まれた後に、YouTube Playerを作成する
  let player;
  function onYouTubePlayerAPIReady() {
    player = new YT.Player('ytplayer', { // playerはiframeに置き換えるdivタグのid
      height: '360', // プレイヤーの高さ
      width: '640', // プレイヤーの幅
    });
  }

コードの前半部分の、プレイヤーを作成するところまでは公式のサンプルページとほぼいっしょです。
異なる点は、プレイヤーオブジェクトを作成するときに、動画IDを指定せずに後からキュー関数で読み込んでいるところです。

  const url = document.getElementById('url');
  let videoId;
  // url入力フォームの入力されたイベントを監視
  url.addEventListener('input', () => {
    // urlのv=以降が動画id
    videoId = url.value.split('v=')[1];
    // 正しいurlの形式だったとき
    if (videoId) {
      // &=クエリパラーメターがついていることがあるので取り除く
      const ampersandPosition = videoId.indexOf('&');
      if(ampersandPosition != -1) {
          videoId = videoId.substring(0, ampersandPosition);
      }
    }
    // 指定さらた動画IDのサムネイルを読み込み、動画を再生する準備をする。
    player.cueVideoById({videoId: videoId});
});

</script>

URLから動画IDを取得する方法は、こちらのサイトを参考にさせていただきました。
https://stackoverflow.com/questions/3452546/how-do-i-get-the-youtube-video-id-from-a-url?rq=1

inputイベントはvalue要素が変更されるたびに発生するので、テキストが変更されるたびにURLから動画IDを取得します。

取得した動画IDをcueVideoById関数に渡すことで、プレイヤーに動画を読み込ませることができます。

また、loadVideoById関数は動画IDを渡すことで即座に動画を再生することができます。

ちなみにcueVideoByUrlloadVideoByUrlなど動画IDの代わりにURLを渡すことができる関数もあるのですが、URLの形式がhttp://www.youtube.com/v/VIDEO_ID?version=3完全に適合している必要があります1

そのため、フォームから入力させるには少々使いにくくなっていますね...

その他の例

ラジオボタンから読み込む
radio.gif

<div id="player"></div><br>
好きなVtuberは?
<input type="radio" name="video_id" value="Lv46-MIAS8o">ギバラ
<input type="radio" name="video_id" value="EtKEC-4dW18" >月ノ美兎
<input type="radio" name="video_id" value="5YOeKW0OfPE" >本間ひまわり

<script>
  const tag = document.createElement('script');
  tag.src = "https://www.youtube.com/player_api";
  const firstScriptTag = document.getElementsByTagName('script')[0];
  firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

  let player;
  function onYouTubePlayerAPIReady() {
    player = new YT.Player('player', {
      height: '360',
      width: '640',
    });
  }

  const video_ids = document.getElementsByName('video_id');
  video_ids.forEach(function(video_id) {
    video_id.addEventListener('click', function() {
      const videoId = document.querySelector('input:checked[name=video_id]').value;
      if (videoId){
        player.loadVideoById({videoId: videoId});
      }
    })
});
</script>


  1. 通常のURLの形式は`https://www.youtube.com/watch?v=VIDEO_ID 

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

LitElement x TypeScript x WebpackでWebComponentsベースのアプリを作る

先日公開された Web Developer Roadmap 2020 で新たに追加された WebComponents の勉強も兼ねて
LitElement x TypeScript x Webpack で WebComponentsベースのアプリを作ることにした。

題材

どうせなら使えるものがよいと思い、
モンテカルロ法(ルーレットの必勝法みたいなやつ)の計算をしてくれるアプリを題材にした。

https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_124296_d81ec72e-226b-fc18-cc07-ddbd0c7264dd.png

扱う技術

WebComponents

簡単に言うとオリジナルのHTMLタグを作るための技術の総称。
まだ賛否はあるものの、非常に再利用性の高いコンポーネントを作れること
Web標準の技術であることが特徴。

LitElement

Webコンポーネントを簡単に作るためのライブラリ。
Googleが推進しているPolymer Projectの一つ。

TypeScript と Webpack

静的型付き言語とバンドルツール。
いずれもWeb Developer Roadmapでは学習を推奨されている。

スクリーンショット 2020-01-14 21.50.56.png スクリーンショット 2020-01-14 21.51.30.png

引用:https://github.com/kamranahmedse/developer-roadmap

実装

基本的にはTypeScriptの記法で、LitElementを継承したクラスを作れば良い。

simple-greeting.ts
import { LitElement, html, property, customElement } from 'lit-element';

@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
  @property() name = 'World';

  render() {
    return html`<p>Hello, ${this.name}!</p>`;
  }
}

customElement で指定した文字列がタグ名となる。

@customElement('simple-greeting')
<simple-greeting name="Everyone"></simple-greeting>

ビルド環境

素直に ts-loader を通せばコンパイルすることもできた。
ただしLitElementはES5用ビルドがされていないため(後述)
class構文などに対応していない IE11 などに対応するには
ここからさらにbabelを通す必要があると思われる。

webpack.config.js
module: {
  rules: [
    {
      test: /\.tsx?$/,
      use: [
        {
          loader: 'ts-loader',
          options: {
            configFile: 'tsconfig.json'
          }
        }
      ],
      exclude: /node_modules/
    }
  ]
}
tsconfig.json
{
  "compilerOptions": {
    "module": "es6",
    "target": "es6",
    "sourceMap": true,
    "experimentalDecorators": true,
    "moduleResolution": "node"
  },
  "exclude": ["node_modules"],
  "plugins": [
    {
      "name": "typescript-tslint-plugin",
      "alwaysShowRuleFailuresAsWarnings": true,
      "configFile": "./tslint.json"
    },
    {
      "name": "typescript-lit-html-plugin"
    }
  ]
}

結果

<cc-monte-carlo></cc-monte-carlo>

というタグを設置すると
モンテカルロ法の計算ができるアプリが起動されるようになった。

ScreenRecorderProject30 2.gif

リポジトリ: https://github.com/cc822jp/montecarlo-simulator

特徴

WebComponentsならでは特徴を述べると、
Shadow DOMになっているので外からの影響を全く受けることがない
(iframeタグを埋め込んでいるのと同じイメージ)

つまり外から document.querySelector でDOM操作される心配もないし
クラス名の衝突で不要なスタイルが当たることもない。

「いいねボタン」など、あらゆるWebサイトで使われる(再利用性が求められる)コンポーネントは
WebComponentsで作ることで安全にHTMLとJavaScript, CSSが一体となったタグを提供することができる。

参考にしたもの

参考になった LitElement x TypeScript x Webpack のボイラーテンプレートがいくつかあった。

LitElement-TypeScript-and-Webpack-Example

検索すると最初に出てきたもの。

https://github.com/abenz1267/LitElement-TypeScript-and-Webpack-Example

lit-element-form-example

Vaadinのコミッターが作ったもの。
いま思えばこっちを主に参考にすればよかった..。

https://github.com/marcushellberg/lit-element-form-example

余談: LitElementはES5用のビルドをしていない

LitElementはES5用にビルドされていない。(普通のライブラリはしている)
issueを追ってみると、ES5用にビルドするとパフォーマンスが大幅に低下するし
肥大化、バグの温床にもなるという説明があった。

最後は大御所コミッターが進撃のcloseで終了していた。
https://github.com/Polymer/lit-element/issues/302

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

HTMLImageElementインターフェースについてまとめ

はじめに

JSを勉強している中で、「HTMLImageElementインターフェース」というモノを知り、最初は何を言っているのかさっぱりわからなかったところから少しづつ理解したこととまとめてみます。

HTMLImageElementとは

HTMLImageElement インターフェースは、 タグに相当します。
エレメントの一種です。
画像を読み込んで表示するための機能がまとまっています。

つまり、JS上で <img>タグ に相当するモノ(オブジェクト)を作成し、htmlに反映できるだと理解しました。

こちらのサイトがわかりやすかったので引用させていただきました。

https://hakuhin.jp/js/image.html

どんな感じに使うのか

使い方:Image() コンストラクタを使用する

コンストラクタを使用
// HTMLImageElement オブジェクトを作成する
var image = new Image();

簡単にボタンクリックで画像切り替え処理を作成しました。

See the Pen LYEBEGd by RS (@shimamar) on CodePen.

まとめ

簡単な処理を作成してみましたが、まだまだ理解仕切れてないことが多いなと痛感しました。
もっと画像をJSで扱う動作を勉強してもっと臨機応変なコードを描けるようにしたいと改めて思いました!
勉強をしていく上でもっと良い方法や内容があれば更新していきます。

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

最新のJavaScript: ES2016からES2019までの知るべき全て

訳者前書き

本記事は、InspiredWebDevの原文記事(英語)について、自身の理解を深める為に日本語翻訳したものです。

はじめに

JavaScriptは絶えず進化している言語であり、過去数年で多くの新機能がEMCAScriptの仕様に追加されました。

この記事は、Alberto氏の著書「最新のJavaScriptの完全ガイド(Complete Guide to Modern JavaScript)」の抜粋です。
同書では、ES2016、ES2017、ES2018、ES2019の新規追加要素について説明しています。

記事の最後に、すべてを要約したチートシートをダウンロードするためのリンクがあります。

1. ES2016の新機能

ES2016で導入された2つの新機能について説明します。

2. Array.prototype.includes()

配列のincludes()メソッドは、自身の配列に特定の要素が含まれている場合はtrueを、そうでない場合はfalseを返します。

let array = [1,2,4,5];

array.includes(2);
// true
array.includes(3);
// false

includes() と 要素検索開始インデックス (fromIndex) の組み合わせ

.includes()に対して、要素の検索を開始するためのインデックスを提供できます。
デフォルト(インデックスを指定しない場合)の値は0です。また、負の値を渡すこともできます。
※訳者注:負の値を渡した場合、配列要素数+負の値をインデックスに指定した扱いとなります。

1番目の引数は検索する要素、2番目の引数は検索を開始するインデックスです。

let array = [1,3,5,7,9,11]; // 訳者注:配列のインデックスは0番目から始まります

array.includes(3,1);
// 配列の1番目から数字の3を探します
// true
array.includes(5,4);
// 配列の4番目から数字の5を探します
// false
array.includes(1,-1);
// 配列の(配列要素数-1)番目から数字の1を探します
// false
array.includes(11,-3);
// 配列の(配列要素数-3)番目から数字の11を探します
// true

array.includes(5,4);falseを返します。
配列は数字の5を含みますが、同要素より後ろの4番目の要素から探し始めた為です。

array.includes(1,-1);falseを返します。
配列は数字の1を含みますが、同要素より後ろの(配列要素数6 - 1 =) 5番目の要素から探し始めた為です。

array.includes(11,-3);trueを返します。
配列は数字の11を含んでおり、同要素より手前の(配列要素数6 - 3 =) 3番目の要素から探し始めた為です。

3. 指数演算子(べき乗)

ES2016より前の仕様では、指数計算を次のように記述していました。

Math.pow(2,2);
// 4
Math.pow(2,3);
// 8

ES2016で追加された新しい指数演算子を使うと、次のように記述できます。

2**2;
// 4
2**3;
// 8

指数演算子は、次の例のように複数の操作を組み合わせる場合に、非常に役立ちます。

2**2**2;
// 16
Math.pow(Math.pow(2,2),2);
// 16

上記のようなケースでMath.pow()を使用するには、同関数を継続的に連結する必要があり、非常に長く複雑になります。
指数演算子は、同じことをより簡潔に行う方法を提供します。

4. ES2017の新機能

ES2017では数多くの素晴らしい新機能が導入されました。
ここでは、それらの機能について説明します。

5. 文字列のパディング(.padStart() および .padEnd())

文字列の末尾(.padEnd())または先頭(.padStart())に余白を追加できます。

"hello".padStart(6);
// " hello"
"hello".padEnd(6);
// "hello "

余白が6つ分必要と指定したのに、両方のケースで1つの空白しか取得できなかったのは何故でしょうか?
それは、padStartpadEndが、元の文字列で埋めきれない空きスペースを埋める為です。
この例では、「hello」は5文字で、パディングは6文字ですので、空きスペースは1文字分しか残っていません。

こちらの例を見てください。

"hi".padStart(10);
// 10 - 2 = 8 empty spaces
// "        hi"
"welcome".padStart(10);
// 10 - 6 = 4 empty spaces
// "   welcome"

padStartを用いて右揃えする

padStartは何かを右揃えしたい場合に使用できます。

const strings = ["short", "medium length", "very long string"];

const longestString = strings.sort(str => str.length).map(str => str.length)[0];

strings.forEach(str => console.log(str.padStart(longestString)));

// very long string
//    medium length
//            short

上記の例では、予め用意した配列内から、最も長い文字列の要素を取得し、その文字列の長さを測定しました。
次に配列の各要素に対してpadStartを測定した文字長で適用することで、すべての文字列を綺麗に右揃えにできました。

カスタム値をパディングに追加する

パディングとして空白を追加するだけでなく、文字列と数字の両方を渡すことができます。

"hello".padEnd(13," Alberto");
// "hello Alberto"
"1".padStart(3,0);
// "001"
"99".padStart(3,0);
// "099"

6. Object.entries() および Object.values()

最初にオブジェクトを作成しましょう。

const family = {
  father: "Jonathan Kent",
  mother: "Martha Kent",
  son: "Clark Kent",
}

JavaScriptの以前のバージョンでは、次のようにオブジェクト内の値にアクセスしていました。

Object.keys(family);
// ["father", "mother", "son"]
family.father;
"Jonathan Kent"

Object.keys()は、値にアクセスする為に必要なキーのみの配列を返しました。

オブジェクトにアクセスする方法は更に2つあります。

Object.values(family);
// ["Jonathan Kent", "Martha Kent", "Clark Kent"]

Object.entries(family);
// ["father", "Jonathan Kent"]
// ["mother", "Martha Kent"]
// ["son", "Clark Kent"]

Object.values()は値のみの配列を返し、Object.entries()はキーと値の両方を含む配列の配列を返します。

7. Object.getOwnPropertyDescriptors()

このメソッドは、オブジェクトのすべてのプロパティ記述子を返します。
返すことができる属性はvalue, writable, get, set, configurable および enumerableです。

const myObj = {
  name: "Alberto",
  age: 25,
  greet() {
    console.log("hello");
  },
}
Object.getOwnPropertyDescriptors(myObj);
// age:{value: 25, writable: true, enumerable: true, configurable: true}

// greet:{value: ƒ, writable: true, enumerable: true, configurable: true}

// name:{value: "Alberto", writable: true, enumerable: true, configurable: true}

8. パラメータ列挙時における末尾カンマの許容

これは、構文の小さな変更です。
オブジェクトを書き込む時に、最後のパラメータであるかどうかに依らず、各パラメータの末尾にカンマをつけることができるようになります。

// こうだったのが
const object = {
  prop1: "prop",
  prop2: "propop"
}

// こうなる
const object = {
  prop1: "prop",
  prop2: "propop",
}

2番目のプロパティの最後にカンマを記述したことに注目してください。
入れなくてもエラーは発生しませんが、同僚やチームメンバーの生活を楽にする為に従うことをおすすめします。

// 私が書いたコードに・・・
const object = {
  prop1: "prop",
  prop2: "propop"
}

// 同僚がコードを更新し、新しいプロパティ prop3 を追加しますが・・・
const object = {
  prop1: "prop",
  prop2: "propop" // <- 末尾カンマがないよ!(構文エラー)
  prop3: "propopop"
}
// 直上のパラメータ prop2 の末尾にコンマを付け忘れた為、エラーになってしまいました

9. 共有メモリと Atomics(不可分操作)

MDNより:

不可分操作
メモリーが共有されている場合、複数のスレッドがメモリー内の同じデータを読み書きできます。
アトミック演算では、予測される値の書き込みと読み込みを保証するため、次の演算が開始される前に現在の演算が完了し、その演算が割り込まれないようにします。

Atomicsはコンストラクタではなく、そのプロパティとメソッドは(Mathと同様に)すべて静的です。
そのため、新しい演算子で使用したり、Atomicsオブジェクトを関数として呼び出すことはできません。

メソッドの例は次のとおりです。

  • 加算 add / 減算 sub
  • 論理積 and / 論理和 or / 排他的論理和 xor
  • 読み出し load / 格納 store

Atomicsメソッドの例をいくつか見てみましょう。

10. Atomics.add(), Atomics.sub(), Atomics.load() および Atomics.store()

Atomics.add()は、配列、インデックス、値の3つの引数を取ります。
インデックスで示された配列要素へ値を加算し、返り値として加算前の配列要素の値を返します。

// 共有メモリー上に配列バッファを用意します
// 訳者注: Spectre 型脆弱性から保護する為、SharedArrayBufferは主要ブラウザで無効化されています(Chromeについては一部例外あり)
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer
const buffer = new SharedArrayBuffer(16);
const uint8 = new Uint8Array(buffer);

// 配列の先頭要素を追加します
uint8[0] = 10;

console.log(Atomics.add(uint8, 0, 5));
// 10

// 10 + 5 = 15
console.log(uint8[0])
// 15
console.log(Atomics.load(uint8,0));
// 15

ご覧のとおり、Atomics.add()は、ターゲットの配列位置で以前の値を返します。
その後、再度uint8[0]の内容を問い合わせると、加算演算が実行され、15が得られたことがわかります。

Atomics.loadに配列とインデックスの2つの引数を渡すことで、配列から特定の要素の値を取得できます。

Atomics.sub()Atomics.add()と同じ要領で機能し、値を減算します。

// 共有メモリーに配列バッファを用意します
const buffer = new SharedArrayBuffer(16);
const uint8 = new Uint8Array(buffer);

// 配列の先頭要素を追加します
uint8[0] = 10;

console.log(Atomics.sub(uint8, 0, 5));
// 10

// 10 - 5 = 5
console.log(uint8[0])
// 5
console.log(Atomics.store(uint8,0,3));
// 3
console.log(Atomics.load(uint8,0));
// 3

ここでは、uint8[0]の値10から5を減算するためにAtomics.sub()を使用しています。
Atomics.add()と同様に、メソッドはその配列要素の前の値(この場合は10)を返します。

次にAtomics.store()を、配列の特定要素(この場合はuint8[0])に、特定の値(この場合は3)を格納するために使用しています。
Atomics.store()は渡された値(この場合は3)を返します。

最後にAtomics.load()で同要素の値を読み出すと、5ではなく3が取得されることがわかります。

11. Atomics.and(), Atomics.or() および Atomics.xor()

これらの3つのメソッドはすべて、配列の特定要素の値に対して、ビット単位のAND、OR、XORの演算を実行します。
ウィキペディアのこちらのリンクでビット演算の詳細を読むことができます。

12. ES2017における Async と Await

ES2017では、「async / await」と呼ばれる Promise を操作する新しい方法を導入しました。

13. Promise の復習

この新しい構文に飛び込む前に、通常のPromiseを書く方法を簡単に確認しましょう。

// githubからユーザーデータを取得します
fetch('api.github.com/user/AlbertoMontalesi').then( res => {
  // 得られたデータをjson形式で返します
  return res.json();
}).then(res => {
  // もし上手くいったら、取得したデータを出力します
  console.log(res);
}).catch( err => {
  // どこかでダメだったら、エラーを出力します
  console.log(err);
})

これは、GitHubからユーザー情報を取得してコンソールに出力するという非常に単純な約束(Promise)です。

別の例を見てみましょう。

// 指定された時間歩きます(必ず歩くとはいってない)
function walk(amount) {
  return new Promise((resolve,reject) => {
    // 指定された時間が500ミリ秒未満なら、歩くのを却下します
    if (amount < 500) {
      reject ("値が小さすぎます");
    }
    // 指定された時間(ミリ秒)待ってから、指定されたメッセージを出力します
    setTimeout(() => resolve(`あなたは${amount}ミリ秒、歩きました`),amount);
  });
}

walk(1000).then(res => { // まずは1000ミリ秒歩いてみる
  console.log(res);
  return walk(500); // 上手くいったら、更に500ミリ秒歩く
}).then(res => {
  console.log(res);
  return walk(700); // 上手くいったら、更に700ミリ秒歩く
}).then(res => {
  console.log(res);
  return walk(800); // 上手くいったら、更に800ミリ秒歩く
}).then(res => {
  console.log(res);
  return walk(100); // 上手くいったら、更に100ミリ秒歩く
}).then(res => {
  console.log(res);
  return walk(400); // 上手くいったら、更に400ミリ秒歩く
}).then(res => {
  console.log(res);
  return walk(600); // 上手くいったら、更に600ミリ秒歩く
});

// あなたは1000ミリ秒、歩きました
// あなたは500ミリ秒、歩きました
// あなたは700ミリ秒、歩きました
// あなたは800ミリ秒、歩きました
// uncaught exception: 値が小さすぎます

それでは、新しい async / await 構文で上記のPromiseを書き換える方法を見てみましょう。

14. Async と Await

先ほどの例を、新しい構文で書き換えるとこうなります。

function walk(amount) {
  return new Promise((resolve,reject) => {
    if (amount < 500) {
      reject ("値が小さすぎます");
    }
    setTimeout(() => resolve(`あなたは${amount}ミリ秒、歩きました`),amount);
  });
}

// `async`キーワードを用いて非同期な関数を作ります
async function go() {
  // `await`キーワードを用いて非同期に応答を待ちます
  const res = await walk(500);
  console.log(res);
  const res2 = await walk(900);
  console.log(res2);
  const res3 = await walk(600);
  console.log(res3);
  const res4 = await walk(700);
  console.log(res4);
  const res5 = await walk(400);
  console.log(res5);
  console.log("finished");
}

go();

// あなたは500ミリ秒、歩きました
// あなたは900ミリ秒、歩きました
// あなたは600ミリ秒、歩きました
// あなたは700ミリ秒、歩きました
// uncaught exception: 値が小さすぎます

今やったことを分析しましょう。

  • 非同期関数を作成するには、その前にasyncキーワードを配置する必要があります
  • キーワードはJavaScriptが常にpromiseを返すように指示します
  • return を指定すると、promiseにラップされた値が返されます
  • awaitキーワードは唯一、非同期関数の内部でのみ動作します
  • その名前が示すように、awaitはpromiseが結果を返すまで待つようにJavaScriptに指示します

それでは、async付き関数の外でawaitを使用しようとするとどうなるか見てみましょう。

// awaitを普通の関数の中で使用してみます
function func() {
  let promise = Promise.resolve(1);
  let result = await promise; 
}
func();
// SyntaxError: await is only valid in async functions and async generators

// awaitを最上位空間で使用してみます
let response = Promise.resolve("hi");
let result = await response;
// SyntaxError: await is only valid in async functions and async generators

覚えておきましょう: awaitasyncの付いた非同期関数の中でしか使えません。

15. Promiseのエラーハンドリング

通常のPromiseでは、Promiseによって返される最終的なエラーをキャッチする為に.catch()を使用します。
ここでは、それほどの違いはありません。

async function asyncFunc() {

  try {
    let response = await fetch('http:your-url');
  } catch(err) {
    console.log(err);
  }
}

asyncFunc();
// TypeError: failed to fetch

エラーを取得する為にtry...catchを使いましたが、それを用いずとも、次のようにエラーをキャッチできます。

async function asyncFunc(){
  let response = await fetch('http:your-url');
}
asyncFunc();
// Uncaught (in promise) TypeError: Failed to fetch

asyncFunc().catch(console.log);
// TypeError: Failed to fetch

16. ES2018の新機能

ES2018で導入されたものを見てみましょう。

17. オブジェクトの rest / spread

ES6(ES2015)で以下のようなコードが書けるようになったことを覚えていますか?

const veggie = ["トマト","きゅうり",""];
const meat = ["豚肉","牛肉","鶏肉"];

const menu = [...veggie, "パスタ", ...meat];
console.log(menu);
// Array [ "トマト", "きゅうり", "豆", "パスタ", "豚肉", "牛肉", "鶏肉" ]

オブジェクトに対しても、 rest / spread 構文を使用できるようになりました。
次のコードを見てみましょう。

let myObj = {
  a:1,
  b:3,
  c:5,
  d:8,
}

// rest 演算子を使って、オブジェクトに残っている他の全てを取得します
let { a, b, ...z } = myObj;
console.log(a);     // 1
console.log(b);     // 3
console.log(z);     // {c: 5, d: 8}

// spread 構文を使って、オブジェクトのクローン(複製)を取得します
let clone = { ...myObj };
console.log(clone);
// {a: 1, b: 3, c: 5, d: 8}
myObj.e = 15;
console.log(clone)
// {a: 1, b: 3, c: 5, d: 8}
console.log(myObj)
// {a: 1, b: 3, c: 5, d: 8, e: 15}

Objectにspread演算子を使用すると、元のObjectを変更したときにも変更されないようなクローンを簡単に作成できます。
これは、配列について説明したときに見たものと同じです。

18. 非同期な反復(Asynch Iteration)

Async Iterationを使用すると、データに対して非同期に反復処理が行えます。

ドキュメントより:

非同期イテレータは、そのnext()メソッドが{ value, done }ペアの Promise を返すことを除いて、通常のイテレータによく似ています。

for-await-ofループを使用すると、残りが1つではない反復可能オブジェクトをPromiseに変換します。

const iterables = [1,2,3];

async function test() {
    for await (const value of iterables) {
        console.log(value);
    }
}
test();
// 1
// 2
// 3

実行中に、[Symbol.asyncIterator]()メソッドを使用してデータソースから非同期イテレータが作成されます。
シーケンスないの次の値にアクセスするたびに、イテレータメソッドから返されるPromiseを暗黙的に待機します。

19. Promise.prototype.finally()

Promiseの終了時に、コールバックを呼び出すことができます。

const myPromise = new Promise((resolve,reject) => {
  resolve();
})

myPromise
  .then( () => {
    console.log('まだ作業中です');
  })
  .catch( () => {
    console.log('エラーになっちゃいました');
  })
  .finally(()=> {
    console.log('作業完了!');
  })

.finally()もPromiseを返すので、より多くのチェーンを辿ってそれをキャッチできますが、それらのPromiseはチェーンされたPromiseに基づいて満たされます。

const myPromise = new Promise((resolve,reject) => {
  resolve();
})

myPromise
  .then( () => {
    console.log('まだ作業中です');
    return 'まだ作業中です';
  })
  .finally(()=> {
    console.log('作業完了!');
    return '作業完了!';
  })
  .then( res => {
    console.log(res);
  })
// まだ作業中です
// 作業完了!
// まだ作業中です

ご覧のとおり、finallyではなく、最初のthenによって作成されたPromiseの返り値が、finallyより後のthenにチェーンされます。

20. 正規表現の機能群

正規表現に関する4つの新しい機能が、新バージョンのECMAScriptに追加されました。
それらは以下のとおりです:

正規表現のsフラグ (dotAll)

ECMAScriptの正規表現が新たにsフラグを導入したことで、.は行終端記号を含む任意の文字に一致するようになります。

/foo.bar/s.test('foo\nbar');
// true

正規表現の名前付きキャプチャグループ

ドキュメントより:

番号付きのキャプチャグループを使用すると、正規表現が一致する文字列の特定の部分を参照できます。
各キャプチャグループには一意の番号が割り当てられ、その番号を使用して参照できますが、これにより正規表現の把握とリファクタリングが難しくなります。
たとえば、日付に一致する/(\d{4})-(\d{2})-(\d{2})/が与えられた場合、周囲のコードを調べずに、月に対応するグループと日に対応するグループを確認することはできません。
また、月と日の順序を入れ替えたい場合は、グループ参照も更新する必要があります。

キャプチャグループには、(?<name>...)構文を使用して任意の識別子名をnameに付けることができます。
日付の正規表現は、/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/uのように記述できます。
各識別子名は一意で、ECMAScriptの識別子名の文法に従う必要があります。
名前付きグループには、正規表現処理結果のgroupsプロパティにおける、識別子名のプロパティとしてアクセスできます。
名前のないグループと同様に、グループへの番号付き参照も作成されます。 例えば次のように:

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';

// result[0] === '2015-01-02';
// result[1] === '2015';
// result[2] === '01';
// result[3] === '02';

let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
console.log(`one: ${one}, two: ${two}`); 
// one: foo, two: bar

正規表現の後読みアサーション

ドキュメントより:

後読みアサーションを使用すると、ドル記号をキャプチャせずにドル金額を一致させるといった感じで、パターンの前に別のパターンがあるかどうかを確認できます。
肯定的な後読みアサーションは(?<=...)として示され、アサーションに続くパターンの前に含まれるパターンを確実にします。
たとえば、ドル記号をキャプチャせずにドル金額を一致させたい場合は、/(?<=$)\d+(\.\d*)?/を使用して、'$10.53'と一致し、'10.53'を返します。
ただし、これは'€10.53'とは一致しません。
ネガティブな後読みアサーションは(?<!...)として示されますが、一方で、内部のパターンがアサーションに続くパターンの前にないことを確認してください。
たとえば、/(?<!$)\d+(?:\.\d*)/'$10.53'とは一致しませんが、'€10.53'とは一致します。

正規表現のUnicodeプロパティエスケープ

ドキュメントより:

これにより、\p{…}および\P{…}という形式のUnicodeプロパティエスケープが追加されます。
Unicodeプロパティエスケープは、uフラグが設定された正規表現で使用可能な、新しいタイプのエスケープシーケンスです。
この機能を使用すると、次のように記述できます。

const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π');
// true

21. テンプレートリテラルの制限の解除

タグ付きテンプレートリテラルを使用すると、エスケープシーケンスの制限がなくなります。

詳細はこちらをご覧ください。

22. ES2019の新機能

それではECMAScriptの最新バージョンであるES2019に含まれるものを見ていきましょう。

23. Array.prototype.flat() / Array.prototype.flatMap()

Array.prototype.flat()は、指定した深さまで配列を再帰的に平坦化します。
depth引数が指定されていない場合、1がデフォルト値です。
Infinityを使用すれば、すべてのネストされた配列をフラット化できます。

const letters = ['a', 'b', ['c', 'd', ['e', 'f']]];
// デフォルトの深さは1
letters.flat();
// ['a', 'b', 'c', 'd', ['e', 'f']]

// 深さ2を指定
letters.flat(2);
// ['a', 'b', 'c', 'd', 'e', 'f']

// これは、深さ1を指定して2回平坦化した場合と同じです
letters.flat().flat();
// ['a', 'b', 'c', 'd', 'e', 'f']

// ネストされた配列を含まない配列になるまで再帰平坦化を行います
letters.flat(Infinity)
// ['a', 'b', 'c', 'd', 'e', 'f']

Array.prototype.flatMap()は'depth'引数を処理する方法に関しては前のものと同じです。
しかし、配列を単純に平坦化する代わりに、flatMap()を使用してマッピングし、新しい配列を処理結果として返すこともできます。

let greeting = ["Greetings from", " ", "Vietnam"];

// まずは通常の`map()`関数を使用してみましょう
greeting.map(x => x.split(" "));
// ["Greetings", "from"]
// ["", ""]
// ["Vietnam"]

greeting.flatMap(x => x.split(" "))
// ["Greetings", "from", "", "", "Vietnam"]

ご覧のとおり、.map()を使用すると、複数階層の配列が得られます。
この配列を平坦化する為の手段として.flatMap()を使用できます。

24. Object.fromEntries()

Object.fromEntries()はkey-valueペアの配列をオブジェクトに変換します。

const keyValueArray = [
  ['key1', 'value1'],
  ['key2', 'value2']
]

const obj = Object.fromEntries(keyValueArray)
// {key1: "value1", key2: "value2"}

Object.fromEntries()の引数として、反復可能プロトコルを実装する配列、マップ、または他のオブジェクトのいずれでも、反復可能オブジェクトを渡すことができます。

反復プロトコルの詳細についてはこちらをお読みください:
https://developer.mozilla.org/ja-JP/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol

25. String.prototype.trimStart() / .trimEnd()

String.prototype.trimStart()は文字列の先頭から空白を削除します。
String.prototype.trimEnd()は末尾から空白を削除します。

let str = "    this string has a lot of whitespace   ";

str.length;
// 42

str = str.trimStart();
// "this string has a lot of whitespace   "
str.length;
// 38

str = str.trimEnd();
// "this string has a lot of whitespace"
str.length;
// 35

.trimLeft().trimStart()のエイリアスとして使用することができます。
.trimRight().trimEnd()のエイリアスとして使用することができます。

26. キャッチバインディングの補完(例外変数の省略)

ES2019より前は、常にcatch句に例外変数を含める必要がありました。
ES2019では、省略できます。

// 以前
try {
   ...
} catch(error) { // 例外変数の明記が必要
   ...
}

// ES2019
try {
   ...
} catch { // 例外変数を省略可能
   ...
}

これは、エラーを無視する場合に便利です。
この使用例の詳細なリストについては、こちらの記事を強くお勧めします。
http://2ality.com/2017/08/optional-catch-binding.html

27. Function​.prototype​.toString()

.toString()メソッドは、関数のソースコードを表す文字列を返します。

function sum(a, b) {
  return a + b;
}

console.log(sum.toString());
// function sum(a, b) {
//    return a + b;
//  }

コメントも含まれています。

function sum(a, b) {
  // 合計するよ
  return a + b;
}

console.log(sum.toString());
// function sum(a, b) {
//   // 合計するよ
//   return a + b;
// }

28. Symbol.prototype.description

.descriptionは、Symbolオブジェクトの任意の説明を返します。

const me = Symbol("Alberto");
me.description;
// "Alberto"

me.toString()
//  "Symbol(Alberto)"

29. チートシートをダウンロードする

翻訳元記事のチートシートはこのリンクからダウンロードできます。


最後までお読みいただきありがとうございます。
もし翻訳文章や内容についてお気づきの点があれば、コメント等でご連絡いただけると幸いです。

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

AWS APIGateway/LambdaとJavascriptで簡易問い合わせサイトをつくる

概要

問い合わせフォーム(javascript) ⇒ APIGateway ⇒ Lambda(Node.js) ⇒ Lambda(Node.js) の流れで簡単な問い合わせサイトを作ります。
contact_form.png
一応レスポンシブにします。
contact_form2.png
バリデーションもあります。
contact_form3.png
contact_form4.png

index.htmlのコーディング

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" />
  <link rel="stylesheet" href="style.css" />
  <title>問い合わせ</title>
</head>

<body>
  <div class="header">
    <a href="#" class="logo">Hoge Hoge Company</a>
    <a class="active" href="#">Home</a>
  </div>

  <main class="main-container">
    <h2>問い合わせフォーム</h2>
    <div class="container">
      <form id="form">
        <div class="row">
          <div class="col-20">
            <label for="name">名前</label>
          </div>
          <div class="col-80">
            <input type="text" id="name" name="name" placeholder="Your name.." />
          </div>
        </div>
        <div id="name-alert" class="alert-message" hidden>入力値が不正です。</div>
        <div class="row">
          <div class="col-20">
            <label for="email">E-Mail</label>
          </div>
          <div class="col-80">
            <input type="email" id="email" name="email" placeholder="Your E-Mail.." />
          </div>
        </div>
        <div id="email-alert" class="alert-message" hidden>入力値が不正です。</div>
        <div class="row">
          <div class="col-20">
            <label for="dept">所属</label>
          </div>
          <div class="col-80">
            <input type="text" id="dept" name="dept" placeholder="Ex: Example Co., Ltd." />
          </div>
        </div>
        <div id="dept-alert" class="alert-message" hidden>入力値が不正です。</div>
        <div class="row">
          <div class="col-20">
            <label for="body">内容</label>
          </div>
          <div class="col-80">
            <textarea id="body" name="body" placeholder="お気軽にお問い合わせください。" style="height:200px"></textarea>
          </div>
        </div>
        <div id="body-alert" class="alert-message" hidden>入力値が不正です。</div>
        <div class="row">
          <button id="submitBtn">送信</button>
        </div>
      </form>
    </div>

    <!-- The Modal -->
    <div id="myModal" class="modal">
      <!-- Modal content -->
      <div id="modal-content">
        <span class="close">&times;</span>
        <p id="result"></p>
        <p id="detail" style="font-size: small;"></p>
      </div>
    </div>
  </main>
  <script src="main.js" defer></script>
</body>
</html>

CSSのコーディング

style.css
* {
  margin: 0;
  padding: 0;
  font-family: "Hiragino Kaku Gothic Pro", "ヒラギノ角ゴ Pro W3", "メイリオ",
    Meiryo, "MS Pゴシック", sans-serif;
}

/* Style the header with a grey background and some padding */
.header {
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  justify-content: space-between;
  background-color: #000000;
  padding: 20px 10px;
}

/* Style the header links */
.header a {
  color: #f2f2f2;
  text-align: center;
  padding: 12px;
  text-decoration: none;
  font-size: 18px;
  line-height: 25px;
  border-radius: 4px;
}

/* Style the logo link (notice that we set the same value of line-height and font-size to prevent the header to increase when the font gets bigger */
.header a.logo {
  font-size: 25px;
  font-weight: bold;
}

/* Change the background color on mouse-over */
.header a:hover {
  background-color: #ddd;
  color: black;
}

/* Style the active/current link*/
.header a.active {
  background-color: dodgerblue;
  color: white;
}

/* Add media queries for responsiveness - when the screen is 500px wide or less, stack the links on top of each other */
@media screen and (max-width: 500px) {
  .header {
    justify-content: center;
  }
  .header a {
    padding: 12px;
  }
}

.main-container {
  width: 90%;
  margin: auto;
}
.main-container h2 {
  margin-top: 12px;
}

/* Style inputs, select elements and textareas */
input,
textarea {
  width: 100%;
  padding: 12px;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
  resize: vertical;
}

.alert-red {
  border: #ff4500 2px solid;
}

.alert-green {
  border: #00ff7f 2px solid;
}

.alert-message {
  color: #ff4500;
  font-size: small;
  text-align: end;
  margin-bottom: 10px;
}

/* Style the label to display next to the inputs */
label {
  padding: 12px 12px 12px 0;
  display: inline-block;
}

/* Style the submit button */
#submitBtn {
  background-color: #4caf50;
  color: white;
  padding: 10px 18px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 15px;
  float: right;
}

/* Style the container */
.container {
  border-radius: 5px;
  background-color: #f2f2f2;
  padding: 20px;
}

/* Floating column for labels: 25% width */
.col-20 {
  float: left;
  width: 20%;
  margin-top: 6px;
}

/* Floating column for inputs: 75% width */
.col-80 {
  float: left;
  width: 80%;
  margin-top: 6px;
}

/* Clear floats after the columns */
.row:after {
  content: "";
  display: table;
  clear: both;
}

/* Responsive layout - when the screen is less than 600px wide, make the two columns stack on top of each other instead of next to each other */
@media screen and (max-width: 600px) {
  .col-20,
  .col-80,
  #submitBtn {
    width: 100%;
    margin-top: 0;
  }
}

/* The Modal (background) */
.modal {
  display: none; /* Hidden by default */
  position: fixed; /* Stay in place */
  z-index: 1; /* Sit on top */
  left: 0;
  top: 0;
  width: 100%; /* Full width */
  height: 100%; /* Full height */
  overflow: auto; /* Enable scroll if needed */
  background-color: rgb(0, 0, 0); /* Fallback color */
  background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
}

/* Modal Content/Box */
#modal-content {
  background-color: #fefefe;
  margin: 15% auto; /* 15% from the top and centered */
  padding: 20px;
  width: 80%; /* Could be more or less, depending on screen size */
}

.request-loading {
  border: 1px solid #888;
}

.request-success {
  border: 1px solid #00ff7f;
}

.request-fail {
  border: 1px solid #ff4500;
}

/* The Close Button */
.close {
  color: #aaa;
  float: right;
  font-size: 28px;
  font-weight: bold;
}

.close:hover,
.close:focus {
  color: black;
  text-decoration: none;
  cursor: pointer;
}

javascriptのコーディング

変数URLはAPIGatewayで発行してから実装します。

main.js
const URL = 'https://'; // APIGatewayで作成したURL

const LOADING = '処理中...';
const RESULT_OK = 'リクエストを受け付けました。';
const RESULT_OK_DETAIL =
  "正常に処理が完了すると 'support@hogehoge.com' からメールが配信されます。";
const RESULT_NG = 'リクエストの受付に失敗しました';
const RESULT_NG_DETAIL =
  '大変申し訳ありません。担当者へ直接お問い合わせください。';
const DOMAIN = '@sample.com';
document.getElementById('email').value = DOMAIN;

(() => {
  const modal = document.getElementById('myModal');
  const modalContent = document.getElementById('modal-content');
  const sendBtn = document.getElementById('submitBtn');
  const span = document.getElementsByClassName('close')[0];
  const result = document.getElementById('result');
  const form = document.getElementById('form');
  const nameAlert = document.getElementById('name-alert');
  const emailAlert = document.getElementById('email-alert');
  const deptAlert = document.getElementById('dept-alert');
  const bodyAlert = document.getElementById('body-alert');
  const resultDetail = document.getElementById('detail');

  const inputValueClear = () => {
    form.name.value = '';
    form.name.setAttribute('class', '');
    form.email.value = DOMAIN;
    form.email.setAttribute('class', '');
    form.dept.value = '';
    form.dept.setAttribute('class', '');
    form.body.value = '';
    form.body.setAttribute('class', '');
    result.innerHTML = '';
  };

  // When the user clicks on <span> (x), close the modal
  span.onclick = () => {
    if (result.innerHTML !== LOADING) {
      modal.style.display = 'none';
      if (result.innerHTML === RESULT_OK) inputValueClear();
    }
  };

  // When the user clicks anywhere outside of the modal, close it
  window.onclick = event => {
    if (event.target === modal) {
      if (result.innerHTML !== LOADING) {
        modal.style.display = 'none';
        if (result.innerHTML === RESULT_OK) inputValueClear();
      }
    }
  };

  sendBtn.addEventListener('click', async event => {
    event.preventDefault();
    const name = form.name.value.trim();
    form.name.value = name;
    const email = form.email.value.trim();
    form.email.value = email;
    const dept = form.dept.value.trim();
    form.dept.value = dept;
    const body = form.body.value.trim();
    form.body.value = body;

    // validation check
    if (validation(name, email, dept, body)) return;

    result.innerHTML = LOADING;
    modalContent.setAttribute('class', 'request-loading');
    modal.style.display = 'block';
    const jsonData = JSON.stringify({ name, email, dept, body });

    try {
      const res = await fetch(URL, {
        method: 'POST',
        mode: 'cors',
        cache: 'no-cache',
        body: jsonData,
        headers: {
          'Content-Type': 'application/json; charset=utf-8'
        }
      });
      console.log('Response!!', res.status);
      modalContent.setAttribute('class', 'request-success');
      result.innerHTML = RESULT_OK;
      resultDetail.innerHTML = RESULT_OK_DETAIL;
    } catch (error) {
      console.log(error);
      modalContent.setAttribute('class', 'request-fail');
      result.innerHTML = RESULT_NG;
      resultDetail.innerHTML = RESULT_NG_DETAIL;
    }
  });

  const validation = (name, email, dept, body) => {
    let validationResult = false;
    if (!name.length) {
      form.name.setAttribute('class', 'alert-red');
      nameAlert.hidden = false;
      validationResult = true;
    } else {
      form.name.setAttribute('class', 'alert-green');
      nameAlert.hidden = true;
    }
    if (!email.length || !/^[^@]+@sample.com$/.test(email)) {
      form.email.setAttribute('class', 'alert-red');
      emailAlert.hidden = false;
      validationResult = true;
    } else {
      form.email.setAttribute('class', 'alert-green');
      emailAlert.hidden = true;
    }
    if (!dept.length) {
      form.dept.setAttribute('class', 'alert-red');
      deptAlert.hidden = false;
      validationResult = true;
    } else {
      form.dept.setAttribute('class', 'alert-green');
      deptAlert.hidden = true;
    }
    if (!body.length) {
      form.body.setAttribute('class', 'alert-red');
      bodyAlert.hidden = false;
      validationResult = true;
    } else {
      form.body.setAttribute('class', 'alert-green');
      bodyAlert.hidden = true;
    }
    return validationResult;
  };

  form.name.addEventListener('input', event => {
    const name = event.target.value.trim();
    if (!name) {
      event.target.setAttribute('class', 'alert-red');
      nameAlert.hidden = false;
    } else {
      event.target.setAttribute('class', 'alert-green');
      nameAlert.hidden = true;
    }
  });

  form.email.addEventListener('input', event => {
    const email = event.target.value.trim();
    event.target.value = email;
    if (!email || !/^[^@]+@sample.com$/.test(email)) {
      event.target.setAttribute('class', 'alert-red');
      emailAlert.hidden = false;
    } else {
      event.target.setAttribute('class', 'alert-green');
      emailAlert.hidden = true;
    }
  });

  form.dept.addEventListener('input', event => {
    const dept = event.target.value.trim();
    event.target.value = dept;
    if (!dept) {
      event.target.setAttribute('class', 'alert-red');
      deptAlert.hidden = false;
    } else {
      event.target.setAttribute('class', 'alert-green');
      deptAlert.hidden = true;
    }
  });

  form.body.addEventListener('input', event => {
    const body = event.target.value.trim();
    if (!body) {
      event.target.setAttribute('class', 'alert-red');
      bodyAlert.hidden = false;
    } else {
      event.target.setAttribute('class', 'alert-green');
      bodyAlert.hidden = true;
    }
  });
})();

AWS SES(Simple Email Service)を設定

※ドメインはRoute53で取得済みの前提
※併せてACM(AWS Certificate Manager)で証明書を取得済み
※SESは送信のみの設定です。

  1. AWSへログイン
  2. SESのコンソールへ移動
  3. リージョンはバージニア北部を選択
  4. 左のナビゲーションからDomainsを選択
    image.png
  5. image.png をクリック
  6. ↓のように入力。自分のドメインとDKIM設定をチェックして、image.pngをクリックimage.png
  7. 次のモーダルではCNAMEやTXTが表示され、登録しろと指示が出る。Route53を利用している場合は、このモーダル上でDNSの登録がすべて完了する。
  8. こんな感じになればOK!(※現時点ではサンドボックス上での制限された利用が可能)
    image.png
  9. 左のナビゲーションでEmail Addressesを選択
    image.png
  10. Veryfy a New Email Addressでサンドボックス上で利用できるメールアドレスを登録する。
  11. 登録したメールアドレスをSend a Test Emailで登録したドメインからメールが送られるかテストして正常な動作を確認

最後に起動するLambdaを作成

  1. Lambdaのコンソールページへ移動
  2. image.png をクリック
  3. image.png を選択
  4. image.png 適当な関数名を入力
  5. ランタイムはNode.js 12.xを選択
  6. そのほかはデフォルトのままでOK
  7. image.png をクリック
  8. 環境変数へ配信元のアドレスを設定
    image.png
  9. 実行ロールにはSESの権限を許可
    image.png
  10. 関数コードへ↓のコードを実装
index.js
'use strict'
const SES = require("aws-sdk/clients/ses");
const ses = new SES({ region: "us-east-1" }); // 米国東部(バージニア北部)
const FROM = process.env.FROM; // 環境変数から取得

exports.handler = async (event) => {
    console.log(event);
    const TO = [event.email];
    const params = {
        Destination: {
            ToAddresses: TO
        },
        Message: {
            Body: {
                Text: {
                    Data: [
                        event.dept + ' ' + event.name + '',
                        ' ',
                        'Hoge Hoge Companyです。',
                        'お問い合わせしていただきありがとうございます。',
                        '下記の内容で承りました。',
                        ' ',
                        '[お問い合わせ内容]' + "\n" + event.body,
                    ].join("\n"),
                    Charset: "utf-8"
                }
            },
            Subject: {
                Data: '受付完了:Webからの問い合わせ',
                Charset: "utf-8"
            }
        },
        // From
        Source: FROM
    };

    const result = {statusCode: 200};
    try {
        const response = await ses.sendEmail(params).promise();
        console.log('Response: ', response);
        result.body = 'OK!!!!!';
    } catch (error) {
        console.log('Error: ', error);
        result.statusCode = error.code;
        result.body = error.message;
    }
    return result;
};

process.envでは環境変数で設定したキーと値が利用可能(暗号化も可能)

最後に起動するLambdaのテスト

  1. image.png テストイベントの選択をクリック
  2. ↓のようにテストを作成(※emailキーにはSESで登録したアドレスを記載すること)
    image.png
  3. image.png をクリックしてテスト実行
  4. 成功となればOK。失敗ならログを確認。
    image.png
  5. テストでemailキーに指定したアドレスにメールが配信されていればOK

最初に起動するLambdaを作成

  1. Lambdaのコンソールページへ移動
  2. image.png をクリック
  3. image.png を選択
  4. image.png 適当な関数名を入力
  5. ランタイムはNode.js 12.xを選択
  6. そのほかはデフォルトのままでOK
  7. image.png をクリック
  8. 環境変数へ配信元のアドレスと受付先のアドレスを入力
    image.png
  9. 実行ロールはSESとLambdaの権限を追加
    image.png
  10. 関数コードへ↓のコードを実装
index.js
'use strict'
const SES = require("aws-sdk/clients/ses");
const ses = new SES({ region: "us-east-1" }); // 米国東部(バージニア北部)
const TO = [process.env.TO]; // 環境変数からの値を取得
const FROM = process.env.FROM;

// 次のLambdaを起動するための設定
const Lambda = require("aws-sdk/clients/lambda");
const lambda = new Lambda({ region: "ap-northeast-1" });

exports.handler = async (event) => {
    const name = event.form.name;
    const email = event.form.email;
    const dept = event.form.dept;
    const body = event.form.body;
    const sesParams = {
        Destination: {
            ToAddresses: TO
        },
        Message: {
            Body: {
                Text: {
                    Data: [
                        '[名前] : ' + name,
                        '[メールアドレス] : ' + email,
                        '[所属部署] : ' + dept,
                        '[お問い合わせ] : ' + "\n" + body,
                    ].join("\n"),
                    Charset: "utf-8"
                }
            },
            Subject: {
                Data: 'Webからの問い合わせ',
                Charset: "utf-8"
            }
        },
        // From
        Source: FROM
    };

    // 次のLambdaに送るデータ
    const payload = { name, email, dept, body };
    console.log('payload: ', payload);
    const lambdaParams = {
        FunctionName: "inpuirySendMailForm_2nd", // 最後に起動するLambdaの名称
        InvocationType: "Event",
        Payload: JSON.stringify(payload)
    };

    const result = {statusCode: 200};
    try {
        const response = await ses.sendEmail(sesParams).promise();
        console.log('Response: ', response);
        const callLambda = await lambda.invoke(lambdaParams).promise();
        console.log("Lambda Response: ", callLambda);
        result.body = 'OK!!!!!';
    } catch (error) {
        console.log('Error: ', error);
        result.statusCode = error.code;
        result.body = error.message;
    }
    return result;

};

最初に起動するLambdaのテスト(次のLambdaも起動する)

  1. image.png テストイベントの選択をクリック
  2. ↓のようにテストを作成
    image.png
  3. image.png をクリックしてテスト実行
  4. 成功となればOK。失敗ならログを確認。
    image.png
  5. テストでemailキーに指定したアドレスに受付完了メールが配信され、環境変数でTOに指定した受付先アドレスへ問い合わせ内容が配信されていればOK

APIGatewayの設定

  1. image.pngをクリック
  2. REST API構築を選択
    image.png
  3. API名説明を入力してAPIの作成をクリック(他はデフォルト)
    image.png
  4. アクション⇒リソースの作成を選択⇒リソース名を入力(リソースパスは自動入力)⇒CORSを有効化⇒リソースの作成をクリック
  5. アクション⇒メソッドの作成⇒POSTを選択⇒セットアップで最初に起動するLambda関数名を入力⇒保存をクリック
  6. 統合リクエストをクリック⇒マッピングテンプレートを追加⇒Content-Typeはapplication/json⇒テンプレートには↓を入力⇒保存をクリック
{
    "form": {
        "name":  "$util.escapeJavaScript($input.path('$.name'))",
        "email": "$util.escapeJavaScript($input.path('$.email'))",
        "dept":  "$util.escapeJavaScript($input.path('$.dept'))",
        "body":  "$util.escapeJavaScript($input.path('$.body'))"
    }
}

APIGatewayのテスト

  1. テストを実行する
    image.png
  2. Lambdaが起動してメールが配信されればOK

APIGatewayのステージを作成

今回はv1というステージを作成

APIGatewayのデプロイ

リソース⇒アクション⇒APIのデプロイを選択⇒v1ステージを選択して⇒デプロイをクリック

発行されたURLをjavascriptのURL変数へ割り当て

全体の動作確認

問い合わせフォームへSESで登録したアドレスと必要事項を入力し、送信ボタンをクリックして動作を確認

実用化に向けて

  • LambdaのSDK呼び出しは動作確認ができているバージョンが呼び出されるようにする必要あり。
  • SESの上限緩和申請が必要。
  • APIGatewayでリソースポリシーを指定して不要なアクセスを防ぐ

最後に

かなり大雑把に書きました。
小規模利用なら十分かと思います。
役に立つかな?
計画性なく作ってしまったのでここからブラッシュアップ

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

複数Youtubeを埋め込んだとき、再生する動画を1つだけにする

Youtube をサイトに複数の埋め込みをした場合、再生する動画を一つにしたいという要件は少なくないと思う。これは複数YoutubeをEmbed APIを使って、再生、停止の制御をすることで実現することができます。

global で onYouTubeIframeAPIReady する必要がある

大体の人はトランスコンパイルしていると思うので、onYouTubeIframeAPIReady() が発火しなくて new YT のタイミングに困ったりする。そこで解決方法としてCustomEventを使いました。IE11のためにpolyfillも必要になります

// CustomEvent Polyfill
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
(function() {
  if (typeof window.CustomEvent === 'function') return false

  function CustomEvent (event, params) {
    params = params || { bubbles: false, cancelable: false, detail: undefined }
    var cevent = document.createEvent('CustomEvent')
    cevent.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
    return cevent
  }
  CustomEvent.prototype = window.Event.prototype

  window.CustomEvent = CustomEvent
})()

function onYouTubeIframeAPIReady () {
  var event = new CustomEvent('youtubeApi', {
    detail: {
      apiReady: true
    }
  })

  window.dispatchEvent(event)
}

poryfill.io とか使ってもいいとおもうけど、ここではMDNからコピペしました。上記のコードはトランスコンパイルできないので、IE11では動かないArrow functionあたりをうっかり使わないように気をつけます

あとは、js側からaddEventListenerでnew YT するだけ

jsの簡易サンプル

HTMLに <iframe class="movie"> としてリスト表示しているとすると

let players =[]
window.addEventListener('youtubeApi', function(event) {
  if (!event.detail.apiReady) return
  const $movies = document.querySelectorAll('.movie')
  $movies.forEach(($movie) => {
    players.push(new YT.Player($movie, {
      events: {
        'onStateChange': onPlayerStateChange
      }
    }))
  })
})

あとは onPlayerStateChange で再生と停止の制御をすればいいはず。自分の場合、表示するのは動画1つでスライダー内の動画だったので、スライドをめくると動画を停止するという挙動になったので onPlayerStateChange は使わなかった

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

StripeのキーをFirebaseの環境変数に設定した

はじめに

先日、Stripeとfirebaseを用いたサービスをリリースしました。その際にStripeのシークレットキーを環境変数に設定したくなり、ドキュメントを漁りました。
https://firebase.google.com/docs/functions/config-env?hl=ja

まずStripeだけども

stripeには2種類のキーがあります。
公開可能キーとシークレットキーですね。
公開可能キーはStripe.jsつまりフロント側で使うキーです。
一方でシークレットキーは名前の通り外に公開してはいけないので、StripeAPIつまりサーバー側で使う(隠さなければならない)キーです。
今回はシークレットキーの設定についての話です。

シークレットキーを環境変数で設定するには

必要なこととしては以下たったの2つ!
※私はCloud FunctionsでAPIを作っています。
1.コンソールでコマンドを記述
2.Cloud Functionsでシークレットキーを呼び出すための記述

1.コンソールでコマンドを記述

firebase functions:config:set stripe.secret_token="sk_××××"

これだけです。ちなみに設定した環境変数を確認するには

firebase functions:config:get

して

{
  "stripe": {
    "secret_token": "sk_××××"
  }
}

となるはずです。

2.Cloud Functionsでシークレットキーを呼び出すための記述

index.js
const stripe = require('stripe')(functions.config().stripe.secret_token);

functions.config()で環境変数にアクセスでき、stripeオブジェクトのsecret_tokenプロパティを呼んでます。

まとめ

ドキュメントにあるようにStripeAPIに限らず様々なAPIで活用できる方法です。
セキュリティ対策のためにもぜひ活用してみてください!

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

初心者によるプログラミング学習ログ 210日目

100日チャレンジの210日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。

100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。

210日目は

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

Ionic Angular データバインディングで表示させた文章を改行する方法

はじめに

タイトルに堂々と「Angularのデータバインディング」 と書いてますが、これから書く内容がデータバインディングなのか私はわかりません。。

そこまで用語に対して詳しくないので、言葉が間違ってたら教えてください。

本記事で見せるサンプルの環境

今回この記事で使うサンプルは、以下のような環境です。

・Ionic + Angularのアプリ
・Firebaseに保存された文字列を表示させている
・ページAで文字列を入力&保存、その文字列をページBで表示

こんな感じでーす。

文字列が改行されない問題

以下の例を見て欲しい。

ページA

a.page.html
<ion-item>
            <ion-label position="floating">タイトル</ion-label>
            <ion-input type="text" [(ngModel)]="event.title" name="title">
            </ion-input>
          </ion-item>
          <ion-item>
            <ion-label position="floating">メモ</ion-label>
            <ion-textarea rows="7" [(ngModel)]="event.memo"  name="memo">
            </ion-textarea>
          </ion-item>

スクリーンショット 2020-01-14 21.27.10.jpg

こんな感じで保存したとする。

ページB

b.page.html
<ion-label *ngIf="event.title">
                 <ion-text color="dark">タイトル : </ion-text> {{ event.title }}
                 <hr>
                </ion-label>
                <div *ngIf="event.memo">
                    <ion-text color="dark">メモ : </ion-text> {{ event.memo }}
                  <hr>
                </div>

スクリーンショット 2020-01-14 21.27.44.jpg

そう、改行されないのだーーー!!!

入力&保存をするページAではしっかり改行しておいたのに、データバインディングで表示させているページBでは改行されていない。

cssだけで改行できました

ページBにcssクラスを追加

b.page.html
<div *ngIf="event.memo" class="brake">
                    <ion-text color="dark">メモ : </ion-text> {{ event.memo }}
                  <hr>
                </div>
b.page.scss
.brake {
white-space: pre-wrap;
}

スクリーンショット 2020-01-14 21.26.57.jpg

最後に

詳しいことはわかりません。しかし、改行できた!

こちらを参考にしました。
【AngularJS】改行をとにかく簡単に有効にする方法

CSSのwhite-spaceにはprewrapnowrapなど、いろいろと種類があるようなので、気になる人は調べてみてくださいな〜。

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

TypeScriptで60進数から10進数へ変換する

やりたい事

DMS(※1)形式 から Degree形式 に変換したい

つまりタイトル通り 60進数 to 10進数 をしたい
10進数 to 60進数も↓の方で実装してます

e.g.) 東京の緯度経度
DMS形式 [35度39分10.1952秒, 139度50分22.1208秒]

Degree形式 [35.652832, 139.839478]

※1 Degree Minute Second の略

実装

interface PointInDMS {
  lat: DMS;
  lng: DMS;
}

interface PointInDegree {
  lat: number;
  lng: number;
}

interface DMS {
  degree: number;
  minute: number;
  second: number;
}

const degreeMinuteSecond2Degree = (pointInDMS: PointInDMS): PointInDegree => {
  return {
    lat:
      pointInDMS.lat.degree +
      pointInDMS.lat.minute / 60 +
      pointInDMS.lat.second / 60 / 60,
    lng:
      pointInDMS.lng.degree +
      pointInDMS.lng.minute / 60 +
      pointInDMS.lng.second / 60 / 60
  };
};

export const main = () => {
  // 東京
  const tokyoPoint: PointInDMS = {
    lat: { degree: 35, minute: 39, second: 10.1952 },
    lng: { degree: 139, minute: 50, second: 22.1208 }
  };

  console.log({ tokyoPoint });

  const deg: PointInDegree = degreeMinuteSecond2Degree(tokyoPoint);

  console.log({ deg });
};

main();

実行結果

{
  tokyoPoint: {
    lat: { degree: 35, minute: 39, second: 10.1952 },
    lng: { degree: 139, minute: 50, second: 22.1208 }
  }
}
{ deg: { lat: 35.652832, lng: 139.839478 } }

逆もやってみた

10進数 to 60進数

一般的にDegree形式が使われてると思うので、こちらを使う事が多そう

interface PointInDMS {
  lat: DMS;
  lng: DMS;
}

interface PointInDegree {
  lat: number;
  lng: number;
}

interface DMS {
  degree: number;
  minute: number;
  second: number;
}

const degreeMinuteSecond2Degree = (pointInDMS: PointInDMS): PointInDegree => {
  return {
    lat:
      pointInDMS.lat.degree +
      pointInDMS.lat.minute / 60 +
      pointInDMS.lat.second / 60 / 60,
    lng:
      pointInDMS.lng.degree +
      pointInDMS.lng.minute / 60 +
      pointInDMS.lng.second / 60 / 60
  };
};

const degree2DegreeMinuteSecond = (pointInDMS: PointInDegree): PointInDMS => {
  const d2dms = (degree: number): DMS => {
    const minuteSecond = parseFloat("0." + degree.toString().split(".")[1]) * 60;
    return {
      degree: Math.trunc(degree),
      minute: Math.trunc(minuteSecond),
      second: parseFloat("0." + minuteSecond.toString().split(".")[1]) * 60
    };
  };

  return {
    lat: d2dms(pointInDMS.lat),
    lng: d2dms(pointInDMS.lng)
  };
};

export const main = () => {
  // 東京
  const tokyoPoint: PointInDMS = {
    lat: { degree: 35, minute: 39, second: 10.1952 },
    lng: { degree: 139, minute: 50, second: 22.1208 }
  };

  console.log({ tokyoPoint });

  const deg: PointInDegree = degreeMinuteSecond2Degree(tokyoPoint);

  console.log({ deg });

  const dms: PointInDMS = degree2DegreeMinuteSecond(deg);

  console.log({ dms });
};

main();

実行結果

{
  tokyoPoint: {
    lat: { degree: 35, minute: 39, second: 10.1952 },
    lng: { degree: 139, minute: 50, second: 22.1208 }
  }
}
{ deg: { lat: 35.652832, lng: 139.839478 } }
{
  dms: {
    lat: { degree: 35, minute: 39, second: 10.1952 },
    lng: { degree: 139, minute: 50, second: 22.1208 }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScriptで60進数から10進数へ変換する(緯度経度)

やりたい事

DMS(※1)形式 から Degree形式 に変換したい

つまりタイトル通り 60進数 to 10進数 をしたい
10進数 to 60進数も↓の方で実装してます

e.g.) 東京の緯度経度
DMS形式 [35度39分10.1952秒, 139度50分22.1208秒]

Degree形式 [35.652832, 139.839478]

※1 Degree Minute Second の略

実装

interface PointInDMS {
  lat: DMS;
  lng: DMS;
}

interface PointInDegree {
  lat: number;
  lng: number;
}

interface DMS {
  degree: number;
  minute: number;
  second: number;
}

const degreeMinuteSecond2Degree = (pointInDMS: PointInDMS): PointInDegree => {
  return {
    lat:
      pointInDMS.lat.degree +
      pointInDMS.lat.minute / 60 +
      pointInDMS.lat.second / 60 / 60,
    lng:
      pointInDMS.lng.degree +
      pointInDMS.lng.minute / 60 +
      pointInDMS.lng.second / 60 / 60
  };
};

export const main = () => {
  // 東京
  const tokyoPoint: PointInDMS = {
    lat: { degree: 35, minute: 39, second: 10.1952 },
    lng: { degree: 139, minute: 50, second: 22.1208 }
  };
  console.log({ tokyoPoint });

  const deg: PointInDegree = degreeMinuteSecond2Degree(tokyoPoint);
  console.log({ deg });
};

main();

実行結果

{
  tokyoPoint: {
    lat: { degree: 35, minute: 39, second: 10.1952 },
    lng: { degree: 139, minute: 50, second: 22.1208 }
  }
}
{ deg: { lat: 35.652832, lng: 139.839478 } }

逆もやってみた

10進数 to 60進数

一般的にDegree形式が使われてると思うので、こちらを使う事が多そう

interface PointInDMS {
  lat: DMS;
  lng: DMS;
}

interface PointInDegree {
  lat: number;
  lng: number;
}

interface DMS {
  degree: number;
  minute: number;
  second: number;
}

const degreeMinuteSecond2Degree = (pointInDMS: PointInDMS): PointInDegree => {
  return {
    lat:
      pointInDMS.lat.degree +
      pointInDMS.lat.minute / 60 +
      pointInDMS.lat.second / 60 / 60,
    lng:
      pointInDMS.lng.degree +
      pointInDMS.lng.minute / 60 +
      pointInDMS.lng.second / 60 / 60
  };
};

const degree2DegreeMinuteSecond = (pointInDMS: PointInDegree): PointInDMS => {
  const d2dms = (degree: number): DMS => {
    const minuteSecond = parseFloat("0." + degree.toString().split(".")[1]) * 60;
    return {
      degree: Math.trunc(degree),
      minute: Math.trunc(minuteSecond),
      second: parseFloat("0." + minuteSecond.toString().split(".")[1]) * 60
    };
  };

  return {
    lat: d2dms(pointInDMS.lat),
    lng: d2dms(pointInDMS.lng)
  };
};

export const main = () => {
  // 東京
  const tokyoPoint: PointInDMS = {
    lat: { degree: 35, minute: 39, second: 10.1952 },
    lng: { degree: 139, minute: 50, second: 22.1208 }
  };
  console.log({ tokyoPoint });

  const deg: PointInDegree = degreeMinuteSecond2Degree(tokyoPoint);
  console.log({ deg });

  const dms: PointInDMS = degree2DegreeMinuteSecond(deg);
  console.log({ dms });
};

main();

実行結果

{
  tokyoPoint: {
    lat: { degree: 35, minute: 39, second: 10.1952 },
    lng: { degree: 139, minute: 50, second: 22.1208 }
  }
}
{ deg: { lat: 35.652832, lng: 139.839478 } }
{
  dms: {
    lat: { degree: 35, minute: 39, second: 10.1952 },
    lng: { degree: 139, minute: 50, second: 22.1208 }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

利用規約を全て読んだ上でチェックをしたら、ボタンをクリックさせる

したいこと

よくサービスにある利用規約。
こちらを以下の条件を満たさないとボタンをクリックさせないようにしたい。

(条件)
・利用規約を全て読むこと(規約を読んだ分のスクロール量を取得)
・同意するのチェックボタンの押下していること

今後よく使いそうなのでメモ。

チェック前のイメージ

スクリーンショット 2020-01-14 16.59.46.png

チェック後のイメージ

スクリーンショット 2020-01-14 16.59.58.png

ソースコード

<div id="agreeContents" class="p-agree-block">
  本文本文本文本文本文本文本文本文本文本文本文本文
    本文本文本文本文本文本文本文本文本文本文本文本文...
  <div id="agreeEnd"></div>
</div>
<div class="text-center">
<div class="m-t-35 m-b-15">
  <div class="agree-check">
    <div class="border-checkbox-section">
<div class="border-checkbox-group border-checkbox-group-primary">
  <input class="border-checkbox" type="checkbox" id="checkbox1">
  <label class="border-checkbox-label" for="checkbox1">利用規約に同意する</label>
</div>
</div>
</div>
</div>
<div class="col-md-8 offset-md-2">
  <button class="btn btn-disabled btn-block" disabled="disabled" id="agreeButton" onclick="location.href='finance-matterLists.html'">
    利用を開始する
  </button>
</div>
</div>
"use strict";

$(function(){
  let scrollHeight,scrollPosition;
  const $Contents = $("#agreeContents");
  const $Button = $("#agreeButton");
  const $checkbox = $("#checkbox1");
  let checkboxFlag;
  let scrollFlag;
  let judge = () => {
    if(checkboxFlag && scrollFlag) {
      $Button.removeAttr("disabled");
      $Button.removeClass("btn-disabled");
      $Button.addClass("btn-primary");
    }else{
      $Button.attr("disabled");
      $Button.addClass("btn-disabled");
      $Button.removeClass("btn-primary");
    }
  }
  $Contents.scroll(function() {
    scrollHeight = $(this).get(0).scrollHeight;
    scrollPosition = $Contents.get(0).offsetHeight + $(this).scrollTop();
    if (scrollHeight <= scrollPosition ) {
      scrollFlag = true;
    }
    judge();
  });
  $checkbox.click(function() {
    checkboxFlag = $checkbox.get(0).checked;
    judge();
  });
});

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

【Nuxt.js】ローカル環境をhttpsで起動する方法

なぜこれが必要なのか

位置情報などを取得するGeolocationなど、http通信では動作しないものを動作させたい場合など。

どうやってやればいいか

nuxt.jsの設定でやる方法と、外部サービスを利用する2パターンがあります。

  • Nuxt.jsの設定でやる方法
  • 外部サービス(ngrok)を利用する方法

Nuxt.jsの設定でやる方法

実行環境

  • @nuxt/cli v2.8.1
  • macOS Catalina(v10.15.2)
  • パッケージ管理はyarn

自己署名入り証明書の作成

mkcertを使って、オレオレ証明書を作成します。(コマンド内容は割愛)

$ brew install mkcert
$ mkcert -install
$ mkcert localhost

上記手順で、以下の証明書が発行されます。

  • localhost.pem
  • localhost-key.pem

起動設定にhttpsの起動設定を追加

nuxt.config.jsに、先程作成した証明書を設定します。
Ref:公式ドキュメント - serverプロパティ(HTTPS設定)

  • importの追加
  • serverプロパティにhttpsのプロパティを追加して、発行した証明書を設定する
nuxt.config.js
import path from 'path'
import fs from 'fs'

export default {
server: {
    port: 3000,
    host: "0.0.0.0",
    https: {
      key: fs.readFileSync(path.resolve(__dirname, "localhost-key.pem")),
      cert: fs.readFileSync(path.resolve(__dirname, "localhost.pem"))
    },
    // 以下その他設定...
}

起動

この状態で

$ yarn run dev

とすればhttpsでローカル環境が起動します。

外部サービスを利用する方法

設定周りをいじらなくても、ngrokというサービスを使うと、ローカル環境をhttpsで公開してくれます。便利!

利用方法は以下のブログが詳しく説明されています(外部リンク)
ngrokを使ってローカル開発中のVueアプリをHTTPSで公開する

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

ポエム(短歌)でWebAssembyの低レイヤーに入門

WebAssembly(WASM)のバイトコードやスタックマシンに対する解説が少なかったので、一つガツンと低レイヤーの入門記事を書こうかなと思いました!3句ほど詠んでマニアックなWASMの世界に最速で旅立ちましょう。

WebAssembly自体の楽しみな展望や意義については末尾に書かせてもらいますので、そっちから読んでもらっても構いません!

まず、WASMには3つの形態があります。

①WAT(.wat)
これはLispっぽい感じの人が読み書きできるWASMの形で、WASMの高級言語版です。

②WAST(.wast)
これはほぼ機械語のWASMの表記です。アセンブリ言語っぽいのはこちらで、WATはこれにシンタックスシュガーを加えた形になります。

③WASM(.wasm)
これがWASMの機械語バイナリです。コンパイル後の形であり、C言語、Rust、Go言語、WATなどからコンパイルでき、ブラウザでJSのように実行可能になります。

さて、では単に「42」とコンソールログに表示するプログラムをWATで書いてブラウザで走らせてみましょう。
まずはWASMコンパイラのインストール

$ git clone --recursive https://github.com/WebAssembly/wabt
$ cd wabt
$ make gcc-release

ではWATファイル作ります

$ vim test.wat

(module
  (func $main (result i32)
    i32.const 42)
  (export "main" (func $main)))

まー、だいだい意味はわかりますね。単に42を返す関数です。i32はint32型です。
これを次のようにすると、test.wasmが生まれます。

$ ./wabt/out/gcc/Release/wat2wasm test.wat -o test.wasm

ここで生まれたWASMバイナリをバイナリエディターでみてみましょう。
https://hexed.it/
でtest.wasmをロードしてみて下さい。

WASM (8).jpg

このバイナリ列をJSの配列にしてみます。hexdumpコマンドで16進数表記は出ますが、文字列処理面倒なので僕が整えたデータを使ってください。
ブラウザのコンソールで次のように打ち込んで下さい。ChromeならCntl+SHift+Iかな?

> var wasmCode = new Uint8Array([0x00,0x61,0x73,0x6D,0x01,0x00,0x00,0x00,0x01,0x05,0x01,0x60,0x00,0x01,0x7F,0x03,0x02,0x01,0x00,0x07,0x08,0x01,0x04,0x6D,0x61,0x69,0x6E,0x00,0x00,0x0A,0x09,0x01,0x07,0x00,0x01,0x01,0x01,0x41,0x2A,0x0B])

続いて、このWebAssemblyのコードは次のようにModule読み込み、エクスポートして使えます。

> var wasmModule = new WebAssembly.Module(wasmCode);
> var wasmInstance = new WebAssembly.Instance(wasmModule)
> console.log(wasmInstance.exports.main());

コンソールに42と表示されたはずです。

しかしながら、あのバイトコード一体なんだったのでしょう?
基本的にWASMの仕様は全てここに書いてあります。
https://webassembly.org/docs/binary-encoding/#start-section

この仕様書通りに正確に読み進めていくと全部解読できるようになっています。もっと簡略に意味がある程度分かるラベルが見たい人は次のように打ってください。

$ ./wabt/out/gcc/Release/wat2wasm  -v blank.wat

このコマンドで出てくるガイドは多少実際に生まれるバイナリとズレます。いずれにせよ、仕様書を読む必要があり、適宜参照しながら進めていきます。では解釈を始めます。
まずは、出だしから。

WASM.png

WebAssemblyのModuleの構造をみていくべきでしょう。
Moduleはおまじないとバージョン情報から始まり、次はセクションが羅列されていて、その中にサブセクションがあるという構造になっています。
バージョン情報の次の瞬間からセクション1が始まります。
セクション1はこの表の通り、Type(型定義)のセクションです。
WASM.jpg

引用元:https://ukyo.github.io/wasm-usui-book/webroot/binary-format.html

次にセクションサイズの5が書かれていますが、このセクションサイズにより、この後5バイトでセクションが終わり、次のセクションの読み込みを始められることがVMに伝えることができるでしょう。定義された型の数はが次にかかれます。そして次に一つ一つの型が列挙されていきます。今回は1個のみですが。

タイプは関数を表す0x60が刻まれます。
func $main (result i32)の通り、引数0のi32型の返り値が一つです。

では次のバイトコード
WASM (1).jpg

今回は何もインポートしてないのでセクション2:インポートセクションは飛ばされました。
セクション3:関数宣言では、関数の数とそのインデックスが定義されて終わりとなっています。

では次のバイトコード

WASM (1).png
ここで名前の定義がされています。関数宣言セクションでもNameセクションなどでもないことに注意です。

では次のバイトコード
WASM (3).jpg

いよいよ関数本体のセクション、セクション10です。見ていただくと分かりますが、命令セットがあるのはWASM全体で412Aの部分だけなんですね。そりゃそうか。だってやってること42返してるだけだもん。もちろん、長い関数処理を書いたらこの部分も凄く長くなります。WebAssemblyで196個と大量に用意されてるAssemblyオペコードはこの部分を書くことに使われます。

===============

さて、ではWebAssemblyをより深く理解するための遊びに移りましょう。
文化的WASM人としてポエムでイキリ散らかしましょう。
そして日本人で低レイヤー界隈なら短歌ですよね。そりゃ。
というわけで、早速バイトコードで短歌を詠みましょう。

WASMで5バイト、7バイト、5バイト、7バイト、7バイト合計31バイトで 余が7歩を歩む間に一句読め!!!

image.png
・・・・・・・・。
そもそもこのWASMの時点で、37バイトで字数超えてるし・・・。
そう。我々がコードとしていじれるのは今回0x41,0x2Aの2バイトだった0x0B直前の関数Body領域です。ここで31バイトで詠みましょう。
詠め!!!!
image.png
・・・・・・・・・・・。

===========================

オケです、曹丕並みの即興で詠みましょう!低レイヤーなら誰でも知っているNOPオペコード、つまり何もしないオペコードであと29バイト埋めましょう。WASMのオペコード表見れば分かりますが、WASMのNOPは0x01です。
文法とスタックマシンの関係はこちら(https://webassembly.org/docs/semantics/)
全体をセクションごとに区切って書きます。

> var wasmCode = new Uint8Array([
0x00,0x61,0x73,0x6D,0x01,0x00,0x00,0x00,
0x01,0x05,0x01,0x60,0x00,0x01,0x7F,
0x03,0x02,0x01,0x00,
0x07,0x08,0x01,0x04,0x6D,0x61,0x69,0x6E,0x00,0x00,
0x0A,0x06,0x01,0x04,0x00,

0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,

0x41,0x2A,0x0B])

これでいいでしょうか!!? いえ、これだとエラーが出るでしょう!! なぜなら、セクションで定義された長さと関数Body長が変わってしまいますから。再掲します。
WASM (3).jpg

では直前の0x06と0x04にそれぞれ29を足しましょう。0x23と0x21かな?
ではバイトコードを置き換えます。

> var wasmCode = new Uint8Array([
0x00,0x61,0x73,0x6D,0x01,0x00,0x00,0x00,
0x01,0x05,0x01,0x60,0x00,0x01,0x7F,
0x03,0x02,0x01,0x00,
0x07,0x08,0x01,0x04,0x6D,0x61,0x69,0x6E,0x00,0x00,
0x0A,0x23,0x01,0x21,0x00,
//ここから短歌
0x01,0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x01,0x41,0x2A,
//短歌終わり
0x0B])

さてと、ではConsoleに出して詠みますか。

> var wasmModule = new WebAssembly.Module(wasmCode);
> var wasmInstance = new WebAssembly.Instance(wasmModule)
> console.log(wasmInstance.exports.main());

> 42

詠めた!!いや〜粋だねぇ〜!?このNOPの静けさ、まるで「4分33秒」の演奏を聴いているかのようですよ〜。「29バイト」ってタイトルにしましょう。声に出して読むと

ノップ、ノップ、ノップ、ノップ、ノップ
ノップ、ノップ、ノップ、ノップ、ノップ、ノップ、ノップ
ノップ、ノップ、ノップ、ノップ、ノップ
ノップ、ノップ、ノップ、ノップ、ノップ、ノップ、ノップ
ノップ、ノップ、ノップ、ノップ、ノップ、アイサンジュウニコンスト、ヨンジュウニ

いや〜ね。粋だね〜〜。

けどさ、ピアノのいい曲教えてって聞かれて4分33秒沈黙されたら普通ヒクよね?
初心者にこのイキさは分からないかもしれない。
もう少し、いろんなオペコードを知ってもっと自由な世界で詠もう。

というわけで、そのためにも、他のWASMプログラムも見て見ましょう。
次は引数を2つ取って足し算して返すという関数をエクスポートし、ブラウザで走らせます。

$ vim addTwo.wast

(module
  (func $addTwo (param i32 i32) (result i32)
    get_local 0
    get_local 1
    i32.add)
  (export "addTwo" (func $addTwo)))

では、WASMのバイナリを作りましょう

$ ./wabt/out/gcc/Release/wat2wasm addTwo.wat -o addTwo.wasm

バイナリエディターでみてみましょう。
https://hexed.it/
でaddTwo.wasmをロードしてみて下さい。今回はこんな感じですね。

WASM (4).jpg
では、どんな内容になっていくか1セクションずつ確認しましょう。
WASM (5).jpg
今回は引数があるのでちょっと違いますね。
次のバイトコード
WASM (6).jpg
ここはエクスポート名がmainがaddTwoになったくらいしか違いはありません。
WASM (7).jpg
引数が増えてもローカル変数は増えません。
このようにローカル変数(index)からPop処理をスタックマシンにするのがget_localです。このコードでは2つポップし、i32.addしてスタックにPushします。
こういったStack処理についてはこちらが分かりやすく書いてます。
https://retrage01.hateblo.jp/entry/2018/03/04/144355

こんな感じでオペコードというのは使っていくことができるのです。
というわけで。。。。。。。詠め!!!!

image.png
・・・・・・・・・・・・・。

お気付きの方は多かと思いますが、get_local、set_localには引数かローカル変数を関数で定義しないといけないので、さっきみたく、オペコードを自由に改造して一句詠むことことが難しくなります。引数定義する場合はそういう設定の関数を一度コンパイルしてから改造する形になるでしょう。
基本的にx86アーキテクチャーでアセンブリ短歌が気軽に楽しめるのは、x86アーキテクチャーがEAXなどのレジスタ記憶領域に直接アクセスでき、定義せずとも変数として扱えるからです。ここはWebAssemblyと違います。

ではどうするか?

https://webassembly.github.io/spec/core/_download/WebAssembly.pdf

よく、オペコード表と仕様書を読むと、スタックとは独立な記憶領域である線形メモリとその操作オペコードが定義されていることが分かります。i32.load、i32.storeです。これを変数代わりに使えば詠めそうですね!!
よし、では先ほどの短歌のNOPの部分をちょっと削ってi32.load、i32.storeを入れて見ます。

i32.const 0
i32.const 42
i32.store 2 0

これが0番アドレスに42を書き込むという意味になります。
細かく言えば、i32の0をスタックにPush,i32の42をスタックにPush、二つPoPしてi32.storeをオフセット0からi32型2個分読み込むんで上記の処理に使うということです。
そして、これをロードしてStackにpushするのは

i32.const 0
i32.load 2 0

二つ合わせて翻訳します。

0x41 0x00
0x41 0x2A
0x36 0x02 0x00
0x41 0x00
0x28 0x02 0x00

これをそのまま、この前のNOPに置き換えたいところですが、メモリを用意しないとエラーが出てしまうので、最初のバイナリにメモリ領域を追加しましょう。

$ vim blank.wat

module
  (import "js" "memory" (memory 1))
  (func $main (result i32)
    i32.const 42)
  (export "main" (func $main)))

では生成されたバイナリをブラウザコンソールのJSで読み込みます。
セクションごとに区切ってJSスタイルで見ましょう。

> var wasmCode = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f, 
0x02, 0x0e, 0x01, 0x02, 0x6a, 0x73, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x01,
0x03, 0x02, 0x01, 0x00, 
0x07, 0x08, 0x01, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x0a, 0x06, 0x01, 0x04, 0x00,
0x41, 0x2a, 
0x0b])

メモリセクションであるセクション2が追加されてることが分かりますね。これを追加しておけば今後汎用的に使えるでしょう。この方法は変数・引数を定義して詠みたい人はそれでもいいでしょう。
個人的には最後の0x0b直前にオペコードを31バイト書いて詠んでいれば十分風流で粋だと思います。

さて、これに先ほど作った42をi32.storeとi32.loadするコード、0x41,0x00,0x41,0x2A,0x36,
0x02,0x00,0x41,0x00,0x28,0x02,0x00,をNOPつきで挿入しましょう。

> var wasmCode = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f, 
0x02, 0x0e, 0x01, 0x02, 0x6a, 0x73, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x01,
0x03, 0x02, 0x01, 0x00, 
0x07, 0x08, 0x01, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x0a, 0x23, 0x01, 0x21, 0x00,

//ここから短歌
0x01,0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x01,
0x01,0x01,0x41,0x00,0x41,0x2A,0x36,
0x02,0x00,0x41,0x00,0x28,0x02,0x00, 
//終わり

0x0b])

さて、実行しますが、メモリをJSサイドで渡してあげないといけないので、注意です。

> var wasmModule = new WebAssembly.Module(wasmCode);
> const memory = new WebAssembly.Memory({initial:1});
> var importObject = {
    js: {
      memory: memory
    }
  };
> var wasmInstance = new WebAssembly.Instance(wasmModule,importObject )
> console.log(wasmInstance.exports.main());

> 42

いや〜〜〜ね〜、粋だね〜!この最初の575のシンとした静けさを破る最後のstoreとload。ブラウザのコンソールでこんな粋な歌を詠ん出るなんてChromeの開発チームもび〜っくりだよ〜!いや〜粋だね〜。
もう変数を読み書きできるんだから直接42を出すこと以外もできるでしょう。なんかプログラマっぽいことやりましょう。

Loop開始のオペコードはLoopです。0x03ですな。ここでWASM(WAT)のLOOP文法は少々特殊なので特記しておきます。

(block ;;ここがdepth 1
  (loop ;; ここがdepth 0    
    (br_if 1(これはdepth) 条件式のオペコード)
    (br 0) ;;何も書かないとループされない。一番下にdepthを指定してJUMPする必要
  )
) 

Depthを指定してJUMPするのがループになるんですね。これをオペコード表と見比べながら組んでいきましょう。
では、今までにやった操作も加えて面白くしてみます。

(module
    (import "js" "memory" (memory 1))
    (func $main (result i32)
    (block
      (loop
        (br_if 1 (i32.eq (i32.load (i32.load (i32.load (i32.const 8)))) (i32.const 17)))
        (br 0)
      )
    )
    (i32.load (i32.const 0))
    )
  (export "main" (func $main)))

これはアドレス8をロードしたら出てくるアドレスをロードしたら出てくるアドレスをロードしたら17が出てくると、ループを脱して0番アドレスの中身を出力します。
出来なかったら無限ループです。これは入力を間違うと、(OSX上のChromeで試したところ)タブが全然閉じれない不具合を引き起こしたもので、間違わないようにする緊張感が出ますね。上の条件を満たすメモリ(WebAssembly.memory Object)を渡さなければなりません。
コンパイルするとbody部分は29byte。字足らずですが、粋なので良いでしょう。

ではどうやってメモリオブジェクトを操作するかという話になりますが、公式ドキュメントから操作方法が出されています。
https://developer.mozilla.org/ja/docs/WebAssembly/Using_the_JavaScript_API

> var memory = new WebAssembly.Memory({initial:1});
> new Uint32Array(memory.buffer)[0] = 17;
> new Uint32Array(memory.buffer)[1] = 0; 
> new Uint32Array(memory.buffer)[2] = 4; 

ここは一つの配列要素ごとに32bit=4byte取っています。
つまり、0が入っているアドレスは0x04です。アドレス8は[2]で入力出来ます。
よって上記をWASMを実行するとアドレス8を読んで4が出て、4を読んだら0が出て、0を読んだら17が出てきてループを抜けれるはずです。

では短歌から。

> var wasmCode = new Uint8Array([
0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,
0x01,0x05,0x01,0x60,0x00,0x01,0x7f,
0x02,0x0e,0x01,0x02,0x6a,0x73,0x06,0x6d,0x65,0x6d,0x6f,0x72,0x79,0x02,0x00,0x01,
0x03,0x02,0x01,0x00,
0x07,0x08,0x01,0x04,0x6d,0x61,0x69,0x6e,0x00,0x00,0x0a,0x22,0x01,0x20,0x00,

//短歌始まり
0x02,0x40,0x03,0x40,0x01,
0x41,0x08,0x28,0x02,0x00,0x28,0x02,
0x00,0x28,0x02,0x00,0x41,
0x11,0x46,0x0d,0x01,0x0c,0x00,0x0b,
0x0b,0x41,0x00,0x28,0x02,0x00,
//短歌終わり

0x0b
])

で、メモリの設定をします。

> var memory = new WebAssembly.Memory({initial:1});
> new Uint32Array(memory.buffer)[0] = 17;
> new Uint32Array(memory.buffer)[1] = 0; 
> new Uint32Array(memory.buffer)[2] = 4; 

> var importObject = {
  js: {
    memory: memory
  }
};

【警告】ここから、実行に移りますが、少しでも数字にズレがあれば無限ループに入ってしまいます。
止める為にはタスクマネージャーからブラウザを強制終了する必要があるので、タスクマネージャーを起動しておいてください。

> var wasmModule = new WebAssembly.Module(wasmCode);
> var wasmInstance = new WebAssembly.Instance(wasmModule,importObject )
> console.log(wasmInstance.exports.main());

> 17

いや〜〜〜〜粋だね〜〜〜。ちゃんとメモリ作れればちゃんと返答してくれて、間違った奴には無限ループを永遠に歌わせる短歌。
こんな短歌、百人一首の殿方もび〜〜っくりだよ。「短歌をウィルスのように詠む」ってボタン作りたいくらいだ。いや〜〜粋だね〜〜。
image.png

ウィルスとか詐欺とか不謹慎ですって?分かりました。 そろそろなぜWebAssemblyがそんなに重要なのか夢のある話しましょう。

==============================

WASMで結局どうなる?

image.png

WebAssemblyは命令セットアーキテクチャーです。
今まではブラウザで実行できるものと言えばJavaScriptというスクリプト言語でした。これは他の多くの言語との互換性はないものだったので、ウェブブラウザ、ウェブサイトで動くものを作る際に多くの言語は使えませんでした。

しかしながらWASMは機械語的な命令セットであり、LLVM等をコンパイラにもつ多くの言語から翻訳可能になります。C言語やGo,Rustなどの言語が使えることから、ウェブエンジニアの潜在的な数が爆発的に増えたことになります。
また、WASMは速いです。今はChromeのV8コンパイラとあまり差がありませんが、ウェブが拡大するにつれ、そして最適化に加わる人の数が増えるにつれ、差はついて行くことでしょう。

さて、それだけでしょうか?多くのことを次の記事に昔書きました。

https://alis.to/superparanoid/articles/3Qj6pWpBgG8x

全てのサイトがアプリ化していく世界で、どうなるかは分かりませんが、高レイヤーと呼ばれたWebと低レイヤーと呼ばれたOS・アーキテクチャーの世界がぐちゃぐちゃに混じるであろうWASMの世界は絶対に面白いです。

文責: https://twitter.com/leo_hio

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

初心者による ファイルアップロードの扱い

概要

ファイル選択ボックスから、指定されたファイルの情報を取得する。これにはfileプロパティをつかう。

sample.html
<form>
 <label for="file">ファイル:</label>
 <input id="file" name="file" type="file" multiple />
</form>
sample.js
//windowオブジェクトを使う
window.addEvenetListener('DOMContentLoaded', function() {
 //changeイベントはeを引数に入れる
 document.getElementById('file').addEvenetListener('change', function(e) {
  //filesプロパティ
  let inputs = document.getElementById('file').files
  for(let i = 0,len = inputs.length; i < len; i++){
   let input = inputs[i]
   console.log('ファイル名: ' + input.name)
   console.log('種類: ' + input.type)
   console.log('サイズ:'  + input.size / 1024 + 'KB')
   console.log('最終更新日:' + input.lastModifiedDate)
  }
 }, true)
})

fileプロパティはアップロードされたファイル群(fileListオブジェクト)を返します。
fileオブジェクトで利用できるプロパティは、

name : ファイル名
type : コンテンツタイプ
size : サイズ(バイト単位)
lastModifiedDtate : 最終更新日

FileReaderオブジェクト

fileReaderオブジェクトを利用することで、取得したfileオブジェクトの内容を読み込みことができる。

sample.js
reader.readAsText(file[, charset])
 //reader : fileReaderオブジェクト file : 読み込むファイル charset : 文字コード

テキストファイルの内容を取得する

sample.html
<form>
 <label for="file">ファイル:</label>
 <input id="file" type="file" />
</form>
<hr />
<pre id="result"></pre>
sample.js
window.addEventListener('DOMContentLoaded', function() {
 document.getElementById('file').addEvenetListener('change', function() {
  //実際は1つでも配列のように扱う
  let input = document.getElementById('file').files[0]
  //FileReaderオブジェクトを生成
  let reader = new FileReader()
  //定義
  reader.addEvenetListener('load', function() {
   document.getElementById('result').textContent = reader.result
  }, true)
  //呼び出し(ファイルの呼び出し)
  reader.readAsText(input, 'UTF-8')
 }, true)
})

読み込んだテキストはFileReader.resultプロパティでアクセスできます。なお、reader.readAsTextメソッドでファイルの読み込みを開始する。

ファイル読み込みに失敗した場合

FileReaderオブジェクトでは、errorイベントリスナーを設定しておくことで、エラー処理をすることができる。エラー原因はreader.error.messageプロパティにアクセスすることで、確認することができる。

sample.js
reader.addEventListener('error', function() {
 console.log(reader.error.message)
}, true)

バイナリファイルの内容を取得する

バイナリファイルを読み込むには、readAsTextの代わりにreadAsDataURLメソッドを利用する。これによってバイナリファイルをData URLという形式で取得できます。Data URLとは、URLに直接、画像/音声等のデータを埋め込むための表現でい一般的には、

data:[コンテンツタイプ][;base64],データ本体

Data URL形式のデータは、img要素のsrc属性やa要素のhref属性にそのまま埋め込むことができるので、データをいちいちファイルとして保存する必要がありません。

sample.html
<form>
 <label for="file">ファイル:</label>
 <input id="file" name="file" type="file" />
</form>
<hr />
<img id="result" />
sample.js
window.addEvenetListener('DOMContentLoaded', function() {
 document.getElementById('file').addEvenetListener('change', function(e) {
  let input = document.getElementById('file').files[0]
  let reader = new FileReader()
  reader.addEvenetListener('load', function(e) {
   document.getElementById('result').src = reader.result
  }, true)
  reader.readAsDataURL(input)
 }, true)
})

これで読み込んだ画像ファイルを出力することができるようになった。またAjaxを用いることによって、データベースに保存することもできる。

参考資料

山田祥寛様 「javascript本格入門」

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

toggleClassと同じような動作のjavascript

なにかをクリックすると何かが開く(表示される)アレ。
JQueryで色々ゴリゴリやってるウェブサイトだとこの部品だけjavascriptに書き換えても恩恵は皆無。
またtoggleのような開閉時にアニメーション付けたいとかはひと手間必要。

HTML構造

.trigger#js-trigger ボタン
.target#js-target
  %ul
    %li リスト
    %li リスト
    %li リスト

CSS

.target{
  display: none;
  &.show{
    display: block;
  }
}

BEFORE

一般的なJQueryコード

$(function){
  $('#js-trigger').click(function(){
    $('#js-target').toggleClass('show');
  })
}

AFTER

脱JQueryコード

var trigger = document.getElementById('js-trigger');
if(trigger){
  trigger.addEventListener('click', function(){
    var target = document.getElementById('js-target');
    var showClass = 'show'
    if(target.className.match(showClass)){
      target.classList.remove(showClass);
    }else{
      target.classList.add(showClass);
    }
  }, false);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Redux Hooks API + Jest + TypeScript でユニットテスト

だいぶ前に書いたReact Redux Hooks API でユニットテストをたまに見てくれる方もいるようですが、Sinonに依存しているサンプルしか書いていないため、参考までにここではJestで解決する例を取り上げてみます。
また、useSelectorを複数回実行するコンポーネントをテストしたいといったケースにどう対応するかも紹介してみます。

前提

  • Jest: 24.9.0
  • Enzyme: 3.11.0

今回テストするコンポーネントはこちらです。

Counter.tsx
import React, { FC, useCallback } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { RootState } from 'path/to/store'
import { increment } from 'path/to/actions'

export type CounterSelectedState = number

const Counter: FC = () => {
  const count = useSelector<RootState, CounterSelectedState>(
    ({ counter }) => counter,
  )
  const dispatch = useDispatch()
  const handleClick = useCallback(() => {
    dispatch(increment())
  }, [dispatch])

  return (
    <div>
      <span>{count}</span>
      <button onClick={handleClick}>+1</button>
    </div>
  )
}

export default Counter

CounterSelectedState でエイリアスを作っているのは、後でテストを書く際に使うためです(別に必要なければなくてもいいです)。

Jestを使ってテストを書く

まずテストしたいことを書いてみましょう。

Counter.spec.tsx
import React from 'react'
import { shallow } from 'enzyme'
import Counter from './Counter'
import { increment } from 'path/to/actions'

describe('<Counter />', () => {
  it('dispatches increment action', () => {
    const wrapper = shallow(<Counter />)
    wrapper.find('button').simulate('click')

    expect(wrapper.find('span').text()).toBe('10')
    expect(increment).toBeCalledTimes(1)
  })
})

このままでは当然まだ動きません。

useSelector, useDispatchのスタブ

useSelectoruseDispatchのスタブを用意します。

import React from 'react'
import { shallow } from 'enzyme'
+ import Counter, { CounterSelectedState } from './Counter'
import { increment } from 'path/to/actions'
+ import { useSelector, useDispatch } from 'react-redux'


+ jest.mock('path/to/actions')
+ jest.mock('react-redux')
+ const useSelectorMock = useSelector as jest.Mock<CounterSelectedState>
+ const useDispatchMock = useDispatch as jest.Mock

describe('<Counter />', () => {
+   beforeEach(() => {
+     useSelectorMock.mockReturnValue(10)
+     useDispatchMock.mockReturnValue(jest.fn())
+   })
+   afterEach(() => {
+     jest.resetAllMocks()
+   })

  it('dispatches increment action', () => {
    const wrapper = shallow(<Counter />)
    wrapper.find('button').simulate('click')

    expect(wrapper.find('span').text()).toBe('10')
    expect(increment).toBeCalledTimes(1)
  })
})

Jestでは、jest.mockでモジュールを自動モックすることができます(第2引数にファクトリを指定したりできますが、今回は各テストで異なる値を返したい場合に対応するため、別のアプローチをとります)。

jest.mock('react-redux')

TypeScriptでテストを書いていると、mockReturnValueuseSelectorから生えていないので、
力技ですが下記のようにダウンキャストします。
この際、jest.Mock<CounterSelectedState>とすることで、返り値の型をCounterSelectedStateとして指定することができます。

const useSelectorMock = useSelector as jest.Mock<CounterSelectedState>
const useDispatchMock = useDispatch as jest.Mock
  • 各テスト実行前に、useSelectorは10を返す
  • 各テスト実行後にリセット

させます。
jest.Mock<CounterSelectedState>としたことで、useSelectorMock.mockReturnValueでは、CounterSelectedStateを返すように補完が効いてくれます。
今回はnumberなのでメリットはあまり感じませんが、オブジェクトを返す場合などに有効です。

beforeEach(() => {
  useSelectorMock.mockReturnValue(10)
  useDispatchMock.mockReturnValue(jest.fn())
})

afterEach(() => {
  jest.resetAllMocks()
})

これでテストが通ります。

各テストで異なる値を取りたい

上記では1つのテストしか存在しておらず、useSelectorMock.mockReturnValue(10)でカウントが10固定になっていますが、
異なる値を扱いたい場合が往々にして存在します。
その場合はテスト内部で直接値を指定できます。

  it('has count as 11', () => {
    useSelectorMock.mockReturnValue(11)

    const wrapper = shallow(<Counter />)
    expect(wrapper.find('span').text()).toBe('11')
  })

複数回のuseSelectorに対応する

複数の値をストアから参照したい場合に、一度のuseSelectorで複数の値をまとめたオブジェクトとして返すのではなく、
useSelectorを複数回実行し、小さな単位で取得することが推奨されています
FYI: Call useSelector Multiple Times in Function Components
つまり、先程のコンポーネントで別のStateの値(ここではsomething.isActiveを例とします)を使う際に、

export type CounterSelectedState = {
  count: number
  isActive: boolean
}

const count = useSelector<RootState, CounterSelectedState>(
  ({ counter, something }) => ({
    count: counter,
    something: something.isActive,
  }),
)

ではなく、

export type CounterSelectedState = number

const count = useSelector<RootState, CounterSelectedState>(
  ({ counter }) => counter,
)
const isActive = useSelector<RootState, boolean>(
  ({ something }) => something.isActive,
)

が望ましいということです。
前者であれば、上記のuseSelectorMock.returnValueで対応可能ですが、
後者は複数回useSelectorを実行しているため、異なるアプローチが必要となります。

色々と対応方法はありますが、簡単にいくつか対応方法を紹介しておきます。

方法1: mockReturnValueOnceを使う

JestのモックにはmockReturnValueOnceメソッドが存在しており、呼び出し回数に応じて返す値をコントロールすることができます。ただし、useSelectorを呼び出す順番に依存するため、扱いづらさは残ります。

test('mockReturnValueOnceを使った場合', () => {
  useSelectorMock
    .mockReturnValueOnce(11)
    .mockReturnValueOnce(true)
})

※ ライフサイクルが絡む場合のテストなどにおいて、Enzymeのshallowではなくmountをつかってテストを行いたい場合には、mockReturnValueOnceをうまく使うことでHooksの返り値をコントロールすることができますので、こういったユースケースには有効かもしれません。

方法2: mockImplementationを使う

このケースでは、re-ducksなどの設計パターンに見られる、Stateから値を取得するためのselectorと呼ばれる関数を用います。上記の例でいえば、useSelectorに渡しているコールバック関数がそれですが、mockImplementationで参照を比較するため、関数として定義します。

ここでは下記のような実装を想定します。

export function getCount({ counter }: RootState): number {
  return counter
}

export function isSomethingActive({ something }): boolean {
  return something.isActive
}
const count = useSelector<RootState, CounterSelectedState>(getCount)
const isActive = useSelector<RootState, boolean>(isSomethingActive)

テストでは、useSelector.mockImplementationを使い、引数に応じて返り値をコントロールすることができます。
つまり、useSelectorに渡された引数の関数の参照が一致する場合に、それに応じた値を返すということになります。

beforeEach(() => {
  useSelectorMock.mockImplementation(selector => {
    if (selector === getCount) {
      return 10
    } else if (selector = isSomethingActive) {
      return true
    }
  })
})

方法3: カスタムフックを定義する

これが一番ラクです。
useSelectorMockは使わずに、useSelectorを内部で呼び出すカスタムフックを定義し、
テストではこのカスタムフックのスタブを用意することで対応します。

export function useCount() {
  return useSelector<RootState, number>(({ counter }) => counter)
}

export function useIsActive() {
  return useSelector<RootState, boolean>(({ something }) => something.isActive)
}
jest.mock('path/to/hooks')
const useCountMock = useCount as jest.Mock<number>
const useIsActiveMock = useIsActive as jest.Mock<boolean>

beforeEach(() => {
  useCountMock.mockReturnValue(10)
  useIsActive.mockReturnValue(true)
  useDispatchMock.mockReturnValue(jest.fn())
})

かなりシンプルな例ではありますが一助になれば幸いです。
他にも色々と方法がありますが、「おすすめのこんなやり方あるよ」という方はぜひ共有ください:bow:

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

VSCodeのデバッグ機能でJavascriptとTypescriptをデバッグする

デバッグにはconsole.logを使いがちですが、新しいコードを理解する場合はデバッグ機能の方が便利です。VSCodeでJavascriptとTypescriptをデバッグするのが思った以上に簡単です!

Javascript

① デバッグしたいファイルを開けて、デバッグアイコンをクリックし、Debug with Node.jsをクリックします

Screen Shot 2020-01-14 at 12.51.08.png

以上です。設定したブレークポイントがあれば、ちゃんと止まってくれます!

Screen Shot 2020-01-14 at 12.55.32.png

② たまには、トランスパイルしないと実行できないJSもあるので、もちろんそのままデバッグはできません。この場合は、もう少しだけ複雑になりますが、その方法も下で紹介します。

Typescript

トランスパイル必要なJSの場合と、Typescriptの場合は、手順が似ているので、一緒に紹介します。

① デバッグしたいTSファイルを開けて、デバッグアイコンをクリックし、Create a launch.json fileをクリックします。

Screen Shot 2020-01-14 at 13.01.32.png

すると、ワークスペースのルートに.vscodeフォルダーが生成され、その中にlaunch.jsonファイルができます。このファイルには、デバッグ設定が記載されています。

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program", // 好きな名前にできる
      "skipFiles": ["<node_internals>/**"], // nodeランライムの関数には入らないようにする
      "program": "${workspaceFolder}/file.ts", // デバッグしたいファイル
      "outFiles": ["${workspaceFolder}/dist/*.js"]
    }
  ]
}

programoutFilesが自動的に生成されるので、もし間違いがあればまず修正してください。outFilesというのは、ビルド・トランスパイルのアウトプット先のフォルダーです。VSCodeにそれを知らせると、.tsファイルの中でブレークポイントつけられるようになります。

③ ビルド方法をVSCodeに教えます。デバッグを実行する前に、自動的に再ビルドしたい場合は、タスクとしてlaunch.jsonの中に書きます。

たとえば、package.jsonの中のscriptsは以下だとします。

"scripts": {
    "build": "tsc file.ts --sourceMap --outDir dist"
}

この場合は、launch.jsonpreLaunchTaskとして、npm: buildを指定するだけでOKです。

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "skipFiles": ["<node_internals>/**"],
      "program": "${workspaceFolder}/file.ts",
      "preLaunchTask": "npm: build", // package.jsonが自動的に参照されます
      "outFiles": ["${workspaceFolder}/dist/*.js"]
    }
  ]
}

TSの手順を紹介したけど、babelによるトランスパイルの場合も全く同じです。

気をつけるべきなのは、ソースマップです。これがないと、元のファイルにブレークポイントをつけても、デバッガーが止まってくれないです(現在の実行の位置がわからないからです)。なので、ソースマップもちゃんと生成するようにしましょう。

これで、TSもデバッグできます!

Screen Shot 2020-01-14 at 13.24.01.png

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

高卒プログラマーのアルゴリズム体操(XORで奇数検知)

codewars.com の問題を解いていて、ほぇえええ(感嘆)なコードを見たので、理解するまでの流れを復習の意味でまとめています。

書いてる人

  • 数学知識は皆無(高校でやったはずですがほとんど覚えていません、、算数ができる程度です)
  • 主にJSを利用(以下の問題もJSで解いています)
  • JSに関して初学者ではないが、リファレンスに載ってる関数を全部把握してるかと言われると微妙
  • アルゴリズムってねーあれだよねー

という感じですので、同じような人におすすめです。
codewars.comprojecteuler.net を解きながら初歩の初歩から数学やアルゴリズムの勉強をしている最中です。

codewars

codewars.com はサイト上でプログラミングの問題を解いていくサービスです。
以下が詳しいです。
https://qiita.com/javacommons/items/7c473cda7825ab99e08c

今回の問題

https://www.codewars.com/kata/54da5a58ea159efa38000836

ざっくり説明すると、与えられた配列の中から奇数回出現する数字を求めなさい。
奇数回出現する数字は必ず一つだけです。
という問題です。例えば

[5,4,3,2,1,5,4,3,2,10,10]

上の配列では1だけが奇数回の出現、他はすべて偶数回出現します。
正解はになります。

まず愚直な回答

自分が何も考えすに書いたコードです。

function findOdd(researchArray) {
    let counts = {}
    researchArray.map(key=>{
        counts[key] = counts[key] ? counts[key] + 1 : 1;
    });
    let keyNumber = Object.keys(counts).find(function (key) {
        return counts[key] % 2 == 1;
    });
    return Number(keyNumber);
}

ハッシュに追加しながら、既存のkeyの場合はvalueに1を足していき、最後にハッシュから奇数のセットを探しています。
我ながら誰でも思い付く何のひねりもないすごく普通の回答に見えます。

感動したコード

上記のコードを提出した後、他の方の回答を見たのですが簡単に奇数を探し出してるコードがありました。
(元のコードはワンライナーなのですが見やすいように少し書き換えています)

function findOdd(researchArray) {
    return researchArray.reduce((a, b) => {
        return a ^ b;
    });
}

すっごい!意味わからない!

なにこれ!??

まずこの謎コードの要素を分解します

reduce関数

便利です、自分も最近知りました。
配列の左から順にループしながら処理を行っていく関数なのですが
上のコードの場合、まず1ステップ目では
a = researchArray[0]
b = researchArray[1]

2ステップ目は
a = コールバック関数内でreturnした値
b = researchArray[2]

となり、これが配列の最後まで繰り返されます。
例えば配列を左から順に全部掛け算したい時は

[1,2,5,6,8,34].reduce((a, b) => {
    retuen a * b;
});

と書くことが出来ます。

a ^ b

XOR(排他的論理和)ってやつですね、普段使う事はないけど何となく知っている程度です。
2つのビットが同じなら0,違ったら1を返します。
以下の4パターンです。

1 ^ 1 = 0;
1 ^ 0 = 1;
0 ^ 1 = 1;
0 ^ 0 = 0;

要するに

reduce関数を使って配列を左から順にXORを求めているという状態です。
なんですが、なんで奇数の数字が返ってくるのか意味不明です。

何が起きてるのか確認してみる

問題を単純化して実際の数字を見ながら確認していきます

2進数で考えてみる

まず与えられた配列が2進数だった場合で考えてみました。

[1,0,0,1,1]

奇数個の数字は1になります。

処理をステップ毎に確認

この配列を順番にxorしてみます。

[ 1 ]  a = 1 
       b = 0
[ 2 ]  a = 1 
       b = 0
[ 3 ]  a = 1
       b = 1
[ 4 ]  a = 0
       b = 1
return     1

確かに1になりました。

10進数でも考えてみる

次は10進数の配列で考えてみます、ビット桁が大きくならない様にMAXを3で確認しました。

[3, 1, 0, 0 ,1]

処理をステップ毎に確認

そもそも10進数をXORした時にどうなるのか分かっていなかったのですが、2進数に変換して各桁のビットに対してXORが走るそうです。
元の10進数と2進数を並べるとこんな流れになります。

[ 1 ]  a = 3: 11
       b = 1: 01
[ 2 ]  a = 2: 10
       b = 0: 00
[ 3 ]  a = 2: 10
       b = 0: 00
[ 4 ]  a = 2: 10
       b = 1: 01
return     3: 11

正しい答えが出ています。なんで、、

XOR交換アルゴリズム

さっぱり分からないのでXORについて調べているとXOR交換アルゴリズムというものに出くわしました。
以下が詳しいです。
https://qiita.com/Riliumph/items/9f3719b2db39cf111208

XORを繰り返す事でAとBの値が入れ替わります。

a = 2;
b = 5;
a = a ^ b;
b = b ^ b;
a = a ^ b;
// a == 5, b == 2

これも不思議なのですが、以下の4条件が成り立つことで交換が動作するとのこと。

  1. 左右交換可能: a ^ b = b ^ A
  2. 順番入れ替えても答え一緒: (a ^ b) ^ c = a (b ^ c)
  3. 0でXORすると元の値: a ^ 0 = a
  4. 自身でXORすると0: a ^ a = 0

うん、、、、という感じですが、上に貼った@Riliumphさんの記事の証明部分を見ると確かに動きそうな事が理解できます。

同じ条件を奇数検知に当てはめる

XORの性質を奇数検知に当てはめてみます。

  1. 条件2により配列の順番は関係なく同じ計算結果になる
  2. 条件4により自身同士(偶数回)のXORの場合0になるので消えていき、奇数回の数字だけが残る
  3. 条件3により値が0の場合は結果に反映されないので、0が奇数で他の数字が偶数場合は0以外の数字もXORの結果0になり最終的に正解の0を返す

おお、動く。こっちの方が簡単ですね。
そして必ず奇数個出現する数字は1つという条件がないと成立しないコードだという事も分かりました。
ってことは、XOR使う回答を想定した出題って事ですね。みんなすごい。

まとめ

問題を解いて知ったこと

  • XORの特性
  • 特性を理解して道具を使いこなすと想像出来なかった成果が生まれる
  • ビットに変換するというのは数字の構造分析みたいな感じなのかなという気がした

今後もアルゴリズムや数学の勉強をしがてらQiitaに書き残していこうと思います

アルゴリズム体操のインデックス (週イチ更新が目標です)

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

Reactタグの作り方

はじめに

備忘録です。
Layoutタグを作ろうかな、と思ったときに以前のコードを見直す必要があったので、
整理とまとめておくために記述します。

もっといい方法があったら教えてください。
タイプミスあったらすみません。

code

Layout.jsxとUseLayout.jsxは同じ階層にあるものとします.
本当は分けた方がいいです...

Typescriptにする場合はChildrenはReactElementreactからimportして割り当ててあげると良い感じで行けます.
children: ReactElement<any>こんな感じで。

Layout.jsx

import React, { Fragment, cloneElement } from "react";

const Layout = props => {
  const { title, children } = props;
  return (
    <Fragment>
      <div>
        {title}
      </div>
      {cloneElement(children)}
    </Fragment>
  )
}

export default Layout;

UseLayout.jsx

import React from "react";
import Layout from "./Layout";

const UseLayout = () => (
  <Layout title="Layoutです">
    <div>
      componentを配置してね
      <button>ぼたんです</button>
    </div>
  </Layout>
)

Dialogを作りたい場合とかイベントを動的に変化させたい場合

material-uiの同じようなDialogを複数ページに適用させたい場合
ただし、イベントは変えたいよって場合
TypeScriptで書いていますが、JavaScriptにしたい場合はpropsを消して、
= ({children, title, ...}) =>= props =>にすればOK

Dialog.tsx

import React, { cloneElement, ReactElement } from "react";
import Dialog from "@material-ui/core/Dialog";
import Button from "@material-ui/core/Button";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";

type Props = {
  children: ReactElement<any>,
  title: string,
  description: string,
  disagree: string,
  agree: string,
  action?: Function | null,
}

const Dialog: React.FC<Props> = ({
  children,
  title,
  description,
  disagree,
  agree,
  action = null,
}) => {
  const [open, setOpen] = React.useState(false);
  return (
    <span>
      {cloneElement(children, {onClick: () => setOpen(true)})}
      <Dialog
        open={open}
        onClose={() => setOpen(false)}
      >
        <DialogTitle>{title}</DialogTitle>
        <DialogContent>
          <DialogContentText>
            {descriotion}
          <DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button onClick={() => setOpen(false)}>
            {disagree}
          </Button>
          <Button onClick={() => {
            action !== null & action()
            setOpen(false);
          }}>
            {agree}
          </Button>
        </DialogActions>
      </Dialog>
    </span>
  )
}

ちょっと解説

上位から受け取ったchildrenはコンポーネントとしてDialog.tsxにもらえるので、

<Layout>
  <Button>Click!</Button>
</Layout>

のように使用できる.

タイトル, OKボタン, NGボタン, 説明は今回は必須の引数としているので

<Layout
  title="test"
  description="OK or NG?"
  disagree="NGボタン"
  agree="OKボタン"
  action={() => concole.log("OK or NGが押されました")}
>
  <Button>Click</Button>
</Layout>

となる.

そしてコンポーネント単位でアクションを設定したいので
actionに実行したい関数を渡してあげるとその関数を実行することが出来る.
react-reduxを使用する場合はactiondispatchを当てて使うこともできます.

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

ブロックリーゲーム

[ブロックリー・ゲーム]https://blockly.games/?lang=ja

この問題は導入として優しい。

しかし、10番目の問題はいきなり難易度が高く、解かなくても先に進められるようにできている。

問題10
10ブロック以内にゴール(赤いピン)まで進むプログラムの作成
image.png

★右壁の法則
とりあえず右壁の法則で解こうと思ってなんとなくやったら偶然できた解
※右の壁を絶えず触りながら迷路を進む方法

8ブロック使用
image.png

ブロックリーゲームズ1.gif

★最短ルートを考える

ループは1つのみ縛りがある。変数も使えない。
そこで分岐ネストというハードコードになるが仕方ない。

曲がる状態がそれぞれ違うので幸いにして最短ルートが作れる。
ぎりぎり10ブロック使用
image.png

ブロックリーゲームズ2.gif

分岐のネストはバグに起因するのでやってはヤバいのだが、この問題では解法が無いので仕方ない。
こういう問題を解いてしまうと勘違いする人が出てきそうなのが怖い。

ゴール到着の動きがおかしかったので修正

まっすぐ進む の位置を変えただけ

★最短ルート(修正版)

ブロックリーゲームズ3.gif

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

【Nuxt.js】inject is 何?

TL;DR.

  • injectを利用すると関数を共通化することができる
  • 関数はthis.$hogeで呼び出すことができる

injectを使用した処理の例

ページに'hoge'を返す関数を作ってみる

<plugins/hoge.js>

export default function injectHoge(context, inject) {
  const returnHoge = num => {
    return 'hoge' * num
  }
  inject('hoge', { get: returnHoge })
}

これをnuxt.config.jsに設定する

<nuxt.config.js>

plugins: [
  { src: '@/plugins/hoge', ssr: false }
]

このようにするとthis.$hoge.get(num)でVueインスタンスで関数を実行することができる

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

【Nuxt.js】 inject is 何?

TL;DR.

  • injectを利用すると関数を共通化することができる
  • 関数はthis.$hogeで呼び出すことができる

injectを使用した処理の例

ページに'hoge'を返す関数を作ってみる

plugins/hoge.js
const returnHoge = () => {
  return 'hoge'
}

export default(context, inject) {
  inject('hoge', returnHoge)
}

これをnuxt.config.jsに設定する

nuxt.config.js
plugins: [
  { src: '@/plugins/hoge', ssr: false }
]

このようにするとthis.$hogeでVueインスタンスで関数を実行することができる

引数を渡したい場合や、関数名を代替したい場合はこのようにする

const calcNumber = (num) => {
  return 3 * num
}

export default(context, inject) {
  inject('calc', { get: calcNumber })
}

これでthis.$calc.get(4)を実行すると12が返ってくる

コンポーネントから呼び出す場合

asyncDataやfetchで使用したい場合はこのように呼び出す

pages/index.vue
export default {
  asyncData(context) {
    context.app.$hoge()
  }
}

injectを使用する利点

  • 確定した処理の共通化が可能
    • 関数を呼び出して必ず実行される処理や分岐に対しては有効
    • ex. 取得処理が成功したら値を返し、失敗したらエラーの処理を実行するなど
  • いちいちページでimportしなくていい
    • これに関しては何を呼び出しているかが明示的にわかりにくくなるので、良し悪しがある
    • 共通化したい処理の中でも関数の実態を把握できるような関数名や共通認識があるものであれば良いが、そうでなければページに残したいところ。。
    • axiosやfirebaseのget, delete、日付処理など、誰が見てもわかり切っているものなら便利そう
  • Vuexストア内でも同様にthis.$で関数を呼ぶことができる
  • 同様の方法にmixinを使用して関数をグローバルに実行する方法があるが、injectの場合はasyncDatafetch内でも共通の関数を利用することができる

参照

Nuxt.js 統合された注入

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

独立したコアレイヤパターンをJavaScript(TypeScript)で実装してみる

@shin1x1さんの独立したコアレイヤパターンをJS(TS)でも利用できるといいかなと思い、サンプルコードを作成してみた。

独立したコアレイヤパターンとは?

  • アーキテクチャパターンのひとつ
  • コアレイヤとアプリケーションレイヤの2つのレイヤからなる
    • コアレイヤ
      • コアレイヤロジックやビジネスロジックを実装する
      • 外部のIF(WebAPIやDBのormなど)には依存しない
    • アプリケーションレイヤ
      • コアレイヤと外部のIFの連携を実装する
      • 外部のIF(WebAPIやDBのormなど)に依存する
  • スーパーヒーローがいなくても使えるパターン

ゴール

サンプルとして、記事に対するファボ(お気に入り)を設定・解除する機能のユースケースを作成する。

サンプルリポジトリ

ohnaka0410/Vue-Ts-Independent-Core-Layer-Pattern

実装

ベース

サクッとTS環境を構築したかったので、@vue/cliを利用した。

コアレイヤ

コアレイヤは、ユースケースとユースケースが利用するサービスのポート(インタフェース)を実装する。ユースケースは、想定される処理を順に記述していく。データベースアクセスなど技術詳細を利用したい場合、必要な API をインタフェースとして実装して、それに依存しておく。ユースケース内では、このインタフェースを利用して値の取得や保存などの処理を行う。

本家サイトより引用

モデル

src/modules/Sample/Article/Core/Model/Article.ts
/**
 * 記事クラス
 */
export default class Article {
  /** 記事ID */
  private id: number;

  /**
   * コンストラクタ
   */
  public constructor(id:number) {
    this.id = id;
  }

  /**
   * ID取得処理
   */
  public getId() {
    return this.id;
  }
}
src/modules/Sample/Article/Core/Model/ArticleFav.ts
/**
 * 記事お気に入りクラス
 */
export default class ArticleFav {
  /** 記事ID */
  private articleId: number;
  /** ユーザID */
  private userId: number;
  /** 記事お気に入りID */
  private id: number | undefined;

  /**
   * コンストラクタ
   */
  public constructor(articleId:number, userId:number, id?:number) {
    this.articleId = articleId;
    this.userId = userId;
    this.id = id;
  }

  /**
   * 記事ID取得処理
   */
  public getArticleId(): number {
    return this.articleId;
  }

  /**
   * ユーザID取得処理
   */
  public getUserId(): number {
    return this.userId;
  }

  /**
   * 記事お気に入りID取得処理
   */
  public getId(): number | undefined {
    return this.id;
  }
}

エクセプション

src/modules/Sample/Article/Core/Exception/NotFoundException.ts
/**
 * 404エラーエクセプションクラス
 */
export default class NotFoundException extends Error {}

ポート

src/modules/Sample/Article/Core/Port/ToggleFavPort.d.ts
import Article from '../Model/Article';
import ArticleFav from '../Model/ArticleFav';

/**
 * お気に入り切り替えIF
 */
export default interface ToggleFavPort {
  /**
   * 記事取得処理
   */
  findArticle(articleId: number): Promise<Article | undefined>;
  /**
   * 記事お気に入り取得処理
   */
  findArticleFav(
    articleId: number,
    userId: number
  ): Promise<ArticleFav | undefined>;
  /**
   * 記事お気に入り保存処理
   */
  saveArticleFav(articleFav: ArticleFav): Promise<void>;
  /**
   * 記事お気に入り削除処理
   */
  deleteArticleFav(articleFav: ArticleFav): Promise<void>;
}

ユースケース

src/modules/Sample/Article/Core/UseCase/ToggleFav.ts
import ToggleFavPort from '../Port/ToggleFavPort';
import NotFoundException from '../Exception/NotFoundException';
import Article from '../Model/Article';
import ArticleFav from '../Model/ArticleFav';

/**
 * お気に入り切り替えユースケースクラス
 */
export default class ToggleFav {
  /**
   * お気に入り切り替え実装ポート
   */
  private port: ToggleFavPort;

  /**
   * コンストラクタ
   */
  public constructor(port: ToggleFavPort) {
    this.port = port;
  }

  /**
   * ユースケース実行処理
   */
  public async run(
    articleId: number,
    on: boolean,
    userId: number
  ): Promise<void> {
    if (!articleId) {
      throw new NotFoundException('articleId not found');
    }

    const article: Article | undefined = await this.port.findArticle(articleId);
    if (!article) {
      throw new NotFoundException('Article not found');
    }

    const articleFav: ArticleFav | undefined = await this.port.findArticleFav(
      article.getId(),
      userId
    );

    if (on) {
      await this.port.saveArticleFav(
        articleFav || new ArticleFav(article.getId(), userId)
      );
    } else {
      if (articleFav) {
        await this.port.deleteArticleFav(articleFav);
      }
    }
  }
}

アプリケーションレイヤ

サービスレイヤは、UI や データベースなどアプリケーション外部との連携を実装する。

このレイヤでは、2つの責務を担う。1 つ目は、UI からのアクションを契機にユースケースを実行することである。コアレイヤから提供されるユースケースクラスを実行することになる。2 つ目は、実行するユースケースが依存しているポートに対するアダプタを実装することだ。インタフェースを満たせば、その実装の詳細は自由である。

本家サイトより引用

アダプタ

実際はaxiosなどを使ってAPIを叩いたり、FireBaseのFireStorageへの保存処理を行う想定です。

src/modules/Sample/Article/Application/Adapter/ToggleFavAdapter.ts
import ToggleFavPort from '../../Core/Port/ToggleFavPort';
import Article from '../../Core/Model/Article';
import ArticleFav from '../../Core/Model/ArticleFav';

/**
 * お気に入り切り替えアダプタクラス
 */
export default class ToggleFavAdapter implements ToggleFavPort {
  /**
   * 記事取得処理
   */
  public async findArticle(articleId: number): Promise<Article | undefined> {
    console.log("findArticle");
    console.log(articleId);
    // call api
    // return dummy data
    return new Article(articleId);
  }
  /**
   * 記事お気に入り取得処理
   */
  public async findArticleFav(
    articleId: number,
    userId: number
  ): Promise<ArticleFav | undefined> {
    console.log("findArticleFav");
    console.log(articleId);
    console.log(userId);
    // call api
    // return dummy data
    const dummyArticleFavId =1;
    return new ArticleFav(articleId, userId, dummyArticleFavId);
  }
  /**
   * 記事お気に入り保存処理
   */
  public async saveArticleFav(articleFav: ArticleFav): Promise<void> {
    console.log("saveArticleFav");
    // call api
  }
  /**
   * 記事お気に入り削除処理
   */
  public async deleteArticleFav(articleFav: ArticleFav): Promise<void> {
    console.log("deleteArticleFav");
    // call api
  }
}

呼び出し部分

※DIコンテナの実装は省略。

src/App.vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png" />
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
    <button @click="toggleFav">ToggleFav</button>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import HelloWorld from "./components/HelloWorld.vue";

import ToggleFavUseCase from "./modules/Sample/Article/Core/UseCase/ToggleFav";
import ToggleFavAdapter from "./modules/Sample/Article/Application/Adapter/ToggleFavAdapter";
const useCase = new ToggleFavUseCase(new ToggleFavAdapter());

@Component({
  components: {
    HelloWorld
  },
  methods: {
    toggleFav: () => {
      const articleId: number = 11;
      const on: boolean = true;
      // const on: boolean = false;
      const userId: number = 22;
      useCase.run(articleId, on, userId);
    }
  }
})
export default class App extends Vue {}
</script>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

まとめ

  • TypeScriptであれば型が利用できるので、利用できそう。(VanillaJSでも頑張ればなんとか。。。)
  • クリーンアーキテクチャーほどレイヤーが多くないので、比較的気軽に導入できそう。
  • 案件規模や開発メンバーのスキルに応じてドメインモデルを入れることもできそう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript勉強の記録その20: alertとconfirmを使ってメッセージボックスを表示

alertを使ってメッセージボックスを画面に表示する

alert('表示したいメッセージ')とすることで、画面に以下のようなメッセージボックスを表示することができます。
image.png

index.js
alert('このページはまだ準備中です');

confirmを使って画面にメッセージボックスを表示

confirm('表示したい内容')とすることで、OKかキャンセルかどちらかを選べるメッセージボックスを表示することができます。
image.png

alertとの大きな違いとしては、confirmは返り値としてBooleanを返してくれますので、ユーザがOKを選んだ時と、キャンセルを選んだ時で違う処理を実行することができます。例えば記事管理アプリを作っていたとして、なんらかの記事を削除する際などに使えるかと思います。 

index.js
const answer = confirm('本当に削除しますか?');

if (answer) {
  console.log(answer); //OKを選んだ時はコンソールにtrueと表示される
} else {
  console.log(answer); //キャンセルを選んだ時はコンソールにfalseと表示される
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript勉強の記録その19: Dateオブジェクトを使って現在時刻を取得

Dateオブジェクトを利用して現在時刻を取得

new Date()としてあげると、現在時刻に関するデータを持つオブジェクトを生成することができます。
Dateオブジェクトには標準でいくつかのメソッドが用意されてあり、日時に関するデータを取得することができるようになっています。

index.js
const d = new Date();
console.log(d);
//=>Mon Jan 13 2020 23:59:08 GMT+0900 (日本標準時)

getFullyearメソッド

以下の例では、生成されたDateオブジェクトがもつ西暦の情報を取得しています。

index.js
const d = new Date();
console.log(d.getFullYear());
//=>2020

getMonthメソッド

読んで字の如しですが、月に関する情報を取得します。
たた注意する点としては、JavascriptのgetMonthメソッドで返ってくる値は、0は1月を表し、1は2月を表します。
したがって、返ってくる値に+1としてあげることで、現在の月を表すことができます。

index.js
const d = new Date();
console.log(`${d.getMonth() + 1}月`);
//=>1月

getDateメソッド

日付に関する情報を取得します。

index.js
const d = new Date();
console.log(`${d.getDate()}日`);
//=>14日

getDayメソッド

曜日に関する情報を取得します。
こちらも注意が必要で、日曜日は0 月曜日は1 火曜日は2という感じで返ってきます。

index.js
const d = new Date();
console.log(d.getDay()); 
//=>2

そのほか時間に関するメソッド 

時間に関するメソッドは以下となっています。

index.js
console.log(d.getHours()); //0~23時
console.log(d.getMinutes()); //0~59分
console.log(d.getSeconds()); //0~59秒
console.log(d.getMilliseconds()); //0~999ミリ秒
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む