【Power Pages】FetchXMLとLiquidを組み合わせてリッチなビューを作成する

_PowerPagesとは

PowerPagesはMicrosoft社が提供しているローコードのサービスとしてのソフトウェア (SaaS) プラットフォームである Power Platform の製品の一つです。

Power Pages とは | Microsoft Learn

Power Platform 製品の中でも使用例が多い Power Apps が、同じテナント内のユーザー向けのサービスでありいわゆる「社内向け」のコンテンツ作成を主としているのに対し、Power Pages は匿名でも利用できる外部ユーザー向けのweb コンテンツ の作成を主としています。

以下がPowerPagesの主な特徴です。

 

ローコードでサイトを作成可能

各コンポーネントをローコードで作成可能であり、ドラッグアンドドロップ等GUI上で操作を行うことで誰でも簡単にweb サイトのデザインを行うことができます。

デザインのテンプレートも多く用意されており、短工期での web サイト制作が可能です。

 

また、ローコードプラットフォームの性質上、どこにどのコンポーネントが配置されているかを把握しやすく、改修箇所が明確になり運用面でも優れています。

 

Dataverse との親和性が高く、DB操作を用いたwebサービスを低コストで開発可能

Dataverse はPower Platform で提供されているクラウドベースのデータベースです。

Power Pages はDataverse との親和性が高く、データベースを介したweb サービスをローコードで簡単に、そしてセキュアに開発することが可能です。

Dataverse との連携により、簡易な web サイトの構築にとどまらない、幅広いサービスを提供することが可能になります。

 

高いセキュリティ

Dataverseへのアクセス権限を細かく設定することができるなど高いセキュリティを有しています。一方で、データへのアクセス権をはじめ、適切なセキュリティの設定には一定以上の知識や技術が必要になります。

 

カスタマイズ性も高い

Java Script を用いたコーディング、HTML/CSS を用いたカスタムデザインの実装などが可能であり、プラットフォームの手が届かない箇所についても任意にカスタマイズが可能です。

 

2_意外と標準機能だとやれること少ない

1 で述べたような開発工数の削減、高いセキュリティなどの Power Pages の利点は、標準機能を用いて実装することで最大限に享受することができます。

一方で、標準機能のみの実装だとカスタマイズの幅はそこまで大きくなく、「かゆいところに手が届かない」といった場面も多いのが悩みどころです。

でも、理想の機能・UIを実現するためとは言え、せっかくのローコードプラットフォームなのに、Java Scriptなどを用いてゴリゴリにコーディングして実装するのはもったいないように感じます。そこで、今回はPower Pages で提供されている機能を用いて、ローコードでカスタマイズを行ってみたいと思います!!

3_各機能の説明

今回用いる2種類の機能について軽く紹介します。

Fetch XML

FetchXML を使用してデータをクエリする (Microsoft Dataverse) - Power Apps | Microsoft Learn

Microsoft Dataverse 独自の XML ベースのクエリ言語であり、html 内で直接クエリの実行が可能な機能になります。

標準機能で実装できるDataverse へのアクセス権限が Fetch XML を用いた Read アクションにも適用されるため、適切なセキュリティ設定を行うことで安全に用いることが可能です。

「ローコードツールなのにコーディングするの?」と思ったそこのあなたへ、Fetch XML は標準機能を用いて複雑なクエリ文を出力可能ですのでその心配は不要です!(手順は後述)

Liquid テンプレート

Liquid の概要 | Microsoft Learn

Power Pages で用いることができる html 上で機能するテンプレートです。

html上で動的コンテンツを扱うことができるため、場面によってはJavaScript よりも直観的にオブジェクトやデータを用いることが可能です。

このLiquid テンプレートとFetch XMLの親和性が高く、クエリで取得したデータをLiquidテンプレートでハンドリングすることで格段に扱いやすくなります。

  • Fetch XML で取得したオブジェクトに対して for 文を用いたループ処理でのデータの取り出しが可能
    • html内でDBとやり取りを行え、そのままhtmlに値を渡すことが可能
  • if 文等の簡単なロジックの実装も可能取得したデータを後天的にフィルターできる

