Input Widgetの値への変更をJavaScriptで、DOM要素の.valueを経由して行うと、Inputに紐づけた変数に値が反映されない。
Inputに機能を追加するBlockを作るときに困るので、解決策を探ってみる。
環境情報
Personal Environment(Version 11.21.0 (Build 39357))
Service Studio(Version 11.54.13)
サンプル
HousesoftSampleReactiveのバージョン1.0.19。
ChangeInputValueWithJS Screenを参照。
NG例:JavaScriptでinput.valueの値を変更
まずはダメなケースの実装を確認する。
以下のコードをJavaScript要素に書いてもうまくいかない。
なお、Input WidgetのIdはJavaScript要素のInput Parameterとして渡している。
document.getElementById($parameters.InputId).value = "JavaScriptでinput.valueを更新"
以下のように、Input Widgetに表示されている値は変わっているが、Input WidgetのVariableに設定した変数の値は変わらない。
原因を検討:恐らくReactのせい
OutSystemsのReactive/Mobileはフロントエンドの仕組みとして、Reactを利用している。
したがって、クライアント側でこういう不思議なことが起こるときは、Reactが怪しい。
というわけで、Reactをキーワードに入れて探すと、以下のページが見つかった。
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により、Reactのバージョンは16。よって引用したページの対策のうち、_valueTrackerを使う方法を利用する。
JavaScript要素に記述するコードは以下の通り。
// 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);
- Input Widgetに対応するinput要素を取得
- 変更前の値を保存し、.valueプロパティを経由して値を変更
- _valueTrackerの値を変更前の値に設定
- inputイベントを発生させる
という流れ。この中の「3」のステップが肝になっている。
ただし、プレフィックスに「_」がついているのであんまり公然と使うものでもなさそう。
方法②inputのsetterを直接呼ぶ
Reactが本来のsetter関数を書き換えるのであれば、書き換えられる前のsetter関数を使って値を設定すればよい、というアイデア。
What is the best way to trigger change or input event in react jsのacceptedな回答で示されていた方法。
// 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);
- Input Widgetに対応するinput要素を取得
- input要素のsetter関数(PropertyDescriptorのsetから取得)を使って値を更新
- input Eventを発生させる
Input Masks LibraryというForgeコンポーネントもこの方法を使っている。
適用に当たって
VanillaなJavaScriptのみで実現している方法②をとりたい。
ただし、OutSystemsのバージョンアップ (にともなうReactのバージョンアップやフロントエンドフレームワーク事態の変更)に伴って使えなくなることにも備えたい。
そこで、こういう習性は共通部品のBlockやClient Actionに分離し、必要になったら簡単に置き換えられるようにしよう。