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

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

Power Apps のApp オブジェクトにStartScreen プロパティが追加されました

はじめに

Power Apps のApp オブジェクトにStartScreen プロパティが追加されました。

f:id:koruneko:20211021192834p:plain

公式docs には現在英語のドキュメントにしかStartScreen プロパティに関する記載がないので英語のドキュメントを確認しましょう。

docs.microsoft.com

StartScreen プロパティを使うとなにができるの?

アプリ起動時に最初に表示する画面を定義することができます。

従来はツリービューの先頭に設定してある画面がアプリ起動時に最初に表示される画面でしたが、StartScreen プロパティに画面のオブジェクトを設定することで、設定した画面が最初に表示されます。

このプロパティのデフォルト値は空であり、空の場合は従来同様ツリービューの先頭に設定してある画面が表示されます。

使用方法

基本形

App の StartScreen にScreen のオブジェクトが結果として返されるような式を設定すればよいです。

一番単純なものですと、

App.StartScreen

Screen2

みたいな記載ですね。
この設定を行った後、App の右にある・・・(三点リーダ)を選択して「StartScreen に移動する」を実行してみましょう。

f:id:koruneko:20211021231408p:plain

Screen2 へ遷移したと思います。(Screen2 は事前に作成しておいてください。)

もちろんこの設定はアプリ起動時に読み込まれますので、このアプリを公開してアプリを開いてみるとScreen2 から画面が表示されることになります。

パラメータを渡して起動する

Power Apps で作成されたアプリを開く際にパラメータを指定してアプリを開くことができます。
この時指定されたパラメータは、Param 関数で取得することができます。
詳しい説明は公式docs をご覧ください。

docs.microsoft.com

以下のように使用します。

App.StartScreen

Switch(
    Param("ScreenNum"),
    "1", Screen1,
    "2", Screen2,
    "3", Screen3
)

これで
https://apps.powerapps.com/play/...-...-...?tenantId=...-...-...&ScreenNum=2
のリンクからアプリを開いた場合はScreen2 が開かれます。

リストの値と比較する

リストの値を取得してその値と比較を行うことでパラメータ先を決める方法です。

例えば以下のようにして、アプリを利用するユーザが特定レコードのユーザ列に存在しているか?で遷移先を制御することが可能です。

App.StartScreen

If(User().Email in LookUp(UserLists, Title = "TitleA", Users).Email, Screen2, Screen3)

StartScreen プロパティでできないこと

StartScreen では、動作関数や変数、コレクションを使用することはできません。

動作関数とはNewForm 関数などのことを指します。
もちろんNavigate 関数も含みます。

変数及びコレクションは App.OnStart で宣言されたものも参照することができないので、注意が必要です。
もし、変数の値を利用したい。という場合はその変数に設定予定の式をそのまま App.StartScreen にコピペして、If 関数で判定を行った後、しかるべき画面へ遷移させてあげましょう。

個人的にちょっと不満なこと

不満というより困ったな。といったほうが正確かもです。

Navigate 関数だと第3引数に変数を宣言して、その変数を遷移先の画面に渡すことができました。

docs のサンプルの式を引用すると、以下のような記載方法です。

Navigate( Details, ScreenTransition.Fade, { ID: 12 } )

これで Details という画面では、"ID" という変数が利用でき、"ID" を参照すると"12" という値を得ることが可能でした。

しかし、StartScreen プロパティでは遷移先の画面に変数を渡す。なんて処理はできません。
困った。

現状のこの問題の回避策はぱっと思いつく限りで挙げると2通りあります。

まず、グルーバル変数を使用する方法。

これはStartScreen で画面遷移を行うために記載した条件式と同じものを、OnStart プロパティにも記載して、Set 関数で変数を定義する方法。

これで一応問題は解決できますが、変数がグローバルになってしまいます。
変数のスコープは極力最小にしたい私としてはこの書き方には聊か不満があります。

また、既存処理でNavigate 関数を使って変数を渡しているような処理(App.OnStart 以外で)があった場合、Navigate 関数で渡される変数はコンテキスト変数なので、そこもNavigate 前にSet 関数で変数を宣言するようにして、グローバル変数で統一するように修正が必要です。

もう一つの方法はScreen.OnVisible で特定の条件の場合にコンテキスト変数を設定するようにする方法です。

以下のような設定になります。

App.ScreenStart

If(!IsBlank(Param("ParamMode")), Screen2)

Screen1.Button.OnSelect

Navigate(Screen2, ScreenTransition.None, {mode:"new"})

Screen2.OnVisble

If(
    !IsBlank(Param("ParamMode")) && IsBlank(mode),
    UpdateContext({mode:Param("ParamMode")})
)

Screen1.Button.OnSelect の記載の箇所は他スクリーンから App.ScreenStart へ遷移する際の記載方法の例です。

App.ScreenStart で遷移先の画面のScreen2.OnVisble で App.ScreenStart で画面遷移を行った際の条件であった場合と、Navigate 時に渡す予定の変数が空であった場合に、Navigate 時に渡す予定の変数を初期化するようにしています。

Navigate 時に渡す予定の変数の状態を確認しないで変数を更新してしまうと、Navigate 時に宣言した変数が上書きされてしまうので注意が必要です。

これの問題は、App.ScreenStart で遷移する画面へNavigate 関数で遷移する場合は対象の変数が確実になにかしら宣言される。という条件がある点です。

おわりに

最後に記載した内容は、Twitter とかみている感じあまり困っているような人少なそうな印象だったので割と少数てきな問題なのか。という印象を受けました。

もし同じような問題で困っている人の助けになれば幸いです。

もし記事の中での記載や説明に不備や不足している内容があればコメントで指摘いただけると幸いです。

Power Apps でデータソースから重複を取り除く方法色々

はじめに

Power Apps で重複しているデータを取り除いて表示させる色々な方法を纏めています。

ただ最初に断っておくと、これはこうすればやりたいことできるよ。ってことを纏めただけで私自身このやり方をどんなときでも推奨しているわけではありません。

本当にロジックで解消するべき問題なのか?データ設計見直したほうがいいのではないか?改修に掛かるコストや費用対効果、運用面でのことなどを考慮したうえで採用するようにしましょう。

重複を削除する

1つのフィールドに対して重複を削除する

これはPower Apps に存在する数式、Distinct 関数を利用します。

これは公式のDocs に記載されている通りですね。

Docs に記載されている通りの説明になりますが、

  • コレクション(データソース)を定義します。
    * 後述の説明のために公式docs とは少し内容を変えています。(Tokyo, Japan を追加)
ClearCollect( 
    CityPopulations,
    { City: "London",    Country: "United Kingdom", Population: 8615000 },
    { City: "Berlin",    Country: "Germany",        Population: 3562000 },
    { City: "Madrid",    Country: "Spain",          Population: 3165000 },
    { City: "Hamburg",   Country: "Germany",        Population: 1760000 },
    { City: "Barcelona", Country: "Spain",          Population: 1602000 },
    { City: "Munich",    Country: "Germany",        Population: 1494000 },
    { City: "Tokyo",     Country: "Japan",          Population: 14043239 },
    { City: "Tokyo",     Country: "Japan",          Population: 14043239 }
);
  • 上記データをデータテーブルに表示させます。
    f:id:koruneko:20211005012518p:plain

DataTable1.Items

CityPopulations
  • 重複削除後のデータをデータテーブルに表示させます。
    今回重複を判定するのは Country フィールドです。
    f:id:koruneko:20211005013644p:plain

DataTable2.Items

Distinct(CityPopulations, Country)
  • この式の結果は Result として返ってくるので、フィールドはResult を選択します。
    f:id:koruneko:20211005013812p:plain

f:id:koruneko:20211005013828p:plain

複数のフィールドに対して重複を削除する

先ほどは Country フィールドのみに対して重複チェックしましたが、今度は CityCountry フィールドに対して重複チェックしてみようと思います。

先ほどのDistinct 関数を使ったやり方ですと、Formula 部分に重複をチェックしたいフィールドを & で続けて設定してあげます。

Distinct(CityPopulations, City & Country)

