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
-
Inherit from the appropriate base class
- For visual controls —
sap.ui.core.Control - For containers —
sap.ui.core.Controlorsap.ui.core.Element - For complex widgets — you can inherit from existing controls (
sap.m.Input,sap.m.ListBase, etc.)
- For visual controls —
-
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
metadatato 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
renderermethod (orrenderin 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.