LoginSignup
4
1

More than 3 years have passed since last update.

【SAPUI5】Fragmentを使いこなす

Last updated at Posted at 2020-03-15

はじめに

フラグメントとは、再利用可能なUIパーツです。以下のような目的で使うことが多いと思います。
①ビューの一部を部品化するため(再利用のため、またはビューをすっきりさせるため)
②ダイアログ等のポップアップを出すため

フラグメントの種類にはXML、JS、HTMLがありますが、この記事ではXMLを対象にします。
今回は、「これさえ知っていればフラグメントを使うのに困らない」といえる、基礎から応用の技について紹介したいと思います。

内容

  1. フラグメントの定義
  2. フラグメントのロード
  3. フラグメント内のコントロールの取得
  4. フラグメントのコントローラー
  5. フラグメントのロード処理を共通化する

サンプルアプリ

フラグメントの説明のため、サンプルアプリを作成します。画面の一部にフラグメントを埋め込んでいます。
コード:https://github.com/miyasuta/FragmentSample

image.png

"Show Dialog"ボタンを押すとダイアログが出ます。これもフラグメントで作ります。
image.png

プロジェクト構成は以下のようになっています。
image.png

ODataサービスは定番のNorthwindを使います。
https://services.odata.org/V2/Northwind/Northwind.svc/

1. フラグメントの定義

フラグメント用のXMLファイルを作成します。ファイルの名称は、<namespace>.<FragmentName>.fragment.xmlとします。

フラグメント1:ビューに埋め込むフォーム

Form.fragment.xml
<core:FragmentDefinition
    xmlns="sap.m"
    xmlns:core="sap.ui.core">
    <Panel headerText="{i18n>panelTitle1}">
        <VBox class="sapUiSmallMargin" width="50%">
            <Label text="{i18n>supplierID}" labelFor="supplierId"/>
            <Input id="supplierId" />
            <Label text="{i18n>companyName}" labelFor="companyName"/>
            <Input id="companyName" />
        </VBox>     
    </Panel>
</core:FragmentDefinition>

フラグメント2:ダイアログ

Dialog.fragment.xml
<core:FragmentDefinition
    xmlns="sap.m"
    xmlns:core="sap.ui.core">
    <Dialog title="{i18n>Suppliers}">
        <content>
            <List items="{/Suppliers}">
                <items>
                    <StandardListItem
                        title="{SupplierID}"
                        description="{CompanyName}"
                        type="Active" 
                        press="onItemPress" />
                </items>
            </List>
        </content>
    </Dialog>
</core:FragmentDefinition>

2. フラグメントのロード

フラグメントをロードする方法は2つあります。

  1. XMLビューの中で静的に宣言する
  2. コントローラーの中で動的にロードする

2.1. XMLビューの中で静的に宣言する

フラグメント1は親のビューであるHome Viewに埋め込みます。Home Viewは以下のようになります。

Home.view.xml
<mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" controllerName="demo.Train_27_Fragment.controller.Home"
    xmlns:html="http://www.w3.org/1999/xhtml">

    <Page id="page" title="{i18n>fragmentSample}">
        <content>
            <Panel headerText="{i18n>mainPart}">
                <Button text="{i18n>showDialog}" press="onShowDialog" class="sapUiTinyMarginEnd"/>
                <Button text="{i18n>submit}" press="onSubmit" />                
            </Panel>            
            <!--フラグメントの埋め込み-->
            <core:Fragment fragmentName="demo.Train_27_Fragment.fragment.Form" type="XML" />
        </content>
    </Page>
</mvc:View>

2.2. コントローラーの中で動的にロードする

フラグメント2、"Show Dialog"ボタンが押されたときに動的にロードします。Home Viewのコントローラーで以下のように書きます。

