20200110のJavaScriptに関する記事は29件です。

vueにおいて関数のレスポンシブ化をした話

会津大学でプログラミングを書いてる学生です。

モバイルのときとPCのときで別の挙動をする関数を作った話をします。

もくじ


がいよう

授業でwebアプリ開発をしていて、その中でモバイルのときとPCのときで別の挙動をするようにしたい状況がありました。

  • あるリストがあり、それをクリックするとモーダルウィンドウが開き詳細が見ることができる。
  • そのリストはそれぞれ固有のIDを持っている。
  • 検索フォームがあり、IDと検索文字が部分一致するリストを返す(?)

てな感じ。ですが、使用する状況がモバイルが多く、IDと検索文字が完全一致した時に直接モーダルウィンドウを開いたほうが使いやすいという結論に至りました

これを関数のレスポンシブ化と呼びます(???)。

というわけでいっちょやってみます

しくみ

if(this.$el.offsetWidth < 670){
   ...
}

結構力技かもしれないですけどこんな感じで作れました。

this.$elで現在の画面の情報とかいろんなものがごっちゃになっているelement型?なるものを使えるらしい。

それのoffsetWidthを見るとウィンドウサイズが取れるっぽく、コレを使用してcssのレスポンシブ対応的なノリで記述した。

もっと他に良い方法があるかもしれないけど、今回は力技で対応。

参考リンク API - Vue.js

こんかいまなんだこと

力技っぽいけど、関数のレスポンシブ化ができた。

offsetWidthとかoffsetHeightとか、this.$elの中身を見て長さ高さを取得するとか無理やり色々いじっていたので、普段触れない部分に触れられたのは良かった点だと思う。

今後これを使って何か面白いものを作れそう。

多分。

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

【JavaScript】selectbox(プルダウン)の使い方メモ

1.はじめに

かなり昔に、jqueryのプラグインを使って100万行くらいのデータをグラフ化する
ツールを作る依頼を受けた。
その時に初めてjavascriptを調べて、ツールを作ったがそれ以来触ることが
無くなってしまった。

思い出すためにも、恐竜と鳴き声の回数をセットしたセレクトボックスを用意して
ボタンを押すと鳴き声を表示させるプログラムを練習用として作ってみた。

2.恐竜の鳴き声を指定回数表示してみよう。(ソース)

今回のポイント

  • セレクトボックスの使い方
  • セレクトボックスの値の取得の仕方
  • 指定場所のテキストの変更

鳴かせる恐竜は、我らがダークヒーロー「ティラノサウルス」と、
走る鎧こと「ユーオプロケファラス」
にした。
ユーオプロケファラスは尻尾攻撃が得意とのこと(jwaより)
ユーオプロケファラスの鳴き声は知らないので(というかティラノサウルスの声すら
聞いたことない)、尻尾攻撃の音にしてみた。

下のコードを記載したファイル(こんなのコピーする人がいるか
疑問だけど)を適当なところに保存して実行すると、実行イメージのように表示される。

dino_cry_for.html
<!DOCTYPE html>
<p>鳴き声<br>
<span id="d_cry"></span></p><br>

<form name="test">
    <select name="dino">
        <option value="Tyranno">ティラノサウルス</option>
        <option value="Euoploce">ユーオプロケファラス</option>
    </select>
    <select name="cry_n">
        <option value="2">2</option>
        <option value="4">4</option>
        <option value="6">6</option>
    </select>
</form>
<input type="button" value="dinosor_crying!!" onClick="OnClickCrying()"/>

<script type="text/javascript">

function OnClickCrying(){
    //セレクトボックスのid
    const dino1 = document.test.dino;

    // 値(数値)を取得
    const num_dino = dino1.selectedIndex;

    const cry_n1=document.test.cry_n;
    //セレクトボックスの何番目を選択したか
    const idx_cry=cry_n1.selectedIndex;
    //セレクトボックスで選択した値を取得
    const num_cry2=cry_n1.options[idx_cry].value;

    var str_dino="";
    var c_dino="";

    if(num_dino == 0){
        c_dino="おおーん!!";
    }else{
        c_dino="ぴしっ!";
    }
    for(var i=0;i<num_cry2;i++){
        str_dino=str_dino+c_dino+"<br>";
    }
    document.getElementById("d_cry").innerHTML = str_dino;
}
</script>
</html>

3.実行結果

js_dino_for.jpg

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

初心者による DOM 要素ノードの取得一覧

概要

クライアントサイドのjavascriptにおいて、文書ツリーから要素ノードを取り出すというステップは欠かせない技術です。ここでは、文書ツリーから要素を取得する基本的な手段を一覧にしてみた。

getElementById()

id=""のように取得したい要素が明確な場合はgetElementById()を使う。

sample.js
document.getElementById(id)
 //id: 取得したい要素のid値

id値が複数あってしまった場合は最初に該当された要素1つを返す。

getElementsByTagName()

上のgetElementByIdとは似ているが、Elementsと複数形になっていることに注意。getElementsByTagNameメソッドは、タグ名をキーにして要素を取得する。

sample.js
document.getElementByTagName(name)
 //name: タグ名

これはタグをキーに要素を取得するので、複数の要素が該当する可能性があります。引数に「*」を指定することで、すべての要素を取得できます。
getElementsByTagNameの戻り値は、要素の集合です。これは、

length : リストに含まれる要素数
item(i).属性 or item[i].属性 : i番目の要素を取得
namedItem(name) : id、またはname属性が一致する要素を取得

のメソッドを使うことができる。

getElementsByName()

getElementsByNameメソッドは、name属性をキーに要素を取得する。一般的には、/などのフォーム要素へのアクセスなどで使う。getElementByIdメソッドのほうが使いやすい時が多い。

sample.js
document.getElenetByName(name)
 //name: name属性の値

使う際は、返り値が要素の配列になっているので自分が参照したい要素の順番をわかっていなければならない。逆に言うと、該当する要素が一つだけにしておいたほうが無難。
これはlength, item(i), namedItem(name)を使える。

getElementsByClassName()

これはclass属性をキーに要素を取得する。特定に役割を持った要素に対してなんらかの処理をしたいときにつかう。

javascript.sample.js
document.getElementsByClassName(class)
 //class: class属性の値

これはlength, item(i), namedItem(name)を使える。

querySelector() / querySelectorAll()

より複雑な条件で要素を検索するときに使う。

sample.js
document.querySelector(selector)
document.querySelectorAll(selector)
 //selector: セレクター一式

セレクタの一覧を出してみる。

* : すべての要素を取得
#id名 : 指定したIDの要素を取得
.class名 : 指定したクラス名の要素を取得
タグ名 : 指定したタグ名の要素を取得
セレクタ1, セレクタ2, ... : いずれかのセレクターに一致する要素を取得
parent > child : 子要素を取得
#list li : liをすべて取得
prev + next : 直後の要素を取得
prev ~ siblings : 兄弟要素をすべて取得
セレクタ名[属性値] : 指定した属性を持つ要素を取得
セレクタ名[属性値 = value] : 属性がvalueの要素を取得
セレクタ名[属性値 ^= value] : 属性がvalueから始まる要素を取得
セレクタ名[属性値 $= value] : 属性がvalueで終わる要素を取得
セレクタ名[属性値 *= value] : 属性がvalueを含む要素を取得
セレクタ名[属性][属性][属性] : 複数の属性をすべて含む要素を取得

querySelectorAll()は返り値が配列であるためlength, item(i), namedItem(name)を使える。

get~とquery~の使い分けのまとめ

特定の要素を検索できる場合 → getメソッド
複雑に絞り込む必要がある → queryメソッド

参考資料

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

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

初心者による DOM 要素ノードの取得一覧-1

概要

クライアントサイドのjavascriptにおいて、文書ツリーから要素ノードを取り出すというステップは欠かせない技術です。ここでは、文書ツリーから要素を取得する基本的な手段を一覧にしてみた。

getElementById()

id=""のように取得したい要素が明確な場合はgetElementById()を使う。

sample.js
document.getElementById(id)
 //id: 取得したい要素のid値

id値が複数あってしまった場合は最初に該当された要素1つを返す。

getElementsByTagName()

上のgetElementByIdとは似ているが、Elementsと複数形になっていることに注意。getElementsByTagNameメソッドは、タグ名をキーにして要素を取得する。

sample.js
document.getElementByTagName(name)
 //name: タグ名

これはタグをキーに要素を取得するので、複数の要素が該当する可能性があります。引数に「*」を指定することで、すべての要素を取得できます。
getElementsByTagNameの戻り値は、要素の集合です。これは、

length : リストに含まれる要素数
item(i).属性 or item[i].属性 : i番目の要素を取得
namedItem(name) : id、またはname属性が一致する要素を取得

のメソッドを使うことができる。

getElementsByName()

getElementsByNameメソッドは、name属性をキーに要素を取得する。一般的には、/などのフォーム要素へのアクセスなどで使う。getElementByIdメソッドのほうが使いやすい時が多い。

sample.js
document.getElenetByName(name)
 //name: name属性の値

使う際は、返り値が要素の配列になっているので自分が参照したい要素の順番をわかっていなければならない。逆に言うと、該当する要素が一つだけにしておいたほうが無難。
これはlength, item(i), namedItem(name)を使える。

getElementsByClassName()

これはclass属性をキーに要素を取得する。特定に役割を持った要素に対してなんらかの処理をしたいときにつかう。

javascript.sample.js
document.getElementsByClassName(class)
 //class: class属性の値

これはlength, item(i), namedItem(name)を使える。

querySelector() / querySelectorAll()

より複雑な条件で要素を検索するときに使う。

sample.js
document.querySelector(selector)
document.querySelectorAll(selector)
 //selector: セレクター一式

セレクタの一覧を出してみる。

* : すべての要素を取得
#id名 : 指定したIDの要素を取得
.class名 : 指定したクラス名の要素を取得
タグ名 : 指定したタグ名の要素を取得
セレクタ1, セレクタ2, ... : いずれかのセレクターに一致する要素を取得
parent > child : 子要素を取得
#list li : liをすべて取得
prev + next : 直後の要素を取得
prev ~ siblings : 兄弟要素をすべて取得
セレクタ名[属性値] : 指定した属性を持つ要素を取得
セレクタ名[属性値 = value] : 属性がvalueの要素を取得
セレクタ名[属性値 ^= value] : 属性がvalueから始まる要素を取得
セレクタ名[属性値 $= value] : 属性がvalueで終わる要素を取得
セレクタ名[属性値 *= value] : 属性がvalueを含む要素を取得
セレクタ名[属性][属性][属性] : 複数の属性をすべて含む要素を取得

querySelectorAll()は返り値が配列であるためlength, item(i), namedItem(name)を使える。

get~とquery~の使い分けのまとめ

特定の要素を検索できる場合 → getメソッド
複雑に絞り込む必要がある → queryメソッド

参考資料

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

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

Everything you need to know from ES2016 to ES2019 日本語訳

原文はこちら:
https://inspiredwebdev.com/everything-from-es-2016-to-es-2019

こんにちは, shuzonです。

上記のES2016-2019のまとめ記事は非常に良い記事でした。
英語だから気持ちが乗らず読まない 人がいると勿体無いなーとふと思い、

日本語で気軽に広く読んでもらいたいなーと思い日本語訳をしてみました。

訳者のステータス

  • js歴は2年ちょっとくらい
    • jquery, vue, reactを少し
    • 最新のjsをちゃんと触り始めたのはここ3ヶ月

誤訳や解釈の誤りがあった場合はコメントにて指摘してください。

原文ママの方が伝わりやすいと判断した部分に関しては原文を採用します。

ちょっとしたメモ

本文中に僕のメモを混ぜ込んでいます。

本文と切り離すために以下のprefixと共に記述します。

  • FYI: for your information
  • IMO: in my opinion
  • MEMO: 一言メモ

では本文に入ります。

Everything you need to know from ES2016 to ES2019

JavaScriptは常に進化し続ける言語の1つであり、ここ数年で数多くの特徴がECMAScriptの仕様に追加された。

この記事は Complete Guide to Modern JavaScript から新規に追加されたES2016, ES2017, ES2018, ES2019に関する部分を抜粋する。

この記事の終わりで、全てを要約したチャートシートのlinkを見つけることができるだろう。

FYI: みながら読むとわかりやすいのでここに貼っておきます。
cheat sheet

Everything new in ES2016

ES2016に導入されたのはたった2つの特徴だ。

  • Array.prototype.includes()
  • The exponential operator (べき乗演算子)

Array.prototype.includes()

includes()

  • arrayが要素を含めば true
  • それ以外はfalse

を返す

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

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

indexとの組み合わせ

includes() はあたえられたindexから検索を開始できる。
デフォルトは0で、負の数を与えることもできる。

第1引数は検索対象で, 第2引数はindexだ。

let array = [1,3,5,7,9,11];

array.includes(3,1);
// index 1(つまり1の位置)から値3を探す
// true
array.includes(5,4);
//false
array.includes(1,-1);
// 末尾スタートで1を探す
// false
array.includes(11,-3);
// true

array.includes(5,4); はarrayに5を含むにも関わらずfalseを返す。
なぜなら検索が4要素目から始まるからだ。 5を見つけられないため false が返る。

array.includes(1,-1); はfalseを返す。
index -1はarrayの最後尾を表し、検索がそれ以降から始まるからだ。

array.includes(11,-3); はtrueを返す。
index -3(つまり7) に戻ったとしても11が検索経路上に存在するからだ。

The exponential operator (べき乗演算子)

ES2016以前はこう書く必要があった。

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

新規追加されたべき乗演算子を用いると以下のように書ける。

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

複数回の操作を行うときちょっと便利になった。

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

Math.pow() は連結が必要だしちょっと長い上に乱雑だ。

べき乗演算子は同じことをより早くクリーンに記述できる。

ES2017 string padding, Object.entries(), Object.values() and more

ES2017はcoolな機能がたくさん増えた、ではみていこう。

String padding (.padStart() and .padEnd())

stringをpadding(うめる)機能がいくつか増えた。

  • 末尾からpaddingする .padEnd()
  • 先頭からpaddingする .padStart()
"hello".padStart(6);
// " hello"
"hello".padEnd(6);
// "hello "

6文字でpaddingすると指定したのに、なんで1つのspaceしか与えられないのか?

padStartpadEnd は空を埋める(fill the empty spaces)からだ。

