PowerAppsで遭遇する５つの委任問題とちょっと強引な回避方法（SharePointリスト利用時）その３

(2019/08/13)
ユーザー列のフィルタリングが委任可能になったことに伴い、本記事の以下箇所の記載を微修正しました。

ケース４：動的にフィルタ処理したい
　の 改善２：登録者のフィルタ処理の記述を簡素化する

---

委任シリーズ第3回目です。本記事をご覧になりたい方は、先に前回の記事をご覧いただくと話がスムーズです↓
[PowerAppsで遭遇する５つの委任問題とちょっと強引な回避方法（SharePointリスト利用時）その１](https://qiita.com/KodamaJn/items/4341865e6784847a005d)
[PowerAppsで遭遇する５つの委任問題とちょっと強引な回避方法（SharePointリスト利用時）その２](https://qiita.com/KodamaJn/items/148eb21be6fd0f31951c)
<br>

最終回（？）となるその３では、列の種類に関わらずそこそこ委任問題に遭遇した以下のケース

　ケース４：動的にフィルタ処理したい
　ケース５：動的にソート処理したい

について、私が実際に行っている、委任問題のちょっと強引な回避方法について紹介します。


# 紹介で利用するデータソース
ここでは、以下のSharePointリスト「テストリスト」について考えます。
※列の種類は、前回（その２）の記事の作業終了時点の状態です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/d80f7cec-f4e5-c49d-df57-3a7c37b4f915.png)


#ケース４：動的にフィルタ処理したい
例えばステータスで、
「すべてのステータス」
「"新規"ステータスのみ」
「"新規"ステータス以外」
をユーザー操作で切り替えて表示する、などの需要は結構あるかと思います。

ということで早速、数値列"ステータス"に対して、上記のフィルタ処理を切り替える機能を追加してみます。
こんなイメージ↓
![Rec3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/d95751aa-3c53-70dc-e8cf-d6446b9d2ff8.gif)

まず、ラジオボタンを追加し、Itemsに以下を記載しておきます。

```:Items
["すべてのステータス", "新規ステータスのみ", "新規ステータス以外"]
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/8177caa0-bf13-3e6b-915e-4d64c3fcfe0c.png)

そして、ラジオボタンの選択に応じてフィルタを変える処理をGalleryに書いてみます。
フィルタの条件を変えるのだから、Filter関数内で分岐すればいいよね…
※ "すべてのステータス"の時も何かしら処理を書かないとFilter関数の書式を満たせないので、"すべてのステータス"の時は全ての値を網羅できるよう「ステータス >= 1」と書いている

```:Items
Filter(テストリスト, 
    Switch(Radio1.Selected.Value, 
        "すべてのステータス", ステータス >= 1,
        "新規ステータスのみ", ステータス = 1,
        "新規ステータス以外", ステータス <> 1
    )
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/abea8334-2d95-488b-eff8-359f14b9cb96.png)
委任警告さん、しつこいね。
どうやらFilter関数内の分岐処理が委任できないようです。
Switch文の代わりにIf文を使用しても委任警告に出会うため、私が知る限り、SharePointではフィルタ内の条件分岐は委任できません。
<br>

### 回避方法
委任警告の回避方法自体は単純で、条件分岐をFilter関数の外に出せば解決します。

```:Items
Switch(Radio1.Selected.Value, 
    "すべてのステータス", Filter(テストリスト, ステータス >= 1),
    "新規ステータスのみ", Filter(テストリスト, ステータス = 1),
    "新規ステータス以外", Filter(テストリスト, ステータス <> 1)
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/1db078ef-9e78-2dcc-cd37-12d1860cb11b.png)
委任警告が消えました。めでたしめでたし。
PowerApps側から見ればどちらも結果的には同じ処理に見えますが、SharePoint側から見ると両者は異なるため、このような違いが生まれます。
イメージ図↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/dd8ff6c9-7dd5-9a5e-de4e-fb01d9bb68df.png)

ただこの方法だと、フィルタ条件が増えれば増えるほどGalleryのItemsに記載するFilter関数の数が肥大化します。
例えば上記に加えて登録者が"全員"と"本人のみ"を切り替える機能を追加するには、ステータス3条件×登録者2条件＝6個のFilter関数が必要です。
物理的には可能ですが、Itemsの可視性が失われ、修正が困難になってしまいます。

そこで、フィルタ条件が増えてもいいように、**フィルタ処理の記載を簡素化する方法**について考えてみることにしました。
<br>

###改善１：ステータスのフィルタ処理の記述を簡素化する
ここで話は前回の記事（ケース３）のまとめに戻ります。

> ステータス名を保持したいのなら、なぜ"新規"などのステータス名をそのままテキスト列に保存せずに、わざわざ数値列を使って面倒な処理にしたの？？

実はこれには目的がありまして、
「すべてのステータス」
「"新規"ステータスのみ」
「"新規"ステータス以外」
などの様々な条件を、**"なるべくフィルタ処理の記述を簡単に"**実装するために、数値列を使用しました。

> (ﾟДﾟ)ﾊｧ???

早速やってみます。
<br>

"新規"などのステータスは、数値列に数値として保存しています。
再掲↓

| 値 | ステータス |
|---:|:----------|
| 1  | 新規      |
| 2  | 対応中    |
| 3  | 完了      |

まず、AppのOnStart処理で、ステータス値の範囲を指定する２つの変数：LowerとUpperを以下のように初期化します。

```:OnStart
Set(Lower, 1);
Set(Upper, 3);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/036d1760-06c2-333d-abd4-ee495fa2f60f.png)

