🎃

Power Apps portals で Web ページにカスタムしたテーブルを表示する(2/2)

2022/05/11に公開

Power Pages でも使えます。

Power Apps portals は素早く外部公開できるサイトを作成することができますが、設置できるコンポーネントはある程度決まったものとなります。
これが物足りないという場合にはカスタマイズとなるのですが、そのサンプルとして JavaScript と Web API や Liquid を使って Web ページに独自のテーブルを表示してみました。

前回は単純な JavaScript を動作させ、標準のコンポーネントを使わずるところまで作成しました。
今回は、Dataverse からデータを取得して表示するところを作成します。

完成イメージ

Grid.js を使ってテーブルを表示しています。
特になにも変わっていないように見えますが、Dataverse テーブルからデータを取得しています。

前回の記事

https://zenn.dev/nanoka/articles/20220020_powerapps_portals_memo

事前準備

作業に入る前に、取得するテーブル、列、データ、ビューを作成しておきます。
下記ではソリューションを使っていますが、使う必要はありません。

テーブル

データ

ビュー


作業概要(Liquid)

標準コンポーネントを使用せずデータを表示する 1つ目の方法は Liquid を使うことです。
今回は Liquid を使って、画面の読み込み時に Dataverse から任意のデータを取得し、前回作った Script に適用します。

テーブルのアクセス許可設定

まず、Dataverse のテーブルにアクセスするための設定を行います。ここで許可していないテーブルは、標準コンポーネントからもアクセスすることができなくなっています。

  • Studio を起動し、テーブルのアクセス許可をクリックします。

  • 新しいアクセス許可をクリック、以下のように入力し保存をクリックします。

項目 設定値
名前 CustomTableAccess
テーブル {事前に作ったテーブル}
アクセスの種類 グローバルアクセス
アクセス許可 読み取り
ロール 管理者

これで、対象のテーブルは、管理者ロールを持つユーザに対してすべてのレコードへのアクセスを許可することになります。
また、アクセスの種類を変更することで、ログインユーザが作成したレコードのみだとか、ログインユーザが所属している取引先企業に関連付けられているレコードのみといった設定が可能ですので、様々なセキュリティの設定を行うことができます。

Web テンプレート (JavaScript 部品)の編集

次に前回作成した、JavaScript を動作させる部分のみを記載した Web テンプレートを編集します。

  • ポータル管理アプリ から Web テンプレート をクリックし、CustomTableScript をクリックします。
項目 設定値
名前 CustomTableScript
Web サイト {自身のポータルサイト}
ソース 以下に記載
ソースに入力する値
<link href="https://unpkg.com/gridjs/dist/theme/mermaid.min.css" rel="stylesheet" />
<script src="https://unpkg.com/gridjs/dist/gridjs.umd.js"></script>
{% comment %} ライブラリの参照 {% endcomment %}

<style>
.my-btn-class {
  color: black;
}
.my-btn-class:focus,
.my-btn-class.focus {
  color: black;
}
</style>
{% comment %} 競合するスタイルの上書き {% endcomment %}

<script>
document.addEventListener('DOMContentLoaded', async () => {

{% comment %} 変更点ここから {% endcomment %}
  let data = [];
  let record = [];
{% entityview logical_name:'nan_employee' name:'ポータル用'%}
  {% for item in entityview.records %}
    record = ["{{item.nan_name | escape }}", "{{item.nan_email | escape }}","{{item.nan_phone_number | escape }}"];
    data.push(record);
  {% endfor %}
{% endentityview %}
{% comment %} Dataverse からデータを読み取り、JavaScript の配列変数に格納 {% endcomment %}
{% comment %} 変更点ここまで {% endcomment %}

  new gridjs.Grid({
    columns: ["Name", "EMail", 'Phone Number'],
    data: data, {% comment %} 変更点 {% endcomment %}
    className: {
      paginationButton: "my-btn-class"
    },
    search: {
      enabled: true
    },
    sort: true,
    pagination: {
      enabled: true,
      limit: 2,
      summary: true
    },
    language: {
      "search": {
        "placeholder": "🔍 Search..."
      },
      "pagination": {
        "previous": "←",
        "next": "→",
        "showing": "😃 Displaying",
        "results": () => "Records"
      }
    }
  }).render(document.getElementById("gridWrapper"));

}, false);
</script>
{% comment %} Dataverse テーブルデータでの Grid.js の実行 {% endcomment %}

entityview を使って、指定のテーブルからデータを取得し、それを JavaScript の配列変数に格納します。
その後、前回と同じく、Grid.js に適用しています。

動作の確認

最後に動作を確認します。

  • 同期の構成、Web サイトの参照 をクリックし表示された画面にの実行結果であるテーブルに、Dataverse の情報が表示されていれば完了です。

作業概要(Web API)

標準コンポーネントを使用せずデータを表示する 2つ目の方法は Web API を使うことです。
Liquid の時と同じの結果になる内容を Web API を使ってやっていきます。

テーブルへのアクセス権限の設定

こちらも同じく、Dataverse のテーブルにアクセスするための設定を行います。

Web API の使用に関する設定

次に、Web API を使用するための設定を行います。ここは、Web API 専用の設定です。

  • ポータル管理アプリ から サイト設定 をクリックし、新規をクリックします。

  • テーブルに対する Web API の有効化を設定します。

