[ODC]ページ内の特定箇所へのリンクに対応する

大量のコンテンツがあるScreenで、特定の箇所を共有したい（URLを開くと、その箇所へスクロールさせたい）。

# 環境情報
ODC Studio(Version 1.5.9)

# 先に結論
以下の実装が良さそう。
1. Screenにスクロール先WidgetのIdを渡すInput Parameterを追加
1. ScreenのOnReadyで以下の処理
    1. 渡されたWidgetのIdが許容される値かチェックする
    1. 許容される値であれば、OutSystemsUI/ScrollToElement ActionのWidgetIdパラメータに渡す

# 一般のWebページならURLにFragmentを付与すれば良い
[URI フラグメント - URI | MDN](https://developer.mozilla.org/ja/docs/Web/URI/Reference/Fragment)より、FragmentはURL内の#で始まる部分。

> フラグメントは URI の最後の部分で、# の文字で始まる部分です。文書内のセクションや動画内の位置など、リソースの特定の部分を識別するために使用されます。

HTMLでページを開いた時、特定の場所にスクロール表示するには、URL末尾に「#要素のid」を追加する。

> HTML 文書内の場合は、要素の id 属性となり、ブラウザーはその要素までスクロールします。

OutSystemsアプリケーションでは、この方法はうまくいかない。
以下、この点について確認し、解決策を検討する。

# 検証用Screenの実装
Sectionを3つ配置し、Nameプロパティを、それぞれSection1, Section2, Section3とした。
![スクリーンショット 2025-03-01 23.26.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/eabf9575-b353-457f-92af-3705b0713e77.png)

Section1に対応するHTMLのタグ構造。
![スクリーンショット 2025-03-01 23.35.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/0e3c07b3-7121-4a58-ab78-98fe04630065.png)

# URLにFragmentを付与するだけだとうまくいかない
画面上のSection1にスクロールした状態で開きたいとき、以下のアドレスにアクセスする。
「https://\<FQDN>/\<App名>/\<Screen名>#Section1」

一般のWebページならこれでうまくいくのだが、OutSystemsではうまくいかない。

恐らくReactの影響かと思われる（OutSystemsは、内部でReactを使っている）。
- ReactとFragmentをキーワードにして検索すると、同じ問題にぶつかっている投稿がいくつかある
- SPAであるから、サーバーから返されるHTMLは枠だけが記述されていて、後からJavaScriptでページ構築される？　→ 取得時には遷移先となる場所がない

# OutSystemsUI/ScrollToElement ActionでFragmentまでスクロール　→ 可能だが問題あり
## JavaScriptでFragmentを取得
現在のURL内のFragmentはJavaScriptを使えば取得できる。
window.location.hashを使う。
[Location: hash property - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Location/hash)

JavaScript要素をAction Flowに配置し、Output ParameterとしてFragmentを追加。

コード例。
window.location.hashは#で始まるが、Fragmentがないときは空文字列を返す。値が入っているか確認し、入っているときは、先頭の1文字を削って返している。
``` js
$parameters.Fragment = window.location.hash;
if ($parameters.Fragment.startsWith('#'))
    $parameters.Fragment = $parameters.Fragment.substring(1);
```

## ScreenのOnReadyでScrollさせる
ScreenのOnReady Eventを選ぶのは、必要なDOMの準備が終わったタイミングで、1回の画面表示につき1回だけ発生するEventであるため。

OnReadyのハンドラー例。
JavaScriptコードで取得したFragmentをWidgetIdパラメータに設定してScrollToElement Actionを呼ぶ。
![スクリーンショット 2025-03-02 11.21.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/cc25afa4-61be-4e92-a8ef-de9aa42f03f9.png)

補足
- ListAppendAllでは、画面上でスクロール先として使えるWidgetのIdを、Local VariableのListに登録している
- GetFragmentの実装は、↑に示したJavaScript
- ListIndexOfで取得したFragmentが使えるIdと合致するかチェックしている（これは明示的に許可した値だけを利用可能にすることで、セキュリティに想定外の問題が起きるのを防ぐため）
- 許容されるFragmentであれば、ScrollToElementにGetFrgmentの結果を渡してスクロールさせる

## 結果
ブラウザのアドレスバーにFragment付きのURLを入力して開いたところ、想定通り、指定した要素までスクロールして開いた。

しかし、以下のパターンの操作をすると、画面が全面白の状態のまま表示が止まってしまう（通信によってページのHTML自体は取得できている）。
1. 一度ページを開く
1. アドレスバーのURLを編集し、Fragmentを別の値に変更する
1. アドレスバーでEnterキー

これもReactが原因の気がする。
TODO: Reactを後で調べる

# FragmentでなくScreenのInput Parameterでスクロール先を指定する
基本的な方法は↑と同じだが、Fragmentではなく、Screenに追加したInput Parameterでスクロール先を指定する（↓の例では、TargetInThePage）。
![スクリーンショット 2025-03-02 11.34.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/44c16e26-b48f-4067-bff2-36a62854f24c.png)

OnReadyの実装は基本的に同じで、GetFragment呼び出しを無くし、そのOutput Parameterの代わりにTargetInThePageを使うだけ。
![スクリーンショット 2025-03-02 11.37.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/bce811ad-63bc-4703-a659-4e59c867772b.png)

この方法なら、アドレスバーでパラメータを変更→Enterキーの操作でも問題なく再表示できた。