From e4a50934a2cfb0e652b872376decf581bc0ab057 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Johannes=20Dahlstr=C3=B6m?= Date: Wed, 19 Mar 2014 17:29:45 +0200 Subject: [PATCH] Add reinit method for preserve-on-refresh UIs (#12191) UI.reinit() is now called when an existing, preserved UI is shown after a browser reload of the current page. The default implementation is empty. The browser window size and location are up to date in UI.reinit(); window resize and URI fragment listeners, if any, will be called after returning from UI.reinit(). Change-Id: Ie7aa670aaecf8e0e1510c91325b2a137b41263af --- .../vaadin/annotations/PreserveOnRefresh.java | 5 + server/src/com/vaadin/server/Page.java | 72 +++++++++-- server/src/com/vaadin/server/UIProvider.java | 5 + .../server/communication/UIInitHandler.java | 7 +- server/src/com/vaadin/ui/UI.java | 57 ++++++++- .../src/com/vaadin/ui/UIInitReinitTest.java | 116 ++++++++++++++++++ .../vaadin/tests/components/ui/UIReinit.java | 49 ++++++++ .../tests/components/ui/UIReinitTest.java | 39 ++++++ 8 files changed, 331 insertions(+), 19 deletions(-) create mode 100644 server/tests/src/com/vaadin/ui/UIInitReinitTest.java create mode 100644 uitest/src/com/vaadin/tests/components/ui/UIReinit.java create mode 100644 uitest/src/com/vaadin/tests/components/ui/UIReinitTest.java diff --git a/server/src/com/vaadin/annotations/PreserveOnRefresh.java b/server/src/com/vaadin/annotations/PreserveOnRefresh.java index 0b503b8c3f..801c1e78f2 100644 --- a/server/src/com/vaadin/annotations/PreserveOnRefresh.java +++ b/server/src/com/vaadin/annotations/PreserveOnRefresh.java @@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import com.vaadin.server.UIProvider; +import com.vaadin.ui.UI; /** * Marks a UI that should be retained when the user refreshed the browser @@ -30,6 +31,10 @@ import com.vaadin.server.UIProvider; * adding this annotation to a UI class, the framework will instead reuse the * current UI instance when a reload is detected. *

+ * Whenever a request is received that reloads a preserved UI, the UI's + * {@link UI#reinit(com.vaadin.server.VaadinRequest) reinit} method is invoked + * by the framework. + *

* By using * {@link UIProvider#isPreservedOnRefresh(com.vaadin.server.UICreateEvent)}, the * decision can also be made dynamically based on other parameters than only diff --git a/server/src/com/vaadin/server/Page.java b/server/src/com/vaadin/server/Page.java index 3b96ec2ee1..70b8306bc3 100644 --- a/server/src/com/vaadin/server/Page.java +++ b/server/src/com/vaadin/server/Page.java @@ -32,6 +32,7 @@ import com.vaadin.shared.ui.ui.PageClientRpc; import com.vaadin.shared.ui.ui.PageState; import com.vaadin.shared.ui.ui.UIConstants; import com.vaadin.shared.ui.ui.UIState; +import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.JavaScript; import com.vaadin.ui.LegacyWindow; import com.vaadin.ui.Link; @@ -635,6 +636,9 @@ public class Page implements Serializable { } public void init(VaadinRequest request) { + // NOTE: UI.reinit makes assumptions about the semantics of this method. + // It should be kept in sync if this method is changed. + // Extract special parameter sent by vaadinBootstrap.js String location = request.getParameter("v-loc"); String clientWidth = request.getParameter("v-cw"); @@ -676,28 +680,51 @@ public class Page implements Serializable { } /** - * Updates the internal state with the given values. Does not resize the - * Page or browser window. - * + * For internal use only. Updates the internal state with the given values. + * Does not resize the Page or browser window. + * + * @deprecated As of 7.2, use + * {@link #updateBrowserWindowSize(int, int, boolean)} instead. + * * @param width - * The new width + * the new browser window width * @param height - * The new height + * the new browse window height */ + @Deprecated public void updateBrowserWindowSize(int width, int height) { - boolean fireEvent = false; + updateBrowserWindowSize(width, height, true); + } + + /** + * For internal use only. Updates the internal state with the given values. + * Does not resize the Page or browser window. + * + * @since 7.2 + * + * @param width + * the new browser window width + * @param height + * the new browser window height + * @param fireEvents + * whether to fire {@link BrowserWindowResizeEvent} if the size + * changes + */ + public void updateBrowserWindowSize(int width, int height, + boolean fireEvents) { + boolean sizeChanged = false; if (width != browserWindowWidth) { browserWindowWidth = width; - fireEvent = true; + sizeChanged = true; } if (height != browserWindowHeight) { browserWindowHeight = height; - fireEvent = true; + sizeChanged = true; } - if (fireEvent) { + if (fireEvents && sizeChanged) { fireEvent(new BrowserWindowResizeEvent(this, browserWindowWidth, browserWindowHeight)); } @@ -917,14 +944,37 @@ public class Page implements Serializable { /** * For internal use only. Used to update the server-side location when the * client-side location changes. + * + * @deprecated As of 7.2, use {@link #updateLocation(String, boolean)} + * instead. + * + * @param location + * the new location URI */ + @Deprecated public void updateLocation(String location) { + updateLocation(location, true); + } + + /** + * For internal use only. Used to update the server-side location when the + * client-side location changes. + * + * @since 7.2 + * + * @param location + * the new location URI + * @param fireEvents + * whether to fire {@link UriFragmentChangedEvent} if the URI + * fragment changes + */ + public void updateLocation(String location, boolean fireEvents) { try { String oldUriFragment = this.location.getFragment(); this.location = new URI(location); String newUriFragment = this.location.getFragment(); - if (newUriFragment == null ? oldUriFragment != null - : !newUriFragment.equals(oldUriFragment)) { + if (fireEvents + && !SharedUtil.equals(oldUriFragment, newUriFragment)) { fireEvent(new UriFragmentChangedEvent(this, newUriFragment)); } } catch (URISyntaxException e) { diff --git a/server/src/com/vaadin/server/UIProvider.java b/server/src/com/vaadin/server/UIProvider.java index 3e7c85aea9..a76f396767 100644 --- a/server/src/com/vaadin/server/UIProvider.java +++ b/server/src/com/vaadin/server/UIProvider.java @@ -129,6 +129,11 @@ public abstract class UIProvider implements Serializable { * detect that the application is opened in a browser window where it has * previously been open. The framework attempts to discover this by checking * the value of window.name in the browser. + *

+ * Whenever a preserved UI is reused, its + * {@link UI#reinit(com.vaadin.server.VaadinRequest) reinit} method is + * invoked by the framework first. + * * * @param event * the UI create event with information about the UI and the diff --git a/server/src/com/vaadin/server/communication/UIInitHandler.java b/server/src/com/vaadin/server/communication/UIInitHandler.java index 6ab9d9dc58..5dc44208d1 100644 --- a/server/src/com/vaadin/server/communication/UIInitHandler.java +++ b/server/src/com/vaadin/server/communication/UIInitHandler.java @@ -266,12 +266,7 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler { */ private void reinitUI(UI ui, VaadinRequest request) { UI.setCurrent(ui); - - // Fire fragment change if the fragment has changed - String location = request.getParameter("v-loc"); - if (location != null) { - ui.getPage().updateLocation(location); - } + ui.doReinit(request); } /** diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java index b3004e9ad2..2b2e773601 100644 --- a/server/src/com/vaadin/ui/UI.java +++ b/server/src/com/vaadin/ui/UI.java @@ -16,6 +16,7 @@ package com.vaadin.ui; +import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -27,6 +28,7 @@ import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; +import com.vaadin.annotations.PreserveOnRefresh; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; import com.vaadin.event.ActionManager; @@ -161,7 +163,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements public void resize(int viewWidth, int viewHeight, int windowWidth, int windowHeight) { // TODO We're not doing anything with the view dimensions - getPage().updateBrowserWindowSize(windowWidth, windowHeight); + getPage().updateBrowserWindowSize(windowWidth, windowHeight, true); } @Override @@ -361,7 +363,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements if (variables.containsKey(UIConstants.LOCATION_VARIABLE)) { String location = (String) variables .get(UIConstants.LOCATION_VARIABLE); - getPage().updateLocation(location); + getPage().updateLocation(location, true); } } @@ -659,6 +661,57 @@ public abstract class UI extends AbstractSingleComponentContainer implements */ protected abstract void init(VaadinRequest request); + /** + * Internal reinitialization method, should not be overridden. + * + * @since 7.2 + * @param request + * the request that caused this UI to be reloaded + */ + public void doReinit(VaadinRequest request) { + // This is a horrible hack. We want to have the most recent location and + // browser window size available in reinit(), but we want to call + // listeners, if any, only after reinit(). So we momentarily assign the + // old values back before setting the new values again to ensure the + // events are properly fired. + + Page page = getPage(); + + URI oldLocation = page.getLocation(); + int oldWidth = page.getBrowserWindowWidth(); + int oldHeight = page.getBrowserWindowHeight(); + + page.init(request); + + reinit(request); + + URI newLocation = page.getLocation(); + int newWidth = page.getBrowserWindowWidth(); + int newHeight = page.getBrowserWindowHeight(); + + page.updateLocation(oldLocation.toString(), false); + page.updateBrowserWindowSize(oldWidth, oldHeight, false); + + page.updateLocation(newLocation.toString(), true); + page.updateBrowserWindowSize(newWidth, newHeight, true); + } + + /** + * Reinitializes this UI after a browser refresh if the UI is set to be + * preserved on refresh, typically using the {@link PreserveOnRefresh} + * annotation. This method is intended to be overridden by subclasses if + * needed; the default implementation is empty. + *

+ * The {@link VaadinRequest} can be used to get information about the + * request that caused this UI to be reloaded. + * + * @since 7.2 + * @param request + * the request that caused this UI to be reloaded + */ + protected void reinit(VaadinRequest request) { + } + /** * Sets the thread local for the current UI. This method is used by the * framework to set the current application whenever a new request is diff --git a/server/tests/src/com/vaadin/ui/UIInitReinitTest.java b/server/tests/src/com/vaadin/ui/UIInitReinitTest.java new file mode 100644 index 0000000000..f8ec0e68c2 --- /dev/null +++ b/server/tests/src/com/vaadin/ui/UIInitReinitTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2000-2013 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.ui; + +import org.easymock.EasyMock; +import org.easymock.IMocksControl; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.server.Page.BrowserWindowResizeEvent; +import com.vaadin.server.Page.BrowserWindowResizeListener; +import com.vaadin.server.Page.UriFragmentChangedEvent; +import com.vaadin.server.Page.UriFragmentChangedListener; +import com.vaadin.server.VaadinRequest; + +public class UIInitReinitTest { + + private boolean initCalled; + private boolean reinitCalled; + private boolean fragmentChangeCalled; + private boolean browserWindowResizeCalled; + + private class TestUI extends UI implements UriFragmentChangedListener, + BrowserWindowResizeListener { + @Override + protected void init(VaadinRequest request) { + getPage().addBrowserWindowResizeListener(this); + getPage().addUriFragmentChangedListener(this); + + initCalled = true; + + Assert.assertEquals("foo", getPage().getUriFragment()); + Assert.assertEquals(100, getPage().getBrowserWindowWidth()); + Assert.assertEquals(100, getPage().getBrowserWindowHeight()); + + Assert.assertFalse(fragmentChangeCalled); + Assert.assertFalse(browserWindowResizeCalled); + } + + @Override + protected void reinit(VaadinRequest request) { + reinitCalled = true; + + Assert.assertEquals("bar", getPage().getUriFragment()); + Assert.assertEquals(200, getPage().getBrowserWindowWidth()); + Assert.assertEquals(200, getPage().getBrowserWindowHeight()); + + Assert.assertFalse(fragmentChangeCalled); + Assert.assertFalse(browserWindowResizeCalled); + } + + @Override + public void browserWindowResized(BrowserWindowResizeEvent event) { + Assert.assertEquals(200, event.getWidth()); + Assert.assertEquals(200, event.getHeight()); + browserWindowResizeCalled = true; + } + + @Override + public void uriFragmentChanged(UriFragmentChangedEvent event) { + Assert.assertEquals("bar", event.getUriFragment()); + fragmentChangeCalled = true; + } + }; + + @Before + public void setUp() { + initCalled = reinitCalled = fragmentChangeCalled = browserWindowResizeCalled = false; + } + + @Test + public void testListenersCalled() { + IMocksControl control = EasyMock.createNiceControl(); + + VaadinRequest initRequest = control.createMock(VaadinRequest.class); + EasyMock.expect(initRequest.getParameter("v-loc")).andReturn( + "http://example.com/#foo"); + EasyMock.expect(initRequest.getParameter("v-cw")).andReturn("100"); + EasyMock.expect(initRequest.getParameter("v-ch")).andReturn("100"); + + VaadinRequest reinitRequest = control.createMock(VaadinRequest.class); + EasyMock.expect(reinitRequest.getParameter("v-loc")).andReturn( + "http://example.com/#bar"); + EasyMock.expect(reinitRequest.getParameter("v-cw")).andReturn("200"); + EasyMock.expect(reinitRequest.getParameter("v-ch")).andReturn("200"); + + control.replay(); + + UI ui = new TestUI(); + ui.doInit(initRequest, 0, ""); + + Assert.assertTrue(initCalled); + Assert.assertFalse(fragmentChangeCalled); + Assert.assertFalse(browserWindowResizeCalled); + + ui.doReinit(reinitRequest); + + Assert.assertTrue(reinitCalled); + Assert.assertTrue(fragmentChangeCalled); + Assert.assertTrue(browserWindowResizeCalled); + } +} diff --git a/uitest/src/com/vaadin/tests/components/ui/UIReinit.java b/uitest/src/com/vaadin/tests/components/ui/UIReinit.java new file mode 100644 index 0000000000..bef7a5a6d1 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/ui/UIReinit.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2013 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.tests.components.ui; + +import com.vaadin.annotations.PreserveOnRefresh; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Label; + +@PreserveOnRefresh +public class UIReinit extends AbstractTestUI { + + public static final String REINIT_ID = "reinit"; + + @Override + protected void setup(VaadinRequest request) { + } + + @Override + protected void reinit(VaadinRequest request) { + Label l = new Label("Reinit!"); + l.setId(REINIT_ID); + addComponent(l); + } + + @Override + public String getTestDescription() { + return "UI reinit after refresh"; + } + + @Override + protected Integer getTicketNumber() { + return Integer.valueOf(12191); + } +} diff --git a/uitest/src/com/vaadin/tests/components/ui/UIReinitTest.java b/uitest/src/com/vaadin/tests/components/ui/UIReinitTest.java new file mode 100644 index 0000000000..82132d3c65 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/ui/UIReinitTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2013 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.tests.components.ui; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.testbench.By; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class UIReinitTest extends MultiBrowserTest { + + @Test + public void testUIReinit() { + openTestURL(); + Assert.assertFalse(reinitLabelExists()); + // Reload the page; UI.reinit should be invoked + openTestURL(); + Assert.assertTrue(reinitLabelExists()); + } + + private boolean reinitLabelExists() { + return !getDriver().findElements(By.id(UIReinit.REINIT_ID)).isEmpty(); + } +} -- 2.39.5