Navigation issue in Vaadin - Why can't I navigate

I think I have an issue.

When I want to navigate to a route, then I say.

UI.getCurrent().navigate("Intranet");

Then I want to go to

@Route("Intranet")

But I got another route named

@Route("Intranet/Bokning")

So when I call the function

UI.getCurrent().navigate("Intranet");

I’m going to http:localhost:8080/Intranet/Bokning and not http:localhost:8080/Intranet/
What am I doing wrong here?

Hi.

Would you have a small project with the 2 views that this happens with?
Whit this information I can’t readily come up with how “Intranet” would get targeted to a longer
route String in any possible situation.

Also on the server I would suggest using ui.navigate(Intranet.class) instead of the route String as then
we use the navigation string that the class is registered on.

  • Mikael

Mikael Grankvist:
Hi.

Would you have a small project with the 2 views that this happens with?
Whit this information I can’t readily come up with how “Intranet” would get targeted to a longer
route String in any possible situation.

Also on the server I would suggest using ui.navigate(Intranet.class) instead of the route String as then
we use the navigation string that the class is registered on.

  • Mikael

Yes!

The issue is when I’m want to go to http://localhost:8080/Intranet then Vaadin sends me to http://localhost:8080/Intranet/Bokning. Even if I type it in manually in my web browser. It’s like http://localhost:8080/Intranet does not exist, only http://localhost:8080/Intranet/Bokning

I have a page named “Intranet”. This page have a button where it stands “Klicka här”. I assume that you can read Swedish.

@Viewport("width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes, viewport-fit=cover")
@Route("Intranet")
@CssImport("./CSS/Intranet.css")
public class Intranet extends AppLayout {

	private static final long serialVersionUID = 1L;

	@Autowired
	private Top top;

	@PostConstruct
	public void init() {
		
		// set the top
		boolean touchOptimized = true;
		addToNavbar(touchOptimized, top.getBarImage(), top.getDrawerToggle());
		addToDrawer(top.getTabs());

		// Content
		VerticalLayout verticalLayout = new VerticalLayout();
		verticalLayout.setClassName("verticalLayout");
		verticalLayout.setAlignItems(Alignment.CENTER);

		Label title = new Label("Intranät");
		title.setClassName("title");
		String text = "Detta är Spektrakon's intranät. Här kan våra medlemmar komma åt webbapplikationer som vi själva har skapat.";
		Label text_title = new Label(text);
		text_title.setClassName("text_title");
		verticalLayout.add(title, text_title);
		
		
		String content_text = "Vill du boka vårat konferansrum? Då kan du göra det här. Bara logga in med din Facebook-användare och välj en tid som passar dig.";
        createSubjectWithPictures(true, verticalLayout, "subject_horizontal_red", "target-1955257_1920_16_9.jpg", "Bokning", "titel_subject_text_white", content_text, "content_subject_text_white", "button_subject_white", "bild_in_subject_right", Bokning.class);


        
		// The bottom - icons and brand
		Footer footer = new Footer();
		verticalLayout.add(footer.getIcons(), footer.getQualityBrand());

		setContent(verticalLayout);
	}

	public Intranet() {

	}

	// Each red or white row in the web app is a subject
	private void createSubjectWithPictures(boolean pictureRightSide, VerticalLayout verticalLayout,
			String subjectClassName, String bildName, String titel_text, String title_text_css_class,
			String content_text, String content_text_css_class, String button_text_css_class,
			String bild_in_subject_css_class, Class<Bokning> navigationClass) {

		HorizontalLayout subject_horizontal = new HorizontalLayout();
		subject_horizontal.setClassName(subjectClassName);
		VerticalLayout subject_bild = new VerticalLayout();
		Image bild_in_subject = new Image("img/" + bildName, "Picture");
		subject_bild.add(bild_in_subject);
		bild_in_subject.setClassName(bild_in_subject_css_class);
		VerticalLayout text_subject = new VerticalLayout();
		Label titel_subject = new Label(titel_text);
		titel_subject.setClassName(title_text_css_class);
		Label content_subject = new Label(content_text);
		content_subject.setClassName(content_text_css_class);
		
		Button clickButton = new Button("Klicka här");
		clickButton.setClassName(button_text_css_class);
		clickButton.addAttachListener(e -> UI.getCurrent().navigate(navigationClass)); // <--- HERE I WILL NAVIGATE
		
		text_subject.add(titel_subject, content_subject, clickButton);
		if (pictureRightSide == true) {
			subject_horizontal.add(text_subject, subject_bild);
		} else {
			subject_horizontal.add(subject_bild, text_subject);
		}

		verticalLayout.add(subject_horizontal);

	}
}