ただし、こちらの結果は以下のようにくっつけたフィールドの文字列が結合された形式で返ってきてしまいます。

f:id:koruneko:20211005014834p:plain

なので、少し見やすい形式に直してあげます。

ForAll(
    Distinct(CityPopulations, City & "," & Country),
    With(
        {SplitResults:Split(ThisRecord.Result, ",")},
        {
            City:   Last(FirstN(SplitResults, 1)).Result,
            Country:Last(FirstN(SplitResults, 2)).Result
        }
    )
)

行っていることを日本語にすると、

  1. CityCountry の文字列を , で結合して重複を削除します。
  2. 1の結果より得られたテーブルを1レコードずつ評価します。
  3. 評価の内容としては、まず結合された文字列をSplit 関数を使って分解します。
  4. このままだとテーブルの中にテーブルが含まれていますし、列名も適切なものではありません。
    なので、レコードを定義して適切な値をそれぞれ設定してあげます。

ちょっとめんどくさいですね。

重複を削除した結果をレコード単位で取得する

Distinct 関数でえられた結果は、重複チェックを行ったフィールドだけしか得られませんでした。
しかし、重複削除を行った結果のすべてのフィールドが欲しい。というケースもあるかもしれません。

そういう場合はDistinct 関数は使えないので、独自でロジックを作成してあげます。

やり方としては、

  1. 重複チェックを行いたいコレクションのレコード数分レコードを評価する(レコードすべてについて1レコードずつ評価を行う)。
  2. Nレコード目の評価を行う場合は、先頭からNレコード目までのテーブルを比較対象とし、Nレコード目の重複チェック対象のフィールドの値が既に存在しているかチェック(自身を含むので1でないかを判断)。
  3. 上記の結果、存在していなけばNレコード目の値をセットし、存在していれば空レコードをセットする。
  4. 結果として、重複していたところは空レコードがセットされているので、空レコードを除外する。

となります。
これを実際の式にすると以下のようになります。

Filter(
    ForAll(
        Sequence(CountRows(CityPopulations)),
        With(
            {tmpCol:FirstN(CityPopulations, ThisRecord.Value)},
            If(
                CountIf(tmpCol, Country = Last(tmpCol).Country) = 1,
                Last(tmpCol)
            )
        )
    ),
    !IsBlank(ThisRecord)
)

おわりに

以上、Power Apps で重複データを取り除く色々な方法でした。

そこまで深く考えて作成したロジックじゃないので、もっと最適な方法もあるかもです。

Power Apps のコンポーネントでOnVisible を実装する

はじめに

唐突ですが、皆さんPower Apps でコンポーネント作成されていますでしょうか?

今回はPower Apps のコンポーネントが表示されたときをトリガーに特定のアクションを実行するためのロジックを作成してみようと思います。
いわゆるScreen コントロールで設定可能な OnVisible ですね。

動作イメージは以下。

f:id:koruneko:20211003221844g:plain

実装方法

実装方法は極めて単純です。

使用するのは切り替え(トグル)コントロールのみです。

トグルコントロールを配置して、 Defalt に以下式を記載します。

Toggle.Defalt

Parent.Visible

これにより親要素、つまりコンポーネントが表示されている場合は true (トグルがチェック)、コンポーネントが非表示の場合は false (トグルがオフ)が設定されるようになります。

コンポーネントが表示された場合 = トグルがチェックされた場合
ですので、トグルの OnCheckコンポーネントOnVisible で実行させたいアクションを記載します。

例で挙げたgif だと以下のようになりますね。

Toggle.OnCheck

Notify("コンポーネントが表示されました。")

もし、コンポーネント呼び出し側のアプリでアクションを定義したいのであれば、カスタム プロパティにて動作型のプロパティを作成してあげて

f:id:koruneko:20211003224436p:plain

トグルの OnCheck で動作型のプロパティを実行するようにします。

Toggle.OnCheck

Parent.OnVisible()

おわりに

コンポーネントが表示されたときに上のgif のようにメッセージを表示したい場合や、タイマーを開始させたい場合、グローバル変数を初期化したいときなどに使えそうですね。

Power Automate で日付を色々な形式で表示する

はじめに

こちらの記事のPower Automate Ver です。

koruneko.hatenablog.com

参考文献

docs.microsoft.com

docs.microsoft.com

docs.microsoft.com

Power Automate での式の書き方

式を入力する箇所で、fx というマークがあるので、そこを選択すると式が入力できるようになります。

日本時間

addHours

よくみかける、 addHours するやり方です。

JST(日本時間)はUTC(世界協定時間)に +9 した時間なので、UTC に +9 時間する方法です。

addHours(utcNow(), 9)

ただしこれだと出力結果が

2021-09-24T19:09:46.4347603Z

となってしまい、この結果を他で利用しようとした際システム的にUTCと判断されてしまいます。(末尾に Z があるため)

なので、日本時間を利用したい場合は以下方法を推奨します。

タイムゾーンを日本に変換する

タイムゾーンを日本に変換する方法です。

convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time')

このときの出力結果は

2021-09-24T19:09:46.5441398

となります。

日本時間を扱いたい場合はこの方法を利用するようにしましょう。

Custom Format

カスタム書式設定を使ってフォーマットを指定するパターンです。

式, 結果を記載していきます。

日付

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'),
    'yyyy/MM/dd'
)

結果

2021/09/24

日付(省略系)

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'yy/M/d'
)

結果

21/9/24

日時(AM/PM 表記)

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'yyyy/MM/dd hh:mm:ss tt'
)

結果

2021/09/24 07:09:46 PM

日時(24時間表記)

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'yyyy/MM/dd HH:mm:ss'
)

結果

2021/09/24 19:09:46

日時(12時間表記)

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'yyyy/MM/dd hh:mm:ss'
)

結果

2021/09/24 07:09:46

このように時間は hh (小文字)だと12時間表記となり、 HH (大文字)だと24時間表記になるので、利用の際は気を付けましょう。

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'MMMM'
)

結果

September

月(省略形)

Sep

結果

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'MMM'
)

曜日

英語表記

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'dddd'
)

結果

Friday

日本語表記

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'dddd',
    'ja-JP'
)

結果

金曜日

曜日(省略形)

英語表記

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'ddd'
)

結果

Fri

日本語表記

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'ddd', 
    'ja-JP'
)

結果

月(数値)

int(
    formatDateTime(
        convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
        'MM'
    )
)

結果

9

日(数値)

int(
    formatDateTime(
        convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
        'dd'
    )
)

結果

24

時間(数値)

int(
    formatDateTime(
        convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'),
        'HH'
    )
)

結果

19

分(数値)

int(
    formatDateTime(
        convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'),
        'mm'
    )
)

結果

9

年号

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'gg'
)

結果

A.D.

Single Format

文字1文字でフォーマットを指定するやりかたです。
このため、前述のCustom Format での書式設定に1文字だけのフォーマット(例えば h だけなど)はエラーもしくは想定と違う結果が得られる可能性があることに注意してください。

短い日付

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'd'
)

結果

9/24/2021

長い日付

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'D'
)

結果

Friday, September 24, 2021

短い日時

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'f'
)

結果

Friday, September 24, 2021 7:09 PM

長い日時

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'F'
)

結果

Friday, September 24, 2021 7:09:46 PM

短い一般の日時

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'g'
)

結果

9/24/2021 7:09 PM

長い一般の日時

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'G'
)

結果

9/24/2021 7:09:47 PM

月日

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'm'
)

結果

September 24

* 大文字 / 小文字区別しません。

ラウンドトリップされた日時

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'o'
)

結果

2021-09-24T19:09:47.1066473

* 大文字 / 小文字区別しません。

RFC1123パターンの日時

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'r'
)

結果

Fri, 24 Sep 2021 19:09:47 GMT

* 大文字 / 小文字区別しません。

並べ替え可能な日時

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    's'
)

結果

2021-09-24T19:09:47

短い時刻

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    't'
)

結果

7:09 PM

長い時刻

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'T'
)

結果

7:09:47 PM

短い世界共通の日時

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'u'
)

結果

2021-09-24 19:09:47Z

