コルネの進捗や備忘録が記されたなにか

進捗や成果物や備忘録てきななにかを雑に更新していきます。

Power Apps でRedo & Undo 機能を実装する


スポンサードリンク

はじめに

Power Apps にはRedo & Undo アイコン(やり直し & 元に戻すアイコン)が存在しますが、これらの動作を実現するための機能は備わっていません。

f:id:koruneko:20210822145634p:plain

今回はこのRedo & Undo 機能の実装方法の一例を纏めたいと思います。

今回行う実装は下の例のようなものになっています。

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})

TasksRedo & Undo 機能をつけたいコレクションの宣言です。
History が履歴保持のためのコレクションです。 nowHistoryID は現在のデータのID です。
つまり、 LookUp(History, _id = nowHistoryID, data) = Tasks となります。

次にこの History に元のコレクション( Tasks )の変更に従って履歴情報を更新しようと思います。

履歴情報保持のは Tasks コレクションを更新したときに適宜処理を呼び出すことで実施できるようにします。

そのためにボタンを追加し、 OnSelect に履歴保持のための処理を記載していきます。

このときの処理ですが、以下3つの処理を実装する必要があります。

  1. 現在利用しているデータのID より大きいID のデータの削除
  2. 現在利用しているデータのID を加算
  3. 履歴の保持

まず 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 アイコンは戻る履歴がある時だけ選択可能なようにします。

戻る履歴があるかどうかは nowHistoryIDHistory_id を比較して判定したいと思います。

UndoIcon.DisplayMode

If(CountIf(History, _id < nowHistoryID) > 0, DisplayMode.Edit, DisplayMode.Disabled)

Undo アイコンが押されたときは、

  1. nowHistoryID を1減算する
  2. nowHistoryID と同じ _id 値の data をもとに Tasks を更新する

2の処理について Historydata にはJSON 形式でコレクションデータが格納されているのでParse 処理が必要です。
ただ、Power Apps にはJSON をParse するような機能はないので、独自に定義します。

まず、JSON 関数で変換を行ったコレクションですが A-Z で昇順ソートされます。

今回取り出したいJSONValue":, もしくは } で囲まれているので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)やカスタム コネクタでのコード記述によって実装したほうがよいかもしれませんね。

docs.microsoft.com

docs.microsoft.com


スポンサードリンク