20. Работа с кастомными контроллами
20. Работа с кастомными контроллами
Когда действительно стоит создавать кастомный контрол?
- Используй стандартные контролы, если это возможно.
SAPUI5/Fiori предоставляет огромную библиотеку стандартных элементов. Кастомные контролы нужны только если стандартных решений нет или они не покрывают бизнес-требования. - Вдохновляйся стандартными контролами по UX и API.
Пользователь не должен догадываться, что элемент кастомный — он должен вести себя как "родной".
Архитектура и структура
-
Наследуйся от подходящего базового класса
- Для визуальных контролов —
sap.ui.core.Control - Для контейнеров —
sap.ui.core.Controlилиsap.ui.core.Element - Для сложных виджетов — можно наследоваться от существующих контролов (
sap.m.Input,sap.m.ListBaseи т.д.)
- Для визуальных контролов —
-
Структурируй код по best practice UI5
- Каждый контрол — отдельный файл (ES6/TypeScript модуль).
- Используй неймспейсы и правильные пути (см. [14. TypeScript.md] и [19. Миграция старых проектов.md]).
Основные best practices
1. Минимальный API
- Открывай только необходимые свойства, события и методы.
- Используй
metadataдля описания API (properties, aggregations, events, associations). - Документируй публичные методы и параметры.
2. Data Binding
- Поддерживай data binding для свойств и агрегаций.
- Используй стандартные механизмы
setProperty,getProperty,bindPropertyи т.д. - Не реализуй собственные костыли для биндинга.
3. Рендеринг
- Всегда реализуй метод
renderer(илиrenderв ES6/TS). - Не вставляй "сырой" HTML — используй API SAPUI5 для генерации элементов.
- Не храни состояние в DOM — только в модели/свойствах контрола.
4. Стилизация
- Используй CSS-классы через
addStyleClass/removeStyleClass. - Для кастомных стилей — создавай отдельный CSS-файл и подключай его через
sap.ui.define/require. - Не переопределяй глобальные стили, не лезь в чужие классы.
5. Performance
- Не создавай тяжелых контролов без необходимости.
- Используй lazy rendering, если контрол сложный.
- Не делай тяжелых вычислений в рендерере.
6. Тестируемость
- Пиши unit-тесты для логики контрола (см. [17. Автоматическое тестирование.md]).
- Проверяй работу биндинга, событий, рендеринга.
7. Поддержка i18n
- Все тексты — через i18n (см. [07. i18n и разные языки.md]).
- Не хардкоди строки в контроле.
8. Документирование
- Описывай назначение, параметры, ограничения.
- Приводи примеры использования (XML/JS).
Работа с агрегациями
Агрегации — это способ вкладывать одни контролы в другие (например, кнопки в тулбар, элементы в список). Для кастомных контролов важно правильно реализовать поддержку агрегаций:
- Описывай агрегации в
metadata.aggregations:metadata: { aggregations: { items: { type: "sap.ui.core.Control", multiple: true, singularName: "item" } } } - Используй стандартные методы для работы с агрегациями:
addItem,removeItem,getItems,destroyItemsи т.д. Они генерируются автоматически. - Для рендеринга агрегаций используй:
oRM.renderControl(oControl.getAggregation("items")[i]); // или для всех сразу: oControl.getItems().forEach(function(oItem) { oRM.renderControl(oItem); }); - Поддерживай data binding для агрегаций (см. [09. DataBinding во View.md]):
<my:CustomList items="{path: '/myItems'}"> <my:CustomListItem text="{name}" /> </my:CustomList> - Не забывай вызывать
this.invalidate()при изменениях, если требуется перерисовка. - Для сложных агрегаций (например, с шаблонами) реализуй поддержку шаблонного биндинга через
bindAggregation.
Пример кастомного контейнера с агрегацией:
sap.ui.define(["sap/ui/core/Control"], function(Control) {
return Control.extend("my.namespace.FancyList", {
metadata: {
aggregations: {
items: { type: "sap.ui.core.Control", multiple: true, singularName: "item" }
}
},
renderer: function(oRM, oControl) {
oRM.write("<ul");
oRM.writeControlData(oControl);
oRM.write(">");
oControl.getItems().forEach(function(oItem) {
oRM.write("<li>");
oRM.renderControl(oItem);
oRM.write("</li>");
});
oRM.write("</ul>");
}
});
});
Пример простого кастомного контрола (ES6)
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.namespace.FancyButton", {
metadata: {
properties: {
"text": { type: "string", defaultValue: "" }
},
events: {
"press": {}
}
},
renderer: function(oRM, oControl) {
oRM.write("<button");
oRM.writeControlData(oControl);
oRM.addClass("myFancyButton");
oRM.writeClasses();
oRM.write(">");
oRM.writeEscaped(oControl.getText());
oRM.write("</button>");
},
onclick: function() {
this.firePress();
}
});
});
Типичные ошибки
- Переизобретение стандартных контролов — сначала ищи аналоги в стандартной библиотеке.
- Жесткая привязка к DOM — не используй jQuery для манипуляций внутри рендерера.
- Отсутствие поддержки биндинга — свойства должны быть bindable.
- Нет тестов — даже простые контролы могут ломаться при обновлениях UI5.