[OutSystems]Aggregate/Data Actionに紐づけたTableが更新された後で発生するEvent

Aggregateを紐づけたTableを作り、初期表示時点で、JavaScriptを使ってtbody以下のタグを操作したいとする。
どのイベントを使うのが適切だろうか？
ScreenのOn ReadyやAggregateのOn After Fetchでは、DOM要素がまだ準備できていないためにうまく行かないようだ。
というわけで実現方法を検討する。

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

# サンプル
[HousesoftSampleReactive](https://www.outsystems.com/forge/component-overview/7086/housesoftsamplereactive) v1.0.21のTableUpdateEvent Screenを参照。

別の環境で試してみたところ、Readyのタイミングで、すでにAggregateの取得が完了していることがあった。すると、このタイミングでJavaScriptを動作させても、問題が発生しないことになる。ドキュメントを見る限り、常にあてにできる動作ではないため、今回の要求に対しては、やはりRenderを使った方がよいはず。
↑の場合は、AggregateのFetch=Only on demandにした上で、ボタンクリックのタイミングでAggregateに対してRefresh Dataを適用すれば問題の状況が作れる。

:::note info
(2023/07/02)上記に対応するため、サンプルのv1.0.22では、Aggregateを使っていた部分をData Actionに改め、さらにSleepで3秒間待ってからScreenに返すようにした。
:::

# 関連するイベントを整理
[画面とブロックのライフサイクルイベント](https://success.outsystems.com/ja-jp/documentation/11/developing_an_application/implement_application_logic/screen_and_block_lifecycle_events/)を参照しながら、関連するイベントを整理してみる。

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/048772b2-c719-e685-3240-776f7e0c21d2.png)
> 上記リンク先のライフサイクルの各ステージ > アプリケーションを開くときの画像を引用（2023/06/30)

画面の初期表示から、Aggregateを取得、更にそのデータをTableに表示するまでには以下のイベントが発生する。
1. (Screen) Initialize: 画面を開く直前に発生するイベント。変数の初期化などに使う
1. (Screen) Ready: 画面内のDOM準備ができたときに発生するイベント。通常は、JavaScriptを動かすのはこのイベントでよいのだが、Aggregate/Data Actionのデータを元にTableの本体が生成される今回のケースでは、（操作対象のDOMがまだないので）使えない
1. (Screen) Render（初回表示）: 画面がレンダリングされたとき（初期表示、または、UIに紐づくデータが更新されて反映されたとき）に実行されるイベント。初回のRenderでは、Aggregateが終了しておらず、まだ早い
1. (Aggregate) After Fetch: Aggregateのデータ取得が終わったあとに゙動作する。データはあるが、まだそのデータを元にしたDOM要素が生成されていないのでまだ早い
1. (Screen) Render（Aggregate取得後）: Aggregateの取得完了後、結果のListに紐づけられたUI（この場合はTable）の更新完了のイベント

# 解決策
上記のイベントの流れから、ScreenのRenderイベントでJavaScriptを起動すればよいことがわかる。
なお、Renderイベントは、初期表示（Readyの後）と各種UI要素の更新のタイミングで発生する。そのため、

初期表示時点等Aggregateが終わっていないときに動作することもあるため、「AggregateのIsDataFetchedをチェックする」。また、

- 同じ変更が繰り返し適用されても問題ない
- 必要な時だけ動作するようにロジックを書く

のいずれかが必要。

JavaScriptでStyleを適用するだけなら前者にあたるので、以下のように書ける。
ScreenのOn Renderのハンドラーを作成。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/ca3955ff-5ac5-1df5-0ae5-852e891274f2.png)

On RenderではAggregate取得完了をIsDataFetchedプロパティで確認。
この値がTrueであれば、データ取得済みであるため、JavaScriptでスタイルを変更する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/267bb37f-d482-f8df-8140-c7c4e71c5025.png)

コード例：3行目のtdタグを取得し、その背景色を青にしている。trタグの背景色を設定しないのは、元々tdタグに背景色設定があるため、trにだけ設定しても、tdの方の背景色が表示されてしまうため。
``` js
document.querySelectorAll("#" + $parameters.TableId + " > tbody > tr:nth-of-type(3) > td")
        .forEach(td => td.style.backgroundColor = "blue");
```

## 他の案
### フラグを使って対象のAggregate取得後のOn Renderでだけ処理をする
1. ScreenにLocal VariableとしてBoolean型のものを用意する（デフォルトTrue）
1. On RenderでAggregateのIsDataFetchedと↑の変数が共にTrueのときだけロジックを動かす
1. 動かしたら値をFalseにする

という方法が考えられる。
このとき、On Renderには
> 画面またはブロックのデータの変更は避けてください。それは、このデータが変更するたびにOn Renderイベントがトリガーされ、アプリの実行が無限ループになる可能性があるためです。

という注意事項があるため、この点に引っかからないように注意して実装する。Local Variableの値を変更すると、もう一度Renderが動作するようなので、それが結果的に無限ループしないようにする。また、On Renderで直接フラグの値を変更するとService Studio上で警告が出るようなので、Screen内の別のActionに切り出した方がいいかもしれない。

### BlockのOn Render
Render EventはScreenだけでなく、Blockにもある。
よって、問題となるTableとAggregateを独立したBlockに分離できるなら、余計なRender Eventが発生して繰り返し該当ロジックが動作することを心配しなくてもよいかもしれない。