長い世界共通の日時

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'U'
)

結果

Friday, September 24, 2021 7:09:47 PM

年日

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'y'
)

結果

September 2021

* 大文字 / 小文字区別しません。


2023/05/10追記

和暦の表示

以下コメントをいただきました。
参考になっているようでよかったです!

いつも助けていただいております。powerautomateで和暦を表示する方法が知りたいです。

さて、和暦の表示方法に関してですが、Power Automateで日付のフォーマットを行うにはformatDateTime関数を用いて日付のフォーマットを行うのですが、この関数による日付フォーマットはISO 8601規格に準拠しています。

en.wikipedia.org

このISO 8601規格は国際規格であり、和暦のタイムフォーマットには対応していません

和暦にタイムフォーマットを行うには"JIS X 0301"に準拠している必要があります。
よって、Power Automateにある関数では和暦に変換することはできません。

行うのであれば自身で変換を行う必要があります。

やり方の一例としては、変換を行いたい日付がどの和暦に相当するか?を判断した後に変換を行う。
という方法があります。

元号 記号 当該元号の日付 対応する西暦日付
明治 明,M M01.01.01 1868-01-25
M45.07.29 1912-07-29
大正 大,T T01.07.30 1912-07-30
T15.12.24 1926-12-24
昭和 昭,S S01.12.25 1926-12-25
S64.01.07 1989-01-07
平成 平,H H01.01.08 1989-01-08
H31.04.30 2019-04-30
令和 令,R R01.05.01 2019-05-01

yyyy-MM-dd

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'yyyy-MM-dd',
    'ja-JP'
)

yyyy

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'yyyy',
    'ja-JP'
)

MM

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'MM',
    'ja-JP'
)

dd

formatDateTime(
    convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time'), 
    'dd',
    'ja-JP'
)

和暦変換

concat(
if(
    and(
        greaterOrEquals(
            int(
                split(
                    dateDifference(
                        '1868-01-25',
                        outputs('yyyy-MM-dd')
                    ),
                    ':'
                )[0]
            ), 
            0
        ),
        lessOrEquals(
            int(
                split(
                    dateDifference(
                        '1912-07-29',
                        outputs('yyyy-MM-dd')
                    ),
                    ':'
                )[0]
            ), 
            0
        )
    ), 
    concat('M', add(int(outputs('yyyy')), -1867)),

if(
    and(
        greaterOrEquals(
            int(
                split(
                    dateDifference(
                        '1912-07-30',
                        outputs('yyyy-MM-dd')
                    ),
                    ':'
                )[0]
            ), 
            0
        ),
        lessOrEquals(
            int(
                split(
                    dateDifference(
                        '1926-12-24',
                        outputs('yyyy-MM-dd')
                    ),
                    ':'
                )[0]
            ), 
            0
        )
    ), 
    concat('T', add(int(outputs('yyyy')), -1911)),

if(
    and(
        greaterOrEquals(
            int(
                split(
                    dateDifference(
                        '1926-12-25',
                        outputs('yyyy-MM-dd')
                    ),
                    ':'
                )[0]
            ), 
            0
        ),
        lessOrEquals(
            int(
                split(
                    dateDifference(
                        '1989-01-07',
                        outputs('yyyy-MM-dd')
                    ),
                    ':'
                )[0]
            ), 
            0
        )
    ), 
    concat('S', add(int(outputs('yyyy')), -1925)),
    
if(
    and(
        greaterOrEquals(
            int(
                split(
                    dateDifference(
                        '1989-01-08',
                        outputs('yyyy-MM-dd')
                    ),
                    ':'
                )[0]
            ), 
            0
        ),
        lessOrEquals(
            int(
                split(
                    dateDifference(
                        '2019-04-30',
                        outputs('yyyy-MM-dd')
                    ),
                    ':'
                )[0]
            ), 
            0
        )
    ), 
    concat('H', add(int(outputs('yyyy')), -1988)),

if(
    greaterOrEquals(
        int(
            split(
                dateDifference(
                    '2019-05-01',
                    outputs('yyyy-MM-dd')
                ),
                ':'
            )[0]
        ), 
        0
    ),
    concat('R', add(int(outputs('yyyy')), -2018)),
    ''
)
)
)
)
),
'-',
outputs('MM'),
'-',
outputs('dd')
)

出力例

R5-05-10

めんどくさいですね。

おわりに

以上Power Automate での日付の取り扱い纏めでした。

日付を利用したい場合の参考になれば幸いです。

これはどうやって表現すればいい?などご質問ありましたらコメント等でお気軽にどうぞー。

Power Apps で日付を色々な形式で表示する

はじめに

こちらの公式Docs 記載のフォーマットで日付を表示してみただけです。

docs.microsoft.com

Docs だと、「en-US」つまり英語での表示しか載っていないので日本語だとどうなるのかも比較で載せています。

DateTimeFormat

実際にPower Apps で DateTimeFormat 列挙型を用いて日付をフォーマットした場合は以下のようになります。

f:id:koruneko:20210920154420p:plain

このような表示を行うためには以下のような式を利用します。

Text(Date, DateTimeFormatEnum [, ResultLanguageTag ])
  • Date
    日付
  • DateTimeFormatEnum
    DateTimeFormat 列挙のメンバー
    もしくは二重引用符で囲まれた 1 つ以上のプレース ホルダー
  • ResultLanguageTag
    結果のテキストに使用する言語タグ

実際の式にすると、以下のようになります。

日本語

Text(Now(), DateTimeFormat.LongDate, "ja-JP")

英語

Text(Now(), DateTimeFormat.LongDate, "en-US")

ただし、言語の箇所は省略可能でユーザの言語で表示させたい場合は設定しなくてもよいです。

* 表示させる形式によっては、言語も考慮しなくてはならない点にご注意ください。(AM/PMを表示させたい場合など)

コピペ用にテキスト形式にした日英の比較表も記載しておきます。

DateTimeFormat 表示例(日本語) 表示例(英語)
LongDate 2021年9月20日 月曜日 Monday, September 20, 2021
LongDateTime 2021年9月20日 月曜日 15時11分53秒 Monday, September 20, 2021 3:11:57 PM
LongDateTime24 2021年9月20日 月曜日 15時11分53秒 Monday, September 20, 2021 15:11:57
LongTime 15時11分53秒 3:11:57 PM
LongTime24 15時11分53秒 15:11:57
ShortDate 2021年9月20日 9/20/2021
ShortDateTime 2021年9月20日 15時11分 9/20/2021 3:11 PM
ShortDateTime24 2021年9月20日 15時11分 9/20/2021 15:11
ShortTime 15時11分 3:11 PM
ShortTime24 15時11分 15:11
UTC 2021-09-20T06:11:53.489Z 2021-09-20T06:11:57.044Z

Date and time placeholders

実際にPower Apps でフォーマットした場合は以下のようになります。

f:id:koruneko:20210920160301p:plain

こちらも先ほどの DateTimeFormat のときと使い方は同じです。

実際の式にすると、以下のようになります。

日本語

Text(Now(), "yyyy/mm/dd", "ja-JP")

英語

Text(Now(), "yyyy/mm/dd", "en-US")

注意が必要な点として、月のフォーマットに利用する mm は小文字なことに注意が必要です。

コピペ用にテキスト形式にした日英の比較表も記載しておきます。

Placeholders 表示例(日本語) 表示例(英語)
yyyy/mm/dd 2021/09/20 2021/9/20
yy/m/d 21/9/20 2021/9/20
yyyy/mm/dd hh:mm:ss 2021/09/20 15:31:33 2021/9/20 15:31
yy/m/d h:m:s 21/9/20 15:31:33 2021/9/20 15:31
mmm 9月 Sep
mmmm 9月 September
ddd 月曜日 Mon
dddd 月曜日 Monday
yyyy/mm/dd hh:mm:ss.f 2021/09/20 15:31:33.511 31:24.8
yyyy/mm/dd AM/PM hh:mm 2021/09/20 午後 03:31 2021/09/20 PM 03:31

おまけ

年だけ表示する

Year(Now())

月だけ表示する

Month(Now())

