とあるWeb屋の備忘録

とあるWeb屋の備忘録。たまに雑記。

Vueで書籍管理画面っぽいものを作ってみた

Vueの学習のために書籍管理画面っぽいものを作ってみました。
書籍リストを読む処理にaxiosを使いましたがcodepen上はaxiosを使用しないソースになっています。
axiosを使用したソースはページ下部に載せておきます。

ちなみに書籍リストは以下のユニークなidを持った配列にしました。

[
    { "id":1,  "title": "book01", "price": 100 },
    { "id":2,  "title": "book02", "price": 200 },
    { "id":3,  "title": "book03", "price": 300 },
    { "id":4,  "title": "book04", "price": 400 },
    { "id":5,  "title": "book05", "price": 500 },
    { "id":6,  "title": "book06", "price": 600 },
    { "id":7,  "title": "book07", "price": 700 },
    { "id":8,  "title": "book08", "price": 800 },
    { "id":9,  "title": "book09", "price": 900 },
    { "id":10, "title": "book10", "price": 1000 }
]


仕様

  • 価格絞り込み
  • 数量絞り込み
  • 昇順/降順並び替え
  • 書籍登録
  • 書籍更新
  • 書籍削除
  • 書籍リストのリロード(リロード中にNow Loading表示)
  • 500円以下は10%OFF付ける

価格絞り込み・数量絞り込み

Vueコンストラクタ関数を使ってVueインスタンスを作成します。
Vueインスタンスにはいったんデフォルトで以下のオプションオブジェクトを渡します。

