[ 初めての kintone REST API ] 旅費精算申請アプリを "ちょっとだけ" 使いやすくしよう

# 🔖 はじめに
[kintone REST API](https://developer.cybozu.io/hc/ja/articles/201941754) の GET（取得）と PUT（更新）メソッドを使って、
旅費精算申請アプリを "ちょっとだけ" 使いやすくカスタマイズする記事です。

[cybozu developer network](https://developer.cybozu.io/hc/ja)（以降、愛称である **devnet** と記載） を見ると、順番処理のために [Promise](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise) や
書き方の簡略化のために [jQuery](https://jquery.com/) がよく使われていますが、いきなりそれはきつい...ですよね。
ということで、そこら辺は一旦置いておいて、まず基本的な書き方を勉強しましょう。
REST API の使い方が分かると、ゆくゆくはアプリ間連携や他サービス連携もできるようになります٩( ᐛ )و

"kintone REST API 初めの一歩" の課題としてチャレンジしてみてください！

# 🔖 やること
[旅費精算申請アプリ](https://kintone-sol.cybozu.co.jp/apps/073-ryohi.html)を使って、以下のカスタマイズしていきます。

- レコード一覧画面を表示した時に、ログインユーザーが申請中のレコード件数を表示する
- レコード追加画面で保存成功後に、自動でプロセスを次に進める（申請のし忘れ防止）

# 🔖 完成イメージ
レコード一覧画面を開くと...
![IndexScreen.png](https://qiita-image-store.s3.amazonaws.com/0/90265/ba404e0a-ef14-a4cf-58de-8a2ff7fe0812.png)

レコード追加画面で保存ボタンを押すと...
![CreateSubmitScreen.gif](https://qiita-image-store.s3.amazonaws.com/0/90265/19f1c67e-7a2a-f77c-9012-a514c210839f.gif)

こうなれば成功です！
それでは早速作り込んでいきましょう。

# 🔖 下準備
JavaScript カスタマイズをする前に、下準備です。

### 1. kintone のアプリ作成
ご自身の kintone 環境にアクセスして、アプリストアから「旅費精算申請」アプリを追加します。
後々の動作確認のために予め数件レコード追加しておきます。
（💡[アプリストアからアプリを追加する](https://jp.cybozu.help/k/ja/user/create_app/add_app_store.html)）

※ kintone 環境をお持ちでない方は、検証目的で1年間無償でご利用いただける「開発者ライセンス」がありますので、お申し込みください。
（💡[kintone環境の取得方法](https://qiita.com/RyBB/items/73c50f1751ad137ba410)）
![旅費精算申請アプリ.png](https://qiita-image-store.s3.amazonaws.com/0/90265/4a94d401-8d40-415f-9cda-189476e040e8.png)

### 2. テキストエディタの用意
コーディングをするためのテキストエディタを用意します。
ノートPCに標準搭載されている メモ帳 などを使うこともできますが、
[Visual Studio Code](https://code.visualstudio.com/) や [Sublime Text](https://www.sublimetext.com/) が書きやすくて個人的にはおすすめです。

# 🔖 kintone REST API とは
コーディングに入る前に、kintone REST API の基本を押さえておきましょう。

kintone JavaScript API は、**ブラウザ上の情報を取得・操作できる**もので、一方 kintone REST API は、**kintone アプリのレコード操作やフォーム設計情報の取得、スペース操作ができる**ものです。
※ REST APIとは：特定の URL にパラメータを指定して、HTTP/HTTPS（通信プロトコルの種類） でアクセスすることで、データ操作（取得・登録・更新・削除など）ができる仕組みのこと。

REST API 自体は、様々な方法で実行することができますが、ここでは kintone のカスタマイズ内に記述します。

### 基本構文
kintone REST API の基本構文は下記のようになっています。

```javascript
kintone.api(pathUrl, method, params, callback, opt_errback)

// pathUrl: 特定のURL
// method: 指定した URL に対して、何をしたいかを指定する部分
// params: API に渡すパラメータをオブジェクトで指定
// callback: APIの呼び出しが成功したら実行される関数（省略可）
// opt_errback: APIの呼び出しが失敗したら実行される関数（省略可）
```
ここから kintone.api メソッドの各引数を見ていきましょう。

### URL/URI
pathUrl の部分。kintone の**何の API を扱うか**を URL で指定します。どの API を使うかは操作したいデータによって異なります。

```text
// レコードを１件操作する
https://(サブドメイン名).cybozu.com/k/v1/record.json

// レコードを複数件操作する
https://(サブドメイン名).cybozu.com/k/v1/records.json

// レコードのステータスを操作する
https://(サブドメイン名).cybozu.com/k/v1/record/status.json
```

サブドメインとは、この赤枠のところです。
![subdomain.png](https://qiita-image-store.s3.amazonaws.com/0/90265/852e9316-8209-b30d-3bc5-09884409dd71.png)

また、kintone 上でカスタマイズする場合は、**kintone.api.url() メソッド** を使うことで、 URL を短縮することができます。

```text
kintone.api('https://(サブドメイン名).cybozu.com/k/v1/records.json')
  // ↓ 短縮可能
kintone.api(kintone.api.url('/k/v1/records'))
```
### メソッド
method の部分。**何をしたいか**を記述します。
kintone では以下の4種類のメソッドを用意しています。

```text
GET（取得）
POST（登録）
PUT（更新）
DELETE（削除）
```
### パラメータ
params の部分。**具体的に扱いたいデータ**を記述します。
例えば...
レコード1件を操作しようとした時、**「どのアプリの、どのレコードか」**が分からないと何もできません。
レコード複数件を操作しようとした時、**「どのアプリで、どんな条件で」**が分からないと何もできません。

レコード1件の場合は、以下のように扱いたいデータを指定します。

```javascript
var params = {
  app: 10,
  record: 1
};
```
※ パラメータに指定できるものは、URL や メソッドによって変わるので、注意が必要です。

### コールバックとエラーバック
callback と opt_errback の部分。**「成功した時にどうするか」と「失敗した時にどうするか」**を記述します。

例えば、以下のような感じです。

```javascript
var params = {
  app: 10,
  record: 1
};

// 成功した時
function callback(resp) {
  console.log(resp);
}

// 失敗した時
function opt_errback(error) {
  console.log(error)
}

kintone.api(kintone.api.url('/k/v1/record'), 'GET', params, callback, opt_errback)
```

### 注意点
今回は、kintone アプリ内で完結するデータ操作なので、ヘッダー（認証情報）の記述は必要ありません。
外部サービスから kintone API を実行する場合は、「アクセスして情報をもらってもいいですか？」と
お伺いを立てないといけないので、そのために上記以外に認証情報の設定が必要になります。
（💡[kintone REST APIの共通仕様](https://developer.cybozu.io/hc/ja/articles/201941754)）

# 🔖 JavaScript カスタマイズ
ここからは、やりたいシナリオに沿って JavaScript で記述していきます。
[devnet](https://developer.cybozu.io/hc/ja) も確認しながら進めます。

### 『ログインユーザーが申請中のレコード件数を表示する』 の巻
まずは、**「レコード一覧画面の表示」**イベント内で、**「ログインユーザーが申請中のレコード件数を表示する」**カスタマイズ。
このカスタマイズは、細かくすると以下の3ステップに分けることができます。

- レコード一覧画面が表示されたら、ログインユーザーが申請中のレコードを取得
- 取得が成功したら、一覧画面のメニューの下側の空白部分の要素を取得して、件数を表示する
- 取得が失敗したら、エラーメッセージを表示する

それでは順番に書いていきましょう。

#### ✏️ 条件に合うレコードの取得
- レコード一覧画面の表示時イベント「'app.record.index.show'」を指定して、getRecords 関数を渡します。

```javascript
(function() {
  'use strict';

  function getRecords(event) {

  }

  kintone.events.on('app.record.index.show', getRecords);
})();
```
- getRecords 関数内に kintone REST API の GETメソッドを記述します。     
  ※ 同時にコールバックとエラーバックの関数も、宣言しておきましょう

```javascript
// (function() {
//   'use strict';

  function getRecords(event) {
    var params = {
      'app': event.appId,
      'query': '申請者 in (LOGINUSER()) and ステータス = "申請中"'
    };

    function showCount(resp) {

    }

    function handleError(error) {

    }

    kintone.api(kintone.api.url('/k/v1/records'), 'GET', params, showCount, handleError);
  }

//   kintone.events.on('app.record.index.show', getRecords);
// })();
```
> `📝Check devnet!`

> 今回は、自分が申請中のレコードを全て取得したいということで、
「[レコードの一括取得（クエリで条件を指定）](https://developer.cybozu.io/hc/ja/articles/202331474#step2)」を実行します。

> **pathUrl**
> 赤枠の URL を短縮したものを入力します。
>![getRecords.png](https://qiita-image-store.s3.amazonaws.com/0/90265/f3a7245b-b56f-f1e0-a54b-360c9695f013.png)

> **method**
> GET（取得）メソッドを指定します。

> **params**
オブジェクト内で、**app（アプリID） と query（取得するレコードの条件）**を指定します。
> ![params.png](https://qiita-image-store.s3.amazonaws.com/0/90265/1afe2704-3cee-dca2-4f96-469c435267cd.png)
> app：[レコード一覧画面の表示後](https://developer.cybozu.io/hc/ja/articles/201941964-%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E4%B8%80%E8%A6%A7%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88#step1)の event オブジェクト **appId プロパティ**を指定。

> ```javascript
> app: event.appId
> ```
> query: [クエリ記法](https://developer.cybozu.io/hc/ja/articles/202331474#step2)を元に、「申請者フィールドがログインユーザー かつ ステータスが申請中」を記述。

> ```javascript
> query:  '申請者 in (LOGINUSER()) and ステータス = "申請中"'
> ```

> **callback** と **opt_errback**
成功した時の操作を showCount 関数、失敗した時の操作を handleError 関数と定義します。
（詳細は後ほど）

#### ✏️ レコード取得が成功した時
- 取得成功時の操作を showCount 関数内に記述します。
    - countSpace 要素を作成
    - id, innerText, style.marginLeftプロパティを指定
    - レコード一覧のメニューの下側の空白部分の要素を取得して、appendChild メソッドを使って、countSpace 要素を追加

```javascript
// (function() {
//   'use strict';

//   function getRecords(event) {
//     var params = {
//       'app': event.appId,
//       'query': '申請者 in (LOGINUSER()) and ステータス = "申請中"'
//     };

    function showCount(resp) {
      var countSpace = document.createElement('countSpace');
      countSpace.id = 'space';
      countSpace.innerText = 'あなたの申請中のレコードは ' + resp.records.length + ' 件です';
      countSpace.style.marginLeft = '20px';
      kintone.app.getHeaderSpaceElement().appendChild(countSpace);
    }

//     function handleError(error) {

//     }

    kintone.api(kintone.api.url('/k/v1/records'), 'GET', params, showCount, handleError);
//  }

//   kintone.events.on('app.record.index.show', getRecords);
// })();
```

> `📝Check devnet!`

> 新規に作成した countSpace 要素のテキストに、取得したレコード数を表示します。
> kintone api()から受け取った resp オブジェクト内の records.length（レコード件数）を入れます。

> ```javascript
> countSpace.innerText = 'あなたの申請中のレコードは ' + resp.records.length + ' 件です';
> ```

> [レコード一覧のメニューの下側の空白部分の要素の取得](https://developer.cybozu.io/hc/ja/articles/201942004#step5)をします。

> ```javascript
kintone.app.getHeaderSpaceElement();
> ```

> 取得した要素に、countSpace 要素を追加します。
> ここでは、以下の appendChild メソッドを使います。

> ```javascript
// A 要素に B 要素を追加
A.appendChild(B);
> ```

> ```javascript
kintone.app.getHeaderSpaceElement().appendChild(countSpace);
> ```

#### ✏️ レコード取得が失敗した時
- 取得失敗時の操作を handleError 関数内に記述します。
    - エラー内容をアラート表示

```javascript
// (function() {
//   'use strict';

//   function getRecords(event) {
//     var params = {
//       'app': event.appId,
//       'query': '申請者 in (LOGINUSER()) and ステータス = "申請中"'
//     };

//     function showCount(resp) {
//       var countSpace = document.createElement('countSpace');
//       countSpace.id = 'space';
//       countSpace.innerText = 'あなたの申請中のレコードは ' + resp.records.length + ' 件です';
//       countSpace.style.marginLeft = '20px';
//       kintone.app.getHeaderSpaceElement().appendChild(countSpace);
//     }

    function handleError(error) {
      window.alert(error.message);
    }

    kintone.api(kintone.api.url('/k/v1/records'), 'GET', params, showCount, handleError);
//  }

//   kintone.events.on('app.record.index.show', getRecords);
// })();
```

> `📝Check!`
> error オブジェクト内の message を、エラー内容としてアラート表示します。

> ```javascript
> window.alert(error.message);
> ```

#### ✏️ JavaScript ファイルの kintone への適用
ここまで書けたら、「arrange.js」など任意のファイル名で保存して、
kintone の「JavaScript / CSSでカスタマイズ」設定ページにアップロードして保存します。
※ メモ帳をお使いの方は、「UTF-8（BOMなし）」を指定して保存してください。
（💡[JavaScriptやCSSでアプリをカスタマイズする](https://jp.cybozu.help/k/ja/user/app_settings/js_customize.html)）
![customizationScreen.png](https://qiita-image-store.s3.amazonaws.com/0/90265/5778330f-9b6b-e895-764f-3d30240799f6.png)

レコード一覧画面を表示した時に、ログインユーザー（自分）が申請中のレコード件数が正しく表示されたら成功です。
![IndexScreen.png](https://qiita-image-store.s3.amazonaws.com/0/90265/7a88e6ae-5d21-ee99-70e5-8166e6b4da2a.png)

### 『自動でプロセスを次に進める』 の巻
次に、**「レコード追加画面で保存成功後」**イベント内で、**「自動でプロセスを次に進める」**カスタマイズ。
これはレコード保存はしたけれど、プロセス管理の「申請」ボタン押し忘れるというケースを防止するための小技です。

こちらも以下の3ステップに分けて考えます。

- レコード追加画面の保存成功後に、プロセスを次（申請中）に進める
- 更新が成功したら、実行完了のアラート表示をする
- 更新が失敗したら、エラーメッセージを表示する

#### ✏️ 保存後にプロセスを次に進めて更新
ここまで書いてきたプログラムの下に書き足していきます。

- レコード追加画面の保存成功後イベント「'app.record.create.submit.success'」を指定して、putRecord 関数を渡します。

```javascript
// (function() {
//   'use strict';
//   ・・・
//     kintone.api(kintone.api.url('/k/v1/records'), 'GET', params, showCount, handleError);
//   }

  function putRecord(event) {

  }

  kintone.events.on('app.record.create.submit.success', putRecord);
// })();
```

- putRecord 関数内に kintone REST API の PUT メソッドを記述します。

```javascript
// (function() {
//   'use strict';
//   ・・・

  function putRecord(event) {
    var params = {
      app: event.appId,
      id: event.recordId,
      action: '申請',
      assignee: event.record.上長.value[0].code
    };

    function handleSuccess() {

    }

    function handleError(error) {

    }

    kintone.api(kintone.api.url('/k/v1/record/status'), 'PUT', params, handleSuccess, handleError);
  }

//   kintone.events.on('app.record.create.submit.success', putRecord);
// })();
```
> `📝Check devnet!`

> 今回は、プロセスを次に進めたいということで、
「[1つのレコードのステータスの更新する](https://developer.cybozu.io/hc/ja/articles/204791550#anchor_changeRecordStatus)」を実行します。

> **pathUrl**
> 赤枠の URL を短縮したものを入れます。
> <img width="958" alt="putRecord.png" src="https://qiita-image-store.s3.amazonaws.com/0/90265/a1514316-1af9-3f64-6e6e-fa93f35a6a1c.png">

> **method**
> PUT（更新）メソッドを指定します。

> **params**
オブジェクト内で、**app（アプリID） と id（レコードID） と action(実行するアクションの名前) と assignee(ユーザー名)**を指定します。
> <img width="1172" alt="params_putRecord.png" src="https://qiita-image-store.s3.amazonaws.com/0/90265/8a76d802-6118-be47-4e07-51db2adf2e54.png">

> app: [レコード追加画面の保存成功後](https://developer.cybozu.io/hc/ja/articles/201941984#success)の event オブジェクト **appId プロパティ**を指定。

> ```javascript
> app: event.appId
> ```

> id: 上記同様に、event オブジェクト **recordId プロパティ**を指定。

> ```javascript
> id: event.recordId
> ```

> action: 実行するアクション名を指定。今回は「申請」。

> ```javascript
> action: '申請'
> ```

> assignee: 申請の承認者（ユーザーのログイン名）を指定。

> ```javascript
> assignee: event.record.上長.value[0].code
> ```

> **callback** と **opt_errback**
成功した時の操作を handleSuccess 関数、失敗した時の操作を handleError 関数と定義します。
（詳細は後ほど）

#### ✏️ レコード更新が成功した時

- 更新成功時の操作を handleSuccess 関数内に記述します。今回は特に何もしないので、空のままにしておきます。

```javascript
// (function() {
//   'use strict';
//   ・・・
//   function putRecord(event) {
//     var params = {
//       app: event.appId,
//       id: event.recordId,
//       action: '申請',
//       assignee: event.record.上長.value[0].code
//     };

    function handleSuccess() {

    }

//     function handleError(error) {

//     }

    kintone.api(kintone.api.url('/k/v1/record/status'), 'PUT', params, handleSuccess, handleError);
//   }

//   kintone.events.on('app.record.create.submit.success', putRecord);
// })();
```

#### ✏️ レコード更新が失敗した時

- 更新失敗時の操作を handleError 関数内に記述します。
    - エラー内容をアラート表示

```javascript
// (function() {
//   'use strict';
//   ・・・
//   function putRecord(event) {
//     var params = {
//       app: event.appId,
//       id: event.recordId,
//       action: '申請',
//       assignee: event.record.上長.value[0].code
//     };

//     function handleSuccess() {

//     }

    function handleError(error) {
      window.alert('エラーが発生しました。手動で申請ボタンをクリックしてください。\nエラー内容：' + error.message);
    }

    kintone.api(kintone.api.url('/k/v1/record/status'), 'PUT', params, handleSuccess, handleError);
//   }

//   kintone.events.on('app.record.create.submit.success', putRecord);
// })();
```

#### ✏️ JavaScript ファイルの kintone への適用
arrange.js ファイルを上書き保存して、先程と同様に設定画面よりアップロードして保存します。
（💡[JavaScriptやCSSでアプリをカスタマイズする](https://jp.cybozu.help/k/ja/user/app_settings/js_customize.html)）

レコード追加画面で保存ボタンを押した後に、ステータスが "申請中" に進んでいたら成功です。
※ ブラウザの挙動によって、画面の更新が必要になる場合もあります。
![CreateSubmitScreen.gif](https://qiita-image-store.s3.amazonaws.com/0/90265/1743dda0-4c75-8ecb-fa2f-16de5a2e674b.gif)

# 🔖 プログラム全文
上記の 1. と 2. をまとめて書いた完成形のプログラムはこちらです。

```javascript:arrange.js
(function() {
  'use strict';

  // 一覧画面の表示時にログインユーザーの申請中のレコード件数を表示する
  function getRecords(event) {
    // パラメータ定義
    var params = {
      app: event.appId,
      query: '申請者 in (LOGINUSER()) and ステータス = "申請中"'
    };

    // 成功時の関数
    function showCount(resp) {
      var countSpace = document.createElement('countSpace');
      countSpace.id = 'space';
      countSpace.innerText = 'あなたの申請中のレコードは ' + resp.records.length + ' 件です';
      countSpace.style.marginLeft = '20px';
      kintone.app.getHeaderSpaceElement().appendChild(countSpace);
    }

    // 失敗時の関数
    function handleError(error) {
      window.alert(error.message);
    }

    // GETメソッドの実行
    kintone.api(kintone.api.url('/k/v1/records'), 'GET', params, showCount, handleError);
  }

  // 一覧画面の表示イベントで getRecords 関数を実行
  kintone.events.on('app.record.index.show', getRecords);

  // レコード追加画面の保存成功後にプロセスを次に回す
  function putRecord(event) {
    // パラメータ定義
    var params = {
      app: event.appId,
      id: event.recordId,
      action: '申請',
      assignee: event.record.上長.value[0].code
    };

    // 成功時の関数
    function handleSuccess() {

    }

    // 失敗時の関数
    function handleError(error) {
      window.alert('エラーが発生しました。手動で申請ボタンをクリックしてください。\nエラー内容：' + error.message);
    }

    // PUTメソッドの実行
    kintone.api(kintone.api.url('/k/v1/record/status'), 'PUT', params, handleSuccess, handleError);
  }

  // レコード追加画面の保存成功後イベントで putRecord 関数を実行
  kintone.events.on('app.record.create.submit.success', putRecord);
})();
```

# 🔖 備考
『自動でプロセスを次に進める』 の巻で、以下のように書きました。
> ※ ブラウザの挙動によって、画面の更新をする必要がある場合もあります。

裏側ではレコードの更新が成功しているのですが、ユーザー側で画面の更新作業が発生するのは親切ではありません。
これを解決するには、JavaScript の Promise という順番処理の書き方を学ぶ必要があります。
詳細については、ここでは割愛しますが、気になる方は以下の記事をご覧ください。
💡[kintoneにおけるPromiseの書き方の基本](https://developer.cybozu.io/hc/ja/articles/360023047852)
💡[レコード保存後に自動でステータス更新！](https://developer.cybozu.io/hc/ja/articles/214639003)

# 🔖 おわりに
今回は、以下のメソッドやイベントを使いました。

- kintone REST API
    - [**GET** メソッド](https://developer.cybozu.io/hc/ja/articles/202331474)
    - [**PUT** メソッド](https://developer.cybozu.io/hc/ja/articles/201941784)
- kintone JavaScript API
    - [**'app.record.index.show'** イベント](https://developer.cybozu.io/hc/ja/articles/201941964#step1)
    - [**'app.record.create.submit.success'** イベント](https://developer.cybozu.io/hc/ja/articles/201941984#success)

kintone には他にも利用できるイベント、それぞれで可能な操作、
データ操作をリクエストするためのメソッドがあります。
**"やりたいこと" を処理レベルに分解すると、どんなイベントやメソッドを使って、
どの順番でコーディングしていけばいいかも見えてきます。**
ぜひ他の KAIZEN にもチャンレンジしてみてください📣