日だけ表示する

Day(Now())

時間だけ表示する

Hour(Now())

分だけ表示する

Minute(Now())

秒だけ表示する

Second(Now())

短い形式で時間だけ表示する

Mod(Hour(Now()), 12)

曜日だけを表示する

Text(Now(), "ddd")

短い形式で曜日だけを表示する

Substitute(Text(Now(), "ddd", "ja-JP"), "曜日", "")

おわりに

他にもこんな風に表示したい場合はどうすればいいの?
などあればご気軽に質問してください。

Power Automate でワクチン後体調報告システムを作成してみた

はじめに

Power Automate を使ってTwitter にワクチン接種後の体調を報告するシステムを作成してみたので、そのフローの仕組みを纏めたいと思います。

実際にツイートされたものはこんな感じ。

フローの概要

フローの全体図

フローの全体図はこんな感じ。

f:id:koruneko:20210919210234p:plain

トリガー

トリガーにはスケジュールの「繰り返し」を用いています。

f:id:koruneko:20210919210538p:plain

このフローは

  • 2h 毎に実行
  • (日本時間で)2021/09/17 16:00:00 以降に実行
  • 9時以降のみ実行(夜間は実行しない)

となっています。

「2h 毎に実行」は「間隔」と「頻度」の設定ですね。

「(日本時間で)2021/09/17 16:00:00 以降に実行」は「タイムゾーン」と「開始時刻」の設定です。
ここで「開始時刻」を設定するときに一つ注意が必要です。

例ではタイムフォーマットの末尾に Z がついていますが、

f:id:koruneko:20210919211421p:plain

JST(日本時間)を扱う場合はこの Z をつけてはいけません。

2021-09-17T16:00:00

末尾に Z をつけてしまうとUTC(世界協定時間)として扱われてしまいます。
なので日本時間を利用するときは Z をつけないようにしましょう。

最後に「9時以降のみ実行(夜間は実行しない)」という設定ですが、これはフローの設定の「トリガー条件」で設定しています。

右上の3点リーダより、「設定」を選択すると、

f:id:koruneko:20210919212319p:plain

以下のような画面が表示されます。

f:id:koruneko:20210919212421p:plain

ここのトリガー条件に '@' + [式]を設定すると、その式が true になったときのみ、そのトリガーが実行される。という設定になります。

@greaterOrEquals(int(formatDateTime(addHours(utcNow(), 9), 'HH')), 9)

例えば上の式だと int(formatDateTime(addHours(utcNow(), 9), 'HH')) で日本時間で現在の時間を取得して greaterOrEquals でその時間が9より大きいかを判断しています。

この式の記載に自信がない場合はテスト用のフローなどで、変数などを用意してその式の結果がどうなるのか動作確認したうえで設定するといいでしょう。

アクション

後続のアクションでは2種類のアクションを並列に実行するようにしています。

1つは、体調のツイートするアクションで、

f:id:koruneko:20210919214541p:plain

もう1つは、体調報告をさぼったときなどにタイムアウトツイートをするアクションです。

f:id:koruneko:20210919214558p:plain

体調をツイートする

現在の体調を報告するための質問をTeams でAdaptive Cards を送信して確認しています。

Adaptive Cards は以下のようになっています。

メッセージ

{
    "type": "AdaptiveCard",
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.2",
    "body": [
        {
            "type": "TextBlock",
            "text": "現在の体温は?",
            "wrap": true
        },
        {
            "type": "Input.Text",
            "placeholder": "36.2",
            "id": "temperature"
        },
        {
            "type": "TextBlock",
            "wrap": true,
            "text": "現在の体調は?"
        },
        {
            "type": "Input.Text",
            "placeholder": "元気",
            "id": "physical"
        },
        {
            "type": "TextBlock",
            "wrap": true,
            "text": "腕の痛みは?"
        },
        {
            "type": "Input.Text",
            "id": "pain",
            "placeholder": "痛い"
        },
        {
            "type": "TextBlock",
            "wrap": true,
            "text": "その他"
        },
        {
            "type": "Input.Text",
            "placeholder": "なにかあれば",
            "id": "other"
        }
    ],
    "actions": [
        {
            "type": "Action.Submit",
            "title": "Tweet",
            "style": "positive",
            "data": {
                "res": "tweet"
            }
        }
    ]
}

実際に送られてくるメッセージはこんな感じ。

f:id:koruneko:20210919222709p:plain

このAdaptive Cards は以下のサイトで作成することができます。

adaptivecards.io

このAdaptive Cards での入力内容は id に記載の値で取得できるので、次のツイートを行うアクションでは、それを参照することでツイートに入力内容を埋め込みます。

ツイート テキスト

・体温
@{if(empty(outputs('アダプティブ_カードを投稿して応答を待機する')?['body/data/temperature']), '測るのめんどくさかったです', concat(outputs('アダプティブ_カードを投稿して応答を待機する')?['body/data/temperature'], ''))}
・体調
@{outputs('アダプティブ_カードを投稿して応答を待機する')?['body/data/physical']}
・腕の痛み
@{outputs('アダプティブ_カードを投稿して応答を待機する')?['body/data/pain']}
@{if(empty(outputs('アダプティブ_カードを投稿して応答を待機する')?['body/data/other']), '', Concat(uriComponentToString('%0A'), uriComponentToString('%0A'), outputs('アダプティブ_カードを投稿して応答を待機する')?['body/data/other'], uriComponentToString('%0A'), uriComponentToString('%0A')))}
ワクチン後体調報告Bot
#PowerAutomate

このツイートを行ったあとは、このフローは終了としたいので最後に「終了」アクションを設定しています。

タイムアウト

タイムアウトには並列アクションの最初に待ち時間を設定して、指定時間待機するようにします。

f:id:koruneko:20210919222543p:plain

今回は30分で設定しました。

その後、タイムアウトツイートを行ってフローを終了アクションで終了しています。

おわりに

以上でPower Automate でワクチン後体調報告システムの完成です。
お手軽にできますね。

この仕組み利用して、PJメンバーに進捗確認用システムとか、新人向けに困っていることないか確認するシステムの作成とか作れないかな?
ただ技術ありきで業務システムなどをつくると大抵ろくなことにならないので、要注意です。

Power Apps でタスク管理アプリを作成したおはなし

はじめに

Power Apps でタスク管理作成してみましたのでその作成方法を簡単にまとめます。

こちらのアプリの作成した経緯はAkira さんからバトンをもらったからです。
面白い試み?企画?ですねー

Akira さんはmk さんからバトンが回ってきて作成したようです。

関連記事

Akira さん

hanakuso365.hatenablog.com

mk さん

note.com

タスク管理アプリを作成する

ヘッダを作成する

ヘッダ部分ですが以下みたいなちょっとポップなフォントにして、縁取りしたかったのですが

f:id:koruneko:20210912234133p:plain

これはPower Apps のラベルコントロールだけでは実現できません。

フォントの問題に関しては、Power Apps のフォントプロパティの選択肢だと以下しか選べませんが

f:id:koruneko:20210912235612p:plain

このようにフォント名を直接指定することで、 Font であらかじめ定義されているフォント以外も指定することができます。

f:id:koruneko:20210912235728p:plain

フォント名にスペースを含む場合は ' (シングルクォート)で囲めばよいです。

f:id:koruneko:20210913000120p:plain

ただ、文字の縁取りは現状Power Apps のラベルコントロールでは行うことができません。

なので今回はSVG を使って文字を表示させたいと思います。

SVG は画像コントロールImage

Image

"data:image/svg+xml,"& 
EncodeUrl(
    "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg'> 
    </svg>"
)

みたいに記載することで利用できます。

ほかの表現方法は

koruneko.hatenablog.com

をご覧ください。

画像コントロールを追加し、以下のように式を記載します。

TitleCharImage.Image

