This documentation describes how to migrate from the deprecated components to the modern sap.m.p13n implementation. Use this documentation to update personalization dialogs to the current API, which provides improved state management, persistence integration, and TypeScript support.
Starting with version 1.98, the main elements of the sap.m.P13n* component family began to be deprecated. The remaining elements were deprecated in version 1.124.
Note:
This documentation covers the migration of
sap.m.P13nDialogand its panels. For more information about migratingsap.m.TablePersoController, see Table Personalization (deprecated).
In the following table, each deprecated class is mapped to its replacement in the sap.m.p13n namespace.
| Deprecated Class | Deprecated Since | Replacement |
|---|---|---|
| `sap.m.P13nDialog` | 1.98 | `sap.m.p13n.Popup` \(standalone\) or `sap.m.p13n.Engine` \(with persistence\) |
| `sap.m.P13nPanel` | 1.124 | `sap.m.p13n.BasePanel` \(abstract base, not used directly\) |
| `sap.m.P13nColumnsPanel` | 1.98 | `sap.m.p13n.SelectionPanel` and `sap.m.p13n.SelectionController` |
| `sap.m.P13nSortPanel` | 1.98 | `sap.m.p13n.SortPanel` and `sap.m.p13n.SortController` |
| `sap.m.P13nGroupPanel` | 1.98 | `sap.m.p13n.GroupPanel` and `sap.m.p13n.GroupController` |
| `sap.m.P13nFilterPanel` | 1.124 | `sap.m.p13n.FilterPanel` and `sap.m.p13n.FilterController` |
| `sap.m.P13nDimMeasurePanel` | 1.120 | No direct equivalent; see the chart personalization section below |
| `sap.m.P13nItem` | 1.124 | `sap.m.p13n.SelectionState` objects |
| `sap.m.P13nColumnsItem` | 1.120 | `sap.m.p13n.SelectionState` objects |
| `sap.m.P13nSortItem` | 1.120 | `sap.m.p13n.SortState` objects |
| `sap.m.P13nGroupItem` | 1.120 | `sap.m.p13n.GroupState` objects |
| `sap.m.P13nFilterItem` | 1.124 | `sap.m.p13n.FilterStateItem` objects |
When migrating from sap.m.P13nDialog to sap.m.p13n, complete the following steps:
Mark aggregation items with a key: Add app:p13nKey to each Column (or list item) in your XML view.
Create a MetadataHelper: Replace P13nItem aggregations with a MetadataHelper array. Include key and label for every property. Include path as well — it is declared required in the type definition. In practice, the runtime only reads it when you call helper.getPath(key) to resolve binding paths yourself. For common visibility/sort/group scenarios, the Engine does not access it, but include it for type correctness and forward compatibility.
Register the control with the Engine: Call Engine.getInstance().register() with one controller per personalization type (SelectionController, SortController, etc.). Attach the state change handler before calling register().
Replace panel events with a single StateChange handler: Remove all granular event handlers (changeColumnsItems, addSortItem, removeSortItem, etc.) and implement one attachStateChange handler that applies the full state to your control.
Clean up in onExit: Call detachStateChange() and deregister() to prevent memory leaks when the view is destroyed.
The new sap.m.p13n API is not a simple renaming. There are three conceptual shifts that affect how you write personalization code.
The old API notified you about every individual change via granular events:
// OLD: three separate handlers just for sorting
addSortItem: function(oEvt) { /* item added */ },
removeSortItem: function(oEvt) { /* item removed */ },
updateSortItem: function(oEvt) { /* item updated */ }
The new API delivers a single snapshot of the complete state after every change:
// NEW: one handler for everything
Engine.getInstance().attachStateChange(function(oEvt) {
const oState = oEvt.getParameter("state");
// oState.Sort = [{ key: "name", descending: false }] // absent entries = not sorted
// Apply the full state to your control
});
The old API mixed state and UI: P13nColumnsItem was an OpenUI5 element (extending sap.ui.core.Item) used as an aggregation to carry the current selection state.
// OLD: state lives in UI5 controls inside an aggregation
columnsItems: [
new P13nColumnsItem({ columnKey: "name", visible: true, index: 0 }),
new P13nColumnsItem({ columnKey: "age", visible: false, index: 1 })
]
The new API separates them clearly: Panels display the UI, the state is a typed plain object (sap.m.p13n.SelectionState), and no OpenUI5 control instantiation is needed.
// NEW: state is a sap.m.p13n.SelectionState object
oSelectionPanel.setP13nData([
{ name: "name", label: "Name", visible: true },
{ name: "age", label: "Age", visible: false }
]);
The old API required you to manage the dialog lifecycle manually by rebuilding aggregations:
Create once
Open
Close
Reset
The new Engine handles all of this. Engine.show() creates and opens the dialog; the Engine delegates the dialog lifecycle to its internal UIManager — the popup is added as a dependent to the source control and closed on OK/Cancel/Escape, with destruction following the standard OpenUI5-dependent lifecycle. Engine.reset() replaces manual aggregation teardown.
The following table shows when to use which entity.
| Use Case | Recommended Pattern |
|---|---|
| Persist settings across sessions \(variant management, key user adaptation\) | `sap.m.p13n.Engine` with `sap.ui.fl` |
| Simple in-session personalization, no `sap.ui.fl` dependency | Stand-alone `sap.m.p13n.Popup` |
sap.ui.fl to Your Projectsap.m.p13n.Engine loads sap.ui.fl internally at runtime. When using UI5 CLI (local development with ui5 serve), add sap.ui.fl to ui5.yaml if it is not already in your dependency tree (for example, transitively via sap.ui.mdc, sap.f, or sap.ui.rta). Without it, the browser will throw a script load error for sap/ui/fl/library.js, and the Engine will fail.
Add the library to the framework.libraries list in your project’s ui5.yaml:
framework:
name: SAPUI5
version: "1.149.0"
libraries:
- name: sap.m
- name: sap.ui.core
- name: sap.ui.fl
During development, restart the server after adding it so the tooling can download the package. This step is not needed in productive SAP BTP or SAP Fiori launchpad environments where sap.ui.fl is always available.
The SelectionController identifies items in the target aggregation by reading a p13nKey custom data entry from each item. Before using any Engine-based recipe, add a CustomData element with key "p13nKey" to every item in the personalized aggregation.
The best way is the custom namespace shorthand:
<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
xmlns:app="http://schemas.sap.com/sapui5/extension/sap.ui.core.CustomData/1">
<Table id="myTable">
<columns>
<Column app:p13nKey="firstName_col"><Text text="First Name"/></Column>
<Column app:p13nKey="lastName_col"><Text text="Last Name"/></Column>
<Column app:p13nKey="city_col"><Text text="City"/></Column>
<Column app:p13nKey="size_col" visible="false"><Text text="Size"/></Column>
</columns>
</Table>
This key must match the key values you provide to MetadataHelper. Without it, the SelectionController cannot map aggregation items to personalization state entries.
Note:
The attribute name (
p13nKey,key, or any other string) is arbitrary — you choose it. What matters is consistency: The name you use inapp:p13nKey="…"must be exactly whatgetKeyForItemreads viaoColumn.data("p13nKey"), and the same string must appear askeyin yourMetadataHelperentries. The examples in this guide usep13nKeythroughout.
Remember:
If you choose a name other than
p13nKey, you must also provide agetKeyForItemcallback — the built-in default readsoItem.data("p13nKey")specifically (seeSelectionController._calcPresentState). Without the callback, the mapping from aggregation items to metadata keys breaks.
Tip:
If certain items should never appear in the personalization dialog but must still be taken into account during delta calculation (for example, a mandatory first column), pass them via the
stableKeysoption onSelectionController:new SelectionController({ control: oTable, targetAggregation: "columns", getKeyForItem: (oCol) => oCol.getVisible() ? oCol.data("p13nKey") : null, stableKeys: ["id_col"] // always present, never shown in dialog })Edge case: If a stable item can be invisible at runtime, its key must still be returned by
getKeyForItemeven when hidden — otherwise it won’t appear ingetCurrentState(), and the delta reinsertion logic will silently skip it. For items listed instableKeysthat can be invisible, return their key unconditionally insidegetKeyForItem.This is the one exception to the general rule in the Common Pitfalls section, which says to return
nullfor invisible columns. For stable columns (those listed instableKeys), return their key even when invisible. TheEngineexcludes them from the dialog automatically viastableKeys, so there is no risk of them appearing as selected.
This is the most common type of migration: Replacing P13nColumnsPanel with SelectionPanel and SelectionController via the Engine.
Before:
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/P13nDialog", "sap/m/P13nColumnsPanel",
"sap/m/P13nItem", "sap/m/P13nColumnsItem"
], function(Controller, P13nDialog, P13nColumnsPanel, P13nItem, P13nColumnsItem) {
return Controller.extend("my.app.controller.Main", {
onInit: function() {
this._oDialog = new P13nDialog({
showReset: true,
panels: [
new P13nColumnsPanel({
items: [
new P13nItem({ columnKey: "name", text: "Name" }),
new P13nItem({ columnKey: "city", text: "City" })
],
columnsItems: [
new P13nColumnsItem({ columnKey: "name", visible: true, index: 0 }),
new P13nColumnsItem({ columnKey: "city", visible: false, index: 1 })
],
changeColumnsItems: function(oEvt) {
const aItems = oEvt.getParameter("items");
aItems.forEach(function(oItem) {
const oColumn = oTable.getColumns().find(
(c) => c.data("key") === oItem.columnKey
);
oColumn.setVisible(oItem.visible);
});
}
})
],
ok: function() { this._oDialog.close(); }.bind(this),
cancel: function() { this._oDialog.close(); }.bind(this),
reset: function() { /* rebuild aggregations manually */ }
});
}
});
});
After:
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/p13n/Engine",
"sap/m/p13n/SelectionController",
"sap/m/p13n/MetadataHelper"
], function(Controller, Engine, SelectionController, MetadataHelper) {
"use strict";
return Controller.extend("my.app.controller.Main", {
onInit: function() {
const oTable = this.byId("myTable");
// Attach BEFORE register() — if the control has stored personalization
// (xConfig CustomData with `modified: true`), the Engine fires StateChange
// during register(). Attach first to avoid missing that initial replay.
Engine.getInstance().attachStateChange(this._onStateChange, this);
Engine.getInstance().register(oTable, {
helper: new MetadataHelper([
{ key: "name", label: "Name", path: "name" },
{ key: "city", label: "City", path: "city" }
]),
controller: {
Columns: new SelectionController({
control: oTable,
targetAggregation: "columns",
getKeyForItem: function(oColumn) {
// Fallback: called when oColumn.data("p13nKey") is falsy —
// i.e. for invisible columns OR columns with no p13nKey CustomData.
// Returning null excludes them from the state.
return oColumn.getVisible() ? oColumn.data("p13nKey") : null;
}
})
}
});
},
onExit: function() {
Engine.getInstance().detachStateChange(this._onStateChange, this);
Engine.getInstance().deregister(this.byId("myTable"));
},
_onStateChange: function(oEvt) {
const oTable = this.byId("myTable");
const oState = oEvt.getParameter("state");
// oState always contains the full state of ALL registered controllers.
if (!oState) { return; }
if (oState.Columns) {
const aVisibleKeys = oState.Columns.map((o) => o.key);
oTable.getColumns().forEach((oCol) => {
oCol.setVisible(aVisibleKeys.includes(oCol.data("p13nKey")));
});
}
},
onOpenDialog: function(oEvt) {
Engine.getInstance().show(this.byId("myTable"), ["Columns"], {
contentHeight: "35rem",
contentWidth: "30rem",
source: oEvt.getSource()
});
},
onReset: function() {
Engine.getInstance().reset(this.byId("myTable"), ["Columns"]);
}
});
});
Before:
new P13nDialog({
panels: [
new P13nColumnsPanel({
items: [ /* ... */ ], columnsItems: [ /* ... */ ],
changeColumnsItems: this._onColumnsChanged.bind(this)
}),
new P13nSortPanel({
items: [ /* ... */ ],
addSortItem: this._onSortAdded.bind(this),
removeSortItem: this._onSortRemoved.bind(this),
updateSortItem: this._onSortUpdated.bind(this)
}),
new P13nGroupPanel({
items: [ /* ... */ ],
addGroupItem: this._onGroupAdded.bind(this),
removeGroupItem: this._onGroupRemoved.bind(this),
updateGroupItem: this._onGroupUpdated.bind(this)
})
]
});
The granular sort and group handlers received items with columnKey and operation: "Ascending"|"Descending":
_onSortAdded: function(oEvt) {
var oItem = oEvt.getParameter("sortItem");
// oItem.getColumnKey() → "city"
// oItem.getOperation() → "Descending"
},
_applySort: function() {
aData.sort(function(a, b) {
var iResult = a[oSort.columnKey] < b[oSort.columnKey] ? -1 : 1;
return oSort.operation === "Descending" ? -iResult : iResult;
});
}
Note:
State shape changes after migration. The new
SortStateandGroupStateobjects use different property names from the oldP13nSortItem/P13nGroupItem:
Old \(`P13nSortItem`\) New \(`SortState`\) `getColumnKey()` → `"city"` `oSort.key` → `"city"` `getOperation()` → `"Descending"` `oSort.descending` → `true`
Old \(`P13nGroupItem`\) New \(`GroupState`\) `getColumnKey()` → `"city"` `oGroup.key` → `"city"` Update any existing
_applySortingor_applyGroupingimplementations to use the new property names.
After:
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/p13n/Engine",
"sap/m/p13n/SelectionController",
"sap/m/p13n/SortController",
"sap/m/p13n/GroupController",
"sap/m/p13n/MetadataHelper"
], function(Controller, Engine, SelectionController, SortController,
GroupController, MetadataHelper) {
"use strict";
return Controller.extend("my.app.controller.Main", {
onInit: function() {
const oTable = this.byId("myTable");
Engine.getInstance().attachStateChange(this._onStateChange, this);
Engine.getInstance().register(oTable, {
helper: new MetadataHelper([
{ key: "name", label: "Name", path: "name" },
{ key: "age", label: "Age", path: "age" },
{ key: "city", label: "City", path: "city" }
]),
controller: {
Columns: new SelectionController({
control: oTable, targetAggregation: "columns",
getKeyForItem: (oCol) => oCol.getVisible() ? oCol.data("p13nKey") : null
}),
Sort: new SortController({ control: oTable }),
Group: new GroupController({ control: oTable })
}
});
},
onExit: function() {
Engine.getInstance().detachStateChange(this._onStateChange, this);
Engine.getInstance().deregister(this.byId("myTable"));
},
// oState always contains the full state of ALL registered controllers,
// regardless of which one the user actually changed.
_onStateChange: function(oEvt) {
const oState = oEvt.getParameter("state");
if (oState.Columns) { this._applyColumnVisibility(oState.Columns); }
if (oState.Sort) { this._applySorting(oState.Sort); }
if (oState.Group) { this._applyGrouping(oState.Group); }
},
// oState.Columns is a sap.m.p13n.SelectionState array — one entry per
// visible column in display order. Columns absent from the array are hidden.
_applyColumnVisibility: function(aColumns) {
const oTable = this.byId("myTable");
const aVisibleKeys = aColumns.map((o) => o.key);
oTable.getColumns().forEach((oCol) => {
oCol.setVisible(aVisibleKeys.includes(oCol.data("p13nKey")));
});
},
// oState.Sort is a sap.m.p13n.SortState array — one entry per active
// sorter in priority order. Apply as a multi-level sort to the model data.
_applySorting: function(aSortState) {
const oModel = this.getView().getModel();
const aData = oModel.getProperty("/items").slice();
aData.sort(function(a, b) {
for (const oSort of aSortState) {
const vA = a[oSort.key];
const vB = b[oSort.key];
const iCmp = vA < vB ? -1 : vA > vB ? 1 : 0;
const iResult = oSort.descending ? -iCmp : iCmp;
if (iResult !== 0) { return iResult; }
}
return 0;
});
oModel.setProperty("/items", aData);
},
// oState.Group is a sap.m.p13n.GroupState array. sap.m.Table does not
// have built-in grouping — implement by sorting the data by the group key
// so rows with the same value appear together.
_applyGrouping: function(aGroupState) {
if (!aGroupState || aGroupState.length === 0) { return; }
const oModel = this.getView().getModel();
const aData = oModel.getProperty("/items").slice();
aData.sort(function(a, b) {
for (const oGroup of aGroupState) {
const vA = a[oGroup.key];
const vB = b[oGroup.key];
const iCmp = vA < vB ? -1 : vA > vB ? 1 : 0;
if (iCmp !== 0) { return iCmp; }
}
return 0;
});
oModel.setProperty("/items", aData);
},
onOpenDialog: function() {
Engine.getInstance().show(this.byId("myTable"), ["Columns", "Sort", "Group"]);
}
});
});
Note:
sap.m.Tablehas no built-in grouping renderer. The_applyGroupingimplementation above sorts the model data by the group key so rows with equal values appear together, a common approach for flat tables. Controls that natively support grouping (for example,sap.ui.table.Table) can useSorterwith avGroupfunction instead.
sap.ui.fl)Use this pattern when you manage the state yourself and do not need flexibility persistence.
After (XML view):
<p13n:Popup id="myPopup"
xmlns:p13n="sap.m.p13n"
title="Table Settings"
warningText="Reset all changes?"
reset=".onReset"
close=".onClose">
<p13n:panels>
<p13n:SelectionPanel id="colPanel" title="Columns" enableCount="true" showHeader="true"/>
<p13n:SortPanel id="sortPanel" title="Sort"/>
</p13n:panels>
</p13n:Popup>
After (controller):
Note:
SelectionPanel.setP13nData()accepts bothname(legacy alias, deprecated as of version 1.124) andkey. The examples below usenameto illustrate the direct mapping fromP13nColumnsItem.columnKey, but for new code, usekeyconsistently — as shown in recipes A and B.
onOpenDialog: function(oEvt) {
this.byId("colPanel").setP13nData([
{ name: "name", label: "Name", visible: true },
{ name: "city", label: "City", visible: false }
]);
this.byId("myPopup").open(oEvt.getSource());
},
// close event: reason is "Ok", "Cancel", or "Escape"
onClose: function(oEvt) {
if (oEvt.getParameter("reason") === "Ok") {
const aColumns = this.byId("colPanel").getP13nData();
this._applyState(aColumns);
}
},
onReset: function() {
this.byId("colPanel").setP13nData(this._initialColumns);
}
Note:
Unlike the
Enginepattern, the stand-alonePopupdoes not automatically apply or persist changes. You must read the state withgetP13nData()in theclosehandler and apply it to your control yourself.
The filter migration is the most complex because the old and new APIs use fundamentally different approaches. The old P13nFilterPanel owned the filter input UI internally. The new FilterPanel requires you to provide an itemFactory function that returns the input control for each filterable property — one MultiInput (or similar) per filterable column. The snippet below shows the FilterController configuration; connect the resulting state to your binding via oState.Filter in _onStateChange, using the same key → condition[] map shape shown in the State Object Reference section.
Before:
new P13nDialog({
panels: [
new P13nFilterPanel({
items: [
new P13nItem({ columnKey: "city", text: "City" }),
new P13nItem({ columnKey: "country", text: "Country" })
],
filterItems: [
new P13nFilterItem({
columnKey: "city",
operation: sap.m.P13nConditionOperation.EQ,
value1: "Berlin",
exclude: false
})
],
addFilterItem: function(oEvt) {
var oData = oEvt.getParameter("filterItemData");
// oData.getColumnKey(), oData.getOperation(),
// oData.getValue1(), oData.getValue2(), oData.getExclude()
},
removeFilterItem: function(oEvt) {
// oEvt.getParameter("key"), oEvt.getParameter("index")
},
updateFilterItem: function(oEvt) {
var oData = oEvt.getParameter("filterItemData");
// same shape as addFilterItem
}
})
]
});
After:
new FilterController({
control: oTable,
itemFactory: function(oItem, oFilterPanel) {
const oP13nItem = oFilterPanel.getItemByKey(oItem.name);
return new MultiInput({
tokens: oP13nItem.conditions.map((oCond) =>
new Token({ text: oCond.values[0] })
),
tokenUpdate: function(oEvt) {
oEvt.getParameter("addedTokens").forEach((oToken) => {
oP13nItem.conditions.push({ operator: "EQ", values: [oToken.getText()] });
});
oEvt.getParameter("removedTokens").forEach((oToken) => {
const idx = oP13nItem.conditions.findIndex(
(c) => c.values[0] === oToken.getText()
);
if (idx > -1) { oP13nItem.conditions.splice(idx, 1); }
});
oFilterPanel.setP13nData(oFilterPanel.getP13nData().map((oFilterItem) =>
oFilterItem.name === oP13nItem.name ? oP13nItem : oFilterItem
));
}
});
}
})
For a complete working example, see the sap.m.sample.p13n.EngineCustomFilters sample app in the Demo Kit.
The new API represents the personalization state as typed plain JavaScript objects and without OpenUI5 control instances. Each controller defines its own @typedef in the sap.m.p13n namespace. The typedefs are documented in the respective controller class in the API Reference, not as stand-alone entries.
// sap.m.p13n.SelectionState — one entry per visible item, in display order
// Full shape: { key: string, visible?: boolean, index?: number }
// API Reference: sap.m.p13n.SelectionController
[{ key: "name" }, { key: "city" }]
// sap.m.p13n.SortState — only active sorters appear; absent entries = not sorted
// Note: `sorted` is only used as INPUT to applyState() to remove a sorter — it is
// not returned by getCurrentState() or the StateChange event.
// API Reference: sap.m.p13n.SortController
[{ key: "name", descending: false }]
// sap.m.p13n.GroupState — only active groups appear; absent entries = not grouped
// API Reference: sap.m.p13n.GroupController
[{ key: "city" }]
// sap.m.p13n.FilterState — map of property key to condition array
// API Reference: sap.m.p13n.FilterController
{ "city_col": [{ operator: "EQ", values: ["Berlin"] }] }
Replace each manual aggregation teardown with a single Engine call:
// OLD: destroy and rebuild P13nXxxItem aggregations
oColumnsPanel.destroyColumnsItems();
aInitialState.forEach((o) => oColumnsPanel.addColumnsItem(new P13nColumnsItem(o)));
// NEW: one call resets all specified controllers
Engine.getInstance().reset(oTable, ["Columns", "Sort", "Group"]);
The Engine automatically detects the persistence configuration from the control’s view tree:
| Scenario | Behavior |
|---|---|
| No `PersistenceProvider` or `VariantManagement` in the view | Changes are transient \(lost on reload\) |
| `sap.ui.fl.variants.VariantManagement` wraps the control | Changes are stored in the active variant |
| `sap.m.p13n.PersistenceProvider` with `mode="Global"` | Changes are saved immediately to the back end |
To configure a persistence mode explicitly, place a PersistenceProvider in the view tree:
<p13n:PersistenceProvider for="myTable" mode="Global" xmlns:p13n="sap.m.p13n"/>
<Table id="myTable" .../>
P13nDimMeasurePanel)sap.m.P13nDimMeasurePanel (deprecated in 1.120) has no replacement in the sap.m.p13n namespace. For chart dimension and measure personalization, use the controls provided by sap.chart or the sap.ui.mdc.Chart component.
validationExecutor, onBeforeNavigationFrom)The old API provided panel-level validation via onBeforeNavigationFrom() and dialog-level validation via validationExecutor. There are the following alternatives in the new API:
Show a sap.m.MessageStrip in the panel via panel.setMessageStrip().
Implement validateState(oState, sKey) in your control. The Engine calls this method automatically after every change event.
getKeyForItem Ignoring Column Visibility_calcPresentState() falls back to getKeyForItem whenever sKey is falsy — this covers two cases:
getVisible() returns false)p13nKey CustomData setIf getKeyForItem returns a key for those items, they are included in getCurrentState() and appear checked in the personalization dialog even though they are hidden in the view.
// WRONG — returns a key for invisible columns, so they appear as selected in the dialog
getKeyForItem: function(oColumn) { return oColumn.data("p13nKey"); }
// CORRECT
getKeyForItem: function(oColumn) {
return oColumn.getVisible() ? oColumn.data("p13nKey") : null;
}
StateChange After register()If attachStateChange is called after register(), and the control has stored personalization (xConfig CustomData with modified: true), the Engine fires StateChange during register() — before your handler is attached. New controls with no prior personalization are not affected, but attach first anyway to avoid subtle ordering bugs when personalization is added later.
// WRONG
Engine.getInstance().register(oTable, { ... });
Engine.getInstance().attachStateChange(this._onStateChange, this); // too late
// CORRECT
Engine.getInstance().attachStateChange(this._onStateChange, this);
Engine.getInstance().register(oTable, { ... });
onExitEngine is a global singleton. Without cleanup, the state change handler fires for a destroyed controller.
// WRONG
onExit: function() { }
// CORRECT
onExit: function() {
Engine.getInstance().detachStateChange(this._onStateChange, this);
Engine.getInstance().deregister(this.byId("myTable"));
}
TypeScriptThe recipes in this guide use JavaScript. In a TypeScript project, only two things change: Imports and event handler types. The Engine API itself is identical.
Note:
TypeScripttypes for thep13n.Engineare available since UI5 1.140 and are correctly typed (as a propersap/ui/base/Eventsubclass) since SAPUI5 1.149. The deprecatedsap.m.P13nDialogfamily never hadTypeScriptsupport.
Replace the sap.ui.define AMD block with ES6 imports:
import Controller from "sap/ui/core/mvc/Controller";
import Engine from "sap/m/p13n/Engine";
import type { Engine$StateChangeEvent } from "sap/m/p13n/Engine";
import SelectionController from "sap/m/p13n/SelectionController";
import SortController from "sap/m/p13n/SortController";
import GroupController from "sap/m/p13n/GroupController";
import MetadataHelper from "sap/m/p13n/MetadataHelper";
Type the attachStateChange handler with Engine$StateChangeEvent:
Engine.getInstance().attachStateChange(this._onStateChange, this);
_onStateChange(oEvt: Engine$StateChangeEvent): void {
const oState = oEvt.getParameter("state");
if (oState?.Columns) { /* ... */ }
}
Caution:
Type Definition Mismatch in @sapui5/ts-types-esm in 1.148 or lower versions: In
@sapui5/ts-types-esmup to and including version 1.148,Engine$StateChangeEventis typed as a plain object with a.stateproperty, not as a subclass ofsap/ui/base/Event. Therefore, the following applies:
oEvt.getParameter("state")causes aTypeScriptcompile error on the event parameter.
oEvt.statecompiles correctly, but does not work at runtime. TheEnginepasses a realEventobject where.stateisundefined.The workaround is to define the handler as an arrow class field and cast the event:
// Arrow field keeps `this` bound and gives a stable reference for detachStateChange private readonly _onStateChange = (oEvt: Engine$StateChangeEvent): void => { // Cast needed: ts-types-esm types the event as a plain object, // but the Engine passes a real UI5 Event at runtime. const oState = (oEvt as unknown as { getParameter(s: string): unknown }) .getParameter("state") as { Columns?: Array<{ key: string }>; Sort?: Engine$StateChangeEventSortState[]; Group?: Engine$StateChangeEventGroupState[]; }; if (!oState) { return; } // ... };This issue is resolved in
@sapui5/ts-types-esm1.149 and higher versions, whereEngine$StateChangeEventcorrectly extends the baseEventtype.
The state objects have matching TypeScript types derived from their JSDoc @typedef definitions:
import type { SelectionState } from "sap/m/p13n/SelectionController";
import type { SortState } from "sap/m/p13n/SortController";
import type { GroupState } from "sap/m/p13n/GroupController";
import type { FilterState, FilterStateItem } from "sap/m/p13n/FilterController";
Everything else — register(), MetadataHelper, show(), reset(), deregister() — is identical to the JavaScript recipes.
Parent topic:Personalization
Related Information