summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorArtur <artur@vaadin.com>2017-06-20 06:20:17 +0300
committerIlia Motornyi <elmot@vaadin.com>2017-06-20 06:20:17 +0300
commitb5835ba8bda071e5442a93342605692b32cc9602 (patch)
tree24e1cf64b07ad150216bac3f47140ecf770f17db /server
parentfd78c9b4c656125ef03d1859e981869eb589c447 (diff)
downloadvaadin-framework-b5835ba8bda071e5442a93342605692b32cc9602.tar.gz
vaadin-framework-b5835ba8bda071e5442a93342605692b32cc9602.zip
Add View.beforeLeave to support delayed navigation
Diffstat (limited to 'server')
-rw-r--r--server/src/main/java/com/vaadin/navigator/Navigator.java62
-rw-r--r--server/src/main/java/com/vaadin/navigator/View.java33
-rw-r--r--server/src/main/java/com/vaadin/navigator/ViewBeforeLeaveEvent.java67
-rw-r--r--server/src/main/java/com/vaadin/navigator/ViewLeaveAction.java31
-rw-r--r--server/src/test/java/com/vaadin/tests/server/navigator/NavigatorTest.java199
5 files changed, 332 insertions, 60 deletions
diff --git a/server/src/main/java/com/vaadin/navigator/Navigator.java b/server/src/main/java/com/vaadin/navigator/Navigator.java
index da5dfa0f87..23d6cc5f91 100644
--- a/server/src/main/java/com/vaadin/navigator/Navigator.java
+++ b/server/src/main/java/com/vaadin/navigator/Navigator.java
@@ -594,6 +594,67 @@ public class Navigator implements Serializable {
* parameters passed in the navigation state to the view
*/
protected void navigateTo(View view, String viewName, String parameters) {
+ runAfterLeaveConfirmation(() -> {
+ performNavigateTo(view, viewName, parameters);
+ });
+
+ }
+
+ /**
+ * Triggers {@link View#beforeLeave(ViewBeforeLeaveEvent)} for the current
+ * view with the given action.
+ * <p>
+ * This method is typically called by
+ * {@link #navigateTo(View, String, String)} but can be called from
+ * application code when you want to e.g. show a confirmation dialog before
+ * perfoming an action which is not a navigation but which would cause the
+ * view to be hidden, e.g. logging out.
+ * <p>
+ * Note that this method will not trigger any {@link ViewChangeListener}s as
+ * it does not navigate to a new view. Use {@link #navigateTo(String)} to
+ * change views and trigger all listeners.
+ *
+ * @param action
+ * the action to execute when the view confirms it is ok to leave
+ * @since 8.1
+ */
+ public void runAfterLeaveConfirmation(ViewLeaveAction action) {
+ View currentView = getCurrentView();
+ if (currentView == null) {
+ action.run();
+ } else {
+ ViewBeforeLeaveEvent beforeLeaveEvent = new ViewBeforeLeaveEvent(
+ this, action);
+ currentView.beforeLeave(beforeLeaveEvent);
+ if (!beforeLeaveEvent.isNavigateRun()) {
+ // The event handler prevented navigation
+ // Revert URL to previous state in case the navigation was
+ // caused by the back-button
+ revertNavigation();
+ }
+ }
+ }
+
+ /**
+ * Internal method for activating a view, setting its parameters and calling
+ * listeners.
+ * <p>
+ * Invoked after the current view has confirmed that leaving is ok.
+ * <p>
+ * This method also verifies that the user is allowed to perform the
+ * navigation operation.
+ *
+ * @param view
+ * view to activate
+ * @param viewName
+ * (optional) name of the view or null not to change the
+ * navigation state
+ * @param parameters
+ * parameters passed in the navigation state to the view
+ * @since 8.1
+ */
+ protected void performNavigateTo(View view, String viewName,
+ String parameters) {
ViewChangeEvent event = new ViewChangeEvent(this, currentView, view,
viewName, parameters);
boolean navigationAllowed = beforeViewChange(event);
@@ -1112,4 +1173,5 @@ public class Navigator implements Serializable {
stateManager.setNavigator(null);
ui.setNavigator(null);
}
+
}
diff --git a/server/src/main/java/com/vaadin/navigator/View.java b/server/src/main/java/com/vaadin/navigator/View.java
index 623c8ed6e9..6e87c6e455 100644
--- a/server/src/main/java/com/vaadin/navigator/View.java
+++ b/server/src/main/java/com/vaadin/navigator/View.java
@@ -34,9 +34,8 @@ import com.vaadin.ui.Component;
public interface View extends Serializable {
/**
- * This view is navigated to.
- *
- * This method is always called before the view is shown on screen.
+ * Called before the view is shown on screen.
+ * <p>
* {@link ViewChangeEvent#getParameters() event.getParameters()} may contain
* extra parameters relevant to the view.
*
@@ -49,6 +48,34 @@ public interface View extends Serializable {
public void enter(ViewChangeEvent event);
/**
+ * Called when the user is requesting navigation away from the view.
+ * <p>
+ * This method allows the view to accept or prevent navigation away from the
+ * view or optionally delay navigation away until a later stage. For
+ * navigation to take place, the {@link ViewBeforeLeaveEvent#navigate()}
+ * method must be called either directly when handling this event or later
+ * to perform delayed navigation.
+ * <p>
+ * The default implementation calls {@link ViewBeforeLeaveEvent#navigate()}
+ * directly. If you override this and do nothing, the user will never be
+ * able to leave the view.
+ * <p>
+ * This method is triggered before any methods in any added
+ * {@link ViewChangeListener ViewChangeListeners}. Whenever you call
+ * {@link ViewBeforeLeaveEvent#navigate()}, any {@link ViewChangeListener}s
+ * will be triggered. They will be handled normally and might also prevent
+ * navigation.
+ *
+ * @param event
+ * an event object providing information about the event and
+ * containing the {@link ViewBeforeLeaveEvent#navigate()} method
+ * needed to perform navigation
+ */
+ public default void beforeLeave(ViewBeforeLeaveEvent event) {
+ event.navigate();
+ }
+
+ /**
* Gets the component to show when navigating to the view.
*
* By default casts this View to a {@link Component} if possible, otherwise
diff --git a/server/src/main/java/com/vaadin/navigator/ViewBeforeLeaveEvent.java b/server/src/main/java/com/vaadin/navigator/ViewBeforeLeaveEvent.java
new file mode 100644
index 0000000000..2f7fcbe170
--- /dev/null
+++ b/server/src/main/java/com/vaadin/navigator/ViewBeforeLeaveEvent.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.navigator;
+
+import java.util.EventObject;
+
+/**
+ * Event sent to the View instance before navigating away from it.
+ * <p>
+ * Provides a {@link #navigate()} method which must be called for the navigation
+ * to take place.
+ *
+ * @since 8.1
+ */
+public class ViewBeforeLeaveEvent extends EventObject {
+
+ private ViewLeaveAction action;
+ private boolean navigateRun = false;
+
+ /**
+ * Creates a new event instance for the given navigator.
+ *
+ * @param navigator
+ * the navigator instance
+ * @param action
+ * the command to execute when calling {@link #navigate()}
+ */
+ public ViewBeforeLeaveEvent(Navigator navigator, ViewLeaveAction action) {
+ super(navigator);
+ this.action = action;
+ }
+
+ /**
+ * Performs the navigation which triggered the event in the first place.
+ */
+ public void navigate() {
+ if (navigateRun) {
+ throw new IllegalStateException(
+ "navigate() can only be called once");
+ }
+ action.run();
+ navigateRun = true;
+ }
+
+ /**
+ * Checks if the navigate command has been executed.
+ *
+ * @return <code>true</code> if {@link #navigate()} has been called,
+ * <code>false</code> otherwise
+ */
+ protected boolean isNavigateRun() {
+ return navigateRun;
+ }
+}
diff --git a/server/src/main/java/com/vaadin/navigator/ViewLeaveAction.java b/server/src/main/java/com/vaadin/navigator/ViewLeaveAction.java
new file mode 100644
index 0000000000..945d13bab6
--- /dev/null
+++ b/server/src/main/java/com/vaadin/navigator/ViewLeaveAction.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.navigator;
+
+import java.io.Serializable;
+
+/**
+ * An action to execute when navigating away from a view.
+ *
+ * @since 8.1
+ */
+@FunctionalInterface
+public interface ViewLeaveAction extends Serializable {
+ /**
+ * Executes the action.
+ */
+ public void run();
+}
diff --git a/server/src/test/java/com/vaadin/tests/server/navigator/NavigatorTest.java b/server/src/test/java/com/vaadin/tests/server/navigator/NavigatorTest.java
index fbc55d57cc..31e1c53ec6 100644
--- a/server/src/test/java/com/vaadin/tests/server/navigator/NavigatorTest.java
+++ b/server/src/test/java/com/vaadin/tests/server/navigator/NavigatorTest.java
@@ -25,6 +25,7 @@ import static org.junit.Assert.fail;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.easymock.EasyMock;
@@ -36,6 +37,7 @@ import org.junit.Test;
import com.vaadin.navigator.NavigationStateManager;
import com.vaadin.navigator.Navigator;
import com.vaadin.navigator.View;
+import com.vaadin.navigator.ViewBeforeLeaveEvent;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.navigator.ViewDisplay;
@@ -44,7 +46,6 @@ import com.vaadin.server.Page;
import com.vaadin.server.VaadinRequest;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.ui.PageState;
-import com.vaadin.tests.server.navigator.ClassBasedViewProviderTest.TestView;
import com.vaadin.tests.server.navigator.ClassBasedViewProviderTest.TestView2;
import com.vaadin.ui.Component;
import com.vaadin.ui.HorizontalLayout;
@@ -336,8 +337,8 @@ public class NavigatorTest {
.createMock(NavigationStateManager.class);
ViewDisplay display = control.createMock(ViewDisplay.class);
ViewProvider provider = control.createMock(ViewProvider.class);
- View view1 = control.createMock(View.class);
- View view2 = control.createMock(View.class);
+ TestView view1 = new TestView();
+ TestView view2 = new TestView();
// prepare mocks: what to expect
manager.setNavigator(EasyMock.anyObject(Navigator.class));
@@ -346,7 +347,6 @@ public class NavigatorTest {
.times(2);
EasyMock.expect(provider.getView("test1")).andReturn(view1);
EasyMock.expect(manager.getState()).andReturn("");
- view1.enter(eventParametersEqual(""));
display.showView(view1);
manager.setState("test1");
EasyMock.expect(manager.getState()).andReturn("test1");
@@ -355,7 +355,6 @@ public class NavigatorTest {
.times(2);
EasyMock.expect(provider.getView("test2")).andReturn(view2);
EasyMock.expect(manager.getState()).andReturn("test1");
- view2.enter(eventParametersEqual(""));
display.showView(view2);
manager.setState("test2");
EasyMock.expect(manager.getState()).andReturn("test2");
@@ -364,7 +363,6 @@ public class NavigatorTest {
.times(2);
EasyMock.expect(provider.getView("test1")).andReturn(view1);
EasyMock.expect(manager.getState()).andReturn("test2");
- view1.enter(eventParametersEqual("params"));
display.showView(view1);
manager.setState("test1/params");
EasyMock.expect(manager.getState()).andReturn("test1/params");
@@ -377,12 +375,27 @@ public class NavigatorTest {
navigator.navigateTo("test1");
assertEquals("test1", navigator.getState());
-
+ assertEquals("", view1.getParams());
navigator.navigateTo("test2/");
assertEquals("test2", navigator.getState());
+ assertEquals("", view2.getParams());
navigator.navigateTo("test1/params");
assertEquals("test1/params", navigator.getState());
+ assertEquals("params", view1.getParams());
+ }
+
+ public static class TestView implements View {
+ private String params;
+
+ @Override
+ public void enter(ViewChangeEvent event) {
+ params = event.getParameters();
+ }
+
+ public String getParams() {
+ return params;
+ }
}
@Test
@@ -392,8 +405,8 @@ public class NavigatorTest {
.createMock(NavigationStateManager.class);
ViewDisplay display = control.createMock(ViewDisplay.class);
ViewProvider provider = control.createMock(ViewProvider.class);
- View view1 = control.createMock(View.class);
- View view2 = control.createMock(View.class);
+ TestView view1 = new TestView();
+ TestView view2 = new TestView();
// prepare mocks: what to expect
manager.setNavigator(EasyMock.anyObject(Navigator.class));
@@ -402,14 +415,12 @@ public class NavigatorTest {
.times(2);
EasyMock.expect(provider.getView("test2")).andReturn(view2);
EasyMock.expect(manager.getState()).andReturn("view1");
- view2.enter(eventParametersEqual(""));
display.showView(view2);
manager.setState("test2");
EasyMock.expect(provider.getViewName("")).andReturn("test1").times(2);
EasyMock.expect(provider.getView("test1")).andReturn(view1);
EasyMock.expect(manager.getState()).andReturn("");
- view1.enter(eventParametersEqual(""));
display.showView(view1);
manager.setState("test1");
@@ -417,7 +428,6 @@ public class NavigatorTest {
.times(2);
EasyMock.expect(provider.getView("test1")).andReturn(view1);
EasyMock.expect(manager.getState()).andReturn("test2");
- view1.enter(eventParametersEqual("params"));
display.showView(view1);
manager.setState("test1/params");
@@ -428,8 +438,12 @@ public class NavigatorTest {
navigator.addProvider(provider);
navigator.navigateTo("test2");
+ Assert.assertEquals("", view2.getParams());
+ Assert.assertEquals(null, view1.getParams());
navigator.navigateTo("");
+ Assert.assertEquals("", view1.getParams());
navigator.navigateTo("test1/params");
+ Assert.assertEquals("params", view1.getParams());
}
@Test
@@ -439,8 +453,8 @@ public class NavigatorTest {
.createMock(NavigationStateManager.class);
ViewDisplay display = control.createMock(ViewDisplay.class);
ViewProvider provider = control.createMock(ViewProvider.class);
- View view1 = control.createMock(View.class);
- View view2 = control.createMock(View.class);
+ TestView view1 = new TestView();
+ TestView view2 = new TestView();
ViewChangeTestListener listener = new ViewChangeTestListener();
// create navigator to test
@@ -454,7 +468,6 @@ public class NavigatorTest {
"test1", "");
listener.addExpectedIsViewChangeAllowed(event1, true);
EasyMock.expect(manager.getState()).andReturn("");
- view1.enter(eventParametersEqual(""));
display.showView(view1);
manager.setState("test1");
listener.addExpectedNavigatorViewChange(event1);
@@ -466,7 +479,6 @@ public class NavigatorTest {
"test2", "");
listener.addExpectedIsViewChangeAllowed(event2, true);
EasyMock.expect(manager.getState()).andReturn("test1");
- view2.enter(eventParametersEqual(""));
display.showView(view2);
manager.setState("test2");
listener.addExpectedNavigatorViewChange(event2);
@@ -524,8 +536,8 @@ public class NavigatorTest {
.createMock(NavigationStateManager.class);
ViewDisplay display = control.createMock(ViewDisplay.class);
ViewProvider provider = control.createMock(ViewProvider.class);
- View view1 = control.createMock(View.class);
- View view2 = control.createMock(View.class);
+ TestView view1 = new TestView();
+ TestView view2 = new TestView();
ViewChangeTestListener listener1 = new ViewChangeTestListener();
ViewChangeTestListener listener2 = new ViewChangeTestListener();
@@ -560,7 +572,6 @@ public class NavigatorTest {
"test1", "bar");
listener1.addExpectedIsViewChangeAllowed(event3, true);
listener2.addExpectedIsViewChangeAllowed(event3, true);
- view1.enter(EasyMock.isA(ViewChangeEvent.class));
display.showView(view1);
manager.setState("test1/bar");
listener1.addExpectedNavigatorViewChange(event3);
@@ -575,7 +586,6 @@ public class NavigatorTest {
"test2", "");
listener1.addExpectedIsViewChangeAllowed(event4, true);
listener2.addExpectedIsViewChangeAllowed(event4, true);
- view2.enter(EasyMock.isA(ViewChangeEvent.class));
display.showView(view2);
manager.setState("test2");
listener1.addExpectedNavigatorViewChange(event4);
@@ -803,9 +813,7 @@ public class NavigatorTest {
public void testNavigateToUnknownView() {
TestNavigator navigator = new TestNavigator();
- View errorView = EasyMock.createMock(View.class);
- errorView.enter(EasyMock.anyObject(ViewChangeEvent.class));
- EasyMock.replay(errorView);
+ TestView errorView = new TestView();
try {
navigator.navigateTo("doesnotexist");
@@ -816,16 +824,11 @@ public class NavigatorTest {
navigator.setErrorView(errorView);
navigator.navigateTo("doesnotexist");
- View testView = EasyMock.createMock(View.class);
- testView.enter(EasyMock.anyObject(ViewChangeEvent.class));
- EasyMock.replay(testView);
+ TestView testView = new TestView();
navigator.addView("doesnotexist", testView);
navigator.navigateTo("doesnotexist");
-
- View errorView2 = EasyMock.createMock(View.class);
- errorView2.enter(EasyMock.anyObject(ViewChangeEvent.class));
- EasyMock.replay(errorView2);
+ TestView errorView2 = new TestView();
ViewProvider errorProvider = EasyMock.createMock(ViewProvider.class);
EasyMock.expect(errorProvider.getView("doesnotexist2"))
@@ -1064,34 +1067,11 @@ public class NavigatorTest {
@Test
public void parameterMapFromViewChangeEvent() {
- // create navigator to test
Navigator navigator = createNavigatorWithState("foo");
- View view1 = EasyMock.createMock(View.class);
- View view2 = EasyMock.createMock(View.class);
- ViewProvider provider = new ViewProvider() {
-
- @Override
- public String getViewName(String viewAndParameters) {
- if (viewAndParameters.contains("/")) {
- return viewAndParameters.substring(0,
- viewAndParameters.indexOf('/'));
- } else {
- return viewAndParameters;
- }
- }
-
- @Override
- public View getView(String viewName) {
- if (viewName.equals("view1")) {
- return view1;
- } else if (viewName.equals("view2")) {
- return view2;
- } else {
- return null;
- }
- }
- };
- navigator.addProvider(provider);
+ TestView view1 = new TestView();
+ TestView view2 = new TestView();
+ navigator.addView("view1", view1);
+ navigator.addView("view2", view2);
AtomicReference<Map<String, String>> mapRef = new AtomicReference<>();
AtomicReference<Map<String, String>> mapRefB = new AtomicReference<>();
@@ -1114,4 +1094,109 @@ public class NavigatorTest {
entry("d", ""));
assertMap(mapRefB.get(), entry("a&", ""), entry("", "c&d"));
}
+
+ @Test
+ public void view_beforeLeave_preventNavigation() {
+ Navigator navigator = createNavigatorWithState("foo");
+ View view1 = new View() {
+
+ @Override
+ public void enter(ViewChangeEvent event) {
+ }
+
+ @Override
+ public void beforeLeave(ViewBeforeLeaveEvent event) {
+ // Leaving this empty means the user can never leave
+ }
+
+ };
+ View view2 = EasyMock.createMock(View.class);
+ navigator.addView("view1", view1);
+ navigator.addView("view2", view2);
+ navigator.navigateTo("view1");
+ navigator.navigateTo("view2");
+ Assert.assertEquals("view1", navigator.getState());
+ }
+
+ @Test
+ public void view_beforeLeave_allowNavigation() {
+ Navigator navigator = createNavigatorWithState("foo");
+ View view1 = new View() {
+
+ @Override
+ public void enter(ViewChangeEvent event) {
+ }
+
+ @Override
+ public void beforeLeave(ViewBeforeLeaveEvent event) {
+ event.navigate();
+ }
+
+ };
+ View view2 = EasyMock.createMock(View.class);
+ navigator.addView("view1", view1);
+ navigator.addView("view2", view2);
+ navigator.navigateTo("view1");
+ navigator.navigateTo("view2");
+ Assert.assertEquals("view2", navigator.getState());
+
+ }
+
+ @Test
+ public void view_beforeLeave_delayNavigation() {
+ Navigator navigator = createNavigatorWithState("foo");
+ AtomicReference<ViewBeforeLeaveEvent> eventRef = new AtomicReference<ViewBeforeLeaveEvent>();
+ View view1 = new View() {
+
+ @Override
+ public void enter(ViewChangeEvent event) {
+ }
+
+ @Override
+ public void beforeLeave(ViewBeforeLeaveEvent event) {
+ eventRef.set(event);
+ }
+
+ };
+ View view2 = EasyMock.createMock(View.class);
+ navigator.addView("view1", view1);
+ navigator.addView("view2", view2);
+ navigator.navigateTo("view1");
+ navigator.navigateTo("view2");
+ Assert.assertEquals("view1", navigator.getState());
+ eventRef.get().navigate();
+ Assert.assertEquals("view2", navigator.getState());
+
+ }
+
+ @Test
+ public void navigator_invokeBeforeLeaveManually() {
+ Navigator navigator = createNavigatorWithState("foo");
+ AtomicReference<ViewBeforeLeaveEvent> eventRef = new AtomicReference<ViewBeforeLeaveEvent>();
+ View view1 = new View() {
+
+ @Override
+ public void enter(ViewChangeEvent event) {
+ }
+
+ @Override
+ public void beforeLeave(ViewBeforeLeaveEvent event) {
+ eventRef.set(event);
+ }
+
+ };
+ TestView view2 = new TestView();
+ navigator.addView("view1", view1);
+ navigator.addView("view2", view2);
+ navigator.navigateTo("view1");
+
+ AtomicInteger leaveCount = new AtomicInteger(0);
+ navigator.runAfterLeaveConfirmation(() -> {
+ leaveCount.incrementAndGet();
+ });
+ Assert.assertEquals(0, leaveCount.get());
+ eventRef.get().navigate();
+ Assert.assertEquals(1, leaveCount.get());
+ Assert.assertEquals("view1", navigator.getState());
+ }
}