Power Apps + Power Automate でコンポーネントをPDFにしてローカルに保存できるようにする
A matter of business. Regard it as a matter of business — business that must be done.
- Jarvis Lorry, A Tale of Two Cities
ビジネス上の問題だ。ビジネス上の問題として扱え ― 為さなくてはならないものとして。
- ジャーヴィス・ローリー、「二都物語」より
備忘録程度に書いておく。以下は注意事項である。
- 2023/09/07時点で不安定な機能を用いる(PDF関数)。将来この記事の通りに動作しなくなる可能性がある。
- 文章にまとまりがない。
やりたいこと
「Power Apps の特定コンポーネントを PDF に起こしてダウンロードしたい」の一点である。画面を適当にダウンロードするだけならば Print
関数で済むのだが、今回は特定コンポーネントだけを PDF にしたい。
そうすると Power Apps では一筋縄ではいかなくなり、Power Automate も必要になってくる。よってここに処理の備忘録を書くに至った。
全体の処理の流れ
- 描画ボタンを押すことで特定のコンポーネント領域を PDF blob にする
- ダウンロードボタンを押すことで PDF blob をローカルに保存する
それぞれ第一段階、第二段階として次に詳述する。
第一段階:PDF blob の生成
PDF関数の有効化
先立って、以下の画像のように、PDF関数に関するプレビュー機能を有効化しなければならない。
さもなくば PDF 関数の実行時にエラーが発生し正常に動作しない。
処理の流れ
大まかには次の処理をたどる。
- PDF として出力したい領域をコンポーネントないし画面それ自体で囲う。
-
PDF()
関数に領域を引数として渡す。 -
PDF()
関数をボタンのOnSelect
プロパティや画面のOnVisible
プロパティで実行させる。 - 必要ならば、その結果(PDF の blob である)を変数に代入する。
参考:
PDF
関数
PDF()
は特定の画面ないしコンポーネント全体を PDF blob オブジェクトに変換する関数である。定義は形式的に PDF(Screen or control name [,{Size, DPI, Margin, Orientation, ExpandContainers}])
とされる。オプションは前述のドキュメントを参照するべきだが、今回用いるオプションを以下に示す。
-
Size
: ページのサイズ。 -
ExpandContainers
: 描画領域を超える特定のコンテナを拡張して印刷するようにするか。
具体的な実装例
ここにコンテナー コンポーネント C
と C
に内包されるテキストラベル コンポーネント txtName
と txtDetail
があると考える。それぞれの意味論的な詳細はここでは考えない。
さて、それぞれのコンポーネントについて紙に印刷するため、アプリに印刷機能を搭載したい。要件はPDFで印刷出来るようにせよという一項のみである。
まずは PDF バイナリをアプリで生成するようにする。生成するタイミングは C
が置かれている画面 S
に付せて置かれるボタン btnRender
の押下時とする。ここでコードの実装は次のようになる。
B.OnSelect = UpdateContext({ pdf: PDF(C, { ExpandContainers: true, Size: "A4" })})
これで btnRender
を押下したとき、C
とその配下にある二つのコンポーネントは pdf blob に変換され、最終的に変数 pdf
に保存される。
第二段階:PDF blob のダウンロード
2023/09/07時点では Power Apps で PDF blob のダウンロードを完結させる方法は見当たらなかった。よって Power Automate を経由してダウンロードを実現させる。
参考:
ダウンロード処理の流れ
注意事項:
ダウンロードするためには原理上ダウンロードのリンクを生成しなければならない。よって中間層に SharePoint Online を登場させ、そこに PDF のファイル本体を保管する。同じことが出来るならば OneDrive など別のクラウドストレージでも差し支えない。
以下が一連の流れである。
- 「Power Apps (V2)」[1]トリガーで PDF blob を引数
pdf
として受け取り、処理を始める。pdf
は構造体であり、プロパティname
(ファイル名)とプロパティcontentBytes
(バイナリ実体)を持つ。 - 「SharePoint Online」アクションで SPOに
pdf.contentBytes
を保存する。ファイル名はpdf.name
とする。 - 「Power Apps または Flow に応答する」アクションで前述のアクションからファイルパスを受け取り、適量加工した上で Power Apps に処理を戻す。
- Power Apps の
Download
関数で前述のファイルパスから PDF をダウンロードする。
Power Automate フローの作成
Power Apps Studio のサイドバーに存在する Power Automate に関するツールバーよりフローを作成する。ここでフロー名は F
として、一旦空のまま保存する。
ここで、フロー F
は Power Apps (V2) トリガーによって発動されなければならない。しかし、Power Apps Studio では起点となるトリガーを編集できない。よって、一旦 Power Apps のトップページに戻った上で「フロー」→「F
」を選択して編集する。
トリガー・アクションの作成と実装
以下の3つを連ねて実装する。
- Power Apps → 「Power Apps (V2)」トリガー。
- SharePoint Online → 「ファイルの作成」アクション。
- Power Apps → 「Power Apps または Flow に応答する」アクション。
Power Apps (V2) のプロパティ設定
-
入力:
-
pdf
: ファイル
-
「ファイルの作成」のプロパティ設定
- サイトのアドレス: 任意のサイト(ドロップダウンから選択可能)
- フォルダーのパス: 任意のパス(ドロップダウンから選択可能)
-
ファイル名:
triggerBody()['file']['name']
[2] -
ファイル コンテンツ:
pdf
=triggerBody()['file']['contentBytes']
(Power Apps (V2) のプロパティより)
自環境では pdf
は contentBytes
として解釈されたので自動補完で対応できたものの、pdf.name
は自動補完されなかったため動的な式として定義しなければならなかった。ここは環境によって差異がある可能性がある。
「Power Apps または Flow に応答する」のプロパティ設定
-
出力:
-
path
:https://<SPOのテナント名>.sharepoint.com/sites/<SPOのサイト名>/_layouts/15/download.aspx?SourceUrl=/sites/<SPOのサイト名>
path
[3]
-
URLはダウンロードする PDF ファイルのパスを示している。テナント名・サイト名は環境により変化する。
Power Apps での実装
PDF blob をダウンロードするためのボタン btnDownload
を考える。ここでファイルのダウンロードパスはフロー F
を実行することで得られるのだから、よって、Download
関数を用いてそのパスから PDF をダウンロードすれば良い。実装は次のようになる。
btnDownload.OnSelect = Download(
F.Run(
{
name: $"PDF_{Text(
Now(),
"yyyymmddhhmmss"
)}.pdf",
contentBytes: pdf
}
).path
)
ここでフロー F
とはファイル名とファイルバイナリを受け取り、ファイルを保存したパスを返すような関数 F
として考えられる。そうすると 関数 F
の定義は F({ name: string, contentBytes: blob }): Path
と考えられる。
よってプロパティ name
にはファイル名(今回は現在時刻ベースにファイル名を生成する方式を取った)、プロパティ contentBytes
には PDF blob であり今回最終的にダウンロードしたいバイナリである pdf
を与えれば良い。
F
はフロー定義に基づいてファイルの保存パス path
を返すのであるが、実際のところは F.Run()
に対するプロパティアクセスによってパスを取得する。よって F.Run().path
でダウンロードパスを取得できる。
最後に path
を Download
関数によってローカルに保存する。Download
関数は任意の URL からファイルをダウンロードできる関数である。定義は Download(URL)
であるから、そのまま引数として path
を渡せば良い。
最後に
これで特定コンポーネントを PDF としてダウンロードすることができた。
Discussion