Skip to content

Commit dfc5d78

Browse files
tltvsissbruecker
andauthored
feat!: add support for server side ModalityMode (#8069)
* feat!: add support for server side ModalityMode Introduces Dialog#setModal(ModalityMode) with three options: STRICT, VISUAL and MODELESS. Changes a Dialog to have ModalityMode#VISUAL by default instead of being strict modal. LoginOverlay has ModalityMode#STRICT by default instead of being modeless. Deprecates Dialog#setModal(boolean). Part of: vaadin/flow#22279 * Formatting * Removed unnecessary constructor * docs: Dialogs are modal by default * docs: ConfirmDialog and LoginOverlay are modal in STRICT mode * cleanup * fixed LoginOverlay modality * cleanup * cleanup --------- Co-authored-by: Sascha Ißbrücker <[email protected]>
1 parent 65c0cd3 commit dfc5d78

File tree

13 files changed

+252
-74
lines changed

13 files changed

+252
-74
lines changed

vaadin-confirm-dialog-flow-parent/vaadin-confirm-dialog-flow/src/main/java/com/vaadin/flow/component/confirmdialog/ConfirmDialog.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.vaadin.flow.component.HasComponents;
2626
import com.vaadin.flow.component.HasSize;
2727
import com.vaadin.flow.component.HasStyle;
28+
import com.vaadin.flow.component.ModalityMode;
2829
import com.vaadin.flow.component.Synchronize;
2930
import com.vaadin.flow.component.Tag;
3031
import com.vaadin.flow.component.UI;
@@ -53,6 +54,8 @@
5354
* Each Confirm Dialog should have a title and/or message. The “Confirm” button
5455
* is shown by default, while the two other buttons are not (they must be
5556
* explicitly enabled to be displayed).
57+
* <p>
58+
* Confirm Dialog is modal in {@link ModalityMode#STRICT} mode.
5659
*
5760
* @author Vaadin Ltd
5861
*/
@@ -238,12 +241,15 @@ public Optional<String> getAriaDescribedBy() {
238241
*/
239242
public ConfirmDialog() {
240243
// Initialize auto-add behavior
241-
new OverlayAutoAddController<>(this, () -> true);
244+
new OverlayAutoAddController<>(this, () -> ModalityMode.STRICT);
242245

243246
setOpened(false);
244247

245248
getElement().addPropertyChangeListener("opened", event -> {
246-
setModality(isOpened());
249+
if (isAttached()) {
250+
getUI().ifPresent(
251+
ui -> ui.setChildComponentModal(this, isOpened()));
252+
}
247253
fireEvent(new OpenedChangeEvent(this, event.isUserOriginated()));
248254
});
249255
}
@@ -731,10 +737,4 @@ public void removeAll() {
731737
}
732738
});
733739
}
734-
735-
private void setModality(boolean modal) {
736-
if (isAttached()) {
737-
getUI().ifPresent(ui -> ui.setChildComponentModal(this, modal));
738-
}
739-
}
740740
}

vaadin-context-menu-flow-parent/vaadin-context-menu-flow/src/main/java/com/vaadin/flow/component/contextmenu/ContextMenuBase.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.vaadin.flow.component.ComponentEvent;
2424
import com.vaadin.flow.component.ComponentEventListener;
2525
import com.vaadin.flow.component.HasStyle;
26+
import com.vaadin.flow.component.ModalityMode;
2627
import com.vaadin.flow.component.Synchronize;
2728
import com.vaadin.flow.component.Tag;
2829
import com.vaadin.flow.component.UI;
@@ -100,7 +101,7 @@ public ContextMenuBase() {
100101
});
101102

102103
overlayAutoAddController = new OverlayAutoAddController<>(this,
103-
() -> false);
104+
() -> ModalityMode.MODELESS);
104105
}
105106

