[ServiceNow] [Madrid Patch6] Multi-row variable set の問題を無理やり解決する

# 背景
- Service Catalog で使用できる Multi-row variable set (以降MRVS) が Order Guide 経由だと正常に動かない。
- Desktop UIで事象が出る。Service Portalでは問題なし。
- Order Guide 経由ではなく、Service catalog 単体で試すと正常に動作する。
- 事象が出るのは Madrid Patch6。
- NewYorkにUpgrade済みのPersonal instanceでは再現しない。なので将来のリリースでは直っているはず。

# 問題の詳細
- MRVSの "Add" ボタン押下後、"Add Row" ポップアップ画面にて値を入力、ポップアップ画面の "Add" ボタンを押下しても、元画面に反映されない。ポップアップが閉じない。
- コンソールでは ```TableVariableRow``` クラスの ```getJSON``` メソッドでエラーが出ているのが分かる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72876/19b79acf-42f0-80cb-f110-3f8ad545d9b6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72876/0a3cbe9f-9dc9-dcdd-09f4-69a137631a51.png)



- より詳細に見ていくと、Order guide 経由の場合と Service Catalog 単体の場合で、ポップアップ画面内の Variable の ID が異なることが分かる。（サーバサイドでの処理ミス？）
![catalog 単体.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72876/81e4117b-edba-6d65-b662-51ea6e91103a.png)




- IDのPrefixが異なるせいで、```getVariablesFromDialog``` メソッドで要素とその値を正しく取れていないことが分かる。（これを無理やり直す）

# 無理やり直す

- ```getVariablesFromDialog``` メソッドがいけてないのが分かったので（本当はポップアップ画面がいけてないんだけど）、無理やり以下の様に上書きする。
- ```getVariablesFromDialog``` の処理修正版を作り、```TableVariableService``` の ```renderTableDialogForm``` メソッド内で ```iFrameElement.getVariablesFromDialog``` を上書きしてしまう。
- ```getVariablesFromDialog``` の修正内容は「ここから～ここまで」参照。
- onLoad の Catalog Client Script、Desktop UI 二のみ適用、そして"Isolate script" のチェックは外しています。

