@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
290 lines (247 loc) • 9.49 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides helper sap.ui.core.CustomStyleClassSupport
sap.ui.define(['./Element', "sap/base/assert", "sap/base/Log", "sap/ui/Device"],
function(Element, assert, Log, Device) {
"use strict";
/*global DOMTokenList */
var rAnyWhiteSpace = /\s/;
var rNonWhiteSpace = /\S+/g;
// Workaround for functionality that is missing in IE11
var addAll = DOMTokenList.prototype.add,
removeAll = DOMTokenList.prototype.remove;
if ( Device.browser.msie ) {
/* polyfill, to be called with a DOMTokenlist as this and the class names as individual arguments */
addAll = function() {
for (var i = 0; i < arguments.length; i++) {
this.add(arguments[i]);
}
};
/* polyfill, to be called with a DOMTokenlist as this and the class names as individual arguments */
removeAll = function() {
for (var i = 0; i < arguments.length; i++) {
this.remove(arguments[i]);
}
};
}
/**
* Applies the support for custom style classes on the prototype of a <code>sap.ui.core.Element</code>.
*
* All controls (subclasses of <code>sap.ui.core.Control</code>) provide the support custom style classes. The control API provides functions
* to the application which allow it to add, remove or change style classes for the control.
* In general, this option is not available for elements because elements do not necessarily have a representation in the DOM.
*
* This function can be used by a control developer to explicitly enrich the API of his/her element implementation with the API functions
* for the custom style class support. It must be called on the prototype of the element.
*
* <b>Usage Example:</b>
* <pre>
* sap.ui.define(['sap/ui/core/Element', 'sap/ui/core/CustomStyleClassSupport'], function(Element, CustomStyleClassSupport) {
* "use strict";
* var MyElement = Element.extend("my.MyElement", {
* metadata : {
* //...
* }
* //...
* });
*
* CustomStyleClassSupport.apply(MyElement.prototype);
*
* return MyElement;
* }, true);
* </pre>
*
* Furthermore, the function <code>oRenderManager.writeClasses(oElement);</code> ({@link sap.ui.core.RenderManager#writeClasses}) must be called within
* the renderer of the control to which the element belongs, when writing the root tag of the element. This ensures the classes are written to the HTML.
*
* This function adds the following functions to the elements prototype:
* <ul>
* <li><code>addStyleClass</code>: {@link sap.ui.core.Control#addStyleClass}</li>
* <li><code>removeStyleClass</code>: {@link sap.ui.core.Control#removeStyleClass}</li>
* <li><code>toggleStyleClass</code>: {@link sap.ui.core.Control#toggleStyleClass}</li>
* <li><code>hasStyleClass</code>: {@link sap.ui.core.Control#hasStyleClass}</li>
* </ul>
* In addition the clone function of the element is extended to ensure that the custom style classes are also available on the cloned element.
*
* <b>Note:</b> This function can only be used <i>within</i> control development. An application cannot add style class support on existing elements by calling this function.
*
* @public
* @alias sap.ui.core.CustomStyleClassSupport
* @function
*/
var CustomStyleClassSupport = function () {
// "this" is the prototype now when called with apply()
// Ensure only Elements are enhanced
if (!(this instanceof Element)) {
return;
}
// enrich original clone function
var fnOriginalClone = this.clone;
this.clone = function() {
// call original clone function
var oClone = fnOriginalClone.apply(this, arguments);
// add the style classes of "this" to the clone
if (this.aCustomStyleClasses) {
oClone.aCustomStyleClasses = this.aCustomStyleClasses.slice();
}
//add the style class map of "this" to the clone
if (this.mCustomStyleClassMap) {
oClone.mCustomStyleClassMap = Object.assign(Object.create(null), this.mCustomStyleClassMap);
}
return oClone;
};
this.addStyleClass = function(sStyleClass, bSuppressRerendering) { // bSuppressRerendering is experimental and hence undocumented
assert(typeof sStyleClass === "string", "sStyleClass must be a string");
// ensure that sStyleClass is a non-empty string with no quotes in it (quotes would break string rendering)
if (!sStyleClass
|| typeof sStyleClass !== "string"
|| sStyleClass.indexOf("\"") > -1
|| sStyleClass.indexOf("'") > -1) {
return this;
}
var aCustomStyleClasses = this.aCustomStyleClasses || (this.aCustomStyleClasses = []),
mCustomStyleClassMap = this.mCustomStyleClassMap || (this.mCustomStyleClassMap = Object.create(null)),
aClasses,
bModified = false,
aChangedScopes = [],
aScopes = getScopes();
function check(sClass) {
if (!mCustomStyleClassMap[sClass]) {
mCustomStyleClassMap[sClass] = true;
aCustomStyleClasses.push(sClass);
if (aScopes && aScopes.indexOf(sClass) > -1){
aChangedScopes.push(sClass);
}
bModified = true;
}
}
if ( rAnyWhiteSpace.test(sStyleClass) ) {
aClasses = sStyleClass.match(rNonWhiteSpace);
aClasses && aClasses.forEach(check);
} else {
check(sStyleClass);
}
// if all classes exist already, it's not needed to change the DOM or trigger invalidate
if (!bModified) {
return this;
}
var oRoot = this.getDomRef();
if (oRoot) { // non-rerendering shortcut
if ( aClasses ) {
addAll.apply(oRoot.classList, aClasses);
} else {
oRoot.classList.add(sStyleClass);
}
} else if (bSuppressRerendering === false) {
this.invalidate();
}
if (aChangedScopes.length > 0) {
// scope has been added
fireThemeScopingChangedEvent(this, aChangedScopes, true);
}
return this;
};
this.removeStyleClass = function(sStyleClass, bSuppressRerendering) { // bSuppressRerendering is experimental and hence undocumented
assert(typeof sStyleClass === "string", "sStyleClass must be a string");
if (!sStyleClass
|| typeof sStyleClass !== "string"
|| !this.aCustomStyleClasses
|| !this.mCustomStyleClassMap) {
return this;
}
var aCustomStyleClasses = this.aCustomStyleClasses,
mCustomStyleClassMap = this.mCustomStyleClassMap,
aClasses,
bExist = false,
aChangedScopes = [],
aScopes = getScopes(),
nIndex;
function check(sClass) {
if (mCustomStyleClassMap[sClass]) {
bExist = true;
nIndex = aCustomStyleClasses.indexOf(sClass);
if (nIndex !== -1) {
aCustomStyleClasses.splice(nIndex, 1);
delete mCustomStyleClassMap[sClass];
if (aScopes && aScopes.indexOf(sClass) > -1) {
aChangedScopes.push(sClass);
}
}
}
}
if ( rAnyWhiteSpace.test(sStyleClass) ) {
aClasses = sStyleClass.match(rNonWhiteSpace);
aClasses && aClasses.forEach(check);
} else {
check(sStyleClass);
}
if (bExist) {
var oRoot = this.getDomRef();
if (oRoot) { // non-rerendering shortcut
if ( aClasses ) {
removeAll.apply(oRoot.classList, aClasses);
} else {
oRoot.classList.remove(sStyleClass);
}
} else if (bSuppressRerendering === false) {
this.invalidate();
}
if (aChangedScopes.length > 0) {
// scope has been removed
fireThemeScopingChangedEvent(this, aChangedScopes, false);
}
}
return this;
};
this.toggleStyleClass = function(sStyleClass, bAdd) {
assert(typeof sStyleClass === "string", "sStyleClass must be a string");
if (sStyleClass && typeof sStyleClass === "string") {
if (bAdd === true) {
this.addStyleClass(sStyleClass);
} else if (bAdd === false) {
this.removeStyleClass(sStyleClass);
} else if (bAdd === undefined) {
this.hasStyleClass(sStyleClass) ? this.removeStyleClass(sStyleClass) : this.addStyleClass(sStyleClass);
} else {
Log.warning(this.toString() + "- toggleStyleClass(): bAdd should be a boolean or undefined, but is '" + bAdd + "'");
}
}
return this; // we could (depending on bAdd) return either this or the boolean result of removeStyleClass, but at least in the bAdd===undefined case the caller wouldn't even know which return type to expect...
};
this.hasStyleClass = function(sStyleClass) {
assert(typeof sStyleClass === "string", "sStyleClass must be a string");
if (sStyleClass && typeof sStyleClass === "string" && this.mCustomStyleClassMap) {
if ( rAnyWhiteSpace.test(sStyleClass) ) {
var aClasses = sStyleClass.match(rNonWhiteSpace);
return aClasses != null && aClasses.every(function(sClass) {
return this.mCustomStyleClassMap[sClass];
}, this);
} else {
return !!this.mCustomStyleClassMap[sStyleClass];
}
}
return false;
};
this.getMetadata().addPublicMethods(["addStyleClass", "removeStyleClass", "toggleStyleClass", "hasStyleClass"]);
};
var Parameters;
function getScopes() {
if (!Parameters) {
Parameters = sap.ui.require("sap/ui/core/theming/Parameters");
}
if (Parameters) {
return Parameters._getScopes(/* avoidLoading= */ true);
}
}
function fireThemeScopingChangedEvent(oElement, aScopeClasses, bIsAdded) {
sap.ui.getCore().fireThemeScopingChanged({
scopes: aScopeClasses,
added: bIsAdded,
element: oElement
});
}
return CustomStyleClassSupport;
}, /* bExport= */ true);