But when I go to that page, instead, Vaadin navigates me to this page instead.

@Viewport("width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes, viewport-fit=cover")
@Route("Intranet/Bokning")
@CssImport("./CSS/Bokning.css")
public class Bokning extends AppLayout {

	private static final long serialVersionUID = 1L;

	@Autowired
	private Top top;

	@PostConstruct
	public void init() {
		
		// set the top
		boolean touchOptimized = true;
		addToNavbar(touchOptimized, top.getBarImage(), top.getDrawerToggle());
		addToDrawer(top.getTabs());

		// Content
		VerticalLayout verticalLayout = new VerticalLayout();
		verticalLayout.setClassName("verticalLayout");
		verticalLayout.setAlignItems(Alignment.CENTER);

		Label title = new Label("Bokning konferansrum");
		title.setClassName("title");
		String text = "Här kan du boka ett möte i konferansrummet";
		Label text_title = new Label(text);
		text_title.setClassName("text_title");
		verticalLayout.add(title, text_title);
		
		// Create components
		DatePicker datum = new DatePicker();
		datum.setClassName("item_White");
		datum.setPlaceholder("Datum");
		TimePicker startTid = createTimePicker("Start");
		startTid.setClassName("item_White");
		TimePicker stoppTid = createTimePicker("Stopp");
		stoppTid.setClassName("item_White");
		TextField meddelande = new TextField();
		meddelande.setPlaceholder("Meddelande");
		meddelande.setClassName("item_White");
		EmailField email1 = createEmailField("Medlem 1", "item_White");
		EmailField email2 = createEmailField("Medlem 2", "item_White");
		EmailField email3 = createEmailField("Medlem 3", "item_White");
		EmailField email4 = createEmailField("Medlem 4", "item_White");
		EmailField email5 = createEmailField("Medlem 5", "item_White");
		EmailField email6 = createEmailField("Medlem 6", "item_White");
		EmailField email7 = createEmailField("Medlem 7", "item_White");
		EmailField email8 = createEmailField("Medlem 8", "item_White");
		EmailField email9 = createEmailField("Medlem 9", "item_White");
		EmailField email10 = createEmailField("Medlem 10", "item_White");
		Button boka = new Button("Boka möte");
		boka.setClassName("item_White");
		Button avBoka = new Button("Avboka möte");
		avBoka.setClassName("item_White");
		
		// Grid
		List<BokningarTabell> bokningar = new ArrayList<>();
		bokningar.add(new BokningarTabell());
		Grid<BokningarTabell> grid = new Grid<>(BokningarTabell.class);
		grid.setClassName("storlek_grid");
		grid.setItems(bokningar);
		grid.removeColumnByKey("id");
		grid.removeColumnByKey("medlemmar_epost");
		grid.removeColumnByKey("meddelande");
		
		// Create the form
		HorizontalLayout form = new HorizontalLayout();
		form.setClassName("subject_horizontal_red");
		VerticalLayout left = new VerticalLayout();
		left.setClassName("set_to_left");
		VerticalLayout right = new VerticalLayout();
		right.setClassName("set_to_right");
		HorizontalLayout tid_medlemmar = new HorizontalLayout();
		FormLayout medlemmar = new FormLayout();
		medlemmar.setClassName("set_to_right_medlemmar");
		medlemmar.setResponsiveSteps(new ResponsiveStep("25em", 1)); // För att göra eposten inte så långa
		form.add(left, tid_medlemmar);
		HorizontalLayout buttonForm = new HorizontalLayout();
		
		// Fill the layouts for left
		left.add(datum, buttonForm, grid);
		buttonForm.add(boka, avBoka);
		
		// Fill the layouts for right
		right.add(startTid, stoppTid);
		medlemmar.add(meddelande, email1, email2, email3, email4, email5, email6, email7, email8, email9, email10);
		tid_medlemmar.add(right, medlemmar);
		verticalLayout.add(form);
		
		// The bottom - icons and brand
		Footer footer = new Footer();
		verticalLayout.add(footer.getIcons(), footer.getQualityBrand());
		
		// Applicera API:n
		
		setContent(verticalLayout);
		
	}