追記したら、OnStartの実行をお忘れなく。
続いて、最初に追加したラジオボタンのOnChangeで、ラジオボタンの選択に応じて上記の変数を以下のように更新させます。

```:OnChange
Switch(Radio1.Selected.Value,
    "すべてのステータス",
        Set(Lower, 1); Set(Upper, 3),
    "新規ステータスのみ", 
        Set(Lower, 1); Set(Upper, 1),
    "新規ステータス以外", 
        Set(Lower, 2); Set(Upper, 3)
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/1df184fe-8965-eeff-731a-09158bad70a1.png)


最後に、Galleryのフィルタ処理を以下のように書き換えて完了です。

```:Items
Filter(テストリスト, 
    ステータス >= Lower, ステータス <= Upper
)
```

![Rec1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/a05a0bc9-61d7-30e9-aa6c-fab6cadc3673.gif)

このフィルタ処理では、ステータスの範囲を以下のように変数で定義しています。

```
Lower <= ステータス <= Upper
```

この考え方なら、
　"すべてのステータス"なら、Lower=1、Upper=3 としてステータスの範囲を1～3まで広げる
　"新規ステータスのみ"なら、Lower=1、Upper=1 としてステータスの範囲を実質[ステータス=1]とする
というフィルタ処理が、**条件分岐せずに**可能になります。

この考え方は日付範囲のフィルタリングにも利用できますので、処理したいフィルタの項目が増えても、フィルタの条件式を何個も分岐して記載する負担は大きく軽減されるかと思います。

> ステータスが1または3の時はどうすんの？

あー。。
今回のようにステータスが３つであれば、Lower=3, Upper=1とすれば強引にできますが（笑）、ステータスが４つ以上で"2または4の時"などは表現できないので、場合によっては条件分岐が必要になるかもしれませんm(_ _)m
<br>

###改善２：登録者のフィルタ処理の記述を簡素化する
ここで話は前々回の記事（ケース１：人のフィルタリング）に戻ります。
ケース１の完成イメージでは、Galleryのフィルタ処理が2行必要だったため、これも1行にまとめます。

先ほどの改善１のステータスフィルタ処理に、登録者のフィルタ処理を加えていきます。
まず、登録者のフィルタ処理を切り替えるためのスイッチを用意します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/9535864d-b0ce-8db7-ee56-7d8671cb76ca.png)

続いて、スイッチのチェックを入れた時とチェックを外した時に変数を更新する処理を記載します。
チェックを入れた時（本人のみ表示）は変数に本人のEmailを格納し、チェックを外した時（全員分表示）は変数の中身を空にしています。

```:OnCheck
Set(UserEmail, User().Email)
```

```:OnUncheck
Set(UserEmail, "")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/9c8760b0-6269-4a02-8699-643c444ed0c0.png)

最後に、Galleryのフィルタ処理を以下のように書き換えて完了です。