helloは5文字だから空なのは1文字だけだ。

以下の例をみてみて欲しい。

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

Right align with padStart (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 を適用すれば右詰めになるのだ。

Add a custom value to the padding (カスタム文字列で埋める)

実はspaceだけでなく、文字列や数字も利用できる。

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

Object.entries() and Object.values()

まず、Objectを作る。

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

前のversionのJavaScript では Objectの値にアクセスするときこんな方法を取っていた

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

Object.keys() はObjectのkeysだけを返し、valuesにアクセスするためにはそのkeyを使う必要があった。

今は2つのアクセス方法が増えた。

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

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

Object.values() は 全てのvalueを返し、 Object.entries() は keyとvalue両方を含むarrayを返す。

Object.getOwnPropertyDescriptors()

このmethodは 地震に関する全てのプロパティ情報
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}

Trailing commas in function parameter lists and calls (ケツカンマの用法)

これはちょっとした文法のマイナーチェンジだ。

objectを書くとき、要素が最後かどうかに関わらずカンマをつけることができるようになった。
FYI: つまりケツカンマ可能ってこと

// from this
const object = {
  prop1: "prop",
  prop2: "propop"
}

// to this
const object = {
  prop1: "prop",
  prop2: "propop",
}

例えば、僕が最後(2つめ)の要素にカンマを書き忘れたとしよう。

これは今は特にerrorが起きないけど、君の同僚やチームメイトの命を守るならつける方がベターだね。

IMO: これはlintで制御すればいいし人間がやるべきことではなさそう

// 僕が書いたやつ
const object = {
  prop1: "prop",
  prop2: "propop"
}

// 同僚が新しいプロパティを追加した
const object = {
  prop1: "prop",
  prop2: "propop"
  prop3: "propopop"
}
// 突然, 彼はerrorに見舞われる。なぜなら僕が最後にカンマをつけ忘れたことに気づけないからだ

Shared memory and Atomics

MEMO:
ここは理解度低いです。
どうもAtomicsはthread間でshared memoryを利用するためのAPIのよう。
いい感じに原子性(Atomicity)を担保してくれるAPI、という理解でとどまっています。
正直、原文もMDNをそのままコピペしてるっぽいのでMDN日本語訳を読む方が良さそうです。

From : MDN

// MEMO: MDNから抜粋
メモリーが共有されている場合、複数のスレッドがメモリー内の同じデータを読み書きできます。アトミック演算では、予測される値の書き込みと読み込みを保証するため、次の演算が開始される前に現在の演算が完了し、その演算が割り込まれないようにします。
アトミック演算は、Atomics モジュール上にインストールされます。他のグローバルオブジェクトと異なり、Atomics はコンストラクターではありません。new 演算子 を付けて使用することや Atomics オブジェクトを関数として実行することはできません。Atomics のすべてのプロパティとメソッドは静的です (例えば、Math オブジェクトの場合と同じです)。

例えばこんなmethodがある。
- add/sub
- and/ or / xor
- load / store

MEMO: 以下はMDN SharedArrayBuffer から抜粋

SharedArrayBuffer オブジェクトは、ジェネリックで固定長の生バイナリデータバッファーを表すために使用されます。

いくつか Atomics methodのサンプルをみてみよう。

Atomics.add(), Atomics.sub(), Atomics.load() and Atomics.store()

Atomics.add()array, index, value と3つの引数を取る。
加算とともにindexの位置に以前設定されていた値をreturnする。

// create a `SharedArrayBuffer`
const buffer = new SharedArrayBuffer(16);
const uint8 = new Uint8Array(buffer);
// add a value at the first position
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() を呼び出すとarrayの指定位置に設定されていた前の値を返す。
この時, 再度 uint8[0] を呼び出すと加算結果の15が得られる。
array, indexの2つの引数をとる Atomics.load を使えば特定の値を取り出すことができる。
Atomics.sub()Atomics.add() と同じように動くが減算を行う。

// create a `SharedArrayBuffer`
const buffer = new SharedArrayBuffer(16);
const uint8 = new Uint8Array(buffer);
// add a value at the first position
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

ここでは Atomics.sub() を使い uint8[0] に設定された値から5を減算する(つまり10-5と同じ)。
Atomics.add() と同じく前回設定された値が返る、この場合は10だ。

Atomics.store() を使うことで特定の値をstore(登録)できる。
この場合では, 値3をposition 0 つまりarrayの先頭に設定する。
Atomics.store() は 設定値をそのまま返す、この場合は3だ。
Atomics.load() も指定したindexの値を返す、この場合は3であり5はかえってこない。

Atomics.and(), Atomics.or() and Atomics.xor()

この3つのmethodは bit単位のAND, OR, XOR演算子(bitwise AND, OR and XOR operations)として与えられたarrayの位置に作用する。

もっとbitwise operationsについて知りたい人は以下のwikipediaをみて欲しい。
https://en.wikipedia.org/wiki/Bitwise_operation

ES2017 Async and Await

ES2017では promisesの新しい用法が導入された。その名も"async/await"だ。

Promise review

新しいsyntaxに飛び込む前に、ちょっとだけ書き慣れたpromiseの書き方をみてみよう。

// fetch a user from github
fetch('api.github.com/user/AlbertoMontalesi').then( res => {
  // return the data in json format
  return res.json();
}).then(res => {
  // if everything went well, print the data
  console.log(res);
}).catch( err => {
  // or print the error
  console.log(err);
})

これはgithubからuserを取得してconsoleに表示するとてもシンプルなpromiseだ。
違う例もみてみよう。

function walk(amount) {
  return new Promise((resolve,reject) => {
    if (amount < 500) {
      reject ("the value is too small");
    }
    setTimeout(() => resolve(`you walked for ${amount}ms`),amount);
  });
}

walk(1000).then(res => {
  console.log(res);
  return walk(500);
}).then(res => {
  console.log(res);
  return walk(700);
}).then(res => {
  console.log(res);
  return walk(800);
}).then(res => {
  console.log(res);
  return walk(100);
}).then(res => {
  console.log(res);
  return walk(400);
}).then(res => {
  console.log(res);
  return walk(600);
});

// you walked for 1000ms
// you walked for 500ms
// you walked for 700ms
// you walked for 800ms
// uncaught exception: the value is too small

このpromiseを新しいasync/await syntaxで書き直してみよう。

Async and Await

function walk(amount) {
  return new Promise((resolve,reject) => {
    if (amount < 500) {
      reject ("the value is too small");
    }
    setTimeout(() => resolve(`you walked for ${amount}ms`),amount);
  });
}

