#PowerApps で TreeView Controlを作る

#つくるもの
今回は、階層型のデータから、PowerAppsでTreeView Controlを作成する方法を紹介します。
データソースとしてはSharePoint Onlineのカスタムリストを利用しています。
![tree構成.PNG](https://qiita-image-store.s3.amazonaws.com/0/202779/ab2a4ce8-ed6a-4e40-2607-51e78637b966.png)
* 参考にしたビデオは[こちら](https://www.youtube.com/watch?v=qJv8IqqL01A)です。

最終的には下のように動くアプリを作成しました。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">4階層でもやり方は同じですね。 <a href="https://t.co/9oTzxz8vfM">pic.twitter.com/9oTzxz8vfM</a></p>&mdash; ながちん(Hiro)  #PowerAddict (@mofumofu_dance) <a href="https://twitter.com/mofumofu_dance/status/1082423421789646848?ref_src=twsrc%5Etfw">2019年1月7日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#準備
##SharePointカスタムリスト
(データソースがSharePointカスタムリストである必要は必ずしもありません。利用しやすいデータソースを使ってください。)
データソースのSharePointリストは下図のような構成で作っています。
![SPO.PNG](https://qiita-image-store.s3.amazonaws.com/0/202779/bbcf0dcd-a3fe-2d05-8217-9dda76690b3a.png)
**List Name:**
  TreeViewElement
**Columns:**
 - spElmntName : Title列。 各要素の名前を格納
 - spElmntID : Single line text(必須＆一意） 各要素の名前の重複を許可するために作成
 - spParentID : Lookup このリストのspElmntIDを参照する列
 - spPreantID:Title : Lookup列の結果のspElmntName
 - spElmntLv : Choice 1~4までのレベル(階層)を指定する列

##PowerApps側のCollection
データソースからTreeView Controlを作成するために、PowerApps側で中間の加工データ (Collection)を作成します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/202779/a8f508f9-19b1-4166-bae6-4aac1bcc32ac.png)
元のデータに加えて、TreeViewの開閉のために"Show"という列を追加します。
また、正しくソートするために、"TreePath"という列を追加します。
今回のTreeViewはPowerAppsのGalleryコントロールを利用しますが、そのItemsにはSortByColumns(XXXX,"TreePath",Ascending)を指定します。
具体的にはCollectionを作成するためのアクション（ボタンのOnSelectやスクリーンのOnVisible）に以下のコードを設定します。

```XXX.OnXXX
ForAll(
    TreeViewElement,
    Collect(
        colTreeElmnt,
        {
            ElmntName: Title,
            ElmntId: spElmntID,
            Lv: Value(spElmntLv.Value),
            ParentId: spParentID.Value,
          //Expand/collapseを制御するための列'Show'を追加  
            Show: If(
                Value(spElmntLv.Value) = 1,
                true,//初期状態ではspElmntLv=1の要素のみ表示
                false
            ),
            TreePath: ""//TreePathは次に定義
            
        }
    )
);
//TreePathを定義する
UpdateIf(
    colTreeElmnt,
    Lv = 1,
    {TreePath: ElmntId},
    Lv = 2,
    {TreePath: Concatenate(ParentId,">",ElmntId)},
    Lv = 3,
    {TreePath: 
    //Lv3のTreePathは、自分のParendIdと元のデータソースのspElmntIDが一致するレコード（自分の親レコード）のspParentIDを利用する
      Concatenate(LookUp(TreeViewElement,spElmntID = ParentId,spParentID.Value),">",ParentId,">",ElmntId)
    },
    Lv = 4,
    {TreePath:
    //Lv4のTreePathは、Lv3の手順で得られたspParentIDと、元のデータソースのspElmntIDが一致するレコード（自分の親の親のレコード）のspParentIDを利用する
      Concatenate(
          LookUp(TreeViewElement,
            spElmntID = LookUp(TreeViewElement,spElmntID = ParentId,spParentID.Value),
            spParentID.Value),
          ">",LookUp(TreeViewElement,spElmntID = ParentId,spParentID.Value),">",ParentId,">",ElmntId
      )
    }
)
```
TreePathの構成は少し複雑ですが、
自分のLevelが1なら、自分自身のID。Levelが2なら、自分と親要素のIDをConcatenateで結合。
Levelが3なら、自分の親要素(spParentID)を使ってリストのspElmntIDを検索し、一致したレコードのspParentIDを結合
というように、自分の要素のLevelに応じてTreeのたどり方を指定しています。

このようにして生成されたCollectionをGalleryに表示すると以下のようになります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/202779/d6206da9-9d1a-ada2-da8b-705ff23d88a7.png)
ここで、GalleryのItemsプロパティは以下のように設定しました。

```Gallery.Items
SortByColumns(colTreeElmnt,"TreePath",Ascending)
```

ここまででほとんど完成です。あとは要素の開閉やインデントの調整をしていきます。

#Gallery Control
事前に名前の前に付ける画像を用意しておきます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/202779/e86fc184-eefc-e3a0-a60b-09350eb2b0db.png)
子要素がある場合にはplus/minusを、子要素がない場合にはdotを表示させます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/202779/513a7406-4883-2021-e7be-13649febf84b.png)
あとは設定をしていくだけなので、各プロパティを順に記載します。

```Gallery.Items
//Show=trueだけを表示する
SortByColumns(Filter(colTreeElmnt,Show=true),"TreePath",Ascending)
```

```Image2.Image
If(
    CountRows(
        Filter(
            colTreeElmnt,
            ParentId = ThisItem.ElmntId
        )
    ) = 0,
    dot,//自分のIDがを親要素IDにセットされているレコードがなければdot
    LookUp(
        colTreeElmnt,
        ParentId = ThisItem.ElmntId
    ).Show = true,
    minus,//子要素が表示されていればminus
    plus//子要素が表示されていなければplus
)
```

```Image2.OnSelect
If(
    //自分のIDを親IDとしてもつレコードが0でなく、それらが表示されているか判定
    CountRows(Filter(colTreeElmnt,ParentId=ThisItem.ElmntId))<>0&&LookUp(colTreeElmnt,ParentId=ThisItem.ElmntId).Show= true,
    //表示されていれば、自分のIDをTreePathに持っている列をすべて非表示にして、自分を再表示
    UpdateIf(colTreeElmnt,CountRows(Filter(Split(TreePath,">"),Result=ThisItem.ElmntId))<>0,{Show: false });UpdateIf(colTreeElmnt,ElmntId=ThisItem.ElmntId,{Show: true}),
    //表示されていなければ、自分を親とする要素（直下の要素）を表示
    UpdateIf(colTreeElmnt,ParentId=ThisItem.ElmntId,{Show:!Show})
)
```

ポイントは、Lv1やLv2の場合、自分の下にある要素全てを閉じるために、
```UpdateIf(colTreeElmnt,CountRows(Filter(Split(TreePath,">"),Result=ThisItem.ElmntId))<>0,{Show: false })```
このコードを入れていることです。

あとはインデントの調整だけですが、

```Image2.X
ThisItem.Lv*40-30
```


```Title3.X
30+ThisItem.Lv*40
```
このようにレベルに応じてずらしています。

#最後に
何かわからないことがあればmofumofu_danceまでご連絡ください。