と、非常に便利な組み合わせなのですが、いかんせん実装に関する情報が少ない状況となっています…。頼みの綱の公式のLearnが少々ぱっとしないのも悲しいところです😿

そこで、今回は上記のLiquid テンプレートと Fetch XML を組み合わせた実装例を紹介しようと思います!

4_ハンズオン手順

実際にFetch XML Liquid テンプレートを組み合わせてデザインや機能を追加したビュー画面を作成していきます。今回は、とあるプロジェクトに参画している企業情報を一覧で表示する、という想定で画面を作成していきます

今回作成するビューは Power Pages に用意されているwebテンプレートの「Search」のhtml , Liquid を参考に作成しています。また実装する検索機能については標準機能のビューに設定できるものを参考に作成しています。Power Pages に用意されているテンプレートはカスタムの参考になるものも多いので、興味のある方は中身を見てみることをお勧めします!

まずはPower Pages の標準で実装されている「リスト」を使って作製した企業情報の一覧ページを見てみましょう。

非常に手軽に実装できるのに加えて、各レコードの詳細情報を確認するページへの遷移やレコードのフィルタ機能も簡単に実装可能です。

その分、デザインはシンプルで少々味気ないものにはなってしまいます。必要最低限ということですね。

 

次に今回カスタムで実装する企業一覧画面を見てみましょう。

だいぶ華やかになりましたね!上記画面に実装されているデザイン・機能は以下になります。

  1. タイル形式で要素を表示
  2. 各タイルからレコードの詳細画面に遷移可能
  3. タイルごとに外部リンクを作成する
  4. 内容に応じてタイルごとに色分けを行う
  5. フィルター機能

標準機能の持っている詳細画面への遷移やフィルタ機能も実装できています。
Fetch XML
Liquid テンプレートを用いることで、上記のようなリッチなUIをローコードの利点を失わないまま実装することが可能になります。

それでは、実際の実装手順を追いながら、各機能の実装について説明していきます。

1.テーブルを作成

 

今回の対象となるテーブルを作成します。

今回は以下の4つの列を作成します

列表示名

備考

企業名(プライマリ)

一行テキスト

 

説明

複数行テキスト

 

民間・公共区分

選択肢

色分け表示のフラグにも使用

企業サイトurl

一行テキスト

 

 

2.Fetch XML の定義

先ほど作成したテーブル内容のビュー画面を作成します。

まずは先ほど作成したテーブル向けのクエリ文を作成していきます。

クエリ文の作成にあたり、出力を行うためにモデル駆動アプリを作成します。表示するビューには、今回一覧表示として出力したい列を含めておきます。

ビュー画面から「フィルターを編集する」を選択するとフィルターの編集画面が表示されます。ここに適用したいフィルター条件を当てはめることでFetchXMLに内容を反映できます。今回は画面からのフィルター機能を実装したいので、「企業名」と「区分」を条件に含めて設定します。

フィルター条件の編集が完了したら「FetchXMLのダウンロード」を選択します。すると、今設定した内容を反映したxml ファイルをダウンロードできます。

 

このFetchXMLファイルには以下の内容が反映されています

  • テーブル名
  • ビューに表示した列名
  • ソート順
  • フィルター条件
<fetch version="1.0" output-format="xml-platform" mapping="logical" savedqueryid="dummy_value" no-lock="false" distinct="true">
    <entity name="prefix_companies">
        <attribute name="statecode"/>
        <attribute name="prefix_company_name"/>
        <attribute name="prefix_companiesid"/>
        <attribute name="prefix_detail"/>
        <attribute name="prefix_class"/>
        <attribute name="prefix_site_url"/>
        <filter type="and">
            <condition attribute="statuscode" operator="eq" value="1"/>
            <filter type="and">
                <condition attribute="prefix_comoany_name" operator="like" value="%dummy%"/>
                <condition attribute="prefix_class" operator="eq" value="000000000"/>
                <condition attribute="prefix_class" operator="eq" value="000000001"/>
                <condition attribute="prefix_class" operator="eq" value="000000002"/>
            </filter>
        </filter>
    </entity>
