目次
この記事は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
メソッドを利用する際の留意点をまとめました。
結論として○○に入るのは無限再帰でした。馴染みのなさそうな言葉なのでこれまで深く気にされないかった方も多いのではないかと思います。
本記事について、もし気になる部分や不明点等ございましたら気軽にご質問ください。
ではでは。