Power Apps で親子関係のある入力フォームを作ってみる
はじめに
親子関係のあるような複雑なデータを編集する場合は、できればモデル駆動型アプリを使いたいです。ただし、ライセンスの関係で Microsoft Dataverse を使えない場合もあります。そのため、SharePoint リストを使ったキャンバス アプリで親子関係を持つデータを 1 つの画面で編集するアプリを作成します。今回は見積書を作成するサンプルアプリを作成します。
実行手順
リストの作成
SharePoint の任意のサイトに、以下の 2 つのリストを作成します。
見積書リスト
- 表示名、内部名、種類は次の通りである
| 表示名 | 内部名 | 種類 |
|---|---|---|
| 見積番号 | Title | 1行テキスト |
| 見積日 | QuotationDate | 日付と時刻 |
| 会社名 | CompanyName | 1行テキスト |
| 納期 | Delivery | 1行テキスト |
| 有効期限 | Expiry | 1行テキスト |
| 支払条件 | PaymentTerm | 1行テキスト |
見積書明細リスト
- 表示名、内部名、種類は次の通りである
| 表示名 | 内部名 | 種類 |
|---|---|---|
| 見積書 ID | QuotationId | 数値 |
| 連番 | Index | 数値 |
| 摘要 | Title | 1行テキスト |
| 数量 | Quantity | 数値 |
| 単価 | UnitPrice | 数値 |
| 金額 | Amount | 集計値 |
注意点として、親子関係を定義する際に参照列を使わず、ID の値を格納する列を作成します。参照列は Power Apps では委任の関係から扱いづらくなるため、この方式を採用しています。
アプリの作成
一覧画面

一覧画面は本題ではないため詳細は省略します。見積書リストのアイテムの一覧をギャラリーで表示します。
作成画面

上部にフォームで見積書リストのアイテム、下部にギャラリーで見積書明細リストのアイテムの一覧を表示します。
スクリーンの OnVisible プロパティでは QuotationDetails と QuotationDetailsIndexed という 2 つのコレクションを初期化します。QuotationDetails はギャラリーにバインドされるデータで、QuotationDetailsIndexed は保存時に連番を付けるために使用します。
NewForm(CreateForm);
ClearCollect(QuotationDetails, Filter(見積書明細, '見積書 ID' = 0));
ClearCollect(QuotationDetailsIndexed, Filter(見積書明細, '見積書 ID' = 0))
ギャラリーの Items プロパティには、アイテムが連番で並ぶように次の数式を設定します。
SortByColumns(Filter(QuotationDetails, 連番 > 0), "Index", Ascending)
ギャラリーのテキスト入力の OnChange プロパティでは、入力したテキストがコレクションに反映されるように Patch を呼び出します。
Patch(QuotationDetails, ThisItem, { 摘要: Self.Text })
保存ボタンの OnSelect プロパティでは、まず親の見積書リストのアイテムを SubmitForm で登録します。次に子の見積書明細リストのアイテムを ForAll と Patch を使って登録します。ギャラリーは項目が追加・削除されて連番が続きになっていない場合があります。いったん QuotationDetails のコレクションを QuotationDetailsIndexed にコピーする形で連番を振り直します。
If(
SubmitForm(CreateForm),
Clear(QuotationDetailsIndexed);
ForAll(
QuotationDetails,
Collect(
QuotationDetailsIndexed,
{
ID: ThisRecord.ID,
'見積書 ID': CreateForm.LastSubmit.ID,
連番: CountRows(QuotationDetailsIndexed) + 1,
摘要: ThisRecord.摘要,
数量: ThisRecord.数量,
単価: ThisRecord.単価
}
)
);
ForAll(
QuotationDetailsIndexed,
Patch(
見積書明細,
ThisRecord
);
Navigate(
MainScreen,
ScreenTransition.None
)
)
)
更新画面

基本的には作成画面と大きく変わりません。
スクリーンの OnVisible プロパティではコレクションを初期化しますが、見積書 ID でアイテムを取得するようにします。
EditForm(UpdateForm);
ClearCollect(QuotationDetails, Filter(見積書明細, '見積書 ID' = MainGallery.Selected.ID));
ClearCollect(QuotationDetailsIndexed, Filter(見積書明細, '見積書 ID' = 0))
ギャラリーから項目を削除する場合、新規に追加したものであればそのままコレクションから削除します。既存のアイテムを削除する場合はリストから削除する必要があるため、そのようなアイテムはいったん連番を -1 としておき、保存時にまとめて削除します。削除ボタンの OnSelect プロパティは次のようになります。
If(ThisItem.ID = 0, Remove(QuotationDetails, ThisItem), Patch(QuotationDetails, ThisItem, { 連番: -1 }))
保存ボタンの OnSelect プロパティでは、作成画面とほぼ同じですが、削除とマークされたアイテムを削除するロジックが追加されます。
If(
SubmitForm(UpdateForm),
Clear(QuotationDetailsIndexed);
ForAll(
Filter(
QuotationDetails,
連番 < 0
),
Remove(
見積書明細,
ThisRecord
)
);
ForAll(
Filter(
QuotationDetails,
連番 > 0
),
Collect(
QuotationDetailsIndexed,
{
ID: ThisRecord.ID,
'見積書 ID': UpdateForm.LastSubmit.ID,
連番: CountRows(QuotationDetailsIndexed) + 1,
摘要: ThisRecord.摘要,
数量: ThisRecord.数量,
単価: ThisRecord.単価
}
)
);
ForAll(
QuotationDetailsIndexed,
Patch(
見積書明細,
ThisRecord
);
Navigate(
MainScreen,
ScreenTransition.None
)
)
)
削除ボタンの OnSelect プロパティでは、親子関係のアイテムをすべて削除します。
RemoveIf(見積書明細, '見積書 ID' = MainGallery.Selected.ID);
Remove(見積書, MainGallery.Selected); Navigate(MainScreen, ScreenTransition.None)
実行結果
実行してみます。作成画面で項目を入力して保存をクリックします。

見積書リストにアイテムが追加されます。

同時に見積書明細リストにもアイテムが複数追加されていることがわかります。

明細を編集します。マウス を削除して LAN ケーブル を追加します。

見積書明細リストに編集が反映されています。連番も正しく振られています。

おわりに
SharePoint の親子関係の表現には参照列を使いたくなりますが、参照列は ID でのフィルターが委任制約に引っかかるため、使わないのが正解です。件数が増えてきた場合に備えて、適切にインデックスを貼っておくとよいです。
Discussion
アプリの再現を行おうと試行しているのですが作成画面の見積詳細の追加マークの処理や入力部分をどのように作成しているのかが分からず再現ができない状況です。
細かい手順の解説やアプリのコピーなどいただけたりはしませんでしょうか?