20. Working with Custom Controls

20. Working with Custom Controls

When should you really create a custom control?

  • Use standard controls whenever possible.
    SAPUI5/Fiori provides a huge library of standard elements. Custom controls are only needed if there is no standard solution or it doesn’t meet business requirements.
  • Take inspiration from standard controls for UX and API.
    The user shouldn’t guess that the element is custom — it should behave like a “native” one.

Architecture and structure

  1. Inherit from the appropriate base class

    • For visual controls — sap.ui.core.Control
    • For containers — sap.ui.core.Control or sap.ui.core.Element
    • For complex widgets — you can inherit from existing controls (sap.m.Input, sap.m.ListBase, etc.)
  2. Structure code according to UI5 best practices

    • Each control is a separate file (ES6/TypeScript module).
    • Use namespaces and correct paths (see [14. TypeScript.md] and [19. Migrating Legacy Projects.md]).

Main best practices

1. Minimal API

  • Expose only necessary properties, events, and methods.
  • Use metadata to describe the API (properties, aggregations, events, associations).
  • Document public methods and parameters.

2. Data Binding

  • Support data binding for properties and aggregations.
  • Use standard mechanisms: setProperty, getProperty, bindProperty, etc.
  • Don’t implement your own binding hacks.

3. Rendering

  • Always implement the renderer method (or render in ES6/TS).
  • Don’t insert raw HTML — use the SAPUI5 API to generate elements.
  • Don’t store state in the DOM — only in the model/properties of the control.

4. Styling

  • Use CSS classes via addStyleClass/removeStyleClass.
  • For custom styles — create a separate CSS file and connect it via sap.ui.define/require.
  • Don’t override global styles, don’t touch other classes.

5. Performance

  • Don’t create heavy controls unless necessary.
  • Use lazy rendering if the control is complex.
  • Don’t do heavy calculations in the renderer.

6. Testability

  • Write unit tests for control logic (see [17. Automated Testing.md]).
  • Test binding, events, rendering.

7. i18n support

  • All texts — via i18n (see [07. i18n and Multiple Languages.md]).
  • Don’t hardcode strings in the control.

8. Documentation

  • Describe purpose, parameters, limitations.
  • Provide usage examples (XML/JS).

Working with aggregations

Aggregations are a way to nest controls inside others (e.g., buttons in a toolbar, items in a list). For custom controls, it’s important to properly implement aggregation support:

  • Describe aggregations in metadata.aggregations:
    metadata: {
      aggregations: {
        items: { type: "sap.ui.core.Control", multiple: true, singularName: "item" }
      }
    }
    
  • Use standard methods for working with aggregations: addItem, removeItem, getItems, destroyItems, etc. These are generated automatically.
  • For rendering aggregations, use:
    oRM.renderControl(oControl.getAggregation("items")[i]);
    // or for all at once:
    oControl.getItems().forEach(function(oItem) {
      oRM.renderControl(oItem);
    });
    
  • Support data binding for aggregations (see [09. DataBinding in View.md]):
    <my:CustomList items="{path: '/myItems'}">
      <my:CustomListItem text="{name}" />
    </my:CustomList>
    
  • Don’t forget to call this.invalidate() on changes if a re-render is needed.
  • For complex aggregations (e.g., with templates), implement template binding support via bindAggregation.

Example of a custom container with aggregation:

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>");
    }
  });
});

Example of a simple custom control (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();
    }
  });
});

Common mistakes

  • Reinventing standard controls — always look for an equivalent in the standard library first.
  • Hard DOM binding — don’t use jQuery for manipulations inside the renderer.
  • No binding support — properties should be bindable.
  • No tests — even simple controls can break after UI5 updates.


See also