20. Работа с кастомными контроллами

20. Работа с кастомными контроллами

Когда действительно стоит создавать кастомный контрол?

  • Используй стандартные контролы, если это возможно.
    SAPUI5/Fiori предоставляет огромную библиотеку стандартных элементов. Кастомные контролы нужны только если стандартных решений нет или они не покрывают бизнес-требования.
  • Вдохновляйся стандартными контролами по UX и API.
    Пользователь не должен догадываться, что элемент кастомный — он должен вести себя как "родной".

Архитектура и структура

  1. Наследуйся от подходящего базового класса

    • Для визуальных контролов — sap.ui.core.Control
    • Для контейнеров — sap.ui.core.Control или sap.ui.core.Element
    • Для сложных виджетов — можно наследоваться от существующих контролов (sap.m.Input, sap.m.ListBase и т.д.)
  2. Структурируй код по 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.

Полезные ссылки


См. также