Урок 4. SapUI5: Binding. Layout.
В четвертом уроке рассмотрим элементарный биндинг (binding) json модели и средства визуализации данных из модели.
Для примера возьмем JSON модель прогноза погоды в Москве с yahoo и отобразим эту информацию в разном виде.
Код проекта скачать и посмотреть можно тут https://github.com/kannade/layout_sapui5/.
Фрагменты в SapUI5
Фрагмент — это часть шаблона.
Можно весь код записать в одно представление. Но, если его много, ваше представление (шаблон) оказывается очень большой простыней, в которой сложно разобраться. Как правило, для решения подобных проблем используются дополнительные представления.
Фрагменты представляют собой легкие пользовательские элементы, которые могут использоваться повторно, при этом не имея своего контроллера.
Поэтому в данном примере разный функционал был вынесен в разные фрагменты:
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.
Непосредственно сами фрагменты описываются следующим образом:
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»);)
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"); }, |
Теперь в представлении используем данную модель:
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} путь до элемента:
В результате, указав в представлении {weather>/query/results/channel/location/country} будет отображен текст Russia.
Panel (sap.m.Panel)
Обратим внимание на еще не известный нам Panel (sap.m.Panel). Свойство expandable=»true» устанавливает возможность свернуть / развернуть блок, а expanded=»true» начальное положение (при загрузке элемента блок должен быть по умолчанию открыт или закрыт)(картинка выше).
Хорошо, доступ к единичному элементу понятен. А что делать если у нас массив элементов?
Как например в данной модели:
{weather>/query/results/channel/item/forecast}
В /query/results/channel/item/forecast содержится 10 элементов, которые мы хотим вывести в какой-либо элемент, например, в таблицу (sap.m.Table):
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 из контроллера:
1 2 3 | onTableItemPress: function(evt) { MessageToast.show(evt.getSource().getId() + " Pressed" + evt.getSource().getCells()[0].getText()); }, |
Нажмем на любую строку таблицы и посмотрим результат:
При помощи evt.getSource().getId() мы получаем id нажатой строки! При помощи evt.getSource().getCells()[0].getText() мы получили текст из первого столбца нажатой строки! Вся информация о нажатой строки содержится, в данном примере, в evt.
Обратите внимание на формат вставки значений из элемента массива {weather>date}, используем не полный путь ({weather>/query/results/channel/item/forecast/date}), а указываем лишь нужный нам элемент!
Carousel (sap.m.Carousel)
Carousel — элемент для создания каруселей :).
Так же массив элементов мы можем вывести не в таблице, а, например, в каруселе:
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> |
HorizontalLayout & VerticalLayout (sap.ui.layout)
HorizontalLayout — горизонтальное позиционирование нескольких элементов.
VerticalLayout — вертикальное позиционирование нескольких элементов.
Смотрите картинку ниже, все очень просто!
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> |
Чтобы сделать текст элемента жирным, можно добавить к нему свойство design=»Bold»!
BlockLayout & Grid (sap.ui.layout)
При помощи BlockLayout можно вывести необходимый контент блоками.
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 — описываем ячейку.
Посмотрите пример, в котором мы описали два блока:
Grid (sap.ui.layout.Grid) — элемент управления сеткой!
«Блок» делиться на 12 столбцов, в которые мы можем помещать контент.
Например:
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 блоков).
На картинке ниже области под label и vbox делятся по 3 и 6 блоков соответственно (из 12 возможных).
Обратите внимание, под 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 позволяет разработчику реализовывать самые разнообразные формы на странице.
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» решает много проблем с «ровными» отступами, поиграйтесь с ним сами!
Посмотрите, как интересно можно реализовать состояние активности какого либо поля:
1 2 3 | <radiobutton groupName="GroupA" selected="{odata>/enabled}" text="Var1"> … </radiobutton> |
, где odata — модель, которую мы описали в контроллере:
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"); |
Похожим образом можно реализовывать всевозможные отступы:
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 блока:
На картинке показан вариант для L экрана — 2 блока отступ, 2 блока элемент кнопка!
FlexBox (sap.m.FlexBox)
sap.m.FlexBox помогает нам позиционировать свой контент. Например, когда в блоке необходимо расположить элемент по центру как горизонтально (justifyContent), так по центру вертикально (alignItems), это сделать можно следующим образом:
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 используется как корневой элемент для приложений. Посмотрите как мы объявляли главное представление раньше, и как мы его объявили сейчас:
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 (файл, в котором описываются различные свойства нашего приложения):
1 2 3 4 5 | "sap.ui5": {
"rootView": {
"viewName": "hello.layout.view.Main", "type": "XML"
}, |
В результате чего имеем красивую рамочку вокруг приложения 🙂