Trying to create Dialog from MenuItem and updating url parameters?

I am trying to create a Dialog from the MenuItem of the SideNav, I manage to open it, the problem is that if a user wants to access from the navigation URL, the Dialog does not open, but the components are displayed without the AppLayout.

The ideal would be that if we navigate by the URL the same Dialog opens, any interesting idea ?

Thank you.

navigable-dialog

@Log4j2
//@PageTitle("Settings")
@Route(value = "settings", layout = MainLayout.class)
@RolesAllowed("ADMIN")
public class SettingsDialogView extends Dialog implements AfterNavigationObserver, HasUrlParameter<String> {

    private Layout mainLayout = new Layout();


    public SettingsDialogView() {
        //addClassNames(AlignItems.START, Display.FLEX);
       init();
    }

    private void init() {
        addClassNames(Display.FLEX, LumoUtility.FlexDirection.ROW, LumoUtility.Height.FULL);
        this.setHeaderTitle("Settings");
        final Button closeButton = new Button(new Icon("lumo", "cross"),
                (event) -> super.close());
        super.getHeader().add(closeButton);

        super.add(createLinks(), createForm());
        open();
    }


    public Component createForm() {
        //Not the first time
        this.mainLayout.add(createPublicInformation());
        this.mainLayout.addClassNames(BoxSizing.BORDER, MaxWidth.SCREEN_SMALL, Padding.LARGE);
        this.mainLayout.setFlexDirection(Layout.FlexDirection.COLUMN);
        return mainLayout;
    }


//    private Div createContent() {
//        this.content = new Div();
//        this.content.addClassNames("flex-1", LumoUtility.Overflow.AUTO);
//        return this.content;
//    }

    public Component createPublicInformation() {
        H2 title = new H2("Public information");
        title.addClassNames(LumoUtility.FontSize.XLARGE, LumoUtility.Margin.Top.MEDIUM);
        title.setId(title.getText().replace(" ", "-").toLowerCase());

        Paragraph description = new Paragraph("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
        description.addClassNames(LumoUtility.FontSize.SMALL, LumoUtility.TextColor.SECONDARY);

        Avatar avatar = new Avatar("Emily Johnson");
        avatar.addThemeVariants(AvatarVariant.LUMO_XLARGE);
        avatar.setImage("https://images.unsplash.com/photo-1529626455594-4ff0802cfb7e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80");

        Button uploadButton = new Button("Upload");
        uploadButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);

        Upload upload = new Upload();
        upload.setDropAllowed(false);
        upload.setMaxFiles(1);
        upload.setUploadButton(uploadButton);

        Button delete = new Button("Delete");
        delete.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);

        Layout avatarLayout = new Layout(avatar, upload, delete);
        avatarLayout.addClassNames(LumoUtility.Margin.Bottom.XSMALL, LumoUtility.Margin.Top.MEDIUM);
        avatarLayout.setAlignItems(Layout.AlignItems.CENTER);
        avatarLayout.setGap(Layout.Gap.LARGE);

        TextField username = new TextField("Username");
        TextField firstName = new TextField("First name");
        TextField lastName = new TextField("Last name");