"data:image/svg+xml,"& 
EncodeUrl(
    "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg'>
    <rect width='"& Self.Width & "' height='" & Self.Height & "' fill='aqua' />
    <text 
            x='"& (Self.Width - AuthorImage.Width) / 2 &"' 
            y='"& Self.Height / 2 - 10 &"' 
            font-family='HGPSoeiKakupoptai' 
            font-size='60px'
            font-weight='bold'
            stroke='black'
            stroke-width='2px'
            fill='white'
            text-anchor='middle'
        >
            今から
            <tspan dx='-100' dy='70'>なにする?</tspan>
        </text>
    </svg>"
)

<rect> 部分が背景の塗りつぶし。
<text> 部分が文字の設定。
となっています。

今回は細かい説明は省きます。

ToDoList を作成する

ToDoList は以前Redo & Undo の実装方法の説明で紹介したもののほぼ使いまわしです。

koruneko.hatenablog.com

少し変えた点として、Redo & Undo 時、詳細を表示するかの判定は含めないので、現在のコレクションの値を参照するように変更しました。

UndoIcon.OnSelect

Set(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"
+                   // 詳細の表示/非表示は履歴に含めないので元コレクション参照
+                   viewDetail: Last(FirstN(Tasks, ThisRecord.Value)).viewDetail
                }
            )
        )
    )
)

また、スクリーンを遷移しても変わらず設定したタスクを記憶しておきたかったので、 Screen.OnVisible に記載されていた式は App.OnSelect に変更しました。

App.OnSelect

ClearCollect(
    Tasks,
    {
        _id:1,
        task:"",
        details:"",
        startDate:Today(),
        endDate:Today() + 1,
        level:"Normal",
        status:"未開始",
        achievement:0,
        viewDetail:false
    }
);
ClearCollect(
    History, 
    {
        _id:1, 
        data:JSON(Tasks, JSONFormat.IgnoreBinaryData)
    }
);
Set(nowHistoryID, 1)

これにより、 nowHistoryID がコンテキスト変数からグローバル変数に変更になったのでそれに伴って他で利用しているコンテキスト変数もグローバル変数を更新するように変更しておきましょう。
(RedoIcon.OnSelect , UndoIcon.OnSelect , SetHistory.OnSelect が対象のはずです。)

サイコロを作成する

サイコロは以下のようなサイコロっぽいレイアウトで作成します。

f:id:koruneko:20210913004354p:plain

Power Apps のアイコンコントロールに存在する、Rectangle だけではこのようなレイアウトを作成することはちょっとできないので、ここもSVG で表現します。

DiceImage.Image

"data:image/svg+xml,"& 
EncodeUrl(
    "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg'>
      <defs>
          <g id='cube' class='cube-unit'>
              <rect 
                    width='" & 31.5 * RoundDown(Self.Width / 75, 0) &"' 
                    height='" & 36 * RoundDown(Self.Height / 75, 0) &"' 
                    fill='#CCCCCC' 
                    stroke='#000000' 
                  transform='skewY(30) scale(1,1) rotate(0) translate(0 0)'
                />
              <rect 
                    width='" & 31.5 * RoundDown(Self.Width / 75, 0) &"' 
                    height='" & 36 * RoundDown(Self.Height / 75, 0) &"' 
                    fill='#EEEEEE'
                    stroke='#000000'  
                  transform='skewY(-30) scale(1,1) rotate(0) translate(" & 31.5 * RoundDown(Self.Width / 75, 0) &" " & 36 * RoundDown(Self.Height / 75, 0) &".3)'
                />
              <rect 
                    width='" & 31.5 * RoundDown(Self.Width / 75, 0) &"' 
                    height='" & 31.5 * RoundDown(Self.Height / 75, 0) &"' 
                    fill='#FFFFFF'
                    stroke='#000000'   
                  transform='skewY(0) scale(1.41,.81) rotate(45) translate(0 -" & 31.5 * RoundDown(Self.Width / 75, 0) &")'
                />
          </g>
      </defs>
      <use 
          href='#cube' 
          x='" & 5 * RoundDown(Self.Width / 75, 0) &"' 
          y='" & 20 * RoundDown(Self.Height / 75, 0) &"'
      />
        <text 
            x='" & (35 + Len(Text(Last(FirstN(Objectives, 1))._id))) * RoundDown(Self.Width / 75, 0) &"' 
            y='" & 23 * RoundDown(Self.Height / 75, 0) &"' 
            font-family='Verdana' 
            font-size='35'
            text-anchor='middle'
        >
            "& Last(FirstN(Objectives, 1))._id &"
        </text>
        <text 
            x='" & (20 + Len(Text(Last(FirstN(Objectives, 2))._id))) * RoundDown(Self.Width / 75, 0) &"' 
            y='" & 39 * RoundDown(Self.Height / 75, 0) &"' 
            font-family='Verdana' 
            font-size='35'
            text-anchor='middle'
            transform='skewY(30) scale(1,1) rotate(0) translate(0 0)'
        >
            "& Last(FirstN(Objectives, 2))._id &"
        </text>
        <text 
            x='" & (20 + Len(Text(Last(FirstN(Objectives, 3))._id))) * RoundDown(Self.Width / 75, 0) &"' 
            y='" & 45 * RoundDown(Self.Height / 75, 0) &"' 
            font-family='Verdana' 
            font-size='35'
            text-anchor='middle'
            transform='skewY(-30) scale(1,1) rotate(0) translate(" & 31.5 * RoundDown(Self.Width / 75, 0) &" " & 36 * RoundDown(Self.Height / 75, 0) &".3)'
        >
            "& Last(FirstN(Objectives, 3))._id &"
        </text>
    </svg>"
)

<rect> 要素は3つありますが、上から順に2, 3, 1(画像の数字)の四角形となっています。

<text> 要素も3つありますが、こちらは上から順に1, 2, 3(画像の数字)のテキストとなっています。

テキストで表示している数字はTodoList で作成したタスクの内部ID を表しています。(タスクのタイトルを表示してしまったら文字サイズや位置の調整などが面倒ですからね。。。)

ただ内部ID を表示されてもユーザはなにかわからないのでこのID がどのタスクを表しているのかわかるように参照できるリストを表示させましょう。

f:id:koruneko:20210913010909p:plain

これは、ヘッダ部分はコンテナで作成し、リスト部分はギャラリーで作成しています。

f:id:koruneko:20210913011003p:plain

サイコロでタスクの抽選を行う

「サイコロを動かす」というタイトルにしたかったのですが、サイコロを振るようなアニメーションをSVG で表現するのは大変面倒だったので、今回は辞めました。。。

animateTransform を利用するのではなく、立方体の向きが異なるイメージを数パターン用意しておいてそれをパラパラ漫画のように表示させる方式であればサイコロを振っているようにみえるかもです。

今回は数字をランダムに変えることで抽選を行うことにしたいと思います。(それサイコロの意味ある?という指摘は無しでwww)

数字は Objectives コレクションの最初の3つの _id を表示するようにしているのでこのコレクションの中身をShuffle 関数でランダムに変更させます。

まず初期表示時にもランダムな数値を表示させたいので、 OnVisible に以下式を設定します。
また、 OnVisible では、抽選中かどうかを判断させるための変数を宣言しておきます。

Screen.OnVisible

ClearCollect(Objectives, Shuffle(Filter(Tasks, !(_id in todayTasks._id))));
UpdateContext({isRoll:false})

Shuffle(Filter(Tasks, !(_id in todayTasks._id))) の解説は後程行います。

抽選を行ったり止めたりするためのボタンを用意して、以下式を設定します。

RollBtn.OnSelect

UpdateContext({isRoll:!isRoll});

このボタンが押されたら(isRolltrue になったら)タイマーをスタートさせ、繰り返し処理を行われるようにして、 Objectives の中身をシャッフルさせます。

RollTimer.Start

isRoll

RollTimer.OnTimerEnd

ClearCollect(Objectives, Shuffle(Filter(Tasks, !(_id in todayTasks._id))));
Select(TaskReferenceGallery, First(Objectives)._id)

Duration はサイコロの数字の表示が切り替わる間隔になります。
私の設定では1 に設定しています。

OnTimerEnd にて Select(TaskReferenceGallery, First(Objectives)._id) という式を設定していますが、これはサイコロの右に設定しているID とタスクを紐づけために表示を行っているギャラリーでサイコロの一番上の数字(ID)のレコードを選択するための式となっています。
(このため、対象のギャラリーは現在選択されているレコードを塗りつぶすように設定しています。)

