Browse Source

Some Navigator API changes based on review (#8859).

The changes in this changeset include
- remove the concept of main view (use empty view name)
- remove View.init()
- use ViewChangeEvent in ViewChangeListener
- remove internal parameters
- add getDisplay() and a constructor that creates a SimpleViewDisplay by
default
tags/7.0.0.alpha3
Henri Sara 12 years ago
parent
commit
a1a87b0ce6

+ 40
- 69
src/com/vaadin/navigator/Navigator.java View File

@@ -9,6 +9,7 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.ui.Component;
import com.vaadin.ui.CssLayout;
import com.vaadin.ui.CustomComponent;
@@ -22,7 +23,7 @@ import com.vaadin.ui.Root.FragmentChangedListener;
* The view switching can be based e.g. on URI fragments containing the view
* name and parameters to the view. There are two types of parameters for views:
* an optional parameter string that is included in the fragment (may be
* bookmarkable) and optional internal parameters that are not.
* bookmarkable).
*
* Views can be explicitly registered or dynamically generated and listening to
* view changes is possible.
@@ -44,13 +45,12 @@ public class Navigator implements Serializable {
* Empty view component.
*/
public static class EmptyView extends CssLayout implements View {
public void init() {
public EmptyView() {
setWidth("0px");
setHeight("0px");
}

public void navigateTo(String fragmentParameters,
Object... internalParameters) {
public void navigateTo(String fragmentParameters) {
// nothing to do
}
}
@@ -228,7 +228,6 @@ public class Navigator implements Serializable {
if (!classToView.containsKey(newViewClass)) {
try {
View view = newViewClass.newInstance();
view.init();
classToView.put(newViewClass, view);
} catch (InstantiationException e) {
// TODO error handling
@@ -326,7 +325,6 @@ public class Navigator implements Serializable {

private final FragmentManager fragmentManager;
private final ViewDisplay display;
private String mainViewName = null;
private View currentView = null;
private List<ViewChangeListener> listeners = new LinkedList<ViewChangeListener>();
private List<ViewProvider> providers = new LinkedList<ViewProvider>();
@@ -344,6 +342,19 @@ public class Navigator implements Serializable {
fragmentManager = new UriFragmentManager(root, this);
}

/**
* Create a navigator that is tracking the active view using URI fragments.
* By default, a {@link SimpleViewDisplay} is used and can be obtained using
* {@link #getDisplay()}.
*
* @param root
* whose URI fragments are used
*/
public Navigator(Root root) {
display = new SimpleViewDisplay();
fragmentManager = new UriFragmentManager(root, this);
}

/**
* Create a navigator.
*
@@ -374,15 +385,8 @@ public class Navigator implements Serializable {
*
* @param viewAndParameters
* view name and parameters
* @param internalParameters
* parameters that are only passed when switching views
* explicitly on the server side, not bookmarkable
*/
public void navigateTo(String viewAndParameters,
Object... internalParameters) {
if ("".equals(viewAndParameters)) {
viewAndParameters = mainViewName;
}
public void navigateTo(String viewAndParameters) {
for (ViewProvider provider : providers) {
String viewName = provider.getViewName(viewAndParameters);
if (null != viewName) {
@@ -393,13 +397,13 @@ public class Navigator implements Serializable {
}
View view = provider.getView(viewName);
if (null != view) {
navigateTo(view, viewName, parameters, internalParameters);
navigateTo(view, viewName, parameters);
// stop after a view is found
return;
}
}
}
// TODO if no view is found, use main view or do nothing?
// TODO if no view is found, what to do?
}

/**
@@ -415,14 +419,12 @@ public class Navigator implements Serializable {
* (optional) name of the view or null not to set the fragment
* @param fragmentParameters
* parameters passed in the fragment for the view
* @param internalParameters
* parameters that are only passed when switching views
* explicitly on the server side, not bookmarkable
*/
protected void navigateTo(View view, String viewName,
String fragmentParameters, Object... internalParameters) {
if (!isViewChangeAllowed(currentView, view, viewName,
fragmentParameters, internalParameters)) {
String fragmentParameters) {
ViewChangeEvent event = new ViewChangeEvent(this, currentView, view,
viewName, fragmentParameters);
if (!isViewChangeAllowed(event)) {
return;
}

@@ -436,15 +438,14 @@ public class Navigator implements Serializable {
}
}

view.navigateTo(fragmentParameters, internalParameters);
View previousView = currentView;
view.navigateTo(fragmentParameters);
currentView = view;

if (display != null) {
display.showView(view);
}

fireViewChange(previousView);
fireViewChange(event);
}

/**
@@ -457,26 +458,14 @@ public class Navigator implements Serializable {
* and save the parameters to re-initiate the navigation operation upon user
* action.
*
* @param previous
* the view being deactivated
* @param next
* the view being activated
* @param viewName
* name of the view being activated
* @param fragmentParameters
* parameters passed in the fragment for the view
* @param internalParameters
* parameters that are only passed on the server side, not
* bookmarkable
* @param event
* view change event (not null, view change not yet performed)
* @return true if the view change should be allowed, false to silently
* block the navigation operation
*/
protected boolean isViewChangeAllowed(View previous, View next,
String viewName, String fragmentParameters,
Object[] internalParameters) {
protected boolean isViewChangeAllowed(ViewChangeEvent event) {
for (ViewChangeListener l : listeners) {
if (!l.isViewChangeAllowed(previous, next, viewName,
fragmentParameters, internalParameters)) {
if (!l.isViewChangeAllowed(event)) {
return false;
}
}
@@ -494,43 +483,25 @@ public class Navigator implements Serializable {
}

/**
* Get the main view.
*
* Main view is the default view shown to user when he opens application
* without specifying view name.
*
* @return name of the main view.
*/
public String getMainView() {
return mainViewName;
}

/**
* Set the main view.
* Returns the ViewDisplay used by the navigator. Unless another display is
* specified, a {@link SimpleViewDisplay} (which is a {@link Component}) is
* used by default.
*
* Main view is the default view shown to user when he opens application
* without specifying view name. If the {@link Navigator} does not have a
* current view, the main view is automatically selected when set.
*
* @param mainViewName
* name of the main view.
* @return current ViewDisplay
*/
public void setMainView(String mainViewName) {
this.mainViewName = mainViewName;
// TODO should this be done?
if (currentView == null) {
navigateTo(mainViewName);
}
public ViewDisplay getDisplay() {
return display;
}

/**
* Fire an event when the current view has changed.
*
* @param previousView
* @param event
* view change event (not null)
*/
protected void fireViewChange(View previousView) {
protected void fireViewChange(ViewChangeEvent event) {
for (ViewChangeListener l : listeners) {
l.navigatorViewChanged(previousView, currentView);
l.navigatorViewChanged(event);
}
}


+ 1
- 15
src/com/vaadin/navigator/View.java View File

@@ -19,15 +19,6 @@ import com.vaadin.ui.Component;
*/
public interface View extends Serializable {

/**
* Init view.
*
* Convenience method which navigator calls just before the view is rendered
* for the first time. This is called only once in the lifetime of each view
* instance.
*/
public void init();

/**
* This view is navigated to.
*
@@ -40,11 +31,6 @@ public interface View extends Serializable {
* @param fragmentParameters
* parameters to the view or null if none given. This is the
* string that appears e.g. in URI after "viewname/"
* @param internalParameters
* parameters given directly to
* {@link Navigator#navigateTo(String, Object...)}, not passed
* via the fragment and not preserved in bookmarks
*/
public void navigateTo(String fragmentParameters,
Object... internalParameters);
public void navigateTo(String fragmentParameters);
}

+ 78
- 22
src/com/vaadin/navigator/ViewChangeListener.java View File

@@ -5,6 +5,7 @@
package com.vaadin.navigator;

import java.io.Serializable;
import java.util.EventObject;

/**
* Interface for listening to View changes before and after they occur.
@@ -17,6 +18,77 @@ import java.io.Serializable;
*/
public interface ViewChangeListener extends Serializable {

/**
* Event received by the listener for attempted and executed view changes.
*/
public static class ViewChangeEvent extends EventObject {
private final View oldView;
private final View newView;
private final String viewName;
private final String fragmentParameters;

/**
* Create a new view change event.
*
* @param navigator
* Navigator that triggered the event, not null
*/
public ViewChangeEvent(Navigator navigator, View oldView, View newView,
String viewName, String fragmentParameters) {
super(navigator);
this.oldView = oldView;
this.newView = newView;
this.viewName = viewName;
this.fragmentParameters = fragmentParameters;
}

/**
* Returns the navigator that triggered this event.
*
* @return Navigator (not null)
*/
public Navigator getNavigator() {
return (Navigator) getSource();
}

/**
* Returns the view being deactivated.
*
* @return old View
*/
public View getOldView() {
return oldView;
}

/**
* Returns the view being activated.
*
* @return new View
*/
public View getNewView() {
return newView;
}

/**
* Returns the view name of the view being activated.
*
* @return view name of the new View
*/
public String getViewName() {
return viewName;
}

/**
* Returns the parameters for the view being activated.
*
* @return fragment parameters (potentially bookmarkable) for the new
* view
*/
public String getFragmentParameters() {
return fragmentParameters;
}
}

/**
* Check whether changing the view is permissible.
*
@@ -27,36 +99,20 @@ public interface ViewChangeListener extends Serializable {
* know the view in question), it should return true. If any listener
* returns false, the view change is not allowed.
*
* TODO move to separate interface?
*
* @param previous
* view that is being deactivated
* @param next
* view that is being activated
* @param viewName
* name of the new view that is being activated
* @param fragmentParameters
* fragment parameters (potentially bookmarkable) for the new
* view
* @param internalParameters
* internal parameters for the new view, not visible in the
* browser
* @param event
* view change event
* @return true if the view change should be allowed or this listener does
* not care about the view change, false to block the change
*/
public boolean isViewChangeAllowed(View previous, View next,
String viewName, String fragmentParameters,
Object... internalParameters);
public boolean isViewChangeAllowed(ViewChangeEvent event);

/**
* Invoked after the view has changed. Be careful for deadlocks if you
* decide to change the view again in the listener.
*
* @param previous
* Preview view before the change.
* @param current
* New view after the change.
* @param event
* view change event
*/
public void navigatorViewChanged(View previous, View current);
public void navigatorViewChanged(ViewChangeEvent event);

}

+ 1
- 7
tests/server-side/com/vaadin/tests/server/navigator/ClassBasedViewProviderTest.java View File

@@ -15,14 +15,9 @@ public class ClassBasedViewProviderTest extends TestCase {
private ClassBasedViewProvider provider;

public static class TestView extends Label implements View {
public boolean initialized = false;
public String parameters = null;

public void init() {
initialized = true;
}

public void navigateTo(String parameters, Object... internalParameters) {
public void navigateTo(String parameters) {
this.parameters = parameters;
}

@@ -169,7 +164,6 @@ public class ClassBasedViewProviderTest extends TestCase {
View view = provider.getView("test");
assertNotNull("Did not get view from a provider", view);
assertEquals("Incorrect view type", TestView.class, view.getClass());
assertTrue("View not initialized", ((TestView) view).initialized);
}

public void testGetViewMultipleRegistered() throws Exception {

+ 138
- 66
tests/server-side/com/vaadin/tests/server/navigator/NavigatorTest.java View File

@@ -4,6 +4,8 @@

package com.vaadin.tests.server.navigator;

import java.util.LinkedList;

import junit.framework.TestCase;

import org.easymock.EasyMock;
@@ -13,6 +15,7 @@ import com.vaadin.navigator.FragmentManager;
import com.vaadin.navigator.Navigator;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.navigator.ViewDisplay;
import com.vaadin.navigator.ViewProvider;

@@ -21,6 +24,92 @@ public class NavigatorTest extends TestCase {
// TODO test internal parameters (and absence of them)
// TODO test listeners blocking navigation, multiple listeners

public static class ViewChangeTestListener implements ViewChangeListener {
private final LinkedList<ViewChangeEvent> referenceEvents = new LinkedList<ViewChangeListener.ViewChangeEvent>();
private final LinkedList<Boolean> referenceIsCheck = new LinkedList<Boolean>();
private final LinkedList<Boolean> checkReturnValues = new LinkedList<Boolean>();

public void addExpectedIsViewChangeAllowed(ViewChangeEvent event,
boolean returnValue) {
referenceIsCheck.add(true);
referenceEvents.add(event);
checkReturnValues.add(returnValue);
}

public void addExpectedNavigatorViewChange(ViewChangeEvent event) {
referenceIsCheck.add(false);
referenceEvents.add(event);
}

public boolean isReady() {
return referenceEvents.isEmpty();
}

public boolean equalsReferenceEvent(ViewChangeEvent event,
ViewChangeEvent reference) {
if (event == null) {
return false;
}
if (reference.getNavigator() != event.getNavigator()) {
return false;
}
if (reference.getOldView() != event.getOldView()) {
return false;
}
if (reference.getNewView() != event.getNewView()) {
return false;
}
if (!stringEquals(reference.getViewName(), event.getViewName())) {
return false;
}
if (!stringEquals(reference.getFragmentParameters(),
event.getFragmentParameters())) {
return false;
}
return true;
}

private static boolean stringEquals(String string1, String string2) {
if (string1 == null) {
return string2 == null;
} else {
return string1.equals(string2);
}
}

public boolean isViewChangeAllowed(ViewChangeEvent event) {
if (referenceEvents.isEmpty()) {
fail("Unexpected call to isViewChangeAllowed()");
}
ViewChangeEvent reference = referenceEvents.remove();
Boolean isCheck = referenceIsCheck.remove();
if (!isCheck) {
fail("Expected navigatorViewChanged(), received isViewChangeAllowed()");
}
// here to make sure exactly the correct values are removed from
// each queue
Boolean returnValue = checkReturnValues.remove();
if (!equalsReferenceEvent(event, reference)) {
fail("View change event does not match reference event");
}
return returnValue;
}

public void navigatorViewChanged(ViewChangeEvent event) {
if (referenceEvents.isEmpty()) {
fail("Unexpected call to navigatorViewChanged()");
}
ViewChangeEvent reference = referenceEvents.remove();
Boolean isCheck = referenceIsCheck.remove();
if (isCheck) {
fail("Expected isViewChangeAllowed(), received navigatorViewChanged()");
}
if (!equalsReferenceEvent(event, reference)) {
fail("View change event does not match reference event");
}
}
}

public void testBasicNavigation() {
IMocksControl control = EasyMock.createControl();
FragmentManager manager = control.createMock(FragmentManager.class);
@@ -32,7 +121,6 @@ public class NavigatorTest extends TestCase {
// prepare mocks: what to expect
EasyMock.expect(provider.getViewName("test1")).andReturn("test1");
EasyMock.expect(provider.getView("test1")).andReturn(view1);
view1.init();
EasyMock.expect(manager.getFragment()).andReturn("");
view1.navigateTo(null);
display.showView(view1);
@@ -40,7 +128,6 @@ public class NavigatorTest extends TestCase {

EasyMock.expect(provider.getViewName("test2/")).andReturn("test2");
EasyMock.expect(provider.getView("test2")).andReturn(view2);
view2.init();
EasyMock.expect(manager.getFragment()).andReturn("view1");
view2.navigateTo(null);
display.showView(view2);
@@ -49,7 +136,6 @@ public class NavigatorTest extends TestCase {
EasyMock.expect(provider.getViewName("test1/params"))
.andReturn("test1");
EasyMock.expect(provider.getView("test1")).andReturn(view1);
view1.init();
EasyMock.expect(manager.getFragment()).andReturn("view2");
view1.navigateTo("params");
display.showView(view1);
@@ -75,25 +161,15 @@ public class NavigatorTest extends TestCase {
View view2 = control.createMock(View.class);

// prepare mocks: what to expect
EasyMock.expect(provider.getViewName("test1")).andReturn("test1");
EasyMock.expect(provider.getView("test1")).andReturn(view1);
view1.init();
EasyMock.expect(manager.getFragment()).andReturn("");
view1.navigateTo(null);
display.showView(view1);
manager.setFragment("test1");

EasyMock.expect(provider.getViewName("test2")).andReturn("test2");
EasyMock.expect(provider.getView("test2")).andReturn(view2);
view2.init();
EasyMock.expect(manager.getFragment()).andReturn("view1");
view2.navigateTo(null);
display.showView(view2);
manager.setFragment("test2");

EasyMock.expect(provider.getViewName("test1")).andReturn("test1");
EasyMock.expect(provider.getViewName("")).andReturn("test1");
EasyMock.expect(provider.getView("test1")).andReturn(view1);
view1.init();
EasyMock.expect(manager.getFragment()).andReturn("");
view1.navigateTo(null);
display.showView(view1);
@@ -102,7 +178,6 @@ public class NavigatorTest extends TestCase {
EasyMock.expect(provider.getViewName("test1/params"))
.andReturn("test1");
EasyMock.expect(provider.getView("test1")).andReturn(view1);
view1.init();
EasyMock.expect(manager.getFragment()).andReturn("view2");
view1.navigateTo("params");
display.showView(view1);
@@ -113,8 +188,6 @@ public class NavigatorTest extends TestCase {
// create and test navigator
Navigator navigator = new Navigator(manager, display);
navigator.registerProvider(provider);
// this also triggers navigation
navigator.setMainView("test1");

navigator.navigateTo("test2");
navigator.navigateTo("");
@@ -128,43 +201,46 @@ public class NavigatorTest extends TestCase {
ViewProvider provider = control.createMock(ViewProvider.class);
View view1 = control.createMock(View.class);
View view2 = control.createMock(View.class);
ViewChangeListener listener = control
.createMock(ViewChangeListener.class);
ViewChangeTestListener listener = new ViewChangeTestListener();

// create navigator to test
Navigator navigator = new Navigator(manager, display);

// prepare mocks: what to expect
EasyMock.expect(provider.getViewName("test1")).andReturn("test1");
EasyMock.expect(provider.getView("test1")).andReturn(view1);
view1.init();
ViewChangeEvent event1 = new ViewChangeEvent(navigator, null, view1,
"test1", null);
listener.addExpectedIsViewChangeAllowed(event1, true);
EasyMock.expect(manager.getFragment()).andReturn("");
EasyMock.expect(
listener.isViewChangeAllowed(null, view1, "test1", null,
new Object[0])).andReturn(true);
view1.navigateTo(null);
display.showView(view1);
manager.setFragment("test1");
listener.navigatorViewChanged(null, view1);
listener.addExpectedNavigatorViewChange(event1);

EasyMock.expect(provider.getViewName("test2")).andReturn("test2");
EasyMock.expect(provider.getView("test2")).andReturn(view2);
view2.init();
ViewChangeEvent event2 = new ViewChangeEvent(navigator, view1, view2,
"test2", null);
listener.addExpectedIsViewChangeAllowed(event2, true);
EasyMock.expect(manager.getFragment()).andReturn("view1");
EasyMock.expect(
listener.isViewChangeAllowed(view1, view2, "test2", null,
new Object[0])).andReturn(true);
view2.navigateTo(null);
display.showView(view2);
manager.setFragment("test2");
listener.navigatorViewChanged(view1, view2);
listener.addExpectedNavigatorViewChange(event2);

control.replay();

// create and test navigator
Navigator navigator = new Navigator(manager, display);
// test navigator
navigator.registerProvider(provider);
navigator.addListener(listener);

navigator.navigateTo("test1");
navigator.navigateTo("test2");

if (!listener.isReady()) {
fail("Missing listener calls");
}
}

public void testBlockNavigation() {
@@ -174,71 +250,60 @@ public class NavigatorTest extends TestCase {
ViewProvider provider = control.createMock(ViewProvider.class);
View view1 = control.createMock(View.class);
View view2 = control.createMock(View.class);
ViewChangeListener listener1 = control
.createMock(ViewChangeListener.class);
ViewChangeListener listener2 = control
.createMock(ViewChangeListener.class);
ViewChangeTestListener listener1 = new ViewChangeTestListener();
ViewChangeTestListener listener2 = new ViewChangeTestListener();
Navigator navigator = new Navigator(manager, display);

// prepare mocks: what to expect
// first listener blocks first view change
EasyMock.expect(provider.getViewName("test1")).andReturn("test1");
EasyMock.expect(provider.getView("test1")).andReturn(view1);
view1.init();
EasyMock.expect(manager.getFragment()).andReturn("");
EasyMock.expect(
listener1.isViewChangeAllowed(null, view1, "test1", null,
new Object[0])).andReturn(false);
ViewChangeEvent event1 = new ViewChangeEvent(navigator, null, view1,
"test1", null);
listener1.addExpectedIsViewChangeAllowed(event1, false);

// second listener blocks second view change
EasyMock.expect(provider.getViewName("test1/test")).andReturn("test1");
EasyMock.expect(provider.getView("test1")).andReturn(view1);
view1.init();
EasyMock.expect(manager.getFragment()).andReturn("");
EasyMock.expect(
listener1.isViewChangeAllowed(null, view1, "test1", "test",
new Object[0])).andReturn(true);
EasyMock.expect(
listener2.isViewChangeAllowed(null, view1, "test1", "test",
new Object[0])).andReturn(false);
ViewChangeEvent event2 = new ViewChangeEvent(navigator, null, view1,
"test1", "test");
listener1.addExpectedIsViewChangeAllowed(event2, true);
listener2.addExpectedIsViewChangeAllowed(event2, false);

// both listeners allow view change
EasyMock.expect(provider.getViewName("test1/bar")).andReturn("test1");
EasyMock.expect(provider.getView("test1")).andReturn(view1);
view1.init();
EasyMock.expect(manager.getFragment()).andReturn("");
EasyMock.expect(
listener1.isViewChangeAllowed(null, view1, "test1", "bar",
new Object[0])).andReturn(true);
EasyMock.expect(
listener2.isViewChangeAllowed(null, view1, "test1", "bar",
new Object[0])).andReturn(true);
ViewChangeEvent event3 = new ViewChangeEvent(navigator, null, view1,
"test1", "bar");
listener1.addExpectedIsViewChangeAllowed(event3, true);
listener2.addExpectedIsViewChangeAllowed(event3, true);
view1.navigateTo("bar");
display.showView(view1);
manager.setFragment("test1/bar");
listener1.navigatorViewChanged(null, view1);
listener2.navigatorViewChanged(null, view1);
listener1.addExpectedNavigatorViewChange(event3);
listener2.addExpectedNavigatorViewChange(event3);

// both listeners allow view change from non-null view
EasyMock.expect(provider.getViewName("test2")).andReturn("test2");
EasyMock.expect(provider.getView("test2")).andReturn(view2);
view2.init();
EasyMock.expect(manager.getFragment()).andReturn("view1");
EasyMock.expect(
listener1.isViewChangeAllowed(view1, view2, "test2", null,
new Object[0])).andReturn(true);
EasyMock.expect(
listener2.isViewChangeAllowed(view1, view2, "test2", null,
new Object[0])).andReturn(true);
ViewChangeEvent event4 = new ViewChangeEvent(navigator, view1, view2,
"test2", null);
listener1.addExpectedIsViewChangeAllowed(event4, true);
listener2.addExpectedIsViewChangeAllowed(event4, true);
view2.navigateTo(null);
display.showView(view2);
manager.setFragment("test2");
listener1.navigatorViewChanged(view1, view2);
listener2.navigatorViewChanged(view1, view2);
listener1.addExpectedNavigatorViewChange(event4);
listener2.addExpectedNavigatorViewChange(event4);

control.replay();

// create and test navigator
Navigator navigator = new Navigator(manager, display);
// test navigator
navigator.registerProvider(provider);
navigator.addListener(listener1);
navigator.addListener(listener2);
@@ -247,6 +312,13 @@ public class NavigatorTest extends TestCase {
navigator.navigateTo("test1/test");
navigator.navigateTo("test1/bar");
navigator.navigateTo("test2");

if (!listener1.isReady()) {
fail("Missing listener calls for listener1");
}
if (!listener2.isReady()) {
fail("Missing listener calls for listener2");
}
}

}

Loading…
Cancel
Save