UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

241 lines (205 loc) 8.04 kB
/*! * OpenUI5 * (c) Copyright 2026 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"], function( Element, assert, Log ) { "use strict"; var rAnyWhiteSpace = /\s/; var rNonWhiteSpace = /\S+/g; /** * 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> * * The classes are written to the HTML automatically when using the {@link sap.ui.core.RenderManager * Semantic Rendering API}. To ensure that the classes are written to the HTML with the traditional * string-based rendering, when writing the root tag of the element you must call the function * <code>oRenderManager.writeClasses(oElement);</code> ({@link sap.ui.core.RenderManager#writeClasses}) * within the renderer of the control to which the element belongs. * * 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; function check(sClass) { if (!mCustomStyleClassMap[sClass]) { mCustomStyleClassMap[sClass] = true; aCustomStyleClasses.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 ) { oRoot.classList.add.apply(oRoot.classList, aClasses); } else { oRoot.classList.add(sStyleClass); } } else if (bSuppressRerendering === false) { this.invalidate(); } 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, nIndex; function check(sClass) { if (mCustomStyleClassMap[sClass]) { bExist = true; nIndex = aCustomStyleClasses.indexOf(sClass); if (nIndex !== -1) { aCustomStyleClasses.splice(nIndex, 1); delete mCustomStyleClassMap[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 ) { oRoot.classList.remove.apply(oRoot.classList, aClasses); } else { oRoot.classList.remove(sStyleClass); } } else if (bSuppressRerendering === false) { this.invalidate(); } } 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"]); }; return CustomStyleClassSupport; }, /* bExport= */ true);