UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

1,315 lines 53.2 kB
/**
 * DevExtreme (esm/__internal/ui/form/form.js)
 * Version: 25.1.4
 * Build date: Tue Aug 05 2025
 *
 * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED
 * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
 */
import _extends from "@babel/runtime/helpers/esm/extends";
import "../../../ui/validation_summary";
import "../../../ui/validation_group";
import eventsEngine from "../../../common/core/events/core/events_engine";
import {
    triggerResizeEvent,
    triggerShownEvent
} from "../../../common/core/events/visibility_change";
import messageLocalization from "../../../common/core/localization/message";
import registerComponent from "../../../core/component_registrator";
import config from "../../../core/config";
import {
    getPublicElement
} from "../../../core/element";
import Guid from "../../../core/guid";
import $ from "../../../core/renderer";
import resizeObserverSingleton from "../../../core/resize_observer";
import {
    ensureDefined
} from "../../../core/utils/common";
import {
    Deferred
} from "../../../core/utils/deferred";
import {
    extend
} from "../../../core/utils/extend";
import {
    each
} from "../../../core/utils/iterator";
import {
    isDefined,
    isEmptyObject,
    isObject,
    isString
} from "../../../core/utils/type";
import {
    defaultScreenFactorFunc,
    getCurrentScreenFactor,
    hasWindow
} from "../../../core/utils/window";
import {
    current,
    isMaterial,
    isMaterialBased
} from "../../../ui/themes";
import Widget, {
    FOCUSED_STATE_CLASS
} from "../../core/widget/widget";
import {
    DROP_DOWN_EDITOR_CLASS
} from "../../ui/drop_down_editor/m_drop_down_editor";
import Editor from "../../ui/editor/editor";
import {
    setLabelWidthByMaxLabelWidth
} from "../../ui/form/components/label";
import {
    FIELD_ITEM_CLASS,
    FIELD_ITEM_CONTENT_CLASS,
    FIELD_ITEM_CONTENT_HAS_GROUP_CLASS,
    FIELD_ITEM_CONTENT_HAS_TABS_CLASS,
    FIELD_ITEM_TAB_CLASS,
    FORM_CLASS,
    FORM_FIELD_ITEM_COL_CLASS,
    FORM_GROUP_CAPTION_CLASS,
    FORM_GROUP_CLASS,
    FORM_GROUP_CONTENT_CLASS,
    FORM_GROUP_CUSTOM_CAPTION_CLASS,
    FORM_GROUP_WITH_CAPTION_CLASS,
    FORM_UNDERLINED_CLASS,
    FORM_VALIDATION_SUMMARY,
    GROUP_COL_COUNT_ATTR,
    GROUP_COL_COUNT_CLASS,
    ROOT_SIMPLE_ITEM_CLASS
} from "../../ui/form/constants";
import tryCreateItemOptionAction from "../../ui/form/form.item_options_actions";
import FormItemsRunTimeInfo from "../../ui/form/form.items_runtime_info";
import LayoutManager from "../../ui/form/form.layout_manager";
import {
    concatPaths,
    convertToLayoutManagerOptions,
    createItemPathByIndex,
    getFullOptionName,
    getItemPath,
    getOptionNameFromFullName,
    getTextWithoutSpaces,
    isEqualToDataFieldOrNameOrTitleOrCaption,
    isFullPathContainsTabs,
    tryGetTabPath
} from "../../ui/form/form.utils";
import ValidationEngine from "../../ui/m_validation_engine";
import ValidationSummary from "../../ui/m_validation_summary";
import Scrollable from "../../ui/scroll_view/scrollable";
import TabPanel from "../../ui/tab_panel/tab_panel";
import {
    TEXTEDITOR_CLASS,
    TEXTEDITOR_INPUT_CLASS
} from "../../ui/text_box/m_text_editor.base";
import {
    TOOLBAR_CLASS
} from "../../ui/toolbar/constants";
const ITEM_OPTIONS_FOR_VALIDATION_UPDATING = ["items", "isRequired", "validationRules", "visible"];
class Form extends Widget {
    _init() {
        super._init();
        this._dirtyFields = new Set;
        this._cachedColCountOptions = [];
        this._itemsRunTimeInfo = new FormItemsRunTimeInfo;
        this._groupsColCount = [];
        this._attachSyncSubscriptions()
    }
    _getDefaultOptions() {
        return _extends({}, super._getDefaultOptions(), {
            formID: `dx-${new Guid}`,
            formData: {},
            colCount: 1,
            screenByWidth: defaultScreenFactorFunc,
            labelLocation: "left",
            readOnly: false,
            onFieldDataChanged: null,
            customizeItem: null,
            onEditorEnterKey: null,
            minColWidth: 200,
            alignItemLabels: true,
            alignItemLabelsInAllGroups: true,
            alignRootItemLabels: true,
            showColonAfterLabel: true,
            showRequiredMark: true,
            showOptionalMark: false,
            requiredMark: "*",
            optionalMark: messageLocalization.format("dxForm-optionalMark"),
            requiredMessage: messageLocalization.getFormatter("dxForm-requiredMessage"),
            showValidationSummary: false,
            scrollingEnabled: false,
            stylingMode: config().editorStylingMode,
            labelMode: "outside",
            isDirty: false
        })
    }
    _defaultOptionsRules() {
        return super._defaultOptionsRules().concat([{
            device: () => isMaterialBased(current()),
            options: {
                labelLocation: "top"
            }
        }, {
            device: () => isMaterial(current()),
            options: {
                showColonAfterLabel: false
            }
        }])
    }
    _setOptionsByReference() {
        super._setOptionsByReference();
        extend(this._optionsByReference, {
            formData: true,
            validationGroup: true
        })
    }
    _getGroupColCount($element) {
        return parseInt($element.attr(GROUP_COL_COUNT_ATTR) ?? "1", 10)
    }
    _applyLabelsWidthByCol($container, index) {
        let options = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : {};
        const fieldItemClass = null !== options && void 0 !== options && options.inOneColumn ? FIELD_ITEM_CLASS : FORM_FIELD_ITEM_COL_CLASS + index;
        const cssExcludeTabbedSelector = null !== options && void 0 !== options && options.excludeTabbed ? `:not(.${FIELD_ITEM_TAB_CLASS})` : "";
        setLabelWidthByMaxLabelWidth($container, `.${fieldItemClass}${cssExcludeTabbedSelector}`)
    }
    _applyLabelsWidth($container, excludeTabbed, inOneColumn, colCount) {
        const applyLabelsOptions = {
            excludeTabbed: excludeTabbed,
            inOneColumn: inOneColumn
        };
        const columnsCount = inOneColumn ? 1 : colCount ?? this._getGroupColCount($container);
        for (let i = 0; i < columnsCount; i += 1) {
            this._applyLabelsWidthByCol($container, i, applyLabelsOptions)
        }
    }
    _getGroupElementsInColumn($container, columnIndex, colCount) {
        const cssColCountSelector = isDefined(colCount) ? `.${GROUP_COL_COUNT_CLASS}${colCount}` : "";
        const groupSelector = `.${FORM_FIELD_ITEM_COL_CLASS}${columnIndex} > .${FIELD_ITEM_CONTENT_CLASS} > .${FORM_GROUP_CLASS}${cssColCountSelector}`;
        return $container.find(groupSelector)
    }
    _applyLabelsWidthWithGroups($container, colCount, excludeTabbed) {
        const {
            alignRootItemLabels: alignRootItemLabels
        } = this.option();
        if (true === alignRootItemLabels) {
            const $rootSimpleItems = $container.find(`.${ROOT_SIMPLE_ITEM_CLASS}`);
            for (let colIndex = 0; colIndex < colCount; colIndex += 1) {
                this._applyLabelsWidthByCol($rootSimpleItems, colIndex)
            }
        }
        const alignItemLabelsInAllGroups = this.option("alignItemLabelsInAllGroups");
        if (alignItemLabelsInAllGroups) {
            this._applyLabelsWidthWithNestedGroups($container, colCount, excludeTabbed)
        } else {
            const $groups = this.$element().find(`.${FORM_GROUP_CLASS}`);
            for (let i = 0; i < $groups.length; i += 1) {
                this._applyLabelsWidth($groups.eq(i), excludeTabbed, false, void 0)
            }
        }
    }
    _applyLabelsWidthWithNestedGroups($container, colCount, excludeTabbed) {
        const applyLabelsOptions = {
            excludeTabbed: excludeTabbed
        };
        for (let colIndex = 0; colIndex < colCount; colIndex += 1) {
            const $baseGroups = this._getGroupElementsInColumn($container, colIndex);
            this._applyLabelsWidthByCol($baseGroups, 0, applyLabelsOptions);
            for (let groupsColIndex = 0; groupsColIndex < this._groupsColCount.length; groupsColIndex += 1) {
                const $groupsByCol = this._getGroupElementsInColumn($container, colIndex, this._groupsColCount[groupsColIndex]);
                const groupColCount = this._getGroupColCount($groupsByCol);
                for (let groupColIndex = 1; groupColIndex < groupColCount; groupColIndex += 1) {
                    this._applyLabelsWidthByCol($groupsByCol, groupColIndex, applyLabelsOptions)
                }
            }
        }
    }
    _labelLocation() {
        const {
            labelLocation: labelLocation
        } = this.option();
        return labelLocation
    }
    _alignLabelsInColumn(options) {
        const {
            layoutManager: layoutManager,
            inOneColumn: inOneColumn,
            $container: $container,
            excludeTabbed: excludeTabbed,
            items: items
        } = options;
        if (!hasWindow() || "top" === this._labelLocation()) {
            return
        }
        if (inOneColumn) {
            this._applyLabelsWidth($container, excludeTabbed, true, void 0)
        } else if (this._checkGrouping(items)) {
            this._applyLabelsWidthWithGroups($container, layoutManager._getColCount(), excludeTabbed)
        } else {
            this._applyLabelsWidth($container, excludeTabbed, false, layoutManager._getColCount())
        }
    }
    _prepareFormData() {
        if (!isDefined(this.option("formData"))) {
            this.option("formData", {})
        }
    }
    _setStylingModeClass() {
        const {
            stylingMode: stylingMode
        } = this.option();
        if ("underlined" === stylingMode) {
            this.$element().addClass(FORM_UNDERLINED_CLASS)
        }
    }
    _initMarkup() {
        ValidationEngine.addGroup(this._getValidationGroup(), false);
        this._clearCachedInstances();
        this._prepareFormData();
        this.$element().addClass(FORM_CLASS);
        this._setStylingModeClass();
        super._initMarkup();
        this.setAria("role", "form", this.$element());
        const {
            scrollingEnabled: scrollingEnabled
        } = this.option();
        if (scrollingEnabled) {
            this._renderScrollable()
        }
        this._renderLayout();
        this._renderValidationSummary();
        this._lastMarkupScreenFactor = this._targetScreenFactor || this._getCurrentScreenFactor();
        this._attachResizeObserverSubscription()
    }
    _attachResizeObserverSubscription() {
        if (hasWindow()) {
            const formRootElement = this.$element().get(0);
            resizeObserverSingleton.unobserve(formRootElement);
            resizeObserverSingleton.observe(formRootElement, (() => {
                this._resizeHandler()
            }))
        }
    }
    _resizeHandler() {
        if (this._cachedLayoutManagers.length) {
            each(this._cachedLayoutManagers, ((_, layoutManager) => {
                const {
                    onLayoutChanged: onLayoutChanged
                } = layoutManager.option();
                null === onLayoutChanged || void 0 === onLayoutChanged || onLayoutChanged(layoutManager.isSingleColumnMode())
            }))
        }
    }
    _getCurrentScreenFactor() {
        const {
            screenByWidth: screenByWidth
        } = this.option();
        if (hasWindow()) {
            const currentScreenFactor = getCurrentScreenFactor(screenByWidth);
            return currentScreenFactor
        }
        return "lg"
    }
    _clearCachedInstances() {
        this._itemsRunTimeInfo.clear();
        this._cachedLayoutManagers = []
    }
    _alignLabels(layoutManager, inOneColumn) {
        const {
            items: items
        } = this.option();
        this._alignLabelsInColumn({
            $container: this.$element(),
            layoutManager: layoutManager,
            excludeTabbed: true,
            items: items,
            inOneColumn: inOneColumn
        });
        triggerResizeEvent(this.$element().find(`.${TOOLBAR_CLASS}`))
    }
    _clean() {
        this._clearValidationSummary();
        super._clean();
        this._groupsColCount = [];
        this._cachedColCountOptions = [];
        this._lastMarkupScreenFactor = void 0;
        resizeObserverSingleton.unobserve(this.$element().get(0))
    }
    _renderScrollable() {
        const useNativeScrolling = this.option("useNativeScrolling");
        this._scrollable = new Scrollable(this.$element(), {
            useNative: !!useNativeScrolling,
            useSimulatedScrollbar: !useNativeScrolling,
            useKeyboard: false,
            direction: "both",
            bounceEnabled: false
        })
    }
    _getContent() {
        var _this$_scrollable;
        const {
            scrollingEnabled: scrollingEnabled
        } = this.option();
        return scrollingEnabled ? $(null === (_this$_scrollable = this._scrollable) || void 0 === _this$_scrollable ? void 0 : _this$_scrollable.content()) : this.$element()
    }
    _clearValidationSummary() {
        var _this$_$validationSum;
        null === (_this$_$validationSum = this._$validationSummary) || void 0 === _this$_$validationSum || _this$_$validationSum.remove();
        this._$validationSummary = void 0;
        this._validationSummary = void 0
    }
    _renderValidationSummary() {
        this._clearValidationSummary();
        const {
            showValidationSummary: showValidationSummary
        } = this.option();
        if (showValidationSummary) {
            this._$validationSummary = $("<div>").addClass(FORM_VALIDATION_SUMMARY).appendTo(this._getContent());
            this._validationSummary = super._createComponent(this._$validationSummary, ValidationSummary, {
                validationGroup: this._getValidationGroup()
            })
        }
    }
    _prepareItems(items, parentIsTabbedItem, currentPath, isTabs) {
        if (items) {
            const result = [];
            for (let i = 0; i < items.length; i += 1) {
                let item = items[i];
                const path = concatPaths(currentPath, createItemPathByIndex(i, isTabs));
                const itemRunTimeInfo = {
                    item: item,
                    itemIndex: i,
                    path: path
                };
                const guid = this._itemsRunTimeInfo.add(itemRunTimeInfo);
                if (isString(item)) {
                    item = {
                        dataField: item
                    }
                }
                if (isObject(item)) {
                    const preparedItem = _extends({}, item);
                    itemRunTimeInfo.preparedItem = preparedItem;
                    preparedItem.guid = guid;
                    this._tryPrepareGroupItemCaption(preparedItem);
                    this._tryPrepareGroupItem(preparedItem);
                    this._tryPrepareTabbedItem(preparedItem, path);
                    this._tryPrepareItemTemplate(preparedItem);
                    if (parentIsTabbedItem) {
                        preparedItem.cssItemClass = FIELD_ITEM_TAB_CLASS
                    }
                    if (preparedItem.items) {
                        preparedItem.items = this._prepareItems(preparedItem.items, parentIsTabbedItem, path)
                    }
                    result.push(preparedItem)
                } else {
                    result.push(item)
                }
            }
            return result
        }
        return items
    }
    _isGroupItem(item) {
        return "group" === item.itemType
    }
    _tryPrepareGroupItemCaption(item) {
        if (this._isGroupItem(item)) {
            item._prepareGroupCaptionTemplate = captionTemplate => {
                if (item.captionTemplate) {
                    item.groupCaptionTemplate = this._getTemplate(captionTemplate)
                }
                item.captionTemplate = this._itemGroupTemplate.bind(this, item)
            };
            item._prepareGroupCaptionTemplate(item.captionTemplate)
        }
    }
    _tryPrepareGroupItem(item) {
        if (this._isGroupItem(item)) {
            item.alignItemLabels = ensureDefined(item.alignItemLabels, true);
            item._prepareGroupItemTemplate = itemTemplate => {
                if (item.template) {
                    item.groupContentTemplate = this._getTemplate(itemTemplate)
                }
                item.template = this._itemGroupTemplate.bind(this, item)
            };
            item._prepareGroupItemTemplate(item.template)
        }
    }
    _isTabbedItem(item) {
        return "tabbed" === item.itemType
    }
    _tryPrepareTabbedItem(item, path) {
        if (this._isTabbedItem(item)) {
            item.template = this._itemTabbedTemplate.bind(this, item);
            item.tabs = this._prepareItems(item.tabs, true, path, true)
        }
    }
    _tryPrepareItemTemplate(item) {
        if (item.template) {
            item.template = this._getTemplate(item.template)
        }
    }
    _checkGrouping(items) {
        if (items) {
            for (let i = 0; i < items.length; i += 1) {
                const item = items[i];
                if ("group" === item.itemType) {
                    return true
                }
            }
        }
        return false
    }
    _renderLayout() {
        const {
            items: items
        } = this.option();
        const $content = this._getContent();
        const preparedItems = this._prepareItems(items);
        const {
            colCount: colCount,
            alignItemLabels: alignItemLabels,
            screenByWidth: screenByWidth,
            colCountByScreen: colCountByScreen
        } = this.option();
        this._rootLayoutManager = this._renderLayoutManager($content, this._createLayoutManagerOptions(preparedItems, {
            isRoot: true,
            colCount: colCount,
            alignItemLabels: alignItemLabels,
            screenByWidth: screenByWidth,
            colCountByScreen: colCountByScreen,
            onLayoutChanged: inOneColumn => {
                this._alignLabels.bind(this)(this._rootLayoutManager, inOneColumn)
            },
            onContentReady: e => {
                this._alignLabels(e.component, e.component.isSingleColumnMode())
            }
        }))
    }
    _tryGetItemsForTemplate(item) {
        return item.items ?? []
    }
    _itemTabbedTemplate(tabbedItem, data, $itemContainer) {
        const $tabPanel = $("<div>").appendTo($itemContainer);
        const tabPanelOptions = _extends({}, tabbedItem.tabPanelOptions, {
            dataSource: tabbedItem.tabs,
            onItemRendered: args => {
                var _tabbedItem$tabPanelO, _tabbedItem$tabPanelO2;
                null === (_tabbedItem$tabPanelO = tabbedItem.tabPanelOptions) || void 0 === _tabbedItem$tabPanelO || null === (_tabbedItem$tabPanelO2 = _tabbedItem$tabPanelO.onItemRendered) || void 0 === _tabbedItem$tabPanelO2 || _tabbedItem$tabPanelO2.call(_tabbedItem$tabPanelO, args);
                triggerShownEvent(args.itemElement)
            },
            itemTemplate: (itemData, e, container) => {
                const {
                    screenByWidth: screenByWidth
                } = this.option();
                const $container = $(container);
                const alignItemLabels = ensureDefined(itemData.alignItemLabels, true);
                const layoutManager = this._renderLayoutManager($container, this._createLayoutManagerOptions(this._tryGetItemsForTemplate(itemData), {
                    colCount: itemData.colCount,
                    alignItemLabels: alignItemLabels,
                    screenByWidth: screenByWidth,
                    colCountByScreen: itemData.colCountByScreen,
                    cssItemClass: itemData.cssItemClass,
                    onLayoutChanged: inOneColumn => {
                        this._alignLabelsInColumn({
                            $container: $(container),
                            layoutManager: layoutManager,
                            items: itemData.items,
                            inOneColumn: inOneColumn,
                            excludeTabbed: false
                        })
                    }
                }));
                if (this._itemsRunTimeInfo) {
                    this._itemsRunTimeInfo.extendRunTimeItemInfoByKey(itemData.guid ?? "", {
                        layoutManager: layoutManager
                    })
                }
                if (alignItemLabels) {
                    this._alignLabelsInColumn({
                        $container: $container,
                        layoutManager: layoutManager,
                        items: itemData.items,
                        inOneColumn: layoutManager.isSingleColumnMode(),
                        excludeTabbed: false
                    })
                }
            }
        });
        const tryUpdateTabPanelInstance = (items, instance) => {
            if (Array.isArray(items)) {
                items.forEach((item => this._itemsRunTimeInfo.extendRunTimeItemInfoByKey(item.guid ?? "", {
                    widgetInstance: instance
                })))
            }
        };
        const tabPanel = this._createComponent($tabPanel, TabPanel, tabPanelOptions);
        $($itemContainer).parent().addClass(FIELD_ITEM_CONTENT_HAS_TABS_CLASS);
        tabPanel.on("optionChanged", (eventArgs => {
            const {
                fullName: fullName,
                value: value,
                component: component
            } = eventArgs;
            if ("dataSource" === fullName) {
                tryUpdateTabPanelInstance(value, component)
            }
        }));
        tryUpdateTabPanelInstance([{
            guid: tabbedItem.guid
        }, ...tabbedItem.tabs ?? []], tabPanel)
    }
    _itemGroupCaptionTemplate(item, $group, id) {
        if (item.groupCaptionTemplate) {
            const $captionTemplate = $("<div>").addClass(FORM_GROUP_CUSTOM_CAPTION_CLASS).attr("id", id).appendTo($group);
            item._renderGroupCaptionTemplate = () => {
                var _item$groupCaptionTem;
                const data = {
                    component: this,
                    caption: item.caption,
                    name: item.name
                };
                null === (_item$groupCaptionTem = item.groupCaptionTemplate) || void 0 === _item$groupCaptionTem || _item$groupCaptionTem.render({
                    model: data,
                    container: getPublicElement($captionTemplate)
                })
            };
            item._renderGroupCaptionTemplate();
            return
        }
        if (item.caption) {
            $("<span>").addClass(FORM_GROUP_CAPTION_CLASS).text(item.caption).attr("id", id).appendTo($group)
        }
    }
    _itemGroupContentTemplate(item, $group) {
        const $groupContent = $("<div>").addClass(FORM_GROUP_CONTENT_CLASS).appendTo($group);
        if (item.groupContentTemplate) {
            item._renderGroupContentTemplate = () => {
                var _item$groupContentTem;
                $groupContent.empty();
                const data = {
                    formData: this.option("formData"),
                    component: this
                };
                null === (_item$groupContentTem = item.groupContentTemplate) || void 0 === _item$groupContentTem || _item$groupContentTem.render({
                    model: data,
                    container: getPublicElement($groupContent)
                })
            };
            item._renderGroupContentTemplate()
        } else {
            var _this$_itemsRunTimeIn;
            const layoutManager = this._renderLayoutManager($groupContent, this._createLayoutManagerOptions(this._tryGetItemsForTemplate(item), {
                colCount: item.colCount,
                colCountByScreen: item.colCountByScreen,
                alignItemLabels: item.alignItemLabels,
                cssItemClass: item.cssItemClass
            }));
            null === (_this$_itemsRunTimeIn = this._itemsRunTimeInfo) || void 0 === _this$_itemsRunTimeIn || _this$_itemsRunTimeIn.extendRunTimeItemInfoByKey(item.guid ?? "", {
                layoutManager: layoutManager
            });
            const colCount = layoutManager._getColCount();
            if (!this._groupsColCount.includes(colCount)) {
                this._groupsColCount.push(colCount)
            }
            $group.addClass(GROUP_COL_COUNT_CLASS + colCount);
            $group.attr(GROUP_COL_COUNT_ATTR, colCount)
        }
    }
    _itemGroupTemplate(item, options, $container) {
        var _item$caption;
        const {
            id: id
        } = options.editorOptions.inputAttr;
        const $group = $("<div>").toggleClass(FORM_GROUP_WITH_CAPTION_CLASS, !!(null !== (_item$caption = item.caption) && void 0 !== _item$caption && _item$caption.length)).addClass(FORM_GROUP_CLASS).appendTo($container);
        const groupAria = {
            role: "group",
            labelledby: id
        };
        this.setAria(groupAria, $group);
        $($container).parent().addClass(FIELD_ITEM_CONTENT_HAS_GROUP_CLASS);
        this._itemGroupCaptionTemplate(item, $group, id);
        this._itemGroupContentTemplate(item, $group)
    }
    _createLayoutManagerOptions(items, extendedLayoutManagerOptions) {
        return convertToLayoutManagerOptions({
            form: this,
            formOptions: this.option(),
            $formElement: this.$element(),
            items: items,
            validationGroup: this._getValidationGroup(),
            extendedLayoutManagerOptions: extendedLayoutManagerOptions,
            onFieldDataChanged: args => {
                if (!this._isDataUpdating) {
                    this._triggerOnFieldDataChanged(args)
                }
            },
            onContentReady: args => {
                var _extendedLayoutManage;
                this._itemsRunTimeInfo.addItemsOrExtendFrom(args.component._itemsRunTimeInfo);
                null === (_extendedLayoutManage = extendedLayoutManagerOptions.onContentReady) || void 0 === _extendedLayoutManage || _extendedLayoutManage.call(extendedLayoutManagerOptions, args)
            },
            onDisposing: e => {
                const {
                    component: component
                } = e;
                const nestedItemsRunTimeInfo = component.getItemsRunTimeInfo();
                this._itemsRunTimeInfo.removeItemsByItems(nestedItemsRunTimeInfo)
            },
            onFieldItemRendered: () => {
                var _this$_validationSumm;
                null === (_this$_validationSumm = this._validationSummary) || void 0 === _this$_validationSumm || _this$_validationSumm.refreshValidationGroup()
            }
        })
    }
    _renderLayoutManager($parent, layoutManagerOptions) {
        const baseColCountByScreen = {
            lg: layoutManagerOptions.colCount,
            md: layoutManagerOptions.colCount,
            sm: layoutManagerOptions.colCount,
            xs: 1
        };
        this._cachedColCountOptions.push({
            colCountByScreen: extend(baseColCountByScreen, layoutManagerOptions.colCountByScreen)
        });
        const $element = $("<div>");
        $element.appendTo($parent);
        const instance = this._createComponent($element, LayoutManager, layoutManagerOptions);
        instance.on("autoColCountChanged", (() => {
            this._clearAutoColCountChangedTimeout();
            this.autoColCountChangedTimeoutId = setTimeout((() => !this._disposed && this._refresh()), 0)
        }));
        this._cachedLayoutManagers.push(instance);
        return instance
    }
    _getValidationGroup() {
        const {
            validationGroup: validationGroup
        } = this.option();
        return validationGroup ?? this
    }
    _createComponent(element, component, componentConfiguration) {
        const {
            readOnly: readOnly
        } = this.option();
        this._extendConfig(componentConfiguration, {
            readOnly: readOnly
        });
        return super._createComponent(element, component, componentConfiguration)
    }
    _attachSyncSubscriptions() {
        this.on("optionChanged", (args => {
            const {
                fullName: fullName,
                name: name
            } = args;
            if ("formData" === fullName) {
                if (!isDefined(args.value)) {
                    this._options.silent("formData", args.value = {})
                }
                this._triggerOnFieldDataChangedByDataSet(args.value)
            }
            if (this._cachedLayoutManagers.length) {
                each(this._cachedLayoutManagers, ((_index, layoutManager) => {
                    if ("formData" === fullName) {
                        this._isDataUpdating = true;
                        layoutManager.option("layoutData", args.value);
                        this._isDataUpdating = false
                    }
                    if ("readOnly" === name || "disabled" === name) {
                        layoutManager.option(fullName, args.value)
                    }
                }))
            }
        }))
    }
    _optionChanged(args) {
        const {
            fullName: fullName
        } = args;
        const splitFullName = fullName.split(".");
        if (splitFullName.length > 1 && -1 !== splitFullName[0].search("items") && this._itemsOptionChangedHandler(args)) {
            return
        }
        if (splitFullName.length > 1 && -1 !== splitFullName[0].search("formData") && this._formDataOptionChangedHandler(args)) {
            return
        }
        this._defaultOptionChangedHandler(args)
    }
    _defaultOptionChangedHandler(args) {
        switch (args.name) {
            case "formData":
                if (!this.option("items")) {
                    this._invalidate()
                } else if (isEmptyObject(args.value)) {
                    this._clear()
                }
                break;
            case "onFieldDataChanged":
            case "alignRootItemLabels":
            case "readOnly":
            case "isDirty":
                break;
            case "items":
            case "colCount":
            case "onEditorEnterKey":
            case "labelLocation":
            case "labelMode":
            case "alignItemLabels":
            case "showColonAfterLabel":
            case "customizeItem":
            case "alignItemLabelsInAllGroups":
            case "showRequiredMark":
            case "showOptionalMark":
            case "requiredMark":
            case "optionalMark":
            case "requiredMessage":
            case "scrollingEnabled":
            case "formID":
            case "colCountByScreen":
            case "screenByWidth":
            case "stylingMode":
                this._invalidate();
                break;
            case "showValidationSummary":
                this._renderValidationSummary();
                break;
            case "minColWidth": {
                const {
                    colCount: colCount
                } = this.option();
                if ("auto" === colCount) {
                    this._invalidate()
                }
                break
            }
            case "width":
                super._optionChanged(args);
                this._rootLayoutManager.option(args.name, args.value);
                this._alignLabels(this._rootLayoutManager, this._rootLayoutManager.isSingleColumnMode());
                break;
            case "validationGroup":
                ValidationEngine.removeGroup(args.previousValue || this);
                this._invalidate();
                break;
            default:
                super._optionChanged(args)
        }
    }
    _itemsOptionChangedHandler(args) {
        const {
            value: value,
            fullName: fullName
        } = args;
        const nameParts = fullName.split(".");
        const itemPath = this._getItemPath(nameParts);
        const item = this.option(itemPath);
        const optionNameWithoutPath = fullName.replace(`${itemPath}.`, "");
        const simpleOptionName = optionNameWithoutPath.split(".")[0].replace(/\[\d+]/, "");
        const itemAction = this._tryCreateItemOptionAction(simpleOptionName, item, item[simpleOptionName], args.previousValue, itemPath);
        let result = this._tryExecuteItemOptionAction(itemAction) ?? this._tryChangeLayoutManagerItemOption(fullName, value);
        if (!result && item) {
            this._changeItemOption(item, optionNameWithoutPath, value);
            const {
                items: items
            } = this.option();
            const generatedItems = this._generateItemsFromData(items);
            this.option("items", generatedItems);
            result = true
        }
        return result
    }
    _formDataOptionChangedHandler(args) {
        const nameParts = args.fullName.split(".");
        const {
            value: value
        } = args;
        const dataField = nameParts.slice(1).join(".");
        const editor = this.getEditor(dataField);
        if (editor) {
            editor.option("value", value)
        } else {
            this._triggerOnFieldDataChanged({
                dataField: dataField,
                value: value
            })
        }
        return true
    }
    _tryCreateItemOptionAction(optionName, item, value, previousValue, itemPath) {
        let currentValue = value;
        if ("tabs" === optionName) {
            this._itemsRunTimeInfo.removeItemsByPathStartWith(`${itemPath}.tabs`);
            currentValue = this._prepareItems(currentValue, true, itemPath, true)
        }
        return tryCreateItemOptionAction(optionName, {
            item: item,
            value: currentValue,
            previousValue: previousValue,
            itemsRunTimeInfo: this._itemsRunTimeInfo
        })
    }
    _tryExecuteItemOptionAction(action) {
        return null === action || void 0 === action ? void 0 : action.tryExecute()
    }
    _updateValidationGroupAndSummaryIfNeeded(fullName) {
        const optionName = getOptionNameFromFullName(fullName);
        if (ITEM_OPTIONS_FOR_VALIDATION_UPDATING.includes(optionName)) {
            ValidationEngine.addGroup(this._getValidationGroup(), false);
            if (this.option("showValidationSummary")) {
                var _this$_validationSumm2;
                null === (_this$_validationSumm2 = this._validationSummary) || void 0 === _this$_validationSumm2 || _this$_validationSumm2.refreshValidationGroup()
            }
        }
    }
    _setLayoutManagerItemOption(layoutManager, optionName, value, path) {
        if (this._updateLockCount > 0) {
            if (!layoutManager._updateLockCount) {
                layoutManager.beginUpdate()
            }
            const key = this._itemsRunTimeInfo.findKeyByPath(path);
            this.postponedOperations.add(key, (() => {
                if (!layoutManager._disposed) {
                    layoutManager.endUpdate()
                }
                return Deferred().resolve()
            }))
        }
        const contentReadyHandler = e => {
            e.component.off("contentReady", contentReadyHandler);
            if (isFullPathContainsTabs(path)) {
                const tabPath = tryGetTabPath(path);
                const tabLayoutManager = this._itemsRunTimeInfo.findGroupOrTabLayoutManagerByPath(tabPath);
                if (tabLayoutManager) {
                    const {
                        items: items
                    } = tabLayoutManager.option();
                    this._alignLabelsInColumn({
                        items: items,
                        layoutManager: tabLayoutManager,
                        $container: tabLayoutManager.$element(),
                        inOneColumn: tabLayoutManager.isSingleColumnMode(),
                        excludeTabbed: false
                    })
                }
            } else {
                this._alignLabels(this._rootLayoutManager, this._rootLayoutManager.isSingleColumnMode())
            }
        };
        layoutManager.on("contentReady", contentReadyHandler);
        layoutManager.option(optionName, value);
        this._updateValidationGroupAndSummaryIfNeeded(optionName)
    }
    _tryChangeLayoutManagerItemOption(fullName, value) {
        const nameParts = fullName.split(".");
        const optionName = getOptionNameFromFullName(fullName);
        if ("items" === optionName && nameParts.length > 1) {
            const itemPath = this._getItemPath(nameParts);
            const layoutManager = this._itemsRunTimeInfo.findGroupOrTabLayoutManagerByPath(itemPath);
            if (layoutManager) {
                this._itemsRunTimeInfo.removeItemsByItems(layoutManager.getItemsRunTimeInfo());
                const items = this._prepareItems(value, false, itemPath);
                this._setLayoutManagerItemOption(layoutManager, optionName, items, itemPath);
                return true
            }
        } else if (nameParts.length > 2) {
            const endPartIndex = nameParts.length - 2;
            const itemPath = this._getItemPath(nameParts.slice(0, endPartIndex));
            const layoutManager = this._itemsRunTimeInfo.findGroupOrTabLayoutManagerByPath(itemPath);
            if (layoutManager) {
                const fullOptionName = getFullOptionName(nameParts[endPartIndex], optionName);
                if ("editorType" === optionName) {
                    if (layoutManager.option(fullOptionName) !== value) {
                        return false
                    }
                }
                if ("visible" === optionName) {
                    const formItems = this.option(getFullOptionName(itemPath, "items"));
                    if (null !== formItems && void 0 !== formItems && formItems.length) {
                        const {
                            items: layoutManagerItems
                        } = layoutManager.option();
                        formItems.forEach(((item, index) => {
                            const layoutItem = layoutManagerItems[index];
                            layoutItem.visibleIndex = item.visibleIndex
                        }))
                    }
                }
                this._setLayoutManagerItemOption(layoutManager, fullOptionName, value, itemPath);
                return true
            }
        }
        return false
    }
    _tryChangeLayoutManagerItemOptions(itemPath, options) {
        let result = false;
        this.beginUpdate();
        each(options, ((optionName, optionValue) => {
            result = this._tryChangeLayoutManagerItemOption(getFullOptionName(itemPath, optionName), optionValue);
            if (!result) {
                return false
            }
            return true
        }));
        this.endUpdate();
        return result
    }
    _getItemPath(nameParts) {
        let itemPath = nameParts[0];
        for (let i = 1; i < nameParts.length; i += 1) {
            if (-1 !== nameParts[i].search(/items\[\d+]|tabs\[\d+]/)) {
                itemPath += `.${nameParts[i]}`
            } else {
                break
            }
        }
        return itemPath
    }
    _triggerOnFieldDataChanged(args) {
        this._updateIsDirty(args.dataField ?? "");
        this._createActionByOption("onFieldDataChanged")(args)
    }
    _triggerOnFieldDataChangedByDataSet(data) {
        if (data && isObject(data)) {
            Object.keys(data).forEach((key => {
                this._triggerOnFieldDataChanged({
                    dataField: key,
                    value: data[key]
                })
            }))
        }
    }
    _updateFieldValue(dataField, value) {
        const {
            formData: formData
        } = this.option();
        if (isDefined(formData)) {
            const editor = this.getEditor(dataField);
            this.option(`formData.${dataField}`, value);
            if (editor) {
                const editorValue = editor.option("value");
                if (editorValue !== value) {
                    editor.option("value", value)
                }
            }
        }
    }
    _generateItemsFromData(items) {
        const {
            formData: formData
        } = this.option();
        const result = [];
        if (!items && isDefined(formData)) {
            each(formData, (dataField => {
                result.push({
                    dataField: dataField
                })
            }))
        }
        if (items) {
            each(items, ((_index, item) => {
                if (isObject(item)) {
                    result.push(item)
                } else {
                    result.push({
                        dataField: item
                    })
                }
            }))
        }
        return result
    }
    _getItemByField(field, items) {
        const fieldParts = isObject(field) ? field : this._getFieldParts(field);
        const {
            fieldName: fieldName
        } = fieldParts;
        const {
            fieldPath: fieldPath
        } = fieldParts;
        let resultItem = false;
        if (items.length) {
            each(items, ((_index, item) => {
                const {
                    itemType: itemType
                } = item;
                if (fieldPath.length) {
                    const path = fieldPath.slice();
                    item = this._getItemByFieldPath(path, fieldName, item)
                } else if (this._isGroupItem(item) && !(item.caption || item.name) || "tabbed" === itemType && !item.name) {
                    const subItemsField = this._getSubItemField(itemType);
                    item.items = this._generateItemsFromData(item.items);
                    item = this._getItemByField({
                        fieldName: fieldName,
                        fieldPath: fieldPath
                    }, item[subItemsField])
                }
                if (isEqualToDataFieldOrNameOrTitleOrCaption(item, fieldName)) {
                    resultItem = item;
                    return false
                }
                return true
            }))
        }
        return resultItem
    }
    _getFieldParts(field) {
        let fieldName = field;
        let separatorIndex = fieldName.indexOf(".");
        const resultPath = [];
        while (-1 !== separatorIndex) {
            resultPath.push(fieldName.substr(0, separatorIndex));
            fieldName = fieldName.substr(separatorIndex + 1);
            separatorIndex = fieldName.indexOf(".")
        }
        return {
            fieldName: fieldName,
            fieldPath: resultPath.reverse()
        }
    }
    _getItemByFieldPath(path, fieldName, item) {
        const {
            itemType: itemType
        } = item;
        const subItemsField = this._getSubItemField(itemType);
        const isItemWithSubItems = "group" === itemType || "tabbed" === itemType || item.title;
        let result = false;
        do {
            if (isItemWithSubItems) {
                const name = item.name || item.caption || item.title;
                const isGroupWithName = isDefined(name);
                const nameWithoutSpaces = getTextWithoutSpaces(name);
                let pathNode = "";
                item[subItemsField] = this._generateItemsFromData(item[subItemsField]);
                if (isGroupWithName) {
                    pathNode = path.pop()
                }
                if (!path.length) {
                    result = this._getItemByField(fieldName, item[subItemsField]);
                    if (result) {
                        break
                    }
                }
                if (!isGroupWithName || isGroupWithName && nameWithoutSpaces === pathNode) {
                    if (path.length) {
                        result = this._searchItemInEverySubItem(path, fieldName, item[subItemsField])
                    }
                }
            } else {
                break
            }
        } while (path.length && !isDefined(result));
        return result
    }
    _getSubItemField(itemType) {
        return "tabbed" === itemType ? "tabs" : "items"
    }
    _searchItemInEverySubItem(path, fieldName, items) {
        let result = false;
        each(items, ((_index, groupItem) => {
            result = this._getItemByFieldPath(path.slice(), fieldName, groupItem);
            if (result) {
                return false
            }
            return true
        }));
        if (!result) {
            return false
        }
        return result
    }
    _changeItemOption(item, option, value) {
        if (isObject(item)) {
            item[option] = value
        }
    }
    _dimensionChanged() {
        const currentScreenFactor = this._getCurrentScreenFactor();
        if (this._lastMarkupScreenFactor !== currentScreenFactor) {
            if (this._isColCountChanged(this._lastMarkupScreenFactor, currentScreenFactor)) {
                this._targetScreenFactor = currentScreenFactor;
                this._refresh();
                this._targetScreenFactor = void 0
            }
            this._lastMarkupScreenFactor = currentScreenFactor
        }
    }
    _isColCountChanged(oldScreenSize, newScreenSize) {
        let isChanged = false;
        each(this._cachedColCountOptions, ((_index, item) => {
            if (item.colCountByScreen[oldScreenSize] !== item.colCountByScreen[newScreenSize]) {
                isChanged = true;
                return false
            }
            return true
        }));
        return isChanged
    }
    _refresh() {
        const editorSelector = `.${TEXTEDITOR_CLASS}.${FOCUSED_STATE_CLASS}:not(.${DROP_DOWN_EDITOR_CLASS}) .${TEXTEDITOR_INPUT_CLASS}`;
        eventsEngine.trigger(this.$element().find(editorSelector), "change");
        super._refresh()
    }
    _updateIsDirty(dataField) {
        const editor = this.getEditor(dataField);
        if (!editor) {
            return
        }
        if (editor.option("isDirty")) {
            this._dirtyFields.add(dataField)
        } else {
            this._dirtyFields.delete(dataField)
        }
        this.option("isDirty", !!this._dirtyFields.size)
    }
    updateRunTimeInfoForEachEditor(editorAction) {
        this._itemsRunTimeInfo.each(((_, itemRunTimeInfo) => {
            const {
                widgetInstance: widgetInstance
            } = itemRunTimeInfo;
            if (isDefined(widgetInstance) && Editor.isEditor(widgetInstance)) {
                editorAction(widgetInstance)
            }
        }))
    }
    _clear() {
        this.updateRunTimeInfoForEachEditor((editor => {
            editor.clear();
            editor.option("isValid", true)
        }));
        ValidationEngine.resetGroup(this._getValidationGroup())
    }
    _updateData(data, value, isComplexData) {
        const _data = isComplexData ? value : data;
        if (isObject(_data)) {
            each(_data, ((dataField, fieldValue) => {
                this._updateData(isComplexData ? `${data}.${dataField}` : dataField, fieldValue, isObject(fieldValue))
            }))
        } else if (isString(data)) {
            this._updateFieldValue(data, value)
        }
    }
    registerKeyHandler(key, handler) {
        super.registerKeyHandler(key, handler);
        this._itemsRunTimeInfo.each(((_, itemRunTimeInfo) => {
            if (isDefined(itemRunTimeInfo.widgetInstance)) {
                itemRunTimeInfo.widgetInstance.registerKeyHandler(key, handler)
            }
        }))
    }
    _focusTarget() {
        return this.$element().find(`.${FIELD_ITEM_CONTENT_CLASS} [tabindex]`).first()
    }
    _visibilityChanged() {
        this._alignLabels(this._rootLayoutManager, this._rootLayoutManager.isSingleColumnMode())
    }
    _clearAutoColCountChangedTimeout() {
        if (this.autoColCountChangedTimeoutId) {
            clearTimeout(this.autoColCountChangedTimeoutId);
            this.autoColCountChangedTimeoutId = void 0
        }
    }
    _dispose() {
        this._clearAutoColCountChangedTimeout();
        ValidationEngine.removeGroup(this._getValidationGroup());
        super._dispose()
    }
    clear() {
        this._clear()
    }
    resetValues() {
        this._clear()
    }
    reset(editorsData) {
        this.updateRunTimeInfoForEachEditor((editor => {
            const {
                name: name = ""
            } = editor.option();
            if (editorsData && name in editorsData) {
                editor.reset(editorsData[name]);
                this._updateIsDirty(name)
            } else {
                editor.reset()
            }
        }));
        this._renderValidationSummary()
    }
    updateData(data, value) {
        this._updateData(data, value)
    }
    getEditor(dataField) {
        return this._itemsRunTimeInfo.findWidgetInstanceByDataField(dataField) ?? this._itemsRunTimeInfo.findWidgetInstanceByName(dataField)
    }
    getButton(name) {
        return this._itemsRunTimeInfo.findWidgetInstanceByName(name)
    }
    updateDimensions() {
        const deferred = Deferred();
        if (this._scrollable) {
            this._scrollable.update().done((() => {
                deferred.resolveWith(this)
            }))
        } else {
            deferred.resolveWith(this)
        }
        return deferred.promise()
    }
    itemOption(id, option, value) {
        const {
            items: items
        } = this.option();
        const generatedItems = this._generateItemsFromData(items);
        const item = this._getItemByField(id, generatedItems);
        const path = getItemPath(generatedItems, item);
        if (!item) {
            return
        }
        if (1 === arguments.length) {
            return item
        }
        switch (arguments.length) {
            case 3: {
                const itemAction = this._tryCreateItemOptionAction(option, item, value, item[option ?? ""], path);
                this._changeItemOption(item, option ?? "", value);
                const fullName = getFullOptionName(path, option);
                if (!this._tryExecuteItemOptionAction(itemAction) && !this._tryChangeLayoutManagerItemOption(fullName, value)) {
                    this.option("items", generatedItems)
                }
                break
            }
            default:
                if (isObject(option)) {
                    if (!this._tryChangeLayoutManagerItemOptions(path, option)) {
                        let allowUpdateItems = false;
                        each(option, ((optionName, optionValue) => {
                            const itemAction = this._tryCreateItemOptionAction(optionName, item, optionValue, item[optionName], path);
                            this._changeItemOption(item, optionName, optionValue);
                            if (!allowUpdateItems && !this._tryExecuteItemOptionAction(itemAction)) {
                                allowUpdateItems = true
                            }
                        }));
                        if (allowUpdateItems) {
                            this.option("items", generatedItems)
                        }
                    }
                }
        }
        return
    }
    validate() {
        return ValidationEngine.validateGroup(this._getValidationGroup())
    }
    getItemID(name) {
        const {
            formID: formID
        } = this.option();
        return `dx_${formID}_${name||new Guid}`
    }
    getTargetScreenFactor() {
        return this._targetScreenFactor
    }
}
registerComponent("dxForm", Form);
export default Form;