        Layout layout = new Layout(title, description, avatarLayout, username, firstName, lastName);
        // Viewport < 1024px
        layout.setFlexDirection(Layout.FlexDirection.COLUMN);
        // Viewport > 1024px
        layout.setDisplay(Breakpoint.LARGE, Layout.Display.GRID);
        layout.setColumns(Layout.GridColumns.COLUMNS_2);
        layout.setColumnGap(Layout.Gap.MEDIUM);
        layout.setColumnSpan(Layout.ColumnSpan.COLUMN_SPAN_FULL, title, description, avatarLayout, username);
        return layout;
    }

    public Component createContactInformation() {
        H2 title = new H2("Contact information");
        title.addClassNames(LumoUtility.FontSize.XLARGE, LumoUtility.Margin.Top.MEDIUM);
        title.setId(title.getText().replace(" ", "-").toLowerCase());

        Paragraph description = new Paragraph("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
        description.addClassNames(LumoUtility.FontSize.SMALL, LumoUtility.TextColor.SECONDARY);

        TextField address = new TextField("Address");
        TextField city = new TextField("City");
        ComboBox<String> state = new ComboBox<>("State");
        TextField zip = new TextField("ZIP");

        TextField phone = new TextField("Phone");
        phone.setPrefixComponent(LineAwesomeIcon.PHONE_SOLID.create());

        EmailField email = new EmailField("Email");
        email.setPrefixComponent(LineAwesomeIcon.ENVELOPE.create());

        Layout layout = new Layout(title, description, address, city, state, zip, phone, email);
        // Viewport < 1024px
        layout.setFlexDirection(Layout.FlexDirection.COLUMN);
        // Viewport > 1024px
        layout.setDisplay(Breakpoint.LARGE, Layout.Display.GRID);
        layout.setColumns(Layout.GridColumns.COLUMNS_4);
        layout.setColumnGap(Layout.Gap.MEDIUM);
        layout.setColumnSpan(Layout.ColumnSpan.COLUMN_SPAN_2, city, phone, email);
        layout.setColumnSpan(Layout.ColumnSpan.COLUMN_SPAN_FULL, title, description, address);
//        String baseUrl = RouteConfiguration.forSessionScope().getUrl(getClass());
//        String urlWithParameters = baseUrl + "/contact-information";
//        getUI().ifPresent(ui -> {
//            ui.getPage().getHistory().replaceState(null, urlWithParameters);
//        });
        return layout;
    }

    public Component createPassword() {
        H2 title = new H2("Password");
        title.addClassNames(LumoUtility.FontSize.XLARGE, LumoUtility.Margin.Top.MEDIUM);
        title.setId(title.getText().replace(" ", "-").toLowerCase());

        Paragraph description = new Paragraph("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
        description.addClassNames(LumoUtility.FontSize.SMALL, LumoUtility.TextColor.SECONDARY);

        TextField currentPassword = new TextField("Current password");
        TextField newPassword = new TextField("New password");
        TextField confirmPassword = new TextField("Confirm password");

        Layout layout = new Layout(title, description, currentPassword, newPassword, confirmPassword);
        layout.setFlexDirection(Layout.FlexDirection.COLUMN);
        return layout;
    }

    public Component createNotifications() {
        H2 title = new H2("Notifications");
        title.addClassNames(LumoUtility.FontSize.XLARGE, LumoUtility.Margin.Top.MEDIUM);
        title.setId(title.getText().replace(" ", "-").toLowerCase());

        Paragraph description = new Paragraph("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
        description.addClassNames(LumoUtility.FontSize.SMALL, LumoUtility.TextColor.SECONDARY);

        CheckboxGroup<String> emailNotifications = new CheckboxGroup<>("Email notifications");
        emailNotifications.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL);
        emailNotifications.setItems("Newsletters", "Promotional offers", "Account updates", "New messages or activities", "Events or upcoming appointments");

        CheckboxGroup<String> pushNotifications = new CheckboxGroup<>("Push notifications");
        pushNotifications.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL);
        pushNotifications.setItems("New messages", "Friend requests", "Activity updates", "Order status updates", "Reminders or alerts");

        Layout layout = new Layout(title, description, emailNotifications, pushNotifications);
        layout.setFlexDirection(Layout.FlexDirection.COLUMN);
        return layout;
    }

    public Component createLinks() {

        Button publicInformationButton = new Button("Public Information");
        publicInformationButton.setPrefixComponent(VaadinIcon.INFO.create());
        publicInformationButton.addClickListener(event -> {
            this.mainLayout.removeAll();
            this.mainLayout.add(createPublicInformation());

            String baseUrl = RouteConfiguration.forSessionScope().getUrl(SettingsDialogView.class, "public-information");
            String urlWithParameters = baseUrl + "#public-information";
            getUI().ifPresent(ui -> {
                ui.getPage().getHistory().replaceState(null, urlWithParameters);
            });

        });

        Button contactInformationButton = new Button("Contact information");
        contactInformationButton.setPrefixComponent(VaadinIcon.ARCHIVE.create());
        contactInformationButton.addClickListener(event -> {
            this.mainLayout.removeAll();
            this.mainLayout.add(createContactInformation());

            String baseUrl = RouteConfiguration.forSessionScope().getUrl(SettingsDialogView.class, "contact");
            String urlWithParameters = baseUrl + "#contact-information";
            getUI().ifPresent(ui -> {
                ui.getPage().getHistory().replaceState(null, urlWithParameters);
            });

        });

        Button passwordButton = new Button("Password");
        passwordButton.setPrefixComponent(VaadinIcon.PASSWORD.create());
        passwordButton.addClickListener(event -> {
            this.mainLayout.removeAll();
            this.mainLayout.add(createPassword());

            String baseUrl = RouteConfiguration.forSessionScope().getUrl(SettingsDialogView.class, "password");
            String urlWithParameters = baseUrl + "#password";
            getUI().ifPresent(ui -> {
                ui.getPage().getHistory().replaceState(null, urlWithParameters);
            });

        });

        Button notificationsButton = new Button("Notifications");
        notificationsButton.setPrefixComponent(VaadinIcon.BELL_SLASH_O.create());
        notificationsButton.addClickListener(event -> {
            this.mainLayout.removeAll();
            this.mainLayout.add(createNotifications());

            String baseUrl = RouteConfiguration.forSessionScope().getUrl(SettingsDialogView.class, "notifications");
            String urlWithParameters = baseUrl + "#notifications";
            getUI().ifPresent(ui -> {
                ui.getPage().getHistory().replaceState(null, urlWithParameters);
            });
        });

        Stream.of(notificationsButton, passwordButton, publicInformationButton, contactInformationButton)
                .forEach(button -> button.addClassNames(EspFlowConstants.BOX_SHADOW_VAADIN_BUTTON));

//        final VerticalLayout verticalLayout = new VerticalLayout(publicInformationButton, contactInformationButton, passwordButton, notificationsButton);
//        verticalLayout.addClassNames(Margin.Vertical.XLARGE, Padding.Horizontal.LARGE);

        final Div div = new Div(publicInformationButton, contactInformationButton, passwordButton, notificationsButton);
        div.addClassNames(Display.FLEX, LumoUtility.FlexDirection.COLUMN, LumoUtility.Margin.Vertical.XLARGE, Padding.Horizontal.LARGE);

//        Step step1 = new Step("Public information", PublicInformationView.class);
//        Step step2 = new Step("Contact Information", ContactInformationView.class);
//        Step step3 = new Step("Password", PasswordView.class);
//        Step step4 = new Step("Notifications", NotificationsView.class);
//
//        final Stepper stepper = new Stepper(step1, step2, step3, step4);
//        stepper.addClassNames(BoxSizing.BORDER, MaxWidth.SCREEN_SMALL, Padding.MEDIUM);
//        stepper.setOrientation(Stepper.Orientation.VERTICAL);
//        stepper.setSmall(true);
        final Nav nav = new Nav(div);
        nav.addClassNames(Display.HIDDEN, Display.Breakpoint.Small.FLEX, LumoUtility.FontSize.SMALL, LumoUtility.Position.STICKY, "top-0");


//        final SideNav sideNav = new SideNav();
//        SideNavItem sideNavItem = new SideNavItem("Public Information","profile#public-information");
//        SideNavItem sideNavItem1 = new SideNavItem("Contact information","profile#contact-information");
//        SideNavItem sideNavItem2 = new SideNavItem("Password","profile#password");
//        SideNavItem sideNavItem3 = new SideNavItem("Notifications","profile#notifications");
//        sideNav.addItem(sideNavItem, sideNavItem1, sideNavItem2, sideNavItem3);
//        sideNav.addClassNames(Display.HIDDEN, Display.Breakpoint.Small.FLEX, FontSize.SMALL, Position.STICKY, "top-0");

        return nav;
    }

//    @Override
//    public void showRouterLayoutContent(HasElement content) {
//        if (content != null) {
//            this.content.removeAll();
//            this.content.getElement().appendChild(content.getElement());
//        }
//    }

    @Override
    public void afterNavigation(AfterNavigationEvent event) {

        UI.getCurrent().getPage().executeJs(
                "if (window.location.hash) { " +
                        "  var hash = window.location.hash.substring(1); " +  // Elimina el carácter '#'
                        "  return hash; " +
                        "} else { " +
                        "  return ''; " +  // Devuelve una cadena vacía si no hay fragmento
                        "}"
        ).then(String.class, hash -> {
            System.out.println("Fragmento de URI: " + hash);
            // Aquí puedes usar el fragmento 'hash' según lo necesites

            switch (hash) {
                case "public-information" -> {
                    this.mainLayout.removeAll();
                    this.mainLayout.add(createPublicInformation());
                }
                case "contact-information" -> {
                    this.mainLayout.removeAll();
                    this.mainLayout.add(createContactInformation());
                }
                case "password" -> {
                    this.mainLayout.removeAll();
                    this.mainLayout.add(createPassword());
                }
                case "notifications" -> {
                    this.mainLayout.removeAll();
                    this.mainLayout.add(createNotifications());
                }
            }

        });

    }

    @Override
    protected void onDetach(DetachEvent detachEvent) {
        super.onDetach(detachEvent);
    }

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        super.onAttach(attachEvent);
        if (attachEvent.isInitialAttach()) {
            final UI ui = attachEvent.getUI();
            //this.mainLayout.add(createPublicInformation());

        }
    }

    @Override
    public void setParameter(BeforeEvent event, String parameter) {
        init();
    }
}

