「kintone UI Component」だけを使ってアプリを作ってみた


#はじめに
kintoneアプリのパーツを自作することは、カスタマイズする時に結構ありますよね。
最近は、kintoneライクなUIが手軽に作れる「kintone UI Component」を初めて試して、
便利すぎてすっかりハマりました（笑）

学び盛りのプログラミング初心者なので、使い方の勉強も兼ねて、
今回はkintone UI Componentのフィールドだけでアプリを作ることにチャレンジしてみました:muscle:
早速、作ったアプリを紹介したいと思います！

#完成イメージ

パン作りが楽しくて、毎週趣味でパンを焼いてるんですが、
レシピ探すのに時間がかかるし、足りない材料を検索して購入するのも一手間..なんとかしたい(´・ω・`)

パン作り作業を効率化するために今回は、
**・カテゴリを選択すれば、楽天レシピで該当カテゴリランキング1位のレシピを取得する**
**・足りない材料を選択すれば、楽天市場で該当商品を検索する**

の2つの機能を一画面でできるアプリを、kintone UI Componentで作ってみました。
早速使ってみると...

![アプリイメージ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276157/2b976379-554d-6607-5b3e-fa170ac3624d.gif)

なんと、クリスマスリースパンを勧めてくれた！季節にぴったりですね:santa_tone2::christmas_tree:♫

今回のカスタマイズで、
タイトルのLabelフィールドと、
kintone UI Componentパーツを描画するためのspaceフィールドは、
kintone基本機能のフィールドを使いましたが...

肝心な動き部分の処理はすべでkintone UI Componentで作ったので、
大目に見てもらえるとうれしいです:angel_tone2:(笑)

早速、詳細の説明に入りたいと思います。
#準備作業

- 楽天デベロッパーアカウントの登録
    - 利用するAPI
        - [楽天レシピカテゴリ別ランキングAPI](https://webservice.rakuten.co.jp/explorer/api/Recipe/CategoryRanking/)
        - [楽天市場商品検索API](https://webservice.rakuten.co.jp/explorer/api/IchibaItem/Search/)

- kintoneアプリの作成
    - レシピアプリを表示するアプリの用意
    - レシピのカテゴリ番号を保存するアプリの用意

- ライブラリの適用
    - [kintone UI Component](https://github.com/kintone/kintone-ui-component)から下記2つをダウンロードし、レシピ表示用アプリに適用
        - kintone-ui-component.min.css
        - kintone-ui-component.min.js
        
#要件の整理
せっかくの「kintone UI Component」祭りなので、
今回はパーツを「動的に作成できる」という特徴を生かしたいです。

操作の順を追って各パーツを順番に表示させるUIにしたいので、
まずは、必要なフィールドと処理の流れの整理をしていきます。

##必要なフィールド

なるべく色々なフィールドの使い方を試したいので、
今回は下記４つの要件に、6つのフィールドを使いました。

**①カテゴリの選択** → **②検索結果の表示** → **③材料の選択** → **④購入リンクの表示**

|要件|画面上の表示  |使ったフィールド  |
|---|---|---|
|①カテゴリの選択  |大カテゴリ  |ラジオボタン  |
|  |中カテゴリ |ドロップダウン  |　
|②検索結果の表示  |レシピタイトル |ラベル  |
|  |レシピ材料 |チェックボックス  |　
|③材料の選択  |レシピ材料  |チェックボックス  |
|  |検索ボタン  |ボタン  |
|④購入リンクの表示  |購入リンク  |テーブル  |
<font color="DarkGray">＊処理の流れがわかりやすいようにチェックボックスを２回書きましたが、同じフィールドを指します。</font>

##処理の流れ
必要なフィールドを使って、もう少し丁寧に流れを整理してみます。

**①カテゴリの選択**
　→非表示の状態で全部の``フィールドを配置``する
　→``ラジオボタンのchangeイベント``で``ドロップダウンに項目をセット``して、表示させる

**②検索結果の表示**
　→``ドロップダウンのchangeイベント``で``楽天レシピAPI``を叩く
　→取得したレシピ情報を``ラベル``、``チェックボックス項目にセット``して、``ボタン``と一緒に表示させる

**③材料の選択**
　→``ボタンのclickイベント``で``チェックボックスの値を取得``し、その値で``楽天市場API``を叩く

**④購入リンクの表示**
　→商品情報が取得できたら、商品名とリンクを``テーブルの値にセット``して、表示させる

<img width="864" alt="スクリーンショット 2019-12-09 17.25.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276157/968fbd8f-6968-28d9-6e1c-51ea76d4d2ef.png">
<font color="DarkGray">＊今回は特にレコードとして保存したいデータがないため、レコードの詳細画面を使いました。</font>

#詳細コード
##①レシピカテゴリの選択
###コード
・全部の``フィールドを配置``する（``非表示の状態``）

```javascript
  //ラジオボタン項目
  const radioButtonItems = [{
    label: '未選択',
    value: '未選択',
    isDisabled: false
  }];

  //ドロップダウン項目
  const dropdownItems = [];
  const dropdownOption = {items: dropdownItems};

  //チェックボックス項目
  const checkboxItems = [];
  const checkboxOption = {items: checkboxItems};

  //テーブル項目
  const data = [];
  const columns = [
    {
      header: '商品名',
      cell: function() {
        return kintoneUIComponent.createTableCell('label', 'label1');
      }
    },
    {
      header: '購入リンク',
      cell: () => {
        return kintoneUIComponent.createTableCell('label', 'label2');
      }
    }
  ];

  // 詳細画面に必要なフィールドをセット
  const setFields = () => {
    // ラジオボタンを作成
    const radioButtonOption = {name: '種類', items: radioButtonItems, value: '未選択'};
    const radioButton = new kintoneUIComponent.RadioButton(radioButtonOption);
    kintone.app.record.getSpaceElement('radioButtonSapce').appendChild(radioButton.render());

    // 非表示状態でドロップダウンを作成
    const dropdown = new kintoneUIComponent.Dropdown(dropdownOption);
    kintone.app.record.getSpaceElement('dropdown').appendChild(dropdown.render());
    dropdown.hide();

    // 非表示状態状態でチェックボックスを作成
    const checkbox = new kintoneUIComponent.CheckBox(checkboxOption);
    kintone.app.record.getSpaceElement('checkbox').appendChild(checkbox.render());
    checkbox.hide();

    // 非表示状態でラベルを作成
    const label = new kintoneUIComponent.Label({
      text: '',
      textColor: '#B81C22',
      isRequired: true
    });
    kintone.app.record.getSpaceElement('title').appendChild(label.render());
    label.hide();

    // 画像タグを作成
    const foodImage = document.createElement('img');

    // 非表示状態でボタンを作成
    const button = new kintoneUIComponent.Button({text: '楽天市場で検索！'});
    kintone.app.record.getSpaceElement('button').appendChild(button.render());
    button.hide();

    // 非表示状態でテーブルを作成
    const table = new kintoneUIComponent.Table({
      data: data,
      columns: columns
    });
    kintone.app.record.getSpaceElement('table').appendChild(table.render());
    table.hide();

    // ラジオボタンとドロップダウンに項目、値をセットする処理を呼び出す
    setValue_radioButton_dropdown(radioButton, dropdown, label, button, foodImage, checkbox, table);
  };
    // レコード詳細画面が開いたタイミングで、フィールドを設置する処理を呼び出す
    kintone.events.on('app.record.detail.show', setFields);
```
・```ラジオボタンのchangeイベント```で```ドロップダウンに項目をセット```して、表示させる

```javascript
// ラジオボタンとドロップダウンの項目をセットする処理を定義
  const setValue_radioButton_dropdown = (radioButton, dropdown, label, button, foodImage, checkbox, table) => {

    // *カテゴリの中身を変更する度にコードを変更しなくて済むように、
    // *レシピのカテゴリ番号を別のkintoneアプリに保存しておいた
    // カテゴリアプリから、予め保存したカテゴリ名、番号データを取得
    kintone.api(kintone.api.url('/k/v1/records', true), 'GET', body, (resp) => {
      for (let i = 0; i < resp.records.length; i++) {
        if (resp.records[i].種類.value) {
          radioButtonItems.push({
            label: resp.records[i].種類.value,
            value: resp.records[i].種類.value,
            isDisabled: false
          });
        }
      }
      // 取得したカテゴリデータをラジオボタンの項目にセットする
      radioButton.setItems(radioButtonItems);

      // ラジオボタンがchangeされたタイミングで、ドロップダウンの項目をセットする
      radioButton.on('change', (value) => {
        removeDropdownItems(dropdown);
        for (let i = 0; i < resp.records.length; i++) {
          if (resp.records[i].種類.value !== value) {
            continue;
          }
          for (let j = 0; j < resp.records[i].Table.value.length; j++) {
            dropdownItems.push({
              label: resp.records[i].Table.value[j].value.名前.value,
              value: resp.records[i].Table.value[j].value.コード.value,
              isDisabled: false
            });
          }
        }
        dropdown.setItems(dropdownItems);
        dropdown.show();
      });

    });
    // ラベル、ボタン、写真、チェックボックスに値をセットする処理を呼び出す
    setValue_label_button_foodImage_checkbox(dropdown, label, button, foodImage, checkbox, table);
  };
```
###ポイント

・``フィールドの設置``

|メソッド |用途 |
|---|---|---|
|render() |  フィールドの設置|

一番基本のメソッド。必要な属性だけ記入しておけば、
特にHTML要素やcssを指定することなく、簡単にkintoneライクなUIが作成できます。

・``非表示・表示``

|メソッド |用途 |
|---|---|---|
|hide()/show()  | 表示・非表示|

動的にフィールドを表示させる処理で使ったメソッドです。

今回の場合やりたかった、
詳細画面開いたタイミングで非表示な状態でフィールドをまとめて作成しておき、
タイミングに合わせて適宜にフィールドを表示させる処理は、
hide()/show()でかんたんに実現できました。

・``change イベント``

|メソッド |用途 |
|---|---|---|
|on(eventName, callBack)  | change イベント|

動的にフィールドを表示させる処理の、もう一つ重要な要素です。

今回のカスタマイズで、
 **ラジオボタンの値を変更したらドロップダウンに項目をセットする。**
 **ドロップダウンの値を変更したら楽天レシピAPIからレシピデータを取得する。**
のような処理でchangeイベントを使いました。

ラジオボタン、ドロップダウンのchangeイベントの場合、
引数に選択された項目の値が渡されて、その後の処理で直接使えるので、便利です。

・``項目の追加``

|メソッド |用途 |
|---|---|---|
|setItems()  | 項目の追加|

予め作っておいたフィールドに、
選択したレシピによって異なる項目をセットする処理で使った、選択式フィールドのメソッドです。

選択式フィールドの場合、選択肢として基本複数の項目が必要なので、
こちらのsetItems()を使えば、複数の項目を一度にセットすることができます。

・``項目の取得・削除``

|メソッド |用途 |
|---|---|---|
|getItems()/removeItem()  | 項目の取得・削除|

項目追加メソッドを使う時の気づきですが、
すでに項目が存在する場合、setItems()で項目を追加する際の処理は、
上書きをするではなくもとある項目の後に追加する形になります。

今回のドロップダウン、チェックボックスの場合、
カテゴリを選んだ度に項目を増やすのではなく、上書きをしたいので、
ドロップダウンの項目追加部分で使ったremoveDropdownItems()処理のように、
値を追加する前に、getItems()でその前の値を取得し、removeItem()でリセットする必要があります。

```javascript

 const removeDropdownItems = (dropdown) => {
    dropdown.setValue('');
    while (dropdown.getItems().length) {
      dropdown.removeItem(0);
    }
  };
```
##②検索結果の表示
###コード
・``ドロップダウンのchangeイベント``で``楽天レシピAPI``を叩く
・取得したレシピ情報を``ラベル``、``チェックボックス項目にセット``して、``ボタン``と一緒に表示させる

```javascript
  // ラベル、ボタン、写真、チェックボックスに値をセットする処理
  const setValue_label_button_foodImage_checkbox = (dropdown, label, button, foodImage, checkbox, table) => {

   // ドロップダウンの値が変更されるタイミングでレシピ情報を取得
    dropdown.on('change', (value) => {
      const categoryId = value;
      const url = 'https://app.rakuten.co.jp/services/api/Recipe/CategoryRanking/20170426?format=json&client_id='
      + client_id + '&client_secret=' + client_secret + '&categoryId=' + categoryId;
      kintone.proxy(url, 'GET', {}, {}, (r) => {
        const recipeDate = JSON.parse(r);
        const recipe = recipeDate.result[0];
        const recipeMaterial = recipe.recipeMaterial;
        const recipeTitle = recipe.recipeTitle;
        const list = checkbox.getItems();

       // 画像、チェックボックスの増殖を防ぐためのリセットをする処理
        if (list.length !== 0) {
          kintone.app.record.getSpaceElement('image').removeChild(foodImage);
          while (checkbox.getItems().length) {
            checkbox.removeItem(0);
          }

       // 取得した材料データを整形し、チェックボックスの値にセット
        for (let i = 0; i < recipeMaterial.length; i++) {
          const checkboxItem = {
            label: recipeMaterial[i],
            value: recipeMaterial[i],
            isDisabled: false
          };
          checkbox.addItem(checkboxItem);
        }

       // 取得したレシピ名をラベルにセット
        label.setText(recipeTitle);

       // 取得したレシピ画像を詳細画面に表示
        const recipeImageUrl = recipe.foodImageUrl;
        foodImage.setAttribute('style', 'width: 200px; height: 250px');
        foodImage.setAttribute('src', recipeImageUrl);
        kintone.app.record.getSpaceElement('image').appendChild(foodImage);
   
        // ラベル、ボタン、チェックボックスを詳細画面に表示させる
        label.show();
        button.show();
        checkbox.show();
        setValue_table(button, checkbox, table);
      });
    });
  };
```
###ポイント

・``項目の追加``

|メソッド |用途 |
|---|---|---|
|addItem()  | 項目の追加|

選択式フィールドに、一度に１つの項目を追加するメソッドです。

チェックボックスも同じく、今回は複数の項目をセットしたいので、
前の処理で使ったsetItems()メソッドも使えるのですが、
せっかくなので、チェックボックスでaddItem()を試してみました。

気づきとしては、繰り返し処理を使えば、addItemも複数項目対応できるので、
（今回は使っていませんが）条件分岐で各項目の値をフィルタにかけた上で追加する利用シーンもありかな？と思いました。

・``テキストの設定``

|メソッド |用途 |
|---|---|---|
|setText()  |  テキストの追加|

レシピ名などのテキストをあとから追加したい場合に、setText()メソッドが使えます。
さらに、下記メソッドを使えば、文字色や背景色などのstyleの設定もできます。
　setTextColor(color);
　setBackgroundColor(color);

##③材料の選択
###コード
・```ボタンのclickイベント```で```チェックボックスの値を取得```し、その値で```楽天市場API```を叩く

```javascript
  //テーブルの値をセットする処理
  const setValue_table = (button, checkbox, table) => {

    //ボタンがクリックされた際に、チェックボックスの値を取得
    button.on('click', () => {
      const checkboxValue = checkbox.getValue();
      const setData = [];

      //取得したチェックボックスの値をキーワードで楽天市場で検索
      for (let i = 0; i < checkboxValue.length; i++) {
        const keyword = checkboxValue[i];
        const url = 'https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706?format=json&client_id='
        + client_id + '&client_secret=' + client_secret + '&keyword=' + keyword;
        kintone.proxy(url, 'GET', {}, {}, (resp) => {
          const materialItems = JSON.parse(resp);
          const materialItemUrl = materialItems.Items[0].Item.itemUrl;
          const materialItemLink = '<a href="' + materialItemUrl + ' target="_blank">購入ページへ遷移</a>';

          //取得した商品情報をテーブルの値にセットする
          setData.push({
            label1: {text: materialItems.Items[0].Item.itemName},
            label2: {text: materialItemLink}
          });
          table.setValue(setData);

          //テーブルを表示させる
          table.show();
        });
      }
    });
  };
```
###ポイント

・``値の取得``

|メソッド |用途 |
|---|---|---|
|getValue()  |  値の取得|

チェックボックスの値をまとめて取得できるメソッドです。
今回は買いたい材料にチェックを入れる操作になるので、
getValue()を使えば、チェックされた材料名を配列で取得することができます。

・``click イベント``

|メソッド |用途 |
|---|---|---|
|on(eventName, callBack)  |  clickイベント|


足りない材料があれば、その材料の項目をチェックして、
チェックされた項目をキーワードで楽天市場で商品検索する処理を発火させるために、
ボタンのclickイベントを使いました。

チェックボックスにもchangeイベントがあるので、
今回はそちらを使っても問題ないですが、

チェックボックスは複数の項目を選択できるので、
changeされる度に値を渡すより、まとめて値を取得したほうが効率良いですね。
そのため、getValue()で値を取得して、ボタンのclickイベントで発火する処理にしました。

##④購入リンクの表示
###コード
・楽天市場の商品情報が取得できたら、商品名と商品ページのリンクを``テーブルの値にセット``して、表示させる

```javascript
         //取得した商品情報をテーブルの値にセットする
          setData.push({
            label1: {text: materialItems.Items[0].Item.itemName},
            label2: {text: materialItemLink}
          });
          table.setValue(setData);

          //テーブルを表示させる
          table.show();
```
###ポイント

・``値の操作``

|メソッド |用途 |
|---|---|---|
|setValue()  |  値の追加|

選択肢の項目をセットするメソッドと異なり、
値をセットする際はもとあるデータのあとに追加されるのではなく上書きされるので、
事前に値をリセットする必要はありませんでした。
 

#最後に
長くなってしまいましたが、
kintone UI Componentの基本的な使い方を色々触れてスッキリです。

色々試した結果、
一番「kintone UI Component」を使うメリットを感じさせられたのは、選択式フィールドでした。
「kintone UI Component」を使えば、中身の項目を動的に変更させる処理が手軽にできますし、
メソッドも結構共通で使えるので、とても扱いやすいです。

今回は、シンプルな処理しか書けませんでしたが..
これからのカスタマイズでも、「kintone UI Component」を積極的に使っていきたいと思います:muscle:

##参考記事
[「kintone UI Component」を使って簡単にkintoneライクなUIを設置する](https://developer.cybozu.io/hc/ja/articles/360000511023)
[「kintone UI Component」を使って動的ドロップダウンを作成しよう！](https://developer.cybozu.io/hc/ja/articles/360014149732)