	public Bokning() {
	
	}
	
	private EmailField createEmailField(String medlem, String classID) {
		EmailField emailField = new EmailField();
		emailField.setClassName(classID);
		emailField.setClearButtonVisible(true);
		emailField.setPlaceholder(medlem);
		emailField.setErrorMessage("Skriv en korrekt epostaddres");
		return emailField;
	}
	
	private TimePicker createTimePicker(String label) {
		TimePicker timePicker = new TimePicker();
		timePicker.setPlaceholder(label);
		timePicker.setMin("00:00");
		timePicker.setMax("23:00");
		return timePicker;
	}
}

The Intranet class have the route @Route("Intranet") and the Bokning class have the route @Route("Intranet/Bokning")

Notice that the navigation to the Intranet class is at a tab in an AppLayout here. And there is in THIS class, there is the issue. See the arrow in the comment below.

@Data
@CssImport("./CSS/Top.css")
@Component
@UIScope
public class Top {
	private Image barImage;
	private DrawerToggle drawerToggle;
	private Tabs tabs;

	public Top() {
		// Navigation bar
		barImage = new Image("img/cropped-logo_liggande_rod.png", "Spektrakon Logo");
		barImage.setClassName("barImage");
		drawerToggle = new DrawerToggle();
		drawerToggle.clickInClient(); // Start with closed tabs
		drawerToggle.setClassName("drawerToggle");

		// Tabs
		Tab hem = new Tab("Hem");
		Tab produktutveckling = new Tab("Produktutveckling");
		Tab industriellDesign = new Tab("Industriell Design");
		Tab system = new Tab("System");
		Tab kvalitet = new Tab("Kvalitet");
		Tab omOss = new Tab("Om oss");
		Tab ledigaTjanster = new Tab("Lediga Tjänster");
		Tab intranat = new Tab("Intranät");
		tabs = new Tabs(hem, produktutveckling, industriellDesign, system, kvalitet, omOss, ledigaTjanster, intranat);
		tabs.setOrientation(Tabs.Orientation.VERTICAL);
		tabs.setFlexGrowForEnclosedTabs(2);
		tabs.addSelectedChangeListener(event -> {
			String tabLabel = event.getSelectedTab().getLabel();
			System.out.println(tabLabel);
			if (tabLabel.equals("Hem")) {
				UI.getCurrent().navigate("");
			} else if (tabLabel.equals("Produktutveckling")) {
				UI.getCurrent().navigate(Produktutveckling.class);
			} else if (tabLabel.equals("Industriell Design")) {
				UI.getCurrent().navigate(IndustriellDesign.class);
			} else if (tabLabel.equals("System")) {
				UI.getCurrent().navigate(Systemutveckling.class);
			} else if (tabLabel.equals("Kvalitet")) {
				UI.getCurrent().navigate(Kvalitet.class);
			} else if (tabLabel.equals("Om oss")) {
				UI.getCurrent().navigate(OmOss.class);
			}else if (tabLabel.equals("Lediga Tjänster")) {
				UI.getCurrent().navigate(LedigaTjanster.class);
			}else if (tabLabel.equals("Intranät")) {
				UI.getCurrent().navigate(Intranet.class); // <--- HERE I will navigate to Intranet.class, but instead I nagivating to the Bokning.class - WHY?
			}

		});
	}
}

