diff options
author | Matti Tahvonen <matti@vaadin.com> | 2017-01-13 17:07:36 +0200 |
---|---|---|
committer | Pekka Hyvönen <pekka@vaadin.com> | 2017-01-13 17:07:36 +0200 |
commit | 62c44dd77e47c908361a87332182f2e2465972c0 (patch) | |
tree | 2485171f3a1326d0f7bf0a067569e535dd18d626 /server | |
parent | fe536b3efc4ed3d6637fead851c01375af91f762 (diff) | |
download | vaadin-framework-62c44dd77e47c908361a87332182f2e2465972c0.tar.gz vaadin-framework-62c44dd77e47c908361a87332182f2e2465972c0.zip |
Support for HTML5 push/replaceState for proper deep linking features (#8116)
* Added support for HTML5 push/replaceState for proper deep linkin features
* Automated test script now works at least on chrome
* Uses html5 push/popstate to implement uri fragment feature
* fire legacy fragment change events also via popstate events rpc calls
* send new fragments via pushstate mechanism
* formatting
* Formatting and adding test and workaround for IE bug
* Formatting and depracated UriFragmentListener
* Aligned naming in the new API
* Ignored IE due to web driver bug
Tested a workaround with javascript based window.location.href fetch,
but that don’t seem to work stable enough.
Diffstat (limited to 'server')
-rw-r--r-- | server/src/main/java/com/vaadin/server/Page.java | 203 | ||||
-rw-r--r-- | server/src/main/java/com/vaadin/ui/UI.java | 15 |
2 files changed, 205 insertions, 13 deletions
diff --git a/server/src/main/java/com/vaadin/server/Page.java b/server/src/main/java/com/vaadin/server/Page.java index c96114b3f7..bf55e7ce9f 100644 --- a/server/src/main/java/com/vaadin/server/Page.java +++ b/server/src/main/java/com/vaadin/server/Page.java @@ -251,7 +251,9 @@ public class Page implements Serializable { * changes. * * @see Page#addUriFragmentChangedListener(UriFragmentChangedListener) + * @deprecated Use {@link PopStateListener} instead */ + @Deprecated @FunctionalInterface public interface UriFragmentChangedListener extends Serializable { /** @@ -273,6 +275,32 @@ public class Page implements Serializable { "uriFragmentChanged", UriFragmentChangedEvent.class); /** + * Listener that that gets notified when the URI of the page changes due to + * back/forward functionality of the browser. + * + * @see Page#addPopStateListener(PopStateListener) + * @since 8.0 + */ + @FunctionalInterface + public interface PopStateListener extends Serializable { + /** + * Event handler method invoked when the URI fragment of the page + * changes. Please note that the initial URI fragment has already been + * set when a new UI is initialized, so there will not be any initial + * event for listeners added during {@link UI#init(VaadinRequest)}. + * + * @see Page#addUriFragmentChangedListener(UriFragmentChangedListener) + * + * @param event + * the URI fragment changed event + */ + public void uriChanged(PopStateEvent event); + } + + private static final Method URI_CHANGED_METHOD = ReflectTools.findMethod( + Page.PopStateListener.class, "uriChanged", PopStateEvent.class); + + /** * Resources to be opened automatically on next repaint. The list is * automatically cleared when it has been sent to the client. */ @@ -328,6 +356,53 @@ public class Page implements Serializable { } } + /** + * Event fired when the URI of a <code>Page</code> changes (aka HTML 5 + * popstate event) on the client side due to browsers back/forward + * functionality. + * + * @see Page#addPopStateListener(PopStateListener) + * @since 8.0 + */ + public static class PopStateEvent extends EventObject { + + /** + * The new URI as String + */ + private final String uri; + + /** + * Creates a new instance of PopstateEvent. + * + * @param source + * the Source of the event. + * @param uri + * the new uri + */ + public PopStateEvent(Page source, String uri) { + super(source); + this.uri = uri; + } + + /** + * Gets the page in which the uri has changed. + * + * @return the page in which the uri has changed + */ + public Page getPage() { + return (Page) getSource(); + } + + /** + * Get the new URI + * + * @return the new uri + */ + public String getUri() { + return uri; + } + } + @FunctionalInterface private static interface InjectedStyle extends Serializable { public void paint(int id, PaintTarget target) throws PaintException; @@ -483,6 +558,9 @@ public class Page implements Serializable { private String windowName; + private String newPushState; + private String newReplaceState; + public Page(UI uI, PageState state) { this.uI = uI; this.state = state; @@ -516,7 +594,10 @@ public class Page implements Serializable { * @param listener * the URI fragment listener to add * @return a registration object for removing the listener + * @deprecated Use {@link Page#addPopStateListener(PopStateListener)} + * instead */ + @Deprecated public Registration addUriFragmentChangedListener( Page.UriFragmentChangedListener listener) { return addListener(UriFragmentChangedEvent.class, listener, @@ -524,6 +605,27 @@ public class Page implements Serializable { } /** + * Adds a listener that gets notified every time the URI of this page is + * changed due to back/forward functionality of the browser. + * <p> + * Note that one only gets notified when the back/forward button affects + * history changes with-in same UI, created by + * {@link Page#pushState(String)} or {@link Page#replaceState(String)} + * functions. + * + * @see #getLocation() + * @see Registration + * + * @param listener + * the Popstate listener to add + * @return a registration object for removing the listener + * @since 8.0 + */ + public Registration addPopStateListener(Page.PopStateListener listener) { + return addListener(PopStateEvent.class, listener, URI_CHANGED_METHOD); + } + + /** * Removes a URI fragment listener that was previously added to this page. * * @param listener @@ -580,6 +682,7 @@ public class Page implements Serializable { try { location = new URI(location.getScheme(), location.getSchemeSpecificPart(), newUriFragment); + pushState(location); } catch (URISyntaxException e) { // This should not actually happen as the fragment syntax is not // constrained @@ -588,7 +691,6 @@ public class Page implements Serializable { if (fireEvents) { fireEvent(new UriFragmentChangedEvent(this, newUriFragment)); } - uI.markAsDirty(); } private void fireEvent(EventObject event) { @@ -866,9 +968,14 @@ public class Page implements Serializable { notifications = null; } - if (location != null) { - target.addAttribute(UIConstants.LOCATION_VARIABLE, - location.toString()); + if (newPushState != null) { + target.addAttribute(UIConstants.ATTRIBUTE_PUSH_STATE, newPushState); + newPushState = null; + } + if (newReplaceState != null) { + target.addAttribute(UIConstants.ATTRIBUTE_REPLACE_STATE, + newReplaceState); + newReplaceState = null; } if (styles != null) { @@ -932,6 +1039,84 @@ public class Page implements Serializable { } /** + * Updates the browsers URI without causing actual page change. This method + * is useful if you wish implement "deep linking" to your application. + * Calling the method also adds a new entry to clients browser history and + * you can further use {@link PopStateListener} to track the usage of + * back/forward feature in browser. + * <p> + * Note, the current implementation supports setting only one new uri in one + * user interaction. + * + * @param uri + * to be used for pushState operation. The URI is resolved over + * the current location. If the given URI is absolute, it must be + * of same origin as the current URI or the browser will not + * accept the new value. + * @since 8.0 + */ + public void pushState(String uri) { + newPushState = uri; + uI.markAsDirty(); + location = location.resolve(uri); + } + + /** + * Updates the browsers URI without causing actual page change. This method + * is useful if you wish implement "deep linking" to your application. + * Calling the method also adds a new entry to clients browser history and + * you can further use {@link PopStateListener} to track the usage of + * back/forward feature in browser. + * <p> + * Note, the current implementation supports setting only one new uri in one + * user interaction. + * + * @param uri + * the URI to be used for pushState operation. The URI is + * resolved over the current location. If the given URI is + * absolute, it must be of same origin as the current URI or the + * browser will not accept the new value. + * @since 8.0 + */ + public void pushState(URI uri) { + pushState(uri.toString()); + } + + /** + * Updates the browsers URI without causing actual page change in the same + * way as {@link #pushState(String)}, but does not add new entry to browsers + * history. + * + * @param uri + * the URI to be used for replaceState operation. The URI is + * resolved over the current location. If the given URI is + * absolute, it must be of same origin as the current URI or the + * browser will not accept the new value. + * @since 8.0 + */ + public void replaceState(String uri) { + newReplaceState = uri; + uI.markAsDirty(); + location = location.resolve(uri); + } + + /** + * Updates the browsers URI without causing actual page change in the same + * way as {@link #pushState(URI)}, but does not add new entry to browsers + * history. + * + * @param uri + * the URI to be used for replaceState operation. The URI is + * resolved over the current location. If the given URI is + * absolute, it must be of same origin as the current URI or the + * browser will not accept the new value. + * @since 8.0 + */ + public void replaceState(URI uri) { + replaceState(uri.toString()); + } + + /** * For internal use only. Used to update the server-side location when the * client-side location changes. * @@ -943,7 +1128,7 @@ public class Page implements Serializable { */ @Deprecated public void updateLocation(String location) { - updateLocation(location, true); + updateLocation(location, true, false); } /** @@ -957,8 +1142,11 @@ public class Page implements Serializable { * @param fireEvents * whether to fire {@link UriFragmentChangedEvent} if the URI * fragment changes + * @param firePopstate + * whether to fire {@link PopStateEvent} */ - public void updateLocation(String location, boolean fireEvents) { + public void updateLocation(String location, boolean fireEvents, + boolean firePopstate) { try { String oldUriFragment = this.location.getFragment(); this.location = new URI(location); @@ -967,6 +1155,9 @@ public class Page implements Serializable { && !SharedUtil.equals(oldUriFragment, newUriFragment)) { fireEvent(new UriFragmentChangedEvent(this, newUriFragment)); } + if (firePopstate) { + fireEvent(new PopStateEvent(this, location)); + } } catch (URISyntaxException e) { throw new RuntimeException(e); } diff --git a/server/src/main/java/com/vaadin/ui/UI.java b/server/src/main/java/com/vaadin/ui/UI.java index 437a188361..c0dabb37c0 100644 --- a/server/src/main/java/com/vaadin/ui/UI.java +++ b/server/src/main/java/com/vaadin/ui/UI.java @@ -193,6 +193,12 @@ public abstract class UI extends AbstractSingleComponentContainer public void acknowledge() { // Nothing to do, just need the message to be sent and processed } + + @Override + public void popstate(String uri) { + getPage().updateLocation(uri, true, true); + + } }; private DebugWindowServerRpc debugRpc = new DebugWindowServerRpc() { @Override @@ -437,11 +443,6 @@ public abstract class UI extends AbstractSingleComponentContainer actionManager.handleActions(variables, this); } - if (variables.containsKey(UIConstants.LOCATION_VARIABLE)) { - String location = (String) variables - .get(UIConstants.LOCATION_VARIABLE); - getPage().updateLocation(location, true); - } } /* @@ -789,10 +790,10 @@ public abstract class UI extends AbstractSingleComponentContainer int newWidth = page.getBrowserWindowWidth(); int newHeight = page.getBrowserWindowHeight(); - page.updateLocation(oldLocation.toString(), false); + page.updateLocation(oldLocation.toString(), false, false); page.updateBrowserWindowSize(oldWidth, oldHeight, false); - page.updateLocation(newLocation.toString(), true); + page.updateLocation(newLocation.toString(), true, false); page.updateBrowserWindowSize(newWidth, newHeight, true); } |