第8話 予約メニューのメッセージ作成
こんにちは!Kenです。
前回はSQLで顧客データベースの作成を行いました。
さて、今回からはいよいよ予約手続きのプログラム作成に入っていきたいと思います。
予約プログラムの中で重要な要素は次の項目です。
■FLEX Messageの作成
■Postbackイベントの処理
FLEX Messageは以下参照していただけるとわかりますが、レイアウトを自由に構成できるメッセージです。本アプリはボタンコンポーネントを多用したメッセージを使いますので、FLEX Messageを使いこなすことは必須となります。
Postbackイベントはイベントタイプの一つです。今までに登場したイベントタイプは覚えていますでしょうか。
そうですね、友達登録した際に発火するイベント"follow"とテキスト等のメッセージを送った際に発火するイベント"message"でしたね。
"postback"イベントはボタンが押された際に、そのボタンが持つ"data"の値を返してあげるものです。概念的にはちょっと難しいので、実際に実践した方がわかりやすいと思います。一応説明は以下で見れます。
予約の起点
では、予約メニューをお客様へ返す起点はどこにおきましょうか。お客様が「予約する」というメッセージに対し、リプライすることにしましょう。
「予約する」というメッセージのイベント種類はわかりますか?
もちろん、"message"イベントになりますよね。
では、index.js中のhandleMessageEvent関数の中を見てみましょう。現状次のようになっていると思います。
const handleMessageEvent = async (ev) => {
const profile = await client.getProfile(ev.source.userId);
const text = (ev.message.type === 'text') ? ev.message.text : '';
return client.replyMessage(ev.replyToken,{
"type":"text",
"text":`${profile.displayName}さん、今${text}って言いました?`
});
}
お客様が送ったメッセージに対し、全ておうむ返しするというプログラムでした。
それでは、テキストの内容が「予約する」の場合とそれ以外の場合で処理を分けてみましょう。
const handleMessageEvent = async (ev) => {
console.log('ev:',ev);
const profile = await client.getProfile(ev.source.userId);
const text = (ev.message.type === 'text') ? ev.message.text : '';
if(text === '予約する'){
return client.replyMessage(ev.replyToken,{
"type":"text",
"text":"かしこまりました。次回予約ですね。メニューは・・・"
});
}else{
return client.replyMessage(ev.replyToken,{
"type":"text",
"text":`${profile.displayName}さん、今${text}って言いました?`
});
}
}
こんな感じで、「予約する」が来た場合だけ、違うメッセージを発するようにしました。デプロイして確認してみましょう。
「予約する」という言葉に反応していることがわかりますね。
でもこのテキストメッセージのリプライでは、次のアクションとしてお客様がメニューを選ぶことができません。
そこで、テキストメッセージの代わりに、メニューを選ぶことのできるFLEX Messageをリプライするのです。
FLEX Messageの作成
では、FLEX Messageを作成していきましょう。以下ページでFLEX Messageの要素を見て下さい。
大元のBubble Containerは4つの要素Header、Hero、Body、Footerで構成されています。さらにそれぞれの要素もBox(vertical,horizontal)で複数に分割できます。
そして、これらをJSONで表現したものがFLEX Messageになります。
つまり、すごい複雑なので自分でJSONを書くのは効率上やめましょう。
FLEX Messageを作成する上で、めちゃめちゃ便利なのが、FLEX Message Simulatorです。
先ほどのページの左のメニュー欄に「FLEX Message Simulator」があるかと思いますので、それを開いて下さい。
こんな感じのページです。デフォルトのFLEX Messageが表示されていますね。
右上の青いNewボタンをクリック→bubbleを選択し新規作成します。
新規作成のデフォルト状態は、bodyの中にvertical boxが1つ、その中にテキストラベルが1つあり、テキストラベルの内容が「hello,world」というものです。
では作りたいFLEX Messageのイメージを先にお見せします。
4要素の構成は上のようになっており、更にbodyの中はhorizontal boxが4段積み重なっているイメージですね。とまぁ、文字で見るよりも実際にやってみた方が早いですね。simulatorに戻りましょう。
まずheaderをクリックし選択したら、上の青い+ボタンをクリックし、boxを選択しましょう。すると、headerの中にvertical boxが作られたことがわかると思います。
では、header-vertical box - A hello,woldを選択してみましょう。(Aはテキストラベルを意味します。)
右欄で各種パラメータを変更することができます。今回いじるパラメータは以下です。
■text:メニューを選択して下さい
■size:lg(※フォントサイズです)
■align:center(※文字列の水平方向の位置です)
では同様にheroをいじっていきましょう。
こんな感じです。
先ほどと同じ要領で、
■text:(1つのみ選択可能です)
■size:md
■align:center
また、先ほどと異なるのはseparatorが入っていることです。heroの中のvertical boxを選択したら青い+ボタン→separatorを選択し挿入することができます。
では、次にbodyの中身をいじっていきましょう。bodyの中は全てボタンのみですので、まずbodyの中のテキストラベルを選択し、赤い✖️ボタンで削除しましょう。
そして、vertical box → box追加で更にboxを追加します。
追加したboxはデフォルトでverticalになっているので、右欄のlayoutをhorizontalへ変更しましょう。
更にhorizontal boxの中のテキストも削除し、horizontal boxの中にbuttonを2つ追加しましょう。horizontalなので水平方向に追加されます。
以下のようになればOKです。
これをあと3回繰り返します(汗汗
最終的にこうなればOKです。
もうだいたい要領がわかってきたのではないでしょうか。趣味ですが、vertical boxの一番最後にseparatorを追加してみましょう。
ボタンパラメータは次の内容へ変えてbody部分は完成です。
<Button>
■margin: md
■style: primary
■color: #999999
<Action(各メニューボタン)>
■type: postback
■label: 各メニュー(カット、シャンプー、カラーリング、・・・)
■data: menu&i (iは各メニューに対応する0〜5の数値。例えば、カットの場合はmenu&0、カラーリングの場合はmenu&2とする)
<Action(選択終了ボタン)>
■type: postback
■label: 選択終了
■data: end
ボタンを押すと、postbackイベントが発生し、サーバーへはdataが返されます。例えば、シャンプーが押されると、サーバーへはmenu&1のデータが返されるため、この1によってサーバー側はシャンプーが選択されたことを判別するわけです。当然、その処理は後ほどコーディングしていきます。
さて、bodyは最終的にこのようになりました。
上下のボタンがくっついてしまってる場合、各horizontal boxのmargin: mdを設定してあげれば上下も間隔をあけることができます。
最後にフッターを編集しましょう。もう要領はおわかりですね。footerの中にボタンを1つ作成します。
<Action>
■type: postback
■label: キャンセル
■data: cancel
これで完成です。
では、このFLEX MessageのJSONを取得しましょう。
画面右上の</> View as JSONボタンをクリックしてみましょう。
これが、このFLEX MessageのJSONになります。クッソ長いですね・・汗汗。これを手書きできる人がいればほんと尊敬します(笑)
{
"type": "bubble",
"header": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "メニューを選択して下さい",
"align": "center",
"size": "lg"
}
]
},
"hero": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "(1つのみ選択可能です)",
"size": "md",
"align": "center"
},
{
"type": "separator"
}
]
},
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "box",
"layout": "horizontal",
"contents": [
{
"type": "button",
"action": {
"type": "postback",
"label": "カット",
"data": "menu&0"
},
"style": "primary",
"color": "#999999",
"margin": "md"
},
{
"type": "button",
"action": {
"type": "postback",
"label": "シャンプー",
"data": "menu&1"
},
"style": "primary",
"color": "#999999",
"margin": "md"
}
]
},
{
"type": "box",
"layout": "horizontal",
"contents": [
{
"type": "button",
"action": {
"type": "postback",
"label": "カラーリング",
"data": "menu&2"
},
"margin": "md",
"style": "primary",
"color": "#999999"
},
{
"type": "button",
"action": {
"type": "postback",
"label": "ヘッドスパ",
"data": "menu&3"
},
"margin": "md",
"style": "primary",
"color": "#999999"
}
],
"margin": "md"
},
{
"type": "box",
"layout": "horizontal",
"contents": [
{
"type": "button",
"action": {
"type": "postback",
"label": "マッサージ&パック",
"data": "menu&4"
},
"margin": "md",
"style": "primary",
"color": "#999999"
},
{
"type": "button",
"action": {
"type": "postback",
"label": "顔そり",
"data": "menu&5"
},
"style": "primary",
"color": "#999999",
"margin": "md"
}
],
"margin": "md"
},
{
"type": "box",
"layout": "horizontal",
"contents": [
{
"type": "button",
"action": {
"type": "postback",
"label": "眉整え",
"data": "menu&6"
},
"margin": "md",
"style": "primary",
"color": "#999999"
},
{
"type": "button",
"action": {
"type": "postback",
"label": "選択終了",
"data": "end"
},
"margin": "md",
"style": "primary",
"color": "#0000ff"
}
],
"margin": "md"
},
{
"type": "separator"
}
]
},
"footer": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "button",
"action": {
"type": "postback",
"label": "キャンセル",
"data": "cancel"
}
}
]
}
}
では、最後にこのJSONを使ってメニュー選択メッセージを返信するところまで実装しましょう。
orderChoice( ) 関数の実装
後々改造しやすいように、メニューの返信は別関数として実装することにします。handleMessageEvent( )関数内を次のように変更しましょう。
if(text === '予約する'){
orderChoice(ev);
}
evを引数としてorderChoice関数を実行します。
ではorderChoice関数です。
const orderChoice = (ev) => {
return client.replyMessage(ev.replyToken,{
"type":"flex",
"altText":"menuSelect",
"contents":
{
"type": "bubble",
"header": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "メニューを選択して下さい",
"align": "center",
"size": "lg"
}
]
},
"hero": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "(1つのみ選択可能です)",
"size": "md",
"align": "center"
},
{
"type": "separator"
}
]
},
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "box",
"layout": "horizontal",
"contents": [
{
"type": "button",
"action": {
"type": "postback",
"label": "カット",
"data": "menu&0"
},
"style": "primary",
"color": "#999999",
"margin": "md"
},
{
"type": "button",
"action": {
"type": "postback",
"label": "シャンプー",
"data": "menu&1"
},
"style": "primary",
"color": "#999999",
"margin": "md"
}
]
},
{
"type": "box",
"layout": "horizontal",
"contents": [
{
"type": "button",
"action": {
"type": "postback",
"label": "カラーリング",
"data": "menu&2"
},
"margin": "md",
"style": "primary",
"color": "#999999"
},
{
"type": "button",
"action": {
"type": "postback",
"label": "ヘッドスパ",
"data": "menu&3"
},
"margin": "md",
"style": "primary",
"color": "#999999"
}
],
"margin": "md"
},
{
"type": "box",
"layout": "horizontal",
"contents": [
{
"type": "button",
"action": {
"type": "postback",
"label": "マッサージ&パック",
"data": "menu&4"
},
"margin": "md",
"style": "primary",
"color": "#999999"
},
{
"type": "button",
"action": {
"type": "postback",
"label": "顔そり",
"data": "menu&5"
},
"style": "primary",
"color": "#999999",
"margin": "md"
}
],
"margin": "md"
},
{
"type": "box",
"layout": "horizontal",
"contents": [
{
"type": "button",
"action": {
"type": "postback",
"label": "眉整え",
"data": "menu&6"
},
"margin": "md",
"style": "primary",
"color": "#999999"
},
{
"type": "button",
"action": {
"type": "postback",
"label": "選択終了",
"data": "end"
},
"margin": "md",
"style": "primary",
"color": "#0000ff"
}
],
"margin": "md"
},
{
"type": "separator"
}
]
},
"footer": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "button",
"action": {
"type": "postback",
"label": "キャンセル",
"data": "cancel"
}
}
]
}
}
});
}
さぁ、これをherokuへデプロイし、メッセージが返信されるか確認しましょう。
大成功です!
さて、本日はこの辺にしましょう。次回はpostbackイベントの処理を実装していきます。
最後までお読みいただきありがとうございました。
もし少しでも参考になりました「スキ」をいただけると、今後の執筆モチベーションになります。
MENTA でLINEBOT開発サポートをしております。お気軽にご相談ください。