Урок 8. SapUI5: Модели. Взаимодействие интерфейса с моделью. Биндинг.

Размещен в sapui5 15.12.2017 10:41
Теги: , , , , , , ,

Продолжим знакомство с SapUI5 и перейдем к одной из важнейших тем — взаимодействие интерфейса (представления) и модели в SapUI5.
Тестовый пример вы всегда можете посмотреть и импортировать к себе в webIDE тут https://github.com/kannade/DataBinding_sapui5.

Модель (Model) предоставляет доступ к данным!

Какие бывают модели в SapUI5?

В SapUI5 может быть использовано 4 типа модели:

Как забиндить модель в SapUI5?

Выделим 4 способа:

Как получить модель в SapUI5?

Хорошо, давайте пройдемся по каждой и посмотрим как можно объявить модель и использовать её в своем sapui5 приложении!

В index.html определяем контейнер для приложения:

/DataBinding_sapui5/blob/master/webapp/index.html
1
2
3
4
5
6
7
8
9
10
sap.ui.getCore().attachInit(function() {
	new sap.m.Shell({
		app: new sap.ui.core.ComponentContainer({
			async	: true,
			height : "100%",
			name : "alfa.DataBindingExample",
			propagateModel : true		})
	}).placeAt("content");
});

propagateModel : true — для корректной работы моделей!

XML модель.

XML модели редко где используются, но бывают разные ситуации, разные сервисы и разные задачи.
Поместим всю модель в файл model/products.xml:

/DataBinding_sapui5/blob/master/webapp/model/products.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<root>
	<stores>
		<store name="Ленинский проспект">
			<info>
				<employees>45</employees>
				<manager>Кузнецова Ирина Семеновна</manager>
			</info>
			<product count="123" price="54.99" weight="23.79">Яблоки</product>
			<product count="54" price="103.99" weight="15.17">Груши</product>
			<product count="95" price="65" weight="197">Апельсины</product>
			<product count="4" price="165" weight="9">Манго</product>
			<product count="144" price="85" weight="932">Грейпфруты</product>
		</store>
		<store name="Академическая">
			<info>
				<employees>25</employees>
				<manager>Петрова Елена Ивановна</manager>
			</info>
			<product count="93" price="45" weight="23.79">Груши</product>
			<product count="23" price="120" weight="321">Апельсины</product>
			<product count="900" price="100" weight="153">Яблоки</product>
		</store>
	</stores>
</root>

Корневой элемент root, затем идет stores а затем у нас два элемента store со своей внутренней иерархией.

Подключим xml модель:

Процедура подключения моделей более подробна описана в 4 уроке.

/DataBinding_sapui5/blob/master/webapp/controller/XmlModelView.controller.js
1
2
3
4
5
onInit: function() {
	var oMdl = new sap.ui.model.xml.XMLModel(); // определяем модель
	oMdl.loadData("model/products.xml"); //загружаем данные
	this.getView().setModel(oMdl); //устанавливаем модель для данного представления
}

В представлении выводим XML модель:

/DataBinding_sapui5/blob/master/webapp/view/XmlModelView.view.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<mvc:view displayBlock="true" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:html="http://www.w3.org/1999/xhtml"
	controllerName="alfa.DataBindingExample.controller.XmlModelView" xmlns:z="sap.ui.layout">
	<panel class="panelStyle" headerText="XML модель" expandable="true" expanded="false">
		<content>
			<z:verticallayout>
				<text text="Название магазина 2: {/stores/store/1/@name}"></text>
				<text text="Количество продукта 2: {/stores/store/1/product/1/@count}"></text>
			</z:verticallayout>
			<list headerText="Магазины" noDataText="Drop list items here" items="{/stores/store/}">
				<standardlistitem type="Navigation" title="{@name}" description="{info/manager}" icon="sap-icon://picture"
					counter="{ path: 'info/employees/text()', formatter : '.formatter.formatToInt' }"></standardlistitem>
			</list>
		</content>
	</panel>
</mvc:view>

Обратите внимание как мы это делаем!

