Урок 4. SapUI5: Binding. Layout.

В четвертом уроке рассмотрим элементарный биндинг (binding) json модели и средства визуализации данных из модели.
Для примера возьмем JSON модель прогноза погоды в Москве с yahoo и отобразим эту информацию в разном виде.

Код проекта скачать и посмотреть можно тут https://github.com/kannade/layout_sapui5/.

Фрагменты в SapUI5

Фрагмент — это часть шаблона.

Можно весь код записать в одно представление. Но, если его много, ваше представление (шаблон) оказывается очень большой простыней, в которой сложно разобраться. Как правило, для решения подобных проблем используются дополнительные представления.

Фрагменты представляют собой легкие пользовательские элементы, которые могут использоваться повторно, при этом не имея своего контроллера.

Поэтому в данном примере разный функционал был вынесен в разные фрагменты:

/layout_sapui5/blob/master/webapp/view/Main.view.xml
1
2
3
4
5
6
7
8
9
10
<panel headerText="Погода в {weather>/query/results/channel/location/country}" class="sapUiResponsiveContentPadding" expandable="true"
						expanded="true">
	<content>
		<core:fragment id="idXmlFrag" fragmentName="hello.layout.view.weather" type="XML"></core:fragment>
		<core:fragment id="idXmlFrag_ekb" fragmentName="hello.layout.view.weather_ekb" type="XML"></core:fragment>
	</content>
</panel>
<core:fragment id="idCbrFrag" fragmentName="hello.layout.view.cbr" type="XML" class="sapUiVisibleOnlyOnDesktop"></core:fragment>
<core:fragment id="idFormFrag" fragmentName="hello.layout.view.form" type="XML"></core:fragment>
<core:fragment id="idFlexFrag" fragmentName="hello.layout.view.flexbox" type="XML"></core:fragment>

<core:Fragment id=»idXmlFrag» fragmentName=»hello.layout.view.weather» type=»XML»/> — фрагмент, который физически доступен по адресу {пространство имен hello.layout}/view/weather.fragment.xml.

Непосредственно сами фрагменты описываются следующим образом:

/layout_sapui5/blob/master/webapp/view/weather.fragment.xml
1
2
3
<core:fragmentdefinition xmlns="sap.m" xmlns:core="sap.ui.core" xmlns:f="sap.ui.layout.form" xmlns:l="sap.ui.layout"></core:fragmentdefinition>

Стоит отметить еще одну интересную особенность фрагментов! Если во фрагменте описана кнопка с событием press=»onBtnPress» и данный фрагмент используется в разных представлениях (а у каждого представления свой контроллер), то при клике кнопки метод onBtnPress будет запущен в контроллере, который отвечает за представление, в котором используется фрагмент с кнопкой.

Другими словами, есть два представления со своими контроллерами:

  • first.xml (controller_first.js)
  • second.xml (controller_second.js)

и есть фрагмент fragment.xml, в котором есть кнопка btn со свойством press=»onBtnPress». Схематически представим это так:
fragment.xml -> btn -> press=»onBtnPress».

Будем использовать этот фрагмент в обоих представлениях, получиться следующая схема:

  • first.xml (controller_first.js) -> fragment.xml -> btn -> press=»onBtnPress»
  • second.xml (controller_second.js) -> fragment.xml -> btn -> press=»onBtnPress»

Получается, что метод onBtnPress может быть вызван из разных контроллеров, в зависимости от представления, в котором использовался фрагмент:

  • controller_first.js -> onBtnPress
  • controller_second.js -> onBtnPress

Как соединить модель JSON с SapUI5?

Модели, описанные на JSON, XML или OData биндятся (присоединяются к представлению) практически одинаково.

Разберем биндинг на примере JSON модели. Для примера возьмем прогноз погоды в Москве в JSON.

В контроллере в методе инициализации onInit:

  • Объявляем JSON модель (var oModel = new sap.ui.model.json.JSONModel();)
  • Загружаем модель (oModel.loadData(…))
  • Подключаем модель (this.getView().setModel(oModel, «weather»);)
/layout_sapui5/blob/master/webapp/controller/Main.controller.js
1
2
3
4
5
6
7
8
9
10
11
12
onInit: function() {
                        // объявляем модель (JSONModel)
			var oModel = new sap.ui.model.json.JSONModel();
 
                        // загружаем модель
			oModel.loadData(
				"https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22moscow%22)%20and%20u%3D%22c%22&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys"
			);
 
                        //Устанавливаем модель для представления
			this.getView().setModel(oModel, "weather");
		},

Теперь в представлении используем данную модель:

