🖨️

Power Apps + Power Automate でコンポーネントをPDFにしてローカルに保存できるようにする

2023/09/07に公開

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 も必要になってくる。よってここに処理の備忘録を書くに至った。

全体の処理の流れ

  1. 描画ボタンを押すことで特定のコンポーネント領域を PDF blob にする
  2. ダウンロードボタンを押すことで PDF blob をローカルに保存する

それぞれ第一段階、第二段階として次に詳述する。

第一段階:PDF blob の生成

PDF関数の有効化

先立って、以下の画像のように、PDF関数に関するプレビュー機能を有効化しなければならない。
さもなくば PDF 関数の実行時にエラーが発生し正常に動作しない。

Power Apps の PDF 関数に関する設定項目

処理の流れ

大まかには次の処理をたどる。

  1. PDF として出力したい領域をコンポーネントないし画面それ自体で囲う。
  2. PDF() 関数に領域を引数として渡す。
  3. PDF() 関数をボタンの OnSelect プロパティや画面の OnVisible プロパティで実行させる。
  4. 必要ならば、その結果(PDF の blob である)を変数に代入する。

参考:
https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-pdf
https://learn.microsoft.com/ja-jp/power-apps/maker/canvas-apps/how-to/pdf-function

PDF 関数

PDF() は特定の画面ないしコンポーネント全体を PDF blob オブジェクトに変換する関数である。定義は形式的に PDF(Screen or control name [,{Size, DPI, Margin, Orientation, ExpandContainers}]) とされる。オプションは前述のドキュメントを参照するべきだが、今回用いるオプションを以下に示す。

  • Size: ページのサイズ。
  • ExpandContainers: 描画領域を超える特定のコンテナを拡張して印刷するようにするか。

具体的な実装例

ここにコンテナー コンポーネント CC に内包されるテキストラベル コンポーネント txtNametxtDetail があると考える。それぞれの意味論的な詳細はここでは考えない。

さて、それぞれのコンポーネントについて紙に印刷するため、アプリに印刷機能を搭載したい。要件は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 を経由してダウンロードを実現させる。

参考:
https://www.matthewdevaney.com/power-apps-pdf-function-create-view-download-pdfs/

ダウンロード処理の流れ

注意事項:
ダウンロードするためには原理上ダウンロードのリンクを生成しなければならない。よって中間層に SharePoint Online を登場させ、そこに PDF のファイル本体を保管する。同じことが出来るならば OneDrive など別のクラウドストレージでも差し支えない。

以下が一連の流れである。

  1. 「Power Apps (V2)」[1]トリガーで PDF blob を引数 pdf として受け取り、処理を始める。pdf は構造体であり、プロパティ name(ファイル名)とプロパティ contentBytes(バイナリ実体)を持つ。
  2. 「SharePoint Online」アクションで SPOに pdf.contentBytes を保存する。ファイル名は pdf.name とする。
  3. 「Power Apps または Flow に応答する」アクションで前述のアクションからファイルパスを受け取り、適量加工した上で Power Apps に処理を戻す。
  4. Power Apps の Download 関数で前述のファイルパスから PDF をダウンロードする。

Power Automate フローの作成

Power Apps Studio のサイドバーに存在する Power Automate に関するツールバーよりフローを作成する。ここでフロー名は F として、一旦空のまま保存する。

ここで、フロー F は Power Apps (V2) トリガーによって発動されなければならない。しかし、Power Apps Studio では起点となるトリガーを編集できない。よって、一旦 Power Apps のトップページに戻った上で「フロー」→「F」を選択して編集する。

トリガー・アクションの作成と実装

以下の3つを連ねて実装する。

  1. Power Apps → 「Power Apps (V2)」トリガー。
  2. SharePoint Online → 「ファイルの作成」アクション。
  3. Power Apps → 「Power Apps または Flow に応答する」アクション。

Power Apps (V2) のプロパティ設定

  • 入力
    • pdf : ファイル

「ファイルの作成」のプロパティ設定

  • サイトのアドレス: 任意のサイト(ドロップダウンから選択可能)
  • フォルダーのパス: 任意のパス(ドロップダウンから選択可能)
  • ファイル名triggerBody()['file']['name'] [2]
  • ファイル コンテンツpdf = triggerBody()['file']['contentBytes'](Power Apps (V2) のプロパティより)

自環境では pdfcontentBytes として解釈されたので自動補完で対応できたものの、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 でダウンロードパスを取得できる。

最後に pathDownload 関数によってローカルに保存する。Download 関数は任意の URL からファイルをダウンロードできる関数である。定義は Download(URL) であるから、そのまま引数として path を渡せば良い。

最後に

これで特定コンポーネントを PDF としてダウンロードすることができた。

脚注
  1. (V2) が付記されていないトリガーでは引数を受け取れない。 ↩︎

  2. 「ファイル名」テキストボックスを選択すると表示されるツールチップの「動的なコンテンツの追加」 → 「式」から設定する。 ↩︎

  3. path は自動補完されるであろう SPO アクションの変数名である。またここで / でファイルパスを結合する必要はない。 ↩︎

Discussion