// create an async function
async function go() {
  // use the keyword `await` to wait for the response
  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();

// you walked for 500ms
// you walked for 900ms
// you walked for 600ms
// you walked for 700ms
// uncaught exception: the value is too small

何をやってるか分解してみる。

  • async functionを作る。functionの前に async キーワードをおく必要がある。
  • async キーワードによって関数は常にpromiseを返すことをJavaScriptに伝えることができる。
  • もし promiseでない値を関数内で返したとしても, async functionが値をpromiseでラップ(wrapped) してくれる。
  • await キーワードは async function内で のみ 動作する
  • 名前が示すように、 awaitはpromiseの結果が返るまで待つことをJavaScriptに伝える。

もし、awaitasync functionの外で使った時どうなるか試してみよう。

// 普通の関数内でawaitを使う
function func() {
  let promise = Promise.resolve(1);
  let result = await promise;
}
func();
// SyntaxError: await is only valid in async functions and async generators


// codeのtop-levelでawaitを使う
let response = Promise.resolve("hi");
let result = await response;
// SyntaxError: await is only valid in async functions and async generators

注意書き: awaitはasync function内でしか使えない

Error handling

通常のpromiseではerrorを捕捉する場合 .catchを使う。
asyncもそんなに変わらない。

async function asyncFunc() {

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

asyncFunc();
// TypeError: failed to fetch

try...catch を使えerrorを捕捉できるが、それがなくてもこんな感じでerrorの捕捉ができる。

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

ES2018 Async Iteration and more?

ES2018 で導入された機能についてみていく。

Rest / Spread for Objects

ES6(ES2015)でこの機能が使えるようになったのは覚えているだろうか?

const veggie = ["tomato","cucumber","beans"];
const meat = ["pork","beef","chicken"];

const menu = [...veggie, "pasta", ...meat];
console.log(menu);
// Array [ "tomato", "cucumber", "beans", "pasta", "pork", "beef", "chicken" ]

これが rest/spread syntax によって objectに対しても行えるようになった。
どうやるのかみてみよう。

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

// rest演算子を使えばobject内に余った全部の要素を捕捉できる
let { a, b, ...z } = myObj;
console.log(a);     // 1
console.log(b);     // 3
console.log(z);     // {c: 5, d: 8}

// spread sytaxで Objectをクローンできる
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}

spread演算子によってObjectのクローンが簡単に作れるようになった。

そしてoriginalのObjectを変更したとしても、クローンされたObjectは変更されない。

arrayで見られる挙動とよく似ている。

Asynchronous Iteration

Asynchronous Iteration(非同期イテレーション) によって非同期でデータをイテレーションすることができるようになった。

documentから引用する

async iteratorはnext() methodが{value, done}のペアで構成されるpromiseを返すことを除いて、iteratorにとてもよく似ている、

どうするかというと 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

実行中、async iterator は [Symbol.asyncIterator]() method によってデータソースが作られる。
シーケンス中に次の値にアクセスしようとする時、暗黙的にiterator methodがpromiseを返すまで待つ。

Promise.prototype.finally()

promiseが終了した後もcallbackの呼び出しが可能だ。

const myPromise = new Promise((resolve,reject) => {
  resolve();
})
myPromise
  .then( () => {
    console.log('still working');
  })
  .catch( () => {
    console.log('there was an error');
  })
  .finally(()=> {
    console.log('Done!');
  })

.finally() 自身もPromiseを返し、thencatch でチェーン(chain)することができる。
ただし、それらのPromiseはチェーンされたPromiseに基づいて実行される。

const myPromise = new Promise((resolve,reject) => {
  resolve();
})
myPromise
.then( () => {
    console.log('still working');
    return 'still working';
  })
  .finally(()=> {
    console.log('Done!');
    return 'Done!';
  })
  .then( res => {
    console.log(res);
  })
// still working
// Done!
// still working

finally の後に then をチェーンすることができるが、返される値は finally によって作られたものではなく最初の then によるものとなる。

RegExp features

新しい正規表現機能がECMAScriptに追加された。

  • s(dotAll) flag for regular expressions (sフラグ)
  • RegExp named capture groups (名前付きキャプチャグループ)
  • RegExp Lookbehind Assertions (後置アサーション)
  • RegExp Unicode Property Escapes (ユニコードエスケープ)

s (dotAll) flag for regular expression (sフラグ)

新しいs flagは改行を含む全ての文字にマッチする . を利用可能にする。

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

RegExp named capture groups (名前付きキャプチャグループ)

documentより

数字付きキャプチャグループ(Numbered capture groups) は正規表現にマッチした特定位置の文字列を参照する。

それぞれのキャプチャグループには一意の数字が振り分けられ、参照時にはその数字を利用できる。

しかしこれは正規表現の把握やリファクタリングを難しくさせる。

例えば /(\d{4})-(\d{2})-(\d{2})/ にマッチする日付について、どのグループが月や日に対応するのか周辺のコード無しでは理解できない。

また、月と日の順序が変わった時はグループの参照を更新しなければらない。

(?<name>...) syntaxを用いることでキャプチャグループに名前を与え識別することが可能になる。

先ほどの正規表現はこう書き換えることができる /(?<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

RegExp Lookbehind Assertions (後置アサーション)

documentより

Lookbehind Assertions (後置アサーション) はパターンの前に別のパターンがあるかどうかを確認する。

例えば, ドルの金額を$記号なしでマッチしたい。

肯定マッチ(positive lookbehind assertion) では (?<=...) で示されたパターンが次のパターンよりも前に存在している場合にマッチする。

例えば、ドルの金額を $ 記号なしでマッチしたい時は /(?<=$)\d+(\.\d*)?/ を使えばできる。
$10.53 の場合は 10.53 を返すが、€10.53 にはマッチしない。

否定マッチ(Negative lookbehind assertions) では (?<!...) で示されたパターンが次のパターンの前に存在しない場合にマッチする。
例えば、 /(?<!$)\d+(?:\.\d*)/$10.53 にはマッチしないが €10.53 にはマッチする。

RegExp Unicode Property Escapes (ユニコードエスケープ)

documentより

ユニコードエスケープは \p{…}\P{…} を使えばいい。
新しいタイプのエスケープシーケンスで u フラグを設定することで使える。

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

Lifting template literals restriction

memo: ここは省略しますね

What's new in ES2019?

最新のES2019について紹介しよう。

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

Array.prototype.flat() は指定した深さまで再帰的にarrayを平坦化(flatten)する。
特に何も指定しなければ階層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']

// 2度実行すると1階層ずつflatten
letters.flat().flat();
// ['a', 'b', 'c', 'd', 'e', 'f']

// 全階層flatten
letters.flat(Infinity)
// ['a', 'b', 'c', 'd', 'e', 'f']

Array.prototype.flatMap() は階層の扱いに関しては flat() と同じである。
しかし、単にarrayを平坦化する代わりに、flatMap() はマッピングを行い結果をもった新しいarrayを返す。

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

// 普通のmap()をみてみよう
greeting.map(x => x.split(" "));
// ["Greetings", "from"]
// ["", ""]
// ["Vietnam"]


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

.map() は複数階層になってしまっているが、 解決方法として.flatMap() を使うことで平坦化されたarrayを得ることができる。

MEMO: flatten 2019で導入されたのまじ!?遅くない!?と読んでいて思いました。

Object.fromEntries()

Object.fromEntries() は key-value pairのlistをobjectに変換する

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

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

Object.fromEntries() はイテレータなものは引数にとることができる。ArrayやMap, iterable protocolを実装しているObjectであれば可能だ。

もっと知りたい人はここを読もう。

MDN iteration protocols 日本語版

FYI: Object.entries() の逆バージョンですね

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()のalias,
.trimRight().trimEnd(). のaliasだ。

Optional Catch Binding

ES2019以前は常にcatch 節に 例外変数をとる必要があった。
ES2019は省略可能だ。

// Before
try {
   ...
} catch(error) {
   ...
}

// ES2019
try {
   ...
} catch {
   ...
}

これはerrorを無視したいときに便利だ。(memo: いや...それどうなの...?)

もっと詳しく知りたいならこれを読んでほしい http://2ality.com/2017/08/optional-catch-binding.html

Function.prototype.toString()

.toString() method は function のコードを文字列として出力してくれる。

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

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

コメントも出してくれるよ。

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

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

Symbol.prototype.description

.descriptionSymbol Objectのoptionalな説明を出してくれる。

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

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

以上です

javascriptがここ数年で着実に進化してきたことがわかるとてもいい記事ですね。
読んでくださってありがとうございます。

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

非同期通信でTodoの保存を行うアプリケーションのJSを細かくみてみる。

アプリは以下のような物

alt

jsファイル、コントローラー、ビューは以下の通り。

todo.js
$(function() {

  function buildHTML(todo) {
    var html = $('<li class="todo">').append(todo.content);
    return html;
  }

  $('.js-form').on('submit', function(e) {
    e.preventDefault();
    var todo = $('.js-form__text-field').val();
    $.ajax({
      type: 'POST',
      url: '/todos.json',
      data: {
        todo: {
          content: todo
        }
      },
      dataType: 'json'
    })
    .done(function(data) {
      var html = buildHTML(data);
      $('.todos').append(html);
      $('.js-form__text-field').val('');
    })
    .fail(function() {
      alert('error');
    });
  });
});
index.html
<div class="contents">
  <%= form_for @todo, html: { class: 'form js-form' } do |f|%>
    <%= f.text_field :content, class: 'form__text-field js-form__text-field' %>
    <%= f.submit class: 'form__submit js-submit' %>
  <% end %>
  <ul class="todos">
    <%= render @todos %>
  </ul>
</div>
todo_controller.rb
class TodosController < ApplicationController
  def index
    @todo = Todo.new
    @todos = Todo.order('created_at ASC')
  end

  def create
    @todo = Todo.create(todo_params)
    respond_to do |format|
    # respond_to メソッドでリクエストがHTMLのレスポンス求めているのか、
    # それともJSONのレスポンス求めているのか,条件分岐させる。
      format.html { redirect_to :root }  # htmlリクエストの場合
      format.json { render json: @todo}  # jsonリクエストの場合
    end
  end

  private
  def todo_params
    params.require(:todo).permit(:content)
  end
end

・Json

Rubyのハッシュと同様、キーとバリューの組み合わせでデータを表現するデータ記述形式。
表示形式は下記参照
{user_name: "testさん", created_at: "2019-08-28T10:35:13.000+09:00", content: "これがJSONの形です!", image_url: null, id: 6}

以下でjsファイルを細かく見ていく

todo.js
$(function() {    // function で以下が関数であると定義する

  function buildHTML(todo) {
    var html = $('<li class="todo">').append(todo.content);  //todoの内容をcontentに追加する。
    return html;  //controllerで設定された処理を実行する (format.html { redirect_to :root })
  }

  $('.js-form').on('submit', function(e) {   //イベントの設定(submitイベント(クリックorエンターキー)が行われた際に処理が行われる。)
    e.preventDefault();   //デフォルトの、通信処理を通じてホームを送信する処理を止めている
    var todo = $('.js-form__text-field').val();
    $.ajax({  // 非同期通信を行うための記述。
      type: 'POST', // HTTP通信の種類を記述している。GETとPOSTの2種類がある。
      url: '/todos.json',data: {todo: {content: todo}},dataType: 'json'})  // わかりやすいように引数の部分を一列にした
      // url:リクエストを送信する先のURLを記述する。
      // data:サーバに送信する値を記述する。
      // dataType   :サーバから返されるデータの型を指定する。
    .done(function(data) {   // 非同期通信が成功した際の処理
      var html = buildHTML(data);   // HTMLのレスポンスに関して、buildHTML(data) の処理を実行する
      $('.todos').append(html);   // 記入された内容をHTMLに追加する。
      $('.js-form__text-field').val('');  // 未記入の時には追加しないというバリデーションを設定している
    })
    .fail(function() {   // 非同期通信が失敗した際の処理
      alert('error');   //error というアラート文を表示する。
    });
  });
});

現状の理解はこのようなイメージ。
誤りがございましたらご教示ください。

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

エンターキーでsubmitしたくないフォームをJavaScriptで制御する

入力フォームを選択した状態でエンターキーではsubmitしたくない(送信ボタン等を押下した時だけsubmitしたい)場合、以下JSで制御できる。

const forms = Array.from(document.querySelectorAll('form')).filter(el => !el.getAttribute('data-allow-enter-submit'));
if (!forms.length) return;

for (let i = 0; i < forms.length; i += 1) {
  const form = forms[i];
  form.addEventListener('keydown', e => {
    if (
      ((e.which && e.which === 13) || (e.keyCode && e.keyCode === 13)) && e.target.tagName !== 'TEXTAREA'
    ) {
      e.preventDefault();
      return false;
    }
    return true;
  });
}

補足

((e.which && e.which === 13) || (e.keyCode && e.keyCode === 13)) && e.target.tagName !== 'TEXTAREA'
  • 13はエンターキーのこと。
  • whichkeyCodeの両方を指定しているのは、ブラウザによって挙動が異なるため。
  • テキストエリア(複数行)を選択した状態だと、改行のためにエンターを押しただけでイベントがトリガーされて改行ができなくなってしまうため、TEXTAREAでない場合という条件も追加している。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ejectなしのExpoアプリにStripe決済をWebViewを使って埋め込む

決済サービスのStripeをExpoで使用する方法を模索していて、この記事はその一つの実装例です。
最新のStripe APIをJSで使用する際、zaburoさんの下記の記事が参考になりますので、まずは目を通してください。

(2020年元旦時点で最新の)Stripeの決済をReactで使う
https://qiita.com/zaburo/items/7d4de7723b6d2445f356

さて、この記事での前提条件はこんな感じです。

  • ejectはしない
  • Stripe Checkoutではなく、Stripe Elementsを使用する
  • 決済はCharge方式ではなくPaymentIntentを使う
  • 当然だけどStripe Elementsの中の値には関与しない。つまり、入力をネイティブ領域で行わせてそれをWebViewのSripe Elementsに反映するというようなことはしない。(Stripe Elementsでそれができるのかはわからないが、巧妙に回避されているのは確か)

Expoプロジェクトをejectせずに使用できるライブラリが存在しないため、必然的に決済部分をWebViewに埋め込む方向で実装していきます。
ちなみに、公式にはこちらのeject必須のもの↓が紹介されています。
https://docs.expo.io/versions/latest/sdk/payments/

また、モーダルで表示されるパターンのCheckoutをWebViewで利用した実装例(expo-stripe-checkout)がありますが、こちらは現在のところ動作しませんでした。

1. サーバーサイドの準備

まずは、PaymentIntentを作成しclient_secretをアプリに返すAPIを簡単に用意します。
このサンプルでは、expressサーバーをFirebase Cloud Functionsにデプロイしています。

functions/index.js
const functions = require('firebase-functions');

const app = require('express')();
const stripe = require('stripe')('sk_test_...'); // 秘密鍵を入れる
const cors = require('cors');
const bodyParser = require('body-parser');

app.use(require('body-parser').text());
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

app.post('/createPaymentIntent', async (req, res) => {
  const result = await stripe.paymentIntents.create({
    amount: req.body.amount,
    currency: req.body.currency,
    description: 'some description', //option
    metadata: { username: req.body.username, tranId: '11111' } //option
  });

  res.json(result);
});

exports.api = functions.https.onRequest(app);

ひとまずこれをデプロイするだけですが、PaymentIntentというのはユーザーセッションと同列のもののようなので、実際のユースケースでは支払い画面に来た時点で作成するのがベターだと思います。
その場合はユーザーの動向に合わせキャンセル処理などを行うためのAPIを用意したり、Firebase AuthユーザーやFirestoreと連携したりとやるべきことが増えるかと思います。
PaymentIntentではなくChargeを使ったFirebase公式のサンプルはこちらにあります。

2. アプリ側を作成

WebViewで表示する場合は基本的にReact等で作りこんだものをどこかでホスティングするか、アプリ内で生成したHTMLを表示するかのどちらかの方針が考えられます。
またはCheckoutを使ってStripeによって生成された画面を表示する方法もありますが、そちらはまた調査しようと思います。
このためだけにどこかにホスティングするのは面倒なのと、どちらにしろ外部サイトを開くのであればCheckoutを使ってしまった方がセキュアだと思うので、
この記事ではまずアプリ内でHTMLを生成する方法をとって、ギリギリまでネイティブ領域とシームレスなコンポーネントとして実装してみます。

[補足]react-stripe-elementsについて

react-stripe-elementsはReactを使用したWebアプリケーションにStripe Elementsを導入するための公式のライブラリです。
やっていることはリッチなUIの提供等ではなくStripe.jsStripe ElementsをReactのレンダリングツリーの中でうまく扱うためのProviderの役目であり、Stripe Elements自体はもともと外部から状態を変更できないようになっているのでReactのコントロール外です。
既存のReactアプリケーション内に組み込む場合には有用なことは確かですが、最初からカード入力部分のみを作る場合にはそれほど重要ではないかもという印象です。今回は使っていません。

完成イメージ

このように、カード入力フォームの表示と決済処理のみをWebViewに任せ、ネイティブ領域と連携させます。
screens.png

WebViewを使ったコンポーネントを作成

StripePayment.js
import React, { Component } from 'react';
import { View, ActivityIndicator } from 'react-native';
import * as PropTypes from 'prop-types';
import { WebView } from 'react-native-webview';

/**
 * Stripe決済をWebViewを介して行うコンポーネント
 */
class StripePayment extends Component {
  callbacks = {}; // 決済処理のコールバックが入る
  state = {
    ready: false // Elementsの初期化が完了したかどうか
  };

  constructor(props) {
    super(props);
    this.readyState = { // 各Elementsの初期化状態フラグ
      cardNumber: false,
      cardExpiry: false,
      cardCvc: false
    };
    this.completeState = { // 各Elementsの入力完了フラグ
      cardNumber: false,
      cardExpiry: false,
      cardCvc: false
    };
    this.complete = false; // 全てのElementsの入力完了フラグ
    this.eventHandlers = { // WebViewから送られたイベントに対応するメソッドを設定
      PAYMENT_STATUS: this.onPaymentStatus,
      READY: this.onReady,
      CHANGE: this.onChange,
      FOCUS: this.onFocus,
      BLUR: this.onBlur,
      LOG: console.log
    };
  }

  /**
   * 決済処理を開始
   * @param {number} amount
   * @param {function} callback
   */
  payment = (amount, callback) => {
    if (this.webview) {
      const callbackKey = (+new Date()).toString(); // WebViewとコールバックのやり取りをするためのキーを作る
      this.webview.injectJavaScript(`payment(${amount}, ${callbackKey});`);
      this.callbacks[callbackKey] = callback;
    }
  };

  /**
   * WebViewからのデータを処理
   * @param event
   */
  onMessage = (event) => {
    const json = JSON.parse(event.nativeEvent.data);
    const { data, type } = json;
    this.eventHandlers[type] && this.eventHandlers[type](data);
  };

  /**
   * Elementのreadyイベントに対応
   * @param {string} elementType
   */
  onReady = ({ elementType }) => {
    this.readyState[elementType] = true;
    // 全てのElementがreadyになったらWebViewを表示する
    if (this.readyState.cardNumber && this.readyState.cardExpiry && this.readyState.cardCvc) {
      this.setState({ ready: true });
    }
  };

  /**
   * Elementのchangeイベントに対応
   * @param {string} elementType
   * @param {boolean} complete
   */
  onChange = ({ elementType, complete }) => {
    this.completeState[elementType] = complete;
    const completeAll = this.completeState.cardNumber && this.completeState.cardExpiry && this.completeState.cardCvc;
    const { onChange } = this.props;
    this.complete = completeAll;
    onChange && onChange({
      complete: completeAll, // 全てのElementがcompleteかどうか
      elements: Object.assign({}, this.completeState)
    });
  };

  /**
   * 決済処理の状態が変わったら呼ばれる
   * paymentメソッドで渡したコールバックを呼ぶ
   * @param {string} status
   * @param {string} callbackKey
   * @param {string} error
   */
  onPaymentStatus = ({ status, callbackKey, error }) => {
    if (this.callbacks[callbackKey]) {
      this.callbacks[callbackKey]({ status, error });
      delete this.callbacks[callbackKey];
    }
  };

  render() {
    const { ready } = this.state;
    const {
      publicKey,
      currency,
      elementStyle,
      locale,
      style,
      backgroundColor,
      height,
      username
    } = this.props;
    const html = `
      <!DOCTYPE html>
      <html lang="${locale}">
        <head>
          <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0,user-scalable=0" />
          <style>
            body {
              margin: 0;
              background-color: ${backgroundColor};
            }
            .container {
              display: flex;
              height: ${height}px;
              align-items: center;
            }
            .element {
              box-sizing: border-box;
              padding: 0 5px;
            }
            #card-number {
              flex-grow: 1.5;
            }
            #card-expiry {
              flex-grow: 0.6;
            }
            #card-cvc {
              flex-grow: 0.4;
            }
          </style>
        </head>
        <body>
          <div class="container">
            <div class="element" id="card-number"></div>
            <div class="element" id="card-expiry"></div>
            <div class="element" id="card-cvc"></div>
          </div>
          <script src="https://js.stripe.com/v3/"></script>
          <script>
            var style = ${JSON.stringify(elementStyle)};
            var stripe = Stripe('${publicKey}');
            var elements = stripe.elements({ locale: '${locale}' });
            var cardNumberElement = elements.create('cardNumber', { style: style });
            cardNumberElement.mount('#card-number');
            listenElement(cardNumberElement);
            var cardExpiryElement = elements.create('cardExpiry', { style: style });
            cardExpiryElement.mount('#card-expiry');
            listenElement(cardExpiryElement);
            var cardCvcElement = elements.create('cardCvc', { style: style });
            cardCvcElement.mount('#card-cvc');
            listenElement(cardCvcElement);
           
            /**
             * Elementのイベントをハンドリング
             * @param {StripeElement} element
             */
            function listenElement(element) {
              ['ready', 'change', 'focus', 'blur'].forEach(function(eventType) {
                element.on(eventType, function(event) {
                  postMessage(eventType.toUpperCase(), event);
                });
              });
            }

            /**
             * アプリ側にデータを送る
             * @param {string} type
             * @param data
             */
            function postMessage(type, data) {
              window.ReactNativeWebView.postMessage(JSON.stringify({ type, data }));
            }

            /**
             * 決済処理を開始
             * @param {number} amount
             * @param {string} callbackKey
             */
            function payment(amount, callbackKey) {
              //paymentIntentの作成をリクエスト
              postMessage('LOG', 'payment start');
              fetch('https://[リージョン]-[プロジェクトID].cloudfunctions.net/api/createPaymentIntent', {
                method: 'POST',
                headers: {
                  'Content-type': 'application/json'
                },
                body: JSON.stringify({ amount: amount, currency: '${currency}', username: '${username}' })
              }).then(function(response) {
                return response.json();
              }).then(function(responseJson) {
                // paymentIntentを決済
                return stripe.confirmCardPayment(responseJson.client_secret, {
                  payment_method: {
                    card: cardNumberElement
                  }
                });
              }).then(function(response) {
                // 完了したらアプリにステータスを送信
                postMessage('PAYMENT_STATUS', {
                  callbackKey: callbackKey,
                  status: response.paymentIntent.status
                });
              }).catch(function(e) {
                postMessage('PAYMENT_STATUS', {
                  callbackKey: callbackKey,
                  status: 'error',
                  error: e.message
                });
              });
            }
          </script>
        </body>
      </html>
    `;
    return (
      <View style={[{
        margin: 0,
        width: '100%',
        height,
        backgroundColor
      }, style]}>
        {!ready && <ActivityIndicator style={{ width: '100%', height }}/>}
        <WebView
          ref={(ref) => {
            if (ref) {
              this.webview = ref;
            }
          }}
          javaScriptEnabled={true}
          scrollEnabled={false}
          bounces={false}
          source={{ html }}
          onMessage={this.onMessage}
          style={{
            width: '100%',
            margin: 0,
            height,
            padding: 0,
            opacity: ready ? 1 : 0
          }}
        />
      </View>
    );
  }
}

StripePayment.propTypes = {
  publicKey: PropTypes.string.isRequired,
  username: PropTypes.string,
  currency: PropTypes.string,
  onChange: PropTypes.func,
  elementStyle: PropTypes.shape({
    base: PropTypes.object,
    complete: PropTypes.object,
    empty: PropTypes.object,
    invalid: PropTypes.object
  }),
  locale: PropTypes.string,
  style: PropTypes.object,
  backgroundColor: PropTypes.string,
  height: PropTypes.number
};

StripePayment.defaultProps = {
  currency: 'JPY',
  username: null,
  onChange: null,
  elementStyle: {},
  locale: 'ja',
  style: null,
  backgroundColor: '#fff',
  height: 100
};

export default StripePayment;

WebView内のHTML・CSS・JSが入っているので長いですが、ややこしいのはWebViewとアプリとのやりとりをしている部分です。
HTMLは別ファイルにしてWebPackの設定を追加したり、WebViewとの連携をしやすくするユーティリティを別で作ったりしてもかなとも思いつつ、一つのソースにまとめてみました。
Elementの作成はHTML内の<script>タグの最初の部分にあり、決済処理はHTML内のpayment関数の部分です。
createPaymentIntentAPIのURLは適宜変更してください。

[追記]
本当に必要な部分のみをWebViewにしたい場合は、amountcurrencyなどを渡してclient_secretを取得する処理もWebViewの外、ネイティブ側に持たせることが可能です。その場合はCloud FunctionsのhttpsCallableなどが使いやすいかも。

画面を作成

上記のコンポーネントを使って画面を作成します。ローディングや入力完了フラグをチェックしたりして、試しに固定で1000円分を決済します。

App.js
import React, { Component } from 'react';
import { StyleSheet, Text, View, TouchableOpacity, ActivityIndicator } from 'react-native';
import StripePayment from './StripePayment';

export default class App extends Component {
  state = {
    loading: false,
    complete: false,
    succeeded: false
  };

  /**
   * 決済処理を開始
   */
  payment = () => {
    this.setState({ loading: true }, () => {
      this.stripe.payment(1000, ({ status, error }) => {
        const succeeded = status === 'succeeded';
        this.setState({ loading: false, succeeded });
        if (error) {
          Alert.alert(error);
        }
      });
    });
  };

  /**
   * Stripe Elementsのchangeイベントが発生したら呼ばれる
   * @param complete
   * @param elements
   */
  onChangeStripe = ({ complete, elements }) => {
    console.log('card completion', complete, elements);
    if (this.state.complete !== complete) { // 全てcompleteならボタンを有効化
      this.setState({ complete });
    }
  };

  render() {
    const { loading, complete, succeeded } = this.state;
    return (
      <View style={styles.container}>
        <Text style={styles.title}>支払いのテスト</Text>
        <StripePayment
          ref={(ref) => {
            if (ref) {
              this.stripe = ref;
            }
          }}
          publicKey="pk_test_..."
          onChange={this.onChangeStripe}
          backgroundColor="#eee"
          style={styles.stripe}
          elementStyle={{
            base: {
              fontSize: '16px',
              lineHeight: '32px',
              // textAlign: 'center'
            }
          }}
          username="test"
        />
        <TouchableOpacity onPress={this.payment} disabled={!complete || loading || succeeded}>
          <View
            style={[
              styles.button,
              !complete && styles.disabledButton,
              succeeded && styles.succeededButton
            ]}
          >
            {loading && <ActivityIndicator color="#ffffff" style={styles.buttonIndicator} />}
            <Text
              style={[styles.buttonText, succeeded && styles.succeededButtonText]}
            >
              {succeeded ? '支払いが完了しました' : '支払い'}
            </Text>
          </View>
        </TouchableOpacity>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    height: '100%',
    width: '100%',
    backgroundColor: '#ffffff',
    alignItems: 'center',
    justifyContent: 'center'
  },
  title: {
    position: 'relative',
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 24
  },
  stripe: {
    marginBottom: 24
  },
  button: {
    position: 'relative',
    width: 240,
    height: 50,
    borderRadius: 25,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'orange'
  },
  disabledButton: {
    opacity: 0.5
  },
  succeededButton: {
    borderColor: 'orange',
    borderWidth: 2,
    backgroundColor: 'transparent'
  },
  buttonText: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#ffffff'
  },
  succeededButtonText: {
    color: 'orange'
  },
  buttonIndicator: {
    position: 'absolute',
    top: 0,
    left: 0,
    width: 50,
    height: 50,
    justifyContent: 'center',
    alignItems: 'center'
  }
});

ダッシュボードで確認

「支払い」ボタンが問題なく動作したら、ダッシュボードを確認してみます。
ダッシュボード.jpg
問題なくテスト決済が成功しました。

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

【Nuxt.js】filter実践編:文末を…で省略しよう!

前置き

スクリーンショット 2020-01-08 14.30.24.png

filterを使って
文末を…(三点リーダー)で省略してみます?
コードの後にそれぞれの
各ポイントで解説をしていきます。

【考え方】
文字の長さlengthが
・10文字まで(0〜9)はそのまま表示
・11文字目以降(10〜)も続くなら
  10文字目まではそのまま表示
  11文字目を…にする

CSSでもできるよね?

できます!
が、
基準が字数ではなく、行数や幅になります。
https://www.yoheim.net/blog.php?q=20180702

コード(ローカルver)

sliceが分かりやすいよう
data内テキストは数字の0〜にしました。
では後ろの解説をどうぞ。

index.vue
<template>
 <div class="page">
   {{ text | omittedText }}
 </div>
</template>

<script>
export default {
 data () {
   return {
     text: "0123456789101112",
   }
 },
  filters: {
    omittedText(text) {
     return text.length > 10 ? text.slice(0, 10) + "" : text;
    },
  },
}
</script>

<style lang="scss" scoped>
</style>

filter

実践編〜〜〜〜で
基本的な使い方をご確認ください!

前回には記載していない点があります。
filterにはthisを書けず
dataに直接アクセスできないことです。

そのためthis.textとは書けません。
その代わりに引数にtextを渡しています。

slice

sliceを使って、
指定文字を抜き出します。
今回は10文字目まで(0〜9)を表示?
slice(0, 10) でOKです♪

sliceの意味は…
指定番号〜後ろの指定番号より前を抜き出す?

slice(2) = 2番目〜を抜き出す
slice(0, 10) = 0番目〜9文字目までを抜き出す

MDNが参考になります!
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice#Examples

三項演算

◾️式1 ? 式2 : 式3
  式1が
  ・trueなら式2
  ・falseなら式3

今回は文字の長さによって条件分岐させます。

文字の長さ ? 10文字まではそのまま表示 + 11文字目以降は"…" : 10文字まではそのまま表示 ;

これをコードにすると

text.length > 10 ? text.slice(0, 10) + "…" : text;

◾️式1:文字の長さlengthが10文字より多い
(より、なので10は含まず11〜)
・trueなら式2 10文字まではそのまま表示
        11文字目を…にする
・falseなら式3 10文字目まではそのまま表示

20文字目を…にしたい場合

【index.vue】
filterの数字を変更しましょう!
sliceが慣れてくれば簡単ですね?

index.vue
// 変更前
return text.length > 10 ? text.slice(0, 10) + "…" : text;

// 変更後
return text.length > 19 ? text.slice(0, 19) + "…" : text;

コード(グローバルver)

plugin/filter.js
import Vue from 'vue'

Vue.filter('toLowercase', function (text) {
 return text.length > 10 ? text.slice(0, 10) + "" : text;
})
nuxt.config.js
export default {
 plugins: [
   '~/plugins/filter.js'
 ],
}
index.vue
<template>
 <div class="page">
   {{ text | omittedText }}
 </div>
</template>

<script>
export default {
 data () {
   return {
     text: "0123456789101112",
   }
 },
}
</script>

<style lang="scss" scoped>
</style>

このアカウントでは
Nuxt.js、Vue.jsを誰でも分かるよう、
超簡単に解説しています??
これからも発信していくので、
ぜひフォローしてください♪

https://twitter.com/aLizlab

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

配列の中に空objectがあったら削除する処理

やりたかった事

今回やりたかった事は何かしらPOSTする時にエンドユーザーが必須項目でない入力欄を空で
submitした際に事前に配列の中身の空objectをdelete処理してPOSTする事。
因みに備忘録です。

実際の処理

実際にありそうなプロフィール入力だとこんな感じになるのかな?

hoge.component.ts
//入力された項目
const profile = {name : 'suzuki', age : '30', sex : null, address : null};
//この処理でnullだった場合、空objectを削除
Object.entries(profile).forEach( o => (o[1] === null ? delete profile[o[0]] : 0));
conosle.log(profile);
//{name: "suzuki", age: "30"}

まとめ

配列の中の空objectを削除したいってなった時にまぁ安易にif文使おうとしてたのでこういうやり方もあるんだなと勉強になった次第です。

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

?【CakePHP2】Fromをsubmitする前にRemodalを表示させる

環境

PHP 7.2.21
CakePHP 2.10.18
MySQL 5.7.27

やりたいこと

Form内容を入力後にRemodalを表示させ、その「OK」ボタン押下をsubmitボタンとしたい

やったこと

兎にも角にも公式ページよりダウンロード
https://github.com/VodkaBears/Remodal/releases

distディレクトリにある4ファイルをすべて所定のディレクトリに配置する
remodal.min.jsが欠けているとオプションが無効になり
cssが欠けていると当然デザインが壊れる

Hoge.ctp
<!--jsとcssを読み込む-->
<script type="text/javascript" src="/js/vendor/1.1.1/remodal.js"></script>
<script type="text/javascript" src="/js/vendor/1.1.1/remodal.min.js"></script>
<link rel="stylesheet" type="text/css" href="/js/vendor/1.1.1/css/remodal.css">
<link rel="stylesheet" type="text/css" href="/js/vendor/1.1.1/css/remodal-default-theme.css">


<!-- Remodalの「OK」ボタンが押された後にForm内容を送信させる -->
<script>
$(function($) {
    $(document).on('confirmation', '.remodal', function () {
        $('#HogeHogeConfForm').submit();
    });
});
</script>


<!-- Remodalの表示内容 -->
<div class="remodal" data-remodal-id="setting-caution-modal">
    <h1>Hoge設定の保存</h1>
    <div style="text-align: left">
        <p>
            <div id="setting-caution-modal-message">この内容にて保存してもよろしいですか?</div>
        </p>
    </div>
    <div style="text-align: center">
        <button data-remodal-action="confirm" class="remodal-confirm">OK</button>
        <button data-remodal-action="cancel" class="remodal-cancel">キャンセル</button>
    </div>
</div>


<!-- Formのcreate() -->
<?php
    echo $this->Form->create('HogeHogeConf', [
        'url' => '/hoge/hoge_hoge/conf',
        'inputDefaults' => [
            'label' => false,
            'div'   => false,
        ],
    ]);
?>


<!-- Form入力欄を色々と用意 -->
<!-- ... -->
<!-- ... -->
<!-- ... -->


<!-- クリックでRemodalを表示させるボタン -->
<?php
    $setingButton  = '<button onclick="location.href=\'#setting-caution-modal\';return false;">保存</button>';
    echo $setingButton;
?>


<!-- Formのend() -->
<?= $this->Form->end(); ?>

結果

Remodalを呼び出しOK押下後にForm内容をsubmitすることができる
buttonの表示もCakePHPのFormHelperでやるべきだが思いつかなかった

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

React にて、publicフォルダ内の画像を参照する

tl;dr

process.env.PUBLIC_URLもしくはwindow.location.originからpublicフォルダのパスを取得できます。

サンプル

?以下のサンプルは、create-react-app直後のApp.jsを編集したものです。

App.js
import React from 'react';

function App() {
  return (
    <div className="App">
        <img src={`${process.env.PUBLIC_URL}/logo192.png`} />
        <img src={`${window.location.origin}/logo192.png`} />
    </div>
  );
}

export default App;

参考

?ReactJS and images in public folder

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

OSXでTypeScriptのビルド時に大文字と小文字を区別する

起こった問題

ローカル環境(OSX)では通っていたTypeScriptnのビルドが、ステージング環境(Linux)では失敗した。

原因

OSXのデフォルトのファイルシステムがcase insentive(大文字と小文字を区別しない)するのに対して、 Linuxの方がcase sensitive(大文字と小文字を区別する)であったため。

UIコンポーネントを開発している時は、モジュール名の先頭を大文字にしていたが、import時に小文字にtypoしてしまっていた。それでもMacの場合は、ビルドが通ってしまう。

*Linux Kernelでもファイルシステムによっては case insentiveになる模様。

解決策

一旦はtypoを修正してことなきを得たが、恒久対応をしたい。

tsconfig.jsonのcompilerOptionsで設定できないかなと思ったが出来なかった。

2018年から議論をしているが導入までには至っていないようだった。個人的にはcompilerOptionsで設定出来るようにして欲しい。
該当のissue

最終的には、以下のwebpackのpluginで解決した。
https://github.com/Urthen/case-sensitive-paths-webpack-plugin

This Webpack plugin enforces the entire path of all required modules match the exact case of the actual path on disk. Using this plugin helps alleviate cases where developers working on OSX, which does not follow strict path case sensitivity, will cause conflicts with other developers or build boxes running other operating systems which require correctly cased paths.

OSXで開発する人向けであり、今回の問題解決にドンピシャのpluginだった。

webpack.config.js
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');

module.exports = {
  // other plugins ...
  plugins: [
    new CaseSensitivePathsPlugin(),
  ]
  // other webpack config ...
};

moduleをinstallすれば特にオプションを追加することもなく、上記のようにプラグインを設定するだけで、大文字と小文字を区別することが出来る。

Macで開発する時は常に入れておいた方が良い気がする。

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

Vue.jsのカスタムコンポーネントを拡張する

Vue.jsのカスタムコンポーネントを拡張する

既存のコンポーネントの機能を生かしつつ、機能追加する方法のまとめ。
VuetifyのVTextFieldを拡張して、数値をカンマ区切りで表示する入力フィールドを作ります。

仕様

  • 入力(focus)時はtype="number"としてカンマなしの数値を入力する
  • 表示(非focus)時はtype="text"として3桁のカンマ区切りで表示する
  • valueの型はNumber|nullとする
  • v-modelでの双方向バインディングが出来るようにする

ラッパーコンポーネントの作成

まずプロパティ、イベント、スロットを透過的にVTextFieldに渡すコンポーネントを作成します。

プロパティ

プロパティをVTextFieldに受け渡すために属性の継承を無効化して、v-bindを指定します。

デフォルトの挙動はテンプレートのルート要素に属性が継承されますが、$propsに受け渡されるわけではなく、$attrsに直接渡されてしまうため、期待した動作をしません。
※この辺りの詳細は参考の記事に分かりやすくまとめられています

また、VTextFieldをルート以外の要素にしたい場合はデフォルトの継承では出来ないのでv-bindで渡す必要があります。

<template>
  <v-text-field
    v-bind="$attrs"
  >
  </v-text-field>
</template>

<script>
export default {
  inheritAttrs: false,
}
</script>

参考

イベント

v-onにイベントリスナーを受け渡します。
後のv-modelの実装を分かりやすくするため:value props listeners()を実装していますが、子がカスタムコンポーネントなので、inputイベントの内容を変更する必要が無ければv-on="$listeners"を追加するだけでも動作します。

<template>
  <v-text-field
    v-bind="$attrs"
    :value="value"
    v-on="listeners"
  >
  </v-text-field>
</template>

<script>
export default {
  inheritAttrs: false,
  props: ['value'],
  computed: {
    listeners() {
      const vm = this;
      return {
        ...vm.$listenres,
        // 子がカスタムイベントなのでeventに直接値が入っている
        input: event => vm.$emit('input', event)
      };
    },
  }
}
</script>

参考

スロット

$slotsからスロット名を取り出して、VTextFieldに渡します。

<template>
  <v-text-field
  >
    <template v-for="(value, name) in $slots" v-slot:[name]>
      <slot :name="name"/>
    </template>
  </v-text-field>
</template>

参考

これで作成したコンポーネントがVTextFieldの機能をすべて利用できるようになりました。

実装

続いて追加機能を実装します。

入力時はtype="number"、表示時はtype="text"にする。

dataに入力/表示の状態を保持するeditingを追加して、@focusin @focusoutで制御します。

<template>
  <v-text-field

    :type="type"
    @focusin="edit"
    @focusout="display"
  >

  </v-text-field>
</template>

<script>
export default {

  data() {
    return {
      editing: false
    };
  },
  computed: {

    type() {
      if (this.editing) {
        return "number";
      } else {
        return "text";
      }
    }
  },
  methods: {
    display() {
      this.editing = false;
    },
    edit() {
      this.editing = true;
    }
  }
};
</script>

valueの型をNumberに。入力時はカンマ無し、表示時はカンマ区切り。

valueをNumberにして、このコンポーネントが受け取る場合はNumber、VTextFieldに渡す場合はstringにします。
受け渡し用に算出プロパティのinnerValueを追加して、getterでNumber -> stringとカンマ区切りの変換、setterでstring -> Number の変換を行います。
算出プロパティを使うことでその中で使用している変数(value、editing)が変更された場合に算出プロパティに反映されます。

<template>
  <v-text-field
    v-bind="$attrs"
    :value="innerValue" // VTextFieldには算出プロパティを渡す
    v-on="listeners"

  >

  </v-text-field>
</template>

<script>
export default {
  inheritAttrs: false,
  props: {
    value: {
      type: Number,
      default: null
    }
  },
  data() {
    return {
      editing: false
    };
  },
  computed: {
    listeners() {
      const vm = this;
      return {
        ...vm.$listenres,
        input: event => vm.innerValue = event // 直接emitせずにinnerValueを通すように変更
      };
    },
    innerValue: {
      get() {
        if (this.editing) {
          return this.value != null ? this.value.toString() : "";
        } else {
          return this.value != null ? this.value.toLocaleString() : "";
        }
      },
      set(newValue) {
        const value = newValue.length > 0 ? Number(newValue) : null;
        this.$emit("input", value);
      }
    },
  },

};
</script>

これで、v-modelに値を渡せば双方向バインディングでリアルタイムに書き換わるようになります。

呼び出し元コンポーネント

<template>
  <div id="app">
    <my-number-field v-model="val" label="Number Field">
      <template v-slot:append></template>
    </my-number-field>
  </div>
</template>

<script>
import MyNumberField from "./components/MyNumberField.vue";

export default {
  name: "App",
  components: {
    MyNumberField
  },
  data() {
    return {
      val: 1000
    };
  },
}
</script>

コード

全体コードはこちら

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

M5StickCを使ってブラウザ上で3Dモデルを動かしてみる

はじめに

obnizOSを搭載したM5StickCに内蔵された角速度センサーをコントローラーにして、ブラウザ上の3Dモデルを動かしたいと思います。

ブラウザ上に3Dモデルを描画する方法として、A-Frame(ブラウザ上に簡単にVRや3Dを作成できるフレームワーク)を使用します。

完成品

用意するもの

作成手順

1. プログラムを書く

最終プログラムは以下になります。

index.html
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
  <script src="https://obniz.io/js/jquery-3.2.1.min.js"></script>
  <script src="https://aframe.io/releases/latest/aframe.min.js"></script>
  <script src="https://unpkg.com/obniz@3.1.0/obniz.js" crossorigin="anonymous"></script>
  <script src="https://unpkg.com/m5stickcjs/m5stickc.js"></script>
  <style>
    #title{
      text-align: center;
      margin-top: 20px;
      margin-bottom: 20px;
    }
  </style>
