ギャラリーコントロールで複数レコードを編集する

#概要
- PowerAppsで入力フォームを実装する際は編集フォームを使いますが、こちらは単一レコードしか編集できません。
- データを沢山入力させるようなアプリだと、一レコードずつ画面遷移して入力するのは少し不便です。
- そこで、複数レコードを表示できるギャラリーコントロールを使ってデータを編集する方法がないか調べてみました。覚え書きです。

#ギャラリーコントロールでデータ編集
現状ギャラリーコントロールは、フォーム機能がなくデータの閲覧専用です。
既にやっている人がいないか調べたところ、Patch関数を使って実現している事例がいくつかありました。
その中で自分が一番シンプルだと思った方法について解説してみます。
→後述しますが、複数レコードの一括更新はあまりおすすめはできません。

#完成例
例として、設備の点検記録で、設備ごとの測定結果を記録するアプリの場合で考えてみます。
※他に例が思いつかなかったので。
![設備点検結果.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/3239004d-17cf-dbf9-768c-d856b7143236.gif)
このアプリでは、左が親テーブル(設備一覧)、右が子テーブル(設備に紐づく測定データリスト)となっています。
まず親テーブルで記録する設備を選択し、子テーブルで測定結果を記録していきます。

#実装方法
##データソースの準備
データソースはSharePointリストで作成しました。
サンプルアプリの親テーブルの情報は割愛します。
子テーブルの列は以下のように準備しました。

|     列名     	|  データ型  	|            説明            	|
|:------------:	|:----------:	|:--------------------------:	|
| MeasureIndex 	| 数値、必須 	| 行番(子レコード内での連番) 	|
| Measure1     	| 数値       	| 測定値1                    	|
| Measure2     	| 数値       	| 測定値2                    	|
| Measure3     	| 数値       	| 測定値3                    	|
| MeasureId    	| 参照       	| 親テーブルのIDを参照       	|


##コントロールの配置
1. まずギャラリーコントロールを配置しデータソースを指定、レイアウトは空白とします。
2. 次に入力する列数に応じたテキストボックスを配置します。
3. 次に、編集状態を表すチェックボックスを配置します。
4. アイコンとして、＋アイコン(新規レコード)、フロッピー(保存)、ゴミ箱(アイテム削除)を配置
5. 後はギャラリーコントロールの見出しに相当するラベルを追加します(行番、測定1・・)。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/c992155f-89d0-ec23-ce57-878636e55b16.png)

##各コントロールの関数

###①テキストボックス
各テキストボックスのDefaultにThisitem.列名をセットして、フィールドの値を表示します。
行番は編集させたくないので表示モードを無効に。

###②チェックボックス
.Defaultに以下のような関数を入力します。
各テキストボックスのText(変更後の値)とレコードの値(変更前の値)を比較して、一つでも変わっていればチェックボックスがTrueになるようにします。

```JavaScript:OnSelect
TextInput_Index.Text <> Text(ThisItem.MeasureIndex)
|| TextInput1_Measure1.Text <> Text(ThisItem.Measure1)
|| TextInput1_Measure2.Text <> Text(ThisItem.Measure2)
|| TextInput1_Measure3.Text <> Text(ThisItem.Measure3)
```
###③保存アイコン
OnSelectで以下の関数を入力します。
ForAllとFilterでギャラリーのアイテムのうち、チェックがtrueになっているアイテムに対してPatch関数にてアップデートを行います。
列は数値型なのでValue関数にてTextを数値に変換。

```JavaScript:OnSelect
UpdateContext({MessageLabel: "保存処理中です。"});
ForAll(
    Filter(Gallery_ChildItems.AllItems,Checkbox1.Value = true),
    Patch(
        測定結果リスト,
        ThisRecord,
        {
            MeasureIndex: Value(TextInput_Index.Text),
            Measure1: Value(TextInput1_Measure1.Text),
            Measure2: Value(TextInput1_Measure2.Text),
            Measure3: Value(TextInput1_Measure3.Text)
        }
    )
);
Refresh(測定結果リスト);
UpdateContext({MessageLabel: "保存が完了しました。"});
```


###④新規レコードアイコン
OnSelectで以下の関数を入力します。
Patch関数で新規レコードを追加するだけです。
サンプルアプリでMeasureId列は参照列なので、親テーブルの参照情報を直接セットしてます。
それからMeasureIndex(行番)は子レコード内での連番なので、ギャラリーの最後のアイテムの行番+1をセット。

```JavaScript:OnSelect
Patch(測定結果リスト,Defaults(測定結果リスト),
{
    MeasureId:{  
        '@odata.type' : "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedReference",  
        Id:Value(Label_SelectedId.Text),  
        Value:Label_SelectedId.Text
    },
    MeasureIndex:Last(Gallery_ChildItems.AllItems).MeasureIndex+1
});
UpdateContext({MessageLabel:"新規行を追加しました。"});
```

###⑤ゴミ箱アイコン
OnSelectで```Remove(測定結果リスト,ThisItem)```
新規と削除はRefreshを入れなくても勝手に更新されるよう？です。

###⑥新規アイコンと保存アイコンの編集状態切り替え
レコードの編集中に新規ボタンを押すと勝手にRefreshされ保存前のデータが消えてしまうため、編集中は新規アイコンを押せないようにします。

まずラベルを一つ追加し、Label_UnSavedItemsとしてDefaultに```CountIf(Gallery_ChildItems.AllItems,Checkbox1.Value=true)```を設定。編集中のアイテムをカウントします。
※サンプルアプリで未保存の右にある「0」

次に新規ボタンのDisplayModeに以下を設定。

```JavaScript:DisplayMode
If(
    Value(Label_UnSavedItems.Text) > 0,
    DisplayMode.Disabled,
    DisplayMode.Edit
)
```
保存ボタンのDisplayModeは上記のDisabledとEditを逆に設定します。

完成です。


#考察
ただしForAllによる一括更新はあまりオススメはできません。
データのバリデーションができないため、エラーが発生してもどのレコードが駄目だったか分からないためです。

#検証
- Patch関数の戻り値でエラー判定できないか検証してみました。Patch関数は更新に成功すると戻り値として更新したレコードがセットされます。
- 検証として必須フィールド(サンプルでMeasureIndex列)、数値フィールド(サンプルでMeasure1列)に空白やBlankを設定してPatchを実行。
- 結果、何れもエラーとはならず、フィールドには以前の値がセットされて更新が成功してしまいました。
- 要するに、数値列を空欄で保存しても、元入っていた数値に戻ってしまい、意図しない結果が生まれてしまいます。
- 簡単な対応策としては、事前にif関数で空白かどうかチェックして、空白の場合は0を入れてやると良さそうです。

```JavaScript:検証に使用したコード
//ボタンクリックでギャラリーの最後のレコードをPatchで更新し、戻り値=更新後のレコードを確認する。
Clear(Record_Updated);
With(
    {LastRecord:Last(Gallery_ChildItems.AllItems)},
    //Patchの結果をRecord_Updatedにセットして確認
    ClearCollect(
        Record_Updated,
        Patch(
            測定結果リスト,
            LastRecord,
            {
                //必須フィールドをブランクにしても元の値がセットされる
                MeasureIndex:Blank(),
                //数値フィールドをブランクにしても元の値がセットされる
                Measure1: If(LastRecord.TextInput1_Measure1.Text="",Blank(),Value(LastRecord.TextInput1_Measure1.Text)),
                Measure2: Value(LastRecord.TextInput1_Measure2.Text),
                //簡単な対応としては空白かチェックして0をセット
                Measure3: If(LastRecord.TextInput1_Measure3.Text = "",0,Value(LastRecord.TextInput1_Measure3.Text))
            }
        )
    )
)
```

#参考にした動画
https://www.youtube.com/watch?v=mj4mKS1hS7M