Curtis Snowden
Curtis Snowden

Reputation: 493

Vaadin Navigate to Same Route With Different Parameters

I have a situation in my application where I have a view page for an entity. These entities can be either by themselves, or link to each other as parent/child. In a situation where I'm viewing a child, I want the user to simply press a button to navigate to the parent. This entails opening up the exact same page via the same route. However, navigate does not reload the page when navigating to itself. I need it to, because I'm dealing with dozens of fields and submodules that all need to be rebuilt and repopulated. Presently, what I'm doing is this:

if (myEntity.getParent() != null) {
    myMenu.add(new Button("View Parent", evt -> 
        /*UI.getCurrent().navigate(
            MyEntityViewUI.class,
            new RouteParameters(HasUrlParameterFormat.PARAMETER_NAME, String.valueOf(myEntity.getParent().getId()))
        )*/
        UI.getCurrent().getPage().setLocation(
            GeneralUtils.getAnnotationForClass(MyEntityViewUI.class, Route.class).value() + "/" + myEntity.getParent().getId()
        )
    ));
}

The commented out portion is what I'd like to do. The setParameter method is invoked, but not the constructor and thus not any of the logic I've currently implemented that actually builds my page. And even if I manually invoked that, I'd have to rewrite 2 layers of abstract parents to properly gut my existing components so that I can just rebuild everything fresh. I'd really prefer to not have to do that if it can be avoided.

The current code I have is using setLocation, but this is hackey and isn't a smooth transition like navigate() is. Plus, I can't just provide the class and RouteParameters via this, hence the manual construction of the string.

Is it possible to force Vaadin to perform a full page reload when navigating to the same Route? I don't really understand why it wouldn't be re-instantiating the navigation target.

I'm using Vaadin 24.6.2.


To clarify, I'm not building things within the constructor. I am utilizing a general BeforeEnter listener in my abstract super class which invokes an abstract initUI() method that is overridden by implementation classes. Part of the problem is that my button and menu in the above is done inside of a customized menu put into the AppLayout's Drawer pane, and even the HasDynamicTitle is utilizing information from the viewed entity.

Upvotes: 1

Views: 70

Answers (1)

cfrick
cfrick

Reputation: 37073

You are doing all your work in the c'tor of your view -- but Vaadin Flow only instantiates views when needed (e.g. when navigating to a different view). So this is to be expected and instead of trying to hack around that, you are expected to do the bulk of the work not in the c'tor, but in the navigation callbacks.

  1. So build your fixed content for the view in the c'tor (things, that never change).
  2. Then implement HasUrlParameter and friends if you need to.
  3. Finally implement AfterNavigationObserver and do everything there.

The two callbacks are always called for each navigation into this view in this order. In the afterNavigation use the information previously gathered in setParameter and/or from the given event to update the UI, even if this means to remove everything and add something new.

From a separation-of-concerns point of view, it generally makes sense to not put too much "UI" into the view. The main concern here is, integration into routing, layout-nesting, maybe authorization, …

@Route(value = "")
class MainView() : FlexLayout(), HasUrlParameter<String>, AfterNavigationObserver {

    val paramDisplay: TextField = TextField()
    val mainDisplay: TextField = TextField()

    init {
        flexDirection = FlexDirection.COLUMN
        addClassName(LumoUtility.Gap.MEDIUM)
        add(
            paramDisplay,
            mainDisplay.apply {
                value = "Init"
            },
            Button("Nav").apply {
                addClickListener {
                    ui.ifPresent { it.navigate(MainView::class.java, counter.getAndIncrement().toString()) }
                }
            }
        )
    }

    override fun setParameter(event: BeforeEvent?, parameter: String?) {
        paramDisplay.value = parameter ?: "n/a"
    }

    override fun afterNavigation(event: AfterNavigationEvent?) {
        mainDisplay.value = Instant.now().toString()
    }

}

Upvotes: 0

Related Questions