項目 設定値
名前 Webapi/{対象のテーブル}/enabled
Web サイト {自身のポータルサイト}
true
  • テーブルの列に対する Web API の有効化を設定します。
項目 設定値
名前 Webapi/{対象のテーブル}/fields
Web サイト {自身のポータルサイト}
{対象の列}
  • エラー処理に対するを設定します。
項目 設定値
名前 Webapi/error/innererror
Web サイト {自身のポータルサイト}
true

Web テンプレート (JavaScript 部品)の編集

前回作成した、JavaScript を動作させる部分のみを記載した Web テンプレートを編集します。

  • ポータル管理アプリ から Web テンプレート をクリックし、CustomTableScript をクリックします。
ソースに入力する値
<link href="https://unpkg.com/gridjs/dist/theme/mermaid.min.css" rel="stylesheet" />
<script src="https://unpkg.com/gridjs/dist/gridjs.umd.js"></script>
{% comment %} ライブラリの参照 {% endcomment %}

<style>
.my-btn-class {
  color: black;
}
.my-btn-class:focus,
.my-btn-class.focus {
  color: black;
}
</style>
{% comment %} 競合するスタイルの上書き {% endcomment %}

<script>
{% comment %} 変更点ここから {% endcomment %}
(function(webapi, $){
    function safeAjax(ajaxOptions) {
      var deferredAjax = $.Deferred();
  
      shell.getTokenDeferred().done(function (token) {
        // add headers for ajax
        if (!ajaxOptions.headers) {
          $.extend(ajaxOptions, {
            headers: {
              "__RequestVerificationToken": token
            }
          }); 
        } else {
          ajaxOptions.headers["__RequestVerificationToken"] = token;
        }
        $.ajax(ajaxOptions)
          .done(function(data, textStatus, jqXHR) {
            validateLoginSession(data, textStatus, jqXHR, deferredAjax.resolve);
          }).fail(deferredAjax.reject); //ajax
      }).fail(function () {
        deferredAjax.rejectWith(this, arguments); // on token failure, pass the token ajax and args
      });
  
      return deferredAjax.promise();  
    }
    webapi.safeAjax = safeAjax;
})(window.webapi = window.webapi || {}, jQuery)
{% comment %} Docs にある CSRF token の取得を含む Api 呼び出しのラッパー関数 {% endcomment %}
{% comment %} 変更点ここまで {% endcomment %}

document.addEventListener('DOMContentLoaded', async () => {

  {% comment %} 変更点ここから {% endcomment %}
  const res = await webapi.safeAjax({
        type: "GET",
        url: "/_api/nan_employees?$select=nan_name,nan_email,nan_phone_number",
        contentType: "application/json",
        success: function (res) {
            return res;
        }
  });
  {% comment %} 変更点ここまで {% endcomment %}

  new gridjs.Grid({
    {% comment %} 変更点ここから {% endcomment %}
    columns: ["Name", "EMail", 'Phone Number'],
    data: res.value.map(record => [record.nan_name, record.nan_email, record.nan_phone_number]),
    {% comment %} 変更点ここまで {% endcomment %}
    className: {
      paginationButton: "my-btn-class"
    },
    search: {
      enabled: true
    },
    sort: true,
    pagination: {
      enabled: true,
      limit: 2,
      summary: true
    },
    language: {
      "search": {
        "placeholder": "🔍 Search..."
      },
      "pagination": {
        "previous": "←",
        "next": "→",
        "showing": "😃 Displaying",
        "results": () => "Records"
      }
    }
  }).render(document.getElementById("gridWrapper"));

}, false);
</script>
{% comment %} Dataverse テーブルデータでの Grid.js の実行 {% endcomment %}

webapi.safeAjax を使って、指定のテーブルからデータを取得し、それを Grid.js に適用しています。
webapi.safeAjax は、データの取得だけであれば必要ありませんが、データの登録や更新、削除を行う場合は getTokenDeferred にて取得できるトークンが必要になってきます。

動作の確認

最後に動作を確認します。

  • 同期の構成、Web サイトの参照 をクリックし表示された画面にの実行結果であるテーブルに、Dataverse の情報が表示されていれば完了です。

まとめ

少し設定方法は違いますが、Web API、Liquid いずれでも Dataverse のテーブルにアクセスことができました。
使い分けというか、特徴としては以下かなと思います。

Liquid

  • 画面表示時に Dataverse にアクセスするため、待ち時間が少ない(ような気がします)と思います。
  • 登録、更新、削除はできないので、標準コンポーネントに任せる必要があります。

Web API

  • イベント発生時に Dataverse にアクセスするため、ボタンをクリックした際のイベントなどに適していると思います。
  • 登録、更新、削除など一通りが用意されています。
  • 使用できるまでの作法や、(データの取得のみなら不要ですが)トークンの取得など手順が少し複雑です。

ローコードツールでやるかはさておき、ゴリゴリ拡張するなら Web API を使うのがよいかと思います。

参考(公式)

https://docs.microsoft.com/en-us/power-apps/maker/portals/liquid/liquid-overview
https://docs.microsoft.com/en-us/power-apps/maker/portals/web-api-overview

余談

React などを使った自由なコンポーネントを追加できるようになったらうれしいなあ。

Discussion