</fetch>

必要なパラメータを整えた状態で出力を行うことで、コーディングを行うことなくFetchXMLの作成を行うことができます。

上記機能はDataverseの開発用アプリケーションである XrmToolBox にて提供されているツール「FetchXML Builder」からも利用できます。モデル駆動アプリを作らずに出力を行いたい場合はこちらもご検討ください。

 

3.HTMLの作成

先ほど出力したFetch XML を用いて、webページのHTMLを作製していきます。

{% assign searchCompany = request.params["searchCompany"] %}{% assign searchClass = request.params["searchClass"] %}


<!-- FetchXMLを用いたクエリ -->
{% fetchxml company %}
<fetch version="1.0" output-format="xml-platform" mapping="logical" no-lock="false" distinct="true">
    <entity name="prefix_companies">
        <attribute name="statecode"/>
        <attribute name="prefix_company_name" />
        <attribute name="prefix_companiesid"/>
        <attribute name="prefix_detail"/>
        <attribute name="prefix_class"/>
        <attribute name="prefix_site_url"/>
        <filter type="and">
            <condition attribute="statuscode" operator="eq" value="1"/>
            <filter type="and">
                <!-- 企業名の検索条件 -->
                {% unless searchCompany == empty or searchCompany == null %}
                <condition attribute="prefix_comoany_name" operator="like" value="%{{ searchCompany }}%"/>
                {% endunless %}
                <!-- 区分の検索条件 -->
                {% unless searchClass == empty or searchClass == null %}
                <condition attribute="prefix_class" operator="eq" value="{{ searchClass }}"/>
                {% endunless %}
            </filter>
        </filter>
    </entity>
</fetch>
{% endfetchxml %}


<script>
   
    // 各企業に沿った部分urlを表示する 引数に与えられたurlを参照
    function toDetail(partial_url) {
      window.location.href = './detail?id=' + partial_url;
    }


</script>


<div class="col-md-12" style="padding: 8px; display: flex; flex-direction: column;">
    <h2 class="h2__ttl">企業情報一覧</h2>
    <p>各企業情報のタイルをクリックすると、選択した企業の詳細情報を確認できます。
</div>


<!-- 検索ボックス -->


<div id="dummy_value" class="content-panel card entitylist-filter">
    <form method="post" action="./">
        <div class="card-body">
            <ul id="entitylist-filters" class=" list-inline ">
                <li class="entitylist-filter-option-group list-inline-item">
                    <label class="entitylist-filter-option-group-label h4" data-filter-id="0" for="0">
                        企業名
                    </label>
                    <ul class="list-unstyled" role="presentation">
                    <div class="entitylist-filter-option-group-box-overflow">
                       
                        <li class="entitylist-filter-option" role="presentation">
                           
                            <div class="input-group entitylist-filter-option-text">
                                <input class="form-control has-tooltip" type="text" name="searchCompany" value="{{ searchCompany }}" id="0"  >
                            </div>
                        </li>
                    </div>
                    </ul>
                </li>
                <li class="entitylist-filter-option-group list-inline-item">
                    <label class="entitylist-filter-option-group-label h4" data-filter-id="1" for="dropdown_1" value="{{ searchClass }}">区分</label>
                    <ul class="list-unstyled" role="presentation">
                        <li class="entitylist-filter-option" role="presentation">
                            <div class="input-group entitylist-filter-option-text">
                                <select class="form-control form-select" name="searchClass" id="dropdown_1">
                                    <option value="" label=" "> </option>
                                    <option value="437140000" label="A区分">A区分</option>                              
                                    <option value="437140001" label="B区分">B区分</option>
                                    <option value="437140002" label="C区分">C区分</option>
                                </select>
                            </div>
                        </li>
                   
                    </ul>
                </li>
               
            </ul>
            <div class="float-end">
                <button type="submit" class="btn btn-default btn-entitylist-filter-submit" data-serialized-query="mf" data-target="#EntityList_dummy_value">
                    検索
                </button>
            </div>
        </div>
    </form>
</div>


<!-- 企業一覧 -->
{% assign companyResult = company.results.entities %}
{% if company.results.entities.size != 0 %}


  <div class="row sectionBlockLayout text-left" style="padding: 8px; display: flex; flex-wrap: wrap; margin: 0px; min-height: auto; justify-content: center;">
    <div class="menu__container" tabindex="0">
     
      <!-- fetchで持ってきたテーブルの内容を各タイルに反映させる -->
      {% for company in companyResult %}
        <!-- 区分に応じて背景色を変更 -->
        {% case company.prefix_class.value %}
            {% when '000000000' %}
                {% assign background = "#006442" %}
            {% when '000000001' %}
                {% assign background = "#7A942E" %}
            {% when '000000002' %}
                {% assign background = "#87D37C" %}
            {% else %}
                {% assign background = "#FFFFFF" %}
        {% endcase %}
        <div class="btn-group btn-select input-group-btn">
            <!-- タイル形式でレコードを抽出。各詳細画面への遷移を定義 -->
            <button onclick="toDetail('{{company.prefix_companiesid}}')"
                class=" applicationTypeItem"
                style=" background-color: {{background}}; display: flex; flex-direction: column; margin-right: 7px; margin-left: 7px; margin-bottom: 14px; width: 320px; text-align: left;"
                value="{{company.attributevalue}}">


                <!-- 企業名 -->
                {% unless company.prefix_company_name == empty %}
                <div class="card__container">
                <div class="card__ttl"><h3 style="color: #FFFFFF;">{{company.prefix_company_name}}</h3></div>
                </div>  
                {% endunless %}
                <!-- 説明 -->
                {% unless company.prefix_detail == empty %}
                <div class="card__container">
                <div class="card__ttl"><p style="color: #FFFFFF;">{{company.prefix_detail}}</p></div>
                </div>  
                {% endunless %}
                <!-- 区分 -->
                {% unless company.prefix_class == empty %}
                <div class="card__container">
                <div class="card__ttl"><p style="color: #FFFFFF;">{{company.prefix_class.Label}}</p></div>
                </div>  
                {% endunless %}
                <!-- サイトURL -->
                {% unless company.prefix_site_url == empty %}
                    {% unless company.prefix_company_name == empty %}
                    <div class="card__container">
                    <div class="card__ttl"><a href="{{company.prefix_site_url}}" style="color: #FFFFFF;">{{company.prefix_company_name}} 公式サイトへ</a></div>
                    </div>
                    {% endunless %}
                {% endunless %}


            </button>
        </div>
        {% endfor %}


    </div>
    <!-- 検索結果が見つからなかったメッセージの表示 -->
    {% else %}
      <div class="row sectionBlockLayout text-left" style="padding: 8px; display: flex; flex-wrap: wrap; margin: 0px; min-height: auto; justify-content: center;">
        <div class="menu__container" tabindex="0">
          <div class="col-md-12" style="display: flex; flex-direction: column;">
            <h2 class="h2__ttl">条件に合致する企業情報はありませんでした。条件を変更して再度検索を行ってください。</h2>
          </div>
        </div>
      </div>
    {% endif %}


  </div>
</div>

 

コード内で用いられている {{ }} {%  %} などで囲われている要素が Liquid テンプレートになります。

Fetch XML
のクエリで取得したデータを格納したオブジェクトに対して、Liquidテンプレートを用いて以下の様な処理を行っています。

  • for 文 を用いて、オブジェクトに格納した各レコードを順番に出力
    • 各要素をhtml内に直接埋め込むことができるため、直観的に画面を作成できる
    • ex) href 要素の作成→aタグに直接オブジェクト内の値を受け渡すことが可能
  • if 文を用いて分岐を作成
    • 検索条件が入力されている場合はフィルターを実行、入力されていない場合はフィルターを実行しない
    • 検索結果が表示されなかった場合には検索条件を変えるように促すメッセージを表示

上記コードを適用することで、手順の冒頭で示した以下の画面が作成できました。

以下、実際の実装画面を見ながら各種機能の実装についてみていきます。

 

3_1.タイル状のUIの作成

1.タイル形式で要素を表示
2.各タイルからレコードの詳細画面に遷移可能
3.タイルごとに外部リンクを作成する

上記3つの機能は、Fetch XML を用いたクエリから受け取った値をLiquidテンプレートでハンドリングする中で実装しています。

<!-- fetchで持ってきたテーブルの内容を各タイルに反映させる -->
{% for company in companyResult %}
<!-- 区分に応じて背景色を変更 -->
<div class="btn-group btn-select input-group-btn">
    <!-- タイル形式でレコードを抽出。各詳細画面への遷移を定義 -->
    <button onclick="toDetail('{{company.prefix_companiesid}}')"
        class=" applicationTypeItem"
        style=" background-color: {{background}}; display: flex; flex-direction: column; margin-right: 7px; margin-left: 7px; margin-bottom: 14px; width: 320px; text-align: left;"
        value="{{company.attributevalue}}">


        <!-- 企業名 -->
        {% unless company.prefix_company_name == empty %}
        <div class="card__container">
        <div class="card__ttl"><h3 style="color: #FFFFFF;">{{company.prefix_company_name}}</h3></div>
        </div>  
        {% endunless %}
        <!-- 説明 -->
        {% unless company.prefix_detail == empty %}
        <div class="card__container">
        <div class="card__ttl"><p style="color: #FFFFFF;">{{company.prefix_detail}}</p></div>
        </div>  
        {% endunless %}
        <!-- 区分 -->
        {% unless company.prefix_class == empty %}
        <div class="card__container">
        <div class="card__ttl"><p style="color: #FFFFFF;">{{company.prefix_class.Label}}</p></div>
        </div>  
        {% endunless %}
        <!-- サイトURL -->
        {% unless company.prefix_site_url == empty %}
            {% unless company.prefix_company_name == empty %}
            <div class="card__container">
            <div class="card__ttl"><a href="{{company.prefix_site_url}}" style="color: #FFFFFF;">{{company.prefix_company_name}} 公式サイトへ</a></div>
            </div>
            {% endunless %}
        {% endunless %}


    </button>
</div>
{% endfor %}

受け取ったデータを「companyResult」という変数に格納し、オブジェクトの各要素に対して for 文を用いたループ内で処理を実行しています。

 

1.タイル形式で要素を表示
2.各タイルからレコードの詳細画面に遷移可能

<button>タグに各情報を表示することで実現しています。<button>タグの onclick要素に画面遷移のロジックを実装することで、詳細画面への実装を実現しています。


Power Pages
において、レコードの編集/詳細フォームはレコードの一意識別子の情報をクエリ文字としてurlに含めることで表示が可能です。今回は「toDetail」関数の引数としてオブジェクトから取得した一意識別子を与えることで、詳細画面への遷移を実現しています。

(遷移先の画面)

 

3.タイルごとに外部リンクを作成する

<a>タグ内に、レコードが保持しているurlを適用することで、各タイルに別々の外部リンクを実装しています。

 

3_2.条件に応じた動的なデザイン設定

4.内容に応じてタイルごとに色分けを行う

上記機能はLiquidテンプレートを用いた分岐を実装することで実現しています。

{% case company.prefix_class.value %}
    {% when '000000000' %}
        {% assign background = "#006442" %}
    {% when '000000001' %}
        {% assign background = "#7A942E" %}
    {% when '000000002' %}
        {% assign background = "#87D37C" %}
    {% else %}
        {% assign background = "#FFFFFF" %}
{% endcase %}
<div class="btn-group btn-select input-group-btn">
    <!-- タイル形式でレコードを抽出。各詳細画面への遷移を定義 -->
    <button onclick="toDetail('{{company.prefix_companiesid}}')"
        class=" applicationTypeItem"
        style=" background-color: {{background}}; display: flex; flex-direction: column; margin-right: 7px; margin-left: 7px; margin-bottom: 14px; width: 320px; text-align: left;"
        value="{{company.attributevalue}}">

