navigationvaadinvaadin-flowvaadin12

Vaadin Flow: Navigation broken(?) when using SplitLayout and @ParentLayout


[EDIT] Solution at the end of the question [/EDIT]

Context

I currently struggle to implement a Component which is based on the SplitLayout that looks like this: enter image description here

The idea was that the layout with the class="outer" (the highlighted line) would be a placeholder for content that was supposed to be loaded when the user selects a line in the Grid.

The "outer"-Layout was added to to SplitLayout alongside the grid and is therefore marked with slot='secondary'.

Another class references the 'outer'-Layout with @Route(value = "details", layout = OuterLayout.class)

By clicking on an entry of the Grid the page navigates to "grid/details".


Problem:

I expected Vaadin to place the contents of the annotated class within the 'outer'-Layout but instead it adds a new entry next to it: enter image description here If I remove the first 'outer'-Layout Vaadin tags the second one as slot='secondary' and its content appears: enter image description here Its even updating according the currently selected Gridentry...


Sources:

Splitlayout

@Route(value = "grid", layout = ContentLayout.class)
@ParentLayout(ContentLayout.class)
public class MySplitLayout extends SplitLayout implements RouterLayout {
    private MyGrid grid;
    private MyDetailOuterLayout detailOuterLayout;

    public MySplitLayout() {
        setSizeFull();

        grid = new MyGrid();
        detailOuterLayout = new MyDetailOuterLayout();

        addToPrimary(grid);
        addToSecondary(detailOuterLayout);
    }
}

Outer

@ParentLayout(MySplitLayout.class)
public class MyDetailOuterLayout extends FlexLayout implements RouterLayout{

    public MyDetailOuterLayout() {
        setClassName("outer");
    }
}

Inner

@Route(value = "grid/details", layout = MyDetailOuterLayout.class)
public class MyDetailLayout extends FlexLayout 
        implements HasUrlParameter<Integer>, BeforeEnterObserver
{
    public MonitorDetailLayout() {
        setClassName("inner");

        /* define data via URL*/
    }
}

Did I misunderstand the lifecycle concept?

Thank you in advance


Solution

As suggested by Tatu Lund I modified the default implementation of RouterLayout like this:

@Route(value = "grid", layout = ContentLayout.class)
@ParentLayout(ContentLayout.class)
public class MySplitLayout extends SplitLayout implements RouterLayout {
    private MyGrid grid;
    private MyDetailOuterLayout detailOuterLayout;

    public MySplitLayout() {
        setSizeFull();

        grid = new MyGrid();
        detailOuterLayout = new MyDetailOuterLayout();

        addToPrimary(grid);
        addToSecondary(detailOuterLayout);
    }

    @Override
    public void showRouterLayoutContent(HasElement content) {
        if (content != null) {
            Element rootElement = getElement();
            rootElement.removeChild(detailOuterLayout.getElement()); // aka the secondary Element
            rootElement.appendChild(Objects.requireNonNull(content.getElement()));
        }
    }

}

Solution

  • The idea with RouterLayout use in cases like you have is that, you need to override showRouterLayoutContent(..) method. When navigation happens, this method is called and it does place the content where you navigated to, into the layout. So in your case I assume you need a route target, which wraps the Grid.

    So pattern is like this, e.g. in your main layout you need to have the content holder, here it is Div, but it can be anything (like SplitLayout, or what ever)

    public class MainLayout extends VerticalLayout implements RouterLayout {
       private Div childWrapper = new Div();
    
       @Override
       public void showRouterLayoutContent(HasElement content) {
           childWrapper.getElement().appendChild(content.getElement());
       }
    }
    

    Also note, if you are using @Route(value="..", layout = ParentLayout.class) annotation, you should not use @ParentLayout(ParentLayout.class) together with it. You need to use @ParentLayout only for classes, that are not Route targets.

    There is some more information here: https://vaadin.com/docs/v12/flow/routing/tutorial-router-layout.html and here: https://vaadin.com/tutorials/nested-layouts-in-flow