You should avoid defining @Route values that contain slashes. Instead, you can set this up using routerlayouts and the [@RoutePrefix annotation]
(https://vaadin.com/docs/v14/flow/routing/tutorial-router-layout.html#parentlayout-route-control).

Kaspar Scherrer:
You should avoid defining @Route values that contain slashes. Instead, you can set this up using routerlayouts and the [@RoutePrefix annotation]
(https://vaadin.com/docs/v14/flow/routing/tutorial-router-layout.html#parentlayout-route-control).

I don’t think that’s the problem. If I say there are a path http://localhost:8080/Bokning and a path http://localhost:8080/Intranet

If I go to http://localhost:8080/Intranet in my web browser, then Vaadin will send me to http://localhost:8080/Bokning

Even if I have changed the routes so it stands

@Viewport("width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes, viewport-fit=cover")
@Route("Intranet")
@CssImport("./CSS/Intranet.css")
public class Intranet extends AppLayout {

and

@Viewport("width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes, viewport-fit=cover")
@Route("Bokning")
@CssImport("./CSS/Bokning.css")
public class Bokning extends AppLayout {

I can upload my web page, but it’s 60 Mb large and this forum cannot handle it.

Wow okay that is unexplicable to me, with the provided information. Maybe there is some sort of redirect happening? Do you have a BeforeEnter event defined either in the Intranet View or globally in a UI init listener? I’m thinking of some custom redirection logic as it is done for redirecting to the login view when user is not authenticated for a view. Can you print something to the system log inside the constructor of the Intranet class, or using debugger to see if Intranet Constructor is being called during the navigation?

Kaspar Scherrer:
Wow okay that is unexplicable to me, with the provided information. Maybe there is some sort of redirect happening? Do you have a BeforeEnter event defined either in the Intranet View or globally in a UI init listener? I’m thinking of some custom redirection logic as it is done for redirecting to the login view when user is not authenticated for a view. Can you print something to the system log inside the constructor of the Intranet class, or using debugger to see if Intranet Constructor is being called during the navigation?

Do you have a BeforeEnter event defined either in the Intranet View or globally in a UI init listener?
No. I don’t have that.

I will try to re-generate a new Vaadin project. This time, it’s not going to be a Spring boot application from Spring Initilizer. It going to be from Vaadin’s web page. I very sure that this is a bug because the code is very simple and does not do any much rather than show colors and components.

I just saw the error in your code.

clickButton.addAttachListener(e -> UI.getCurrent().navigate(navigationClass));

you are adding an AttachListener here, which will be executed when the button is attached to the page.
You probably wanted to make a ClickListener, not an AttachListener. Because now just as I suspected it first finds the correct View, but later redirects to the Bokning View when the Intranet Layout is being attached to the page.

clickButton.addClickListener(e -> UI.getCurrent().navigate(navigationClass));

Hi Daniel.

It’s just as Kaspar noted. By mistake you make an immediate renavigation with the button when you add the navigate in addAttachListener, as this is executed when the Button gets attached to the UI instance through the parent components.

  • Mikael

Kaspar Scherrer:
I just saw the error in your code.

clickButton.addAttachListener(e -> UI.getCurrent().navigate(navigationClass));

you are adding an AttachListener here, which will be executed when the button is attached to the page.
You probably wanted to make a ClickListener, not an AttachListener. Because now just as I suspected it first finds the correct View, but later redirects to the Bokning View when the Intranet Layout is being attached to the page.

clickButton.addClickListener(e -> UI.getCurrent().navigate(navigationClass));

Yes! That solved my problem! :slight_smile: Thank you.

I also want to thank you about the routing hint. But I don’t really understand why this is not recommended. It works perfect

@Route("Intranet/Bokning") // For a AppLayout

:slight_smile:

Daniel Mårtensson:
I also want to thank you about the routing hint. But I don’t really understand why this is not recommended. It works perfect

@Route("Intranet/Bokning") // For a AppLayout

When building complex URI path structures, it is recommended to use @RoutePrefix with nested RouterLayouts because it will be much easier to handle and to understand the whole picture. If you only have one view that would fit into such a routePrefix structure it may be possible doing it without routePrefixes and will not cause massive confusion in the codebase.
I like to compare this to using interfaces for your java objects; you don’t really need interfaces most of the time, but seldom is there a good reason why things that are shared by two classes should not be simplified as much as possible.

Imagine if you had 5 more views whose routes all start with “Intranet/…”, at some point it will make sense to make a routerLayout with the “Intranet” prefix, just for clarity. If some parts of their layouts is the same for all, then there’s even more reason to making that routerLayout, as that shared part can be defined in the routerlayout instead of in each sub-view.

Kaspar Scherrer:

Daniel Mårtensson:
I also want to thank you about the routing hint. But I don’t really understand why this is not recommended. It works perfect

@Route("Intranet/Bokning") // For a AppLayout

When building complex URI path structures, it is recommended to use @RoutePrefix with nested RouterLayouts because it will be much easier to handle and to understand the whole picture. If you only have one view that would fit into such a routePrefix structure it may be possible doing it without routePrefixes and will not cause massive confusion in the codebase.
I like to compare this to using interfaces for your java objects; you don’t really need interfaces most of the time, but seldom is there a good reason why things that are shared by two classes should not be simplified as much as possible.

Imagine if you had 5 more views whose routes all start with “Intranet/…”, at some point it will make sense to make a routerLayout with the “Intranet” prefix, just for clarity. If some parts of their layouts is the same for all, then there’s even more reason to making that routerLayout, as that shared part can be defined in the routerlayout instead of in each sub-view.

Hmm. I might give it a try.
I tried RoutePrefix on my “intranet” layout

@Viewport("width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes, viewport-fit=cover")
//@Route("Intranet")
@RoutePrefix("Intranet")
@CssImport("./CSS/Intranet.css")
public class Intranet extends AppLayout {

Then I re-write the route

@Viewport("width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes, viewport-fit=cover")
@Route(value = "Bokning", layout = Intranet.class)
@CssImport("./CSS/Bokning.css")
public class Bokning extends AppLayout {

But then I got the error

com.vaadin.flow.server.InvalidApplicationConfigurationException: Found configuration annotations that will not be used in the application. 
Move the following annotations to a single route or the top RouterLayout of the application: 

Move @Route to a single route or to the top of RouterLayout of the application?

First of all, if it works for you and if you don’t plan to make more views that have the uri prefix “Intranet”, just leave it the way you had it when it worked, using @Route("Intranet/Bokning").


Yes the @RoutePrefix annotation can only be set on a RouterLayout, which your Intranet view is not. You’ll need a new class here.

@RoutePrefix("Intranet")
public class IntranetRouterLayout extends AppLayout implements RouterLayout {
}

Then you use this routerLayout for your views Intranet and Bokning:

@Route(value = "", layout = IntranetRouterLayout.class) // not 100% this works, but it should become www.ABC.com/Intranet/
@CssImport("./CSS/Intranet.css")
public class Intranet extends VerticalLayout {
}

@Route(value = "Bokning", layout = IntranetRouterLayout.class) // becomes www.ABC.com/Intranet/Bokning
@CssImport("./CSS/Bokning.css")
public class Bokning extends VerticalLayout {
}

to prevent naming confusions, it will be a good idea to rename the Intranet view to something more telling, as it is currently named after the “category” of views. Maybe IntranetHome, IntranetOverview, or similar?

Kaspar Scherrer:
First of all, if it works for you and if you don’t plan to make more views that have the uri prefix “Intranet”, just leave it the way you had it when it worked, using @Route("Intranet/Bokning").


Yes the @RoutePrefix annotation can only be set on a RouterLayout, which your Intranet view is not. You’ll need a new class here.

@RoutePrefix("Intranet")
public class IntranetRouterLayout extends AppLayout implements RouterLayout {
}

Then you use this routerLayout for your views Intranet and Bokning:

@Route(value = "", layout = IntranetRouterLayout.class) // not 100% this works, but it should become www.ABC.com/Intranet/
@CssImport("./CSS/Intranet.css")
public class Intranet extends VerticalLayout {
}

@Route(value = "Bokning", layout = IntranetRouterLayout.class) // becomes www.ABC.com/Intranet/Bokning
@CssImport("./CSS/Bokning.css")
public class Bokning extends VerticalLayout {
}

to prevent naming confusions, it will be a good idea to rename the Intranet view to something more telling, as it is currently named after the “category” of views. Maybe IntranetHome, IntranetOverview, or similar?

Okey. Now I understand. Thank you for the hint.