devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
446 lines (445 loc) • 16.3 kB
JavaScript
/**
* DevExtreme (esm/__internal/core/widget/dom_component.js)
* Version: 24.2.6
* Build date: Mon Mar 17 2025
*
* Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import {
resize as resizeEvent,
visibility as visibilityEvents
} from "../../../common/core/events/short";
import config from "../../../core/config";
import {
getPublicElement
} from "../../../core/element";
import {
cleanDataRecursive
} from "../../../core/element_data";
import errors from "../../../core/errors";
import $ from "../../../core/renderer";
import {
grep,
noop
} from "../../../core/utils/common";
import {
extend
} from "../../../core/utils/extend";
import {
each
} from "../../../core/utils/iterator";
import {
attachInstanceToElement,
getInstanceByElement
} from "../../../core/utils/public_component";
import windowResizeCallbacks from "../../../core/utils/resize_callbacks";
import {
addShadowDomStyles
} from "../../../core/utils/shadow_dom";
import {
isDefined,
isFunction,
isString
} from "../../../core/utils/type";
import {
hasWindow
} from "../../../core/utils/window";
import license, {
peekValidationPerformed
} from "../../core/license/license_validation";
import TemplateManagerModule from "../../core/m_template_manager";
import {
uiLayerInitialized
} from "../../core/utils/m_common";
import {
Component
} from "./component";
class DOMComponent extends Component {
static getInstance(element) {
return getInstanceByElement($(element), this)
}
static defaultOptions(rule) {
this._classCustomRules = Object.hasOwnProperty.bind(this)("_classCustomRules") && this._classCustomRules ? this._classCustomRules : [];
this._classCustomRules.push(rule)
}
_getDefaultOptions() {
return extend(super._getDefaultOptions(), {
width: void 0,
height: void 0,
rtlEnabled: config().rtlEnabled,
elementAttr: {},
disabled: false,
integrationOptions: {}
}, this._useTemplates() ? TemplateManagerModule.TemplateManager.createDefaultOptions() : {})
}
ctor(element, options) {
this._customClass = null;
this._createElement(element);
attachInstanceToElement(this._$element, this, this._dispose);
super.ctor(options);
const validationAlreadyPerformed = peekValidationPerformed();
license.validateLicense(config().licenseKey);
if (!validationAlreadyPerformed && peekValidationPerformed()) {
config({
licenseKey: ""
})
}
uiLayerInitialized.resolve()
}
_createElement(element) {
this._$element = $(element)
}
_getSynchronizableOptionsForCreateComponent() {
return ["rtlEnabled", "disabled", "templatesRenderAsynchronously"]
}
_checkFunctionValueDeprecation(optionNames) {
if (!this.option("_ignoreFunctionValueDeprecation")) {
optionNames.forEach((optionName => {
if (isFunction(this.option(optionName))) {
errors.log("W0017", optionName)
}
}))
}
}
_visibilityChanged(value) {}
_dimensionChanged() {}
_init() {
super._init();
this._checkFunctionValueDeprecation(["width", "height", "maxHeight", "maxWidth", "minHeight", "minWidth", "popupHeight", "popupWidth"]);
this._attachWindowResizeCallback();
this._initTemplateManager()
}
_setOptionsByDevice(instanceCustomRules) {
super._setOptionsByDevice([].concat(this.constructor._classCustomRules || [], instanceCustomRules || []))
}
_isInitialOptionValue(name) {
const isCustomOption = this.constructor._classCustomRules && Object.prototype.hasOwnProperty.call(this._convertRulesToOptions(this.constructor._classCustomRules), name);
return !isCustomOption && super._isInitialOptionValue(name)
}
_attachWindowResizeCallback() {
if (this._isDimensionChangeSupported()) {
const windowResizeCallBack = this._windowResizeCallBack = this._dimensionChanged.bind(this);
windowResizeCallbacks.add(windowResizeCallBack)
}
}
_isDimensionChangeSupported() {
return this._dimensionChanged !== DOMComponent.prototype._dimensionChanged
}
_renderComponent() {
addShadowDomStyles(this.$element());
this._initMarkup();
hasWindow() && this._render()
}
_initMarkup() {
const {
rtlEnabled: rtlEnabled
} = this.option() || {};
this._renderElementAttributes();
this._toggleRTLDirection(rtlEnabled);
this._renderVisibilityChange();
this._renderDimensions()
}
_render() {
this._attachVisibilityChangeHandlers()
}
_renderElementAttributes() {
const {
elementAttr: elementAttr
} = this.option() || {};
const attributes = extend({}, elementAttr);
const classNames = attributes.class;
delete attributes.class;
this.$element().attr(attributes).removeClass(this._customClass).addClass(classNames);
this._customClass = classNames
}
_renderVisibilityChange() {
if (this._isDimensionChangeSupported()) {
this._attachDimensionChangeHandlers()
}
if (this._isVisibilityChangeSupported()) {
const $element = this.$element();
$element.addClass("dx-visibility-change-handler")
}
}
_renderDimensions() {
const $element = this.$element();
const element = $element.get(0);
const width = this._getOptionValue("width", element);
const height = this._getOptionValue("height", element);
if (this._isCssUpdateRequired(element, height, width)) {
$element.css({
width: null === width ? "" : width,
height: null === height ? "" : height
})
}
}
_isCssUpdateRequired(element, height, width) {
return !!(isDefined(width) || isDefined(height) || element.style.width || element.style.height)
}
_attachDimensionChangeHandlers() {
const $el = this.$element();
const namespace = `${this.NAME}VisibilityChange`;
resizeEvent.off($el, {
namespace: namespace
});
resizeEvent.on($el, (() => this._dimensionChanged()), {
namespace: namespace
})
}
_attachVisibilityChangeHandlers() {
if (this._isVisibilityChangeSupported()) {
const $el = this.$element();
const namespace = `${this.NAME}VisibilityChange`;
this._isHidden = !this._isVisible();
visibilityEvents.off($el, {
namespace: namespace
});
visibilityEvents.on($el, (() => this._checkVisibilityChanged("shown")), (() => this._checkVisibilityChanged("hiding")), {
namespace: namespace
})
}
}
_isVisible() {
const $element = this.$element();
return $element.is(":visible")
}
_checkVisibilityChanged(action) {
const isVisible = this._isVisible();
if (isVisible) {
if ("hiding" === action && !this._isHidden) {
this._visibilityChanged(false);
this._isHidden = true
} else if ("shown" === action && this._isHidden) {
this._isHidden = false;
this._visibilityChanged(true)
}
}
}
_isVisibilityChangeSupported() {
return this._visibilityChanged !== DOMComponent.prototype._visibilityChanged && hasWindow()
}
_clean() {}
_modelByElement(element) {
const {
modelByElement: modelByElement
} = this.option();
const $element = this.$element();
return modelByElement ? modelByElement($element) : void 0
}
_invalidate() {
if (this._isUpdateAllowed()) {
throw errors.Error("E0007")
}
this._requireRefresh = true
}
_refresh() {
this._clean();
this._renderComponent()
}
_dispose() {
this._templateManager && this._templateManager.dispose();
super._dispose();
this._clean();
this._detachWindowResizeCallback()
}
_detachWindowResizeCallback() {
if (this._isDimensionChangeSupported()) {
windowResizeCallbacks.remove(this._windowResizeCallBack)
}
}
_toggleRTLDirection(rtl) {
const $element = this.$element();
$element.toggleClass("dx-rtl", rtl)
}
_createComponent(element, component, componentConfiguration) {
const configuration = componentConfiguration ?? {};
const synchronizableOptions = grep(this._getSynchronizableOptionsForCreateComponent(), (value => !(value in configuration)));
const {
integrationOptions: integrationOptions
} = this.option();
let {
nestedComponentOptions: nestedComponentOptions
} = this.option();
nestedComponentOptions = nestedComponentOptions ?? noop;
const nestedComponentConfig = extend({
integrationOptions: integrationOptions
}, nestedComponentOptions(this));
synchronizableOptions.forEach((optionName => nestedComponentConfig[optionName] = this.option(optionName)));
this._extendConfig(configuration, nestedComponentConfig);
let instance;
if (isString(component)) {
const $element = $(element)[component](configuration);
instance = $element[component]("instance")
} else if (element) {
instance = component.getInstance(element);
if (instance) {
instance.option(configuration)
} else {
instance = new component(element, configuration)
}
}
if (instance) {
const optionChangedHandler = _ref => {
let {
name: name,
value: value
} = _ref;
if (synchronizableOptions.includes(name)) {
instance.option(name, value)
}
};
this.on("optionChanged", optionChangedHandler);
instance.on("disposing", (() => this.off("optionChanged", optionChangedHandler)))
}
return instance
}
_extendConfig(configuration, extendConfig) {
each(extendConfig, ((key, value) => {
!Object.prototype.hasOwnProperty.call(configuration, key) && (configuration[key] = value)
}))
}
_defaultActionConfig() {
const $element = this.$element();
const context = this._modelByElement($element);
return extend(super._defaultActionConfig(), {
context: context
})
}
_defaultActionArgs() {
const $element = this.$element();
const model = this._modelByElement($element);
const element = this.element();
return extend(super._defaultActionArgs(), {
element: element,
model: model
})
}
_optionChanged(args) {
const {
name: name
} = args;
switch (name) {
case "width":
case "height":
this._renderDimensions();
break;
case "rtlEnabled":
this._invalidate();
break;
case "elementAttr":
this._renderElementAttributes();
break;
case "disabled":
case "integrationOptions":
break;
default:
super._optionChanged(args)
}
}
_removeAttributes(element) {
const attrs = element.attributes;
for (let i = attrs.length - 1; i >= 0; i--) {
const attr = attrs[i];
if (attr) {
const {
name: name
} = attr;
if (!name.indexOf("aria-") || -1 !== name.indexOf("dx-") || "role" === name || "style" === name || "tabindex" === name) {
element.removeAttribute(name)
}
}
}
}
_removeClasses(element) {
element.className = element.className.split(" ").filter((cssClass => 0 !== cssClass.lastIndexOf("dx-", 0))).join(" ")
}
_updateDOMComponent(renderRequired) {
if (renderRequired) {
this._renderComponent()
} else if (this._requireRefresh) {
this._requireRefresh = false;
this._refresh()
}
}
endUpdate() {
const renderRequired = this._isInitializingRequired();
super.endUpdate();
this._isUpdateAllowed() && this._updateDOMComponent(renderRequired)
}
$element() {
return this._$element
}
element() {
const $element = this.$element();
return getPublicElement($element)
}
dispose() {
const element = this.$element().get(0);
cleanDataRecursive(element, true);
element.textContent = "";
this._removeAttributes(element);
this._removeClasses(element)
}
resetOption(optionName) {
super.resetOption(optionName);
if ("width" === optionName || "height" === optionName) {
const initialOption = this.initialOption(optionName);
!isDefined(initialOption) && this.$element().css(optionName, "")
}
}
_getAnonymousTemplateName() {
return
}
_initTemplateManager() {
if (this._templateManager || !this._useTemplates()) {
return
}
const {
integrationOptions: integrationOptions = {}
} = this.option();
const {
createTemplate: createTemplate
} = integrationOptions;
this._templateManager = new TemplateManagerModule.TemplateManager(createTemplate, this._getAnonymousTemplateName());
this._initTemplates();
return
}
_initTemplates() {
const {
templates: templates,
anonymousTemplateMeta: anonymousTemplateMeta
} = this._templateManager.extractTemplates(this.$element());
const anonymousTemplate = this.option(`integrationOptions.templates.${anonymousTemplateMeta.name}`);
templates.forEach((_ref2 => {
let {
name: name,
template: template
} = _ref2;
this._options.silent(`integrationOptions.templates.${name}`, template)
}));
if (anonymousTemplateMeta.name && !anonymousTemplate) {
this._options.silent(`integrationOptions.templates.${anonymousTemplateMeta.name}`, anonymousTemplateMeta.template);
this._options.silent("_hasAnonymousTemplateContent", true)
}
}
_getTemplateByOption(optionName) {
return this._getTemplate(this.option(optionName))
}
_getTemplate(templateSource) {
const templates = this.option("integrationOptions.templates");
const isAsyncTemplate = this.option("templatesRenderAsynchronously");
const skipTemplates = this.option("integrationOptions.skipTemplates");
return this._templateManager.getTemplate(templateSource, templates, {
isAsyncTemplate: isAsyncTemplate,
skipTemplates: skipTemplates
}, this)
}
_saveTemplate(name, template) {
this._setOptionWithoutOptionChange(`integrationOptions.templates.${name}`, this._templateManager._createTemplate(template))
}
_useTemplates() {
return true
}
}
export default DOMComponent;