##はじめに
フラグメントとは、再利用可能なUIパーツです。以下のような目的で使うことが多いと思います。
①ビューの一部を部品化するため(再利用のため、またはビューをすっきりさせるため)
②ダイアログ等のポップアップを出すため
フラグメントの種類にはXML、JS、HTMLがありますが、この記事ではXMLを対象にします。
今回は、「これさえ知っていればフラグメントを使うのに困らない」といえる、基礎から応用の技について紹介したいと思います。
##内容
- フラグメントの定義
- フラグメントのロード
- フラグメント内のコントロールの取得
- フラグメントのコントローラー
- フラグメントのロード処理を共通化する
###サンプルアプリ
フラグメントの説明のため、サンプルアプリを作成します。画面の一部にフラグメントを埋め込んでいます。
コード:https://github.com/miyasuta/FragmentSample
"Show Dialog"ボタンを押すとダイアログが出ます。これもフラグメントで作ります。
ODataサービスは定番のNorthwindを使います。
https://services.odata.org/V2/Northwind/Northwind.svc/
##1. フラグメントの定義
フラグメント用のXMLファイルを作成します。ファイルの名称は、<namespace>.<FragmentName>.fragment.xml
とします。
フラグメント1:ビューに埋め込むフォーム
<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:ダイアログ
<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つあります。
- XMLビューの中で静的に宣言する
- コントローラーの中で動的にロードする
###2.1. XMLビューの中で静的に宣言する
フラグメント1は親のビューであるHome Viewに埋め込みます。Home Viewは以下のようになります。
<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のコントローラーで以下のように書きます。
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の中でバインドを定義していますが、モデルがセットされていないようです。
####フラグメントでモデルが使えない?
実は、コントローラーから開いたフラグメントの場合、モデルが自動的には使用可能になりません。
参考:[Using Dialogs Defined as Fragments]
(https://sapui5.hana.ondemand.com/1.75.0/#/topic/aeb86c181b9742a2bf88049abf9ccb95)
対処方法は2つあります。
①ロードした後にフラグメントにモデルをセットする
②ロードした後にフラグメントをビューのdependentに追加する
どちらのやり方も上記のリンク先に載っています。今回は②の方法でやってみます。
}).then(function(oDialog) {
this._oDialog = oDialog;
//ダイアログからモデルを使用できるようにする
this.getView().addDependent(this._oDialog);
this._oDialog.open();
}.bind(this));
##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がつきません。
####3.1.1 XMLビューで静的に宣言したフラグメントの場合
フラグメント内のコントロールは、ビューのコントロールと同じように取得することができます。
var oControl = this.byId("controlId");
例として、"Show Dialog"でサプライヤを選択したときに、Supplier IDとCompany Nameに値を設定します。
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"でサプライヤを選択したときに、サプライヤにひもづく製品のリストを表示させます。
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
});
}
});
<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ビューで静的に宣言
<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としてつきます。
####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")をキーとした配列を作成しておいて、フラグメントをロードしたら配列にフラグメントを格納します。次回以降ロードしたい場合は、配列の中からロード済のフラグメントを取得して返します。
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();
});
}