Все довольно просто, посмотрите сами на примере!

Подробней про XML Model — https://openui5.hana.ondemand.com/#/topic/a53e71d85fae4d0887a8b58431197a27.html

JSON модель.

С JSON моделью мы уже сталкивались и использовали ее в предыдущих уроках.

Поместим всю модель в файл model/prod.json:

/DataBinding_sapui5/blob/master/webapp/model/prod.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
	"stores": [{
		"name" : "Ленинский проспект",
		"info": {
			"employees": 45,
			"manager": "Кузнецова Ирина Семеновна"
		},
		"products": [{
			"name": "Яблоки",
			"count": "123",
			"price": 54.99
		}, 
…
		]
	}, {
		"name" : "Академическая",
		"info": {
			"employees": 25,
			"manager": "Петрова Елена Ивановна"
		},
		"products": [{
			"name": "Апельсины",
			"count": "93",
			"price": 45
		},
…
]
	}]
}

Подключим json модель:

/DataBinding_sapui5/blob/master/webapp/controller/JsonModelView.controller.js
1
2
3
4
5
6
onInit: function() {
	var oMdl = new sap.ui.model.json.JSONModel(); //определяем модель
	oMdl.loadData("model/prod.json"); //загружаем модель
	this.getView().setModel(oMdl); //устанавливаем модель
	this.changeTableBinding();
},

В представлении выводим json модель:

/DataBinding_sapui5/blob/master/webapp/view/JsonModelView.view.xml
1
2
3
4
5
6
<text text="Магазин 2: {/stores/1/name}"></text>
<text text="Количество продукта 2: {/stores/1/products/1/count}"></text>
<list mode="SingleSelectMaster" headerText="Магазины" noDataText="Drop list items here" id="__list0" items="{/stores}"
	selectionChange="onSelList">
<standardlistitem type="Navigation" title="{name}" description="{info/manager}" counter="{info/employees}" icon="sap-icon://picture" id="__item0"></standardlistitem>
</list>

А вот тут нас ждет один очень интересный момент.
Сделаем так, что при нажатии на магазин, мы получали список продуктов в нем:

json model sapui5

В List мы определили метод selectionChange="onSelList", то есть при нажатии на элемент из List (магазин) будет отрабатывать метод onSelList, который мы определяем в контроллере:

/DataBinding_sapui5/blob/master/webapp/controller/JsonModelView.controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
onSelList: function(oEvt) { //нажали на элемент из List
	this.changeTableBinding(); //вызвали метод changeTableBinding
},
 
changeTableBinding: function() {
	var oList = this.getView().byId("__list0"); // получили ссылку на List
	var oItem = oList.getSelectedItem(); // взяли выбранный элемент
	// взяли oItem.getBindingContext().sPath и определили на какой магазин нажали (смотрите картинку ниже)
       var i = oItem ? oItem.getBindingContext().sPath.split("/")[2] : 0;
 
	var oTbl = this.getView().byId("table0"); //получили ссылку на таблицу
	var oBindInfo = oTbl.getBindingInfo("items"); //получаем информацию о биндинге таблицы
 
	if (!oBindInfo) {
		return;
	}
	oTbl.bindItems({ // биндим новые строки в зависимости от нажатого магазина (i)
        //Таблица будет забиндина либо на /stores/0/products, либо на /stores/1/products
		path: "/stores/" + i + "/products",
		template: oBindInfo.template
	});
}

Обратим внимание на представление, таблица биндиться пустым значением items="{}":

/DataBinding_sapui5/blob/master/webapp/view/JsonModelView.view.xml
1
2
<table id="table0" headerText="Продукты" items="{}">
</table>

Подробней про JSON модель — https://sapui5.hana.ondemand.com/#/topic/70ef981d350a495b940640801701c409.

oData модель.

oData — самая предпочтительная, сложная и удобная модель!
В данном примере мы используем уже готовый сервис, предоставляющий нам модель Northwind.
Чтобы наш пример работал корректно, нам необходимо сообщить WebIDE где брать данную модель.
Для этого идем в SAP cloud platform cockpit:
SAP cloud platform cockpit