</head>
<body>

  <div id="obniz-debug"></div>
  <h1 id="title">M5StickC 3D</h1>
  <a-scene>
    <a-cube id="cube" position="0 1.5 0" rotation="40 0 0" width="1.6" height="0.8" depth="3.4" color="#FF6600">
      <a-plane position="0 0.42 -0.5" rotation="90 0 0" width="0.8" height="1.7" color="#333333"></a-plane>
    </a-cube>
  </a-scene>
  <script>

    const m5 = new M5StickC('OBNIZ_ID_HERE');
    console.log("connecting");

    m5.onconnect = async function(){

      console.log("conncected");
      let imu = m5.wired("MPU6886",{i2c:m5.m5i2c});
      let data = await imu.getGyroWait();
      let cube = document.getElementById("cube");

      let a,b,c,x,y,z;
      [a,b,c,x,y,z] = [data.x,data.z,data.y,0,0,0];

      async function gyro(){
        let data = await imu.getGyroWait();
        x += (data.x-a)/5;
        y += (data.z-b)/5;
        z -= (data.y-c)/5;
        cube.setAttribute("rotation",{x:x,y:y,z:z});
      }
      let interval = setInterval(gyro,200);
    }

  </script>
</body>
</html>


各コードの解説をしていきます。

aframe.js, obniz.js, m5stickc.jsをそれぞれ読み込みます。
obniz.jsを読み込んだ後に、m5stickc.jsを読み込むようにしてください。