```:Items
Filter(テストリスト, 
    ステータス >= Lower, ステータス <= Upper,
    StartsWith(登録者, UserEmail)
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/aa8cc4d4-3478-54ba-5323-ebec4a757e4d.png)
StartsWith関数は、あるテキスト文字列が別のテキスト文字列で始まるかどうかを確認するもので、ここでは登録者メールアドレスが変数UserEmailで始まるかどうかをチェックしています。
この記載だけで「全員分表示」も処理が可能なのは、StartsWith関数が「検索するテキストが空の文字列の場合、StartsWith は true を返す」仕様であるためです。
ちなみにこのStartsWith関数は、アプリをデータソースから自動生成した際にGallery処理に最初から備わっているので、仕様含めご存知の方も多いかと思います。
参考：[PowerApps の EndsWith 関数と StartsWith 関数](https://docs.microsoft.com/ja-jp/powerapps/maker/canvas-apps/functions/function-startswith)


### 完成！
以上で、「ケース４：動的にフィルタ処理したい」は完成です。
ステータスと登録者の6条件のフィルタ処理が、たった1つのFilter関数で実現できました。

完成イメージ↓
![Rec2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/db49867e-334b-dbd0-500c-9acba9767d14.gif)

<br>

#ケース５：動的にソート処理したい
例えば、
「日付順」
「ステータス順」
をユーザー操作で切り替えて表示する、などの需要は結構あるかと思います。

ということで早速、ソートする列を切り替える機能を追加してみます。
なお、昇順/降順の切り替え処理は、アプリをデータソースから自動生成した際にGallery処理に最初から備わっているので、ここでは説明を割愛しすべて昇順（Ascending）固定で説明します。

まず、ラジオボタンを追加し、Itemsに以下を記載しておきます。

```:Items
["日付順", "ステータス順"]
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/a9c262d7-427f-e7fa-e0ca-1c06c36a057f.png)


そして、ラジオボタンの選択に応じてソート対象列を変える処理をGalleryに書いてみます。
ケース４で完成したフィルタ処理を、ソート関数SortByColumnsで包みました。
（"CreatedValue"は登録日時数値列、"Status"はステータス列の列名です）
ソート条件を変えるのだから、こんな感じでSortByColumns関数内で列の指定部分を分岐すればいいよね…

```:Items
SortByColumns(
    Filter(テストリスト, ステータス >= Lower, ステータス <= Upper, StartsWith(登録者メールアドレス, UserEmail)),
    If(Radio2.Selected.Value = "日付順", "CreatedValue", "Status"),
    Ascending
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/edfe776b-b807-4761-58ca-0f97ad2dac5a.png)
委任警告さん、もうやめて！！！
どうやらSortByColumns関数内の分岐処理が委任できないようです。
残念ながら、分岐処理の代わりに列名が格納された変数に置き換えても、委任警告に出会いました。
よって、私が知る限り、SharePointではソート列の動的な指定は委任できません。
<br>

### 回避方法
委任警告の回避方法自体は単純で、条件分岐をSortByColumns関数の外に出せば解決します。

```:Items
If(Radio2.Selected.Value = "日付順", 
    SortByColumns(Filter(テストリスト, ステータス >= Lower, ステータス <= Upper, StartsWith(登録者メールアドレス, UserEmail)), "CreatedValue", Ascending),
    SortByColumns(Filter(テストリスト, ステータス >= Lower, ステータス <= Upper, StartsWith(登録者メールアドレス, UserEmail)), "Status", Ascending)
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/68b8045f-4cae-179e-e2d3-64e4b1ce478d.png)
委任警告が消えました。めでたしめでたし。
<br>

…って、おい！！
それじゃケース４の途中と同じで可視性が失われるじゃねぇか！それだけじゃ"回避"とは呼ばせねぇぞ！！

ごめんなさい。。
私には、**"SharePoint側"**にソート列の動的な指定を委任する方法や驚くべき回避方法は発見できませんでした。

> **"SharePoint側"**には委任できない、ならいっそのこと…

ということで、最終手段を取ります。

###最終手段：フィルタ処理結果を都度Collectionに格納し、Collectionに対して動的ソートを行う
先ほどのSortByColumns関数内でのソート列の動的な指定で委任警告が出たというのは、つまり
「ソート列の動的な指定を"SharePoint側"に委任できなかった」
ということになります。

逆に言えば、これまでケース１～４まで行ってきたフィルタ処理はなんとか委任警告を回避できたのだから、**フィルタ処理とソート処理を分けて**考え、フィルタ処理はSharePointに委任し、**ソート処理は自分で**やればよいのではないか！
ということで、フィルタ処理結果を一旦変数に格納し、PowerApps側でその変数に対してソート処理してみることにしました。