const app = new Vue({
    el: '#app',
    data: {
        budget: 2000,
        amount: 20,
        books: []
    },
)

画面上で入力した値をデータにバインドするためにv-modelディレクティブを使います。

// HTML
<input v-model.number="budget"><span>円以下</span>
<input v-model.number="amount">冊以内に絞り込む


価格絞り込みについて

computedを使います。

  1. filterメソッド(非破壊的)を使って予算以下の書籍リストを構築してreturnする
  2. returnされたリストを表示させる
// computed
matched: funciton() {
    return this.books.filter(books => {
        book.price <= this.budget
    }, this)
}
// HTML
<li v-for="book in matched" :key="book.id">

これで価格の絞り込みが完了です。


数量絞り込みについて

computedを使います。

this.matchからreturnされる配列のうち数量分をsliceする

// computed
amounted: function() {
    return this.matched.slice(0, this.amount)
}
// HTML
<li v-for="book in amounted" :key="book.id">

これで価格の絞り込みからの数量絞り込みが完了です。


昇順/降順並び替えについて

昇順/降順ボタンを押すたびに順序を切り替えるように実装します。

  1. データにflg用のプロパティを持たせてflgの状態で昇順/降順を切り替える
  2. Lodashを使って並び変える
  3. returnされる配列を表示させる
const app = new Vue({
    el: '#app',
    data: {
        budget: 2000,
        amount: 20,
        books: [],
        sort: false // add
    },
)
// computed
sorted: function() {
    return _.orderBy( this.amounted, 'price', this.sort ? 'desc' : 'asc')
}
// HTML
<button @click="sort=!sort">
    <span v-if="sort">降順</span>
    <span v-else>昇順</span>
</button>

これで価格絞り込みと数量絞り込みをした書籍リストを昇順/降順で並び替えることができます。


書籍登録について

  1. 書籍にはそれぞれユニークなidを振っているので書籍リストのlengthに応じてidの値を自動で割り振れるようにlengthを取得するメソッドを用意する
  2. 追加する書籍のプロパティを入力する(idは1で用意したメソッドを使う)
  3. pushメソッドで書籍リストに「入力したプロパティを持つ新しい要素」を追加する
const app = new Vue({
    el: "#app",
    data: {
        budget: 2000,
        amount: 20,
        books: [],
        sort: false,
        titleR: null, // add
        priceR: null  // add
    }
// computed
max: function() {
    return this.books.reduce((a, b)=> {
        return a > b.id ? a : b.id
    }, 0)
}
// methods
regist: function() {
    this.books.push({
        id: this.max + 1,
        title: this.titleR,
        price: this.priceR
    })
    this.titleR = null;
    this.priceR= null;
}
// HTML
<span>書籍名:</span><input v-model="titleR">
<span>価格:</span><input v-model.number="priceR">
<button @click="regist">書籍登録</button>

書籍更新について

  1. 更新したい書籍idと更新後のタイトル、価格を入力する
  2. 更新したい書籍idと一致する要素の配列インデックスを取得する
  3. その配列インデックスのプロパティを$setを使って更新する
const app = new Vue({
    el: "#app",
    data: {
        budget: 2000,
        amount: 20,
        books: [],
        sort: false,
        titleR: null,
        titleU: null, // add
        priceR: null,
        priceU: null, // add
        id: null,     // add
    }
// methods
update: function() {
    if(this.id != "") {
        this.books.forEach(el=> {
            if(el.id == this.id) {
                let target = this.books.indexOf(el);
                this.$set(this.books, target, {
                    id: this.id,
                    title: this.titleU,
                    price: this.priceU
                })
            }
        }, this)
    };
    this.id = null;
    this.titleU = null;
    this.priceU= null;
}
// HTML
<span>書籍No.</span><input v-model.number="id">
<span>書籍名:</span><input v-model="titleU"><br class="spi">
<span>値段:</span><input v-model.number="priceU">円
<button @click="update">書籍更新</button>

書籍削除について

  1. 削除ボタンを押す
  2. 削除ボタンが押された要素の配列インデックスを取得する
  3. spliceでその配列インデックスの要素を取り除く
// methods
delate: function(el) {
    let target = this.books.indexOf(el);
    this.books.splice(target, 1);
}
// HTML
<button @click="delate(book)">削除する</button>

書籍リストのリロード(リロード中にNow Loading表示)について

※ここでは書籍リストを直書きしていますが、axiosを使ってjsonから読ませる方法も次のセクションに記載します。

  1. Vueのウォッチャを使って書籍リストの高さを監視する(データにheightを持たせてデフォルトの高さを0にしておく)
  2. 書籍リストがない場合はNow Loadingの表示を出す
  3. 一秒後に書籍リストをDOMにマウントする
  4. $nextTickを使って書籍リストがDOMにマウントされたら書籍リストの高さを取得する
  5. 取得した高さをstyle属性にバインドする
  6. リロードボタン押下時にいったん書籍リストを空にして2~5の処理を繰り返す
const app = new Vue({
    el: "#app",
    data: {
        budget: 2000,
        amount: 20,
        books: [],
        sort: false,
        titleR: null,
        titleU: null,
        priceR: null,
        priceU: null,
        id: null,
        height: 0, // add
    }
// watch
books: function() {
    console.log(this.$refs.booksCont.getBoundingClientRect().height)
    this.$nextTick(()=> {
        console.log(this.$refs.booksCont.getBoundingClientRect().height)
        this.height = this.$refs.booksCont.getBoundingClientRect().height
    })
}
// methods
loadBooks: function() {
    this.books = []; // リロードボタン押下時のための処理
    setTimeout(()=> {
        this.books = [
            { "id":1,  "title": "book01", "price": 100 },
            { "id":2,  "title": "book02", "price": 200 },
            { "id":3,  "title": "book03", "price": 300 },
            { "id":4,  "title": "book04", "price": 400 },
            { "id":5,  "title": "book05", "price": 500 },
            { "id":6,  "title": "book06", "price": 600 },
            { "id":7,  "title": "book07", "price": 700 },
            { "id":8,  "title": "book08", "price": 800 },
            { "id":9,  "title": "book09", "price": 900 },
            { "id":10, "title": "book10", "price": 1000 }
        ]
    }, 1000)
}
// HTML

// Now Loading箇所
<div class="p-top__loading" v-if="!books.length">
    Now Loading...
</div>

// 高さをbindする箇所
<div :style="{height: height + 'px'}">
    <ul v-cloak ref="booksCont">

// リロードボタン箇所
<button @click="loadBooks" :disabled="!books.length">
    <span>リロード</span>
</button>

書籍リストをaxiosでjsonから読む方法

書籍リストをaxiosでjsonから取得する方法を以下に記載します。
やっていることはjsonから書籍リストを読み込んで1秒後にDOMにマウントする処理をcreatedで実行しています。

// JSON
[
    { "id":1,  "title": "book01", "price": 100 },
    { "id":2,  "title": "book02", "price": 200 },
    { "id":3,  "title": "book03", "price": 300 },
    { "id":4,  "title": "book04", "price": 400 },
    { "id":5,  "title": "book05", "price": 500 },
    { "id":6,  "title": "book06", "price": 600 },
    { "id":7,  "title": "book07", "price": 700 },
    { "id":8,  "title": "book08", "price": 800 },
    { "id":9,  "title": "book09", "price": 900 },
    { "id":10, "title": "book10", "price": 1000 }
]
// methods
loadBooks: function() {
    this.books = []; // リロードボタン押下時のための処理
    axios.get("./json/books.json").then(responce => {
        setTimeout(()=> {
            this.books = responce.data
        }, 1000)
    })
    .catch(e => {
        console.log(e)
    })
}
// created
created: function() {
    this.loadBooks();
}

500円以下は10%OFF付ける

// HTML
<span v-if="book.price < 500 ">10%OFF</span>

分かりづらい部分があるかもしれないので後日リライトするかもしれないです。
あと今はPromiseよりAsync/awaitを使うのが一般的かと思いますので、もう少しモダンな書き方ができるかもしれません。

機能を実装するにあたりこちらの本を参考にさせていただきました!!ありがとうございます!!!
基礎から学ぶ Vue.js
https://www.amazon.co.jp/dp/B07D9BYHMZ/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

ここまで読んでいただいてありがとうございました!!!

【まつもとゆきひろ氏 特別講演】20代エンジニアのためのプログラマー勉強法まとめ

先週末にサポーターズさん主催の【まつもとゆきひろ氏 特別講演】20代エンジニアのためのプログラマー勉強法 に参加してきました!
これまで技術的なことしか書けていなかったのですが、とても学びが深い講演会だったので初めて雑記的にまとめを書いてみようと思います。
内容濃すぎ && 80分 という講演会だったのでざっくりまとめです!
講演会の様子はyoutubeで配信もされたので、このブログの最後にURL貼っておきます。


以下Matzさんによるプログラマー勉強法まとめ

今回「勉強」という言葉を使ったのはあえてミスリードを誘う目的。
具体的には「(学校)勉強」には「苦手を克服するべき」というメタファーがある。
「苦手を克服するべき」は社会人の勉強では間違い。

学生と社会人の勉強の違い9つ

1.満点VS満点なし
社会人の勉強に満点がない、つまり上限がない。平均値や偏差値がないから学生時代の常識が通用しない。

2.苦手克服VS得意を伸ばす
社会人にとって苦手を克服するのは意味がない。自分の苦手は誰かが得意だったりする。
得意を頑張ったほうがいい。好きなことは上達する、上限はないからどこまでも伸ばせる。
「苦手を克服すべき」というメタファーは誤り。得意を伸ばすことがあるべき戦略。

3.記憶VS把握
試験があるわけではないから記憶しておく必要はない、それよりも何を知っているか把握することが重要。
基本的な概念、使い方、知識のインデックスを頭の中に作ることが重要。
Googleは友。詳細は必要な時に調べればいい。

4.知識VSインデックス
学生時代は知識を丸ごと頭の中に入れておかなければいけないが、社会人では知識のインデックスを作ることが重要。

5.試験VS常在戦場
学生時代は試験のための勉強。社会人の勉強は、いつ役に立つか分からない勉強をすることになる。
社会人の勉強は、「この日に役に立つ」というスコープ外れてるから、いつ役に立つか、そもそも役に立つものなのかすらわからない。

6.評価軸が一次元VS多次元
数学は数学でのみ評価される。次元が一次元。
エンジニアのスキルセットでいえば、それぞれみんな違う特質を持っている。
そのため評価軸は多次元になる。

7.メインVSサブ
学生は勉強がメイン。社会人は仕事がメインなので勉強がサブ。

8.間接的VS直接的
学生のときに勉強したことが役に立つかは直接はわからない。
社会人の勉強はスキルに反映するので直接的に伸びる。

9.安定VS変化
学生のときは教科書の内容が変わることは滅多にないし勉強が安定している。
社会人の勉強、例えばJSに関して言えば今勉強していることが古くなったり消える。
社会人の勉強はこういうことがけっこうある。

こんなに違うものを同一視して、このメタファー(苦手を克服すること)は役に立つのかというと役に立たないことがけっこうある。
社会人は「勉強」ではなく「学習」では?


なぜ勉強するのか

「成功したい」「高収入が欲しい」「良好な人間関係が欲しい」「嫌なことはしたくない」「好きなことで生きていきたい」など目的がある。
これらを達成する確率を上げるために必要なことは「高評価」「尊敬」「尊重」など。
勉強の目的はこれらを得るため。


どうやって「何を勉強するか」「どうやって勉強するか」を決めるかのヒント

1.必要なことは「Self reflection(内省)」
自分を真剣に見つめることが大事。
走り出すのは楽、走り続けるのは大変。まず走る前に方向性を考えることが大切。
仕事について、自分が好きなもの、嫌いなもの、得意なこと、苦手なことを即答できるか大事。
好きなことは上達するから苦手を克服するのはあまり意味はない。好きなことに集中したほうが効果的。
内省して以下をする。
何を勉強するかどうやって勉強するかを自分で決める。
自分のスキルについて真剣に考えてinventory(棚卸し)をする。
方向性を決める。

2.パターン認識について
パターン認識は成功の秘訣。
アメリカのスタートアップ数百人に共通して持っている素質はパターン認識能力が高い。
社会における様々なパターンを抽象化して認識する。過去の人たちの失敗を抽象化してパターン認識して失敗を避ける。
自分が尊敬する人はだれか?そういう人たちからパターンを探せ(尊敬できる人のパターンを認識して抽象化する必要がある)。

知らない人を尊敬することはできない。
自分が尊敬あるいは尊重を得るためには「知られないといけない」。
余談:尊敬は人格的評価が伴い、尊重には人格的評価は無関係。

一般論として人は有名人に弱い。本能的にミーハー。
知名度は価値と可換である。つまり以下。
× 成功したから有名になる。
〇 成功のためには有名にならなければならない。

成功するためにはまず知られる必要がある(=価値があると思われる必要がある)。
「有名である」=「価値がある」は循環論法。
価値があると思うから有名になる。有名であるから価値があると思ってもらえる。
今世間から評価されていない人はどうしたらいいのか。
マーケティングキャズム理論で説明できる。

キャズム理論
顧客5段階
イノベーター アーリーアダプター アーリーマジョリティ レートマジョリティ ラガード
アーリーマジョリティまで商品が届くことはあまりない。例えばJSの新しいフレームワーク。これ仕事で使う?様子見。このまま浸透しない。
アーリーアダプターとアーリーマジョリティの間にはキャズムがある。
キャズムを乗り越えるために以下。
ニッチに進出 → 一定のシェアを超える → 横展開する → キャズムを乗り越えられる可能性がある。

今日のテーマ「勉強法」というより、結局は自分のマーケティングの話。
ユニークさを強調すること。埋没しないことが重要。周囲の目に留まる確率が高ければ高いほどいい。

未来は誰にも分らないし未来のことを考えても意味がない。
それより自分の傾向について考える。内省する。
自分の将来を運任せにするのではなく戦略を立てる。

具体的な勉強方法について

1.モチベーション
好きなことは放っておいてもやるからモチベーションは高い。
漠然とした希望はモチベーションが弱い。例:このスキル身に着けたら面白そう → もっと具体的なほうが頑張れる
「暇」「退屈」は自分に対する注意報。自分のモチベーションを管理できていない。
本当にやりたいことを優先順位をつけて上から取っていけば「暇」や「退屈」って思う暇がない。
「暇の撲滅」を考える。

2.時間管理
どうやって時間をつくるか。
その人の時間の使い方でその人の優先順位がわかる。
勉強に関して言えば、本当に勉強が大事だと思っているか、もしくは大事だと自分を説得できているかで、時間を勉強に使うことができる。
プライベートを犠牲にするのはあまりよくない。
社会人の勉強は、仕事に反映されるために勉強する。
社会人の勉強は就業時間にすることが原則。
そのために仕事の生産性を高めて仕事の時間を勉強に使う。
大事なことは自分を忙しすぎない状態に持っていくこと。
作業の見積りは3倍くらいのバッファを取ることが適当。
社畜やめよう。
こういうことを理解する上司がいるか会社を選ぶバロメータになる、尊重されているか分かる。
これを理解してくれない会社は滅びたほうがいい、逃げたほうがいい。

3.アウトプット
勉強する=インプットはミスリード
インプットは差別化要因にならない。
アウトプットするかが重要。アウトプットは面倒、億劫、羞恥心があるから大変。
例:youtuberがやっていることは誰でもできることだが、実際は心理障壁があるから誰もはできない。
アウトプットによる知識の定着がある → ほかの人に読んでもらうところまで考えるともう一段階高いところで体系化されることがある。
クオリティは棚に上げてとりあえず世にだすこと大事。
アプトプットを繰り返すと楽にアウトプットできるようになってくる。
アプトプットを最適化していく。
アウトプットの心理障壁が取れてくる。
そうすると人間の可塑性が起きてくる。

4.可塑性
可塑性とは変化しやすさ。
立場によって人は変われる。
人は置かれた環境に合わせて変化できる。自分は固定的だと思わないほうがいい。
環境に強制されたときにゆっくりだが確実に変化することができる。しかも予期しない方向で変わることがある。
自分のinventoryをしていたとしても、自分の変化は予期できない。変化を発見したらそれに従う。


最後のアドバイス

1.基礎を抑える
あまり変化しない定番の知識
コンピュータサイエンス
アルゴリズム
コンピュータアーキテクチャ

2.英語
英語があれば18億人とコミュニケーションできる。
IT業界の新しい情報は英語で発生する。
タイムマシン経営ができる(アメリカで流行りを日本に持ってきてビジネスする)。
英語は読むのが最低限。次に雑談ができること。そして公演ができる状態へ。
語学は場数。脳の中に回路を作れるかの勝負。
完璧を目指さない。

3.コンフォートゾーン
コンフォートゾーンは居心地の良い空間のこと。
ずっといると成長がない。コンフォートゾーンを意識的に出ると成長できる。
変化を恐れない訓練をする。
誰かを見下さない。見下すとコミュニケーションができない。我々の仕事は人相手の仕事。
自分は多次元でものを見れていれば人を見下さない。例:この面では勝っているがこの点では劣っているな
同様に自分を卑下しない。


内容も時間も盛りだくさんだったのでざっくりですが以上な感じでまとめてみました!
最後に感想ですが、80分の講演のMatzさんの言葉で特に印象的だったのが以下の2点の内容でした!

「自分は運が良かったから大して深く考えていたわけじゃないけどこのキャリアを歩めたけど、運という要素は誰にでもあてはまるものではないから、戦略的に成功する確率を高めることが必要」ということ。

「未来は誰にも予想できないから考えても無駄。大企業が出している未来予測も実際は当たらない。自分の傾向や内省について真剣に考えることが大事」ということ。

講演会参加前は、具体的なプログラミング勉強法(例えばこのサイトは便利とか、こういう順番で知識を身に着けると効率的とか)を聞けるかと思っていましたが、
実際はかなり体系的なお話を聞くことができて、とても学びが深い講演会でした!
今後もたまにはこういう技術的なこと以外も書いていけたらと思います。ここまで読んでいただいてありがとうございました!!

まつもとゆきひろ氏 特別講演】20代エンジニアのためのプログラマー勉強法
https://youtu.be/YG0MzpNiZUg

Vueで絞り込みとソート機能を実装してみた02

前回は絞り込み機能を実装したので今回はそれにソート機能を実装していきます。
前回:Vueで絞り込みとソート機能を実装してみた01

今回やること
* 降順と昇順を任意に表示切替できるようにする

ソートするにはcomputedのmatchedの返り値の配列を受け取ってそれをLodashを使ってソートしてから配列を返せば良いです。 この配列を既存のcomputedのlimitedで加工します。limitedの処理は前回と受け取る配列が違うだけです。 それでは実装していきます。
main.js

const app = new Vue({
    el: "#app",
    data: {
        budjet: 10000,
        limit: 3,
        flg: false,
        list: [
            {
                id: 1, name:"ザバス ホエイプロテイン100 ココア味", price: 3659
            },
            {
                id: 2, name:"ウイダー マッスルフィットプロテイン ココア味", price: 1922
            },
            {
                id: 3, name:"アミノバイタル アミノプロテイン レモン味", price: 3157
            },
            {
                id: 4, name:"パワープロダクション マックスロード チョコレート風味", price: 7980
            },
            {
                id: 5, name:"ゴールドスタンダード 100% ミルクチョコレート味", price: 4980
            }
        ]
    },
    computed: {
        matched: function() {
            return this.list.filter((el) => {
                return el.price <= this.budjet
            },this)
        },
        sorted: function() {
            return _.orderBy(this.matched, 'price' this.flg ? 'desc' : 'asc')
        },
        limited: function() {
            return this.sorted.slice(0, this.limit)
        }
    }
})

sortedではLodashを使ってソート済みの配列を返しています。
色々なメソッドが揃ってます。使い方はドキュメントを参照してください。
Lodash Documentation
https://lodash.com/docs/4.17.10

index.htmlは以下のような感じにします。

<div id="app">
    <p v-cloak>
        <input v-model.number="badjet">円以下に絞り込む<br>
        <input v-model.number="limit">件を表示する
    </p>
    <ul>
        <li v-for="item in list" :key="item.id">
            商品名:{{ item.name }}価格:{{ item.price }}円
        </li>
    </ul>
    <button @click="flg=!flg">切り替え</button>
</div>

これで切り替えボタンをクリックするごとにVueインスタンスのデータに持たせているflgプロパティのtrue/falseが切り替わり、
それによって降順/昇順が切り替わって表示されるようになります。
これでソート機能も追加することができました。

今回こちらの本を参考にさせていただいています。ありがとうございます!
基礎から学ぶ Vue.js
https://www.amazon.co.jp/dp/B07D9BYHMZ/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

読んでいただいてありがとうございました!!!

Vueで絞り込みとソート機能を実装してみる01

Vueのcomputedとmethodsの違いがいまいち分からなかったので、computedを使って絞り込みとソート機能を実装してみます。

今回やること
* 商品のリストから自由に値段と絞り込み件数を指定して画面に表示する

次回やること
* 降順と昇順を任意に表示切替できるようにする

今回は規模がかなり小規模なのでルートのコンポーネントに全部まとめて書いていきます。
Vue.jsもダウンロードしたファイルを読ませて、ソート処理で使うLodashもCDNで読ませています。

それでは実装していきます。
main.js

const app = new Vue({
    el: "#app",
    data: {
        budjet: 10000,
        limit: 3,
        list: [
            {
                id: 1, name:"ザバス ホエイプロテイン100 ココア味", price: 3659
            },
            {
                id: 2, name:"ウイダー マッスルフィットプロテイン ココア味", price: 1922
            },
            {
                id: 3, name:"アミノバイタル アミノプロテイン レモン味", price: 3157
            },
            {
                id: 4, name:"パワープロダクション マックスロード チョコレート風味", price: 7980
            },
            {
                id: 5, name:"ゴールドスタンダード 100% ミルクチョコレート味", price: 4980
            }
        ]
    },
    computed: {
        matched: function() {
            return this.list.filter((el) => {
                return el.price <= this.budjet
            },this)
        },
        limited: function() {
            return this.matched.slice(0, this.limit)
        }
    }
})

computedで2つの算出プロパティを定義しています。

matchedではfilterメソッドを使います。これで条件に満たない要素を削除し、新たな配列をreturnします。
filterメソッドについて

limitedではsliceメソッドを使います。フィルタリングされた配列の中から、sliceで指定した長さだけ切り抜いて配列をreturnします。
sliceメソッドについて


index.htmlは以下のような感じにします。

<div id="app">
    <p v-cloak>
        <input v-model.number="badjet">円以下に絞り込む<br>
        <input v-model.number="limit">件を表示する
    </p>
    <ul>
        <li v-for="item in list" :key="item.id">
            商品名:{{ item.name }}価格:{{ item.price }}円
        </li>
    </ul>
</div>

以上で絞り込み機能の実装は完了しました。

今回こちらの本を参考にさせていただいています。ありがとうございます!
基礎から学ぶ Vue.js
https://www.amazon.co.jp/dp/B07D9BYHMZ/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

次回はこれにソート機能を加えていきます。

読んでいただいてありがとうございました!!!

Twilio + Node.jsで自動音声を流す1

昨年あたりにテレビとスマホの連動企画が流行って色々な施策が世に登場しましたが、その中のひとつに、CMやドラマの役者が持っているスマホの電話番号が画面に映り込み、その番号に電話すると実際に役者が電話に出てくれるっていう施策がありました。

これはおそらくKDDIがやってるTwilioっていうプラットフォームで実装できそうなので自分でもやってみます。

Twilioについて

KDDIがやっているプラットフォームサービス(Paas)
電話をかける、電話をうける、SMS通知などを操作するAPIがそろっていて、簡単にアプリケーションとこれらの機能を連携させることができます。
例えばコールセンターに代表されるような、電話のプッシュダイヤル操作なども作れます。
無料トライアルも用意されているので簡単に試すことができます。
Twilioのホームページはこちら
https://twilio.kddi-web.com/

Twilio登録方法

f:id:tmykndr:20180718234702p:plain 1. Twilioのトップページかからサインアップ(右上)クリック


f:id:tmykndr:20180718234706p:plain 2. 利用規約とプライバシーポリシーにチェックを入れて「同意してサインアップ」クリック(チェック入れる前はグレーアウトしています)


f:id:tmykndr:20180718234710p:plain 3. 項目を埋めて「はじめる」クリック


f:id:tmykndr:20180718234713p:plain 4. 本人確認用に電話番号を入力して「Verify」クリック。SMSで認証コードが送られてきます


f:id:tmykndr:20180718234716p:plain 5. 認証コードを入力して「Submit」クリック


f:id:tmykndr:20180718234719p:plain 6. このアカウントで使用するプロジェクト名を入力して「続ける」クリック。プロジェクト名はマイページに「プロジェクト名+ダッシュボード」の形で記載されます。


f:id:tmykndr:20180718234723p:plain 7. マイページが表示されます。登録お疲れ様でした。


Twilioの電話番号から発信させる方法

まず、一番単純なかたちで、メッセージをしゃべらせてみようと思います。
Twilioが用意しているREST APIを使ってTwilioの電話番号から自分宛に電話をかけてみます。
Twilioに登録するとaccountSidとauthTokenが発行されるので、こちらを使ってユーザー認証します。(これらは自分のダッシュボードから確認できます。)

const twilio = require('twilio');
const accountSid = '自分のaccountSid';
const authToken = '自分のauthToken';
const client = twilio(accountSid, authToken);

今回しゃべらせる言葉はテキストでソース上にそのまま記述していきます。

const word = "今年の夏は猛暑でつらいですが頑張ってください。";

次にやることは、TwimlというTwilioが用意しているXMLで、実行したい処理を指定します。
TwiMLは電話やSMSを受信したときにどういう動作をさせるかを指定するマークアップ言語です。
TwiMLについての詳細はこちら

今回、メッセージを読み上げたいので、TwiMLsayメソッドを使います。
少しまえに某凄腕 腐女s 美人エバンジェリストがGoogleHomeを筋肉ボイス化しておりましたが、ここは普通の女性の声でしゃべっていただきたいのでvoicewomanに指定します。
languageをしゃべらせたい言語に指定することで各国の言葉にも対応しています。  

var twiml = '<Response><Say voice="woman" language="ja-jp">' + word + '</Say></Response>';

ここまで終わりましたら、処理を指定したTwiMLを実際にしゃべらせてみます。
そのためにやることはアウトバウンドコール(架電)を作成することです。

アウトバウンドコールを作成するため指定する項目が3つあります。
* 自分の携帯電話番号
* Twilioアカウントを作成した後に取得できるTwilio電話番号
* 作成したTwiMLをTwilioで用意している「Echo」という機能を使ってurlにセット

「Echo」機能は、http://twimlets.com/echo?Twiml="返したいTwiMLといった形式でリクエストすると、TwiMLを返してくれます。
「Echo」機能についてはTwilio LabsにTwiMLジェネレータという便利なものもあります。
Twilio Labs

ここで1点気を付けるポイントがあって、Echoでリクエストするときにクエリをパースする必要があります。
そのためにquerystringを使います。以下のようにquerystringを追加します。

const twilio = require('twilio');
const accountSid = '自分のaccountSid';
const authToken = '自分のauthToken';
const client = twilio(accountSid, authToken);
const querystring = require('querystring');

追加したら、実際にパースして指定します。
querystring.escape(twiml)で、escapeを使って指定された文字列に対してURLパーセントエンコーディングをかけます。

client.calls.create({
    to: '+818012345678',
    from: '+815012345678',
    url: 'http://twimlets.com/echo?Twiml=' + querystring.escape(twiml)
}, function (err, responseData) {
    if(err) throw err;
    console.log(responseData.from);
});

これで完了です。
ソースはこんな感じです。

//outboundCall.js

const twilio = require('twilio');
const accountSid = '自分のaccountSid';
const authToken = '自分のauthToken';
const client = twilio(accountSid, authToken);
const querystring = require('querystring');

const word = "今年の夏は猛暑でつらいですが頑張ってください。";
var twiml = '<Response><Say voice="woman" language="ja-jp">' + word + '</Say></Response>';

client.calls.create({
    to: '+818012345678',
    from: '+815012345678',
    url: 'http://twimlets.com/echo?Twiml=' + querystring.escape(twiml)
}, function (err, responseData) {
    if(err) throw err;
    console.log(responseData.from);
});

それではコンソールを出して以下のコマンドを実行します。

node outboundCall.js

これで携帯電話に電話がかかってきます。
いかがでしょうか。これで今年の猛暑も乗り越えられること間違いなしですね。

無料トライアル版だと電話応答時に数秒間広告が流れますが、有料版では流れません。
簡単にですが、以上でTwilio+Node.jsでのアウトバウンドコール作成は完成です。

ご指摘などあれば大歓迎で受け付けております!!!よろしくお願いします!!!

Vueを実践で使った話01

この前実際に仕事でVue.jsを使う機会があったので、実践編としてその使い方を残しておきます。
使いどころとしては複数のモーダルがあってモーダル表示でyoutubeを自動再生されるものです。
Gulpタスクはビルドするだけのシンプルなものにしました。
ディレクトリ構成もindex.htmlとJSに絞って書きます。

ソースはこちらにあります。
https://github.com/tomoyukionodera/vuefunc01

ディレクトリ構成

root/
 ├ gulpfile.js
 ├ js/
 │ └ main.js
 │ └ bundle.js
 └ index.html

gulpfile.js

"use strict"

const gulp = require('gulp');
const browserify = require('browserify');
const babelify = require('babelify');
const source = require('vinyl-source-stream');
const vueify = require('vueify');

gulp.task('browserify', function() {
  return browserify('./js/main.js')
  .transform(babelify,{presets:["env"]})
  .transform(vueify)
  .bundle()
  .on('error', function(err){
    console.log(err.message);
    console.log(err.stack);
  })
  .pipe(source('bundle.js'))
  .pipe(gulp.dest('./js/'));
})

main.js

const Vue = require('vue');

const bus = new Vue();

new Vue({
    el: '#trg',
    data: {
        artists: [
            {
                name: "あああああ",
                url: "https://www.youtube.com/embed/hLMJXH8TMJg?rel=0&autoplay=1"
            },
            {
                name: "いいいいい",
                url: "https://www.youtube.com/embed/PEBy3aegIvE?rel=0&autoplay=1"
            },
            {
                name: "ううううう",
                url: "https://www.youtube.com/embed/CUpgdwuEJxw?rel=0&autoplay=1"
            },
            {
                name: "えええええ",
                url: "https://www.youtube.com/embed/lTDeS-PmLgY?rel=0&autoplay=1"
            },
            {
                name: "おおおおお",
                url: "https://www.youtube.com/embed/0M3HoC2uGhM?rel=0&autoplay=1"
            },
            {
                name: "かかかかか",
                url: "https://www.youtube.com/embed/wf1MfO4V7cA?rel=0&autoplay=1"
            }
        ]
    },
    methods: {
        execute: function(artist) {
            bus.$emit('click.trg', artist.url)
        }
    }
})

new Vue({
    el: "#modal",
    data: {
        opened: false,
        url: ""
    },
    methods: {
        open: function(val){
            this.opened = true;
            this.url = val
        },
        close: function(){
            this.opened = false;
        }
    },
    created: function() {
        bus.$on('click.trg', this.open)
    }
})

index.html

<body>
    <div class="wrap">
        <ul id="trg" class="list">
            <li class="item" v-for="(artist,index) in artists" v-on:click="execute(artist)">
                <img v-bind:src="'images/voice' + index + '.jpg'" v-bind:alt="artist.name">
            </li>
        </ul>
        <div id="modal" v-cloak>
            <div class="overlay" v-if="opened" v-on:click.self="close">
                <div class="mcont">
                    <div class="btnclose" v-on:click="close"></div>
                    <iframe width="900" height="506" v-bind:src="url" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
                </div>
            </div>
        </div>
    </div>
    <script src="./js/bundle.js"></script>
</body>

それではmain.jsとindex.htmlについて解説していきます。

main.js

今回、トリガーとなる要素をクリックしたら、それに連動したモーダルが表示されるという動きを作成します。
想定として、トリガー要素のVueインスタンス(dataに各youtubeの埋め込みURLを持っている)をクリック=>連動したモーダルに埋め込みURLを渡しつつそれを使ってモーダル表示させます。
data受け渡しはイベントを使って行うので、イベントハブ用のVueインスタンスを最初に作っておきます。
あとは、<li>を展開するときにv-for="(artist,index) in artistsindexを渡すので、画像名には連番を振っています。
モーダル要素のVueインスタンスにはopened: false,、HTMLにはv-if="opened"を記述しているので最初はDOMにレンダリングされていません。

まずVueを読み込みます。
そしてイベントハブ用のVueインスタンスを作成します。(使い方は後ほど解説します。)

const Vue = require('vue');
const bus = new Vue();

次にトリガーとなる要素のVueインスタンスを作成していきます。

new Vue({
    el: '#trg',
    data: {
        artists: [
            {
                name: "あああああ",
                url: "https://www.youtube.com/embed/hLMJXH8TMJg?rel=0&autoplay=1"
            },
            {
                name: "いいいいい",
                url: "https://www.youtube.com/embed/PEBy3aegIvE?rel=0&autoplay=1"
            },
            {
                name: "ううううう",
                url: "https://www.youtube.com/embed/CUpgdwuEJxw?rel=0&autoplay=1"
            },
            {
                name: "えええええ",
                url: "https://www.youtube.com/embed/lTDeS-PmLgY?rel=0&autoplay=1"
            },
            {
                name: "おおおおお",
                url: "https://www.youtube.com/embed/0M3HoC2uGhM?rel=0&autoplay=1"
            },
            {
                name: "かかかかか",
                url: "https://www.youtube.com/embed/wf1MfO4V7cA?rel=0&autoplay=1"
            }
        ]
    },
    methods: {
        execute: function(artist) {
            bus.$emit('click.trg', artist.url)
        }
    }
})

そしてトリガーがクリックされたときのメソッドを定義します。

methods: {
    execute: function(artist) {
        bus.$emit('click.movie', artist.url);
    }
}

このメソッドはマウントポイント(<ul id="trg">)直下の<li>に設定します。
引数にartistを設定することで、"click.movie"イベントを発火させたときにそのartistに設定されているartist.urlを渡すことができます。

<li class="item" v-for="(artist,index) in artists" v-on:click="execute(artist)">
    <img v-bind:src="'images/voice' + index + '.jpg'" v-bind:alt="artist.name">
</li>

ここでいったんbusについて書きます。
以下の記述でトリガーがクリックされたときのメソッドを定義していますが、これは第一引数でイベント名、第二引数でイベント発火時に受けわたす値を設定しています。
これはemitという書き方をすることで実現できます。emitはイベント発火のメソッドです。

bus.$emit('click.movie', artist.url);

そしてemitで発火させたイベントを受け取る処理がモーダル要素のVueインスタンスに設定されている以下のメソッドです。onはイベント受け取りのメソッドです。
onメソッドは受け取ったイベントとメソッドを関連付ける役割をします。(javascriptのaddEventListennerみたいなもの)

トリガー要素クリック=>emitで定義したイベント名でイベント発火(このときartist.url持ってる)=>onでそのイベントに対応した(第二引数に指定した)メソッドを実行、という流れです。

created: function() {
    bus.$on('click.trg', this.open)
}

ちなみにonで関連付けたopenメソッドは以下なので受け取ったartist.urlを自分(モーダル要素のVueインスタンス)のdataに格納つつ自分のopenedプロジェクトをtrueに変えています。
これでクリックしたときにそれに対応したモーダルが表示されyoutubeが再生されます。

open: function(val){
    this.opened = true;
    this.url = val
}

ちょっとしたポイントとして、ブラウザロード時に一瞬モーダルがちらつく現象があるので、それは以下で回避しています。
index.html

<div id="modal" v-cloak>

index.css

[v-cloak] {
    display: none;
}

長くなりましたがこんな感じでモーダルを実装しました。
今回、こちらを参考にしてソースを書きました!ありがとうございます!!
https://liginc.co.jp/374210

読んでいただいた方いましたら、最後までお付き合いいただきありがとうございました!
当方経験が浅いためご指摘、アドバイスあれば教えてください。

読んでいただいてありがとうございました!!

Cloud Speech API + node.jsで音声認識をさせてみる3

今回は音声をローカルファイルに書き込まず、リアルタイムで音声認識させてconsoleに出す処理を書いていきます。
Cloud Speech API + node.jsで音声認識をさせてみる1
Cloud Speech API + node.jsで音声認識をさせてみる2

今回はリアルタイムで認識させるので、fsモジュールは必要ないので外しておきます。
リアルタイムで認識させるためにstreamingRecognizeを使います。

const record = require('node-record-lpcm16');
const speech = require('@google-cloud/speech');
const speechClient = new speech.SpeechClient();
const SAMPLE_RATE = 16000

const request = {
  config: {
    encoding: 'LINEAR16',
    sampleRateHertz: SAMPLE_RATE,
    languageCode: 'ja-jp'
  }
};

const recognizeStream = speechClient
  .streamingRecognize(request)
  .on('error', console.error)
  .on('data', (data) => {
    console.log(data.results[0]);
  });

record.start({
  sampleRate: SAMPLE_RATE,
  recordProgram: 'sox',
  thresholdStart: 0.2,
  thresholdEnd: 0.1,
  silence: '0.1'
}).pipe(recognizeStream);

まず以下でrecognizeStreamを作成します。

const recognizeStream = speechClient
  .streamingRecognize(request)
  .on('error', console.error)
  .on('data', (data) => {
    console.log(data.results[0]);
  });

そしてレコーディングを開始して、取り込んだデータをrecognizeStreamに渡します。

record.start({
  sampleRate: SAMPLE_RATE,
  recordProgram: 'sox',
  thresholdStart: 0.2,
  thresholdEnd: 0.1,
  silence: '0.1'
}).pipe(recognizeStream);

ここまで記述したindex.jsを起動します。

node ./index.js

これでマイクに向かってしゃべると内容がConsoleに表示されます。
結果はこんな感じです。

$ node index.js
{ alternatives:
   [ { words: [],
       transcript: 'あいうえお',
       confidence: 0.9614928960800171 } ],
  isFinal: true,
  stability: 0 }

今日はここまで!読んでいただいてありがとうございました!!!