<script src="https://aframe.io/releases/latest/aframe.min.js"></script>
<script src="https://unpkg.com/obniz@3.1.0/obniz.js" crossorigin="anonymous"></script>
<script src="https://unpkg.com/m5stickcjs/m5stickc.js"></script>


A-Frameの部分は以下になります。
<a-scene>タグでシーンを作成し、<a-cube>で四角形、<a-plane>で平面を作成しています。
position属性で各位置、rotation属性で回転状態を決めています。

<a-scene>
  <a-cube id="cube" position="0 1.5 0" rotation="40 0 0" width="1.6" height="0.8" depth="3.4" color="#FF6600">
    <a-plane position="0 0.42 -0.5" rotation="90 0 0" width="0.8" height="1.7" color="#333333"></a-plane>
  </a-cube>
</a-scene>


このコードでM5StickCのインスタンスを作成します。OBNIZ_ID_HEREにはあなたのobniz_idを入れてください。

const m5 = new M5StickC('OBNIZ_ID_HERE');


M5StickCに内蔵されたMPU6886という6軸センサ(加速度、角速度)を初期化します。

let imu = m5.wired("MPU6886",{i2c:m5.m5i2c});


.getGyroWait()で角速度を一度取得します。

let data = await imu.getGyroWait();

ディスプレイが水平な状態にしてM5StickCを置くと、角速度の値は(x : 0, y : 0, z : 0)になるはずですが、実際に取得してみると、値には少しズレがあります。

そのため、最初にディスプレイが水平な状態で角速度の初期値を取得し、a,b,cに代入します。
その後、data.x, data.y, data.zのそれぞれから初期値 a, b, cを引くことでズレを無くしています。

gyro()という関数を0.2秒に一度実行して、回転角 x, y, zを導出し、<a-cube>のrotation属性に代入することで、M5stickCの動きに合わせて3Dモデルも同じ動きをします。

2. M5StickCを電源に繋ぐ

電源が入ってもディスプレイには何も表示されません。起動したのかわかりづらいですが、電源ボタンを何度も押さないようにしましょう。

3. 実行

実行するとこのようにM5StickCの動きに合わせて、ブラウザ上の3Dモデルも動きます!

最後に

今回は3Dモデルを動かしましたが、M5StickCをコントローラーにしたゲームなどを作ってみるのも面白いかもしれません。色々試してみたいと思います!
最後まで読んでいただきありがとうございました。

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

javascript基礎 HelloWorldから変数と定数まで

hello

■ コンソールでHello World

html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>JavaScript practice</title>
        <style>
        </style>
    </head>
    <body>
    </body>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script>
        'use strict';
        console.log("Hello World")
    </script>
</html>
console
Hello World

■ 変数

変数・・値を格納する箱

■ let

main.js
let name = "hoge";
console.log("再代入前は" + name);//①
name = "huga";
console.log("再代入後は" + name);//②
再代入前はhoge  
再代入後はhuga  

再代入はできる

再宣言はできない

main.js
let name = "hoge";
console.log("再代入前は" + name);//①
name = "huga";
console.log("再代入後は" + name);//②
let name = "gugu"
Uncaught SyntaxError: Identifier 'name' has already been declared

■ 定数

■ const

main.js
const name = "hoge";
console.log("再代入前は" + name);//①
name = "huga";
console.log("再代入後は" + name);//②
再代入前はhoge
main.js:4 Uncaught TypeError: Assignment to constant variable. at main.js:4

再代入できない
再宣言できない

main.js
const name = "hoge";
console.log("再代入前は" + name);//①
name = "huga";
console.log("再代入後は" + name);//②
const name = "gugu"
console
Uncaught SyntaxError: Identifier 'name' has already been declare

基本的には、constを使用する。

■ テンプレートリテラル

main.js
const name = "hoge";
console.log(`テンプレートリテラルで${name}を表示する。`);
console
テンプレートリテラルでhogeを表示する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者による ES6 ジェネレーター

概要

ES6のgeneratorを利用することで、列挙可能なオブジェクトをより簡単に実装することができる。generatorを実装するのに必要なのは

1 function* {} で定義する。
2 yield 命令で値を返す。
3 next() で開始/再開

書き方としては、

sample.js
function* print(){}
function *print(){}

のどちらでもよいが、後者のほうがオブジェクトリテラルでの簡潔な書き方に親しみやすい。

sample.js
const print = {
 *printDatas(){
  //コード
 }
}

yieldはreturnと似ているが、yieldはその値をいったん呼び出し元に返した後、その位置から処理を再開させます。yieldで返されたオブジェクトは停止している状態であり、next()メソッドが呼び出されるまで何もしない。

sample.js
//列挙可能なオブジェクト
function* print() {
 yield 'Hello!'
 yield 'ES6'
 yield 'generator!'
}

//next()で毎回呼び出す
console.log(print.next()) //Hello
console.log(print.next()) //ES6
console.log(print.next()) //generator!

//next()を内部的に含んでいるfor...ofで呼び出す
for(let t of print()){
 console.log(t)
}

next()メソッドを呼び出すたびに、iteratorオブジェクトと同様なvalueとdoneの2つのプロパティを持つオブジェクトが返される。for...ofを使うことでprint()をすべて列挙することができる。yieldによってその時々で必要な処理をすることができるので、一度に列挙してからそれぞれに処理するよりもメモリ消費を抑えられるのが特徴だ。

next()に引数を入れて呼び出した場合かなり複雑になる。

print.next(1)   ->    let A = yield 'a' //前で実行したyieldから次のyieldまで実行
            console.log(A)    // Aオブジェクトのvalueプロパティとして 1 が出力される!
                      yield 'b'

一つ前のnext()においてyieldで 'a' を返したが、次のnext()で引数を添えて呼び出した場合、オブジェクト(今回ではAオブジェクト)にその引数(今回は1)がvalueプロパティとし格納される。

iterator -> generator

自作classを作ったときに、iteratorを実装したがgeneratorでも同じことを実装してみる。

sample.js
class Name {
 constructor(name){
  this.name = name
  //yieldは内部的にiteratorを返している。
  this[Symbol.iterator] = function*() {
   let current = 0
   let that = this
   while(current < that.name.length){
    yield that.data[current++]
   }
  }
}

let itr = new Nmae(['1', '2', '3'])
for(let value of itr){
 console.log(value)
}

for...of命令は内部的には「iteratorの取得、doneメソッドによる判定、valueプロパティによる値の取り出し」を行うので、generatorによって内部的なiteratorオブジェクトを設定しているのである。

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

HLSを使って重い動画をWEBサイトに実装する

はじめに

実務で<video>だけで実装することが少なくなってきたのでHLS化についてまとめようかと。
長くて高画質な動画をスムーズに再生する様に実装しろよ!って無茶にも、華麗に実装できたらかっこいいでしょ?ねぇ?

HLSってなんぞ

HLS は HTTP Live Streaming の略で、Apple が定めた動画のストリーミング、オンデマンド配信(VOD)の技術。
つまり

  • 高いコストをかけずにストリーミング配信ができる配信技術
  • マルチビットレート(アダプティブビットレート)に対応
  • 様々なOS・ブラウザで再生可能
  • ライブ配信にも対応できる

ここで重要なのは様々なOS、ブラウザで配信することができ、ストリーミング再生ができること。

ストリーミング再生とはインターネット上のメディア(映像や音楽など)をすぐに再生する技術のこと

HLSの簡単な仕組み

HLSではセグメント化※(分割)された動画データが必要。mp4形式などの動画ファイルを、

  • セグメントリストファイル(.m3u8)
  • セグメント動画ファイル(.ts)

に分割して、それを再生する。
下の様なファイル群

video.m3u8
video.mp4
video000.ts
video001.ts
video002.ts
video003.ts
video004.ts
video005.ts
video006.ts
...
..
.

これらのセグメントファイルはそれぞれ役割があり、

  • セグメントリストファイル(.m3u8)
    • セグメント動画ファイルの場所、再生時間、再生順序などを記述したメタデータファイル。
  • セグメント動画ファイル(.ts)
    • 動画形式はMPEGトランスポートストリームファイル形式と同じだが、これをさらに10秒毎のように、分割秒数を指定して分割した動画ファイル。

・・・どうやって動画分割すんねん!!!

動画の分割方法(ffmpeg)

ここで登場するのはffmpeg
動画や音声を加工するのに強力で万能な簡単ツールであり、このツールを使うと簡単に.m3u8.tsを生成できます。

インストール

ffmpegはWindowsでもmac,その他OSでも使えてとても便利。
https://ffmpeg.org/
からDLできる。
macはHomebrewでインストール可能。

ターミナル
brew install ffmpeg

Homebrewが入っていない人はこちら

変換

いろいろな変換加工ができるが、今回は分割の方法のみを紹介
他が気になる方はこちら

以下のコマンドで、video.mp4 を video.m3u8 ファイルと、video001.ts などという名前のセグメントファイルを生成できる

ターミナル
ffmpeg -i video.mp4 -c:v copy -c:a copy -f hls -hls_time 9 -hls_playlist_type vod -hls_segment_filename "video%3d.ts" video.m3u8
  • ffmpeg -i video.mp4 video.m3u8
    • -i:変換ファイルを指定
  • -c:v copy,-c:a copy: Audio や Video のコーデックを元の mp4 ファイルのものをそのまま使うように指定(よっぽどがなければこのまま使う)
  • -f hls:動画のフォーマット
  • hls_time 9: 9 秒ごとに .ts ファイルに分割指定
  • -hls_playlist_type vod:オンデマンド配信
  • -hls_segment_filename "video%3d.ts":動画セグメントファイル名のフォーマットを指定

それぞれの用途に合わせて設定できたらHLSで使えるファイルの完成!
こんなのができているはず

video.m3u8
video.mp4
video000.ts
video001.ts
video002.ts
video003.ts
video004.ts
video005.ts
video006.ts
...
..
.

エラー出た人は変換したい動画がある階層にいるか確認してみて。
私は階層移動してなかったから焦ったよ?

HTMLへ実装

実はHLSはSafari、Edgeについてはネイティブ動画プレーヤーが標準でHLS対応しているが、Chrome、Firefox、IEについてはHLSに対応していないため再生できない。

今更そんな事って。。。

安心してください。
対応する js を付け加えるだけで使用可能です。

それはhls.js

以前は Video.js が主流だったがFlash対応向けに設計されている点と不具合が多い為、現在では hls.js が利用が多い。

hls.js を利用すると、以下のコードを書くだけで、Chrome、Firefoxのネイティブ動画プレーヤーにHLSに対応する機能を追加することができ、ストリーム再生可能となる。

index.html
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script> //自前のサーバーに配置しない場合
<video id="video_smaple" controls>
<script>
    if(Hls.isSupported()) {
    var video = document.getElementById('video_smaple');//videoタグと同じIDにする
    var hls = new Hls();
    hls.loadSource('./video.m3u8'); //ここで.m3u8ファイルを指定
    hls.attachMedia(video);
     }
</script>

あら不思議!きれいに動画実装されているよ!

まとめ

Webの動画の掲載方法については、目まぐるしい速度で変化しているので、きっとこの技術もすぐ変わってしまうと思うが、最新の情報だけでなく、今自分が直面している問題に欲している情報をキャッチアップする事が大切だと感じた。
誰かの役に立てばと思います。

ね?カッコよかったでしょ?

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

Webカメラにおける顔認識を最小限のコードで実現させる

はじめに

顔認識のプラットフォームは、すでにAWS、Azure、GCPなどでサービスとして展開されていますが、フロント(Javascript)でもやりたいと思い調査したので、共有します。今回はあくまでもコアとなるコードをわかりやすくするためにできる限り無駄なコードを書かずに実現していますので、他の機能も使ってみたい場合は以下のサイトを確認してみてください。

face-api.js

手順

まず、公式のリポジトリをクローンします。

git clone https://github.com/justadudewhohacks/face-api.js.git

以下のファイルをコピーします。

  • dist/face-api.min.js → js/
  • weights/tiny_face_detector_model-shard1 → models/
  • weights/ttiny_face_detector_model-weights_manifest.json → models/
<!DOCTYPE html>
<html>

<head>
  <title>Face Detect Sample</title>
</head>

<body onload="init()">
  <video id="video" autoplay onloadedmetadata="onPlay()"></video>
  <p id="message"></p>

  <script src="js/face-api.min.js"></script>
  <script>
    const video = document.getElementById("video");
    const init = async () => {

      // Webカメラ初期化
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: false,
        video: {
          width: 400,
          height: 400
        }
      });

      try {
        video.srcObject = stream;
      } catch (err) {
        video.src = window.URL.createObjectURL(stream);
      }
      // (1)モデル読み込み ※フォルダを指定
      await faceapi.nets.tinyFaceDetector.load("models/");
    }

    const onPlay = () => {
      const message = document.getElementById('message')
      const inputSize = 512; // 認識対象のサイズ
      const scoreThreshold = 0.5; // 数値が高いほど精度が高くなる(〜0.9)
      // (2)オプション設定
      const options = new faceapi.TinyFaceDetectorOptions({
        inputSize,
        scoreThreshold
      })
      const detectInterval = setInterval(async () => {
        // (3)顔認識処理
        const result = await faceapi.detectSingleFace(
          video,
          options
        );

        if (result) {
          message.textContent = "認識されてます"
        } else {
          message.textContent = "認識されていません"
        }
      }, 500);
    }
  </script>
</body>

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

外出先でWebサイトをデバッグする

はじめに

PCやUSBケーブルなどを使わずに、外出先でモバイルブラウザ向けの開発コンソールであるErudaを任意のWebサイトで呼び出します。このErudaを使うことでChrome Dev ToolsのようにDOMやリソースなどを表示することができます。
Erudaを使ったデバッグ方法については割愛しますが、
ブックマークレットiOSのショートカットを使ってモバイルブラウザ上でErudaを簡単に呼び出すことができます。

ブックマークレット

最も簡単なのが、以下のGistにあるブックマークレットをブックマークに登録し、iPhoneやAndroidなどのモバイルブラウザから呼び出す方法です。一応、PCのブラウザからも呼び出せますが、素直にChrome Dev Tools を使った方が良いです。
Eruda Bookmarklet - GitHub Gist

任意のサイトを開いて、この登録したブックマークを呼び出すことで、ブラウザの右下にErudaの歯車アイコンが表示されます。
このアイコンをタップすることで、Erudaの開発コンソールが開きます。

iOSのショートカット

こちらは、iPhone限定(iOS12以上)の方法です。
主に、SafariブラウザやSFSafariViewController、ASWebAuthenticationSessionを使ったブラウザで利用することができます。

このショートカットでは、Erudaのjsライブラリを読み込むJavaScriptを任意のWebサイトで実行します。

JavaScriptの実行処理

以下のコード上のブラケットで囲っている部分は変数で、Erudaのjsライブラリ本体をテキストとしてショートカット内部で設定しています。
また、Eruda本体のjs以外に、eruda-dom.jseruda-timing.jserada-code.jsの3つのプラグインを上記のブックマークレットと同様に設定しています。プラグインの詳細はこちらを参照してください。

// eruda本体のjs
{eruda.js}

// erudaのプラグインのjs
{eruda-dom.js}
{eruda-timing.js}
{eruda-code.js}

// erudaの初期化
eruda.init();

// erudaのプラグインの読み込み
eruda.add(erudaDom);
eruda.add(erudaTiming);
eruda.add(erudaCode);

// JavaScriptの完了コールバックの呼び出し
completion(true);

ショートカットの実行方法

作成したショートカットの共有リンクをタップしてショートカットをインストールします。
その後、任意のWebサイトをSafariなどで開き、共有ボタンからErudaというショートカットを呼び出せば、ブラウザの右したにErudaの歯車アイコンが表示されます。

image.png

作成したショートカットの詳細については、インストールしたショートカットの内部を参照してください。

参考サイト

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

[Electron] IPC には新しい ipcRenderer.invoke() メソッドを使ったほうが便利 (v7+)

TL;DR;

Electron v7 から、ipcRenderer.invoke()ipcMain.handle() が新たに追加されました。これは、従来まで利用されてきた ipcRenderer.send()ipcRenderer.sendSync() の上位互換のようなものです。今後は積極的にこちらを使ったほうがよさそう。

従来の Renderer <-> Main プロセス間通信 (IPC)

同期: ipcRenderer.sendSync()

文字通り、同期 (Sync) 的にプロセス間通信を行います。
この際に重要なのは、sendSync によって Main プロセスが呼ばれるとその間は Renderer プロセス上の処理は完全にブロックされます。Main プロセスからの応答があるまでは、renderer プロセス側の操作画面はいわゆるフリーズしたような状態になります。描画処理も止まるので、ローディング画面のような CSS アニメーションも容赦なく固まります。

ドキュメントにも下記のように、どうしても使わざるを得ない状況下においてのみ最終手段として使う、そうでない場合は利用を避けることが推奨されています。

⚠️ WARNING: Sending a synchronous message will block the whole renderer process until the reply is received, so use this method only as a last resort. It's much better to use the asynchronous version, invoke().

renderer
// renderer から Main プロセスを呼び出す
const data = ipcRenderer.sendSync('sync-test', 'ping')
console.log(data)
main
ipcMain.on('sync-test', (event, message) => {
  // message には 呼び出し元からのデータ ('ping') が入っている
  console.log(message)
  event.returnValue = 'pong'
  return
})
console
> pong
> ping

Main プロセス側での処理が終わってから renderer 側で console.log が走るため、結果は上記のようになります。メリットとして、event.returnValue を通じて呼び出し元にデータを返すことができます。

非同期: ipcRenderer.send()

非同期実行のため、Main プロセスの処理中であっても renderer 側ではフリーズすることなく操作が可能です。

ただし Main プロセス側の処理がいつ終わったのか renderer 側では分からないので、これをトリガーに何らかの処理を行いたい場合、renderer 側で ipcRenderer.on() を定義しておくことで、Main -> rendere 方向の戻りの通信が可能になります。

renderer
// main からの呼び出しを待ち受ける
ipcRenderer.on('async-test-complete', (event, message) => {
    console.log(message)
)}

// renderer から Main プロセスを呼び出す
ipcRenderer.sendSync('async-test', 'ping')
console.log('started')


main
ipcMain.on('async-test', (event, message) => {
  // message には 呼び出し元からのデータ ('ping') が入っている
  console.log(message)
  // main から renderer プロセスを呼び出す
  event.sender.send('async-test-complete', 'pong')
  return
})
console
> started
> ping
> pong

新しい Renderer <-> Main プロセス間通信 (IPC)

非同期: ipcRenderer.invoke()

ようやく本題です。Electron version 7+ から利用可能になりました。
こちらも非同期ですが、Main プロセスからは Promise が返ってきます。ですので、await を使って Main プロセスからのデータの受取を下記のようにシンプルに書くことができます。

renderer
// renderer から Main プロセスを呼び出す
const = data = await ipcRenderer.invoke('invoke-test', 'ping')
console.log(data)
main
ipcMain.handle('invoke-test', (event, message) => {
  // message には 呼び出し元からのデータ ('ping') が入っている
  console.log(message)
  // renderer プロセスにデータを返す
  return 'pong'
})
console
> ping
> pong

Main 側では、ipcMain.handle() とメソッド名が変わっています。

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

leafletのテスト