上記の case 文内で、区分の値に応じて 「background」という変数に別々の値を格納するように分岐を実装しています。

今回の場合は、A区分なら濃い緑色、B区分ならオリーブ色、C区分なら黄緑色を与えています。

定義した「background」を、<button>タグの style 要素内の background-color に与えることで、区分に応じた背景色の表示を実装しています。

 

3_3.検索機能(フィルター機能)

5.フィルター機能

こちらは少し応用編になります。POSTメソッドを用いてリクエストのパラメータに検索条件を含め、パラメータから受け取った値をLiquidテンプレートを用いて変数に格納、その変数を用いてFetch XML内のフィルター条件を動的に変更させる、という形で実装しています。

{% assign searchCompany = request.params["searchCompany"] %}{% assign searchClass = request.params["searchClass"] %}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<filter type="and">
    <!-- 企業名の検索条件 -->
    {% unless searchCompany == empty or searchCompany == null %}
    <condition attribute="prefix_comoany_name" operator="like" value="%{{ searchCompany }}%"/>
    {% endunless %}
    <!-- 区分の検索条件 -->
    {% unless searchClass == empty or searchClass == null %}
    <condition attribute="prefix_class" operator="eq" value="{{ searchClass }}"/>
    {% endunless %}
</filter>

受け取ったパラメータをそれぞれ serchCompany,  serchClass  という変数に格納、各変数に値が入っている時のみフィルター条件を追加する、という形で実現しています。

企業名=「いろは」で検索した場合

企業名=「ちりぬるを」で検索した場合(該当なし)

上記操作をJava等を用いたDB操作で実現しようとするとコーティング量が大きくなりますが、Fetch XMLLiquidテンプレートを組み合わせることで、コンパクトに実装することができます。

 

これで、標準機能のビューがもつ機能である「検索機能」「詳細画面」への遷移を実現しつつ、タイル状での表示や条件に応じた色分けなどリッチなビューを作成することができました。

FetchXMLは自動で生成したものを使用しており、JavaScriptも一行も書いておりません。FetchXMLLiquidテンプレートを組み合わせることで、ローコードツールの利点を生かしたままリッチな画面を作成することができました!

もちろん、ここからさらにLiquidテンプレートを用いて複雑な分岐を実装したり、HTML/CSSを書き足して画面を豪華にしたり、JavaScriptを用いて追加の機能を実装することも可能です。選択肢が多く取れるため、実装したい機能、かけられる工数や技術力に応じて、自分の目的にかなった範囲でカスタムを行えるのもPowerPagesの魅力だと筆者は感じております。

 

5_結び

今回はPowerPagesのカスタマイズに重点を置いて記事を作成いたしました。

Power Pagesは、テナントを共有しない外部のユーザーにもサービスを展開できる便利なアプリケーションですが、Power Platform の中で人気の高いPower Apps Power Automate 等と比べると情報が少ないと感じることがしばしばあります

難しい設定やコーディングを用いることなくDB操作を行えるwebサービスを開発できるため利便性が非常に高く、また本記事内で示したように様々なカスタマイズを施すことで目的にかなったweb サービスの展開が可能になりますので、利用目的に合致する場合であれば積極的に活用していくことをお勧めします!!!
(そしてPowerPagesの開発情報が増えることを筆者は切に願っております(笑))

 

関連記事

最新情報をお届けします!

RPAに関する最新コラムやイベント情報をメールで配信中です。
RPA領域でお仕事されている方に役立つナレッジになりますので、ぜび登録してください!

最新情報を受け取る方はこちら

もっと知りたい方はこちら


ページトップ