はじめに
Power Apps にはRedo & Undo アイコン(やり直し & 元に戻すアイコン)が存在しますが、これらの動作を実現するための機能は備わっていません。
今回はこのRedo & Undo 機能の実装方法の一例を纏めたいと思います。
今回行う実装は下の例のようなものになっています。
#PowerApps で Redo & Undo を行うだけのSample 作成しようとしていたら、いつのまにかちょっと機能盛ったタスク管理アプリが作成されていた pic.twitter.com/2rfk5hIsCI
— コルネ (@koruneko32767) August 17, 2021
Redo & Undo 機能を実装する
操作履歴を取得する
Redo & Undo を行うにはユーザがどのような操作をしたか、またはユーザの操作によるデータの差分などが必要になってきます。
今回は一番実装が簡単なデータの全文を変更履歴として保持しようと思います。
この履歴の保持方法は単純にコレクションデータをコレクションに順に追加してあげればよいのです。
しかし、そうしてしまいますと、履歴のためのコレクションのサイズが大きくなってしまい、アプリのパフォーマンスが落ちてしまう可能性があります。
なので、コレクションのデータはJSON に変換してテキスト形式で保持するようにしたいと思います。
履歴データの初期化は、Redo & Undo 機能をつけたいコレクションの初期化の次に行うこととします。
よって以下のように式を記載します。
Screen.OnVisible
ClearCollect( Tasks, { _id:1, task:"メールの返信", details:"XXXさんにメールを返信する。", startDate:Today(), endDate:Today() + 1, level:"Normal", status:"未開始", achievement:0, viewDetail:false } ); ClearCollect( History, { _id:1, data:JSON(Tasks, JSONFormat.IgnoreBinaryData) } ); UpdateContext({nowHistoryID:1})
Tasks
がRedo & Undo 機能をつけたいコレクションの宣言です。
History
が履歴保持のためのコレクションです。
nowHistoryID
は現在のデータのID です。
つまり、 LookUp(History, _id = nowHistoryID, data) = Tasks
となります。
次にこの History
に元のコレクション( Tasks
)の変更に従って履歴情報を更新しようと思います。
履歴情報保持のは Tasks
コレクションを更新したときに適宜処理を呼び出すことで実施できるようにします。
そのためにボタンを追加し、 OnSelect
に履歴保持のための処理を記載していきます。
このときの処理ですが、以下3つの処理を実装する必要があります。
- 現在利用しているデータのID より大きいID のデータの削除
- 現在利用しているデータのID を加算
- 履歴の保持
まず 2 と 3 についてです。
2 は履歴が1つ追加されるわけですので nowHistoryID
の値を1加算する処理が必要になってきます。
続いて3では、 OnVisible
のときと同じような処理で、履歴をコレクションに保持します。
最後に、1の処理です。
これは、Undo (元に戻す)処理を行った後に Tasks
コレクションに対して操作を行った場合に必要になってくる処理です。
Undo 処理を行うと、1つ前の処理に戻るわけですので例えば nowHistoryID
の値が5であった場合、Undo 後の nowHistoryID
の値は4になります。
その後ユーザ操作によって履歴を更新していくにあたり、_id = 5
のデータはもう参照されることはなく、また存在していると _id = 5
のデータが複数できてしまうことになり処理の邪魔にもなります。
よって、Undo 後履歴を更新する場合は、その _id
以降のデータをすべて消去して履歴情報を更新するようにします。
これらを踏まえて処理を記載すると以下のようになります。
RemoveIf(History, _id > nowHistoryID); UpdateContext({nowHistoryID:nowHistoryID + 1}); Collect( History, { _id:nowHistoryID, data:JSON(Tasks, JSONFormat.IgnoreBinaryData) } )
履歴の更新を行いたい場合は、このボタンを呼び出せばいいので、
UpdateIf(...) Select(Btn)
などのように処理を記載していきます。
これで履歴情報の保持のための仕組みが作成できました。
Undo (元に戻す)機能を実装する
Undo アイコンは戻る履歴がある時だけ選択可能なようにします。
戻る履歴があるかどうかは nowHistoryID
と History
の _id
を比較して判定したいと思います。
UndoIcon.DisplayMode
If(CountIf(History, _id < nowHistoryID) > 0, DisplayMode.Edit, DisplayMode.Disabled)
Undo アイコンが押されたときは、
nowHistoryID
を1減算するnowHistoryID
と同じ_id
値のdata
をもとにTasks
を更新する
2の処理について History
の data
にはJSON 形式でコレクションデータが格納されているのでParse 処理が必要です。
ただ、Power Apps にはJSON をParse するような機能はないので、独自に定義します。
まず、JSON 関数で変換を行ったコレクションですが A-Z で昇順ソートされます。
今回取り出したいJSON のValue は ":
と ,
もしくは }
で囲まれているのでMatchAll 関数で取り出します。
MatchAll 関数を用いて正規表現でValue の抽出を行いますが、ここの仕組みについてはPower Apps のコンポーネントでコレクションの情報を渡す - 項目の名前を抜き出すを参照してください。
これでJSON に変換したコレクションからValue を取り出すことができますので、実際に式にしてみます。
以下のようになります。
UndoIcon.OnSelect
UpdateContext({nowHistoryID:nowHistoryID - 1}); With( { colVal: MatchAll(LookUp(History, _id = nowHistoryID, data), "(?<="":).*?(?=(,|}))") }, If( Mod(CountRows(colVal), 9) = 0, ClearCollect( Tasks, ForAll( Sequence(CountRows(colVal) / 9), { _id: Value(Last(FirstN(colVal, 9 * ThisRecord.Value - 8)).FullMatch), achievement:Value(Last(FirstN(colVal, 9 * ThisRecord.Value - 7)).FullMatch), details: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 6)).FullMatch, """", ""), endDate: Value(Last(FirstN(colVal, 9 * ThisRecord.Value - 5)).FullMatch), level: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 4)).FullMatch, """", ""), startDate: Value(Last(FirstN(colVal, 9 * ThisRecord.Value - 3)).FullMatch), status: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 2)).FullMatch, """", ""), task: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 1)).FullMatch, """", ""), viewDetail: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 0)).FullMatch, """", "") = "true" } ) ) ) )
Parse した結果はText 型なので、適宜データ型を直しています。
Redo (やり直し)機能を実装する
基本的にUndo と考え方は同じです。
RedoIcon.DisplayMode
If(CountIf(History, _id > nowHistoryID) > 0, DisplayMode.Edit, DisplayMode.Disabled)
RedoIcon.OnSelect
UpdateContext({nowHistoryID:nowHistoryID + 1}); With( { colVal: MatchAll(LookUp(History, _id = nowHistoryID, data), "(?<="":).*?(?=(,|}))") }, If( Mod(CountRows(colVal), 9) = 0, ClearCollect( Tasks, ForAll( Sequence(CountRows(colVal) / 9), { _id: Value(Last(FirstN(colVal, 9 * ThisRecord.Value - 8)).FullMatch), achievement:Value(Last(FirstN(colVal, 9 * ThisRecord.Value - 7)).FullMatch), details: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 6)).FullMatch, """", ""), endDate: Value(Last(FirstN(colVal, 9 * ThisRecord.Value - 5)).FullMatch), level: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 4)).FullMatch, """", ""), startDate: Value(Last(FirstN(colVal, 9 * ThisRecord.Value - 3)).FullMatch), status: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 2)).FullMatch, """", ""), task: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 1)).FullMatch, """", ""), viewDetail: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 0)).FullMatch, """", "") = "true" } ) ) ) )
以上で、Redo & Undo 機能をPower Apps に実装できました!
おわりに
以上でPower Apps でRedo & Undo 機能を実装できました。
今回も利用したJSON の解析ですが、Power Apps で無理やり実現するのではなく、PCF(Power Apps component framework)やカスタム コネクタでのコード記述によって実装したほうがよいかもしれませんね。