/layout_sapui5/blob/master/webapp/view/Main.view.xml
1
2
3
<panel headerText="Погода в {weather>/query/results/channel/location/country}"
class="sapUiResponsiveContentPadding" expandable="true"	expanded="true">
</panel>

, где weather — имя модели, которое мы указали в this.getView().setModel(oModel, «weather»); , а {weather>/query/results/channel/location/country} путь до элемента:
json model sapui5

В результате, указав в представлении {weather>/query/results/channel/location/country} будет отображен текст Russia.
Panel json

Panel (sap.m.Panel)

Обратим внимание на еще не известный нам Panel (sap.m.Panel). Свойство expandable=»true» устанавливает возможность свернуть / развернуть блок, а expanded=»true» начальное положение (при загрузке элемента блок должен быть по умолчанию открыт или закрыт)(картинка выше).

Хорошо, доступ к единичному элементу понятен. А что делать если у нас массив элементов?
Как например в данной модели:
{weather>/query/results/channel/item/forecast}
json array массив

В /query/results/channel/item/forecast содержится 10 элементов, которые мы хотим вывести в какой-либо элемент, например, в таблицу (sap.m.Table):

/layout_sapui5/blob/master/webapp/view/WtEkb.fragment.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
<table headerText="Погода Москва" id="idForecastTable" items="{weather>/query/results/channel/item/forecast}" growing="true"
						growingThreshold="4" growingScrollToLoad="false" width="100%" noDataText="No data" mode="None" showSeparators="All">
	<columns>
		<column>
			<text text="Дата"></text>
		</column>
		<column minScreenWidth="Tablet" demandPopin="true">
			<text text="Максимальная"></text>
		</column>
		<column minScreenWidth="Tablet" demandPopin="true">
			<text text="Минимальная"></text>
		</column>
	</columns>
 
	<items>
		<columnlistitem press="onTableItemPress" type="Active">
			<cells>
				<text text="{weather>date}"/></text>
				<text text="{weather>high}"/></text>
				<text text="{weather>low}"/></text>
			</cells>
		</columnlistitem>
	</items>
</table>

В свойство items передаем ссылку на массив элементов:
items=»{weather>/query/results/channel/item/forecast}».
При помощи

1
2
3
4
5
<columns>
  <column></column>
  <column></column>
  <column></column>
</columns>

описываем колонки, а при помощи

1
2
3
4
5
6
7
8
9
10
<items>
	<columnlistitem press="onTableItemPress" type="Active">
		<cells>
			<text text="{weather>date}"/>
			</text><text text="{weather>high}"/>
			</text><text text="{weather>low}"/>
			</text>
		</cells>
	</columnlistitem>
</items>

описываем шаблон строки.

При клике на строку вызывается метод onTableItemPress из контроллера:

/layout_sapui5/blob/master/webapp/controller/Main.controller.js
1
2
3
onTableItemPress: function(evt) {
			MessageToast.show(evt.getSource().getId() + " Pressed" + evt.getSource().getCells()[0].getText());
		},

Нажмем на любую строку таблицы и посмотрим результат:
table item press
При помощи evt.getSource().getId() мы получаем id нажатой строки! При помощи evt.getSource().getCells()[0].getText() мы получили текст из первого столбца нажатой строки! Вся информация о нажатой строки содержится, в данном примере, в evt.

Обратите внимание на формат вставки значений из элемента массива {weather>date}, используем не полный путь ({weather>/query/results/channel/item/forecast/date}), а указываем лишь нужный нам элемент!

Carousel (sap.m.Carousel)

Carousel — элемент для создания каруселей :).
Так же массив элементов мы можем вывести не в таблице, а, например, в каруселе:

/layout_sapui5/blob/master/webapp/view/weather.fragment.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<carousel pages="{weather>/query/results/channel/item/forecast}" width="100%" height="20%">
	<pages>
		<f:simpleform title="{weather>date}">
			<f:content>
				<label text="Максимальная температура"></label>
				<text text="{weather>high}"/>
				<label text="Минимальная температура"></label>
				</text><text text="{weather>low}"/>
				<label text="Описание"></label>
				</text><text text="{weather>text}"/>
			</text></f:content>
			<f:title>
				<core:title text="{weather>/query/results/channel/location/city}"/>
			</core:title></f:title>
		</f:simpleform>
	</pages>
</carousel>

carusel sap m

HorizontalLayout & VerticalLayout (sap.ui.layout)

HorizontalLayout — горизонтальное позиционирование нескольких элементов.
VerticalLayout — вертикальное позиционирование нескольких элементов.

