ServiceNow - クライアントスクリプトからテーブルレコードを参照する

#概要
本記事ではクライアントフォームにテーブルからの値を表示する方法を検討します。
今回はユーザを選択されると社員番号を表示するフォームを作成します。
例：
![final_form_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698027/0c50ab87-da06-0d73-4bec-d1f1dd22feec.png)

テーブルからデータを取得する方法を説明するためにページレイアウトや項目は簡単なものにしました。
フォームには次の２つのフィールドを定義しました。

|質問|名前|タイプ|順番|参照テーブル|
|---|---|---|---|---|
|ユーザー|user|参照|20|sys_user|
|User Info|user_info|１行テキスト|30||

#実装
クライアントからServiceNowテーブルデータを取得するには2つの方法があります。一般的に使われるのはGlideAjaxを使う方法です。もう一つはgetReference()を使う方法です。本記事では2つの方法を検討します。

##GlideAjax(getXMLAnswer)
クライアントスクリプト

```
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || newValue == '') {
        return;
    }

    var ajax = new GlideAjax('GetUserInfo'); //Ajaxを利用してサーバを呼び出す。引数にサーバスクリプトファイル名を渡す
    ajax.addParam('sysparm_name', 'getUserNameAndEmpNo'); //呼び出すメソッド名を指定
    ajax.addParam('sysparm_user_id', newValue); //選択されたユーザのsys_idを設定する
    ajax.getXMLAnswer(function(answer) { //Ajax呼び出し
        if (answer.length > 0) { //メールアドレスを取得できたかチェック
            g_form.setValue('user_info', answer); //   結果をメールフィールドに設定する
        }
    });
}
```
スクリプトインクルード

```
var GetUserInfo = Class.create();
GetUserInfo.prototype = Object.extendsObject(AbstractAjaxProcessor, {
    getUserNameAndEmpNo: function() {
		var user_id = this.getParameter('sysparm_user_id');
        var grUser = new GlideRecord('sys_user'); //SystemNowのユーザテーブル（sys_id）を参照する
        if (grUser.get(user_id)) { //指定したユーザが存在するか確認
            return grUser.name + '  [' + grUser.employee_number + ']'; //存在する場合はメールアドレスを返す
        }
        return ''; //存在しない場合は空文字を返す
    },


    type: 'GetUserInfo'
});
```
##GlideAjax(getXML)
サーバから構造化された結果が返される場合はgetXML()メソッドを利用します。
getXMLAnswerと同じスクリプトクライアントを使います。
クライアントスクリプト

```
function onChange(control, oldValue, newValue, isLoading) {
   if (isLoading || newValue == '') {
      return;
   }
    var ajax = new GlideAjax('GetUserInfo');
    ajax.addParam('sysparm_name', 'getUserNameAndEmpNo');
    ajax.addParam('sysparm_user_id', newValue); //選択されたユーザのsys_idを設定する
    ajax.getXML(_displayUserInfo);

    function _displayUserInfo(response) {
        var answer = response.responseXML.documentElement.getAttribute("answer");
		g_form.setValue('user_info', answer);
    }
}
```

##getReference()
getReference()はクライアントスクリプトでサーバ情報を非同期で取得する関数です。スクリプトインクルードを作成する必要がないので少ないコードでテーブルデータを取得することができます。
getReference()の引数は参照型フィールド名,呼び出す関数名です。次の例では参照型フィールドuserのレコードを取得してsetUserInfo関数を呼び出しています。

```
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || newValue == '') {
        return;
    }
    g_form.getReference('user', setUserInfo); //参照フィールドuserのレコードを取得してsetUserInfoを呼び出す

    function setUserInfo(user) { //ユーザ情報をフィールドuser_infoに設定する
        g_form.setValue('user_info', user.name + '  [' + user.employee_number + ']');
    }
}
```
コードから見るとgetReference()の方が簡単なように見えますが、パフォーマンス問題があるために利用は避けられています。getXML()及びgetXMLAnswer()の場合は必要な行列のデータのみを取得しますが、getReference()の場合は１レコード全体を取得します。そのためにネットワーク負荷が多くなり、メモリも多く使われるます。
列が少ないテーブルのデータを取得する場合は問題はないとしてもServiceNowテーブルのように列が多いテーブルのデータを取得するぬは不向きです。


###クライアントスクリプトfunctionのスコープ
余談になりますが、getReference()のコードを見て気が付いた人もいますが、呼び出される関数はonChange()関数内に定義されています。理由はデフォルト設定ではgetReference()からonChange()外部の関数を呼び出すことが禁止されているからです。LondonリリースからXSS対応のためにデフォルトではスコープ外関数の呼び出しを禁止しています。
次のコードを実行するためには「スクリプトを隔離」を無効にします。

```
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || newValue == '') {
        return;
    }
    g_form.getReference('user', setUserInfo);
}

function setUserInfo(user) {
    g_form.setValue('user_info', user.name + '  [' + user.employee_number + ']');
}
```

1.「他のアクション」アイコンを選択。「構成」＞「フォームデザイン」を選択してフォームデザイナーを開く。
![isolate_script_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698027/adbd347a-a5c1-e288-d983-7d3f411eb5f0.png)
2.フィールド「スクリプトを隔離」をフォームに追加する
![isolate_script_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698027/8440b57e-eed4-5dff-d86b-d0ac16d37cb7.png)
3.「スクリプトを隔離」のチェックを外す。それで実行するとsetUserInfo()関数が呼ばれるようになります。
![isolate_script_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698027/730db517-fe7d-f3b0-00ce-eb26d6d1cee7.png)

##クライアントGlideRecord
クライアントスクリプトでもGlideRecordは使えます。しかし、サーバから全列を取得するので列が多い場合は性能が悪化するので推奨されていません。ただし、テーブルに1列しかない場合は簡単に記述できるので便利です。
クライアントスクリプト
```
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || newValue == '') {
        return;
    }

    var userLookup = new GlideRecord('sys_user');
    userLookup.addQuery('sys_id', newValue);
    userLookup.query(function(userLookUp) {
        if (userLookup.next()) {
            g_form.setValue('user_info', userLookup.name + ' [' + userLookup.employee_number + ']');
        } else {
            g_form.clearValue('user_info');
        }
    });
}
```

#テンプレート
スクリプトインクルード

```
var GetUserInfo = Class.create();
GetUserInfo.prototype = Object.extendsObject(AbstractAjaxProcessor, {
    getUserNameAndEmpNo: function() {
        var user_id = this.getParameter('sysparm_user_id');
        var grUser = new GlideRecord('sys_user');
        if (grUser.get(user_id)) {
            return grUser.name + '  [' + grUser.employee_number + ']';
        }
        return '';
    },
    type: 'GetUserInfo'
});
```
クライアントスクリプト

```
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || newValue == '') {
        return;
    }
    var ajax = new GlideAjax('GetUserInfo');
    ajax.addParam('sysparm_name', 'getUserNameAndEmpNo');
    ajax.addParam('sysparm_user_id', newValue);
    ajax.getXMLAnswer(function(answer) {
        if (answer.length > 0) {
            g_form.setValue('user_info', answer);
        }
    });
}
```
以上