UNPKG

devextreme-vue

Version:

DevExtreme Vue UI and Visualization Components

291 lines (289 loc) • 12.4 kB
/*! * devextreme-vue * Version: 25.1.3 * Build date: Wed Jun 25 2025 * * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file in the root of the project for details. * * https://github.com/DevExpress/devextreme-vue */ import { defineComponent, h, } from 'vue'; import CreateCallback from 'devextreme/core/utils/callbacks'; import { triggerHandler } from 'devextreme/events'; import config from 'devextreme/core/config'; import { defaultSlots, getChildren, getComponentProps, getVModelValue, VMODEL_NAME, } from './vue-helper'; import { pullAllChildren } from './children-processing'; import Configuration, { bindOptionWatchers, setEmitOptionChangedFunc } from './configuration'; import { initOptionChangedFunc } from './configuration-component'; import { DX_REMOVE_EVENT } from './constants'; import { camelize, forEachChildNode, getOptionValue, getTemplatePropName, toComparable, } from './helpers'; import { TemplatesManager } from './templates-manager'; const includeAttrs = ['id', 'class', 'style']; const dxClassesPrefix = 'dx-'; config({ buyNowLink: 'https://go.devexpress.com/Licensing_Installer_Watermark_DevExtremeVue.aspx', licensingDocLink: 'https://go.devexpress.com/Licensing_Documentation_DevExtremeVue.aspx', }); function parseClassList(classList) { return classList.trim().split(/\s+/); } function prepareAttrs(attrs, dxClassesSyncedWithClassAttr) { const attributes = {}; includeAttrs.forEach((attr) => { const attrValue = attrs[attr]; if (attrValue !== undefined && attrValue !== null) { if (attr === 'class') { const nonDXClassesFromAttr = attrValue.split(' ') .filter((classFromAttr) => !classFromAttr.startsWith(dxClassesPrefix)) .join(' '); attributes[attr] = [nonDXClassesFromAttr, dxClassesSyncedWithClassAttr].filter((item) => item !== '').join(' '); } else { attributes[attr] = attrValue; } } }); return attributes; } function initBaseComponent() { return defineComponent({ inheritAttrs: false, data() { return { eventBus: CreateCallback(), prevClassAttr: '', }; }, provide() { return { eventBus: this.eventBus, }; }, render() { const thisComponent = this; const children = []; const dxClasses = pickOutDxClasses(this.$el) || []; if (thisComponent.$_config.cleanNested) { thisComponent.$_config.cleanNested(); } pullAllChildren(defaultSlots(this), children, thisComponent.$_config); this.$_processChildren(children); return h('div', { ...prepareAttrs(this.$attrs, dxClasses.join(' ')), }, children); }, beforeUpdate() { const thisComponent = this; thisComponent.$_config.setPrevNestedOptions(thisComponent.$_config.getNestedOptionValues()); this.$_syncElementClassesWithClassAttr(); }, updated() { const thisComponent = this; const nodes = cleanWidgetNode(this.$el); getChildren(thisComponent).forEach((child) => { initOptionChangedFunc(child.$_config, child.type.props || {}, child?.component?.proxy, child.$_innerChanges); }); thisComponent.$_templatesManager.discover(); thisComponent.$_instance.beginUpdate(); this.$_applyConfigurationChanges(); if (thisComponent.$_templatesManager.isDirty) { thisComponent.$_instance.option('integrationOptions.templates', thisComponent.$_templatesManager.templates); const { props } = thisComponent.$.vnode; for (const name of Object.keys(thisComponent.$_templatesManager.templates)) { thisComponent.$_instance.option(getTemplatePropName(props, name), name); } thisComponent.$_templatesManager.resetDirtyFlag(); } for (const name of Object.keys(thisComponent.$_pendingOptions)) { thisComponent.$_instance.option(name, thisComponent.$_pendingOptions[name]); } thisComponent.$_pendingOptions = {}; thisComponent.$_instance.endUpdate(); restoreNodes(this.$el, nodes); this.eventBus.fire(); }, beforeUnmount() { const thisComponent = this; const instance = thisComponent.$_instance; if (instance) { triggerHandler(this.$el, DX_REMOVE_EVENT); instance.dispose(); } }, created() { const thisComponent = this; const props = getComponentProps(this); thisComponent.$_config = new Configuration((n, v) => { if (Array.isArray(v)) { thisComponent.$_instance.option(n, v); } else { thisComponent.$_pendingOptions[n === VMODEL_NAME ? 'value' : n] = v; } }, null, props && { ...props }, thisComponent.$_expectedChildren); thisComponent.$_innerChanges = {}; thisComponent.$_config.init(this.$props && Object.keys(this.$props)); }, methods: { $_syncElementClassesWithClassAttr() { const newClassAttr = typeof this.$attrs?.class === 'string' ? this.$attrs?.class : ''; if (this.prevClassAttr === newClassAttr) { return; } if (this.prevClassAttr.length) { this.$el.classList.remove(...parseClassList(this.prevClassAttr)); } if (newClassAttr.length) { this.$el.classList.add(...parseClassList(newClassAttr)); } this.prevClassAttr = newClassAttr; }, $_applyConfigurationChanges() { const thisComponent = this; thisComponent.$_config.componentsCountChanged.forEach(({ optionPath, isCollection, removed }) => { const options = thisComponent.$_config.getNestedOptionValues(); if (!isCollection && removed) { thisComponent.$_instance.resetOption(optionPath); } else { thisComponent.$_instance.option(optionPath, getOptionValue(options, optionPath)); } }); thisComponent.$_config.cleanComponentsCountChanged(); }, $_createWidget(element) { const thisComponent = this; thisComponent.$_pendingOptions = {}; thisComponent.$_templatesManager = new TemplatesManager(this); const widgetConfig = thisComponent.$_config; if (widgetConfig.initialValues.hasOwnProperty(VMODEL_NAME)) { widgetConfig.initialValues.value = getVModelValue(widgetConfig.initialValues); } const options = { templatesRenderAsynchronously: thisComponent.$_hasAsyncTemplate, ...getComponentProps(thisComponent), ...widgetConfig.initialValues, ...widgetConfig.getNestedOptionValues(), ...this.$_getIntegrationOptions(), }; const instance = new thisComponent.$_WidgetClass(element, options); thisComponent.$_instance = instance; instance.on('optionChanged', (args) => widgetConfig.onOptionChanged(args)); setEmitOptionChangedFunc(widgetConfig, thisComponent, thisComponent.$_innerChanges); bindOptionWatchers(widgetConfig, thisComponent, thisComponent.$_innerChanges); this.$_createEmitters(instance); }, $_getIntegrationOptions() { const thisComponent = this; const result = { integrationOptions: { watchMethod: this.$_getWatchMethod(), }, ...this.$_getExtraIntegrationOptions(), }; if (thisComponent.$_templatesManager.isDirty) { const { templates } = thisComponent.$_templatesManager; result.integrationOptions.templates = templates; const { props } = thisComponent.$.vnode; for (const name of Object.keys(templates)) { result[getTemplatePropName(props, name)] = name; } thisComponent.$_templatesManager.resetDirtyFlag(); } return result; }, $_getWatchMethod() { return (valueGetter, valueChangeCallback, options) => { options = options || {}; if (!options.skipImmediate) { valueChangeCallback(valueGetter()); } return this.$watch(() => valueGetter(), (newValue, oldValue) => { if (toComparable(oldValue) !== toComparable(newValue) || options.deep) { valueChangeCallback(newValue); } }, { deep: options.deep, }); }; }, $_getExtraIntegrationOptions() { return {}; }, $_processChildren(_children) { }, $_createEmitters(instance) { if (this.$attrs) { Object.keys(this.$attrs).forEach((listenerName) => { const eventName = camelize(listenerName); instance.on(eventName, (e) => { this.$emit(listenerName, e); }); }); } }, }, }); } function cleanWidgetNode(node) { const removedNodes = []; forEachChildNode(node, (childNode) => { const parent = childNode.parentNode; const isExtension = childNode.hasAttribute && childNode.hasAttribute('isExtension'); if ((childNode.nodeName === '#comment' || isExtension) && parent) { removedNodes.push(childNode); parent.removeChild(childNode); } }); return removedNodes; } function pickOutDxClasses(el) { return el && Array.from(el.classList).filter((item) => item.startsWith(dxClassesPrefix)); } function restoreNodes(el, nodes) { nodes.forEach((node) => { el.appendChild(node); }); } function initDxComponent() { return defineComponent({ extends: initBaseComponent(), methods: { $_getExtraIntegrationOptions() { return { onInitializing() { this.beginUpdate(); }, }; }, $_processChildren(children) { children.forEach((childNode) => { if (!childNode || typeof childNode !== 'object') { return; } childNode.$_hasOwner = true; }); }, }, mounted() { const nodes = cleanWidgetNode(this.$el); const thisComponent = this; this.$_createWidget(this.$el); this.$_syncElementClassesWithClassAttr(); thisComponent.$_instance.endUpdate(); restoreNodes(this.$el, nodes); if (this.$slots?.default) { getChildren(thisComponent).forEach((child) => { const childExtenton = child; if (childExtenton && childExtenton.$_isExtension) { childExtenton.$_attachTo(this.$el); } }); } }, }); } export { initDxComponent, initBaseComponent };