```javascript
function onLoad() {
	if(!gel('sysparm_guide')) {
		return;
	}
	TableVariableService.renderTableDialogForm = function(tableVariable, action, nodeId) {
		var dialog = new CatalogModalForm(tableVariable.getLabel(), 'catalog_table_variable_dialog');
		dialog.setSize(900);
		var dialogTitle = (action == "create") ? getMessage("Add Row") : getMessage("Edit Row");
		dialog.setTitle(dialogTitle);
		dialog.setPreference("sysparm_table_dialog_action", action);
		dialog.setPreference("sysparm_table_variable_id", tableVariable.getId());
		dialog.setPreference("sysparm_catalog_item_id", tableVariable.getCatalogItem());
		dialog.setPreference("sysparm_source_table", tableVariable.getSourceTable());
		dialog.setPreference("sysparm_source_id", tableVariable.getSourceId());
		if (action == 'update') {
			dialog.setPreference("sysparm_active_node_id", nodeId ? nodeId : "");
			dialog.setPreference("sysparm_active_node_data", tableVariable.getRowData(nodeId));
		}
		dialog.setBackdropStatic('true');
		dialog.setPreference('autoFocus', true);
		dialog.setPreference('focusTrap', true);

		var callback = function(dialogObject) {
			var iFrameElement = gel('dialog_frame') ? gel('dialog_frame').contentWindow : null;
			if (iFrameElement) {
				iFrameElement.catalog_table_dialog = dialogObject;
				iFrameElement.TableVariableService = this.TableVariableService;
				iFrameElement.TableVariable = this.TableVariable;
				iFrameElement.TableVariableRow = this.TableVariableRow;
				iFrameElement.tableVariableCache = this.tableVariableCache;
				// inline frame の Global メソッドを上書きしてしまう。
				// iFrameElement を bind するのをお忘れなく。
				iFrameElement.getVariablesFromDialog = getVariablesFromDialogUpdated.bind(iFrameElement);
				iFrameElement.g_form.setReadOnly('varCityNameAdding', true);
				iFrameElement.g_form.setReadOnly('varFloorAdding', true);
				iFrameElement.g_form.setReadOnly('varCompanyAdding', true);
				iFrameElement.g_form.setReadOnly('varDepartmentAdding', true);
				iFrameElement.g_form.setReadOnly('varRemarksAdding', true);
			} else {}
		}.bind(window);
		dialog.setOnloadCallback(callback);
		dialog.render();
		return dialog;
	}.bind(window);

	function getVariablesFromDialogUpdated( /* TableVariable */ tableVariable) {
		if (!tableVariable) return;
		var namespace = tableVariable.getNameSpace();
		var optionsElements = this.document.getElementById('table_variable_sequence').value.split(',');
		var variables = {};
		for (i = 0; i < optionsElements.length; i++) {
			var id = optionsElements[i];
			var nameSpaceId = namespace + id;
			// for reference displayBox contains displayValue
			var displayBox = this.document.getElementById('sys_display.' + nameSpaceId);
			var hiddenBox = this.document.getElementById(nameSpaceId);
			// ここから
			// displayBoxが取れてなかったら、IDを変えて再取得 (reference field などの display valueっぽい)
			if (!displayBox){
				displayBox = this.document.getElementById('sys_display.ni.QS' + id);
			}
			// hiddenBoxが取れてなかったら、IDを変えて再取得 (input の value っぽい)
			if (!hiddenBox) {
				hiddenBox = this.document.getElementById('ni.QS' + id);
			}
			// ここまで
			if (!hiddenBox) continue;
			var colElement = {
				value: ''
			};
			if (hiddenBox.type == 'radio' || (hiddenBox.hasAttribute('type') && hiddenBox.getAttribute('type') == 'radio')) {
				var selectedOption = $(nameSpaceId).querySelector("input[name='" + nameSpaceId + "']:checked");
				if (selectedOption) {
					colElement.value = selectedOption.value;
					// If value is empty don't honor labels
					if (colElement.value != '' && selectedOption.labels) {
						colElement.displayValue = selectedOption.labels[0] ? selectedOption.labels[0].innerText : "";
					}
				}
			} else if (hiddenBox.type != "undefined" && hiddenBox.type == 'select-one') {
				var options = hiddenBox.options;
				var selectedIndex = hiddenBox.selectedIndex;
				if (selectedIndex > -1 && options.length > selectedIndex) {
					colElement.value = options[selectedIndex].value;
					// If the value is empty, don't render the Label.
					// Even for lookup selectBox, we map label & value to two different columns (value being mandatory & label is not), if there is no value associated with the label, we are not honoring that, its upto them to configure those entries.
					if (colElement.value != '') colElement.displayValue = options[selectedIndex].label;
				}
			} else if (hiddenBox.hasAttribute("data-type") && hiddenBox.getAttribute("data-type") == 'duration') {
				var value = hiddenBox.value;
				var displayValue = hiddenBox.value;
				// check whether Duration API is available
				if (GlideDuration) {
					var displayBoxParts = GlideDuration.parseDurationToParts(value);
					if (displayBoxParts && displayBoxParts.size() == 4) {
						// ignore seconds, we are not supporting
						var d = displayBoxParts[0];
						var h = displayBoxParts[1];
						var m = displayBoxParts[2];
						var s = displayBoxParts[3];
						displayValue = ((d > 0) ? d + ' ' : '') + h + ':' + m + ':' + s;
					}
				}
				colElement.value = value;
				colElement.displayValue = displayValue;
			} else {
				if (displayBox) {
					colElement.displayValue = displayBox.value;
					if (displayBox.hasAttribute("data-type") && displayBox.getAttribute("data-type") == 'catalog_masked') colElement.displayValue = "********";
				}
				colElement.value = hiddenBox.value;
			}
			variables[id] = colElement;
		}
		return variables;
	}
}

```

# 最後に
- 本来ならばPatchなどの将来のリリースで直るのを待つべき。
- 仕方なく直すときにのみ、この様に一時的に直す。