Смотрите картинку ниже, все очень просто!

/layout_sapui5/blob/master/webapp/view/weather.fragment.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<l:verticallayout id="containerLayout_ver"  class="sapUiSmallMargin" width="100%">	<l:content>
		<label text="{weather>/query/results/channel/item/condition/date}" design="Bold">
		</label>
		<text text="{weather>/query/results/channel/item/condition/temp}">
		</text>
		<text text="{weather>/query/results/channel/item/condition/text}">
		</text>
	</l:content>
</l:verticallayout>
 
<l:horizontallayout id="containerLayout_hor" class="sapUiSmallMargin">	<l:content>
		<label text="{weather>/query/results/channel/item/condition/date}" design="Bold" class="sapUiLargeMarginEnd">
		</label>
		<text text="{weather>/query/results/channel/item/condition/temp}" class="sapUiLargeMarginEnd">
		</text>
		<text text="{weather>/query/results/channel/item/condition/text}" class="sapUiLargeMarginEnd">
		</text>
	</l:content>
</l:horizontallayout>

horizontal vertical layout

Чтобы сделать текст элемента жирным, можно добавить к нему свойство design=»Bold»!

BlockLayout & Grid (sap.ui.layout)

При помощи BlockLayout можно вывести необходимый контент блоками.

/layout_sapui5/blob/master/webapp/view/cbr.fragment.xml
1
2
3
4
5
6
7
8
9
10
<l:blocklayout id="BlockLayout">
	<l:blocklayoutrow>
		<l:blocklayoutcell title="50% width cell"></l:blocklayoutcell>
		<l:blocklayoutcell title="50% width cell"></l:blocklayoutcell>
	</l:blocklayoutrow>
</l:blocklayout>

BlockLayoutRow — описываем строку.
BlockLayoutCell — описываем ячейку.

Посмотрите пример, в котором мы описали два блока:
BlockLayout BlockLayoutRow BlockLayoutCell

Grid (sap.ui.layout.Grid) — элемент управления сеткой!

«Блок» делиться на 12 столбцов, в которые мы можем помещать контент.

grid 12 columns

Например:

/layout_sapui5/blob/master/webapp/view/cbr.fragment.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
25
<l:grid class="sapUiSmallMarginTop" hSpacing="1" vSpacing="1" defaultSpan="L6 M6 S10">
	<l:content>
 
		<label text="EUR" width="100%">
			<layoutdata>
				<l:griddata span="L6 M6 S8" linebreakL="true" linebreakM="true" linebreakS="true"></l:griddata>			</layoutdata>
		</label>
		<vbox>
			<text text="{cbr>/date}" class="nameTitle"/>
			</text><text text="{cbr>/rates/EUR}"/>
		</text></vbox>
 
		<label text="RUB" width="100%">
			<layoutdata>
				<l:griddata span="L3 M3 S8" linebreakL="true" linebreakM="true" linebreakS="true"></l:griddata>			</layoutdata>
		</label>
		<vbox>
			<text text="{cbr>/date}"/>
			</text><text text="{cbr>/rates/RUB}"/>
		</text></vbox>
 
	</l:content>
</l:grid>

На картинке ниже, на label и vbox выделяется по 6 блоков (из 12 возможных) (label — 6 блоков и vbox — 6 блоков. в сумме выйдет все 12 блоков).
sapui5 grid

На картинке ниже области под label и vbox делятся по 3 и 6 блоков соответственно (из 12 возможных).
sapui5 grid 2
Обратите внимание, под label было выделено в 2 раза меньше места (3 блока вместо 6)!

При помощи Grid мы можем условно разбить область на 12 столбцов и ими манипулировать.
На первой картинке для label мы устанавливаем шаблон L6 M6 S8, а для VBox шаблон берется из свойства самого Grid (defaultSpan=»L6 M6 S10″).

Что такое L M S?

Подробно про данные параметры можно прочитать тут https://experience.sap.com/fiori-design-web/form/#!.

А если кратко, то это размеры экрана.

  • S — размер экрана до 600 px включительно!
  • M — размер экрана от 601 px да 1024 px включительно!
  • L — размер экрана от 1025 px до 1440 px включительно!
  • XL — размер экрана от 1441 px!

То есть, строка L6 M6 S8 означает, что

  • при размере экрана от 1025 px до 1440 px (L) элемент будет занимать 6 блоков из 12 возможных,
  • при размере экрана от 601 px да 1024 px (M) элемент будет занимать 6 блоков из 12 возможных,
  • при размере экрана до 600 px (S) элемент будет занимать 8 блоков из 12 возможных.