早速やってみます。
まず、AppのOnStart処理で、フィルタ処理結果をCollection：DataListに格納します。
（登録者フィルタで使用する変数UserEmailを初期化していなかったので、ついでに追記）

```:OnStart
Set(UserEmail, "");
ClearCollect(DataList, 
    Filter(テストリスト,ステータス>=Lower,ステータス<=Upper,StartsWith(登録者メールアドレス,UserEmail))
);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/473082f9-fd5f-56a0-4f7c-f2835b4af3d2.png)

追記したら、OnStartの実行をお忘れなく。
これだけだと、アプリ起動時にしかSharePoint側からデータを取得できないので、これまで追加したフィルタ処理の切り替えコントロール（ラジオボタンや切り替えスイッチ）を変更する際にも都度SharePoint側からデータを取得するように処理を追記します。

各コントロールに同じ処理を書いてもよいのですが、煩雑になるので、再読み込みボタンに処理を集約します。
再読み込みボタンのOnSelect処理を以下に書き換えます。
（AppのOnStartのClearCollect処理と同じです）

```:OnSelect
ClearCollect(DataList, 
    Filter(テストリスト,ステータス>=Lower,ステータス<=Upper,StartsWith(登録者メールアドレス,UserEmail))
);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/1989dff0-ffbf-b39c-288d-8b34758142d9.png)

続いて、フィルタ処理の切り替えコントロールの変更時に再読み込みボタンの処理が発動するよう、Select処理を追記します。
ステータスフィルタ処理用のラジオボタン↓

```:OnChange
Select(IconRefresh1);
// 追記する1行前の処理の末尾に ; がないとエラーになるので注意
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/4e8c209e-ba54-af99-b068-d0a16ddfa2c0.png)

登録者フィルタ処理用の切り替えスイッチ↓

```:OnChange
Select(IconRefresh1);
// 追記する1行前の処理の末尾に ; がないとエラーになるので注意
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/337bd3e6-9f4b-71da-a3b1-bd243f4bb606.png)

最後に、Galleryのフィルタ処理を以下のように書き換えます。
（SortByColumnsの第一引数をDataListに置き換えただけ）

```:Items
SortByColumns(DataList,
    If(Radio2.Selected.Value = "日付順", "CreatedValue", "Status"),
    Ascending
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/6d6b3820-eebd-4ebf-1ab3-3e72d61e5df7.png)


完成！
ってあれ？なんかエラーが出てる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/0e746044-b4d8-c2dc-2923-dc4ee1c2bee4.png)

DetailScreenのアイテム削除処理でエラーが発生しています。
これは、「SharePointのテストリストから、Galleryで選択されているアイテムを削除する」というRemove処理です。
先ほどGalleryのItemsをSharePointリストからコレクションDataListに変更したことで、「SharePointのテストリスト」と「コレクションDataList」の間でレコードの互換性が取れなくなり、SharePoint側が「Galleryで選択されているアイテム」を認識できなくなったことが原因です。
エラーのイメージ↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/99e3fd64-582f-e69a-ea82-928f525610b0.png)

仕方ないので、無理やりレコードを繋ぎます。
IconDeleteのOnSelect処理を以下に書き換えます。

```:OnSelect
Remove(テストリスト, LookUp(テストリスト, ID = BrowseGallery1.Selected.ID));
If (IsEmpty(Errors(テストリスト, LookUp(テストリスト, ID = BrowseGallery1.Selected.ID))), Back())
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/86ef1028-d76a-f155-9746-02d433552722.png)


<br>

### 完成！
以上で、「ケース５：動的にソート処理したい」は完成です。

完成イメージ↓
![Rec3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405544/c8e7cb25-2bee-f91e-fb58-17a74df714d4.gif)



<br>

#まとめ
Galleryを動的にフィルタ＆ソート処理するために、最終的にSharePointへの委任を"諦める"という、強引な方法というか一部妥協する方法についてご紹介しました。

<br>

これまで３回に分けてご紹介した委任回避方法ですが、ここまでやればSharePointリストに対するフィルタ処理の大半は委任警告を回避できるかと思います。
（あくまで私の知見であり、動作保証はできないことをご了承ください）
ただ、回避したことでGallery以外の処理が複雑化していることも事実ですので、どこまでPowerAppsで作り込むかは微妙なところかもしれません。

<br>

委任に悩むすべての方へ、少しでもご参考になれば幸いです。
もっといい方法をご存知の方は、是非教えてください！