20
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

kintoneAdvent Calendar 2020

Day 9

フィールド値変更イベントで気を付けること

Last updated at Posted at 2020-12-08

今年もあっという間にアドベントカレンダーの季節がやってまいりましたね。
Qiitaに記事を投稿したことがなかったのですが、自身のアウトプットの練習備忘録を兼ねて
何か記事を書けたらなということで、アドベントカレンダーに参加をしてみました。

2022年04月05日 いただいたコメントを記事に反映いたしました。遅くなり申し訳ございません。

はじめに

  • Qiita初投稿なのでツッコミどころが多いと思う
  • 上記の通り、備忘録でもあるということ
  • kintoneカスタマイズ初心者に向けた記事かも?
  • たぶん既出のネタ

と、記事を書いている本人も思っていますので、ある程度手加減していただけると幸いです。

フィールド値変更イベントとは

基本的なkintoneカスタマイズについてお話をしておくと、
サイボウズ様が用意した kintone JavaScript API を利用して、特定のイベントが動作した際に
カスタマイズ処理を動作させるのが、基本的なカスタマイズの方法だと思います。

その中でも今回は、

レコード追加画面のフィールド値変更時イベント
https://developer.cybozu.io/hc/ja/articles/201941984#step3

レコード編集画面のフィールド値変更時イベント
https://developer.cybozu.io/hc/ja/articles/202166270#step3

についての記事になります。

ざっくりと説明すると、特定のフィールドの値が変わった際に、カスタマイズを動かしたい時に使うイベントです。

フィールド値変更イベントの使い方

例えば、ドロップダウンフィールドと文字列1行フィールドをアプリに配置し、前述のフィールド値変更時イベントのカスタマイズを記述します。
スクリーンショット 2020-12-08 1.11.56.png

const watch = 'ドロップダウン';
const changeEvent = [
  `app.record.create.change.${watch}`,
  `app.record.edit.change.${watch}`
];

kintone.events.on(changeEvent, event => {
  event.record['文字列'].value = 'ヨシ!'
  return event;
});

そして、レコード追加(編集)画面でドロップダウンフィールドを変更すると・・
スクリーンショット 2020-12-08 1.25.12.png

文字列フィールドに、カスタマイズで文字が挿入されました。
基本的な使い方としてはこんな感じではないでしょうか。

それでは本題に入ります。

本題

基本的な使い方については先ほど説明した通りですが、ある程度カスタマイズに慣れてくると、もっと応用することもあるかと思います。

例えば、ルックアップの 「ほかのフィールドのコピー」でフィールドの値が変更された時に、フィールド値変更イベントを動作させる。 なんてことも可能ですよね。
ただし、この場合に注意しなければいけないことがある。というのが本題です。

実は、ルックアップの 「ほかのフィールドのコピー」でフィールド値変更イベントが動作した場合、ハンドラーの引数に渡されるオブジェクトの中身は、ルックアップのフィールドコピーが完了していない ものが格納されています。

例えば、

  • ルックアップフィールドを用意し「ほかのフィールドのコピー」を設定
  • 「ほかのフィールドのコピー」で設定したフィールドに対して、フィールド値変更イベントのカスタマイズを用意

本題1.png

const watch = '担当者名';
const changeEvent = [
  `app.record.create.change.${watch}`,
  `app.record.edit.change.${watch}`
];

kintone.events.on(changeEvent, event => {
  console.log({ event })
  return event;
});

上記のカスタマイズが実行されると、コンソールに event が出力されます。
※ChromeであればF12キーを押すことで、コンソールが表示できます。

では、ルックアップの取得を行い、カスタマイズを動作させてみましょう。

image.png
image.png

アプリの画面上では、すべてのグレーアウトしているフィールドに値が入っていますが、
イベント発火時にコンソールに出力されたオブジェクトを見ると、ルックアップの「ほかのフィールドのコピー」が
中途半端にしか動いていない(途中である) ことがわかると思います。

画像の例で言うと、ルックアップの取得により担当者名フィールドに値が入ったタイミングで、カスタマイズが動いているようで、その時点では [郵便番号] ~ [住所] までのフィールドには、値のコピーが実施されていない ということです。

これを知らずに、以下のようなカスタマイズを作ったとします。

  • イベント発火は [担当者名]フィールドフィールド値変更イベント
  • [日報]フィールド に [担当者名] と [住所] の値を入れる
const contact = '担当者名';
const address = '住所';
const changeEvent = [
  `app.record.create.change.${contact}`,
  `app.record.edit.change.${contact}`
];

kintone.events.on(changeEvent, event => {
  console.log({ event })
  event.record['日報'].value = ` 担当者名:${event.record[contact].value} 住所:${event.record[address].value}`;
  return event;
});

これを実行すると、前述の説明の通り、[住所]に値がコピーされる前に動作するため、正しく取得することができません。

本題2.png

じゃあどうするの?

どうやら、このルックアップの値コピーについては、画面上の上から順番に値のコピーを行っている(ような気がする)。
なので、カスタマイズソースを修正せずに対応する場合は [担当者名] を下に移動すること。

本題3.png

なので、[担当者名] を下に移動させると・・

image.png
image.png
引数に渡されたオブジェクトを見ると、ルックアップの**「ほかのフィールドのコピー」がすべて動いた後**に、
イベントが発火していることがわかります。(担当者名が一番最後にコピーされるはずなので。)
また、先ほどは "undefined" となってしまった [日報] に記載の住所、こちらも正しい値が格納できています。

これでほぼ解決かなと思いますが、

[担当者] が当初の配置場所よりも、下のほうに配置することになった

のが少し気になります。

