@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
632 lines (536 loc) • 17.1 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2023 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
sap.ui.define([
"sap/ui/base/EventProvider",
"./Element",
"./ShortcutHint",
"./Popup",
"./InvisibleText",
"sap/ui/events/checkMouseEnterOrLeave",
"sap/ui/Device"
],
function(
EventProvider,
Element,
ShortcutHint,
Popup,
InvisibleText,
checkMouseEnterOrLeave,
Device
) {
"use strict";
/**
* A mixin that adds shortcut hints to a control's instance. A shortcut hint
* may be provided directly as a text, as a key from the message bundle, or
* as an event name to get it from a command execution attached to that event.
* The shortcut shows in a popup next to conrol on focusin and mouseover.
* The popup shows in place of the native tooltip, merging the tooltip text
* with the provided hint.
*
* @private
* @alias sap.ui.core.ShortcutHintsMixin
* @mixin
* @since 1.80.0
* @ui5-restricted sap.m.Button
*/
var ShortcutHintsMixin = function(oControl) {
this.sControlId = oControl.getId();
this._hintConfigs = [];
};
ShortcutHintsMixin.init = function(oControl) {
oControl._shortcutHintsMixin = new ShortcutHintsMixin(oControl);
};
/**
* Adds a config describing a hint.
* @param {sap.ui.core.Control} oControl The control that will show the config
* @param {object} [oConfig] An object which defines the options
* @param {string} [oConfig.domrefid_suffix] A suffix used to identify the DOM node that
* shows the shortcut hint popup. It is concatenated with the ID of the control that is registered
* for a command execution. (Attaches the popup to the element with ID CONTROL_ID + domrefid_suffix)
* If it is omitted the CONTROL_ID will be used without suffix
* @param {string} [oConfig.position] Left and top offset of the shortcut hint popup from the default positioning
* (center top of the popup appears at center bottom of the referring DOM node)- e.g. "5 0"
* @param {boolean} [oConfig.addAccessibilityLabel] Whether we add an area-describedby label - ID to a hidden
* label with the content of the replaced native tooltip (for screen readers)
* @param {boolean} [oConfig.message] The string to be used as a shortcut hint
* @param {boolean} [oConfig.messageBundleKey] A message bundle key in the hint
* provider's library to be used as a translatable shortcut hint
* @param {boolean} [oConfig.event] Event name - to show a shortcut hint for a command
* attached to that event
* @param {sap.ui.core.Control} oHintProviderControl The control that will provide the hint content
* (e.g. the shortcut of a command registered to it)
*/
ShortcutHintsMixin.addConfig = function(oControl, oConfig, oHintProviderControl) {
if (Device.system.phone) {
return;
}
if (/sap-ui-xx-noshortcuthints=true/.test(document.location.search)) {
return;
}
var oMixin = oControl._shortcutHintsMixin;
if (!oMixin) {
ShortcutHintsMixin.init(oControl);
oMixin = oControl._shortcutHintsMixin;
}
oMixin._hintConfigs.push(oConfig);
oMixin.initHint(oConfig, oHintProviderControl);
};
/*
* Hides the shortcuts hints for all registered controls.
*/
ShortcutHintsMixin.hideAll = function() {
var oControl;
for (var sControlId in oHintRegistry.mControls) {
oControl = Element.registry.get(sControlId);
if (oControl) {
oControl._shortcutHintsMixin.hideShortcutHint();
}
}
};
/**
* Checks whether a dom node is registered to show a hint.
*
* @param {string} sDOMRefID The ID of the dom node to check
*/
ShortcutHintsMixin.isDOMIDRegistered = function(sDOMRefID) {
return oHintRegistry.mDOMNodes[sDOMRefID]
&& !!oHintRegistry.mDOMNodes[sDOMRefID].length;
};
/**
* Checks whether a control is registered to show a hint.
*
* @param {string} sControlId The ID of the control to check
*/
ShortcutHintsMixin.isControlRegistered = function(sControlId) {
return !!oHintRegistry.mControls[sControlId];
};
ShortcutHintsMixin.prototype._attachToEvents = function() {
var oControl;
if (!ShortcutHintsMixin.isControlRegistered(this.sControlId)) {
oControl = Element.registry.get(this.sControlId);
oControl.addEventDelegate(oHintsEventDelegate, this);
}
};
/*
* Registers the control for showing a commands' shortcut hint on focus and
* on hover.
*/
ShortcutHintsMixin.prototype.register = function(sDOMRefID, oConfig, oHintProviderControl) {
this._attachToEvents();
if (!ShortcutHintsMixin.isControlRegistered(this.sControlId)) {
var oControl = Element.registry.get(this.sControlId);
oControl._originalExit = oControl.exit;
oControl.exit = function() {
if (oControl._originalExit) {
oControl._originalExit.apply(oControl, arguments);
}
this.deregister();
}.bind(this);
}
oHintRegistry.mControls[this.sControlId] = true;
if (!oHintRegistry.mDOMNodes[sDOMRefID]) {
oHintRegistry.mDOMNodes[sDOMRefID] = [];
}
oHintRegistry.mDOMNodes[sDOMRefID].push(new ShortcutHint(oHintProviderControl, oConfig));
};
ShortcutHintsMixin.prototype.deregister = function() {
var aInfos = this.getRegisteredShortcutInfos(),
i;
delete oHintRegistry.mControls[this.sControlId];
for (i = 0; i < aInfos.length; i++) {
delete oHintRegistry.mDOMNodes[aInfos[i].id];
}
};
ShortcutHintsMixin.prototype.initHint = function(oConfig, oHintProviderControl) {
var oHintInfo = this._getShortcutHintInfo(oConfig);
if (oHintInfo.message) {
this.register(oHintInfo.id,
{ message: oHintInfo.message },
oHintProviderControl);
} else if (oHintInfo.messageBundleKey) {
this.register(oHintInfo.id,
{ messageBundleKey: oHintInfo.messageBundleKey },
oHintProviderControl);
} else if (oHintInfo.event) {
var oEventListeners = EventProvider.getEventList(oHintProviderControl)[oHintInfo.event],
aAttachedCommands = [];
if (oEventListeners) {
aAttachedCommands = oEventListeners.reduce(function(aResults, oListener) {
if (oListener.fFunction && oListener.fFunction._sapui_commandName) {
aResults.push(oListener.fFunction._sapui_commandName);
}
return aResults;
}, []);
}
if (aAttachedCommands.length) {
this.register(oHintInfo.id,
{
commandName: aAttachedCommands[0]
},
oHintProviderControl
);
} else {
oHintProviderControl.attachEvent("EventHandlerChange", function(oEvent) {
var oFn = oEvent.getParameter("func");
if (oEvent.getParameter("type") === "listenerAttached"
&& oFn && oFn._sapui_commandName
&& oEvent.getParameter("EventId") === oHintInfo.event) {
this.register(oHintInfo.id,
{
commandName: oFn._sapui_commandName
},
oHintProviderControl
);
}
}, this);
}
}
};
/**
* Returns an array of runtime shortcut hint information objects for the control,
* containing the actual reference element in the DOM.
*/
ShortcutHintsMixin.prototype._getShortcutHintInfos = function() {
return this._hintConfigs.map(this._getShortcutHintInfo, this);
};
/**
* Gets a shortcut hint information object for the provided config.
* The same as a config, but as a runtime object - resolves the actual DOM
* reference id where the popup is shown.
*
* @param {object} option A shortcut hint config
*/
ShortcutHintsMixin.prototype._getShortcutHintInfo = function(option) {
var id;
if (option.domrefid) {
id = option.domrefid;
} else if (option.domrefid_suffix) {
id = this.sControlId + option.domrefid_suffix;
} else {
id = this.sControlId;
}
return {
id: id,
event: option.event,
position: option.position,
messageBundleKey: option.messageBundleKey,
message: option.message,
addAccessibilityLabel: option.addAccessibilityLabel
};
};
ShortcutHintsMixin.prototype.getRegisteredShortcutInfos = function() {
return this._getShortcutHintInfos().filter(function(info) {
return ShortcutHintsMixin.isDOMIDRegistered(info.id);
}, this);
};
/**
* Shows a shortcut hint for this instance for the attached command.
*/
ShortcutHintsMixin.prototype.showShortcutHint = function(oHintInfos) {
var sTimeoutID,
sPosition = oHintInfos[0].position || "0 8",
sMy = Popup.Dock.CenterTop,
sOf = Popup.Dock.CenterBottom,
oPopup = _getHintPopup(),
$ShortcutHintRef = oHintInfos[0].ref,
sShortcut = _getShortcutHintText(oHintInfos[0].id),
mTooltips;
if (!_isElementVisible($ShortcutHintRef) || !_isElementInViewport($ShortcutHintRef)) {
return;
}
// concatenate with the tooltip
mTooltips = this._getControlTooltips();
if (mTooltips[oHintInfos[0].id]) {
sShortcut = mTooltips[oHintInfos[0].id].tooltip + " (" + sShortcut + ")";
}
if (!oPopup) {
oPopup = _createShortcutHintPopup(sShortcut);
}
oPopup.oContent.children[0].textContent = sShortcut;
// in the mass case open, only once with the position for the lead control of the group
if (!oPopup.isOpen()) {
oPopup.open(1000, sMy, sOf, $ShortcutHintRef, sPosition, "flipfit", function(params) {
oPopup.oContent.style.visibility = "hidden";
if (sTimeoutID) {
clearTimeout(sTimeoutID);
}
sTimeoutID = setTimeout(function() {
if (!_isElementVisible($ShortcutHintRef) || !_isElementInViewport($ShortcutHintRef)) {
return;
}
oPopup.oContent.style.visibility = "visible";
}, 1000);
oPopup._applyPosition(oPopup._oLastPosition);
});
}
};
/**
* Hides the shortcut hint for this instance.
*/
ShortcutHintsMixin.prototype.hideShortcutHint = function() {
var oPopup = _getHintPopup();
if (oPopup && oPopup.isOpen()) {
oPopup.close();
}
};
/**
* Finds the matching shortcut hint info that
* refers or contains the DOM event target.
* As a side effect caches the tested DOM references.
*/
ShortcutHintsMixin.prototype._findShortcutOptionsForRef = function(domEventTarget) {
var oHintInfo,
aInfos = this.getRegisteredShortcutInfos(),
i,
aResultInfos = [];
for (i = 0; i < aInfos.length; i++) {
oHintInfo = aInfos[i];
oHintInfo.ref = document.getElementById(oHintInfo.id);
if (oHintInfo.ref && oHintInfo.ref.contains(domEventTarget)) {
aResultInfos.push(oHintInfo);
}
}
return aResultInfos;
};
/**
* Gets all the native tooltip texts for a control. Relies on a
* control-provided tooltip getter - _getTitleAttribute. Uses
* control's getTooltip as a fallback.
*
* @returns {object} A map with tooltip strings by DOM node
*/
ShortcutHintsMixin.prototype._getControlTooltips = function() {
var aInfos = this.getRegisteredShortcutInfos(),
oControl = Element.registry.get(this.sControlId);
return aInfos.reduce(function(mResult, oHintInfo) {
var sTooltip = oControl._getTitleAttribute && oControl._getTitleAttribute(oHintInfo.id);
if (!sTooltip) {
sTooltip = oControl.getTooltip();
}
if (sTooltip) {
mResult[oHintInfo.id] = {
tooltip: sTooltip
};
}
return mResult;
}, {});
};
/**
* Maintains the accessibility label's content and reference to the control's DOM.
*
* @param {object} oHintInfo An object with the registration details for the shortcut
*/
ShortcutHintsMixin.prototype._updateShortcutHintAccLabel = function(oHintInfo) {
var oInvText,
sInvTextId,
oControl;
if (!oHintInfo.addAccessibilityLabel) {
return;
}
oControl = Element.registry.get(this.sControlId);
if (!oControl.getAriaDescribedBy) {
return;
}
oInvText = getInvisibleText(oControl);
sInvTextId = oInvText.getId();
oInvText.setText(_getShortcutHintText(oHintInfo.id));
if (!oInvText.getText()) {
oControl.removeAriaDescribedBy(sInvTextId);
} else if (oControl.getAriaDescribedBy().indexOf(sInvTextId) === -1) {
oControl.addAriaDescribedBy(sInvTextId);
}
};
/*
* This is a registry for all controls interested in showing command shortcuts.
*/
var oHintRegistry = Object.create(null);
oHintRegistry.mControls = {};
oHintRegistry.mDOMNodes = {};
var oHintsEventDelegate = {
/**
* @type {sap.ui.core.Control}
* @private
*/
"onfocusin": function(oEvent) {
var oShortcutHintRefs = this._findShortcutOptionsForRef(oEvent.target);
if (!oShortcutHintRefs.length) {
return;
}
ShortcutHintsMixin.hideAll();
this._updateShortcutHintAccLabel(oShortcutHintRefs[0]);
this.showShortcutHint(oShortcutHintRefs);
},
/**
* @type {sap.ui.core.Control}
* @private
*/
"onfocusout": function(oEvent) {
var oShortcutHintRefs = this._findShortcutOptionsForRef(oEvent.target);
if (!oShortcutHintRefs.length) {
return;
}
this.hideShortcutHint();
},
/**
* @type {sap.ui.core.Control}
* @private
*/
"onmouseover": function(oEvent) {
var oShortcutHintRefs = this._findShortcutOptionsForRef(oEvent.target),
oDOMRef;
if (!oShortcutHintRefs.length) {
return;
}
oDOMRef = oShortcutHintRefs[0].ref;
if (!_isElementFocusable(oDOMRef)) {
return;
}
if (checkMouseEnterOrLeave(oEvent, oDOMRef)) {
ShortcutHintsMixin.hideAll();
this.showShortcutHint(oShortcutHintRefs);
}
},
/**
* @type {sap.ui.core.Control}
* @private
*/
"onmouseout": function(oEvent) {
var oShortcutHintRefs = this._findShortcutOptionsForRef(oEvent.target);
if (!oShortcutHintRefs.length) {
return;
}
if (checkMouseEnterOrLeave(oEvent, oShortcutHintRefs[0].ref)) {
// do not hide if the element is focused
if (oShortcutHintRefs[0].ref.contains(document.activeElement)) {
return;
}
this.hideShortcutHint();
}
},
/**
* @type {sap.ui.core.Control}
* @private
*/
"onAfterRendering": function() {
var aInfos = this.getRegisteredShortcutInfos(),
oElement,
sDOMRefID;
for (var i = 0; i < aInfos.length; i++) {
sDOMRefID = aInfos[i].id;
oElement = document.getElementById(sDOMRefID);
oElement.setAttribute("aria-keyshortcuts", _getShortcutHintText(sDOMRefID));
}
}
};
/**
* Gets the shortcut hint text for a registered DOM node.
*
* @param {string} sDOMRefID A registered DOM node ID
*/
function _getShortcutHintText(sDOMRefID) {
var aHints = oHintRegistry.mDOMNodes[sDOMRefID];
if (!aHints || !aHints.length) {
return;
}
return aHints.map(function(oHint) {
return oHint._getShortcutText();
}).join(", ");
}
/**
* Gets or creates an InvisibleText for the control's shortcut accessiblity.
*
* @param {sap.ui.core.Control} oControl A control that have shortcut assigned
*/
function getInvisibleText(oControl) {
if (!oControl._shortcutInvisibleText) {
var oFunc = oControl.exit;
oControl._shortcutInvisibleText = new InvisibleText();
oControl._shortcutInvisibleText.toStatic();
oControl.exit = function() {
this._shortcutInvisibleText.destroy();
oFunc.call(this);
};
}
return oControl._shortcutInvisibleText;
}
/**
* Gets a popup for the shortcut hint.
*/
function _getHintPopup() {
return ShortcutHintsMixin._popup;
}
/**
* Creates a popup with the provided text content.
*
* @param {string} sTextContent Text content for the popup
*/
function _createShortcutHintPopup(sTextContent) {
var oPopup,
oContainerElement,
oTextContentElement;
oContainerElement = document.createElement("span");
oContainerElement.classList.add("sapUiHintContainer");
oTextContentElement = document.createElement("div");
oTextContentElement.classList.add("sapUiHintText");
oTextContentElement.textContent = sTextContent;
oContainerElement.appendChild(oTextContentElement);
oPopup = new Popup(
oContainerElement,
false,
false,
false
);
//set open animation
oPopup.setAnimations(function($ref, iDuration, callback) {
setTimeout(callback, iDuration);
}, function($ref, iDuration, callback) {
callback();
});
ShortcutHintsMixin._popup = oPopup;
return oPopup;
}
/**
* Determines if a DOM element is inside the viewport.
*/
function _isElementInViewport(oDomElement) {
var mRect;
if (!oDomElement) {
return false;
}
mRect = oDomElement.getBoundingClientRect();
return (
mRect.top >= 0 &&
mRect.left >= 0 &&
mRect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
mRect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
/**
* Determines if a DOM element is visible.
*/
function _isElementVisible(elem) {
return elem.offsetWidth > 0 || elem.offsetHeight > 0 || elem.getClientRects().length > 0;
}
/**
* Determines if a DOM element has a tabindex.
*/
function _elementHasTabIndex(elem) {
var iTabIndex = elem.tabIndex;
return iTabIndex != null
&& iTabIndex >= 0
&& (elem.getAttribute("disabled") == null
|| elem.getAttribute("tabindex"));
}
/**
* Determines if a DOM element is focusable.
*/
function _isElementFocusable(elem) {
return elem.nodeType == 1
&& _isElementVisible(elem)
&& _elementHasTabIndex(elem);
}
return ShortcutHintsMixin;
});