@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
427 lines (391 loc) • 14.4 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/Log',
'sap/base/util/isEmptyObject',
'sap/ui/core/Component',
'sap/ui/core/Element',
'sap/ui/core/Shortcut'
], function (
Log,
isEmptyObject,
Component,
Element,
Shortcut
) {
"use strict";
/**
* Creates and initializes a new CommandExecution.
*
* @class
* The CommandExecution registers a shortcut when it is added to the dependent
* aggregation of a control. The shortcut information is retrieved from the
* owner components manifest (<code>/sap.ui5/commands/<command></code>).
*
* You can use a CommandExecution instead of an event handler in XMLViews by
* using <code>cmd:</code> plus the command name.
*
* Example for <code>sap.m.Button</code>:
*
* <pre>
* <Button press="cmd:MyCommand" />
* </pre>
*
* When the press event is fired, the CommandExecution will be triggered and
* the <code>execute</code> event is fired.
*
* When using commands, the component will create a model named <code>$cmd</code>.
* The model data provides the enabled and visible state of all CommandExecutions.
* With that, action-triggering controls (e.g. a button) can be bound to the enable/visible property
* of the CommandExecution to centrally control their state.
*
* <b>Note: The usage of the <code>$cmd</code> model is restricted to <code>sap.suite.ui.generic</code></b>
*
* When binding a button's enabled state to this model, it follows the
* enabled state of the CommandExecution. The binding path must be relative
* like <code>myCommand/enabled</code>:
*
* <pre>
* <Button press="cmd:MyCommand" enabled="$cmd>MyCommand/enabled" />
* </pre>
*
* A CommandExecution can have three states:
* <ul>
* <li>the CommandExecution is visible and enabled. If the configured shortcut is executed,
* the configured event handler of this CommandExecution is called
* </li>
* <li>the CommandExecution is visible but not enabled. If the configured shortcut is executed,
* neither the configured event handler of this CommandExecution nor any event handler configured on CommandExecutions
* in the ancestor chain is called
* </li>
* <li>the CommandExecution is not visible. If the configured shortcut is executed,
* the configured event handler of this CommandExecution is not called, but the event is propagated
* to its parent, which can then handle the event by a configured CommandExecution or propagate the event to its parent,
* until no parent exits anymore and the browser can handle the executed shortcut
* </li>
* </ul>
*
* @alias sap.ui.core.CommandExecution
* @since 1.70
*
* @public
*/
var CommandExecution = Element.extend("sap.ui.core.CommandExecution", /** @lends sap.ui.core.CommandExecution.prototype */ {
metadata: {
library: "sap.ui.core",
properties: {
/**
* The command's name, that has to be defined in the manifest.
* This property can only be applied initially.
*/
command: { type: "string" },
/**
* Whether the CommandExecution is enabled or not. By default, it is enabled.
* If the CommandExecution is disabled, the CommandExecution processes the event,
* but the event handler for it will not be called.
* Therefore, also no event handler configured on CommandExecutions on ancestors is called.
*/
enabled: { type: "boolean" , defaultValue: true},
/**
* Whether the CommandExecution is visible, or not. By default, it is visible.
* If not visible, the CommandExecution won't process the event, and
* the event handler for it will not be called, regardless of the enabled state.
* Therefore, the configured event handler on the next CommandExecution in the ancestor chain or,
* if no ancestor in the ancestor chain has any CommandExecutions configured for this shortcut,
* the event handler of the browser is called.
*/
visible: { type: "boolean" , defaultValue: true}
},
events: {
/**
* Execute will be fired when the CommandExecution will be triggered.
*/
execute: {}
}
},
bSkipUpdate:false,
/**
* Fires the execute event and triggers the attached handler.
* If the CommandExecution is disabled, the handler will not be triggered.
* @public
*/
trigger: function () {
if (this.getVisible() && this.getEnabled()) {
this.fireExecute({});
}
},
/**
* Sets a new value for property {@link #setting:command command}.
*
* The commands name, that has to be defined in the manifest.
* This property can only be applied initially.
*
* @param {string} sCommand New value for property <code>command</code>
* @return {this} Reference to <code>this</code> in order to allow method chaining
* @private
*/
setCommand: function(sCommand) {
if (!this.getCommand()) {
this.setProperty("command", sCommand, true);
} else {
Log.error("The 'command' property can only be applied initially!");
}
return this;
},
/**
* Returns the Command info defined in the owner component's manifest
* (<code>/sap.ui5/commands/<command></code>).
*
* In case no owner component is available or the command is not defined in the manifest
* <code>null</code> is returned.
*
* @returns {object} The command information from the manifest or <code>null</code>
* @private
*/
_getCommandInfo: function () {
if (!this.oCommand) {
var oCommand,
oControl = this.getParent(),
oComponent = Component.getOwnerComponentFor(this);
//if no owner found check the parent chain to find the next owner component...
while (!oComponent && oControl && oControl.getParent()) {
oComponent = Component.getOwnerComponentFor(oControl);
oControl = oControl.getParent();
}
if (oComponent) {
oCommand = oComponent.getCommand(this.getCommand());
}
this.oCommand = oCommand ? Object.assign({}, oCommand) : null;
}
return this.oCommand;
},
/**
* Update Context data
* @param {object} oCommandData The CommandData if it already exists
* @private
*/
_updateContextData: function(oCommandData) {
var oParent = this.getParent();
oCommandData[this.getCommand()] = {};
oCommandData[this.getCommand()].enabled = this.getEnabled();
oCommandData[this.getCommand()].id = this.getId();
oCommandData[this.getCommand()].visible = this.getVisible();
this.getModel("$cmd").setProperty("/" + oParent.getId(), oCommandData);
},
/**
* Creates command data in <code>$cmd</code> model.
*
* @param {object} [oParentData] An optional parent object to chain if necessary
*
* @private
*/
_createCommandData: function(oParentData) {
if (!this.bSkipUpdate) {
//prevent multiple updates due to model changes;
this.bSkipUpdate = true;
var oParent = this.getParent(),
oModel = oParent.getModel("$cmd"),
oData = oModel.getData(),
oContainerData = oData[oParent.getId()];
//no data yet
if (!oContainerData) {
oContainerData = Object.create(oParentData);
//parent data changed
} else if (oParentData !== Object.getPrototypeOf(oContainerData)) {
oContainerData = Object.create(oParentData);
}
this._updateContextData(oContainerData);
if (!oParent.getObjectBinding("$cmd")) {
oParent.bindElement("$cmd>/" + oParent.getId());
}
this.bSkipUpdate = false;
}
},
/** @inheritdoc */
setParent: function (oParent) {
var that = this,
oCommand,
oOldParent = this.getParent(),
oParentData,
sShortcut,
bIsRegistered;
function getParentData() {
var oParentContext = oParent.oPropagatedProperties.oBindingContexts["$cmd"];
return oParentContext ? oParentContext.getObject() : null;
}
function fnModelChange() {
if (oParent.getModel("$cmd")) {
var oParentData = getParentData();
//detach listener first to avoid side effects
that.getParent().detachModelContextChange(fnModelChange);
that._createCommandData(oParentData);
}
}
Element.prototype.setParent.apply(this, arguments);
oCommand = this._getCommandInfo();
if (oCommand) {
if (this.getVisible()) {
if (oParent && oParent !== oOldParent) {
//register Shortcut
sShortcut = oCommand.shortcut;
bIsRegistered = Shortcut.isRegistered(this.getParent(), sShortcut);
if (!bIsRegistered) {
Shortcut.register(oParent, sShortcut, this.trigger.bind(this));
}
if (oParent.getModel("$cmd")) {
oParentData = getParentData();
this._createCommandData(oParentData);
} else {
oParent.attachModelContextChange(fnModelChange);
}
if (!oParent._propagateProperties._sapui_fnOrig) {
var fnOriginalPropagate = oParent._propagateProperties;
oParent._propagateProperties = function(vName, oObject, oProperties, bUpdateAll, sName, bUpdateListener) {
//call orig propagate first, as we need the model for createCommandData
fnOriginalPropagate.apply(oParent, arguments);
var oActualContext = oParent.getBindingContext("$cmd");
// check update of model data for any CommandExecution
var oControl = arguments[1];
if (oActualContext && oControl.isA("sap.ui.core.CommandExecution")) {
var oActualData = oActualContext.getObject();
var oOldParentData = Object.getPrototypeOf(oActualData);
oParentData = getParentData();
if (oOldParentData !== oParentData) {
that._createCommandData.apply(oControl, [oParentData]);
}
}
};
oParent._propagateProperties._sapui_fnOrig = fnOriginalPropagate;
}
}
if (oOldParent && oOldParent != oParent) {
//unregister shortcut
sShortcut = oCommand.shortcut;
bIsRegistered = Shortcut.isRegistered(oOldParent, sShortcut);
if (bIsRegistered) {
Shortcut.unregister(oOldParent, oCommand.shortcut);
}
this._cleanupContext(oOldParent);
}
}
} else {
Log.error(`${this}: Command '${this.getCommand()}' is not defined in component manifest. No shortcut will be registered.`);
}
return this;
},
/**
* Cleanup of command data, binding context and propagation wrapper
*
* @param {sap.ui.core.Control} oControl The Control to cleanup
* @private
*/
_cleanupContext: function(oControl) {
if (oControl.getBindingContext("$cmd")) {
var oCommandData = oControl.getBindingContext("$cmd").getObject();
if (oCommandData) {
delete oCommandData[this.getCommand()];
if (isEmptyObject(Object.assign({}, oCommandData))) {
//reset _propagateProperties if not yet done...
if (oControl._propagateProperties._sapui_fnOrig) {
oControl._propagateProperties = oControl._propagateProperties._sapui_fnOrig;
}
//unbindContext as no command execution data exists anymore (only if parent is not in destruction)
if (!oControl._bIsBeingDestroyed) {
oControl.unbindElement("$cmd");
}
}
}
}
},
/**
* Sets whether the <code>CommandExecution</code> is visible, or not. If set to
* false, the <code>CommandExecution</code> will unregister the shortcut. If not visible,
* the CommandExecution will not be triggered even if it is enabled.
*
* @param {boolean} bValue Whether the CommandExecution is visible, or not.
* @returns {sap.ui.core.Element} The CommandExecution
*
* @public
*/
setVisible: function(bValue) {
var oParent = this.getParent(),
oCmdModel = this.getModel("$cmd");
this.setProperty("visible", bValue, true);
//when null/undefined is passed the property internally gets converted to a default value
bValue = this.getProperty("visible");
if (oParent) {
var oCommand = this._getCommandInfo(),
sShortcut = oCommand.shortcut,
bIsRegistered = Shortcut.isRegistered(oParent, sShortcut);
if (bValue && !bIsRegistered) {
Shortcut.register(oParent, sShortcut, this.trigger.bind(this));
} else if (!bValue && bIsRegistered) {
Shortcut.unregister(oParent, sShortcut);
}
}
//update $cmd Model
if (oCmdModel) {
var oContext = this.getBindingContext("$cmd");
oCmdModel.setProperty(this.getCommand() + "/visible", bValue, oContext);
}
return this;
},
/**
* Sets whether the <code>CommandExecution</code> is enabled, or not. If set to
* false, the <code>CommandExecution</code> will still register the shortcut.
* This will block any configured CommandExecutions on any ancestors for that shortcut.
*
* @param {boolean} bValue Whether the CommandExecution is enabled, or not.
* @returns {sap.ui.core.Element} The CommandExecution
*
* @public
*/
setEnabled: function(bValue) {
var oCmdModel = this.getModel("$cmd");
this.setProperty("enabled", bValue, true);
//update $cmd Model
if (oCmdModel) {
var oContext = this.getBindingContext("$cmd");
oCmdModel.setProperty(this.getCommand() + "/enabled", this.getProperty("enabled"), oContext);
}
return this;
},
/** @inheritdoc */
destroy: function () {
var oParent = this.getParent();
var oCommand = this._getCommandInfo();
if (oParent && oCommand) {
Shortcut.unregister(this.getParent(), oCommand.shortcut);
this._cleanupContext(oParent);
}
Element.prototype.destroy.apply(this, arguments);
}
});
/**
* Searches the control tree for a CommandExecution that matches the given command name.
*
* @param {sap.ui.core.Control} oControl the control/region the shortcut was triggered
* @param {string} sCommand Name of the command
*
* @returns {sap.ui.core.CommandExecution|undefined} The CommandExecution or undefined
* @static
* @private
*/
CommandExecution.find = function(oControl, sCommand) {
var i, oCommandExecution, oAggregation;
oAggregation = oControl.getDependents();
for (i = 0; i < oAggregation.length; i++) {
if (oAggregation[i].isA("sap.ui.core.CommandExecution") && oAggregation[i].getCommand() === sCommand) {
oCommandExecution = oAggregation[i];
}
}
if (!oCommandExecution && oControl.getParent()) {
oCommandExecution = CommandExecution.find(oControl.getParent(), sCommand);
}
return oCommandExecution;
};
return CommandExecution;
});