ループ処理でListとCarouselを実装する方法[Actions on Google]

## 概要
ユーザに複数の選択肢を促す手法として、[Suggestions chips](https://developers.google.com/actions/assistant/responses#suggestion_chips)がありますが、こちらだと表現に限界があります。

たくさん選択肢を表示したい場合や、よりその選択肢を詳細を記述したい場合、[List](https://developers.google.com/actions/assistant/responses#list)や[Carousel](https://developers.google.com/actions/assistant/responses#carousel)で表示したほうが良い場合があります。

ただ、ListやCarouselの場合で表現するには、JSONで出力する必要があるのですが、
その際のループ処理に少し躓いたので、こちらでメモをしておきます。

どんな機能か見てみたいときは、アシスタントに「[ブラウザチェッカー](https://assistant.google.com/services/a/uid/00000048022bf15d?jsmode=o&hl=ja-JP&source=web)につないで」といってみてください!!

## 構造について

まず、[List](https://developers.google.com/actions/assistant/responses#list)と[Carousel](https://developers.google.com/actions/assistant/responses#carousel)の大まかな構造について解説します。
![スクリーンショット 2019-06-25 23.59.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133465/d331d7ec-45a3-995a-97c0-8f2d7032961c.png)

1. Request「○○につないで」といってアプリ起動します。
2. [List](https://developers.google.com/actions/assistant/responses#list)または[Carousel](https://developers.google.com/actions/assistant/responses#carousel)が記述されたデータがResponseされます。  
このとき、クライアントでは[List](https://developers.google.com/actions/assistant/responses#list)または[Carousel](https://developers.google.com/actions/assistant/responses#carousel)が表示されます。  
 サンプルコードでいうと「Sample code」の部分。Welcome Intentはここまで。
3. ユーザは2から好きな項目を選ぶと、Dialogflowで"[actions_intent_OPTION](https://developers.google.com/actions/assistant/helpers#helper_intents)"が指定されたIntentに情報が飛びます。  
 後述しますが、Suggestion chipsとの違いは、ここでの[List](https://developers.google.com/actions/assistant/responses#list)または[Carousel](https://developers.google.com/actions/assistant/responses#carousel)返答は開発者側で自由に選べるところです。
4. Responseされます。サンプルコードでいうと「Handling selected item」でResponseされます。


## 組み方(必要な分だけ絞る)
まず[サンプルコード](https://developers.google.com/actions/assistant/responses#list)からタイトル（title）, 説明(description)だけを絞ると、Responseは以下のようになる。


#### [List](https://developers.google.com/actions/assistant/responses#list)の場合  

```JavaScript

app.intent('Default Welcome Intent', conv => {
    conv.ask('This is a list example.');  // Rich Responseを出力する際はSimple Responseは不可欠
    conv.ask(new List({
        title: 'List Title',
        items: {
            'Title AAA': {
                title: 'Title of AAA',
                description: 'This is a description of AAA.',
            },
            'Title BBB': {
                title: 'Title of BBB',
                description: 'This is a description of BBB.',
            },
            'Title CCC': {
                title: 'Title of CCC',
                description: 'This is a description of CCC.',
            },
        },
    }));
});
```

#### [Carousel](https://developers.google.com/actions/assistant/responses#carousel)の場合

```JavaScript
app.intent('Default Welcome Intent', conv => {
    conv.ask('This is a carousel example.');  // Rich Responseを出力する際はSimple Responseは不可欠
    conv.ask(new Carousel({
        items: {
            'Title AAA': {
                title: 'Title of AAA',
                description: 'This is a description of AAA.',
            },
            'Title BBB': {
                title: 'Title of BBB',
                description: 'This is a description of BBB.',
            },
            'Title CCC': {
                title: 'Title of CCC',
                description: 'This is a description of CCC.',
            },
        },
    }));
});

```

## 組み方2 ループ処理
続いて、大抵の場合は情報を配列とかオブジェクトに格納すると思うので、以下のようにデータが用意し、...①
ListまたはCarousel用のオブジェクトを作ってあげます。...②
引き渡します。...③

```JavaScript
app.intent('hoge', conv =>{

    // ①データを用意
    const original = { 
        "aaa" : {title:'Title aaa', description:"Description aaa", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png'},
        "bbb" : {title:'Title bbb', description:"Description bbb", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png'},
        "ccc" : {title:'Title ccc', description:"Description ccc", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png'},
        "ddd" : {title:'Title ddd', description:"Description ddd", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png'},
        "eee" : {title:'Title eee', description:"Description eee", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png'},
        "fff" : {title:'Title fff', description:"Description fff", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png'},
        "ggg" : {title:'Title ggg', description:"Description ggg", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png'},
    };

    //②リスト用にデータを用意してあげる
    let arr = {};  // List, Carouselはオブジェクトを返す必要があることに注意!!
    for(let i in original){
        let title = original[i].title;
        let description = original[i].description;
        let imageUrl = original[i].imageUrl;

        // console.log("[確認]title--------->" + title);
        // console.log("[確認]description--->" + description);
        // console.log("[確認]imageUrl------>" + imageUrl);

        let valueObj = {
            'title': title,
            'description': description,
            image: new Image({
                url: imageUrl,
                alt: title,
            }),
        }
        arr[title] = valueObj;  // 変数化したKeyにValueを代入する。
    }
    // console.log("[確認]arr----------->" + JSON.stringify(arr));

    //③引き渡す
    conv.ask("Simple respponse.")
    conv.ask(new List({       // <-- Carousel表示したいの場合はCarouselに入れ替える
        title: "List Test",   // <-- Carousel表示したいの場合はここのtitleはいらない
        items: arr
    }));

})
```

## 組み方3 ListとCarouselの返答
ユーザが入力した項目をIntentで受け取るためには、Dialogflow側で新たにインテントを作成し、
Event入力欄に"actions_intent_OPTION"を入力します。
[Helper Intents](https://developers.google.com/actions/assistant/helpers#helper_intents)にEvent指定できる一覧があります。

![actions_intent_OPTION](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133465/9b136a96-9466-117a-7ac6-108dcba92012.png)


ソースコード側では以下のように指定すると、ユーザが選択した情報を回答として出力することができます。

```JavaScript
app.intent('actions.intent.OPTION', (conv, params, option) =>{
    conv.ask(option); // ユーザの返答は引数optionに入ってくる
})
```



## おまけ [Browsing Carousel](https://developers.google.com/actions/assistant/responses#browsing_carousel)
私が知る限り、アプリ起動したままマイククローズできるを唯一の手段はBrowsing Carouselです。
ただそのかわり、Browsing Carouselは、項目をタップしたらWebページに飛ぶ使用です。
なので、飛び先のリンクを用意する必要があります。

詳しくは→[Browsing Carousel](https://designguidelines.withgoogle.com/conversation/visual-components/browsing-carousel.html#browsing-carousel-guidance) が詳しいです。

```JavaScript
app.intent('hoge', conv =>{

    // ①データを用意、飛先リンクとしてsearchUrlを追加
    const original = { 
        "aaa" : {title:'Title aaa', description:"Description aaa", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png', searchUrl:'https://google.com'},
        "bbb" : {title:'Title bbb', description:"Description bbb", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png', searchUrl:'https://google.com'},
        "ccc" : {title:'Title ccc', description:"Description ccc", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png', searchUrl:'https://google.com'},
        "ddd" : {title:'Title ddd', description:"Description ddd", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png', searchUrl:'https://google.com'},
        "eee" : {title:'Title eee', description:"Description eee", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png', searchUrl:'https://google.com'},
        "fff" : {title:'Title fff', description:"Description fff", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png', searchUrl:'https://google.com'},
        "ggg" : {title:'Title ggg', description:"Description ggg", imageUrl:'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png', searchUrl:'https://google.com'},
    };

    //②配列を用意してあげる
    let arr = [];   // <-- ListやCarouselの場合はObjectでしたが、
                    //     Browsing Carouselの場合は配列です。
    for(let i in original){
        let title = original[i].title;
        let description = original[i].description;
        let imageUrl = original[i].imageUrl;
        let searchUrl = original[i].searchUrl;

        // console.log("[確認]title--------->" + title);
        // console.log("[確認]description--->" + description);
        // console.log("[確認]imageUrl------>" + imageUrl);
        // console.log("[確認]searchUrl----->" + searchUrl);

        let item = new BrowseCarouselItem({
            title: title,
            description: description,
            url: searchUrl,
            image: new Image({
                url: imageUrl,
                alt: title,
            }),
            footer: 'タップするとWebページに飛ぶぜよ。',
        })
        arr.push(item);  // Browsing Carouselの場合は配列なのでPushが使える!!
    }
    
    // console.log("[確認]arr----------->" + arr);

    //③引き渡す
    conv.ask("Simple respponse.")
    conv.ask(new BrowseCarousel({
        items: arr
    }));

})
```




## そんなことより
上記のListとBrowsing Carousel機能を取り入れたアプリを作ったので、
リストってどんな感じなの？ってかたはアシスタントに「ブラウザチェッカーにつないで」といって見てみてください！笑
[ブラウザチェッカー](https://assistant.google.com/services/a/uid/00000048022bf15d?jsmode=o&hl=ja-JP&source=web)