106107
/**

vaadin-dialog-flow-parent/vaadin-dialog-flow-integration-tests/src/main/java/com/vaadin/flow/component/dialog/tests/ModalityDialogsPage.java

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.vaadin.flow.component.dialog.tests;
1717

18+
import com.vaadin.flow.component.ModalityMode;
1819
import com.vaadin.flow.component.dialog.Dialog;
1920
import com.vaadin.flow.component.html.Div;
2021
import com.vaadin.flow.component.html.Hr;
@@ -33,21 +34,27 @@ public class ModalityDialogsPage extends Div {
3334
private Log log = new Log();
3435

3536
public ModalityDialogsPage() {
36-
Dialog modalDialog = createModalDialog();
37+
Dialog strictModalDialog = createStrictModalDialog();
38+
39+
Dialog visualModalDialog = createVisualModalDialog();
3740

3841
Dialog nonModalDialog = setupNonModalDialog();
3942

40-
NativeButton openModal = new NativeButton("Open modal dialog",
41-
event -> modalDialog.open());
42-
openModal.setId("open-modal-dialog");
43+
NativeButton openModal = new NativeButton("Open STRICT modal dialog",
44+
event -> strictModalDialog.open());
45+
openModal.setId("open-strict-modal-dialog");
46+
47+
NativeButton openVisualModal = new NativeButton(
48+
"Open VISUAL modal dialog", event -> visualModalDialog.open());
49+
openVisualModal.setId("open-visual-modal-dialog");
4350

4451
NativeButton addModal = new NativeButton("Add modal dialog to UI",
45-
event -> add(modalDialog));
52+
event -> add(strictModalDialog));
4653
addModal.setId("add-modal-dialog");
4754

4855
NativeButton enableCloseOnOutsideClick = new NativeButton(
4956
"Enable close on outside click",
50-
event -> modalDialog.setCloseOnOutsideClick(true));
57+
event -> strictModalDialog.setCloseOnOutsideClick(true));
5158
enableCloseOnOutsideClick.setId("enable-close-on-outside-click");
5259

5360
NativeButton openNonModal = new NativeButton("Open non modal dialog",
@@ -58,12 +65,17 @@ public ModalityDialogsPage() {
5865
event -> this.log.log("Clicked"));
5966
log.setId("log");
6067

61-
final NativeButton showModal = new NativeButton("Show Hidden Modal",
62-
e -> modalDialog.setVisible(true));
63-
showModal.setId("show");
68+
final NativeButton showStrictModal = new NativeButton(
69+
"Show Hidden Modal", e -> strictModalDialog.setVisible(true));
70+
showStrictModal.setId("show-strict-modal");
71+
72+
final NativeButton showVisualModal = new NativeButton(
73+
"Show Visual Modal", e -> visualModalDialog.setVisible(true));
74+
showVisualModal.setId("show-visual-modal");
6475

65-
add(openModal, addModal, enableCloseOnOutsideClick, showModal,
66-
openNonModal, log, new Hr(), this.log);
76+
add(openModal, addModal, enableCloseOnOutsideClick, showStrictModal,
77+
openVisualModal, showVisualModal, openNonModal, log, new Hr(),
78+
this.log);
6779
}
6880

6981
private Dialog setupNonModalDialog() {
@@ -73,8 +85,23 @@ private Dialog setupNonModalDialog() {
7385
return nonModalDialog;
7486
}
7587

76-
private Dialog createModalDialog() {
88+
private Dialog createVisualModalDialog() {
89+
Dialog visualModalDialog = new Dialog();
90+
visualModalDialog.setModality(ModalityMode.VISUAL);
91+
visualModalDialog.setCloseOnOutsideClick(false);
92+
final NativeButton modalClose = new NativeButton("close",
93+
e -> visualModalDialog.close());
94+
modalClose.setId("close");
95+
final NativeButton hide = new NativeButton("hide",
96+
e -> visualModalDialog.setVisible(false));
97+
hide.setId("hide");
98+
visualModalDialog.add(modalClose, hide);
99+
return visualModalDialog;
100+
}
101+
102+
private Dialog createStrictModalDialog() {
77103
Dialog modalDialog = new Dialog();
104+
modalDialog.setModality(ModalityMode.STRICT);
78105
modalDialog.setCloseOnOutsideClick(false);
79106
final NativeButton modalClose = new NativeButton("close",
80107
e -> modalDialog.close());
@@ -106,7 +133,7 @@ private Dialog createNonModalDialog() {
106133
Dialog nonModalDialog = new Dialog();
107134

108135
nonModalDialog.setCloseOnOutsideClick(false);
109-
nonModalDialog.setModal(false);
136+
nonModalDialog.setModality(ModalityMode.MODELESS);
110137

111138
return nonModalDialog;
112139
}

vaadin-dialog-flow-parent/vaadin-dialog-flow-integration-tests/src/test/java/com/vaadin/flow/component/dialog/tests/ModalityDialogsPageIT.java

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,27 @@ public void openNonModalDialog_logButtonClickable() {
5353
}
5454

5555
@Test
56-
public void openModalDialog_removeBackdrop_logClickNotAccepted() {
57-
$(NativeButtonElement.class).id("open-modal-dialog").click();
56+
public void openVisualModalDialog_modalityCurtainVisible_logButtonClickable() {
57+
$(NativeButtonElement.class).id("open-visual-modal-dialog").click();
58+
verifyNumberOfDialogs(1);
59+
60+
getDialog().$("*").id("overlay").$(DivElement.class).id("backdrop");
61+
62+
// Even with the modality curtain, button is programmatically clickable.
63+
$(NativeButtonElement.class).id("log").click();
64+
65+
verifyOpened();
66+
Assert.assertEquals("Click should have resulted in a log message", 1,
67+
$(DivElement.class).id(LOG_ID).$("div").all().size());
68+
69+
getDialog().$(NativeButtonElement.class).first().click();
70+
71+
verifyClosedAndRemoved();
72+
}
73+
74+
@Test
75+
public void openStrictModalDialog_removeBackdrop_logClickNotAccepted() {
76+
$(NativeButtonElement.class).id("open-strict-modal-dialog").click();
5877
final DivElement backdrop = getDialog().$("*").id("overlay")
5978
.$(DivElement.class).id("backdrop");
6079

@@ -81,11 +100,11 @@ public void openModalDialog_removeBackdrop_logClickNotAccepted() {
81100
}
82101

83102
@Test
84-
public void openModalDialog_outsideClickToCloseDialog_logButtonClickable() {
103+
public void openStrictModalDialog_outsideClickToCloseDialog_logButtonClickable() {
85104
$(NativeButtonElement.class).id("add-modal-dialog").click();
86105
$(NativeButtonElement.class).id("enable-close-on-outside-click")
87106
.click();
88-
$(NativeButtonElement.class).id("open-modal-dialog").click();
107+
$(NativeButtonElement.class).id("open-strict-modal-dialog").click();
89108

90109
// Click anything to close dialog
91110
$("body").first().click();
@@ -98,8 +117,8 @@ public void openModalDialog_outsideClickToCloseDialog_logButtonClickable() {
98117
}
99118

100119
@Test
101-
public void openModalDialog_openNonModalOnTop_nonModalCanBeUsed() {
102-
$(NativeButtonElement.class).id("open-modal-dialog").click();
120+
public void openStrictModalDialog_openNonModalOnTop_nonModalCanBeUsed() {
121+
$(NativeButtonElement.class).id("open-strict-modal-dialog").click();
103122

104123
final DialogElement dialog = getDialog();
105124
dialog.$(NativeButtonElement.class).id("open-sub").click();
@@ -120,8 +139,30 @@ public void openModalDialog_openNonModalOnTop_nonModalCanBeUsed() {
120139
}
121140

122141
@Test
123-
public void openModalDialog_hideComponent_logClickAccepted() {
124-
$(NativeButtonElement.class).id("open-modal-dialog").click();
142+
public void openStrictModalDialog_hideComponent_logClickAccepted() {
143+
$(NativeButtonElement.class).id("open-strict-modal-dialog").click();
144+
145+
getDialog().$(NativeButtonElement.class).id("hide").click();
146+
147+
verifyOpened();
148+
Assert.assertFalse("Dialog should be hidden",
149+
getDialog().isDisplayed());
150+
151+
$(NativeButtonElement.class).id("log").click();
152+
153+
Assert.assertEquals("Click should have resulted in a log message", 1,
154+
$(DivElement.class).id(LOG_ID).$("div").all().size());
155+
156+
$(NativeButtonElement.class).id("show-strict-modal").click();
157+
158+
getDialog().$(NativeButtonElement.class).id("close").click();
159+
160+
verifyClosedAndRemoved();
161+
}
162+
163+
@Test
164+
public void openVisualModalDialog_hideComponent_logClickAccepted() {
165+
$(NativeButtonElement.class).id("open-visual-modal-dialog").click();
125166

126167
getDialog().$(NativeButtonElement.class).id("hide").click();
127168

@@ -134,7 +175,7 @@ public void openModalDialog_hideComponent_logClickAccepted() {
134175
Assert.assertEquals("Click should have resulted in a log message", 1,
135176
$(DivElement.class).id(LOG_ID).$("div").all().size());
136177

137-
$(NativeButtonElement.class).id("show").click();
178+
$(NativeButtonElement.class).id("show-visual-modal").click();
138179

139180
getDialog().$(NativeButtonElement.class).id("close").click();
140181

vaadin-dialog-flow-parent/vaadin-dialog-flow/src/main/java/com/vaadin/flow/component/dialog/Dialog.java

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.vaadin.flow.component.HasComponents;
3333
import com.vaadin.flow.component.HasSize;
3434
import com.vaadin.flow.component.HasStyle;
35+
import com.vaadin.flow.component.ModalityMode;
3536
import com.vaadin.flow.component.Synchronize;
3637
import com.vaadin.flow.component.Tag;
3738
import com.vaadin.flow.component.UI;
@@ -52,7 +53,8 @@
5253
* <p>
5354
* Dialogs can be made modal or non-modal. A modal Dialog blocks the user from
5455
* interacting with the rest of the user interface while the Dialog is open, as
55-
* opposed to a non-modal Dialog, which does not block interaction.
56+
* opposed to a non-modal Dialog, which does not block interaction. Dialogs are
57+
* modal by default using {@link ModalityMode#VISUAL}.
5658
* <p>
5759
* Dialogs can be made draggable and resizable. When draggable, the user is able
5860
* to move them around using a pointing device. It is recommended to make
@@ -88,6 +90,7 @@ public class Dialog extends Component implements HasComponents, HasSize,
8890
private String maxHeight;
8991
private DialogHeader dialogHeader;
9092
private DialogFooter dialogFooter;
93+
private ModalityMode modality = ModalityMode.VISUAL;
9194

9295
/**
9396
* Creates an empty dialog.
@@ -125,7 +128,7 @@ public Dialog() {
125128
setRole("dialog");
126129

127130
// Initialize auto-add behavior
128-
new OverlayAutoAddController<>(this, this::isModal);
131+
new OverlayAutoAddController<>(this, this::getModality);
129132
}
130133

131134
/**
@@ -602,29 +605,73 @@ public void close() {
602605
* Sets whether component will open modal or modeless dialog.
603606
* <p>
604607
* Note: When dialog is set to be modeless, then it's up to you to provide
605-
* means for it to be closed (eg. a button that calls
608+
* means for it to be closed (e.g. a button that calls
606609
* {@link Dialog#close()}). The reason being that a modeless dialog allows
607610
* user to interact with the interface under it and won't be closed by
608611
* clicking outside or the ESC key.
609612
*
610613
* @param modal
611614
* {@code false} to enable dialog to open as modeless modal,
612615
* {@code true} otherwise.
616+
* @deprecated use {@link #setModality(ModalityMode)} instead
613617
*/
618+
@Deprecated(since = "25.0", forRemoval = true)
614619
public void setModal(boolean modal) {
615-
getElement().setProperty("modeless", !modal);
616-
getUI().ifPresent(ui -> ui.setChildComponentModal(this, modal));
620+
setModality(modal ? ModalityMode.STRICT : ModalityMode.MODELESS);
617621
}
618622