В меню слева выбираем Connectivity -> Destinations.

Там ищем кнопку New Destination и создаем новый destination для northwind:
northwind destination
URL - http://services.odata.org/V2/Northwind/Northwind.svc/.

В файле neo-app.json у нас есть запись:

/DataBinding_sapui5/blob/master/neo-app.json
1
2
3
4
5
6
7
8
{
    	"path": "/Northwind/Northwind.svc/",
        "target": {
        "type": "destination",
        "name": "northwind"
      },
      "description": "northwind service"
    }

которая говорит, что если WebIDE нашел путь /Northwind/Northwind.svc/, то он будет отсылать браузер по ссылке, которую мы указали выше в создании destination с именем "northwind" ("name": "northwind").

И сразу посмотрим как мы определяем oData модель:

/DataBinding_sapui5/blob/master/webapp/controller/OdataModelView.controller.js
1
2
3
4
5
6
onInit: function() {
	var oMdl = new sap.ui.model.odata.v2.ODataModel("/Northwind/Northwind.svc/", {
		defaultCountMode: CountMode.Inline,
		useBatch: false
	});
	this.getView().setModel(oMdl);

Не забываем подключать необходимые библиотеки для работы:

/DataBinding_sapui5/blob/master/webapp/controller/OdataModelView.controller.js
1
2
3
4
5
6
7
8
9
10
sap.ui.define([
	"sap/ui/core/mvc/Controller",
	"./../model/formatter",
	"sap/m/GroupHeaderListItem",
	"sap/ui/model/Sorter",	"sap/ui/model/Filter",	"sap/ui/model/odata/CountMode",	"sap/ui/model/FilterOperator"], function(Controller, Formatter, GroupHeaderListItem, Sorter, Filter, CountMode, FilterOperator) {
	"use strict";

Откроем и посмотрим что собой представляет Northwind, добавив мета тег metadata — http://services.odata.org/V2/Northwind/Northwind.svc/$metadata.
EntityType — описание сущности, тип.
Возьмем, например, Employee:
entitytype odata

В EntityType содежится описание всех полей, которые содержит сущность, описанная на данном EntityType.

А чуть ниже описали EntitySet с именем Employees:
<EntitySet Name="Employees" EntityType="NorthwindModel.Employee"/>

Employees — это то имя, к которому мы будем обращаться, чтобы получить данные и отобразить их.

Сущность Employees (EntitySet) описана на типе Employee (EntityType)!

Обратимся к сущности Employees — http://services.odata.org/V2/Northwind/Northwind.svc/Employees.

В ответ oData сервис вернет XML. Но я хочу получить данные в JSON формате! Поэтому укажу это сервису, добавив мета тег ?$format=json к url:
http://services.odata.org/V2/Northwind/Northwind.svc/Employees?$format=json.

Ура, мы получили ответ в JSON. Теперь я хочу посмотреть заказы (Orders) первого человека из массива:
http://services.odata.org/V2/Northwind/Northwind.svc/Employees(1)/Orders?$format=json.

Или, я хочу получить всех людей, но только отсортировать массив по полю EmployeeID в порядке убывания, $orderby=EmployeeID desc:
http://services.odata.org/V2/Northwind/Northwind.svc/Employees?$format=json&$orderby=EmployeeID%20desc.

А теперь из полученного массива взять только 5 человек, начиная с третьего, $skip=3 $top=5:
http://services.odata.org/V2/Northwind/Northwind.svc/Employees?$format=json&$orderby=EmployeeID%20desc&$skip=3&$top=5.

Хочу посмотреть тех, кто из Лондона, $filter=City eq 'London':
http://services.odata.org/V2/Northwind/Northwind.svc/Employees?$format=json&$orderby=EmployeeID%20desc&$filter=City%20eq%20%27London%27.

И выведу только два поля LastName и FirstName, $select=LastName,FirstName
http://services.odata.org/V2/Northwind/Northwind.svc/Employees?$format=json&$orderby=EmployeeID%20desc&$filter=City%20eq%20%27London%27&$select=LastName,FirstName.

На этих примерах понятно, что в нашем распоряжении есть вполне рабочий язык для составления разнообразных выборок данных.
Подробней — http://www.odata.org/documentation/odata-version-2-0/uri-conventions/.

Посмотрите, как можно прочитать oData модель применив к ней определенные фильтры (к работе нашего примера данный кусок кода не имеет никакого отношения, написано только для примера чтения):

/DataBinding_sapui5/blob/master/webapp/controller/OdataModelView.controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
oMdl.read("/Employees", {
	urlParameters: {
		"$expand": "Orders",
		"$select": "EmployeeID,LastName,FirstName,Orders/OrderDate,Orders/ShipName",
		"$orderby": "EmployeeID desc",
		"$inlinecount": "allpages",
		"$skip": "5",
		"$top": "2"
	},
	filters: [
		new sap.ui.model.Filter({
			path: "EmployeeID",
			operator: "GE",
			value1: "1"
		})
	],
	success: function(oEvt) {
		if (oEvt) {
//тут мы должны получить ответ в параметре и забиндить результат на элемент
	}
	},
	error: function(oEvt) {
		if (oEvt) {
 
	}
	}
});
},

В методе success мы должны будем получить ответ (массив results) и забиндить его:
odata read

1
2
3
4
5
6
if (oEvt) {
	var oJsonModel = new sap.ui.model.json.JSONModel(oEvt); //oResults);
 
	//выберем представление <em>idMain</em> -> представление <em>ListingView</em> (находиться в <em>idMain</em>) -> таблицу <em>files</em> и установим для таблицы модель с именем <em>model2</em>
	sap.ui.getCore().byId("idMain").byId("ListingView").byId("files").setModel(oJsonModel, "model2");
}

Вернемся к примеру и…

Выведем oData модель:

/DataBinding_sapui5/blob/master/webapp/view/OdataModelView.view.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<table selectionChange="onTblSel" mode="SingleSelectMaster" id="table0" headerText="Сотрудники"
				items="{ path: '/Employees', parameters: { select : 'Photo, EmployeeID,LastName,FirstName,City,HireDate' }, sorter : { path : 'City', descending: false, group: '.getGroup' }, groupHeaderFactory: '.getGroupHeader' }">
				<!--parameters: { select : 'Photo, EmployeeID,LastName,FirstName,City,HireDate' },-->
	<items>
		<columnlistitem id="firstItem" press="onTblDetail">
			<cells>
				<image src="{ path: 'Photo', formatter: '.formatter.formatImg' }"></image>
				<text text="{LastName}"></text>
				<text text="{FirstName}"></text>
				<datepicker dateValue="{HireDate}" displayFormat="dd.MM.yyyy" editable="false"></datepicker>
			</cells>
		</columnlistitem>
	</items>
</table>

Посмотрим на вывод данных на странице, все люди группируются по полю City (в биндинге модели мы задали условия группировки):
group

group: '.getGroup' — условие группировки данных
groupHeaderFactory: '.getGroupHeader' — заголовок:

/DataBinding_sapui5/blob/master/webapp/controller/OdataModelView.controller.js
1
2
3
4
5
6
7
8
9
10
getGroup: function(oContext) {
	return oContext.getProperty('City');
},
 
getGroupHeader: function(oGroup) {
	return new GroupHeaderListItem({
		title: oGroup.key,
		upperCase: false
	});
},

Сделаем так, чтобы по нажатию на строку в таблице ниже отображалась информация о заказах конкретного человека:
Посмотрите код представления выше, для строк у нас определено событие press="onTblDetail":

/DataBinding_sapui5/blob/master/webapp/controller/OdataModelView.controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
onTblSel: function() {
	this.changeTableBinding(); //Вызываем метод changeTableBinding
},
 
changeTableBinding: function() {
	var oTbl = this.getView().byId("table0"); //получаем ссылку на таблицу
	var oItem = oTbl.getSelectedItem(); //получаем ссылку на выбранную строку
	if (!oItem) {
		return;
	}
	var oBndCnt = oItem.getBindingContext(); // getBindingContext() от выбранной строки
	if (!oBndCnt) {
		return;
	}
 
	var oSelObj = oBndCnt.getObject(); // выбранный элемент
 
	var oTblDtl = this.getView().byId("tableDtl"); //Получаем ссылку на таблицу, в которую будем выводить
	var oBindInfo = oTbl.getBindingInfo("items");
 
	if (oBindInfo) {
		oTblDtl.bindRows({
			path: "/Employees(" + oSelObj.EmployeeID + ")/Orders", // oBindInfo.path, 
			template: oBindInfo.template,
			parameters: {
				select: "OrderID, ShipPostalCode, ShipCountry, ShipCity, ShipAddress, ShipName, OrderDate, ShippedDate, Freight"
			}
		});
         }
},

Красиво, не правда ли? :)

Нажмите правой кнопкой мыши на первую колонку:
filter odata sapui5:

Посмотрите как устанавливаются такие фильтры (https://openui5.hana.ondemand.com/#/api/sap.ui.table.Column):

/DataBinding_sapui5/blob/master/webapp/view/OdataModelView.view.xml
1
2
3
<t:column id="orderIdCol" filterProperty="OrderID" sortProperty="OrderID" hAlign="Center" width="5rem"></t:column><t:column id="addrCol" filterProperty="ShipAddress" sortProperty="ShipPostalCode, ShipCountry, ShipCity" hAlign="Center" width="20rem"></t:column>

Resource модель

Определим Resource модель:

/DataBinding_sapui5/blob/master/webapp/controller/Main.controller.js
1
2
3
4
5
6
7
8
onInit: function() {
	// Resource Model i18n
	var oResourceModel = new sap.ui.model.resource.ResourceModel({
		bundleName: "alfa.DataBindingExample.i18n.i18n"
	});
 
	// Устанавливаем для ядра с именем i18n
	sap.ui.getCore().setModel(oResourceModel, "i18n");

Сама модель:

/DataBinding_sapui5/blob/master/webapp/i18n/i18n_ru_RU.properties
1
2
3
4
5
6
// коммент
firstName=Имя
lastName=Фамилия
midName=Отчество
errorPernr=При обработке табельных номеров {0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12} {13} {14} {15} произошла ошибка
…

НО! Путь до модели мы указываем вида bundleName: "alfa.DataBindingExample.i18n.i18n", а файл i18n_ru_RU.properties.

SapUI5 сам понимает какой языковой файл загрузить в зависимости от языка региона, в котором вы находитесь.
Можно задать язык загрузки, добавив параметр sap-ui-language=DE в url.
SapUI5 будет искать языковой файл с уже другим префиксом:
i18n

Выведем Resource модель:

/DataBinding_sapui5/blob/master/webapp/view/ResourceModelView.view.xml
1
2
3
4
5
6
7
8
9
<panel class="panelStyle" headerText="{i18n>panels.TitleRes}" expandable="true" expanded="false">	<content>
		<l:verticallayout width="100%">
			<l:content>
				<button text="Отобразить сообщение" press="onShowMessage"></button>
			</l:content>
		</l:verticallayout>
	</content>
</panel>

В представлении нарисовали кнопку с событием press="onShowMessage":

/DataBinding_sapui5/blob/master/webapp/controller/ResourceModelView.controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
onShowMessage: function() {
	var aP = [];
	for (var i = 0; i < = 20; i++) {
		aP.push("0000000" + i);
	} //Сделали массив вида: [00000001, 00000002, 00000003, 00000004, 00000005…]
 
 
//Создаем текстовое сообщение. Берем маску errorPernr из модели i18n/i18n_ru_RU.properties и используем ее с массивом
	var sMsg = sap.ui.getCore().getModel("i18n").getResourceBundle().getText("errorPernr", aP);
 
//Глобальная передача данных. Об этом чуть ниже
	jQuery.sap.getObject("sap.alfa.dataBinding", 0);
	if (sap.alfa.dataBinding) {
		sMsg += " " + sap.alfa.dataBinding.key1;
	}
 
	sap.m.MessageToast.show(sMsg); //выводим сообщение
	}

Нажмем на кнопку:
i18n resource model

SapUI5 взял сообщение из модели errorPernr, а в качестве параметров {0} {1} {2} {3}… использовал массив aP.

Глобальная передача данных в SapUI5.

В SapUI5 есть один забавный способ передать данные глобально. То есть получить к определенным данным доступ из любого контроллера.

Устанавливаем объект глобально:

/DataBinding_sapui5/blob/master/webapp/controller/Main.controller.js
1
2
3
4
5
// Использование глобальных данных
jQuery.sap.getObject("sap.alfa.dataBinding", 0);
sap.alfa.dataBinding = {
	key1: "Эта строка из глобального объекта"
};

Используем глобальный объект:

/DataBinding_sapui5/blob/master/webapp/controller/ResourceModelView.controller.js
1
sMsg += " " + sap.alfa.dataBinding.key1;

Взаимодействие модели и интерфейса

Существует 3 типа взаимодействия:

Посмотрим наш пример.

Зададим модель и установим режим для модели OneWay:

/DataBinding_sapui5/blob/master/webapp/controller/ModelAndInterface.controller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
onInit: function() {
	// Ввод данных One-Way Data Binding vs Two-Way Data Binding
	var oData = {
		firstName: "Иван",
		lastName: "Иванов",
		midName: "Петрович"
	};
 
	var oMdl = new sap.ui.model.json.JSONModel(oData);
	// значение считывается из модели единственный раз
	// oMdl.setDefaultBindingMode(sap.ui.model.BindingMode.OneTime);
	// из модели данные передаются на view
	oMdl.setDefaultBindingMode(sap.ui.model.BindingMode.OneWay);
	// из модели данные передаются на view и из view обновляется модель
	// oMdl.setDefaultBindingMode(sap.ui.model.BindingMode.TwoWay);
	this.getView().setModel(oMdl, "inputData");
},

Есть представление, в котором отображаются поля модели:

/DataBinding_sapui5/blob/master/webapp/view/ModelAndInterface.view.xml
1
2
3
4
5
6
7
8
9
<label text="{i18n>firstName}" width="100%"/>
<input id="fName" width="100%" value="{inputData/>/firstName}" valueLiveUpdate="true"/>
</label><label text="{i18n>lastName}" width="100%"/>
<input id="lName" width="100%" value="{inputData/>/lastName}" valueLiveUpdate="true"/>
</label><label text="{i18n>midName}" width="100%"/>
<input id="mName" width="100%" value="{inputData/>/midName}" valueLiveUpdate="true"/>
<text width="100%" text="Введенное ФИО: {inputData>/lastName} {inputData&gt;/firstName} {inputData>/midName}"/>
<button text="Обновить значение модели из view" press="onRefreshMdl"></button>
</text></label>

Если мы начнем менять значения в полях вода, то значения в Text меняться не будут. Модель обновиться, только если нажать на кнопку и запустить соответствующий метод для обновления модели:

/DataBinding_sapui5/blob/master/webapp/controller/ModelAndInterface.controller.js
1
2
3
4
5
6
7
8
9
10
11
onRefreshMdl: function() {
	var oMdl = this.getView().getModel("inputData");
	var sFName = this.getView().byId("fName").getValue();
	var sLName = this.getView().byId("lName").getValue();
	var sMName = this.getView().byId("mName").getValue();
	oMdl.setData({
		firstName: sFName,
		lastName: sLName,
		midName: sMName
	});
}

Если поменяем oMdl.setDefaultBindingMode(sap.ui.model.BindingMode.OneTime); на oMdl.setDefaultBindingMode(sap.ui.model.BindingMode.TwoWay); и запустим приложение, то при редактирования поля ввода, текст из Text будет сразу меняться.

one-way two-way

Такие дела :).

Оставить комментарий: