@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
407 lines (386 loc) • 15.3 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2026 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
sap.ui.define([
"sap/base/i18n/Localization",
"sap/base/util/uid",
"sap/ui/fl/util/ManagedObjectModel" // used implicitly by oModifier.createControl() function
], function (
Localization, uid
) {
"use strict";
/**
* Change handler for combining sap.m.Button(s) in a sap.m.MenuButton inside sap.m.Bar
*
* @alias sap.m.changeHandler.CombineButtons
* @author SAP SE
* @version 1.146.0
*/
var CombineButtons = {};
var sCombineButtonsModelName = "$sap.m.flexibility.CombineButtonsModel";
function fnHandleMenuItems(aButtons, oModifier, oAppComponent, oMenu, oParent, sParentAggregation, oView, oChangeContent, oRevertData) {
var sPropertyEnabled = "";
var sPropertyVisible = "";
var sOR = "";
var aMenuButtonModels = [];
var bIsRtl = Localization.getRTL();
var aMenuButtonName = [];
return aButtons.reduce(function(oPreviousPromise, oButton, index) {
var oIdToSave;
var iIndex = index;
var oMenuItem;
var oManagedObjectModel;
var oSelector = oChangeContent.buttonsIdForSave[iIndex];
var sButtonText;
var sModelName = "$sap.m.flexibility.MenuButtonModel" + iIndex;
return oPreviousPromise
.then(oModifier.getProperty.bind(oModifier, oButton, "text"))
.then(function(sRetrievedButtonText) {
sButtonText = sRetrievedButtonText;
return oModifier.createControl("sap.m.MenuItem", oAppComponent, oView, oSelector);
})
.then(function(oCreatedMenuItem) {
oMenuItem = oCreatedMenuItem;
// Save the original position of the button
return oModifier.findIndexInParentAggregation(oButton);
})
.then(function(iIndexInParentAggregation) {
oRevertData.insertIndexes[iIndex] = iIndexInParentAggregation;
return oModifier.createControl(
"sap.ui.fl.util.ManagedObjectModel",
oAppComponent,
oView,
Object.assign({}, oSelector, {
id: oSelector.id + '-managedObjectModel'
}),
{
object: oButton,
name: sCombineButtonsModelName
}
);
})
.then(function(oCreatedManagedObjectModel) {
oManagedObjectModel = oCreatedManagedObjectModel;
// ManagedObjectModel should be placed in `dependents` aggregation of MenuItem
return oModifier.insertAggregation(oMenuItem, "dependents", oManagedObjectModel, 0, oView);
})
.then(function () {
return oModifier.createControl(
"sap.ui.core.CustomData",
oAppComponent,
oView,
Object.assign({}, oSelector, {
id: oSelector.id + '-customData'
}),
{
key: "{ path: '" + sCombineButtonsModelName + ">key' }",
value: "{ path: '" + sCombineButtonsModelName + ">value' }"
}
);
})
.then(function(oCustomData) {
oModifier.bindProperty(oMenuItem, "text", sCombineButtonsModelName + ">/text");
oModifier.bindProperty(oMenuItem, "icon", sCombineButtonsModelName + ">/icon");
oModifier.bindProperty(oMenuItem, "enabled", sCombineButtonsModelName + ">/enabled");
oModifier.bindProperty(oMenuItem, "visible", sCombineButtonsModelName + ">/visible");
return oModifier.bindAggregation(oMenuItem, "customData", {
path: sCombineButtonsModelName + ">/customData",
template: oCustomData,
templateShareable: false
}, oView);
})
.then(function() {
// FIXME: will not work in XML in case original button has a binding on `text` property
if (sButtonText) {
bIsRtl ? aMenuButtonName.unshift(sButtonText) : aMenuButtonName.push(sButtonText);
}
// Add suffix to the id, so we can get the original ids of the combined buttons
// when we want to split the menu. The suffix is used in SplitMenuButton change handler.
var oNewSelector = Object.assign({}, oSelector, {
id: oSelector.id + '-originalButtonId'
});
// Create CustomData, holding the original ids of the combined buttons
return oModifier.createControl("sap.ui.core.CustomData", oAppComponent, oView, oNewSelector);
})
.then(function(oRetrievedId) {
oIdToSave = oRetrievedId;
oModifier.setProperty(oIdToSave, "key", "originalButtonId");
oModifier.setProperty(oIdToSave, "value", oModifier.getId(oButton));
// FIXME: fix implementation of ObjectPageDynamicHeaderTitle and remove next line
return oModifier.removeAggregation(oParent, sParentAggregation, oButton);
})
// Adding each button control to the container's dependents aggregation
.then(function() {
return oModifier.insertAggregation(oParent, "dependents", oButton, 0, oView);
})
// Saving original ID to original button to avoid conflict with aggregation binding for customData aggregation.
// The new MenuItem will receive this data via ManagedObjectModel synchronization.
.then(function() {
oModifier.insertAggregation(oButton, "customData", oIdToSave, 0, oView);
})
.then(function() {
oModifier.insertAggregation(oMenu, "items", oMenuItem, iIndex, oView);
})
// Create ManagedObjectModel for every MenuItem
// later it will be placed in dependents aggregation of the MenuButton
// and enabled and visibility properties of each item will be bound to the enabled and visibility property of the MenuButton
.then(function() {
return oModifier.createControl(
"sap.ui.fl.util.ManagedObjectModel",
oAppComponent,
oView,
Object.assign({}, oSelector, {
id: oSelector.id + '-managedObjectModelMenuItem'
}),
{
object: oMenuItem,
name: sModelName
}
);
})
.then(function(oCreatedMenuButtonModel) {
aMenuButtonModels[iIndex] = oCreatedMenuButtonModel;
// create binding expression for the visibility and enabled property of the MenuButton
sPropertyEnabled = sPropertyEnabled + sOR + "${" + sModelName + ">/enabled}";
sPropertyVisible = sPropertyVisible + sOR + "${" + sModelName + ">/visible}";
sOR = " || ";
return {
menuButtonModels: aMenuButtonModels,
menuButtonName: aMenuButtonName,
propertyEnabled: sPropertyEnabled,
propertyVisible: sPropertyVisible
};
});
}, Promise.resolve());
}
/**
* Combines sap.m.Button(s) into a sap.m.MenuButton
*
* @param {sap.ui.fl.Change} oChange - Change wrapper object with instructions to be applied on the control map
* @param {sap.m.Bar} oControl - Control containing the buttons
* @param {object} mPropertyBag - Map of properties
* @param {object} mPropertyBag.modifier - Modifier for the controls
* @return {Promise} Promise resolving when the change was applied
*
* @public
*/
CombineButtons.applyChange = function(oChange, oControl, mPropertyBag) {
if (mPropertyBag.modifier.targets !== "jsControlTree") {
return Promise.reject(new Error("Combine buttons change can't be applied on XML tree"));
}
var oChangeContent = oChange.getContent();
var oModifier = mPropertyBag.modifier;
var oView = mPropertyBag.view;
var oAppComponent = mPropertyBag.appComponent;
var oParent;
var oSourceControl;
var iAggregationIndex;
var sParentAggregation;
var aButtons;
var oMenu;
var oMenuButton;
var oRevertData = {
parentAggregation: "",
insertIndexes: []
};
var aMenuButtonModels = [];
var aMenuButtonName = [];
return Promise.resolve()
.then(oModifier.bySelector.bind(oModifier, oChangeContent.combineButtonSelectors[0], oAppComponent, oView))
.then(function(oReturnedSourceControl) {
oSourceControl = oReturnedSourceControl;
oParent = oModifier.getParent(oSourceControl); // === oControl
var aPromises = [];
oChangeContent.combineButtonSelectors.forEach(function(oCombineButtonSelector) {
var oPromise = Promise.resolve()
.then(oModifier.bySelector.bind(oModifier, oCombineButtonSelector, oAppComponent, oView));
aPromises.push(oPromise);
});
return Promise.all(aPromises);
})
.then(function(aCombineButtons){
aButtons = aCombineButtons;
return oModifier.getParentAggregationName(aButtons[0], oParent);
})
.then(function(sRetrievedParentAggregation){
sParentAggregation = sRetrievedParentAggregation;
oRevertData.parentAggregation = sParentAggregation;
return oModifier.findIndexInParentAggregation(oSourceControl);
})
.then(function(iAggrIndex){
iAggregationIndex = iAggrIndex;
return oModifier.createControl("sap.m.Menu", oAppComponent, oView, oChangeContent.menuIdSelector);
})
.then(function(oCreatedMenu){
oMenu = oCreatedMenu;
oCreatedMenu.attachEvent(
"itemSelected",
"sap.m.changeHandler.CombineButtons.pressHandler",
CombineButtons.pressHandler
);
return fnHandleMenuItems(
aButtons,
oModifier,
oAppComponent,
oMenu,
oParent,
sParentAggregation,
oView,
oChangeContent,
oRevertData);
})
// Create MenuButton
.then(function(mMenuItemsInfo) {
aMenuButtonModels = mMenuItemsInfo.menuButtonModels;
aMenuButtonName = mMenuItemsInfo.menuButtonName;
var sPropertyVisible = mMenuItemsInfo.propertyVisible;
var sPropertyEnabled = mMenuItemsInfo.propertyEnabled;
return oModifier.createControl(
"sap.m.MenuButton",
oAppComponent,
oView,
oChangeContent.menuButtonIdSelector,
{
visible: "{= " + sPropertyVisible + "}",
enabled: "{= " + sPropertyEnabled + "}"
}
);
})
.then(function(oCreatedMenuButton){
oMenuButton = oCreatedMenuButton;
// ManagedObjectModel should be placed in `dependents` aggregation of the MenuButton
return aMenuButtonModels.reduce(function (oPreviousPromise, oModel) {
return oPreviousPromise
.then(oModifier.insertAggregation.bind(oModifier, oMenuButton, "dependents", oModel, 0, oView));
}, Promise.resolve());
})
.then(function() {
oModifier.setProperty(oMenuButton, "text", aMenuButtonName.join("/"));
return Promise.resolve()
.then(oModifier.insertAggregation.bind(oModifier, oMenuButton, "menu", oMenu, 0, oView))
.then(oModifier.insertAggregation.bind(oModifier, oParent, sParentAggregation, oMenuButton, iAggregationIndex, oView))
.then(function(){
oChange.setRevertData(oRevertData);
});
});
};
function fnDestroyControls(aCustomData, oModifier) {
return aCustomData.reduce(function(oPreviousInnerPromise, oCustomData) {
return oPreviousInnerPromise
.then(function(){
return oModifier.getProperty(oCustomData, "key");
})
.then(function(sKey) {
if (sKey === "originalButtonId"){
return oModifier.destroy(oCustomData);
}
return undefined;
});
}, Promise.resolve());
}
/**
* Reverts applied change
*
* @param {sap.ui.fl.Change} oChange - Change wrapper object with instructions to be applied on the control map
* @param {sap.m.IBar} oControl - Bar that matches the change selector for applying the change
* @param {object} mPropertyBag - Property bag containing the modifier and the view
* @param {object} mPropertyBag.modifier - Modifier for the controls
* @param {object} mPropertyBag.view - Application view
* @return {Promise} Promise resolving when change is reverted
* @public
*/
CombineButtons.revertChange = function(oChange, oControl, mPropertyBag) {
var oModifier = mPropertyBag.modifier;
var oView = mPropertyBag.view;
var oRevertData = oChange.getRevertData();
var oChangeContent = oChange.getContent();
var sParentAggregation = oRevertData.parentAggregation;
var oMenuButton, oParent, aButtonsIdsReversed;
return Promise.resolve()
.then(function(){
return oModifier.bySelector(oChangeContent.menuButtonIdSelector, mPropertyBag.appComponent, oView);
})
.then(function(oRetrievedButton) {
oMenuButton = oRetrievedButton;
oParent = oModifier.getParent(oMenuButton);
aButtonsIdsReversed = oChangeContent.combineButtonSelectors.slice().reverse();
// FIXME: fix implementation of ObjectPageDynamicHeaderTitle and remove next line
return oModifier.removeAggregation(oParent, sParentAggregation, oMenuButton);
})
.then(function(){
return oModifier.destroy(oMenuButton);
})
.then(function() {
var iLength = aButtonsIdsReversed.length;
return aButtonsIdsReversed.reduce(function(oPreviousPromise, oButtonIdReversed, index){
var iIndex = index;
var oButton;
return oPreviousPromise
.then(function() {
return oModifier.bySelector(oButtonIdReversed, mPropertyBag.appComponent, oView);
})
// Custom data clean up
.then(function(oRetrievedButton) {
oButton = oRetrievedButton;
return oModifier.getAggregation(oButton, "customData");
})
.then(function(aControls) {
return fnDestroyControls(aControls, oModifier);
})
.then(function() {
return oModifier.insertAggregation(oParent, sParentAggregation, oButton, oRevertData.insertIndexes[iLength - iIndex - 1], oView);
});
}, Promise.resolve())
.then(function() {
oChange.resetRevertData();
});
});
};
/**
* Completes the change by adding change handler specific content
*
* @param {sap.ui.fl.Change} oChange - Change wrapper object to be completed
* @param {object} oSpecificChangeInfo - Specific info object
* @param {object} oSpecificChangeInfo.combineElementIds - IDs of selected buttons to be combined
* @param {object} mPropertyBag - Map of properties
* @param {object} mPropertyBag.modifier - Modifier for the controls
*
* @public
*/
CombineButtons.completeChangeContent = function(oChange, oSpecificChangeInfo, mPropertyBag) {
var oModifier = mPropertyBag.modifier;
var oAppComponent = mPropertyBag.appComponent;
var aCombineButtonIds = oSpecificChangeInfo.combineElementIds;
if (aCombineButtonIds && aCombineButtonIds.length > 1) {
var oContent = {};
oChange.addDependentControl(aCombineButtonIds, "combinedButtons", mPropertyBag);
oContent.combineButtonSelectors = aCombineButtonIds.map(function (sCombineButtonId) {
return oModifier.getSelector(sCombineButtonId, oAppComponent);
});
// generate ids for Menu and MenuButton
oContent.menuButtonIdSelector = oModifier.getSelector(oAppComponent.createId(uid()), oAppComponent);
oContent.menuIdSelector = oModifier.getSelector(oAppComponent.createId(uid()), oAppComponent);
// generate id for menu button items
oContent.buttonsIdForSave = aCombineButtonIds.map(function() {
return oModifier.getSelector(oAppComponent.createId(uid()), oAppComponent);
});
oChange.setContent(oContent);
} else {
throw new Error("Combine buttons action cannot be completed: oSpecificChangeInfo.combineElementIds attribute required");
}
};
/**
* Callback function which is attached via modifier in applyChange
*
* @param {sap.ui.base.Event} oEvent - Event object
* @param {object} mParameters - parameters containing the selector and appComponentId
* while applying the change.
*/
CombineButtons.pressHandler = function(oEvent) {
var oButton = oEvent.getParameter("item").getModel(sCombineButtonsModelName).getObject();
oButton.firePress();
};
return CombineButtons;
}, /* bExport= */true);