leafletdemo4_marker.html
<!DOCTYPE html>
<html>
    <head>
        <title>Leaflet Demo4</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="">
        <script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js" integrity="sha512-/Nsx9X4HebavoBvEBuyp3I7od5tA0UzAxs+j83KgC8PU0kgB4XiK4Lfe4y4cgBtaRJQEIFCW+oC506aPT2L1zw==" crossorigin=""></script>
        <style type="text/css">
        <!--
            #mapid { height: 400px; width: 600px}
        -->
        </style>
    </head>
    <body>
        <div id="mapid"></div>

        <script>
            var mymap = L.map('mapid').setView([35.7102, 139.8132], 15);

            L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
                maxZoom: 18,
                attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, '
            }).addTo(mymap);

            var marker = L.marker([35.7101, 139.8107]).addTo(mymap);
            marker.bindPopup("<p>popup1</p><p>地図表示時に一緒に表示されます。</p>").openPopup();

            var marker2 = L.marker([35.7121, 139.8207]).addTo(mymap);
            var popup = L.popup();
            function onMarker2Click(e) {
                popup
                .setLatLng(e.latlng)
                .setContent("<p>popup2</p><p>clickで表示されます。</p>" + e.latlng.toString())
                .openOn(mymap);
            }
            mymap.on('click', onMarker2Click);

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

Firestoreにおける(クライアント側)集計

RDBと違い、Firestoreにおける集計は原則クライアント再度で行う必要があります。
その際の基本的なメモ。なお、JSにおける(配列の)集計処理にはreduceを使うことになりますが、それについてはこちらの記事をご覧ください。

前提

  • firebase-adminを利用してみます。
  • 秘密鍵は準備されているものとします。

テストデータの作成

下記のようなデータをfirestoreに登録します。

createData.js
const admin = require('firebase-admin');
admin.initializeApp({
    credential: admin.credential.cert("/path/to/key.json"),
    databaseURL: "https://xxxxxxxx.firebaseio.com"
});

const db = admin.firestore();

const commodities = [
    { id: 1, name: "apple", category: "fruit", price: 300 },
    { id: 2, name: "cucumber", category: "vegetable", price: 100 },
    { id: 3, name: "orange", category: "fruit", price: 300 },
    { id: 4, name: "grape", category: "fruit", price: 100 },
    { id: 5, name: "cabbage", category: "vegetable", price: 200 },
];

(() => {

    commodities.map(async item => {
        await db.collection("sales").add(item);
    })

    console.log("完了");

})()

key名とかしっくりこないけど、まあとりあえずOKとする。

データの集計

登録が完了したら集計してみます。

getSummary.js
const admin = require('firebase-admin');
admin.initializeApp({
    credential: admin.credential.cert("/path/to/key.json"),
    databaseURL: "https://xxxxxxxx.firebaseio.com"
});

const db = admin.firestore();

(async () => {

    //get data
    const snapshot = await db.collection("sales").get();

    //sum(単純計算)
    const sum = snapshot.docs
        .map(doc => doc.data().price)
        .reduce((prev, current) => prev + current, 0);

    console.log(sum);

    //groupBy(データ整形)
    const groupBy = snapshot.docs
        .map(doc => doc.data())
        .reduce((prev, current) => {
            const element = prev.find(value => value.category === current.category);
            if (element) {
                element.count++;
                element.price += current.price;
            } else {
                prev.push({
                    category: current.category,
                    count: 1,
                    price: current.price,
                });
            }
            return prev;
        }, []);

    console.log(groupBy);

})()

実行結果

下記のような結果が得られるはずです。これをダッシュボード等に描画したり、帳票で出したりすることになります。

1000

[
  { category: 'fruit', count: 3, price: 700 },
  { category: 'vegetable', count: 2, price: 300 }
]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

reduceの使い方メモ(主に集計目的)

reduceを集計目的で使うときのメモ。

reduceの簡単な説明

mapとかfilterとかと同じ部類の関数ですが、他の関数に比べ(名前的にも)何してるのかわかりにくいですが、

[結果] = array.reduce([callback],初期値);

というような形式をとり(初期値は省略可能ですが、prevの意味を理解するためにも付けてたほうがいいです)、前の実行結果の影響を受ける(値を利用する)のが特徴です。

で、下記のように使います。

const array = [1,2,3];
const result = array.reduce((prev,current)=>prev + current,0);

reduceもmap等とどうようループします。で今回は配列が3つなので3回ループし、それぞれの処理として下記のような処理が実行されます。

  • 1回目:prevの値は初期値の0、currentには1が入ります。なので、0 + 1 = 1が(次のprev値として)返ります。
  • 2回目:prevの値は1、currentには2が入るので、 1 + 2 = 3が返ります。
  • 3回目:prevの値は3、currentには3が入るので、 3 + 3 = 6が返ります。で、これが最終的な結果として返ります。

いかがでしょうか。

基本

プリミティブ

合計、平均値、最大値、最小値とか。
平均値に関してだけは、sumを利用して別途計算するのがてっとり速いかと。

const numbers = [1, 2, 3, 4, 5];

//合計
const sum = numbers.reduce((prev, current) => prev + current, 10);
console.log(sum);

//平均値
const average = sum / numbers.length;
console.log(average);

//最大値
const max = numbers.reduce((prev, current) => prev > current ? prev : current);
console.log(max);

//最小値
const min = numbers.reduce((prev, current) => prev < current ? prev : current);
console.log(min);


25
5
5
1

オブジェクト

次にオブジェクト。実際の現場ではこちらのほうが一般的かと。
ま、対象とするkeyを指定するだけで、基本的にはプリミティブと同じ。
とりあえず、合計と最大値のサンプル(後は割愛)。

//data
const members = [
    { name: "foo", score: 10, dep: 'sales1' },
    { name: "bar", score: 20, dep: 'sales2' },
    { name: "baz", score: 30, dep: 'sales2' },
]

//合計
const scoreSum = members.reduce((prev, current) => {
    return prev + current.score;
}, 0);
console.log(scoreSum);

//最大値
const maxScore = members.reduce((prev, current) => {
    return prev.score > current.score ? prev.score : current.score;
}, 0);
console.log(maxScore);


60
30

応用

GroupBy的な

実開発ではよく使うのでメモ。
出力するデータ・フォーマットをイメージしておく必要があります。今回は、部署(dep)毎に下記のような形式でデータを出力する想定。

{
    dep:DEP_NAME, //部署名
    count COUNT, //何件あるか
    score: SCORE //スコアの合計
}

では作ってみます。

//souce data
const members = [
    { name: "foo", score: 10, dep: 'sales1' },
    { name: "bar", score: 20, dep: 'sales2' },
    { name: "baz", score: 30, dep: 'sales2' },
]

//groupBy
const groupBy = members.reduce((result, current) => {

    //部署がprevにあるか
    const element = result.find(value => value.dep === current.dep);

    if (element) {
        //ある時(下記、初期データを操作)
        element.count++;
        element.score += current.score;
    } else {
        //無いとき(新規に初期データを作成)
        result.push({
            dep: current.dep,
            count: 1,
            score: current.score,
        })
    }
    return result;

}, []); //初期値は[]

console.log(groupBy);



[
  { dep: 'sales1', count: 1, score: 10 },
  { dep: 'sales2', count: 2, score: 50 }
]

Average

平均を追記してみます。出力されたデータをmapでループして平均値を追加しています。

const withAverage = groupBy.map(item => ({ ...item, ave: item.score / item.count }));
console.log(withAverage);

[
  { dep: 'sales1', count: 1, score: 10, ave: 10 },
  { dep: 'sales2', count: 2, score: 50, ave: 25 }
]

平均には合計と合計カウントが必要なので1ループでは無理ですかね。。。mapをつなげて書くことはできるかと思います。

Sort

ソート。まあ基本の応用。

//sort
const sorted = withAverage.sort((prev, current) => prev.score < current.score ? 1 : -1); //大きい順
// const sorted = groupBy.sort((prev, current) => prev.score > current.score ? 1 : -1); //小さい順
console.log(sorted);


[
  { dep: 'sales2', count: 2, score: 50, ave: 25 },
  { dep: 'sales1', count: 1, score: 10, ave: 10 }
]

ひとまず以上です。必要に応じて随時追加予定。

番外編:配列データのJOIN(client side join)

集計の際によく2つの配列をJOINすることがあるのでついでに書いておきます。

const deps = [
    { depId: 1, depName: "経理" },
    { depId: 2, depName: "営業" },
    { depId: 3, depName: "その他" },
];

const members = [
    { name: "foo", score: 10, depId: 1 },
    { name: "bar", score: 20, depId: 2 },
    { name: "baz", score: 30, depId: 3 },
]

const membersWithDep = members.map(member => {
    //既存のmemberの要素を展開しつつ、depIdがマッチしたdepNameの値を追記する
    return { ...member, depName: deps.find(value => value.depId === member.depId).depName }
});

console.log(membersWithDep);


[
  { name: 'foo', score: 10, depId: 1, depName: '経理' },
  { name: 'bar', score: 20, depId: 2, depName: '営業' },
  { name: 'baz', score: 30, depId: 3, depName: 'その他' }
]

その他

Firestoreでの適用事例?を記述しました。

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

【Vue.js】オブジェクトのリストをコンポーネントで表示するときのベストプラクティス

こんなデータを表示したい

以下のリスト一つ一つをvueコンポーネントとして表示

list: [
  {
    id: 1,
    name: "リスト1"
  },
  {
    id: 2,
    name: "リスト2"
  }
]

子コンポーネント

propsで親コンポーネントから渡された値を画面に表示するだけのコンポーネントです。

ListComponent.vue
<template>
  <div>{{ target }}</div>
</template>

<script>
export default {
  props: {
    target: {
      type: Object,   // 型がObjectである
      required: true  // 必須のプロパティである
    }
  }
};
</script>

ポイント

何も考えずにpropsに値を渡すと、以下のようになると思います。

<script>
export default {
  props: [target]
};
</script>

この場合、以下のようなデメリットがあります。

  • 値がundefinedのときに気づきにくい
  • 変数にどんな値が入るか推測しづらい

propsには、基本的にバリデーションを行いましょう。

バリデーションには

  • 必須かどうか
  • デフォルト値

などを指定することができます。
ここである程度バリデーションをかけておくことで、エラーの発生原因などを特定しやすくなります。

親コンポーネント

オブジェクトのリストをfor文で全て表示します。

Parent.vue
<template>
  <div>
    <list-component v-for="item in list" :key="item.id" :target="item" />
  </div>
</template>

<script>
export default {
  components: {
    ListComponent
  },
  data: function() {
    return {
      list: [{ id: 1, name: "リスト1" }, { id: 2, name: "リスト2" }]
    };
  }
};
</script>

ポイント

  1. v-forはコンポーネントタグの中で書ける
  2. listの中身を一意に判定できるkeyを指定する
  3. オブジェクトのまま子コンポーネントに渡すことができる

1. v-forはコンポーネントタグの中で書ける

<template>
  <div>
    <div v-for="item in list"> <!-- v-forのためだけのタグで囲う必要はない -->
      <list-component :target="item" />
    </div>
  </div>
</template>

2. listの中身を一意に判定できるkeyを指定する

keyが未指定でもエラー出ませんが、公式ドキュメントでも推奨されています。

リストに変更があった場合

  • 'key`未指定:リストが全件更新、再描画してしまう
  • 'key`指定:変更があったリストの中身を検知し、それだけを更新

3. オブジェクトのまま子コンポーネントに渡すことができる

オブジェクトの中身を以下のようにそれぞれ渡すことができますが、
オブジェクト内部の情報を全て使うのであれば、オブジェクトごと子コンポーネントに渡しましょう。

<template>
  <div>
    <list-component v-for="item in list" :key="item.id"
      :id="item.id"
      :name="item.name"
    />
  </div>
</template>

あとがき

よくあるケース&忘れそうなのでまとめておきました。

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

mp3とかのid3タグを編集するエディタ

mp3のタグを編集するツールを探していたけどなかったので作りました。
メモリをどかぐいするかもしれません

URL

https://misogihagi.github.io/id3editor/

使ったライブラリ

本ツールの最大の貢献者たち
JavaScript-ID3-Reader
browser-id3-writer
handsontable
jszip
FileSaver.js

ライセンス

JavaScript-ID3-Readerのライセンスがなんのライセンスかわからないのでここに記載します。ちなみに本アプリケーション自体のライセンスはMITみたいなものです。

Copyright (c) 2008 Jacob Seidelin, http://blog.nihilogic.dk/ BSD License

Copyright (c) 2009 Opera Software ASA BSD License

Copyright (c) 2010 António Afonso BSD License

Copyright (c) 2010 Joshua Kifer BSD License

何かあった時のビルドコマンド

browserify src > dist.js

おまけ

これの開発するにあたってこちらのライブラリを試していたのですが、browserifyできずにwebpackを使い、構成がかなり面倒だったので供養しておきます。

package.json
{
  "name": "id3",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "id3js": "^2.1.1"
  },
  "devDependencies": {
    "@babel/core": "^7.7.7",
    "@babel/preset-env": "^7.7.7",
    "babel-loader": "^8.0.6",
    "babel-preset-es2015": "^6.24.1",
    "babelify": "^10.0.0",
    "fs": "0.0.1-security",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  }
}
babel.config.js
module.exports = {
  entry: './src.js',
  output: {
    path: __dirname,
    filename: 'dist.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: "babel-loader",
        options: {
          presets: [[
            "@babel/preset-env",
            {
              "useBuiltIns": "entry"
            }
          ]]
        }
      }
    ]
  },
  node: { //Module not found: Error: Can't resolve 'fs' in 'C:~
    fs: "empty"
  }
};

ああ、変数考えるの面倒くさい。西尾維新でもいればいいのに。
気が向いたらプルリクください。

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

Oculus Quest で日本語を打つ

VR 空間でも日本語入力

rdgqs-1spna.gif
文字小さくてごめんなさい?

製作物

日本語メモ  (Glitch プロジェクト)
Oculus Quest なら Alt + Space で半角 ⇔ 日本語入力の切り替え。
URL を開くと ID が URL に付与されますので、再度編集したい場合は ID が付けられた URL をブックマークしてください。
テキストが変更がされると自動でサーバーに保存されます。

解説

Oculus Quest + Firefox Reality + Glitch + A-Frame で VR 内 VR 開発 を推進している gaegae です。

前回の記事でデメリットに上げませんでしたが、
2020/01/10現在、Oculus Quest では Bluetooth キーボードから日本語入力できません。
Firefox Reality ではソフトキーボードで日本語入力できますが、 Bluetooth は非対応です。
デフォルトブラウザはソフトキーボードも対応していません。