MainLayout code snippet

MenuItem userName = userMenu.addItem("");
            Div div = new Div();
            div.add(avatar);
            div.add(user.getName());
            div.add(new Icon("lumo", "dropdown"));
            div.getElement().getStyle().set("display", "flex");
            div.getElement().getStyle().set("align-items", "center");
            div.getElement().getStyle().set("gap", "var(--lumo-space-s)");
            userName.add(div);
            Div divWizard = new Div(VaadinIcon.COG.create());
            userName.getSubMenu().addItem("Settings", e -> {
                final SettingsDialogView wizard = new SettingsDialogView();
                divWizard.add(wizard);

            }).addComponentAsFirst(divWizard);
            userName.getSubMenu().add(new Hr());
            userName.getSubMenu().addItem("Sign out", e -> {
                authenticatedUser.logout();
            }).addComponentAsFirst(VaadinIcon.SIGN_OUT.create());

I hope to be able to do this, a behavior like this as it is.

navigable-dialog-gpt

I’ve managed to do something, but, I don’t know if it’s the best approach, just get the actual URL and recreate the Layout content.

The HasUrlParameter<String> interface I am extending but, I am not doing anything inside the

@Override
public void setParameter(BeforeEvent event, String parameter) {
}

I understand that this could also be considered a deep link ?, of course it is not at the same level of Matty’s post Don't let deep linking code clutter your UI logic | Vaadin

 @Override
    protected void onAttach(AttachEvent attachEvent) {
        super.onAttach(attachEvent);
        final UI ui = attachEvent.getUI();
        ui.getPage().fetchCurrentURL(url -> {
            log.info("Current URL SettingsDialog {}", url);

            String baseUrl = RouteConfiguration.forSessionScope()
                    .getUrl(SettingsDialogView.class, "").replace("/", "");
            String urlWithParameters = url.getPath().concat(baseUrl);

            this.recreateContentAndUpdateUrl(ui, urlWithParameters, url.toString());

        });

    }

    private void recreateContentAndUpdateUrl(final UI ui, String urlWithParameters, String fetchUrl) {
        String newLocation = StringUtils.EMPTY;
        if (fetchUrl.contains("#")) {
            try {
                newLocation = fetchUrl.split("#")[1].split("/")[1];
            } catch (Exception ex) {
                log.info("fetchUrl error {} ", fetchUrl);
            }
        }
        log.info("newLocation {}", newLocation);
        switch (newLocation) {
            case PUBLIC_INFORMATION -> {
                this.mainLayout.removeAll();
                this.mainLayout.add(createPublicInformation());
                ui.getPage().getHistory().replaceState(null, urlWithParameters.concat("/" + newLocation));
            }
            case CONTACT_INFORMATION -> {
                this.mainLayout.removeAll();
                this.mainLayout.add(createContactInformation());
                ui.getPage().getHistory().replaceState(null, urlWithParameters.concat("/" + newLocation));
            }
            case PASSWORD -> {
                this.mainLayout.removeAll();
                this.mainLayout.add(createPassword());
                ui.getPage().getHistory().replaceState(null, urlWithParameters.concat("/" + newLocation));
            }
            case NOTIFICATION -> {
                this.mainLayout.removeAll();
                this.mainLayout.add(createNotifications());
                ui.getPage().getHistory().replaceState(null, urlWithParameters.concat("/" + newLocation));
            }
            default -> {
            }
        }
    }

And the MenuItem is

            div.getElement().getStyle().set("gap", "var(--lumo-space-s)");
            userName.add(div);
            userName.getSubMenu().addItem("Settings", e -> {
                settingsDialogView.setId("settings-dialog");
                settingsDialogView.open();
            }).addComponentAsFirst(VaadinIcon.COG.create());
            userName.getSubMenu().add(new Hr());
            userName.getSubMenu().addItem("Sign out", e -> {
                authenticatedUser.logout();
            }).addComponentAsFirst(VaadinIcon.SIGN_OUT.create());

            layout.add(userMenu, settingsDialogView); <1>

<1> And the dialog is added in the Footer Layout instead of the menuItem as such, I just open it with the listener of the menu item

Try overriding on attach oand then programmatically open the correct dialogue based on the parameters

Yes, that’s just what I’m doing in the code above, it’s working quite well lmao…