この時点で、私たちはまともなコンポーネントを手に入れた。しかし、それを他の人たち(特に開発者ではない人たち)と共有しようと思うと、その有用性は限られてくる。リンゴと梨の選択しかできないのだ。これは私たちの最初の顧客(おそらくフルーツケーキ工場)には完璧でしたが、私たちが次に取り組む自動車会社は、休憩室で従業員がフルーツを選ぶのに拡張現実を使うことはないでしょう。私たちのコンポーネントをもっと再利用可能で構成可能なものにする時が来たのです。
コンポーネントの設定方法は既にご存知でしょう。ワークフローフローチャート上でコンポーネントを選択すると、ワークフローフローチャートの右側にコンポーネント設定パネルがポップアップ表示され、設定オプションが表示されます。
コンポーネントをコンフィギュラブルにすることは、ユーザーがコンフィギュレーションできるものを定義することと、選択された値を処理することの2つの部分からなる。
設定オプションの定義
提供したい設定オプションはJSON形式で定義されます。ユーザーは、単純なテキスト入力、チェックボックス入力、ファイルアップロード入力など、さまざまな入力タイプから選択できます。
以下に設定例を示す。
{
"tab1": {
"title_text": {
"title": "Title",
"inputType": "textinput",
"value": "Please select"
}
},
"tab2": {
"options": {
"title": "Options",
"inputType": "map-input",
"placeholder": {
"key": "Option Key",
"value": "Option Title"
},
"value": [
{
"key": "option1",
"value": "Option 1"
},
{
"key": "option2",
"value": "Option 2"
},
{
"key": "option3",
"value": "Option 3"
}
]
},
"use_all": {
"title": "Offer last option",
"inputType": "checkbox-input",
"value": "true"
}
}
}
JSONファイルのルートレベルにあるオブジェクトは、コンフィギュレーション・パネルのタブです(それらのキーはタブのタイトルとして表示されます)。各入力フィールド・オブジェクトは少なくとも3つの属性を持ちます:
- 設定パネルの入力フィールドの上に表示される見出し
です
。 - 表示させたい入力フィールドのタイプである
inputType
値
: ユーザーが入力フィールドに入力した値 (またはデフォルト値)
info
属性を追加することもできます。info属性はツールチップとして表示され、入力フィールドの目的をより詳しく説明するために使用できます。
さらに、使用される入力タイプに応じて、多くの特定の属性が可能である。
コンフィギュレーション・パネルのUIを改善できる高度なテクニックがあります。例えば、複数の入力フィールドを繰り返し折りたたみ可能なコンテナにグループ化したり、特定の入力フィールドだけを条件付きで表示したりすることができます。これらは次の例で紹介します。
選択された値の処理
コンフィギュレーションを定義したら、次はコンフィギュレーションされた値をコンポーネントに組み込みます。設定ファイルの値には、§{ .
.. }§を使ってアクセスできます。}§.
括弧の中では、ドット記法を使って設定オブジェクトにアクセスすることができます。これらのプレースホルダは、プリコンパイルの段階で設定された値に置き換えられます。
例えば、上記の例で定義した "Title "の値をステップ・レイアウトに含めるマッピングを次のように作成することができる:
<mapping>
<ui_element name="Topic">
<param name="content">§{ tab1.title_text.value }§</param>
</ui_element>
</mapping>
ヒントとコツコンフィギュレーションにアクセスする際の典型的な間違いは、最後の「.value」を忘れることです。
また、設定を実装するためにヘルパー関数を使用することもできます。これらの関数は、以下のような多くのことを行うのに役立ちます:
- 設定に基づき、条件付きでワークフローに追加されるルールの実装
- 設定された値をループし、各値に対してルール/アクション/...を作成する。
- 設定された値をさらに処理するために操作する
ヘルパーの使用例も見てみよう:
§{#each tab2.options.value}§
§{#unless @last}§
<rule id="opt_§{key}§">
<expression><![CDATA[ #{event:command} == '§{value}§' ]]></expression>
<actions>
<action ref="set_command"/>
</actions>
</rule>
§{/unless}§
§{#if @last}§
§{#if (and tab2.use_all.value (compare (collection tab2.options.value "size") ">" 1))}§
<rule id="special_opt_§{key}§">
<expression><![CDATA[ #{event:command} == '§{value}§' ]]></expression>
<actions>
<action ref="special_action"/>
</actions>
</rule>
§{/if}§
§{/if}§
§{/each}§
ここで注意すべき点がいくつかある:
- ヘルパー関数の前にある
#は
必須で、エンフレームしてコードブロックを参照していることを示す。 - ヘルパー関数は入れ子にすることができ、内側から外側に向かって評価されます。この例では、
コレクション
ヘルパーはサイズ 3 を返し、それが 1 と比較
されて true となります。この結果とチェックボックスの入力値に「論理 AND」を適用すると、if ヘルパー関数の最終的なブール値が得られます。 - いくつかのブロック・ヘルパーの内部には、データの配列/マップ内の位置を管理するのに役立つ、自動的に設定される変数がいくつかある。これらは
@first
、@last
、@index
、@key
である。
上記のコンフィギュレーション例でプリコンパイルすると、次のようになる:
<rule id="opt_option1">
<expression><![CDATA[ #{event:command} == 'Option 1' ]]></expression>
<actions>
<action ref="set_command"/>
</actions>
</rule>
<rule id="opt_option2">
<expression><![CDATA[ #{event:command} == 'Option 2' ]]></expression>
<actions>
<action ref="set_command"/>
</actions>
</rule>
<rule id="special_opt_option3">
<expression><![CDATA[ #{event:command} == 'Option 3' ]]></expression>
<actions>
<action ref="special_action"/>
</actions>
</rule>
例
次の2つの例では、コンポーネント構成をわかりやすくするための高度な方法を示しています。
入力フィールドのグループ化
inputTypeの "container "を使って、複数の入力フィールドをグループ化することができます。これにより、視覚的にわかりやすくなり、また、要素のグループを複製するなどの機能が可能になります。
以下は具体的な属性とその説明である。
- container group: 異なるタイプのグループを区別する。ワークフローのマークアップで、コンテナを参照するために使用できる。
- repeatable: グループのコピーを作成できるようにする。これらは別々に変更することができ、繰り返し可能な要素の実装を可能にする。
- 折りたたみ可能:グループを最小化し、タイトルだけを表示できるようにします。
- deleteable: コンフィギュレーションからコンテナを削除する。コピーされたコンテナには自動的に設定されますが、ベースコンテナには使用しないでください。
- editable: ユーザーがコンテナのタイトルを変更できるようにする。
"base_sensor": {
"title": "Sensor 1",
"inputType": "container",
"containerGroup": "sensors",
"repeatable": true,
"collapsible": true,
"deleteable": false,
"editable": true,
"value": {
"sensor_shown": {
"title": "Value Shown",
"inputType": "checkbox-input",
"value": false,
"showIfComputed": true
},
"sensor_type": {
"inputType": "file-upload",
"title": "Icon",
"accept": "image/png",
"multiple": false,
"value": "",
"showIfComputed": true
},
"sensor_unit": {
"title": "Unit",
"inputType": "textinput",
"value": "rpm",
"showIfComputed": true
},
"sensor_json_path": {
"title": "JSON Path ",
"inputType": "textinput",
"value": "rpm",
"showIfComputed": true
}
},
"showIfComputed": true,
"container_editing": false,
"container_opened": true
}
入力フィールドの条件付き表示
showif "属性を使って、入力フィールドの表示・非表示の条件を定義することができます。例えば、あなたのコンポーネントが詳細設定が可能なオプション機能を持っているとします。もしその機能が全く使われないのであれば、詳細な設定パラメータは表示したくないでしょう。
例を見てみよう:
{
"Camera":{
"use_camera":{
"title": "Use Device Camera",
"inputType": "checkbox-input",
"value": "false"
},
"zoom_level":{
"title": "Zoom Level",
"inputType": "dropdown-input",
"showIf": "root.Camera.use_camera.value",
"value": { "name": 1 },
"elements": [
{
"name": 1
},
{
"name": 2
},
{
"name": 3
}
]
},
"show_zoom_level": {
"title": "Show Zoom Level",
"inputType": "checkbox-input",
"showIf": "root.Camera.use_camera.value && root.Camera.zoom_level.value.name > 1",
"value": "false"
},
"timeout":{
"title": "Camera Timeout (ms)",
"showIf": "root.Camera.use_camera.value",
"inputType": "textinput",
"value": 5000
}
}
}
下記は期待される出力である。
このチェックボックスはfalseに設定されているので、他のすべての入力フィールドは表示されません。もしtrueであれば、"ズームレベルを表示 "チェックボックス以外はすべて表示されます:
📌課題
- タイトルと、2つのボタンの画像とテキストの設定フィールドを追加します。
- 両方のボタンに画像が存在する場合のみ画像を表示します。片方のボタンにしか画像が設定されていない場合は、テキストのみを表示します。
ダウンロードコンポーネント(事前課題)
ヘルプ&リソース
レイアウトファイルのコンフィギュレーションにアクセスする
レイアウトタグの属性内では、いつものようにコンフィギュレーションにアクセスできます:
<Button Name="§{ ... }§" .../>
しかし、ブロック・ヘルパー関数の場合は、次のように<スクリプト>を
使う必要がある:
<Script>§{#if ...}§</Script>
<Button .../>
<Script>§{/if}§</Script>
また、他のすべてのタグの中で<Script>
タグを使用することができないことにも注意してください。以下は無効な例です:
<Button>
<Script>§{#...}§</Script>
...
<Script>§{/...}§</Script>
</Button>
最後に、ある要素名が2つ存在するが、プリコンパイル後に一度に存在するのはそのうちの1つだけであることが確実であるため、条件指定の結果、構文エラーDuplicate unique valueが
発生した場合は、構文エラーを無視することができます。
ソリューション
以下は、レイアウトの模範解答である:
<LayoutModel Name="ChoiceScreen" Page="DefaultMaster" Orientation="Vertical">
<Content PlaceHolder="Content" Weight="1" Orientation="Horizontal">
<Script>§{#if (and (compare configuration.leftImage.value.image.value "!=" "") (compare configuration.rightImage.value.image.value "!=" ""))}§</Script>
<Button Name="§{configuration.leftImage.value.text.value}§" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle">
<Image Name="LEFT_IMAGE" Weight="0.8" Margin="0,0,0,0" Content="§{configuration.leftImage.value.image.value}§" ScaleType="CenterCrop"/>
<Text Name="LEFT_TEXT" Style="FooterButtonTextStyle" Weight=".2" MaxSize="30" Content="§{configuration.leftImage.value.text.value}§"/>
<Events/>
</Button>
<Script>§{else}§</Script>
<Button Name="§{configuration.leftImage.value.text.value}§" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle">
<Text Name="LEFT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="§{configuration.leftImage.value.text.value}§"/>
<Events/>
</Button>
<Script>§{/if}§</Script>
<Script>§{#if (and (compare configuration.leftImage.value.image.value "!=" "") (compare configuration.rightImage.value.image.value "!=" ""))}§</Script>
<Button Name="§{configuration.rightImage.value.text.value}§" FocusOrder="1" Weight="0.5" Style="ImageButtonStyle">
<Image Name="RIGHT_IMAGE" Weight="0.8" Margin="0,0,0,0" Content="§{configuration.rightImage.value.image.value}§" ScaleType="CenterCrop"/>
<Text Name="RIGHT_TEXT" Style="FooterButtonTextStyle" Weight=".2" MaxSize="30" Content="§{configuration.rightImage.value.text.value}§"/>
<Events/>
</Button>
<Script>§{else}§</Script>
<Button Name="§{configuration.rightImage.value.text.value}§" FocusOrder="1" Weight="0.5" Style="ImageButtonStyle">
<Text Name="RIGHT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="§{configuration.rightImage.value.text.value}§"/>
<Events/>
</Button>
<Script>§{/if}§</Script>
</Content>
</LayoutModel>
ダウンロード・コンポーネント(事後課題)
このようにレイアウトファイルの中からコンフィギュレーションにアクセスするのは面倒です。もしまた同じような状況に遭遇したら、ワイルドカード・ウィジェットUIエレメントを見てみる価値があるかもしれません。この要素は実行時に動的に編集できます。
ワイルドカード・ウィジェットを使った解決策は次のようになる:
まず、2つのボタンのバリアントで2つのPartTemplateを
作成します。
<PartTemplate Name="OptionButtonsWithImage" Orientation="Horizontal">
<Button Name="§{configuration.rightImage.value.text.value}§" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle">
<Image Name="RIGHT_IMAGE" Weight="0.8" Margin="0,0,0,0" Content="§{configuration.rightImage.value.image.value}§" ScaleType="CenterCrop"/>
<Text Name="RIGHT_TEXT" Style="FooterButtonTextStyle" Weight=".2" MaxSize="30" Content="§{configuration.rightImage.value.text.value}§"/>
<Events/>
</Button>
<Button Name="§{configuration.leftImage.value.text.value}§" FocusOrder="1" Weight="0.5" Style="ImageButtonStyle">
<Image Name="LEFT_IMAGE" Weight="0.8" Margin="0,0,0,0" Content="§{configuration.leftImage.value.image.value}§" ScaleType="CenterCrop"/>
<Text Name="LEFT_TEXT" Style="FooterButtonTextStyle" Weight=".2" MaxSize="30" Content="§{configuration.leftImage.value.text.value}§"/>
<Events/>
</Button>
</PartTemplate>
<PartTemplate Name="OptionButtonsText" Orientation="Horizontal">
<Button Name="§{configuration.leftImage.value.text.value}§" FocusOrder="0" Weight="0.5" Style="ImageButtonStyle">
<Text Name="LEFT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="§{configuration.leftImage.value.text.value}§"/>
<Events/>
</Button>
<Button Name="§{configuration.rightImage.value.text.value}§" FocusOrder="1" Weight="0.5" Style="ImageButtonStyle">
<Text Name="RIGHT_TEXT" Style="FooterButtonTextStyle" Weight="1" MaxSize="30" Content="§{configuration.rightImage.value.text.value}§"/>
<Events/>
</Button>
</PartTemplate>
あなたのLayoutModelは
"WildcardWidget "を含むだけです。
<LayoutModel Name="ChoiceScreen" Page="DefaultMaster" Orientation="Vertical">
<Content PlaceHolder="Content" Weight="1" Orientation="Horizontal">
<WildcardWidget Name="Options" PartTemplateName="OptionButtonWithImage" Weight="1"/>
</Content>
</LayoutModel>
最後に、ワークフローの中で、設定に基づいて使用したいPartTemplateを設定する:
<mapping>
<ui_element name="Options">
<param name="parttemplatename">§{#if (and (compare configuration.leftImage.value.image.value "!=" "") (compare configuration.rightImage.value.image.value "!=" ""))}§OptionButtonsWithImage§{else}§OptionButtonsText§{/if}§</param>
</ui_element>
</mapping>
こうすることで、レイアウトファイルからコンフィギュレーションにアクセスする際の構文エラーや特殊なケースを避けることができます。
ワイルドカード・ウィジェット付きダウンロード・コンポーネント(事後課題)