抽選されたタスクを記録する

抽選されたタスクを記録するためのコレクションを用意します。

タスクを記録するタイミングは抽選終了時なので、抽選ボタンの OnSelect で設定します。
ただし、抽選を行うのは抽選を開始したタイミングではなく抽選を終了したタイミングということに注意が必要です。

RollBtn.OnSelect

UpdateContext({isRoll:!isRoll});
If(
    !isRoll,
    Collect(
        todayTasks,
        AddColumns(
            Table(First(Objectives)),
            "ToDo",
            Last(Sort(todayTasks, SortOrder.Descending)).ToDo + 1
        )
    )
)

このとき、抽選されたタスクをそのまま設定するだけでなく ToDo フィールドを追加して"・やることそのX"という文字列を表示できるようにしておきます。

f:id:koruneko:20210913020003p:plain

抽選で選択されたタスクは抽選結果で選ばれないようにする

一度選ばれたタスクはもう抽選されてほしくないので、除外するようにします。

それが先ほど後で解説するといった、 Shuffle(Filter(Tasks, !(_id in todayTasks._id))) です。

in 演算子 では、 Tasks._id の値の中に存在する todayTasks._id を選択します。
ただ、今回は存在しないものを選択するようにしたいので ! で否定を取っています。

これで、選択されたタスクは抽選に含まれないようになりました。

ToDo フィールドを連番にする

追加されたToDo の値は連番で追加されていくようになっていますが、削除した場合はToDo の値は連番ではなく抜けてしまいます。

なので、削除したレコード以降のレコードのToDo の値を更新してあげる必要があります。

DeleteIcon の OnSelect にて選択されたレコードを削除し、そのレコードの値以降にToDo フィールドの値を更新するようにします。

DeleteIcon_1.OnSelect

RemoveIf(todayTasks, _id = ThisItem._id);
UpdateIf(todayTasks, ToDo >= ThisItem.ToDo, {ToDo:ToDo - 1})

おわりに

バトン企画楽しいですねー
同じようなテーマのアプリでも人によって作成するアプリが異なってきますし、実装方法も人それぞれなのが大変面白いと思いました。

他テーマでもやってみたいですねー

次はよーよんさん に(勝手に)バトン渡しましたが、どんなアプリが作成されるのか今から楽しみです!(ハードルを上げていく)

Power Apps のフォームコントロールについて理解を深めてみる

はじめに

今回はPower Apps のフォームコントロールの理解を深めるために公式docs などをみてフォームコントロールの理解を深めたいと思います。

基礎大事。

なんでこんなことをしようと思ったのかというと、この前こんなツイートしたところ...

Akira さんから以下のような回答をいただきました。

これに対する返信私意味わからないこと言っていますね。

これの後の返信でやまさんからご指摘をもらいようやくなんかよくわからないこと言っていることに気が付きました。。。

今回はその反省も兼ての基礎の見返しになります。

基礎大事。超大事。

参考文献

docs.microsoft.com

docs.microsoft.com

フォームコントロールの理解を深める

現在のフォーム状態を取得する

私がツイートしていた件ですね。

フォームコントロールは、

  • Edit
  • New
  • View

の3種のモードを設定することができます。

現在のフォームがどのモードなのかは、対象フォームの Mode で取得することが可能です。

これで取得されるデータ型は Enum 型ですので、 Value 関数で数値を取り出してあげましょう。

Value(Form1.Mode)

こうすることで、0, 1, 2のいずれかの数値が取り出せたかと思います。

これらの数値が現在のモードを表しています。
この数値は FormMode という既定のEnum 型と対応しており、これらと比較することで現在のモードを判定することができます。

Switch(
    Value(Form1.Mode),
    FormMode.Edit, "FormMode.Edit",
    FormMode.New, "FormMode.New",
    FormMode.View, "FormMode.View"
)

もちろん、これらは数値で判断していますので、

Switch(
    Value(Form1.Mode),
    0, "FormMode.Edit",
    1, "FormMode.New",
    2, "FormMode.View"
)

みたいにかくことも可能です。
ちなみにdocs のほうに載っていなかったのでこれらの内容と数値を記載しておくと

MODE Value 内容
FormMode.Edit 0 フォームには、既存のレコードが設定され、ユーザーはフィールドの値を変更できます。 完了すると、ユーザーはレコードに変更を保存できます。
FormMode.New 1 フォームは既定の値で設定され、ユーザーはフィールド値を変更できます。 完了すると、ユーザーはレコードをデータ ソースに追加できます。
FormMode.View 2 フォームには既存のレコードが設定されますが、ユーザーはフィールドの値を変更できません。

となります。

フォームのエラーを取得する

SubmitForm 関数が失敗したときのエラーメッセージも取得することが可能です。

取得可能なエラーは2種類存在し

  • Error
  • ErrorKind

の2種があります。

Error

Error はユーザフレンドリなエラーメッセージ、つまりユーザにわかりやすいエラーメッセージが表示されます。

このメッセージは必須項目エラーなどと同じようにユーザ(アプリ利用者)が使用している言語でエラーメッセージが返されます。
ただし、一部のメッセージはデータソースから直接送信され、ユーザが使用している言語とは異なる言語で表示される場合があるため注意が必要です。

ErrorKind

ErrorKind はメッセージなどは出力せずエラーの種類を返します。
Mode と同様にEnum 型で定義されており、 ErrorKind という既定のEnum 型と比較することで種類を判別することが可能です。

こちらのdocs にErrorKind の定義かいてはあるんですけど、Power Apps でこれ確認してみるとここに書かれているより多くの定義あるんですよね。
みるとこ間違えているのかな。。。

docs.microsoft.com

例によってこちらもValue が記載されていないので纏めます。

ErrorKind Value 内容
ErrorKind.None 0 エラーはありません。
ErrorKind.Sync 1 データ ソースによってエラーが報告されました。 詳細については、メッセージの列を確認してください。
ErrorKind.MissingRequired 2 必要な列の値がレコードにありません。
ErrorKind.CreatePermission 3 レコードを作成しようとしましたが、現在のユーザーにはレコードを作成するためのアクセス許可がありません。
ErrorKind.EditPermissions 4 (レコードを編集しようとしましたが、現在のユーザーにはレコードを編集するためのアクセス許可がありません。)
ErrorKind.DeletePermissions 5 (レコードを削除しようとしましたが、現在のユーザーにはレコードを削除するためのアクセス許可がありません。)
ErrorKind.Conflict 6 同じレコードに対して別の変更が行われ、変更の競合という結果になりました。 Refresh 関数を使用して、レコードを再読み込みし、変更を再度試します。
ErrorKind.NotFound 7 レコードを編集または削除しようとしましたが、レコードが見つかりませんでした。 別のユーザーがレコードを変更した可能性があります。
ErrorKind.ConstraintViolated 8 (1 つ以上の制約に違反しています。)
ErrorKind.GeneratedValue 9 データ ソースが自動的に生成する列を変更しようとしました。
ErrorKind.ReadOnlyValue 10 読み取り専用の列を変更しようとしました。
ErrorKind.Validation 11 他の種類の 1 つに適合しない、一般的な検証のイシューが検知されました。
ErrorKind.Unknown 12 エラーが発生しましたが、種類が不明です。
ErrorKind.Div0 13 -
ErrorKind.BadLanguageCode 14 -
ErrorKind.BadRegex 15 -
ErrorKind.InvalidFunctionUsage 16 -
ErrorKind.FileNotFound 17 -
ErrorKind.AnalysisError 18 -
ErrorKind.ReadPermission 19 -
ErrorKind.NotSupported 20 -
ErrorKind.InsufficientMemory 21 -
ErrorKind.QuotaExceeded 22 -
ErrorKind.Network 23 -
ErrorKind.Numeric 24 -

"-"はdocs に記載されていなかったもの。

逆にdocs にはあったが、Power Apps 上見つからなかったのは以下。