この場合は、フィールドの配置は以前のままで、イベント発火のきっかけとなるフィールドを
[担当者名] → [住所]
に変更してあげることで解決するのですが、これはこれで
住所が入力されてないレコードをルックアップしたとき動かないじゃん。ということになりますので、どちらかの調整が必要
ということですね。

ここまでをまとめると

  • フィールド値変更イベントの引数に渡されるオブジェクトは、フィールド構成(配置)とイベント発火方法によっては、値が空になっているケースがある
    (ルックアップなどで値がコピーされてイベントが発火した場合)
  • 回避するには、イベント発火のきっかけとなるフィールドを下に持ってくる
  • もしくは、イベント発火のきっかけとなるフィールドを変更する

と、いうことでしょうか。

フィールド値変更イベントで、もう少し難しいこと(非同期処理)をやる場合は

恐らく、みんなこういうコーディングをしているんじゃないでしょうか。(思い込み)

const contact = '担当者名';
const changeEvent = [
  `app.record.create.change.${contact}`,
  `app.record.edit.change.${contact}`
];

kintone.events.on(changeEvent, event => {
  setReport(); // 非同期の関数を呼び出す
  console.log({ event });
});

const setReport = async () => {
  
  /* なにかしらの非同期処理 */

  const event = kintone.app.record.get();
  event.record['日報'].value = "絶対ヨシ!";
  kintone.app.record.set(event);
}

フィールド値変更イベントでは、Promise が使えないので、ハンドラーでは

return event;

を書かずに、関数を呼び出し、最終的に自分で kintone.app.record.set() を使ってレコードの値を変更する。

kintone.app.record.set(event);

確かにこれで動きます。
動きますが、上記例で言うと、setReport関数がとても速く処理された場合、以下のようなエラーが出ます。
image (5).png

エラーメッセージの通り、

イベントハンドラの処理中は、"kintone.app.record.set()" は使えないよ。
※kintone.app.record.get()も同じ

とのこと。

要はこういう時にエラーが起きるんですね。
イベントハンドラが終了する前に、setReport関数の kintone.app.record.set(event) が実行されている。
setReport関数は 非同期関数(async)なので、イベントハンドラで呼び出した際に、setReport関数の終了を待たずに
処理が進んでいますが、イベントハンドラの終了の前に、setReport関数内の kintone.app.record.set(event) が
呼ばれると、上記のようなエラーが発生するようです。

image.png

ん?これって

フィールド値変更イベントは Promise に対応していない。
 ↓
じゃあ、イベントハンドラで非同期関数呼び出して、イベントハンドラでは何も return しなきゃいいんじゃない?
 ↓
概ねうまくいくけど、非同期関数が早く終わり過ぎると、エラーが出る。

・・・

フィールド値変更イベントが Promise に対応するアップデートを待つ
※サイボウズ様、よろしくお願いします。

2022年04月05日 更新
次項に解決できるソースコードを記載。

2022年04月05日追記

※すみません、いただいたコメントを記事に反映することを失念していました。遅くなり申し訳ございません。

@wv-sumichan さんからいただいたコメントの通り
以下のように呼び出した関数内で「await」記述することで、呼び出し元のイベントハンドラの終了を待ってから
後続の処理を実行させることができるようです。

const contact = '担当者名';
const changeEvent = [
  `app.record.create.change.${contact}`,
  `app.record.edit.change.${contact}`
];

kintone.events.on(changeEvent, event => {
  setReport(); // 非同期の関数を呼び出す
  console.log({ event });
  return event;
});

const setReport = async () => {

  /* なにかしらの非同期処理 */
  await Promise.resolve(); // 既に完了している非同期処理の完了を待つ

  const event = kintone.app.record.get();
  event.record['日報'].value = "絶対ヨシ!";
  kintone.app.record.set(event);
}

または

const setReport = () => {
  return new Promise((res, rej) => {
    res();
  }).then(() => {
    const event = kintone.app.record.get();
    event.record['日報'].value = "絶対ヨシ!";
    kintone.app.record.set(event);
  });
}

処理の順番としては、

  1. イベントハンドラ内の setReport() が実行される。
  2. setReport関数内の await Promise.resolve() が実行される。
  3. イベントハンドラ内の console.log({ event }) が実行される。
  4. イベントハンドラが終了する。
  5. setReport関数内の const event = kintone.app.record.get(); 以降が実行される。

@wv-sumichan さんありがとうございました。

まとめ

イベントハンドラで同期処理しかないケース(先ほどの再掲)

  • フィールド値変更イベントの引数に渡されるオブジェクトは、フィールド構成(配置)とイベント発火方法によっては、値が空になっているケースがある
    (ルックアップなどで値がコピーされてイベントが発火した場合)
  • 回避するには、イベント発火のきっかけとなるフィールドを下に持ってくる
  • もしくは、イベント発火のきっかけとなるフィールドを変更する

イベントハンドラで非同期処理があるケース

  • イベントハンドラで非同期関数を呼び出す
  • イベントハンドラの終了よりも、呼び出した非同期関数が先に終わらないように意識する
  • await や .then を使って、呼び出し元であるイベントハンドラの処理を先に終わらせる。
    というところでしょうか。
    間違っていたらごめんなさい。

あとがき

記事を書くというのは難しいですね。
普段何気なく Qiita で自分の困りごとを解決してくれる記事を読んだりしますが
記事を投稿するのは結構大変ですね。感謝しかないです。

それと、実は私が所属する会社は CybozuDays 2020 東京に出展し、私自身もブースに立っておりました。
ブースに立ち寄ってくださったみなさま、ありがとうございました。

20
15
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?