[OutSystems]JavaScriptでInputの値を変更する方法

Input Widgetの値への変更をJavaScriptで、DOM要素の.valueを経由して行うと、Inputに紐づけた変数に値が反映されない。

Inputに機能を追加するBlockを作るときに困るので、解決策を探ってみる。


# 環境情報
Personal Environment(Version 11.21.0 (Build 39357))
Service Studio(Version 11.54.13)

# サンプル
[HousesoftSampleReactive](https://www.outsystems.com/forge/component-overview/7086/housesoftsamplereactive)のバージョン1.0.19。
ChangeInputValueWithJS Screenを参照。

# NG例：JavaScriptでinput.valueの値を変更
まずはダメなケースの実装を確認する。

以下のコードをJavaScript要素に書いてもうまくいかない。
なお、Input WidgetのIdはJavaScript要素のInput Parameterとして渡している。

``` js
document.getElementById($parameters.InputId).value = "JavaScriptでinput.valueを更新"
```
以下のように、Input Widgetに表示されている値は変わっているが、Input WidgetのVariableに設定した変数の値は変わらない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/5028ddad-41a6-f685-ad22-c26fcf22314e.png)

# 原因を検討：恐らくReactのせい
OutSystemsのReactive/Mobileはフロントエンドの仕組みとして、Reactを利用している。
したがって、クライアント側でこういう不思議なことが起こるときは、Reactが怪しい。

というわけで、Reactをキーワードに入れて探すと、以下のページが見つかった。
[Changing a React Input Value from Vanilla Javascript](https://chuckconway.com/changing-a-react-input-value-from-vanilla-javascript/)

> React overrides the native Javascript onChange behavior. Triggering an onChange event does nothing to change the input field value in React’s eyes.

「ReactがJavaScript自身が持っているonChangeのふるまいを変えているため、onChangeイベントを発生させるだけだと、Reactの観点からは値が変わったように見えない。」

# 対策①_valueTrackerを使う
「原因を検討」で引用したページで示されていた対策。
[Platform Server v11.12.0](https://success.outsystems.com/support/release_notes/11/platform_server/#platform_server_11.12.0)により、Reactのバージョンは16。よって引用したページの対策のうち、_valueTrackerを使う方法を利用する。

JavaScript要素に記述するコードは以下の通り。
``` js
// see: https://chuckconway.com/changing-a-react-input-value-from-vanilla-javascript/
// 1
let element = document.getElementById($parameters.InputId);
// 2 
let lastValue = element.value;
element.value = "JavaScriptでinput.valueを更新してdispatchEvent（_valueTracker利用）";

let ev = new Event ("input", { bubbles: true });
// 3
let tracker = element._valueTracker;
if (tracker) {
    tracker.setValue(lastValue);
}
// 4
element.dispatchEvent(ev);
```

1. Input Widgetに対応するinput要素を取得
1. 変更前の値を保存し、.valueプロパティを経由して値を変更
1. _valueTrackerの値を変更前の値に設定
1. inputイベントを発生させる

という流れ。この中の「3」のステップが肝になっている。
ただし、プレフィックスに「_」がついているのであんまり公然と使うものでもなさそう。

この方法で変更すると、以下の通り変数の値も更新される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/adb9c538-a8d3-d791-80b5-a387bee94403.png)

# 方法②inputのsetterを直接呼ぶ
Reactが本来のsetter関数を書き換えるのであれば、書き換えられる前のsetter関数を使って値を設定すればよい、というアイデア。
[What is the best way to trigger change or input event in react js](https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-change-or-input-event-in-react-js#46012210)のacceptedな回答で示されていた方法。

``` js
// see: https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-change-or-input-event-in-react-js#46012210
// 1
let element = document.getElementById($parameters.InputId);
// 2
let nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
nativeSetter.call(element, "JavaScriptでinputのsetterを呼んでdispatchEvent");

// 3
let ev = new Event ("input", { bubbles: true });
element.dispatchEvent(ev);
```

1. Input Widgetに対応するinput要素を取得
1. input要素のsetter関数（PropertyDescriptorのsetから取得）を使って値を更新
1. input Eventを発生させる

[Input Masks Library](https://www.outsystems.com/forge/component-overview/2258/input-masks-library)というForgeコンポーネントもこの方法を使っている。

# 適用に当たって
VanillaなJavaScriptのみで実現している方法②をとりたい。
ただし、OutSystemsのバージョンアップ (にともなうReactのバージョンアップやフロントエンドフレームワーク事態の変更）に伴って使えなくなることにも備えたい。

そこで、こういう習性は共通部品のBlockやClient Actionに分離し、必要になったら簡単に置き換えられるようにしよう。