SimpleForm (sap.ui.layout.form)

Точно так же данный вид разметки можно применить и к разным полям ввода.
SimpleForm позволяет разработчику реализовывать самые разнообразные формы на странице.

/layout_sapui5/blob/master/webapp/view/form.fragment.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<f:simpleform layout="ResponsiveGridLayout" id="idSF" editable="true">
				<f:content>
					<core:title text="Number 1"></core:title>
 
<radiobutton groupName="GroupA" selected="{odata>/enabled}" text="Var1">
	<layoutdata>
		<l:griddata span="L2 M6 S6"></l:griddata>	</layoutdata>
</radiobutton>
<radiobutton groupName="GroupA" text="Var2">
	<layoutdata>
		<l:griddata span="L2 M6 S6"></l:griddata>	</layoutdata>
</radiobutton>
<input enabled="{odata/>/enabled}" value="{ path: 'odata>/name', type: 'hello.layout.model.type.digits' }" valueLiveUpdate="true">
<layoutdata>
	<l:griddata span="L4 M6 S6"></l:griddata></layoutdata>
 
</f:content></f:simpleform>

Для двух радиокнопок и одного поля ввода мы установили шаблон поведения для различных экранов.

Кстати, свойство editable=»true» решает много проблем с «ровными» отступами, поиграйтесь с ним сами!

Посмотрите, как интересно можно реализовать состояние активности какого либо поля:

/layout_sapui5/blob/master/webapp/view/form.fragment.xml
1
2
3
<radiobutton groupName="GroupA" selected="{odata>/enabled}" text="Var1">
…
</radiobutton>

, где odata — модель, которую мы описали в контроллере:

/layout_sapui5/blob/master/webapp/controller/Main.controller.js
1
2
3
4
5
6
7
8
9
10
11
			var oData = {
				"name": "1234",
				"enabled": true,				"date": new Date(),
				"price": "12.1"
			};
 
			var oModel_4 = new sap.ui.model.json.JSONModel();
			oModel_4.setData(oData);
 
			this.getView().setModel(oModel_4, "odata");

Похожим образом можно реализовывать всевозможные отступы:

/layout_sapui5/blob/master/webapp/view/form.fragment.xml
1
2
3
4
5
<button text="Submit" icon="sap-icon://paper-plane" press="onPressSubmit">
	<layoutdata>
		<l:griddata indent="L2 M4" span="L2 M8 S12"></l:griddata>	</layoutdata>
</button>

indent=»L2 M4″ — для экрана L делаем отступ 2 блока, для экрана M делаем отступ 4 блока:

grid indent

На картинке показан вариант для L экрана — 2 блока отступ, 2 блока элемент кнопка!

FlexBox (sap.m.FlexBox)

sap.m.FlexBox помогает нам позиционировать свой контент. Например, когда в блоке необходимо расположить элемент по центру как горизонтально (justifyContent), так по центру вертикально (alignItems), это сделать можно следующим образом:

/layout_sapui5/blob/master/webapp/view/flexbox.fragment.xml
1
2
3
4
5
6
7
8
<flexbox height="100px"
alignItems="Center" justifyContent="Center">	<items>
		<button text="1" type="Emphasized" class="sapUiSmallMarginEnd"></button>
		<button text="2" type="Reject" class="sapUiSmallMarginEnd"></button>
		<button text="3" type="Accept"></button>
	</items>
</flexbox>

Shell (sap.m.Shell)

А теперь самое интересное, sap.m.Shell!
Shell — это тема для отдельного урока. Shell используется как корневой элемент для приложений. Посмотрите как мы объявляли главное представление раньше, и как мы его объявили сейчас:

/layout_sapui5/blob/master/webapp/index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
	// var oView = sap.ui.view({
	// 	id: "idMain",
	// 	viewName: "hello.layout.view.Main",
	// 	type: sap.ui.core.mvc.ViewType.XML
	// });
 
	sap.ui.getCore().attachInit(function() {
		new sap.m.Shell({			app: new sap.ui.core.ComponentContainer({
				height : "100%",
				name : "hello.layout"
					})
			}).placeAt("content");
		});
 
	// oView.placeAt("content");
</script>

, главное представление мы описываем в файле manifest.json (файл, в котором описываются различные свойства нашего приложения):

/layout_sapui5/blob/master/webapp/manifest.json
1
2
3
4
5
"sap.ui5": {
		"rootView": {
			"viewName": "hello.layout.view.Main",			"type": "XML"
		},

В результате чего имеем красивую рамочку вокруг приложения 🙂
sap m shell