Home.controller.js
sap.ui.define([
    "demo/Train_27_Fragment/controller/BaseController",
    "sap/ui/core/Fragment"
], function (BaseController, Fragment) {
    "use strict";

    return BaseController.extend("demo.Train_27_Fragment.controller.Home", {
        onShowDialog: function () {
            if (!this._oDialog) {
                Fragment.load({
                    name: "demo.Train_27_Fragment.fragment.Dialog",
                    controller: this
                }).then(function(oDialog) {
                    //ダイアログがロードされたら
                    this._oDialog = oDialog;
                    this._oDialog.open();
                }.bind(this));
            }else {
                this._oDialog.open();
            }
        }
    });

});

Fragmentのloadファンクションはフラグメントを非同期にロードします。結果(Promise)が返ってきたらダイアログをオープンします。
また、返ってきたダイアログのインスタンスをコントローラーのプロパティthis._oDialogに追加しています。こうすると、次回以降ダイアログが呼ばれたときはthis._oDialogを開くだけでよくなります。

さて、この時点でアプリを実行して、"Show Dialog"を押してみましょう。すると、No dataと出てきてしまいます。Dialog.fragment.xmlの中でバインドを定義していますが、モデルがセットされていないようです。
image.png

フラグメントでモデルが使えない?

実は、コントローラーから開いたフラグメントの場合、モデルが自動的には使用可能になりません。
参考:Using Dialogs Defined as Fragments

対処方法は2つあります。
①ロードした後にフラグメントにモデルをセットする
②ロードした後にフラグメントをビューのdependentに追加する

どちらのやり方も上記のリンク先に載っています。今回は②の方法でやってみます。

                }).then(function(oDialog) {
                    this._oDialog = oDialog;
                    //ダイアログからモデルを使用できるようにする
                    this.getView().addDependent(this._oDialog);
                    this._oDialog.open();
                }.bind(this));

こうすると、データもちゃんと表示されます。
image.png

3. フラグメント内のコントロールの取得

コントローラーからフラグメント内のコントロールにアクセスしたい場合があります。
コントロールの取得方法はフラグメントがどのようにロードされたかによって変わってきます。

フラグメントのID フラグメントのロード方法 コントロールの取得方法
なし XMLビューで静的に宣言 this.byId("controlId")
なし コントローラーでロード sap.ui.getCore().byId("controlId")
あり XMLビューで静的に宣言 this.byId(sap.ui.core.Fragment.createId("fragmentId", "controlId"))
あり コントローラーでロード sap.ui.core.Fragment.byId("fragmentId", "controlId")

Retrieving Control Instances by Their IDをもとに作成

フラグメントのIDとは

フラグメントをロードする際に、フラグメント自体にIDをつけることができます。これによって、以下のようなid重複を回避できます。

  • ビューのコントロールと同じidをフラグメントのコントロールで使用している場合
  • ビュー内で同じフラグメントを複数回使用した場合

3.1. フラグメントにIDを指定しない場合

今回のサンプルではフラグメントにIDを指定していなかったので、まずは指定しない場合の例を見てみましょう。
IDを指定しない場合、静的に宣言したフラグメントのコントロールにはビューのIDがprefixとしてつきます。これは、ビューの中にある通常のコントローラーと同じです。対してコントローラーでロードしたフラグメントにはprefixがつきません。
image.png

3.1.1 XMLビューで静的に宣言したフラグメントの場合

フラグメント内のコントロールは、ビューのコントロールと同じように取得することができます。

var oControl = this.byId("controlId");

例として、"Show Dialog"でサプライヤを選択したときに、Supplier IDとCompany Nameに値を設定します。
image.png

        onItemPress: function (oEvent) {
            //選択されたサプライヤの情報を取得
            var sSupplierId = oEvent.getSource().getBindingContext().getProperty("SupplierID");
            var sCompanyName = oEvent.getSource().getBindingContext().getProperty("CompanyName");

            //フラグメントのコントロールにアクセス
            var oSupplier = this.byId("supplierId");
            var oCompanyName = this.byId("companyName");

            //値をセット
            oSupplier.setValue(sSupplierId);
            oCompanyName.setValue(sCompanyName);

            this._oDialog.close();
        }