ErrorKind 内容
ErrorKind.ConstraintViolation 1 つ以上の制約に違反しています。
ErrorKind.DeletePermission レコードを削除しようとしましたが、現在のユーザーにはレコードを削除するためのアクセス許可がありません。
ErrorKind.EditPermission レコードを編集しようとしましたが、現在のユーザーにはレコードを編集するためのアクセス許可がありません。

これらはおそれくそれぞれ

  • ErrorKind.ConstraintViolation -> ErrorKind.ConstraintViolated
  • ErrorKind.DeletePermission -> ErrorKind.DeletePermissions
  • ErrorKind.EditPermission -> ErrorKind.EditPermissions

に置き換わったのだと思われます。(docs が古い?)

なので上の表では () で記載しています。

一応コピペ用に Switch 式も載せておきます。

Switch(
    Value(Form1.ErrorKind),
    ErrorKind.None, "ErrorKind.None",
    ErrorKind.Sync, "ErrorKind.Sync",
    ErrorKind.MissingRequired, "ErrorKind.MissingRequired",
    ErrorKind.CreatePermission, "ErrorKind.CreatePermission",
    ErrorKind.EditPermissions, "ErrorKind.EditPermissions",
    ErrorKind.DeletePermissions, "ErrorKind.DeletePermissions",
    ErrorKind.Conflict, "ErrorKind.Conflict",
    ErrorKind.NotFound, "ErrorKind.NotFound",
    ErrorKind.ConstraintViolated, "ErrorKind.ConstraintViolated",
    ErrorKind.GeneratedValue, "ErrorKind.GeneratedValue",
    ErrorKind.ReadOnlyValue, "ErrorKind.ReadOnlyValue",
    ErrorKind.Validation, "ErrorKind.Validation",
    ErrorKind.Unknown, "ErrorKind.Unknown",
    ErrorKind.Div0, "ErrorKind.Div0",
    ErrorKind.BadLanguageCode, "ErrorKind.BadLanguageCode",
    ErrorKind.BadRegex, "ErrorKind.BadRegex",
    ErrorKind.InvalidFunctionUsage, "ErrorKind.InvalidFunctionUsage",
    ErrorKind.FileNotFound, "ErrorKind.FileNotFound",
    ErrorKind.AnalysisError, "ErrorKind.AnalysisError",
    ErrorKind.ReadPermission, "ErrorKind.ReadPermission",
    ErrorKind.NotSupported, "ErrorKind.NotSupported",
    ErrorKind.InsufficientMemory, "ErrorKind.InsufficientMemory",
    ErrorKind.QuotaExceeded, "ErrorKind.QuotaExceeded",
    ErrorKind.Network, "ErrorKind.Network",
    ErrorKind.Numeric, "ErrorKind.Numeric"
)

最後に登録したレコードの情報を取得する

これはやまさんのQiita でも紹介されていますが、 LastSubmit を利用すれば取得可能です。

qiita.com

例えばSharePoint リストのID 列などの自動で割り振られる結果を取得したい場合などに便利です。

SubmitForm に成功すると、 Form.OnSuccess の式が実行されるので、ここで UpdateContext などでID を取得するといいかもですね。

未保存のデータがあるかを確認する

編集フォーム内にユーザが未保存の変更が含まれているかを判断することも可能です。

Form1.Unsaved

で判断可能です。
未保存の変更が含まれている場合は true を返します。

例えば未保存のデータがあるにも関わらずユーザがフォームの内容が変更されるような操作を行おうとしている場合や、未保存のデータがある場合にどこかの色などを変えてユーザが視覚的に未保存だということをわかりすくする。
なんて場合に使えますね。

更新内容を取得する

編集フォームでユーザが入力した(既存レコード編集の場合で対象項目を編集していない場合は既存の値)を取得することが可能です。

Form1.Updates

docs の説明だとSubmitForm をする場合は不要で、Patch 関数の利用の際に使う。
とかいてますね。

個人的にはこの他にも、 DataCardValue などのどの項目か分かりにくい名称で入力内容を取得するよりもこっちで取得するようにしたほうが、可読性が上がり保守性も増すのでいいのではないかなぁと思います。

現在の入力内容でSubmit 可能かどうかを調べる

現在の入力内容でSubmit 可能かどうかを調べる方法もあります。

Form1.Valid

こちらは例えば、必須項目が未入力の場合などに false を返すようです。
対象フォーム内で入力されたデータで対象データソースのルールにより、データの登録が失敗する場合は false を返してくれるようなプロパティのようですね。

おわりに

いい機会なので調べてみたら案外知らなかったこととか知ることができました。

docs 今一度読み直そう。
基礎大事。

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

Power Apps でSPO リストのレーティング評価機能(いいね)を利用する

はじめに

以前マシュマロでこのような質問をいただきました。

https://media.marshmallow-qa.com/system/images/ba74d58c-5dfa-439d-9fcb-5ebc7dd4ae67.png

こちらについてTwitter のほうで簡単に回答させていただきましたが、今回はもう少し詳しく回答したいと思います。

SPO リストにレーティング評価機能を追加する

レーティング評価機能といわれてもピンとこない方もいらっしゃると思いますので、こちらの機能の追加方法をまずは紹介したいと思います。

  1. サイトコンテンツへ移動して、レーティング機能を追加したいリストの「⁝」を選択して「設定」を選択します
  2. 「全般設定」の「評価の設定」を選択します
  3. 「評価の設定」を「はい」にします
    その次の項目では、評価を「いいね!」か「星評価」にするかが選べます
    今回は「いいね!」を選択します

これで「いいね!」フィールドが作成されました。

これでこんな感じで「いいね!」ができるようになりました!

ちなみに「いいね!」をしていないユーザからみると以下のようにみえます。

「いいね!」をするとこんな感じ。

Power Apps でレーティング評価機能を扱う

Power Apps で先ほど作成した「いいね!」フィールドの表示/編集を行っていきます。

前準備として、先ほど作成したリストを「データ」より追加しておいてください。

Power Apps でレーティング評価機能を表示する

ギャラリーを追加して、Items に先ほど作成したリストを設定します。

ギャラリー内にラベルを追加して、以下のように設定します。

Label.Text

ThisItem.'「いいね!」と評価したメンバー'

そうすると以下のようなエラーになります。

どうやらこちらはテーブルのようですね。

先ほどの式に . を追加して確認してみると、以下のようなフィールドを確認することができます。

「「いいね!」と評価したメンバー」とかかれているようにこちらには、ユーザ情報が格納されているようですね。

ということは、このテーブル内のレコード数を数えれば「いいね!」をしたユーザの数を取得できそうです。
テーブルのレコード数を数えるにはCountRows 関数を利用します。

Label.Text

CountRows(ThisItem.'「いいね!」と評価したメンバー')

これで「いいね!」の数を取得することができました。

Power Apps でレーティング評価機能を編集する

次にPower Apps から「いいね!」を行えるようにしたいと思います。

今回データの更新にはPatch 関数を利用します。

リストはこのように、自身が「いいね!」を行ったレコード(Title1)と、他のユーザが「いいね!」を行ったレコード(Title2)と、誰も「いいね!」を行っていないレコード(Title3)を用意しておきます。

まずはPower Apps 側でこのリストような表示を行わせたいと思います。

ギャラリーを追加して、"データソース"にSPO リストを設定します。
ギャラリー内には

  • タイトルを表示させるラベル
  • 「いいね!」を押すための👍アイコン
  • 「いいね!」の数を表示するためのラベル

の3つのコントロールを配置します。

それぞれ順に以下のように設定します。

TitleLbl.Text

ThisItem.Title

GoodIcon.Icon

If(CountIf(ThisItem.'「いいね!」と評価したメンバー', Email = User().Email) > 0, Icon.ThumbsUpFilled, Icon.ThumbsUp)

GoodLbl.Text

CountRows(ThisItem.'「いいね!」と評価したメンバー')

タイトルの表示は説明不要かと思います。
また、「いいね!」のカウントも先ほど紹介したので、省きます。

アイコンですが、このように設定することによってアプリを利用しているユーザが「いいね!」を行っている場合は塗りつぶしなしのアイコンになり、ユーザが「いいね!」を行っている場合は塗りつぶしありのアイコンになります。