619623
/**
620624
* Gets whether component is set as modal or modeless dialog.
621625
*
622626
* @return {@code true} if modal dialog (default), {@code false} otherwise.
627+
* @deprecated use {@link #getModality()} instead
623628
*/
629+
@Deprecated(since = "25.0", forRemoval = true)
624630
public boolean isModal() {
625631
return !getElement().getProperty("modeless", false);
626632
}
627633

634+
/**
635+
* Sets the modality of the dialog. The following modes are available:
636+
* <ul>
637+
* <li>{@link ModalityMode#STRICT}: The dialog shows a modality curtain and
638+
* users can not interact with components outside the dialog. Client-side
639+
* events and RPC calls from components outside the dialog are blocked.
640+
* <li>{@link ModalityMode#VISUAL}: The dialog shows a modality curtain and
641+
* users can not interact with components outside the dialog. However,
642+
* client-side events and RPC calls from components outside the dialog are
643+
* allowed.
644+
* <li>{@link ModalityMode#MODELESS}: The dialog does not show a modality
645+
* curtain and users can interact with components outside the dialog.
646+
* Client-side events and RPC calls from components outside the dialog are
647+
* allowed.
648+
* </ul>
649+
* <p>
650+
* Note: When dialog is set to be {@link ModalityMode#MODELESS}, then it's
651+
* up to you to provide means for it to be closed (e.g. a button that calls
652+
* {@link Dialog#close()}). The reason being that a modeless dialog allows
653+
* user to interact with the interface under it and won't be closed by
654+
* clicking outside or the ESC key.
655+
*
656+
* @param mode
657+
* the modality mode, not null
658+
*/
659+
public void setModality(ModalityMode mode) {
660+
this.modality = Objects.requireNonNull(mode,
661+
"ModalityMode must not be null");
662+
getElement().setProperty("modeless", mode == ModalityMode.MODELESS);
663+
getUI().ifPresent(ui -> ui.setChildComponentModal(this, mode));
664+
}
665+
666+
/**
667+
* Gets the modality of the dialog. {@link ModalityMode#VISUAL} by default.
668+
*
669+
* @return the modality mode, not null
670+
*/
671+
public ModalityMode getModality() {
672+
return modality;
673+
}
674+
628675
/**
629676
* Sets whether dialog is enabled to be dragged by the user or not.
630677
* <p>
@@ -885,8 +932,8 @@ public Element getElement() {
885932
public void setVisible(boolean visible) {
886933
super.setVisible(visible);
887934

888-
getUI().ifPresent(
889-
ui -> ui.setChildComponentModal(this, visible && isModal()));
935+
getUI().ifPresent(ui -> ui.setChildComponentModal(this,
936+
visible && isModal() ? getModality() : ModalityMode.MODELESS));
890937
}
891938

892939
/**
@@ -975,7 +1022,8 @@ public boolean isOpened() {
9751022

9761023
private void setModality(boolean modal) {
9771024
if (isAttached()) {
978-
getUI().ifPresent(ui -> ui.setChildComponentModal(this, modal));
1025+
getUI().ifPresent(ui -> ui.setChildComponentModal(this,
1026+
modal ? getModality() : ModalityMode.MODELESS));
9791027
}
9801028
}
9811029

0 commit comments

Comments
 (0)