【ゴールデンウィーク特別企画 】コーディング vs ノンコーディング kintone カスタマイズ対決 コード紹介

# はじめに

2020年5月4日に**[【ゴールデンウィーク特別企画 】コーディング vs ノンコーディング kintone カスタマイズ対決](https://r3it.doorkeeper.jp/events/106102)** を実施しました！
![106102_normal_1587439901_GW_SpecialEvent_Cover_900x300.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153051/aad19a2b-62d7-0399-b7e6-f1db1ba18c96.png)
出題されたお題に対して **即興** でJavaScriptカスタマイズ、gusuku Customine[^1]を使ったノーコードカスタマイズしてわいわい楽しむお祭りイベントです！ノンコーディングではR3 instituteの築山さんが、コーディングでは僕が行いました。
[^1]: [gusuku Customine](https://customine.gusuku.io/ja/): ノーコードでkintoneカスタマイズができるサービス


イベントでは必死にコード書いていたのですが、全５問出題された中で時間内にちゃんとできたカスタマイズは最初の１つだけという・・・
（**<font color="blue">制限時間10分</font>** という短い時間でのコーディングだったからですよっっ！！）

<br/>

視聴者のみなさんも**「ちゃんと時間あったらどんなコードになるの？」** と疑問に思われたと思います。

<br/><br/>

> わかる！その気持ちはとてもわかります！！

<br/><br/>

・・・

<br/><br/>

ということで、
改めて <font color="red">**お題とそれに対して自分が思う完成コード**</font> を紹介します！！

※ こうやってあとから記事書いてフォローするまでが本イベントなのです！

ノンコーディング側の記事は一足先に築山さんが公開してくださっています！！
https://support.gusuku.io/ja-JP/support/solutions/articles/36000225279

# 環境紹介

その前に、イベント中に僕が使っていた環境について軽く触れておきます。
早くお題のコードを見たい方は [お題1](#お題1) にスキップしてください！

- **Google Chrome**
    - ブラウザ
- **Visual Studio Code (VSCode)**
    - コーディングエディタ
- **VSCode kintone Extension**
    - VSCode上でkintoneカスタマイズをしやすくする拡張
    -  （下で紹介します）
- **VSCode Live Server** (当日は使えなかったやつ)
    - ローカルファイルをクラウドサービスから直接参照できるやつ
    - （下で紹介します）
- **kintone-customise-uploader**
    - kintoneカスタマイズファイルをコマンドラインからアップロードできるやつ
    - （下で紹介します）

## VSCode kintone Extension

VSCode上でkintoneカスタマイズをしやすくするVSCode用の拡張ツールです。細かい機能は以下を見てもらえればと思いますが、要は **kintone JavaScript APIのメソッドがエディタの予測変換で出てくる便利ツール** と僕は認識しています。

> ▼ kintone-extension
> https://marketplace.visualstudio.com/items?itemName=kintone-extension.kintone-extension

例えば 「kintoneのスペースフィールドの要素を取得するメソッド」 の場合、
正解は `kintone.app.record.getSpaceElement(spaceid)` なのですが、
`kintone.app.record.getSpace"Field"Element(spaceid)`なのか迷うときがあります。

devnet を見ればわかるのですがいちいち検索するのもめんどくさいので、そういうときにこの拡張は便利です！！
<img src="https://docs.google.com/drawings/d/e/2PACX-1vSxu7IOsvBLX3i20GTWIaDwE3whRZutlnRaOV1mgPhQJLb_I_z1ETyx-biPK396BGFmOFDvM7gd639y/pub?w=904&amp;h=373">
いちいち調べなくても選べばいいので楽ちん！

あと、あるあるなのは `kintone.app.〇〇` なのか `kintone.app.record.〇〇` なのかわからなくなるとき。このときも予測変換でわかるので悩む必要はなし！！

### インストール方法

普通に検索にでてくるので選んで有効にしたらそれでもう適用されます！
<img src="https://docs.google.com/drawings/d/e/2PACX-1vSrJjI6hRfLTIAvXdWxN6NZSnbDgB5VMGhbPp_y3wA__fW9bvrH8s0-PIOVYv3g90a7fJN7jthWBRUe/pub?w=847&amp;h=597">
ぜひぜひ使ってみてください！

## VSCode Live Server

これもVSCodeの拡張なのでインストール方法は↑と同じです。
ローカルファイル（PCのフォルダ上に作成したファイル）を直接クラウドから読み取れるようにするツールです。

kintoneの場合、JS/CSSファイルのアップロードは作業が多く非常に手間なので（特にコーディング中の動作チェック時）、
これを使うとエディタ上で保存すると即kintoneに反映されるのでとても便利です。

*※ あくまでファイルはローカル上にあるので、Live Server機能をOFFにするとアクセスできなくなってエラーがでます。*
*※ 動作確認が終わったら最後はちゃんとkintoneへJS/CSSファイルをアップロードしてください。*

## kintone-customise-uploader

いつもはLive Server機能を使っているのですが、トラブルでLive Serverが使えない場合はこちらを使っています。
コマンドでJavaScript/CSSファイルを直接kintoneにアップロードできるので、アップロードの操作がとても楽になります。

特に`--watch` を使うことでエディタ上で保存したら即アップロードされるので、動作確認がすぐにできて非常に便利です！
※ そしてアップロードしているのでLive Serverのようにリンクエラーにもならないです！

詳しくはdevnetの記事を読んでみてください。

> ▼ customize-uploaderでカスタマイズ用のJS/CSSファイルをコマンドラインからアップロードしよう！
> https://developer.cybozu.io/hc/ja/articles/360015889812

と、ここまでがイベント中に使っていた環境の紹介です！
それでは本題へ移ります〜

# 事前条件

- カスタマイズは <font color="red">**人事労務パック内のアプリ**</font> に対して行います
- フィールドコードは直書きします （ホントは最初に変数化したほうがいいです）
- ES6以降の書き方でコーディングします （そのままではIEでは動きません）
- 日付処理には moment.js ライブラリを利用します

# お題1

<img width="1086" alt="スクリーンショット 2020-05-12 11.00.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153051/9aac09f7-7aff-64b1-6033-5e16099633e9.png">
入力補助系のカスタマイズですね。かなりよくあるカスタマイズだと思います。
ただ、条件の組み合わせが結構複雑だったりして、コードを書いて動作チェックしてるときに **「あれ？このときはどうなる？あ、考慮してなかった...」** となりがちです。

## 考え方

今回は制限時間もないのでロジックの考え方の部分も書いてみようと思います。
*※ あくまでBB個人の考え方なので鵜呑みにはせず参考程度にしてもらえると嬉しいです*

**・在籍状況を在籍中にしてはいけない**

まず問題文自体 ~~ケチつける~~ 整理します

> 入社日が空なら、そもそも入力時に「在籍中」に変更できない

と考えるべきか、

> 入社日が空なら、「在籍中」の状態でレコード保存ができない

と考えるべきか。

前者の方がユーザー的にはわかりやすいはずですが、「ドロップダウンのある項目だけ選べないようにする」のは無理なんですよね・・（→ 選ぶこと自体はできてしまうんですよね・・）
だからといって後者だと、最後にレコード保存するまでエラーなのかわからないのでUI的にもう少し欲しいところです。

ということで、今回は

**入社日が空で在籍状況を「在籍中」に変更するとエラーが表示される**
**入社日が空で在籍状況が「在籍中」ならレコード保存ができない**

と2つのロジックを組んでみました。
*※ YouTube Liveのコメントに「一覧画面の制御は？」とあったのでそこも考慮してみました！*

## コード

```javascript
(() => {
  'use strict';

  const events = [
    'app.record.index.edit.submit',
    'app.record.create.submit',
    'app.record.create.change.在籍状況',
    'app.record.create.change.入社日 ',
    'app.record.edit.submit',
    'app.record.edit.change.在籍状況',
    'app.record.create.change.入社日',
  ];
  kintone.events.on(events, event => {
    const entryDate = event.record.入社日.value;
    const status = event.record.在籍状況.value;
    if (!entryDate && status === '在籍中') {
      event.record.在籍状況.error = '入社日が指定されていません。';
    } else {
      event.record.在籍状況.error = null;
    }
    return event;
  });
})();
```

入社日と在籍状況のchangeイベントも入れることで、

- 入社日が空の状態で在籍状況を「在籍中」にすると、エラーメッセージが表示される。
- 入社日に値が入った状態で在籍状況を「在籍中」にして、その後入社日を空に変更するとエラーメッセージが表示される
- 入社日が空の状態で在籍状況を「在籍中」にして、その後在籍状況を他の項目に変更するとエラーメッセージが消える
- 入社日が空の状態で在籍状況を「在籍中」にして、その後入社日に値が入れるとエラーメッセージが消える

↑まぁいくつかエラーを出したいとき/消したいときのパターンがある、という説明です。

ちなみに、条件分岐の部分は [三項演算子](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) を使うと１行で表せたりもします。

```javascript
event.record.在籍状況.error = (!entryDate && status === '在籍中') ? '入社日が指定されていません。' : null;
```

まぁわかりにくいのですが、一行で書けるとなんか **コード書けてる感** あって好きですｗ

# お題2

<img width="1086" alt="スクリーンショット 2020-05-12 11.00.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153051/03a06f4f-25f8-57ce-246f-477a6bb32af6.png">
条件多すぎ！！
まぁでも最初からこんな複雑な条件はないにしても、あとからあとから条件が追加されていって最終的にはこんな感じになることはそこそこあるのではと思います。まさにスパゲティコード[^2]の要因！
[^2]: 皿に盛られたスパゲティのようにロジックが絡み合ってコード作成者以外解読ができないようなコードのこと

## 考え方

まずは条件の整理をします。条件となるデータを羅列すると、

- 在籍状況
- 入社日
- 生年月日
- 勤労学生
- 配偶者の有無

の5つが条件に使うデータになります。これらにそれぞれ、

- 在籍状況 <font color="blue">**在籍中がどうか**</font>
- 入社日 <font color="blue">**現在より5年前かどうか**</font>
- 生年月日 <font color="blue">**現在より25年前かどうか**</font>
- 勤労学生 <font color="blue">**チェックが付いているかどうか**</font>
- 配偶者の有無 <font color="blue">**有になっているかどうか**</font>

という条件がつきます。あとは脳内でフローチャートを描けば、、、という話ですが、
フローチャートも書いてみました。
<img src="https://docs.google.com/drawings/d/e/2PACX-1vQzsQ6Qtba33xfCATNPIV7azYEzYS1ExwlM2MYK6v3kqJK50a6Am8FLumjAKvRC1p-mBXLDQ_SaJbgT/pub?w=929&amp;h=479">
最初は一番左を考えて、その後分岐を少なくするためにだんだん右へまとめていくイメージです。

## コード

```javascript
(() => {
  'use strict';

  const events = [
    'app.record.detail.show'
  ];
  kintone.events.on(events, event => {
    const status = event.record.在籍状況.value;
    const seniority = moment().diff(moment(event.record.入社日.value), 'year');
    const old = moment().diff(moment(event.record.生年月日.value), 'year');
    const student = event.record.勤労学生.value;
    const partner = event.record.配偶者の有無.value;

    if (partner === '有') return;

    if (
      (status === '在籍中' && seniority >= 5) ||
      (old <= 25 && student.length > 0)
    ) {
      kintone.app.record.getFieldElement('従業員番号').style.backgroundColor = '#FFBF00';
    }
  });
})();
```

今回のキモは問題文の最後に書かれている

> ただし配偶者「有」の場合は、色を変えない

ですね。配偶者有りのときは問答無用で **色は変えない=何も処理をしない** ということなので、
コードの上の方で条件として書けば後が楽だったりします。

# お題3

<img width="1086" alt="スクリーンショット 2020-05-12 11.00.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153051/7c84ff30-b975-bfe9-ad00-be575af6f82d.png">
関連レコードの値を条件にしたいことは結構あると思います。ただ、関連レコードの性質はちょっと特殊なのでJavaScriptでも気をつけないといけない部分です。

## 考え方

関連レコードは画面上にはありますが、データとしては持っていない（= ただ表示しているだけ）なので、イベントハンドラー時のeventオブジェクトの中には入っていません。
→ あくまでデータは別のレコードに保存されていて、それをビュー表示している感じですね

ただ、今回の条件は関連レコードの中にある値なので、値は欲しいです。
画面上にはないkintoneのデータを扱いたい、、、
<br/>
ということで **kintone REST API** の出番です！


関連レコードに表示されているレコードは自アプリの他のレコードなので、自アプリからレコードデータをREST APIで取得します。
そして取得したデータを使って条件分岐させればOKです！

## コード

```javascript
(() => {
  'use strict';

  // 自アプリ（人事評定管理）のレコードを複数件取得する関数
  const getRecords = event => {
    const thisYear = Number(moment().format('YYYY'));
    const params = {
      app: event.appId,
      query: `従業員番号 = "${event.record.従業員番号.value}" and 評価年度 in ("${thisYear - 1}年度")`
    };
    return kintone.api(kintone.api.url('/k/v1/records'), 'GET', params);
  };

  const events = [
    'app.record.create.submit',
    'app.record.edit.submit'
  ];
  kintone.events.on(events, async event => {
    const resp = await getRecords(event).catch(err => {
      alert('データの取得に失敗しました。');
      return false;
    });

    if (resp.records.length === 0) return;
    if (resp.records.length > 1 && !confirm('前年の評価レコードが2件以上あります。レコードを保存してよろしいでしょうか？')) {
      // 保存処理をキャンセル
      return false;
    }

    const thisYearIncreasePay = event.record.昇給額.value;
    const lastYearIncreasePay = resp.records[0].昇給額.value;
    if (
      (Number(thisYearIncreasePay) < Number(lastYearIncreasePay)) &&
      !confirm('昨年より昇給額が下がっていますがよろしいでしょうか？')
    ) {
      // 保存処理をキャンセル
      return false;
    }
  });
})();

```

今回のコードの注目ポイントはレコード取得部分のクエリです。

```
query: `従業員番号 = "${event.record.従業員番号.value}" and 評価年度 in ("${thisYear - 1}年度")
```

クエリを書かない場合、全レコード（上限100件）を取得しますが、今回は **同じ従業員名の去年のレコード** だけで良いので、それをクエリに追加しています。去年のレコードか2年前のレコードか判断は `評価年度フィールド（ドロップダウン）` でできるのでその条件も追加しています。

あとは取得したデータの数を見て

- ０件 → 今年からの人なので何もせず普通に登録できる。
- ２件以上 → 去年の評価が２つ以上あることになるのでアラートを出す。
- １件 → 条件に当てはまるか確認して、当てはまったらアラートを出す。当てはまらなかったら何もせず普通に登録できる。

みたいな分岐をしています。

# お題４

<img width="1086" alt="スクリーンショット 2020-05-12 11.00.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153051/285b027c-dd30-06d8-b171-c9d6ae1db2d0.png">
また条件が複雑です。しかも今回は関連レコード２つ使っています。しかもそれぞれ別アプリに紐付いています。そもそもアプリの構成自体も確認しないといけないですね。これを即興でやれという運営恐ろしい・・・www

## 考え方

関連レコードが別アプリかつ２つということは２つのアプリからそれぞれREST APIでデータを取得する必要があります。ただ、取得してしまえばあとは条件分岐するだけなので今思えばそこまで難しくはありませんね。

1. 配属・異動アプリからレコードデータを取得  
   → 従業員番号が同じレコード、かつ実施日の降順(新しい順に取得する)
2. 人事評定アプリからレコードデータを取得  
   → 従業員番号が同じレコード、かつ評価年度の降順(新しい順に取得する)
3. 1, 2で取得したデータを使って条件分岐

## コード

```javascript
(() => {
  'use strict';

  const ASSIGN_APP_ID = 126;
  const RATING_APP_ID = 125;

  // アプリのレコードを複数件取得する関数
  const getRecords = (app, query) => {
    const params = {
      app,
      query
    };
    return kintone.api(kintone.api.url('/k/v1/records'), 'GET', params);
  };

  const events = [
    'app.record.detail.show',
  ];
  kintone.events.on(events, async event => {
    const personalNumber = event.record.従業員番号.value;
    const assignQuery = `従業員番号 = "${personalNumber}" order by 実施日 desc`;
    const ratingQuery = `従業員番号 = "${personalNumber}" order by 評価年度 desc`;

    const assignRecordData = await getRecords(ASSIGN_APP_ID, assignQuery).catch(err => console.log(err));
    const ratingRecordData = await getRecords(RATING_APP_ID, ratingQuery).catch(err => console.log(err));

    // どちらかのレコードデータが空なら何もしない
    if (!assignRecordData.records.length || !ratingRecordData.records.length) return;

    const recentAssignDate = assignRecordData.records[0].実施日.value;
    const recentRatingVal = ratingRecordData.records[0].評価.value;

    if (
      (moment().diff(recentAssignDate, 'year') >= 3) &&
      (recentRatingVal !== 'S' && recentRatingVal !== 'A')
    ) {
      kintone.app.record.getFieldElement('氏名').style.backgroundColor = '#e85d54';
    }
  });
})();

```

今回は~~めんどくさい~~簡略化するために2つのアプリIDは直書きしてますが、
**フォーム設定の取得API** を使って関連レコードの設定からアプリIDを確認すれば、直書きしなくても良くなります！

# お題5（参加者提供）

<img width="1086" alt="スクリーンショット 2020-05-12 11.01.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153051/db42fffb-e23b-30f4-e17a-b23d0d445932.png">
自由！！

## 考え方

自由だったので本当に自由に決めました。イベント当時とは一部条件を変えています

スペースフィールドに配置したボタンのクリックで

- 正社員、契約社員、派遣社員なら20日、それ以外は10日
- 入社日が今年なら「上の条件の日数×12月末までの残日数÷365」、去年以前なら20日
- 役職が「本部長、部長、課長」なら5日固定

の条件で有給日数をアラート表示させます。
優先度は下になるほど高いです （=正社員で部長なら20日ではなく5日）
※ 役職持ちの日数が少ないのはネタですw 他意はないですw

## コード

```javascript
(() => {
  'use strict';

  const events = [
    'app.record.create.show',
    'app.record.edit.show',
    'app.record.detail.show'
  ];
  kintone.events.on(events, async event => {
    // ボタンの増殖を防ぐ
    if (document.getElementById('check-dayoff')) return;

    const btn = document.createElement('button');
    btn.id = 'check-dayoff';
    const space = kintone.app.record.getSpaceElement('スペース');
    space.appendChild(btn);

    btn.onclick = () => {
      const data = kintone.app.record.get();
      const position = data.record.役職.value;
      const entryDate = data.record.入社日.value;
      const status = data.record.雇用形態.value;
      const dayleft = moment().endOf('year').diff(entryDate, 'days');

      let statusCheck = false;
      let message = '20日だよ！';

      if (status !== '正社員' && status !== '契約社員' && status !== '派遣社員') {
        statusCheck = true;
        message = '10日だよ！';
      }
      if (moment().diff(entryDate, 'year') === 0) {
        message = statusCheck ? `${10 * dayleft / 365}日だよ！` : `${20 * dayleft / 365}日だよ！`;
      }
      if (position === '本部長' || position === '部長' || position === '課長') {
        message = '5日だよ！';
      }
      alert(message);
    };
  });
})();

```

**入社日が今年なら** の条件を今年の残日数の日割りみたいにしてみました。

```javascript
const dayleft = moment().endOf('year').diff(entryDate, 'days');
```

`moment().endOf('year')` これで今年年末の日付がでます。
`.diff(entryDate, 'days')`で入社日との差分の日数が計算できます。それをつなげています。
簡単！ライブラリ万歳！

あとは上でちらっと触れた三項演算子も使ってみました。

```javascript
message = statusCheck ? `${10 * dayleft / 365}日だよ！` : `${20 * dayleft / 365}日だよ！`;
```
ifの中にifになりなんか見栄えが悪いのでつかってみました。
statusCheckがtrueなら手前の`${10 * dayleft / 365}日だよ！`, そうでない（=false）なら`${20 * dayleft / 365}日だよ！`って感じです。

# おわりに

じ、時間があればちゃんと作れるんじゃい！！ってことを言いたくて記事化しました。
ただ、実用的かどうかはわかりませんｗ あくまでイベントのノリで丁寧に作ってみたということでお願いします。（例えばモバイル対応とかしてないですし、エラー処理も甘々です）

初めてのライブコーディングでしたが、とても楽しかったです！！
視聴くださったみなさま、ありがとうございました！
途中コーディングを助けてくださった方々もありがとうございました！！

また何か機会があればやりたいですね〜（もう少しコーディング時間の余裕は欲しいところですがw）

## 余談

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">何があったかお話すると、YouTube Liveで僕と築山さんの画面だけのやつも作っておいてそれぞれ単独の画面も見れるようにするはずだったのですが、直前に「BBの顔がLiveに映るとガイドライン違反となり強制終了される」という事件が数回勃発しましたw<br><br>人の顔がガイドライン違反とはひどい話です🤔</p>&mdash; BB (@RyxBB8) <a href="https://twitter.com/RyxBB8/status/1257258701485826048?ref_src=twsrc%5Etfw">May 4, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

単独の画面がお見せできずに少々見づらくなってしまっていたと思います。
僕の **著作権違反顔** のせいでご迷惑をおかけしたことをお詫びします。 m(_ _)m

それでは！ ≧(+･` ཀ･´)≦