アプリを利用しているユーザが「いいね!」を行っているか?を判断するためには、SPO リストの「いいね!」列を参照してそのレコードの値にアプリを利用しているユーザのメールアドレスが1つでも登録されていれば、「いいね!」済み。と判断しています。

これによりそれっぽい表示ができました!

続いて、この「いいね!」アイコンが押されたときに「いいね!」を行う。もしくは「いいね!」を取り消す。処理を設定したいと思います。

「いいね!」を行うか取り消すかは、ユーザがそのレコードに「いいね!」済みかどうかで判断すればいいので先ほどの式を利用します。

If(
    CountIf(ThisItem.'「いいね!」と評価したメンバー', Email = User().Email) > 0,
    // ユーザが既に「いいね!」済みだった場合
   [true の処理],
   // ユーザが「いいね!」を行っていない場合
   [false の処理]
)

まずは"ユーザが既に「いいね!」済みだった場合"の処理です。
これは簡単で、既存の「いいね!」したユーザのテーブルからアプリ利用者のユーザのレコードを除外してあげればいいです。
この除外処理にはFilter 関数を利用します。

    Patch(
        RatingSample,
        LookUp(RatingSample, ID = ThisItem.ID),
        {'「いいね!」と評価したメンバー':Filter(ThisItem.'「いいね!」と評価したメンバー', Email <> User().Email)}
    )

次に"ユーザが「いいね!」を行っていない場合"の処理を設定していきます。
これは既存の「いいね!」したユーザのテーブルにアプリ利用者のユーザのレコードを追加してあげればいいです。
ただし追加方法がちょっとめんどくさいです。

まずは、「いいね!」したユーザのテーブルはどうやって設定されているのかをみてみましょう。

画像のように

  • Claims
  • Department
  • DisplayName
  • Email
  • JobTitle
  • Picture

の6フィールドで構成されています。

こちらは列の種類を"ユーザ"にした場合と同じ設定になっています。

Patch 関数で更新する場合はこれらのフィールド全てが存在している必要があります。

{
    Claims: "i:0#.f|membership|" & Lower(User().Email),
    Department: Office365ユーザー.MyProfileV2().department,
    DisplayName: User().FullName,
    Email: User().Email,
    JobTitle: Office365ユーザー.MyProfileV2().jobTitle,
    Picture: User().Image
}

このようなレコードを追加してあげます。

これらだけをPatch 関数で渡してしまうとアプリ利用者のユーザのみで上書きしてしまうことになってしまいますので既存テーブルと結合させます。

現時点では、Power Apps で CollectClearCollect 関数でコレクションを作成する以外に既存テーブルに特定のレコードを追加する方法はありません。(ないよね?)

なので、ForAll 関数を活用して疑似的に既存テーブルにレコードを追加してあげます。

やり方は単純で、既存テーブルのレコード数 + 1 だけループさせて、レコード数分は既存テーブルのレコードを評価し、+ 1 分は上記のレコードを評価してあげればよいです。

実際式にすると以下のようになります。

    Patch(
        RatingSample, 
        LookUp(RatingSample, ID = ThisItem.ID), 
        {
            '「いいね!」と評価したメンバー':
            With(
                {
                    rating: LookUp(RatingSample, ID = ThisItem.ID, '「いいね!」と評価したメンバー')
                },
                ForAll(
                    Sequence(CountRows(rating) + 1),
                    If(
                        Value = CountRows(rating) + 1,
                        {
                            Claims: "i:0#.f|membership|" & Lower(User().Email),
                            Department: Office365ユーザー.MyProfileV2().department,
                            DisplayName: User().FullName,
                            Email: User().Email,
                            JobTitle: Office365ユーザー.MyProfileV2().jobTitle,
                            Picture: User().Image
                        },
                        Last(FirstN(rating, Value))
                    )
                )
            )
        }
    )

これで、Power Apps からレコーディング評価機能を編集できるようになりました!

式全文は以下のようになっています。

OnSelect

If(
    CountIf(ThisItem.'「いいね!」と評価したメンバー', Email = User().Email) > 0,
    // ユーザが既に「いいね!」済みだった場合
    Patch(
        RatingSample,
        LookUp(RatingSample, ID = ThisItem.ID),
        {'「いいね!」と評価したメンバー':Filter(ThisItem.'「いいね!」と評価したメンバー', Email <> User().Email)}
    ),
    // ユーザが「いいね!」を行っていない場合
    Patch(
        RatingSample, 
        LookUp(RatingSample, ID = ThisItem.ID), 
        {
            '「いいね!」と評価したメンバー':
            With(
                {
                    rating: LookUp(RatingSample, ID = ThisItem.ID, '「いいね!」と評価したメンバー')
                },
                ForAll(
                    Sequence(CountRows(rating) + 1),
                    If(
                        Value = CountRows(rating) + 1,
                        {
                            Claims: "i:0#.f|membership|" & Lower(User().Email),
                            Department: Office365ユーザー.MyProfileV2().department,
                            DisplayName: User().FullName,
                            Email: User().Email,
                            JobTitle: Office365ユーザー.MyProfileV2().jobTitle,
                            Picture: User().Image
                        },
                        Last(FirstN(rating, Value))
                    )
                )
            )
        }
    )
)

現在確認している不具合

いずれも要検証項目ではありますが、作成を行う過程で以下のような不具合(動作)に遭遇しましたの共有です。
なお、いずれも回避策は見つかっておりません。。。

もし記事をご覧の方で、この不具合に心当たりのある方や回避策の案がある方はコメントいただけますと助かります。

SPO リストに「いいね!」の数が反映されない

Power Apps から「いいね!」を行った場合SPO リストに「いいね!」の数が反映されませんでした。

画像のように、「いいね!」を行ったということだけはわかりますが(ハートが塗りつぶされているため)、「いいね!」のカウントが増えていません。

ただ、こちら「いいね!」が設定されていない。というわけではありません。
こちらの状態で、別のユーザが「いいね!」を行うとそのユーザの「いいね!」 + Power Apps でユーザが「いいね!」を行った数がカウントとして更新されます。

これらの色々試した結果の動作からの推測になりますが、どうやら

  • ユーザが「いいね!」を行ったかの判断
    -> リスト表示時
  • 「いいね!」のカウント
    -> リストの「いいね!」を操作時

でそれぞれ値の更新が行われるようです。

Power Apps とSPO リストそれぞれをユーザに操作させる場合はこの点に注意してください。

Power Apps で'「いいね!」と評価したメンバー'列が参照できなくなる

検証のため色々弄っているとPower Apps で'「いいね!」と評価したメンバー'列が参照できなくなるという、大変困った不具合が発生しました。

この状態に陥ると '「いいね!」と評価したメンバー' でも LikedBy のどちらでも参照できなくなってしまいます。
また、私が色々試した限りだとこの状態に陥るとどうやってもPower Apps で「いいね!」フィールドを参照できなくなってしまいます。
詰みです。リスト作成し直してください。

リスト作成の際、既存のリストからリストを作成できますが、詰んだリストをコピーして作成してはいけません。
こうして作成されたリストも同様にPower Apps で「いいね!」フィールドが読み込めません。

この大変困った事象の発生条件はまだ特定できていないのですが、以下操作をSPO リストで行いその後Power Apps 側でデータのから対象リストを「最新の情報に更新」すると発生することがありました。(たまに発生しない。。。)

  • 評価機能を有効後に列を追加する
  • 評価機能を有効後に列を非表示にする(「いいね!」以外の列でも)

これ割と深刻な不具合のような気がするんですよね。。。
ちょっと暇みてMS サポートに確認してみようと思います。

おわりに

以上、Power Apps でレーティング評価機能(いいね!)を扱う方法でした。
この記事のやり方で一応扱えはしますが、最後に記載したような問題があり、実際利用するのはちょっと考えて方がよさそうです。

ただ、この「いいね!」列の実態は「いいね!」したユーザの情報が格納されているだけですので、Power Apps で扱うだけであれば、列の種類「ユーザ」で十分代替可能かと思います。


スポンサードリンク