新アーキテクチャのOutSystems UIにおける拡張機能 JavaScript編

現在進行中のOutSystems UIのアーキテクチャ変更の一環で、拡張機能について新しい方式が導入されたそうなので試してみる。

# 環境
Personal Environment(Version 11.17.0 (Build 36291))
Service Studio(Version 11.53.17)
OutSystems UI (Version 2.11.0)

# サンプル
[Forgeコンポーネント: HousesoftSampleReactive](https://www.outsystems.com/forge/component-overview/7086/housesoftsamplereactive)のV1.0.13。

モジュール > OutSystemsUIFlow > NewExtensibilityFeature Screenの「2. JavaScriptによるパターン」

# OutSystes forumに投稿された、[OutSystem UI V2.11.0リリースに伴うAnnouncement](https://www.outsystems.com/forums/discussion/81647/check-the-new-extensibility-features-in-outsystems-ui-v2-11-0/)
新しいWidgetのいくつかに拡張機能を追加したというアナウンスがあった。

ここでいう拡張機能というのは、Widgetの標準のUIでは実現できないoptionの変更やイベントハンドラーの登録を行える機能ということ。
なんでそんなことができるかというと、各Widgetは別のJavaScriptライブラリーを組み込んでいる（すべて見たわけではないが、少なくとも今回の対象のものはそう）ので、そのライブラリーのAPIを直接操作するため。

このライブラリーのことをProviderと呼んでいるようだ。
拡張機能はこのProviderが提供する機能を利用することで開発者が機能変更を行えるというもの。
全てのWidgetに提供されるわけではなく、今回はいくつかのWidgetにのみ追加されている。

## 新しいI/F
I/FはClient Actionで提供されるものと、JavaScriptでアクセスするものがある。
以下で[ProviderName]となっている部分は、ベースとなるJavaScriptライブラリ（Provider）の名前。Widgetごとに異なる。

- Client Action
    - Set[ProviderName]Configs: 設定項目をAttributeとして持つStructureが提供され、それをInput Parameterに渡すことで設定を変更できる
    - Set[ProviderName]Event: Widgetが提供していないProviderのイベントに対してClient Actionをハンドラーとして登録できる
    - Unset[ProviderName]Event: Set[ProviderName]Eventで登録したハンドラーを解除する
- JavaScript
    - Providerのインスタンスを取得するAPI
    - 設定を行うAPI

この記事ではJavaScriptのAPIを対象とする。

## 古い拡張機能（Avanced Format）について
以前からOutSystems UIには拡張機能があり、そちらはJSONで任意の設定を登録できるものだった。
Advanced Formatという名前。
[UI PatternのAdvanced Formatの使い方](https://qiita.com/jyunji_watanabe/items/ac714f17a40b91b4432d)で使い方の例をまとめてある。

# DatePickerのI/Fを確認
この記事では、例としてDatePicker Widgetを使ってJavaScript APIの使い方を確認する。

OutSystems UIを更新しておく。「DEPRECATED_DatePicker」は古い方なので注意。「DatePicker」というWidgetを使う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/7b892855-26de-3fea-19fe-28118efc2732.png)

DatePickerは背後で、[flatpickr](https://flatpickr.js.org/)というライブラリを使っている。
Providerとして何を使っているか、そもそもどのWidgetが今回の拡張機能に対応しているか、といった情報は、[OutSystems UI Pattern extensibility](https://success.outsystems.com/Documentation/11/Developing_an_Application/Design_UI/Patterns/Using_Mobile_and_Reactive_Patterns/OutSystems_UI_Pattern_extensibility?_gl=1*f7q4mk*_ga*MTEyNTc3NjMwNC4xNjAyMzM1NjYy*_ga_ZD4DTMHWR2*MTY2MzcxMTA5OC4xOTYuMS4xNjYzNzExMjY5LjUwLjAuMA..)で確認できる。

# Widgetが公開していないイベントのハンドラーを書く
DatePickerは、InitializedとOnSelectedしか公開していないので、ほかのイベントを利用したいときは、拡張機能で行う。

## 対象のイベントを特定する
Providerのドキュメントを確認し、ハンドラーを書きたいイベントを特定する。
ProviderのEventに関するドキュメントは[Provider client actions - Provider events](https://success.outsystems.com/Documentation/11/Developing_an_Application/Design_UI/Patterns/Using_Mobile_and_Reactive_Patterns/OutSystems_UI_Pattern_extensibility/Provider_client_actions#provider-events)にリンクがある。

[flatpcker v4のEvents & Hooks](https://flatpickr.js.org/events/)から、例としてonOpenを選んでみる。

> onOpen gets triggered when the calendar is opened.
ということなので、Inputにフォーカスを当てて、カレンダーが開いたときに動作するイベントということ。

## ProviderのAPIを利用してイベントハンドラを登録する
ローコード部分の手順は、公式ドキュメントの[How to access a provider instance through an initialized event](https://success.outsystems.com/Documentation/11/Developing_an_Application/Design_UI/Patterns/Using_Mobile_and_Reactive_Patterns/OutSystems_UI_Pattern_extensibility/Provider_instance_and_JavaScript#how-to-access-a-provider-instance-through-an-initialized-event)にスクリーンショットが貼ってあったので、以下の手順でわからない部分があればそっちを参照して欲しい。

DatePicker Widgetはすでに配置してある前提で、
1. WidgetのInitializedハンドラーとなるScreen Actionを作成
1. JavaScript要素を追加
    1. Input ParameterとしてDatePicker WidgetのIdを渡す（WidgetのInitialize Eventハンドラーを作成すると、DatePickerWidgetIdという名前で渡ってくるのでこれを使う）
    1. Widgetが提供するAPIを使い、ProviderのJavaScriptオブジェクトを取得する
    1. ProviderのJavaScriptに対して、直接イベントハンドラーを登録する

1の実装例 (Initialized Eventに対して、Screen Actionでハンドラーを作成する）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/ca34577d-6c8a-853f-18c0-346959ad9af4.png)

2のJavaScriptの実装例については次のセクションを参照。

## JavaScript要素に書くコードのサンプル
[flatpickrのドキュメント](https://flatpickr.js.org/events/)によると、EventのHookは関数の配列らしい。
> After instantiation, all hooks can be accessed via the instance’s config object. Inside the object, all functions are stored in arrays, so you would need to manipulate the array itself to add or remove functions:
>
> Example: instance.config.onChange.push(function() { } );

そこで、
1. イベントハンドラーとなる関数を定義
1. OutSystems UIが提供するAPI (OutSystems.OSUI.Patterns.DatePickerAPI.GetDatePickerItemById) でflatpickrのインスタンスを取得
1. インスタンスの.provider.config.onOpenがOpenイベントに対するハンドラーの配列なので、これに1で定義した関数をpushする

として、JavaScriptの関数をイベントハンドラーに登録する。
なお、このときflatpickrのイベントハンドラーはselectedDates, dateStr, instanceの3つのパラメータを受け取るとのことなので関数の引数に定義してある。

``` js
// Widgetのベースとなるライブラリ（Provider）のオブジェクトを取得する
var provider = OutSystems.OSUI.Patterns.DatePickerAPI.GetDatePickerItemById($parameters.WidgetId).provider;
// オブジェクトのconfigを経由してonOpenのHook (配列) にハンドラーを登録する
provider.config.onOpen.push(function (selectedDates, dateStr, instance) {
    alert("onOpenが動作した (dateStrパラメータ: " + dateStr + " )");
});
```

## 動作確認
2022/09/28が選択された状態でInputをクリックし、カレンダーを開くときに、以下の通りalertが表示された。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/b9c84848-2faf-941f-afcc-ae3f571f9a60.png)

つまり、想定通りonOpenのハンドラーに任意の関数を登録できた。

# Widgetが提供していない設定値を変更する
Providerの設定値の中に、WidgetのI/Fでは公開されていない値もある（ProviderConfigsに含まれていない属性）。JavaScriptでこれらの値を変更することができる。
## テストに使う設定値（option）を特定
[flatpcker v4のOptions](https://flatpickr.js.org/options/)から、例としてmodeを選んでみる。
WidgetのInput Parameterにも、SetFlatpickrConfigs ActionのProviderConfigsパラメータに渡すFlatpickrConfigs Structureにも出ていない項目を選択した（WidgetがI/Fを提供しないoptionをJavaScriptを利用すれば指定できることを確認するため）

## ProviderのAPIを利用してoptionを設定する
大まかな手順はeventの場合と同じ。

DropDownでModeに対応する値を選択できるようにし、そのOnChangeイベントハンドラで、optionを選択する処理を行う。JavaScript要素のInput Parameterとして、以下の2つを渡している。

- WidgetId: DatePickerのInitializedのタイミングで受け取っていたWidgetIdを保存しておき、引き渡す
- Mode: Flatpickrのmodeオプションが要求する3つの値のいずれか。"single"->デフォルト。1日のみ選択する、"multiple"->カレンダーで複数の日付を選択できる、"range"->カレンダーで一連の日付を選択できる（開始日と終了日を選択できる） 

``` js
// OutSystems UIが提供するAPIを用いて、Providerの設定を行う
OutSystems.OSUI.Patterns.DatePickerAPI.SetProviderConfigs(
    $parameters.WidgetId,
    options = { mode: $parameters.Mode }
);
```


## 動作確認
初期状態では以下の通りmode=singleであり、カレンダーで選択する日は1つ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/b90ca789-2851-2d7c-479d-f4ca600d4667.png)

DropDownで選択するmodeをrangeに切り替えると（上のJavaScript要素のModeパラメータに"range"を渡すと）、以下の通り期間（開始日と終了日）を選択できるようになり、optionの選択が正しく動作していることがわかる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/28422c18-d1d6-0a4c-e20c-c3eb49e56a1f.png)

なお、このカレンダーには、onOpenのタイミングでalertを表示するカスタマイズをいれてあった。ところが、一度optionを変更するとカレンダーを開くときにalertが表示されなくなる。

これは、ドキュメントに以下の注記がある通り、カレンダーの初期化処理が再度動作したのかもしれない。
> Note: This may result in a destroy/init cycle every time this API is called.

# 注意点
ここに書いた方法は、ドキュメントに以下の記述がある通り、WidgetではなくベースのProviderに対する直接の操作。将来、またアーキテクチャの変更があって、Providerが別のライブラリに差し替えられたら、アプリケーションには大きな影響がある。これは、アプリケーションと（OutSystems UIのWidgetではなく）Providerの間に直接の結合が発生してしまうため。
> This is a direct communication between the developer and the provider, so from this step on, OutSystems does not support any issues or features and the developer should be conscious of the code it produces.

蒸気を考慮に入れた開発を行う必要がある。