@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
1,375 lines (1,161 loc) • 81.9 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2026 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides control sap.m.Dialog.
sap.ui.define([
"sap/ui/core/AnimationMode",
"sap/ui/core/ControlBehavior",
"sap/base/i18n/Localization",
"sap/ui/core/Lib",
"./Bar",
"./InstanceManager",
"./AssociativeOverflowToolbar",
"./ToolbarSpacer",
"./Title",
"./library",
"sap/m/Image",
"sap/m/dialogUtils/PreventKeyboardEvents",
"sap/ui/core/Control",
"sap/ui/core/Element",
"sap/ui/core/IconPool",
"sap/ui/core/Popup",
"sap/ui/core/delegate/ScrollEnablement",
"sap/ui/core/RenderManager",
"sap/ui/core/InvisibleText",
"sap/ui/core/theming/Parameters",
"sap/ui/core/util/ResponsivePaddingsEnablement",
"sap/ui/Device",
"sap/ui/core/library",
"sap/ui/events/KeyCodes",
"./TitlePropagationSupport",
"./DialogRenderer",
"sap/base/Log",
"sap/ui/thirdparty/jquery",
"sap/ui/core/Configuration",
"sap/ui/dom/units/Rem",
// jQuery Plugin "firstFocusableDomRef", "lastFocusableDomRef"
"sap/ui/dom/jquery/Focusable"
],
function(
AnimationMode,
ControlBehavior,
Localization,
Library,
Bar,
InstanceManager,
AssociativeOverflowToolbar,
ToolbarSpacer,
Title,
library,
Image,
PreventKeyboardEvents,
Control,
Element,
IconPool,
Popup,
ScrollEnablement,
RenderManager,
InvisibleText,
Parameters,
ResponsivePaddingsEnablement,
Device,
coreLibrary,
KeyCodes,
TitlePropagationSupport,
DialogRenderer,
Log,
jQuery,
Configuration,
Rem
) {
"use strict";
// shortcut for sap.ui.core.OpenState
var OpenState = coreLibrary.OpenState;
// shortcut for sap.m.ButtonType
var ButtonType = library.ButtonType;
// shortcut for sap.m.DialogType
var DialogType = library.DialogType;
// shortcut for sap.m.DialogType
var DialogRoleType = library.DialogRoleType;
// shortcut for sap.ui.core.ValueState
var ValueState = coreLibrary.ValueState;
// shortcut for sap.ui.core.TitleLevel
var TitleLevel = coreLibrary.TitleLevel;
// shortcut for sap.m.TitleAlignment
var TitleAlignment = library.TitleAlignment;
var sAnimationMode = ControlBehavior.getAnimationMode();
var bUseAnimations = sAnimationMode !== AnimationMode.none && sAnimationMode !== AnimationMode.minimal;
// the time should be longer the longest transition in the CSS (200ms),
// because of focusing and transition related issues,
// where 200ms transition sometimes seems to last a little longer
var iAnimationDuration = bUseAnimations ? 300 : 10;
// HTML container scrollbar width
var SCROLLBAR_WIDTH = 17;
var DRAGRESIZE_STEP = Rem.toPx(1);
/**
* Margin on the left and right sides of dialog
*/
var HORIZONTAL_MARGIN = 5;
/**
* Margin on top and bottom of dialog
*/
var VERTICAL_MARGIN = 3; // default value
/**
* Constructor for a new Dialog.
*
* @param {string} [sId] ID for the new control, generated automatically if no ID is given
* @param {object} [mSettings] Initial settings for the new control
*
* @class
* A popup that interrupts the current processing and prompts the user for an action or an input in a modal mode.
* <h3>Overview</h3>
* The Dialog control is used to prompt the user for an action or a confirmation. It interrupts the current app processing as it is the only focused UI element and the main screen is dimmed/blocked.
* The content of the Dialog is fully customizable.
* <h3>Structure</h3>
* A Dialog consists of a title, optional subtitle, content area and a footer for action buttons.
* The Dialog is usually displayed at the center of the screen. Its size and position can be changed by the user.
* To enable this, you need to set the properties <code>resizable</code> and <code>draggable</code> accordingly.
* In that case the dialog title bar can be focused.
* While the keyboard focus is located on the title bar, the dialog can be moved
* with the arrow keys and resized with shift+arrow keys.
*
* There are other specialized types of dialogs:
* <ul>
* <li>{@link sap.m.P13nDialog Personalization Dialog} - used for personalizing sorting, filtering and grouping in tables</li>
* <li>{@link sap.m.SelectDialog Select Dialog} - used to select one or more items from a comprehensive list</li>
* <li>{@link sap.m.TableSelectDialog Table Select Dialog} - used to make a selection from a comprehensive table containing multiple attributes or values</li>
* <li>{@link sap.ui.comp.valuehelpdialog.ValueHelpDialog Value Help Dialog} - used to help the user find and select single and multiple values</li>
* <li>{@link sap.m.ViewSettingsDialog View Settings Dialog} - used to sort, filter, or group data within a (master) list or a table</li>
* <li>{@link sap.m.BusyDialog Busy Dialog} - used to block the screen and inform the user about an ongoing operation</li>
* </ul>
* <h3>Usage</h3>
* <h4>When to use:</h4>
* <ul>
* <li>You want to display a system message.</li>
* <li>You want to interrupt the user’s action.</li>
* <li>You want to show a message with a short and a long description.</li>
* </ul>
* <h4>When not to use:</h4>
* <ul>
* <li>You just want to confirm a successful action.</li>
* </ul>
* <h3>Responsive Behavior</h3>
* <ul>
* <li>If the <code>stretch</code> property is set to <code>true</code>, the Dialog displays on full screen.</li>
* <li>If the <code>contentWidth</code> and/or <code>contentHeight</code> properties are set, the Dialog will try to fill those sizes.</li>
* <li>If there is no specific sizing, the Dialog will try to adjust its size to its content.</li>
* </ul>
* When using the <code>sap.m.Dialog</code> in SAP Quartz and Horizon themes, the breakpoints and layout paddings could be determined by the Dialog's width. To enable this concept and add responsive paddings to an element of the Dialog control, you have to add the following classes depending on your use case: <code>sapUiResponsivePadding--header</code>, <code>sapUiResponsivePadding--subHeader</code>, <code>sapUiResponsivePadding--content</code>, <code>sapUiResponsivePadding--footer</code>.
* <h4>Smartphones</h4>
* If the Dialog has one or two actions, they will cover the entire footer. If there are more actions, they will overflow.
* <h4>Tablets</h4>
* The action buttons in the toolbar are <b>right-aligned</b>. Use <b>cozy</b> mode on tablet devices.
* <h4>Desktop</h4>
* The action buttons in the toolbar are <b>right-aligned</b>. Use <b>compact</b> mode on desktop.
* @extends sap.ui.core.Control
*
* @implements sap.ui.core.PopupInterface
* @author SAP SE
* @version 1.146.0
*
* @constructor
* @public
* @alias sap.m.Dialog
* @see {@link fiori:https://experience.sap.com/fiori-design-web/dialog/ Dialog}
*/
var Dialog = Control.extend("sap.m.Dialog", /** @lends sap.m.Dialog.prototype */ {
metadata: {
interfaces: [
"sap.ui.core.PopupInterface"
],
library: "sap.m",
properties: {
/**
* Icon displayed in the Dialog header. This <code>icon</code> is invisible on the iOS platform and it is density-aware. You can use the density convention (@2, @1.5, etc.) to provide higher resolution image for higher density screen.
*/
icon: {type: "sap.ui.core.URI", group: "Appearance", defaultValue: null},
/**
* Title text appears in the Dialog header.
* <br/><b>Note:</b> The heading level of the Dialog is <code>H1</code>. Headings in the Dialog content should start with <code>H2</code> heading level.
*/
title: {type: "string", group: "Appearance", defaultValue: null},
/**
* Determines whether the header is shown inside the Dialog. If this property is set to <code>false</code>, the <code>text</code> and <code>icon</code> properties are ignored. This property has a default value <code>true</code>.
* @since 1.15.1
*/
showHeader: {type: "boolean", group: "Appearance", defaultValue: true},
/**
* The <code>type</code> of the Dialog. In some themes, the type Message will limit the Dialog width within 480px on tablet and desktop.
*/
type: {type: "sap.m.DialogType", group: "Appearance", defaultValue: DialogType.Standard},
/**
* Affects the <code>icon</code> and the <code>title</code> color.
*
* If a value other than <code>None</code> is set, a predefined icon will be added to the Dialog.
* Setting the <code>icon</code> property will overwrite the predefined icon.
* @since 1.11.2
*/
state: {type: "sap.ui.core.ValueState", group: "Appearance", defaultValue: ValueState.None},
/**
* Determines whether the Dialog will be displayed on full screen on a phone.
* @since 1.11.2
* @deprecated Since version 1.13.1.
* Please use the new stretch property instead. This enables a stretched Dialog even on tablet and desktop. If you want to achieve the same effect as <code>stretchOnPhone</code>, please set the stretch with <code>Device.system.phone</code>, then the Dialog is only stretched when it runs on a phone.
*/
stretchOnPhone: {type: "boolean", group: "Appearance", defaultValue: false, deprecated: true},
/**
* Determines if the Dialog will be stretched to full screen on mobile.
* On desktop, the Dialog will be stretched to approximately 90% of the viewport.
* This property is only applicable to a Standard Dialog. Message-type Dialog ignores it.
* @since 1.13.1
*/
stretch: {type: "boolean", group: "Appearance", defaultValue: false},
/**
* Preferred width of the content in the Dialog. This property affects the width of the Dialog on a phone in landscape mode, a tablet or a desktop, because the Dialog has a fixed width on a phone in portrait mode. If the preferred width is less than the minimum width of the Dialog or more than the available width of the screen, it will be overwritten by the min or max value. The current mininum value of the Dialog width on tablet is 400px.
* @since 1.12.1
*/
contentWidth: {type: "sap.ui.core.CSSSize", group: "Dimension", defaultValue: null},
/**
* Preferred height of the content in the Dialog. If the preferred height is bigger than the available space on a screen, it will be overwritten by the maximum available height on a screen in order to make sure that the Dialog isn't cut off.
* @since 1.12.1
*/
contentHeight: {type: "sap.ui.core.CSSSize", group: "Dimension", defaultValue: null},
/**
* Indicates if the user can scroll horizontally inside the Dialog when the content is bigger than the content area.
* The Dialog detects if there's <code>sap.m.NavContainer</code>, <code>sap.m.Page</code>, <code>sap.m.ScrollContainer</code> or <code>sap.m.SplitContainer</code> as a direct child added to the Dialog. If there is, the Dialog will turn off <code>scrolling</code> by setting this property to <code>false</code>, automatically ignoring the existing value of the property.
* @since 1.15.1
*/
horizontalScrolling: {type: "boolean", group: "Behavior", defaultValue: true},
/**
* Indicates if the user can scroll vertically inside the Dialog when the content is bigger than the content area.
* The Dialog detects if there's <code>sap.m.NavContainer</code>, <code>sap.m.Page</code>, <code>sap.m.ScrollContainer</code> or <code>sap.m.SplitContainer</code> as a direct child added to the Dialog. If there is, the Dialog will turn off <code>scrolling</code> by setting this property to <code>false</code>, automatically ignoring the existing value of this property.
* @since 1.15.1
*/
verticalScrolling: {type: "boolean", group: "Behavior", defaultValue: true},
/**
* Indicates whether the Dialog is resizable. If this property is set to <code>true</code>, the Dialog will have a resize handler in its bottom right corner. This property has a default value <code>false</code>. The Dialog can be resizable only in desktop mode.
* @since 1.30
*/
resizable: {type: "boolean", group: "Behavior", defaultValue: false},
/**
* Indicates whether the Dialog is draggable. If this property is set to <code>true</code>, the Dialog will be draggable by its header. This property has a default value <code>false</code>. The Dialog can be draggable only in desktop mode.
* @since 1.30
*/
draggable: {type: "boolean", group: "Behavior", defaultValue: false},
/**
* This property allows to define custom behavior if the Escape key is pressed. By default, the Dialog is closed.<br/>
* The property expects a function with one object parameter with <code>resolve</code> and <code>reject</code> properties.
* In the function, either call <code>resolve</code> to close the dialog or call <code>reject</code> to prevent it from being closed.
* @since 1.44
* @type {sap.m.Dialog.EscapeHandler}
*/
escapeHandler : {type: "function", group: "Behavior", defaultValue: null},
/**
* Specifies the ARIA role of the Dialog. If the state of the control is "Error" or "Warning" the role will be "AlertDialog" regardless of what is set.
* @since 1.65
* @private
*/
role: {type: "sap.m.DialogRoleType", group: "Data", defaultValue: DialogRoleType.Dialog, visibility: "hidden"},
/**
* Indicates whether the Dialog will be closed automatically when a routing navigation occurs.
* @since 1.72
*/
closeOnNavigation: {type: "boolean", group: "Behavior", defaultValue: true},
/**
* Specifies the Title alignment (theme specific).
* If set to <code>TitleAlignment.Auto</code>, the Title will be aligned as it is set in the theme (if not set, the default value is <code>center</code>);
* Other possible values are <code>TitleAlignment.Start</code> (left or right depending on LTR/RTL), and <code>TitleAlignment.Center</code> (centered)
* @since 1.72
* @public
*/
titleAlignment : {type : "sap.m.TitleAlignment", group : "Misc", defaultValue : TitleAlignment.Auto}
},
defaultAggregation: "content",
aggregations: {
/**
* The content inside the Dialog.<br/><b>Note:</b> When the content of the Dialog is comprised of controls that use <code>position: absolute</code>, such as <code>SplitContainer</code>, the Dialog has to have either <code>stretch: true</code> or <code>contentHeight</code> set.
*/
content: {type: "sap.ui.core.Control", multiple: true, singularName: "content"},
/**
* When a <code>subHeader</code> is assigned to the Dialog, it's rendered directly after the main header in the Dialog. The <code>subHeader</code> is out of the content area and won't be scrolled when the content size is bigger than the content area size.
* @since 1.12.2
*/
subHeader: {type: "sap.m.IBar", multiple: false},
/**
* When it is set, the <code>icon</code>, <code>title</code> and <code>showHeader</code> properties are ignored. Only the <code>customHeader</code> is shown as the header of the Dialog.
* <br/><b>Note:</b> To improve accessibility, titles with heading level <code>H1</code> should be used inside the custom header.
* @since 1.15.1
*/
customHeader: {type: "sap.m.IBar", multiple: false},
/**
* The button which is rendered to the left side (right side in RTL mode) of the <code>endButton</code> in the footer area inside the Dialog.
* As of version 1.21.1, there's a new aggregation <code>buttons</code> created with which more than 2 buttons can be added to the footer area of the Dialog.
* If the new <code>buttons</code> aggregation is set, any change made to this aggregation has no effect anymore.
* With the Belize themes when running on a phone, this <code>button</code> (and the <code>endButton</code> together when set) is (are) rendered at the center of the footer area.
* While with the Quartz themes when running on a phone, this <code>button</code> (and the <code>endButton</code> together when set) is (are) rendered on the right side of the footer area.
* When running on other platforms, this <code>button</code> (and the <code>endButton</code> together when set) is (are) rendered at the right side (left side in RTL mode) of the footer area.
* @since 1.15.1
*/
beginButton: {type: "sap.m.Button", multiple: false},
/**
* The button which is rendered to the right side (left side in RTL mode) of the <code>beginButton</code> in the footer area inside the Dialog.
* As of version 1.21.1, there's a new aggregation <code>buttons</code> created with which more than 2 buttons can be added to the footer area of Dialog.
* If the new <code>buttons</code> aggregation is set, any change made to this aggregation has no effect anymore.
* With the Belize themes when running on a phone, this <code>button</code> (and the <code>beginButton</code> together when set) is (are) rendered at the center of the footer area.
* While with the Quartz themes when running on a phone, this <code>button</code> (and the <code>beginButton</code> together when set) is (are) rendered on the right side of the footer area.
* When running on other platforms, this <code>button</code> (and the <code>beginButton</code> together when set) is (are) rendered at the right side (left side in RTL mode) of the footer area.
* @since 1.15.1
*/
endButton: {type: "sap.m.Button", multiple: false},
/**
* Buttons can be added to the footer area of the Dialog through this aggregation. When this aggregation is set, any change to the <code>beginButton</code> and <code>endButton</code> has no effect anymore. Buttons which are inside this aggregation are aligned at the right side (left side in RTL mode) of the footer instead of in the middle of the footer.
* The buttons aggregation can not be used together with the footer aggregation.
* @since 1.21.1
*/
buttons: {type: "sap.m.Button", multiple: true, singularName: "button"},
/**
* The footer of this dialog. It is always located at the bottom of the dialog. The footer aggregation can not be used together with the buttons aggregation.
* @since 1.110
*/
footer: {type: "sap.m.Toolbar", multiple: false},
/**
* The hidden aggregation for internal maintained <code>header</code>.
*/
_header: {type: "sap.ui.core.Control", multiple: false, visibility: "hidden"},
/**
* The hidden aggregation for internal maintained <code>icon</code> control.
*/
_icon: {type: "sap.ui.core.Control", multiple: false, visibility: "hidden"},
/**
* The hidden aggregation for internal maintained <code>toolbar</code> instance.
*/
_toolbar: {type: "sap.m.OverflowToolbar", multiple: false, visibility: "hidden"},
/**
* The hidden aggregation for the Dialog state.
*/
_valueState: {type: "sap.ui.core.InvisibleText", multiple: false, visibility: "hidden"}
},
associations: {
/**
* <code>LeftButton</code> is shown at the left edge of the bar in iOS, and at the right side of the bar for the other platforms. Please set this to <code>null</code> if you want to remove the Left button from the bar. And the button is only removed from the bar, not destroyed. When <code>showHeader</code> is set to <code>false</code>, this property will be ignored. Setting <code>leftButton</code> will also set the <code>beginButton</code> internally.
* @deprecated Since version 1.15.1.
*
* <code>LeftButton</code> has been deprecated since 1.15.1. Please use the <code>beginButton</code> instead which is more RTL friendly.
*/
leftButton: {type: "sap.m.Button", multiple: false, deprecated: true},
/**
* <code>RightButton</code> is always shown at the right edge of the bar. Please set this to null if you want to remove the Right button from the bar. And the button is only removed from the bar, not destroyed. When <code>showHeader</code> is set to false, this property will be ignored. Setting <code>rightButton</code> will also set the <code>endButton</code> internally.
* @deprecated Since version 1.15.1.
*
* <code>RightButton</code> has been deprecated since 1.15.1. Please use the <code>endButton</code> instead which is more RTL friendly.
*/
rightButton: {type: "sap.m.Button", multiple: false, deprecated: true},
/**
* In the Dialog, focus is initially set on the first focusable element, or on the dialog itself if no such element is available.
* If another control needs to receive focus, set the <code>initialFocus</code> to the control that should be focused.
* Setting <code>initialFocus</code> on input controls does not open the on-screen keyboard on mobile devices.
* Due to browser restrictions, the on-screen keyboard can't be opened with JavaScript code; it must be triggered explicitly by the user.
* @since 1.15.0
*/
initialFocus: {type: "sap.ui.core.Control", multiple: false},
/**
* Association to controls/IDs which describe this control (see WAI-ARIA attribute aria-describedby).
*/
ariaDescribedBy: {type: "sap.ui.core.Control", multiple: true, singularName: "ariaDescribedBy"},
/**
* Association to controls/IDs which label this control (see WAI-ARIA attribute aria-labelledby).
*/
ariaLabelledBy : {type : "sap.ui.core.Control", multiple : true, singularName : "ariaLabelledBy"}
},
events: {
/**
* This event will be fired before the Dialog is opened.
*/
beforeOpen: {
allowPreventDefault: true
},
/**
* This event will be fired after the Dialog is opened.
*/
afterOpen: {},
/**
* This event will be fired before the Dialog is closed.
*/
beforeClose: {
allowPreventDefault: true,
parameters: {
/**
* This indicates the trigger of closing the Dialog. If the Dialog is closed by either the <code>beginButton</code> or the <code>endButton</code>, the button that closes the Dialog is set to this parameter. Otherwise, the parameter is set to <code>null</code>.
* @since 1.9.2
*/
origin: {type: "sap.m.Button"}
}
},
/**
* This event will be fired after the Dialog is closed.
*/
afterClose: {
parameters: {
/**
* This indicates the trigger of closing the Dialog. If the Dialog is closed by either the <code>beginButton</code> or the <code>endButton</code>, the button that closes the Dialog is set to this parameter. Otherwise, the parameter is set to <code>null</code>.
* @since 1.9.2
*/
origin: {type: "sap.m.Button"}
}
}
},
designtime: "sap/m/designtime/Dialog.designtime"
},
renderer: DialogRenderer
});
/**
* Escape handler for sap.m.Dialog control.
*
* @callback sap.m.Dialog.EscapeHandler
* @param {object} oHandlers Object with <code>resolve</code> and <code>reject</code> functions.
* @param {function():void} oHandlers.resolve Call this function if the dialog should be closed.
* @param {function():void} oHandlers.reject Call this function if the dialog should not be closed.
* @returns {void}
* @public
*/
ResponsivePaddingsEnablement.call(Dialog.prototype, {
header: {suffix: "header"},
subHeader: {selector: ".sapMDialogSubHeader .sapMIBar"},
content: {selector: ".sapMDialogScrollCont"},
footer: {selector: ".sapMDialogFooter .sapMIBar"}
});
// Add title propagation support
TitlePropagationSupport.call(Dialog.prototype, "content", function () {
return this._headerTitle ? this._headerTitle.getId() : false;
});
/**
* @type {boolean}
* @private
* @deprecated As of version 1.119, getCompatibilityVersion is deprecated.
* Consumers should behave as if 'edge' was configured
*/
Dialog._bPaddingByDefault = (Configuration.getCompatibilityVersion("sapMDialogWithPadding").compareTo("1.16") < 0);
Dialog._initIcons = function () {
if (Dialog._mIcons) {
return;
}
Dialog._mIcons = {};
Dialog._mIcons[ValueState.Success] = IconPool.getIconURI("sys-enter-2");
Dialog._mIcons[ValueState.Warning] = IconPool.getIconURI("alert");
Dialog._mIcons[ValueState.Error] = IconPool.getIconURI("error");
Dialog._mIcons[ValueState.Information] = IconPool.getIconURI("information");
};
/**
* Returns an invisible text control that can be used to label the header toolbar of
* the dialog for accessibility purposes.
*
* @param {string} sId - The ID to use for the invisible text control.
* @returns {sap.ui.core.InvisibleText} The invisible text control for the header toolbar.
* @private
*/
Dialog._getHeaderToolbarAriaLabelledByText = function() {
if (!Dialog._oHeaderToolbarInvisibleText) {
Dialog._oHeaderToolbarInvisibleText = new InvisibleText("__headerActionsToolbar-invisibleText", {
text: Library.getResourceBundleFor("sap.m").getText("ARIA_LABEL_TOOLBAR_HEADER_ACTIONS")
}).toStatic();
}
return Dialog._oHeaderToolbarInvisibleText;
};
/**
* Returns an invisible text control that can be used to label the footer toolbar of
* the dialog for accessibility purposes.
*
* @param {string} sId - The ID to use for the invisible text control.
* @returns {sap.ui.core.InvisibleText} The invisible text control for the header toolbar.
* @private
*/
Dialog._getFooterToolbarAriaLabelledByText = function() {
if (!Dialog._oFooterToolbarInvisibleText) {
Dialog._oFooterToolbarInvisibleText = new InvisibleText("__footerActionsToolbar-invisibleText", {
text: Library.getResourceBundleFor("sap.m").getText("ARIA_LABEL_TOOLBAR_FOOTER_ACTIONS")
}).toStatic();
}
return Dialog._oFooterToolbarInvisibleText;
};
/* =========================================================== */
/* begin: Lifecycle functions */
/* =========================================================== */
Dialog.prototype.init = function () {
var that = this;
this._oManuallySetSize = null;
this._oManuallySetPosition = null;
this._bRTL = Localization.getRTL();
// used to judge if enableScrolling needs to be disabled
this._scrollContentList = ["sap.m.NavContainer", "sap.m.Page", "sap.m.ScrollContainer", "sap.m.SplitContainer", "sap.m.MultiInput", "sap.m.SimpleFixFlex"];
this.oPopup = new Popup();
this.oPopup.setShadow(true);
this.oPopup.setNavigationMode("SCOPE");
this.oPopup.setModal(true);
this.oPopup.setAnimations(jQuery.proxy(this._openAnimation, this), jQuery.proxy(this._closeAnimation, this));
/**
*
* @param {Object} oPosition A new position to move the Dialog to.
* @param {boolean} bFromResize The function called from the <code>resize</code> event.
* @private
*/
this.oPopup._applyPosition = function (oPosition, bFromResize) {
that._setDimensions();
that._adjustScrollingPane();
if (that._oManuallySetPosition) {
oPosition.at = {
left: that._oManuallySetPosition.x,
top: that._oManuallySetPosition.y
};
} else {
oPosition.at = that._calcPosition();
}
//deregister the content resize handler before repositioning
that._deregisterResizeObserver();
Popup.prototype._applyPosition.call(this, oPosition);
//register the content resize handler
that._registerResizeObserver();
};
if (Dialog._bPaddingByDefault) {
this.addStyleClass("sapUiPopupWithPadding");
}
this._initTitlePropagationSupport();
this._initResponsivePaddingsEnablement();
this._oAriaDescribedbyText = new InvisibleText({id: this.getId() + "-ariaDescribedbyText"});
this._oDescribedbyDragAndResizeHandleText = new InvisibleText({id: this.getId() + "-describedbyDragAndResizeHandleText"});
};
Dialog.prototype.onBeforeRendering = function () {
this._loadVerticalMargin();
var oHeader = this._getAnyHeader();
if (!Dialog._bPaddingByDefault && this.hasStyleClass("sapUiPopupWithPadding")) {
Log.warning("Usage of CSS class 'sapUiPopupWithPadding' is deprecated. Use 'sapUiContentPadding' instead", null, "sap.m.Dialog");
}
//if content has scrolling, disable scrolling automatically
if (this._hasSingleScrollableContent()) {
this.setVerticalScrolling(false);
this.setHorizontalScrolling(false);
Log.info("VerticalScrolling and horizontalScrolling in sap.m.Dialog with ID " + this.getId() + " has been disabled because there's scrollable content inside");
} else if (!this._oScroller) {
this._oScroller = new ScrollEnablement(this, this.getId() + "-scroll", {
horizontal: this.getHorizontalScrolling(), // will be disabled in adjustScrollingPane if content can fit in
vertical: this.getVerticalScrolling()
});
}
if (this._oScroller) {
this._oScroller.setVertical(this.getVerticalScrolling());
this._oScroller.setHorizontal(this.getHorizontalScrolling());
}
this._createToolbarButtons();
if (ControlBehavior.isAccessibilityEnabled() && this.getState() != ValueState.None) {
if (!this._oValueState) {
this._oValueState = new InvisibleText();
this.setAggregation("_valueState", this._oValueState);
this.addAriaLabelledBy(this._oValueState.getId());
}
this._oValueState.setText(this.getValueStateString(this.getState()));
}
// title alignment
if (oHeader && oHeader.setTitleAlignment) {
oHeader.setTitleAlignment(this.getTitleAlignment());
}
if (oHeader && this._getTitles(oHeader).length === 0) {
oHeader._setRootAccessibilityRole("heading");
oHeader._setRootAriaLevel("2");
}
[this._getAnyHeader(), this.getSubHeader(), this._getAnyFooter()].forEach(function (oControl) {
oControl?.addStyleClass("sapMIBar-CTX");
});
this._oAriaDescribedbyText.setText(this._getAriaDescribedByText());
this._oDescribedbyDragAndResizeHandleText.setText(this._getDescribedByDragAndResizeHandleText());
};
Dialog.prototype.onAfterRendering = function () {
this._$scrollPane = this.$("scroll");
//this is not used in the control itself but is used in test and may me used from client's implementations
this._$content = this.$("cont");
this._$dialog = this.$();
if (!this.isOpen() && !this._bDuringOpenCalled) {
this._duringOpen();
}
if (this.isOpen()) {
this._setInitialFocus();
}
};
Dialog.prototype.exit = function () {
InstanceManager.removeDialogInstance(this);
this._deregisterResizeObserver();
this._deregisterWithinAreaResizeObserver();
if (this.oPopup) {
this.oPopup.detachOpened(this._handleOpened, this);
this.oPopup.detachClosed(this._handleClosed, this);
this.oPopup.destroy();
this.oPopup = null;
}
if (this._oScroller) {
this._oScroller.destroy();
this._oScroller = null;
}
if (this._header) {
this._header.destroy();
this._header = null;
}
if (this._headerTitle) {
this._headerTitle.destroy();
this._headerTitle = null;
}
if (this._iconImage) {
this._iconImage.destroy();
this._iconImage = null;
}
if (this._toolbarSpacer) {
this._toolbarSpacer.destroy();
this._toolbarSpacer = null;
}
if (this._oAriaDescribedbyText) {
this._oAriaDescribedbyText.destroy();
this._oAriaDescribedbyText = null;
}
if (this._oDescribedbyDragAndResizeHandleText) {
this._oDescribedbyDragAndResizeHandleText.destroy();
this._oDescribedbyDragAndResizeHandleText = null;
}
PreventKeyboardEvents.restore(this.getDomRef());
this._bDuringOpenCalled = false;
};
/* =========================================================== */
/* end: Lifecycle functions */
/* =========================================================== */
/* =========================================================== */
/* begin: public functions */
/* =========================================================== */
/**
* Open the dialog.
*
* @return {this} <code>this</code> to allow method chaining
* @public
*/
Dialog.prototype.open = function () {
this._bDuringOpenCalled = false;
var oPopup = this.oPopup;
var oPopupOpenState = oPopup.getOpenState();
switch (oPopupOpenState) {
case OpenState.OPEN:
case OpenState.OPENING:
return this;
case OpenState.CLOSING:
this._bOpenAfterClose = true;
break;
default:
}
if (!this.fireBeforeOpen()) {
return this;
}
//reset the close trigger
this._oCloseTrigger = null;
oPopup.attachOpened(this._handleOpened, this);
// reset scroll fix check
this._iLastWidthAndHeightWithScroll = null;
// Open popup
oPopup.setContent(this);
oPopup.open();
this._registerWithinAreaResizeObserver();
InstanceManager.addDialogInstance(this);
return this;
};
/**
* Close the dialog.
*
* @return {this} <code>this</code> to allow method chaining
* @public
*/
Dialog.prototype.close = function () {
this._bOpenAfterClose = false;
var oPopup = this.oPopup;
var eOpenState = this.oPopup.getOpenState();
if (eOpenState === OpenState.CLOSED || eOpenState === OpenState.CLOSING) {
return this;
}
if (!this.fireBeforeClose({origin: this._oCloseTrigger})) {
return this;
}
this._deregisterWithinAreaResizeObserver();
library.closeKeyboard();
oPopup.attachClosed(this._handleClosed, this);
this._bDisableRepositioning = false;
//reset the drag and/or resize
this._oManuallySetPosition = null;
this._oManuallySetSize = null;
oPopup.close();
this._deregisterResizeObserver();
return this;
};
/**
* The method checks if the Dialog is open.
*
* It returns <code>true</code> when the Dialog is currently open (this includes opening and closing animations),
* otherwise it returns <code>false</code>.
*
* @returns {boolean} Whether the dialog is open.
* @public
* @since 1.9.1
*/
Dialog.prototype.isOpen = function () {
return !!this.oPopup && this.oPopup.isOpen();
};
/*
* @inheritdoc
*/
Dialog.prototype.setIcon = function (sIcon) {
this._bHasCustomIcon = true;
return this.setProperty("icon", sIcon);
};
/*
* @inheritdoc
*/
Dialog.prototype.setState = function (sState) {
var sDefaultIcon;
this.setProperty("state", sState);
if (this._bHasCustomIcon) {
return this;
}
if (sState === ValueState.None) {
sDefaultIcon = "";
} else {
Dialog._initIcons();
sDefaultIcon = Dialog._mIcons[sState];
}
this.setProperty("icon", sDefaultIcon);
return this;
};
/* =========================================================== */
/* end: public functions */
/* =========================================================== */
/* =========================================================== */
/* begin: event handlers */
/* =========================================================== */
/**
*
* @private
*/
Dialog.prototype._handleOpened = function () {
this.oPopup.detachOpened(this._handleOpened, this);
this._setInitialFocus();
this.fireAfterOpen();
PreventKeyboardEvents.restore(this.getDomRef());
};
/**
*
* @private
*/
Dialog.prototype._handleClosed = function () {
PreventKeyboardEvents.restore(this.getDomRef());
// TODO: remove the following three lines after the popup open state problem is fixed
if (!this.oPopup) {
return;
}
this.oPopup.detachClosed(this._handleClosed, this);
if (this.getDomRef()) {
// Not removing the content DOM leads to the problem that control DOM with the same ID exists in two places if
// the control is added to a different aggregation without the dialog being destroyed. In this special case the
// RichTextEditor (as an example) renders a textarea-element and afterwards tells the TinyMCE component which ID
// to use for rendering; since there are two elements with the same ID at that point, it does not work.
// As the Dialog can only contain other controls, we can safely discard the DOM - we cannot do this inside
// the Popup, since it supports displaying arbitrary HTML content.
RenderManager.preserveContent(this.getDomRef());
this.$().remove();
}
InstanceManager.removeDialogInstance(this);
this.fireAfterClose({origin: this._oCloseTrigger});
this._bDuringOpenCalled = false;
if (this._bOpenAfterClose) {
this._bOpenAfterClose = false;
this.open();
}
};
/**
* Executed once during the opening of the dialog, after it is rendered.
* @private
*/
Dialog.prototype._duringOpen = function () {
PreventKeyboardEvents.preventOnce(this.getDomRef());
if (Device.system.desktop) {
this.oPopup.setInitialFocusId(this._determineInitialFocusId());
} else {
// Set the initial focus to the dialog itself.
// The initial focus should be set because otherwise the first focusable element will be focused.
// This first element can be input or textarea which will trigger the keyboard to open (mobile device).
// The focus will be change after the dialog is opened;
this.oPopup.setInitialFocusId(this.getId());
}
this._bDuringOpenCalled = true;
};
/**
* Event handler for the <code>focusin</code> event.
* If it occurs on the invisible element at the beginning of the Dialog, the focus is set on the last focusable element of the Dialog, and vice versa.
* @param {jQuery.Event} oEvent The event object
* @private
*/
Dialog.prototype.onfocusin = function (oEvent) {
var oSourceDomRef = oEvent.target;
//Check if the invisible FIRST focusable element (suffix '-firstfe') has gained focus
if (oSourceDomRef.id === this.getId() + "-firstfe") {
//Check if buttons are available
var oLastFocusableDomRef =
this._getAnyFooter()?.$().lastFocusableDomRef() ||
this.$("cont").lastFocusableDomRef({
includeSelf: true,
includeScroller: true
}) ||
this.getSubHeader()?.$().firstFocusableDomRef() ||
this._getAnyHeader()?.$().lastFocusableDomRef();
if (oLastFocusableDomRef) {
oLastFocusableDomRef.focus();
}
return;
}
if (oSourceDomRef.id === this.getId() + "-lastfe") {
//Check if the invisible LAST focusable element (suffix '-lastfe') has gained focus
//First check if header content is available
var oFirstFocusableDomRef = this.getDomRef("dragAndResizeHandler") ||
this._getAnyHeader()?.$().firstFocusableDomRef() ||
this.getSubHeader()?.$().firstFocusableDomRef() ||
this.$("cont").firstFocusableDomRef({
includeSelf: true,
includeScroller: true
}) ||
this.$("footer").firstFocusableDomRef();
if (oFirstFocusableDomRef) {
oFirstFocusableDomRef.focus();
}
}
};
/**
* Makes sure the app developer will always have access to the last created Promise.
* @returns {{reject: reject, resolve: resolve}}
* @private
*/
Dialog.prototype._getPromiseWrapper = function () {
var that = this;
return {
reject: function () {
that.currentPromise.reject();
},
resolve: function () {
that.currentPromise.resolve();
}
};
};
/**
* Event handler for the escape key pressed event.
* If it occurs and the developer hasn't defined the <code>escapeHandler</code> property, the Dialog is immediately closed.
* Otherwise, the <code>escapeHandler</code> is executed and the developer may prevent the closing of the Dialog.
* @param {jQuery.Event} oEvent The event object
* @private
*/
Dialog.prototype.onsapescape = function(oEvent) {
var oEscapeHandler = this.getEscapeHandler(),
oPromiseArgument = {},
that = this;
if (this._isSpacePressed) {
return;
}
if (oEvent.originalEvent && oEvent.originalEvent._sapui_handledByControl) {
return;
}
this._oCloseTrigger = null;
if (typeof oEscapeHandler === 'function') {
// create a Promise to allow app developers to hook to the 'escape' event
// and prevent the closing of the dialog by executing the escape handler function they defined
new Promise(function (resolve, reject) {
oPromiseArgument.resolve = resolve;
oPromiseArgument.reject = reject;
that.currentPromise = oPromiseArgument;
oEscapeHandler(that._getPromiseWrapper());
})
.then(function (result) {
that.close();
})
.catch(function () {
Log.info("Disallow dialog closing");
});
} else {
this.close();
}
//event should not trigger any further actions
oEvent.stopPropagation();
};
/**
* Event handler for the onkeyup event.
* Register if SPACE is released.
*
* @param {jQuery.Event} oEvent The event object
* @private
*/
Dialog.prototype.onkeyup = function (oEvent) {
if (this._isSpace(oEvent)) {
this._isSpacePressed = false;
}
};
/**
* Event handler for the onkeydown event.
* Register if SPACE is pressed.
*
* @param {jQuery.Event} oEvent The event object
* @private
*/
Dialog.prototype.onkeydown = function (oEvent) {
if (this._isSpace(oEvent)) {
this._isSpacePressed = true;
}
var iKeyCode = oEvent.which || oEvent.keyCode;
if ((oEvent.ctrlKey || oEvent.metaKey) && iKeyCode === KeyCodes.ENTER) {
var oPositiveButton = this._findFirstPositiveButton();
if (oPositiveButton) {
oPositiveButton.firePress();
oEvent.stopPropagation();
oEvent.preventDefault();
return;
}
}
this._handleKeyboardDragResize(oEvent);
};
/**
* Finds first positive button
* We call positive the buttons with type "Accept" or "Emphasized"
*
* @private
*/
Dialog.prototype._findFirstPositiveButton = function () {
var aButtons;
if (this.getFooter()) {
aButtons = this.getFooter().getContent().filter(function (oItem) {
return oItem.isA("sap.m.Button");
});
} else {
aButtons = this.getButtons();
}
for (var i = 0; i < aButtons.length; i++) {
var oButton = aButtons[i];
if (oButton.getType() === ButtonType.Accept || oButton.getType() === ButtonType.Emphasized) {
return oButton;
}
}
};
/**
* Handles the keyboard drag/resize functionality
*
* @param {jQuery.Event} oEvent The event object
* @private
*/
Dialog.prototype._handleKeyboardDragResize = function (oEvent) {
if (oEvent.target !== this.getDomRef("dragAndResizeHandler") ||
[KeyCodes.ARROW_LEFT,
KeyCodes.ARROW_RIGHT,
KeyCodes.ARROW_UP,
KeyCodes.ARROW_DOWN].indexOf(oEvent.keyCode) === -1) {
return;
}
if ((!this.getResizable() && oEvent.shiftKey) ||
(!this.getDraggable() && !oEvent.shiftKey)) {
return;
}
var $this = this._$dialog,
oBoundingClientRect = this.getDomRef().getBoundingClientRect(),
mOffset = {
left: oBoundingClientRect.x,
top: oBoundingClientRect.y
},
oAreaDimensions = this._getAreaDimensions(),
iDialogWidth = $this.width(),
iDialogHeight = $this.height(),
iDialogOuterHeight = $this.outerHeight(true),
bResize = oEvent.shiftKey,
mStyles,
iMaxHeight;
this._bDisableRepositioning = true;
if (bResize) {
this._oManuallySetSize = true;
this.$('cont').height('').width('');
}
switch (oEvent.keyCode) {
case KeyCodes.ARROW_LEFT:
if (bResize) {
iDialogWidth -= DRAGRESIZE_STEP;
} else {
mOffset.left -= DRAGRESIZE_STEP;
}
break;
case KeyCodes.ARROW_RIGHT:
if (bResize) {
iDialogWidth += DRAGRESIZE_STEP;
} else {
mOffset.left += DRAGRESIZE_STEP;
}
break;
case KeyCodes.ARROW_UP:
if (bResize) {
iDialogHeight -= DRAGRESIZE_STEP;
} else {
mOffset.top -= DRAGRESIZE_STEP;
}
break;
case KeyCodes.ARROW_DOWN:
if (bResize) {
iDialogHeight += DRAGRESIZE_STEP;
} else {
mOffset.top += DRAGRESIZE_STEP;
}
break;
}
if (bResize) {
iMaxHeight = oAreaDimensions.bottom - mOffset.top - iDialogOuterHeight + iDialogHeight;
if (oEvent.keyCode === KeyCodes.ARROW_DOWN) {
iMaxHeight -= DRAGRESIZE_STEP;
}
mStyles = {
width: Math.min(iDialogWidth, oAreaDimensions.right - mOffset.left),
height: Math.min(iDialogHeight, iMaxHeight)
};
} else {
mStyles = {
left: Math.min(Math.max(oAreaDimensions.left, mOffset.left), oAreaDimensions.right - iDialogWidth),
top: Math.min(Math.max(oAreaDimensions.top, mOffset.top), oAreaDimensions.bottom - iDialogOuterHeight)
};
}
$this.css(mStyles);
};
/**
* Determines if the key from oEvent is SPACE.
*
* @param {jQuery.Event} oEvent The event object
* @private
* @return {boolean} True if the key from the event is space
*/
Dialog.prototype._isSpace = function (oEvent) {
var iKeyCode = oEvent.which || oEvent.key;
return iKeyCode == KeyCodes.SPACE;
};
/* =========================================================== */
/* end: event handlers */
/* =========================================================== */
/* =========================================================== */
/* begin: private functions */
/* =========================================================== */
/**
*
* @param {Object} $Ref
* @param {number} iRealDuration
* @param {function} fnOpened
* @private
*/
Dialog.prototype._openAnimation = function ($Ref, iRealDuration, fnOpened) {
$Ref.addClass("sapMDialogOpen");
setTimeout(fnOpened, iAnimationDuration);
};
/**
*
* @param {Object} $Ref
* @param {number} iRealDuration
* @param {function} fnClose
* @private
*/
Dialog.prototype._closeAnimation = function ($Ref, iRealDuration, fnClose) {
$Ref.removeClass("sapMDialogOpen");
setTimeout(fnClose, iAnimationDuration);
};
/**
*
* @private
*/
Dialog.prototype._setDimensions = function () {
var $this = this.$(),
bStretch = this.getStretch(),
bMessageType = this.getType() === DialogType.Message,
oStyles = {};
//the initial size is set in the renderer when the dom is created
if (!bStretch) {
//set the size to the content
if (!this._oManuallySetSize) {
oStyles.width = this.getContentWidth() || undefined;
oStyles.height = this.getContentHeight() || undefined;
} else {
oStyles.width = this._oManuallySetSize.width;
oStyles.height = this._oManuallySetSize.height;
}
}
if (oStyles.width == 'auto') {
oStyles.width = undefined;
}
if (oStyles.height == 'auto') {
oStyles.height = undefined;
}
if (bStretch && !bMessageType) {
this.$().addClass('sapMDialogStretched');
}
/**
* @deprecated As of version 1.11.2
*/
if (this.getStretchOnPhone() && Device.system.phone) {
this.$().addClass('sapMDialogStretched');
}
$this.css(oStyles);
$this.css(this._calcMaxSizes());
if (!this._oManuallySetSize && !this._bDisableRepositioning) {
$this.css(this._calcPosition());
}
//In Chrome when the dialog is stretched the footer is not rendered in the right position;
if (window.navigator.userAgent.toLowerCase().indexOf("chrome") !== -1 && this.getStretch()) {
//forcing repaint
$this.find('> footer').css({bottom: '0.001px'});
}
};
/**
*
* @private
*/
Dialog.prototype._adjustScrollingPane = function () {
if (this._oScroller) {
this._oScroller.refresh();
}
};
/**
* @private
*/
Dialog.prototype._onResize = function () {
if (!this.getDomRef()) {
return;
}
var $dialog = this.$(),
$dialogContent = this.$('cont'),
sContentWidth = this.getContentWidth(),
iMaxDialogWidth = this._calcMaxSizes().maxWidth, // 90% of the max screen size
oSubHeaderDomRef = this.getSubHeader()?.getDomRef(),
oHeaderDomRef = (this.getCustomHeader() || this._header)?.getDomRef();
if (oHeaderDomRef || oSubHeaderDomRef) {
const iHeaderHeight = oHeaderDomRef ? oHeaderDomRef.getBoundingClientRect().height : 0;
const iSubHeaderHeight = oSubHeaderDomRef ? oSubHeaderDomRef.getBoundingClientRect().height : 0;
this.getDomRef().style.paddingTop = iHeaderHeight + iSubHeaderHeight + "px";
}
//if height is set by manually resizing return;
if (this._oManuallySetSize) {
$dialogContent.css({
width: 'auto'
});
return;
}
// Browsers except chrome do not increase the width of the container to include scrollbar (when width is auto). So we need to compensate
if (Device.system.desktop && !Device.browser.chrome) {
var iCurrentWidthAndHeight = $dialogContent.width() + "x" + $dialogContent.height(),
bMinWidth = $dialog.css("min-width") !== $dialog.css("width");
// Apply the fix only if width or height did actually change.
// And when the width is not equal to the min-width.
if (iCurrentWidthAndHeight !== this._iLastWidthAndHeightWithScroll && bMinWidth) {
if (this._hasVerticalScrollbar() && // - there is a vertical scroll
(!sContentWidth || sContentWidth == 'auto') && // - when the developer hasn't set it explicitly
!this.getStretch() && // - when the dialog is not stretched
$dialogContent.width() < iMaxDialogWidth) { // - if the dialog can't grow anymore
$dialog.addClass("sapMDialogVerticalScrollIncluded");
$dialogContent.css({"padding-right" : SCROLLBAR_WIDTH});
this._iLastWidthAndHeightWithScroll = iCurrentWidthAndHeight;
} else {
$dialog.removeClass("sapMDi