UNPKG

@eclipse-scout/core

Version:
870 lines (757 loc) 28.8 kB
/* * Copyright (c) 2010, 2025 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ import { AbstractLayout, Action, aria, arrays, Button, ButtonAdapterMenu, CloneOptions, CompositeField, EnumObject, fields, Form, FormField, FormFieldStatusPosition, GroupBoxEventMap, GroupBoxGridConfig, GroupBoxLayout, GroupBoxMenuItemsOrder, GroupBoxModel, GroupBoxResponsiveHandler, HAlign, HtmlComponent, InitModelOf, KeyStrokeRenderingHints, LogicalGrid, LogicalGridData, LogicalGridLayout, LogicalGridLayoutConfig, Menu, MenuBar, MenuBarEllipsisPosition, Notification, ObjectIdProvider, ObjectOrChildModel, ObjectOrModel, ResponsiveManager, scout, SplitBox, strings, TabBox, TabItemKeyStroke, tooltips, VerticalSmartGrid, WrappedFormField } from '../../../index'; import $ from 'jquery'; export class GroupBox extends CompositeField implements GroupBoxModel { declare model: GroupBoxModel; declare eventMap: GroupBoxEventMap; declare self: GroupBox; fields: FormField[]; menuBarVisible: boolean; menuBarPosition: GroupBoxMenuBarPosition; menuBarEllipsisPosition: MenuBarEllipsisPosition; notification: Notification; bodyLayoutConfig: LogicalGridLayoutConfig; borderDecoration: GroupBoxBorderDecoration; borderVisible: boolean; mainBox: boolean; menuBar: MenuBar; subLabel: string; scrollable: boolean; expandable: boolean; expanded: boolean; gridColumnCount: number; controls: FormField[]; systemButtons: Button[]; customButtons: Button[]; processButtons: Button[]; processMenus: Menu[]; staticMenus: Menu[]; responsive: boolean; responsiveHandler?: GroupBoxResponsiveHandler; htmlBody: HtmlComponent; $header: JQuery; $body: JQuery; $title: JQuery; $borderBottom: JQuery; $subLabel: JQuery; protected _statusPositionOrig: FormFieldStatusPosition; protected _collapsedWeightY: number; constructor() { super(); this._addWidgetProperties(['fields', 'notification', 'staticMenus']); this._addCloneProperties(['menuBarVisible', 'bodyLayoutConfig', 'borderDecoration', 'borderVisible', 'expandable', 'expanded', 'gridColumnCount', 'scrollable', 'subLabel']); this.fields = []; this.menuBarVisible = true; this.menuBarPosition = GroupBox.MenuBarPosition.AUTO; this.menuBarEllipsisPosition = MenuBar.EllipsisPosition.RIGHT; this.notification = null; this.bodyLayoutConfig = null; this.borderDecoration = GroupBox.BorderDecoration.AUTO; this.borderVisible = true; this.mainBox = false; this.scrollable = null; // set to null to enable conditional default -> it will be set to true if it is a MainBox unless it was explicitly set to false this.expandable = false; this.expanded = true; this.logicalGrid = scout.create(VerticalSmartGrid); this.gridColumnCount = 2; this.gridDataHints.useUiHeight = true; this.gridDataHints.w = FormField.FULL_WIDTH; this.controls = []; this.systemButtons = []; this.customButtons = []; this.processButtons = []; this.processMenus = []; this.staticMenus = []; this.responsive = null; this.$header = null; this.$body = null; this.$title = null; this.$subLabel = null; this._statusPositionOrig = null; } static BorderDecoration = { /** * Makes {@link borderVisible} to be computed automatically which means top and bottom paddings are invisible if it is a {@link mainBox}. * @see _computeBorderVisible */ AUTO: 'auto', /** * The top and bottom paddings are always visible, even if it is a {@link mainBox}. */ EMPTY: 'empty', /** * Currently has no effect. */ LINE: 'line' } as const; static MenuBarPosition = { AUTO: 'auto', TOP: 'top', BOTTOM: 'bottom', TITLE: 'title' } as const; protected override _init(model: InitModelOf<this>) { super._init(model); this.resolveConsts([{ property: 'menuBarPosition', constType: GroupBox.MenuBarPosition }]); this.resolveTextKeys(['subLabel']); this._setBodyLayoutConfig(this.bodyLayoutConfig); this.menuBar = scout.create(MenuBar, { parent: this, menuOrder: new GroupBoxMenuItemsOrder(), ellipsisPosition: this.menuBarEllipsisPosition }); this.menuBar.on('propertyChange:visible', () => this._updateMenuBarStyle()); this._setFields(this.fields); this._setMainBox(this.mainBox); this._updateMenuBar(); ResponsiveManager.get().registerHandler(this, scout.create(GroupBoxResponsiveHandler, { widget: this })); this._setResponsive(this.responsive); } protected override _destroy() { ResponsiveManager.get().unregisterHandler(this); super._destroy(); } getFields(): FormField[] { return this.fields; } insertField(field: ObjectOrChildModel<FormField>, index?: number) { let newFields = this.fields.slice() as ObjectOrChildModel<FormField>[]; index = scout.nvl(index, this.fields.length); newFields.splice(index, 0, field); this.setFields(newFields); } insertFieldBefore(field: ObjectOrChildModel<FormField>, sibling: FormField) { scout.assertParameter('sibling', sibling); let index = this.fields.indexOf(sibling); this.insertField(field, index); } insertFieldAfter(field: ObjectOrChildModel<FormField>, sibling: FormField) { scout.assertParameter('sibling', sibling); let index = this.fields.indexOf(sibling) + 1; this.insertField(field, index); } deleteField(field: FormField) { let index = this.fields.indexOf(field); if (index < 0) { return; } let newFields = this.fields.slice(); newFields.splice(index, 1); this.setFields(newFields); } /** @see GroupBoxModel.fields */ setFields(fields: ObjectOrChildModel<FormField>[]) { this.setProperty('fields', fields); } protected _setFields(fields: FormField[]) { this._setProperty('fields', fields); this._prepareFields(); } protected _renderFields(fields: FormField[]) { this._renderExpanded(); this.invalidateLogicalGrid(true); } protected override _initKeyStrokeContext() { super._initKeyStrokeContext(); this.keyStrokeContext.invokeAcceptInputOnActiveValueField = true; this.keyStrokeContext.$bindTarget = this._keyStrokeBindTarget.bind(this); } protected override _setKeyStrokes(keyStrokes: Action | Action[]) { keyStrokes = arrays.ensure(keyStrokes); let groupBoxRenderingHints: KeyStrokeRenderingHints = { render: () => true, offset: 0, hAlign: HAlign.RIGHT, $drawingArea: ($drawingArea, event) => { if (this.$header && this.$header.isVisible()) { return this.$header; } return this.$body; } }; keyStrokes.forEach(keyStroke => { keyStroke.actionKeyStroke.renderingHints = $.extend({}, keyStroke.actionKeyStroke.renderingHints, groupBoxRenderingHints); }); super._setKeyStrokes(keyStrokes); } /** * Returns a $container used as a bind target for the keystroke context of the group-box. * By default, this function returns the container of the form, or when group-box is has no * form as a parent the container of the group-box. */ protected _keyStrokeBindTarget(): JQuery { let form = this.getForm(); if (form) { // keystrokes on a group-box have form scope return form.$container; } return this.$container; } protected override _render() { this.addContainer(this.$parent, this.mainBox ? 'root-group-box' : 'group-box'); this.$header = this.$container.appendDiv('group-box-header'); HtmlComponent.install(this.$header, this.session); // Complete layout chain for elements inside header (e.g. allow top status to invalidate layout when visibility changes) this.$title = this.$header.appendDiv('title'); this.$borderBottom = this.$header.appendDiv('bottom-border'); this.addLabel(); this.addSubLabel(); this.addStatus(); this.$body = this.$container.appendDiv('group-box-body'); aria.role(this.$body, 'group'); this.htmlBody = HtmlComponent.install(this.$body, this.session); this.htmlBody.setLayout(this._createBodyLayout()); } protected override _remove() { this._removeSubLabel(); super._remove(); } protected override _renderProperties() { this._renderScrollable(); // Need to be before renderExpanded in order to have the scrollbars when the fields are rendered. The status tooltips require a scrollable parent to move when scrolling. this._renderExpanded(); // Need to be before renderVisible is executed, otherwise controls might be rendered if group box is invisible which breaks some widgets (e.g. Tree and Table) super._renderProperties(); this._renderBodyLayoutConfig(); this._renderNotification(); this._renderBorderVisible(); this._renderExpandable(); this._renderMenuBarPosition(); this._renderMenuBarEllipsisPosition(); this._renderMenuBarVisible(); this._renderSubLabel(); } protected override _createLayout(): AbstractLayout { return new GroupBoxLayout(this); } protected _createBodyLayout(): LogicalGridLayout { return new LogicalGridLayout(this, this.bodyLayoutConfig); } setBodyLayoutConfig(bodyLayoutConfig: ObjectOrModel<LogicalGridLayoutConfig>) { this.setProperty('bodyLayoutConfig', bodyLayoutConfig); } protected _setBodyLayoutConfig(bodyLayoutConfig: ObjectOrModel<LogicalGridLayoutConfig>) { this._setProperty('bodyLayoutConfig', LogicalGridLayoutConfig.ensure(bodyLayoutConfig || {})); LogicalGridLayoutConfig.initHtmlEnvChangeHandler(this, () => this.bodyLayoutConfig, layoutConfig => this.setBodyLayoutConfig(layoutConfig)); } protected _renderBodyLayoutConfig() { let layout = this.htmlBody.layout as LogicalGridLayout; let oldMinWidth = layout.minWidth; this.bodyLayoutConfig.applyToLayout(layout); if (oldMinWidth !== this.bodyLayoutConfig.minWidth) { this._renderScrollable(); } if (this.rendered) { this.htmlBody.invalidateLayoutTree(); } } /** * Redraws the group box body by removing and re-rendering every control. * This may be necessary if a field does not support a dynamic property change and therefore needs to be redrawn completely to reflect the change. */ rerenderControls() { this._removeControls(); this._renderControls(); this.htmlBody.invalidateLayoutTree(); } protected _removeControls() { this.controls.forEach(control => control.remove()); } protected _renderControls() { this.controls.forEach(control => { if (!control.rendered) { control.render(this.$body); // set each children layout data to logical grid data control.setLayoutData(new LogicalGridData(control)); } }); } addSubLabel() { if (this.$subLabel) { return; } this.$subLabel = this.$title.appendDiv('sub-label'); tooltips.installForEllipsis(this.$subLabel, { parent: this }); } protected _removeSubLabel() { if (!this.$subLabel) { return; } tooltips.uninstall(this.$subLabel); this.$subLabel.remove(); this.$subLabel = null; } /** @see GroupBoxModel.subLabel */ setSubLabel(subLabel: string) { this.setProperty('subLabel', subLabel); } protected _renderSubLabel() { this.$subLabel.setVisible(strings.hasText(this.subLabel)); this.$subLabel.textOrNbsp(this.subLabel); this.$container.toggleClass('has-sub-label', this.$subLabel.isVisible()); this.invalidateLayoutTree(); } /** @see GroupBoxModel.scrollable */ setScrollable(scrollable: boolean) { this.setProperty('scrollable', scrollable); } protected _renderScrollable() { this._uninstallScrollbars(); // horizontal (x-axis) scrollbar is only installed when minWidth is > 0 if (this.scrollable) { this._installScrollbars({ axis: this.bodyLayoutConfig.minWidth > 0 ? 'both' : 'y' }); } else if (this.bodyLayoutConfig.minWidth > 0) { this._installScrollbars({ axis: 'x' }); } } override get$Scrollable(): JQuery { return this.$body; } protected override _onScroll(event: JQuery.ScrollEvent) { super._onScroll(event); this._updateScrollShadow(); } protected _updateScrollShadow() { if (this.mainBox || !this.rendered) { // No need to do anything if it's the mainBox because header is invisible and the menu bar already takes the full width return; } let hasScrollShadowTop = this.hasScrollShadow('top'); let hasScrollShadowBottom = this.hasScrollShadow('bottom'); let oldHasScrollShadowTop = this.$container.hasClass('has-scroll-shadow-top'); let oldHasScrollShadowBottom = this.$container.hasClass('has-scroll-shadow-bottom'); let hasMenubarTop = this.$container.hasClass('menubar-position-top'); let hasMenubarBottom = this.$container.hasClass('menubar-position-bottom'); let headerVisible = this.$header.isVisible(); this.$container.toggleClass('has-scroll-shadow-top', hasScrollShadowTop); this.$container.toggleClass('has-scroll-shadow-bottom', hasScrollShadowBottom); if ((headerVisible || hasMenubarTop) && oldHasScrollShadowTop !== hasScrollShadowTop || hasMenubarBottom && oldHasScrollShadowBottom !== hasScrollShadowBottom) { this.invalidateLayoutTree(false); } // Enlarge header line if there is a shadow, but don't do it if there is a menubar on top fields.adjustStatusPositionForScrollShadow(this, () => hasScrollShadowTop && headerVisible && !hasMenubarTop); } setMainBox(mainBox: boolean) { this.setProperty('mainBox', mainBox); } protected _setMainBox(mainBox: boolean) { this._setProperty('mainBox', mainBox); if (this.mainBox) { this.menuBar.setCssClass('main-menubar'); if (this.scrollable === null) { this.setScrollable(true); } if (this.responsive === null) { this.setResponsive(true); } } } override addLabel() { if (this.$label) { return; } this.$label = this.$title.appendDiv('label'); if (this._computeTitleVisible()) { // add it as a heading if its not the invisible main box aria.linkElementWithHeader(this.$container, this.$label); } tooltips.installForEllipsis(this.$label, { parent: this }); } protected override _renderLabel() { this.$label.textOrNbsp(this.label); if (this._computeTitleVisible()) { aria.linkElementWithLabel(this.$body, this.$label); // label linked with body so group navigation announces the group when entering it } if (this.rendered) { this._renderLabelVisible(); } } override addStatus() { super.addStatus(); this._updateStatusPosition(); } protected override _renderStatusPosition() { this._updateStatusPosition(); } protected _updateStatusPosition() { if (!this.fieldStatus) { return; } if (this.statusPosition === FormField.StatusPosition.TOP) { // move into header this.$status.appendTo(this.$header); } else { this.$status.appendTo(this.$container); } this.invalidateLayoutTree(); } /** @see GroupBoxModel.notification */ setNotification(notification: ObjectOrChildModel<Notification>) { this.setProperty('notification', notification); } protected _renderNotification() { if (!this.notification) { this.invalidateLayoutTree(); return; } this.notification.render(); this.notification.$container.insertBefore(this.$body); this.invalidateLayoutTree(); } /** @internal */ _prepareFields() { this.processButtons.forEach(this._unregisterButtonKeyStrokes.bind(this)); this.controls = []; this.systemButtons = []; this.customButtons = []; this.processButtons = []; this.processMenus = []; for (let i = 0; i < this.fields.length; i++) { let field = this.fields[i]; if (field instanceof Button) { if (field.processButton) { this.processButtons.push(field); if (field.systemType !== Button.SystemType.NONE) { this.systemButtons.push(field); } else { this.customButtons.push(field); } } else { this.controls.push(field); this._registerButtonKeyStrokes(field); } } else if (field instanceof TabBox) { this.controls.push(field); for (let k = 0; k < field.tabItems.length; k++) { if (field.tabItems[k].selectionKeystroke) { this.keyStrokeContext.registerKeyStroke(new TabItemKeyStroke(field.tabItems[k].selectionKeystroke, field.tabItems[k])); } } } else { this.controls.push(field); } } // Create menu for each process button this.processMenus = this.processButtons.map(button => { let model = ButtonAdapterMenu.adaptButtonProperties(button, { parent: this, menubar: this.menuBar, button: button, // initially defaultMenu should only be set if defaultButton is set to true, false should not be mapped as the default defaultMenu = null setting // would be overridden if this default null setting is overridden MenuBar.prototype.updateDefaultMenu would not consider these entries anymore defaultMenu: button.defaultButton ? true : null }); return scout.create(ButtonAdapterMenu, model); }); this.registerKeyStrokes(this.processMenus); } protected _unregisterButtonKeyStrokes(button: Button) { if (button.keyStrokes) { button.keyStrokes.forEach(keyStroke => this.keyStrokeContext.unregisterKeyStroke(keyStroke)); } } protected _registerButtonKeyStrokes(button: Button) { if (button.keyStrokes) { button.keyStrokes.forEach(keyStroke => this.keyStrokeContext.registerKeyStroke(keyStroke)); } } /** @see GroupBoxModel.borderVisible */ setBorderVisible(borderVisible: boolean) { this.setProperty('borderVisible', borderVisible); } protected _renderBorderVisible() { let borderVisible = this.borderVisible; if (this.borderDecoration === GroupBox.BorderDecoration.AUTO) { borderVisible = this._computeBorderVisible(borderVisible); } this.$body.toggleClass('y-padding-invisible', !borderVisible); this.invalidateLayoutTree(); } /** @see GroupBoxModel.borderDecoration */ setBorderDecoration(borderDecoration: GroupBoxBorderDecoration) { this.setProperty('borderDecoration', borderDecoration); } // Don't include in renderProperties, it is not necessary to execute it initially because renderBorderVisible is executed already protected _renderBorderDecoration() { this._renderBorderVisible(); } override getContextMenuItems(onlyVisible = true): Menu[] { if (this.menuBarVisible) { return []; } return super.getContextMenuItems(onlyVisible); } /** @see GroupBoxModel.menuBarVisible */ setMenuBarVisible(visible: boolean) { this.setProperty('menuBarVisible', visible); } protected _setMenuBarVisible(visible: boolean) { this._setProperty('menuBarVisible', visible); this._updateMenuBar(); } protected _renderMenuBarVisible() { if (this.menuBarVisible) { this._renderMenuBar(); } else { this.menuBar.remove(); } this._updateMenus(); this.invalidateLayoutTree(); } protected _renderMenuBar() { this.menuBar.render(); if (this.menuBarPosition === GroupBox.MenuBarPosition.TITLE) { // move right of title let $control = this.$header.children('.group-box-control'); if ($control.length > 0) { this.menuBar.$container.insertAfter($control); } else { this.menuBar.$container.insertAfter(this.$title); } } else if (this.menuBar.position === MenuBar.Position.TOP) { // move below header this.menuBar.$container.insertAfter(this.$header); } } /** @see GroupBoxModel.menuBarPosition */ setMenuBarPosition(menuBarPosition: GroupBoxMenuBarPosition) { this.setProperty('menuBarPosition', menuBarPosition); } protected _renderMenuBarPosition() { let position = this.menuBarPosition; if (position === GroupBox.MenuBarPosition.AUTO) { position = GroupBox.MenuBarPosition.TOP; if (this.mainBox) { let form = this.getForm(); if (form && form.isDialog()) { position = GroupBox.MenuBarPosition.BOTTOM; } } } if (position === GroupBox.MenuBarPosition.BOTTOM) { this.menuBar.setPosition(MenuBar.Position.BOTTOM); } else { // top + title this.menuBar.setPosition(MenuBar.Position.TOP); } this._renderMenuBarStyle(); if (this.rendered) { this.menuBar.remove(); this._renderMenuBarVisible(); } } /** @see GroupBoxModel.menuBarEllipsisPosition */ setMenuBarEllipsisPosition(menuBarEllipsisPosition: MenuBarEllipsisPosition) { this.setProperty('menuBarEllipsisPosition', menuBarEllipsisPosition); this.menuBar.setEllipsisPosition(menuBarEllipsisPosition); } protected _renderMenuBarEllipsisPosition() { this.menuBar.reorderMenus(); if (this.rendered) { this.menuBar.remove(); this._renderMenuBarVisible(); } } protected _updateMenuBarStyle() { if (this.rendered) { this._renderMenuBarStyle(); } } protected _renderMenuBarStyle() { let visible = this.menuBar.visible; let hasTitleMenuBar = this.menuBarPosition === GroupBox.MenuBarPosition.TITLE; this.$header.toggleClass('has-menubar', visible && hasTitleMenuBar); this.$container.toggleClass('menubar-position-top', visible && !hasTitleMenuBar && this.menuBar.position === MenuBar.Position.TOP); this.$container.toggleClass('menubar-position-bottom', visible && !hasTitleMenuBar && this.menuBar.position === MenuBar.Position.BOTTOM); } protected _computeBorderVisible(borderVisible: boolean): boolean { if (this.mainBox) { borderVisible = false; } else if (this.parent instanceof GroupBox && this.parent.parent instanceof Form && this.parent.parent.parent instanceof WrappedFormField && this.parent.parent.parent.parent instanceof SplitBox && this.parent.getFields().length === 1) { // Special case for wizard: wrapped form in split box with a single group box borderVisible = false; } return borderVisible; } /** @see GroupBoxModel.expandable */ setExpandable(expandable: boolean) { this.setProperty('expandable', expandable); } protected _renderExpandable() { let expandable = this.expandable; let $control = this.$header.children('.group-box-control'); if (expandable) { if ($control.length === 0) { // Create control if necessary this.$container.makeDiv('group-box-control') .on('click', this._onControlClick.bind(this)) .insertAfter(this.$title); } this.$header .addClass('expandable') .on('click.group-box-control', this._onControlClick.bind(this)); } else { $control.remove(); this.$header .removeClass('expandable') .off('.group-box-control'); } this.invalidateLayoutTree(); } /** @see GroupBoxModel.expanded */ setExpanded(expanded: boolean) { this.setProperty('expanded', expanded); } protected _renderExpanded() { this.$container.toggleClass('collapsed', !this.expanded); // Group boxes have set "useUiHeight=true" by default. When a group box is collapsed, it should not // stretch vertically (no "weight Y"). However, because "weightY" is -1 by default, a calculated value // is assigned (LogicalGridData._inheritWeightY()) that is based on the group boxes height. In collapsed // state, this height would be wrong. Therefore, we manually assign "weightY=0" to collapsed group boxes // to prevent them from being stretched. if (this.expanded) { // If group box was previously collapsed, restore original "weightY" gridData value if (this._collapsedWeightY !== undefined) { this.gridData.weightY = this._collapsedWeightY; delete this._collapsedWeightY; } // Update inner layout (e.g. menubar) this.invalidateLayout(); this._renderControls(); } else { // If group box has a weight different from 0, we set it to zero and back up the old value if (this.gridData.weightY !== 0) { this._collapsedWeightY = this.gridData.weightY; this.gridData.weightY = 0; } } this.invalidateLayoutTree(); } /** @see GroupBoxModel.gridColumnCount */ setGridColumnCount(gridColumnCount: number) { this.setProperty('gridColumnCount', gridColumnCount); this.invalidateLogicalGrid(); } override invalidateLogicalGrid(invalidateLayout?: boolean) { super.invalidateLogicalGrid(false); if (scout.nvl(invalidateLayout, true) && this.rendered) { this.htmlBody.invalidateLayoutTree(); } } protected override _setLogicalGrid(logicalGrid: LogicalGrid | string) { super._setLogicalGrid(logicalGrid); if (this.logicalGrid) { this.logicalGrid.setGridConfig(new GroupBoxGridConfig()); } } protected override _renderLabelVisible() { this.$header.setVisible(this._computeTitleVisible()); this._updateFieldStatus(); if (this.menuBarPosition === GroupBox.MenuBarPosition.TITLE) { this.invalidateLayoutTree(); } } protected _computeTitleVisible(labelVisible?: boolean): boolean { labelVisible = scout.nvl(labelVisible, this.labelVisible); return !!(labelVisible && this.label && !this.mainBox); } /** * Only show the group box status if title is visible. */ protected override _computeStatusVisible(): boolean { return super._computeStatusVisible() && this._computeTitleVisible(); } protected override _setMenus(menus: Menu | Menu[]) { super._setMenus(menus); if (this.menuBar) { // updateMenuBar is required because menuBar is not created yet when synMenus is called initially this._updateMenuBar(); } } /** @internal */ _updateMenuBar() { if (!this.menuBarVisible) { // Do not update menuBar while it is invisible, the menus may now be managed by another widget. // -> this makes sure the parent is not accidentally set to the group box, the other widget should remain responsible this.menuBar.setMenuItems([]); return; } let menus = this.staticMenus .concat(this.processMenus) .concat(this.menus); this.menuBar.setMenuItems(menus); } protected _removeMenus() { // menubar takes care of removal } setStaticMenus(staticMenus: ObjectOrChildModel<Menu>[]) { this.setProperty('staticMenus', staticMenus); this._updateMenuBar(); } protected _onControlClick(event: JQuery.ClickEvent) { if (!this.expandable) { return; } const target = scout.widget(event.target); if (this.menuBarPosition === GroupBox.MenuBarPosition.TITLE && this.menuBar.has(target)) { // If the position of the menubar is set to title and a menu has been clicked, then the event must not be handled return; } this.setExpanded(!this.expanded); $.suppressEvent(event); // otherwise, the event would be triggered twice sometimes (by group-box-control and group-box-title) } /** @see GroupBoxModel.responsive */ setResponsive(responsive: boolean) { this.setProperty('responsive', responsive); } protected _setResponsive(responsive: boolean) { this._setProperty('responsive', responsive); if (!this.initialized) { return; } if (this.responsive) { ResponsiveManager.get().reset(this, true); } else { ResponsiveManager.get().reset(this, true); if (this.responsive === null) { let parent = this.findParent(parent => parent instanceof GroupBox && parent.responsive) as GroupBox; ResponsiveManager.get().reset(parent, true); } } this.invalidateLayoutTree(); } override clone(model: GroupBoxModel, options?: CloneOptions): this { let clone = super.clone(model) as GroupBox; this._deepCloneProperties(clone, ['fields'], options); clone._prepareFields(); return clone as this; } } export type GroupBoxBorderDecoration = EnumObject<typeof GroupBox.BorderDecoration>; export type GroupBoxMenuBarPosition = EnumObject<typeof GroupBox.MenuBarPosition>; ObjectIdProvider.uuidPathSkipWidgets.add(GroupBox);