コーディング時に必須ではないので特に問題ないだろうと思っておりましたが、
作業しているときにひらめきを即座にメモできないのがきつく感じてきました?

英語で書くのはしんどい、メモのためにヘッドセット外したら本末転倒。
Firefox Reality のイシューに日本語入力をお願いしていますが、無理難題を Mozilla さんに押し付けるのも失礼。

なのでプログラマなのだから自分でどうにかしたいと思います。
VR 開発の記事をどんどん上げていきたいところですが、その前に足固めです。

方針

Oculus Quest の IME をどうにかするのは正直無理。 やる前からあきらめてました。
最低限、日本語のメモが取れればよかったので、
Javascript で日本語入力 を実装した Web ページを作成することにしました。

詳細

フロントエンドとサーバーサイド、2つの Glitch プロジェクトを作りました。

フロントエンド:https://glitch.com/~oculusquest-jpn-ime
テキストエリアが一つだけの Web ページです。
日本語入力には IgoIME を使用させていただいております。
変更があればサーバーに送信し、再度画面が表示されたときに読み込みます。

サーバーサイド:https://glitch.com/~dot-spruce
認証不要で使えるシンプルな Key Value Store です。
node.js 上でキーをファイル名、値を内容にしてファイルとして保存しています。
他のプロジェクトでも使い回せるように分けました。

おわりに

これでまたヘッドセット着脱の手間が減り、VRへの没入感がさらに上がりました。

今回は Oculus Quest よりも Glitch の話がメインでした。
やはりボタン一発で瞬時に Linux・node.js・IDEと Web 開発に必要なもの一通りを提供してくれるのは素晴らしいですね。
Google や AWS 、Github ページなどを利用するよりも気軽に開発・配信ができると思われますので
Web コンテンツ作りたい方、ぜひどうぞ!

bxeaf-a7wmf.gif
VR開発もやってます~。楽しー!

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

デザイナがReactを始める前に押さえておきたいJavaScriptの基本②(オブジェクト編)

はじめに

WebデザイナーがReactを始める前に最低限押さえておくべきJavaScriptの基本(個人的見解)について、前回のJavaScriptの基本①(配列編)に続き、JavaScriptの基本②(オブジェクト編)について書きます。

なぜこの順番なのか?
オブジェクトを理解するためには配列を理解していることが前提 => クラスを理解するためにはオブジェクトを理解していることが前提 => Reactを理解するためにはクラスを理解していることが大前提となるからです。

(JavaScriptの基本的知識がしっかりしていないままReactの勉強に進んだことで、何度もつまづき、心が折れそうになった自身の経験から書きました。)

オブジェクトいろいろ

配列は連番が付けられた値(変数)をまとめたものですが、オブジェクトは値に名前を付けて管理出来ます。

const points = {
  x: 100,
  y: 180
}

x: 100 ,y: 180のように、オブジェクトを構成するこれらのひとつひとつの要素をプロパティといい、左側は名前、またキー、そして右側は値と言います。
プロパティーには関数なども格納することが出来、関数が格納されたプロパティのことをメソッドといいます。

プロパティーの値にアクセスする方法。

console.log(points.x) //100
console.log(points['y']) //180

オブジェクト名.キーの名前(ドット表記法)、またはオブジェクト名[](ブラケット表記法) の中にキーを文字列の形で渡す方法があります。

代入、追加、削除

オブジェクトpointsのキーxの値を変更。

points.x = 170 //またはpoint['x']
console.log(points) //{x: 170, y: 180}

新しいキーz200のプロパティーを追加。
キーyのプロパティーを削除。

points.z = 200
delete points.y

console.log(points) //{x: 170, z: 200}

スプレット構文

スプレット構文でオブジェクトを展開することが出来ます。

const otherPoints = {age: 30, hobby: 'music'}
const newPoints = {x: 100, y: 180, ...otherPoints}
console.log(newPoints) //{x: 100, y: 180, age: 30, hobby: "music"}

分割代入とレスト構文

オブジェクトのキーと同じ定数名を使うと、そのキーの値が代入されます。(分割代入)
残りのプロパティはオブジェクトのまま取り出せます。(レスト構文)

const {age, hobby, ...newPointsOthers} = newPoint

console.log(age) //30
console.log(hobby) //music
console.log(newPointsOthers) //{x: 100, y: 180}

キーや値を取得

キーを配列で取得。

const newPoints = {x: 100, y: 180, age: 30, hobby: "music"}
const keys = Object.keys(newPoints)
console.log(keys) // ["x", "y", "age", "hobby"]

値を配列で取得。

const values = Object.values(newPoints)
console.log(values) // [100, 180, 30, "music"]

forEach()でキーと値をひとつずつ取得 :kissing_smiling_eyes:

keys.forEach(key => console.log(`key: ${key} value: ${newPoints[key]}`))
//key: x, value: 100
//key: y, value: 180
//key: age, value: 30
//key: hobby, value: music

*keyは文字列(string)で取得されていることに注意

map()を使うと、新しい配列で返してくれます。

const map = keys.map(key => `keyは${key}, valueは${newPoints[key]}です。`)
console.log(map)
//["keyはx, valueは100です。", "keyはy, valueは180です。", "keyはage, valueは30です。", "keyはhobby, valueはmusicです。"]

複数オブジェクトの配列

const posts = [
  {
    content: 'JavaScriptの楽しい!',
    likes: 2,
  },
  {
    content: 'プログラミング楽しい!',
    likes: 3,
  },
  {
    content: 'React楽しい!',
    likes: 1,
  }
]

for()を使って、postsの値をひとつひとつ取得。

for(let i = 0; i < posts.length; i++ ){
  console.log(`${posts[i].content} ${posts[i].likes}いいね`)
}
// JavaScriptの楽しい! 2いいね
// プログラミング楽しい! 3いいね
// React楽しい! 1いいね

forEach()を使って、postsの値をひとつひとつ取得。

posts.forEach(post => console.log(`${post.content} ${post.likes}いいね`))
// JavaScriptの楽しい! 2いいね
// プログラミング楽しい! 3いいね
// React楽しい! 1いいね

map()を使って、postsのひとつひとつを取りだして新しい配列で返します。

const objMap = posts.map(post => `${post.content} ${post.likes}いいね`)
console.log(objMap)
// ["JavaScriptの楽しい! 2いいね", "プログラミング楽しい! 3いいね", "React楽しい! 1いいね"]

メソッドを使う

postをひとつひとつ取り出す。

const posts = [
  {
    content: 'JavaScriptの楽しい!',
    likes: 2,
  },
  {
    content: 'プログラミング楽しい!',
    likes: 3,
  },
  {
    content: 'React楽しい!',
    likes: 1,
  }
]

const getPost = post => console.log(`${post.content} ${post.likes}いいね`)
getPost(posts[0]) // JavaScriptの楽しい! 2いいね
getPost(posts[1]) // プログラミング楽しい! 3いいね
getPost(posts[2]) // React楽しい! 1いいね

getPost()をオブジェクトの中に入れて、プロパティの値として、関数を持たせます。
関数をプロパティの値にした時、その関数をメソッドと言います。
(同じオブジェクト内のプロパティへthisというキーワードでアクセス出来ます。)

const posts = [
  {
    content: 'JavaScript楽しい!',
    likes: 2,
    getPost: function(){console.log(`${this.content} ${this.likes}いいね`)}
  },
  {
    content: 'プログラミング楽しい!',
    likes: 3,
    // getPost: function(){console.log(`${this.content} ${this.likes}いいね`)}
    // functionを省略したメソッドの書き方↓↓↓こちらが一般的
    getPost(){console.log(`${this.content} ${this.likes}いいね`)}
  },
  {
    content: 'React楽しい!',
    likes: 1,
    getPost(){console.log(`${this.content} ${this.likes}いいね`)}
  }
]
posts[0].getPost() // JavaScriptの楽しい! 2いいね
posts[1].getPost() // プログラミング楽しい! 3いいね
posts[2].getPost() // React楽しい! 1いいね

うまくメソッドを呼び出せましたが、オブジェクトの全てにメソッドを付けるのは同じ記述の繰り返しになってしまいます。
このようなオブジェクトをテンプレート化できるのがクラスです:ok_hand_tone1:

次回はクラスについて書きたいと思います。

最後に

道半ばですが、分からないことがあっても諦めずに少しずつすすめると、小さな成長を感じる瞬間があります。
そんな素敵な瞬間を楽しみながら頑張ろうと思っております:innocent:
フロントエンドスキルを武器にしたいWebデザイナーのみなさん、一緒にがんばりましょう!

参考にさせていただいたサイト

https://developer.mozilla.org/

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

ウェブ技術とobnizで会社の受付タブレットアプリを作った話

こんにちは。トップゲートという会社に勤めております、古都ことです。

このたび弊社の大阪事業所が移転しまして、心機一転、新事業所での勤務が始まりました。そして場所が変わればやるべきことも変わり、新たな課題も出てきます。そんな中で、新たな事業所の改善について少し携われる機会をいただけたので、ここで軽く紹介したいと思います。

「受付タブレット」プロジェクト

社会人の方ならよくご存知だと思いますが、会社さんって受付に内線が置いてあって、訪問時にはそれで担当の方を呼び出してもらう、というフローがたぶん一般的になります。また小さなところであれば、ベルが置いてあってそれを鳴らすというスタイルもありますね。

弊社の移転前の大阪事業所では小さな部屋でしたので、来社時にはドアをノックしていただいていました。しかし新事業所ではそういうわけにもいきません。事業所の大きさは約3倍〜4倍になり、ドアをノックしても聞こえません。

ここで内線を置くという手もありますが、弊社はIT企業。自分たちの技術でなんとかしたい。そう思いません?そして生まれたのが「受付タブレット」プロジェクトです。このプロジェクトは社内のエンジニアが自由に受付アプリを作っても良く、そこに私も(勝手に)参加させていただきました。

44482e65-09b1-4a66-9d29-5cf220cb280c.png

普通の仕事の他に、こういった自由気ままに参加できるプロジェクトもあるので仕事の息抜きには最適です。みんなで寄ってたかって実用的な面白いものを作るというのは、実に楽しいです。

実機動画

実際にiPadで動作させている動画が以下になります:

(※実際に設置しているのはiPadですが、ここではiPadProを使用して動画撮影しています)

アプリの機能

このアプリの主な機能は以下の2つです:

  • Slackへの通知
  • obnizと連携しブザーを鳴らす

Slackへの通知がメインの機能となります。来社された方がいたらSlackで担当者にメンションを飛ばし通知します。以下のようなイメージです。

slack.png

このSlack連携部分に関しては別のエンジニアの方が主導となり作成したもので、私はAPIを叩いているだけです。

もうひとつの機能がobnizを用いたブザー機能です。obnizはクラウド経由で双方向通信できるIoTデバイスで、いろいろなセンサを簡単に動かすことができます。Slackでのメンションのみではなかなか気付きにくいということもあり、obnizを用いて室内でブザーを鳴らすことにしました。単純な音を鳴らしているだけですが、通知の見落としを防ぐことができています。

技術要素

このアプリでは以下の技術を採用しています:

  • React
  • TypeScript
  • <canvas>
  • Web Speech API

見ての通り、ウェブ技術で作られています。残念ながらPWAではありませんが、ネイティブに近い体験を提供できているのではないかなと思っています。PWAを不採用とした理由はiPadOSのPWA対応が十分に成熟していないためです。

フレームワークはReactです。Reactのシンプルさと強力さがこの規模のアプリに非常にマッチしており、簡単に作ることができました。

言語はTypeScriptです。現代において生のJavaScriptという選択肢はなかなか厳しいところがあるので、TSを採用しています。

背景はcanvasで生成しています。速度を考えるとWebGLの方が良いかもしれませんが、実装の簡単さを考慮して普通の2DContextを使いました。動き回る点と点が一定距離内に入ると線で繋がるという、よくあるアニメーションです。

音声はWeb Speech APIのSpeechSynthesis(音声合成)を用いています。これによりテキストデータのみで発話することができ、修正が容易になります。

気をつけた点

このアプリを作成するときに一番考えたのは、「迷わせない」ということでした。会社を訪れるお客さんというのは、なにもIT関係の人ばかりではありません。エンジニア基準でUIを作ってしまうと、操作に戸惑ってしまう可能性が出てきます。

そこで多くの人が触れたことがあるはずのATMや券売機といった機器のUIを参考にしました。大きなボタンがいくつか並び、それをタッチして選ぶ、という形式です。

buttons.jpg

また、当初はいきなり用件の選択肢を表示していたのですが、最初に一度ボタンをタップする画面遷移に変更しました。これにより「この白い四角はボタンである」「触れると反応する」ということを理解してから用件の選択に入れるようになっています。

ボタンにはアイコンも付けました。視覚情報が入ることにより、脳が処理する時間をできるだけ小さくできることを期待しています。アイコンは自作なので少々不格好ですが、なんとなくで雰囲気はわかると思います。

とにかく「初見でも理解できるもの」を目指して作りました。会社の受付というのはそう何度も利用するものではありませんし、1度目からスムーズに利用できるのが理想です。

1週間運用してみて

このアプリを実際に1週間設置してみましたが、ちゃんと機能し、きちんと受付の役割を果たせています!初見の方でも迷いなく使用していただけているようです。会社の受付という、場合によっては非常に重要となる要素を担当させていただいて、ドキドキしつつも一定の役割は果たせたようで安心しています。

ユーザにとっての使い心地というのも大切ですが、「事務所内の人がきちんと来客に気づくことができるか」というのも重要なポイントでした。どんなによくできた受付アプリでも、社内の人間が気付けなければ意味がありません。obnizでブザーを鳴らすという選択肢を取ったのは良かったと思います。

いろんな方に使用していただいて、良かった点や今後の課題などがいくつか見えてきたので、これからどんどん改善していきたいと思います。

さいごに

会社の受付のような、重要であり難しくある部分に自由に携われるというのは、非常に光栄です。息抜きにもなりますし、会社を訪れてくれる極めて大切な方を適切におもてなしする責務があると考えると、緊張感とやりがいがグッと上がってきます。

今回は根本となるAPIは別のエンジニアさんに作ってもらったので、私はただ画面を作っただけになってしまうのですが、貴重な経験ができたと思います。

もし弊社の大阪事業所を訪れることがあれば、「これが例の受付アプリか」と触ってみてください。ご来訪、お待ちしております。

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