目次
この記事は5分で読めます。
はじめに
この記事の目的
-
onChangeイベントのClient Scriptで、ServiceNow Client APIのg_form.setValue()メソッドを利用する際に留意すべきことをまとめます。
対象読者
- ServiceNowでClient Scriptを設計・実装する方
症状
以下のように、DescriptionフィールドのonChangeイベントで起動するClient Scriptを作成します。
▼Client Script(onChangeRecursion)

このスクリプトは、Descriptionフィールドの値がユーザーによって変更されたときに、setValueメソッドによってShort Descriptionの値をDesctiptionフィールドに入力します。
このスクリプトをActiveにした状態で、例えば下図のようにDescriptionの値を変更してみます。
▼IncidentフォームのDescriptionフィールド値を変更("onChangeテスト"を入力)

期待される動きはShort Desctiptionフィールドの"Unable to access the shared folder."という文言がDescriptionフィールドに入力されることです。
結果としては以下の通りです。
見た目は問題ないように見えますが、onChange script errorがスローされてしまいました。
▼onChangeイベント起動後のIncidentフォーム

原因
onChangeイベントは、クライアント側のユーザーによる値の変更だけでなく、スクリプトによる値の変更によっても起動されます。
したがって、前述のようなClient Scriptを書いてしまうと、「ユーザーによる値の変更→Client Script起動→Client Scriptによる値の変更→Client Script起動→Client Scriptによる値の変更$\cdots$」というような無限再帰状態に陥ってしまい、スタックオーバーフローが生じます。
これが、onChange script errorが生じる原因です。
ひとつわかりやすい例を示します。
Client Scriptの処理内容を少し変えて、既存のDescriptionの文言$+$無限再帰テストという文字列を入力するスクリプトに変更します。
▼Client Script(onChangeEndlessRecursion)

このスクリプトをActiveにした状態で、Descriptionの値を変更したときの挙動は以下のようになります。
▼IncidentフォームのDescriptionフィールド値を変更(末尾の.を削除)

以上のように、無限再帰テストという文言が何重にも連結され、onChange script errorが生じました。
対処法
では対処法は何かというと、再帰処理の終端条件を定義することです。
以下の図のように、再帰関数onChange()に到達可能な終端条件を設けることで、それ以上深く再帰することがなくなり無限再帰状態に陥らなくなります(当然、終端条件が到達不可の場合、無限再帰状態に陥ります)。
例えば、最初に挙げた「Descriptionフィールドの値がユーザーによって変更されたときに、setValueメソッドによってShort Descriptionの値をDesctiptionフィールドに入力」するClient ScriptでonChange script errorを回避したい場合、2~4行目のif分岐でShort Descriptionの値と同じ場合は即座に終了するようにします。
※6行目にif文を追加して、「Short Descriptionの値と値が異なる場合にのみ実行」としてもかまいません。
▼Client Script(onChangeStopRecursion)

このスクリプトをActiveにした状態で、Descriptionの値を変更したときの挙動は以下のようになります。
▼IncidentフォームのDescriptionフィールド値を変更

今回はonChange script errorのエラー文が表示されなくなりました。
このように終端条件を書くことによって、「ユーザーによる値の変更→Client Script起動→Client Scriptによる値の変更→Client Script起動→Descriptionの値とShort Descriptionの値が一致するため何もせずにClient Scriptを終了」という流れとなり、無限再帰が引き起こされなくなったのです。
余談(oldValueについて)
対処法で挙げた例にて、newValue===oldValueではなく、newValue===g_form.getValue("short_description")としたのは理由があります。
一見、newValue===oldValueでも良さそうですが、こうしてしまうと再帰処理が終了せずにonChange script errorがスローされてしまいます。
実験
この理由を確かめるため再帰処理中のoldValueとnewValueの中身を確認してみましょう。
特別に、「Infoメッセージとして今何回目の再帰処理なのか」、「oldValue、newValueの値は何か」を表示するClient Scriptを作成しました。また、見やすくするために4回目の再帰で終了とします。
▼Client Script(onChangeRecursionValueCheck)

このスクリプトをActiveにした状態で、Descriptionの値を変更したときの挙動は以下のようになります。
▼Descriptionフィールドが空の状態で保存されたIncidentレコード

▼IncidentフォームのDescriptionフィールド値を変更(testを入力)

結果は次の通りです。
- 1回目:
これは1回目の再帰処理です。oldValue: unknown, newValue: test - 2回目:
これは2回目の再帰処理です。oldValue: , newValue: Unable to access the shared folder.2 - 3回目以降は2回目と同様なので省略
1回目については、oldValueはunknownを返し、newValueに入力したtest文字列が表示されているため期待通りの挙動をしています。(ロード時にはoldValueがunknownを返す仕様になっているようです。こちらは深く掘れていないので追々確認できれば追記します。)
興味深いのは2回目以降であり、2回目はUnable to access the shared folder.1、3回目はUnable to access the shared folder.2、4回目はUnable to access the shared folder.3が表示されるのではないかと思われる方もいらっしゃるかと思いますが、結果は空文字列でした。
結論
実は、oldValueはクライアント側での値の変更を追跡してユーザーやスクリプトによる変更前の値を持っているわけではなく、最後にサーバー側のデータベースに保存された値を持っているのです。
今回はDescriptionの値が空の状態で保存したところからスタートしたのでしたね。
したがって、oldValueの値も空文字列となります。
よって、newValue===oldValueという条件式は常にfalseを返します。
これが、newValue===oldValueという条件式を使えない理由です。
さいごに
この記事ではonChangeのClient ScriptでsetValueメソッドを利用する際の留意点をまとめました。
結論として○○に入るのは無限再帰でした。馴染みのなさそうな言葉なのでこれまで深く気にされないかった方も多いのではないかと思います。
本記事について、もし気になる部分や不明点等ございましたら気軽にご質問ください。
ではでは。