3.1.2. コントローラーでロードしたフラグメントの場合

この場合、this.byId("controlId")でアクセスすることはできません。代わりに、以下の方法でアクセスします。

var oControl = sap.ui.getCore().byId("controlId");

例として、"Show Dialog"でサプライヤを選択したときに、サプライヤにひもづく製品のリストを表示させます。
image.png

        onItemPress: function (oEvent) {
            //...           
            //製品のリストの表示
            this._showProducts(sSupplierId);
            this._oDialog.close();
        },

        _showProducts: function (sSupplierId) {
            //テーブルのフラグメントをロード
            if (!this._oTablePanel) {
                Fragment.load({
                    name: "demo.Train_27_Fragment.fragment.Table",
                    controller: this
                }).then(function(oPanel) {
                    this._oTablePanel = oPanel;
                    this._oPage.addContent(this._oTablePanel);
                    //バインドの設定
                    this._bindTable(sSupplierId);
                }.bind(this));
            }else {
                this._bindTable(sSupplierId);
            }
        },

        _bindTable: function (sSupplierId) {
            var sPath = "/Suppliers(" + sSupplierId + ")/Products";
            if (!this._oTemplate) {
                this._oTemplate = new ColumnListItem({
                    cells: [
                        new Text({
                            text: "{ProductID}"
                        }),
                        new Text({
                            text: "{ProductName}"
                        }),
                        new Text({
                            text: "{CategoryID}"
                        })                  
                    ]
                });             
            }
            //フラグメント内のコントロールにアクセス
            var oTable = sap.ui.getCore().byId("productTable");
            oTable.bindItems({
                path: sPath,
                template: this._oTemplate
            });
        }
    });
Table.fragment.xml
<core:FragmentDefinition
    xmlns="sap.m"
    xmlns:core="sap.ui.core">
    <Panel xmlns="sap.m" headerText="{i18n>panelTitle2}">
        <Table id="productTable">
            <columns>
                <Column>
                    <Text text="{i18n>productID}"/>
                </Column>
                <Column>
                    <Text text="{i18n>productName}"/>
                </Column>   
                <Column>
                    <Text text="{i18n>categoryID}"/>
                </Column>               
            </columns>
        </Table>        
    </Panel>
</core:FragmentDefinition>

_bindTableでテーブルにデータをバインドしています。サプライヤが選択される都度、ひもづく製品のパスをテーブルにバインドするため、フラグメント内にあるTableコントロールにアクセスする必要があります。
Tableコントロールにはthis.byId("productTable")でアクセスすることができません。代わりに、sap.ui.getCore().byId("productTable")でアクセスします。

3.2. フラグメントにIDを指定した場合

次に、フォームとテーブルのフラグメントにそれぞれIDを指定してみます。

XMLビューで静的に宣言

Home.view.xml
    <Page id="page" title="{i18n>fragmentSample}">
        <content>
            <!--省略-->           
            <!--フラグメントの埋め込み(ID指定)-->
            <core:Fragment id="formFragment" fragmentName="demo.Train_27_Fragment.fragment.Form" type="XML" />
        </content>
    </Page>

コントローラーでロード

        _showProducts: function (sSupplierId) {
            //テーブルのフラグメントをロード
            if (!this._oTablePanel) {
                Fragment.load({
                    name: "demo.Train_27_Fragment.fragment.Table",
                    controller: this,
                    id: "tableFragment" //ID指定
                }).then(function(oPanel) {
                    this._oTablePanel = oPanel;
                    this._oPage.addContent(this._oTablePanel);
                    //バインドの設定
                    this._bindTable(sSupplierId);
                }.bind(this));
            }else {
                this._bindTable(sSupplierId);
            }
        }

IDを指定した場合、静的に宣言したフラグメントのコントロールにはビューのID + フラグメントのIDがprefixとしてつきます。コントローラーでロードしたフラグメントにはフラグメントのIDがprefixとしてつきます。
image.png

3.2.1 XMLビューで静的に宣言したフラグメントの場合

FragmentのcreateIdというメソッドを使って生成したIDを、this.byId()に渡します。

var oControl = this.byId(sap.ui.core.Fragment.createId("fragmentId", "controlId"));

onItemPressでコントロールを取得する処理は以下のように変わります。

            // var oSupplier = this.byId("supplierId");
            // var oCompanyName = this.byId("companyName");
            var oSupplier = this.byId(sap.ui.core.Fragment.createId("formFragment", "supplierId"));
            var oCompanyName = this.byId(sap.ui.core.Fragment.createId("formFragment", "companyName"));

3.2.2. コントローラーでロードしたフラグメントの場合

FragmentのbyIdというメソッドを使って取得します。

var oControl = sap.ui.core.Fragment.byId("fragmentId", "controlId");

_bindTableでTableコントロールを取得する処理は以下のように変わります。

            //var oTable = sap.ui.getCore().byId("productTable");
            var oTable = sap.ui.core.Fragment.byId("tableFragment", "productTable");

4. フラグメントのコントローラー

フラグメントのコントローラーは、ビューのコントローラーそのものを使うのが一般的です。しかし、ビューのコントローラーに色々詰め込みたくない場合や、フラグメントのコントローラーを再利用したい場合は、独自のコントローラーを渡すこともできます。

フラグメントに独自のコントローラーを渡す方法については、以下の記事をご参照ください。
【SAPUI5】コントローラーを短くするには

5. フラグメントのロード処理を共通化する

一つのビューで複数のフラグメントをロードする場合、フラグメントのロード処理を共通化すると便利です。複数のビューでその処理を使い回せるようにBaseControllerでロード処理を定義しておくとよいでしょう。

フラグメントの名称(例:"Dialog")をキーとした配列を作成しておいて、フラグメントをロードしたら配列にフラグメントを格納します。次回以降ロードしたい場合は、配列の中からロード済のフラグメントを取得して返します。

BaseController.js
sap.ui.define([
    "sap/ui/core/mvc/Controller",
    "sap/ui/core/Fragment"
], function (Controller, Fragment) {
    "use strict";

    return Controller.extend("demo.Train_27_Fragment.controller.BaseController", {

        //フラグメントをロード
        loadFragment: function (sFragmentName, oController, sId) {
            if (!this._aFragments) {
                this._aFragments = {};
            }

            return new Promise(function(resolve, reject) {
                if (!this._aFragments[sFragmentName]) {
                    var sName = this.getOwnerComponent().getMetadata().getComponentName() + ".fragment." + sFragmentName;
                    Fragment.load({
                        id: sId,
                        name: sName,
                        controller: oController
                    }).then(function(oFragment) {
                        this._aFragments[sFragmentName] = oFragment;
                        this.getView().addDependent(oFragment);
                        resolve(this._aFragments[sFragmentName]);
                    }.bind(this));              
                }else {
                    resolve(this._aFragments[sFragmentName]);
                }
            }.bind(this));
        },

        //ロードされていることがわかっているフラグメントを取得するとき
        getFragment: function (sFragmentName) {
            return this._aFragments[sFragmentName];
        }

    });
});

コントローラーから利用する場合は以下のようになります。

sap.ui.define([
    "demo/Train_27_Fragment/controller/BaseController", //BaseControllerに
置き換え
], function (BaseController) {
    "use strict";

    return BaseController.extend("demo.Train_27_Fragment.controller.Home", {

        onShowDialog: function () {
            this.loadFragment("Dialog", this).then(function(oDialog) {
                oDialog.open();
            });                      
        }
4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1