Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cherry pick from 2.2 to master and fix postpone and forward with CCDM (#8342) #8359

Merged
merged 14 commits into from
May 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions flow-client/pom.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.vaadin</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.vaadin.client.flow.StateTree;

import elemental.events.PopStateEvent;
import elemental.json.JsonObject;

/**
* A registry implementation used by {@link ApplicationConnection}.
Expand Down Expand Up @@ -60,11 +61,14 @@ public void setIgnoreScrollRestorationOnNextPopStateEvent(
}

@Override
public void beforeNavigation(String newHref,
boolean triggersServerSideRoundtrip) {
public void beforeClientNavigation(String newHref) {
// don't do anything
}

@Override
public void afterServerNavigation(JsonObject state) {
// don't do anything
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import elemental.dom.Element;
import elemental.dom.Node;
import elemental.json.JsonObject;

/**
* Utility class which handles javascript execution context (see
Expand Down Expand Up @@ -194,6 +195,21 @@ private static Integer getExistingIdOrUpdate(StateNode parent,
return existingId;
}

/**
* Invoke {@link ScrollPositionHandler#afterServerNavigation(JsonObject)}.
*
* @param registry
* the registry
* @param state
* includes scroll position of the previous page and the complete
* href of the router link that was clicked and caused this
* navigation
*/
public static void scrollPositionHandlerAfterServerNavigation(
Registry registry, JsonObject state) {
registry.getScrollPositionHandler().afterServerNavigation(state);
}

private static native boolean isPropertyDefined(Node node, String property)
/*-{
return !!(node["constructor"] && node["constructor"]["properties"] &&
Expand Down
104 changes: 64 additions & 40 deletions flow-client/src/main/java/com/vaadin/client/ScrollPositionHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@ public class ScrollPositionHandler {

private boolean ignoreScrollRestorationOnNextPopStateEvent;

private HandlerRegistration resetScrollRegistration;

/**
* Creates a new instance connected to the given registry.
*
Expand Down Expand Up @@ -143,7 +141,8 @@ private void readAndRestoreScrollPositionsFromHistoryAndSessionStorage(
jsonString = Browser.getWindow().getSessionStorage()
.getItem(createSessionStorageKey(historyResetToken));
} catch (JavaScriptException e) {
Console.error("Failed to get session storage: " + e.getMessage());
Console.error(
"Failed to get session storage: " + e.getMessage());
}
if (jsonString != null) {
JsonObject jsonObject = Json.parse(jsonString);
Expand Down Expand Up @@ -187,9 +186,9 @@ private void onBeforeUnload(Event event) {
Browser.getWindow().getHistory().replaceState(stateObject, "",
Browser.getWindow().getLocation().getHref());
try {
Browser.getWindow().getSessionStorage()
.setItem(createSessionStorageKey(historyResetToken),
sessionStorageObject.toJson());
Browser.getWindow().getSessionStorage().setItem(
createSessionStorageKey(historyResetToken),
sessionStorageObject.toJson());
} catch (JavaScriptException e) {
Console.error("Failed to get session storage: " + e.getMessage());
}
Expand All @@ -204,9 +203,9 @@ private static String createSessionStorageKey(Number historyToken) {
* given pop state event.
* <p>
* This method behaves differently if there has been a
* {@link #beforeNavigation(String, boolean)} before this, and if the pop
* state event is caused by a fragment change that doesn't require a server
* side round-trip.
* {@link #beforeClientNavigation(String)} before this, and if the pop state
* event is caused by a fragment change that doesn't require a server side
* round-trip.
*
* @param event
* the pop state event
Expand Down Expand Up @@ -270,19 +269,16 @@ public void setIgnoreScrollRestorationOnNextPopStateEvent(

/**
* Store scroll positions when there has been navigation triggered by a
* click on a link element.
* click on a link element and no server round-trip is needed. It means
* navigating within the same page.
* <p>
* If href for the page navigated into contains a hash (even just #), then
* the browser will fire a pop state event afterwards.
*
* @param newHref
* the href of the clicked link
* @param triggersServerSideRoundtrip
* <code>true</code> if the navigation will cause a server side
* round-trip, <code>false</code> if not
*/
public void beforeNavigation(String newHref,
boolean triggersServerSideRoundtrip) {
public void beforeClientNavigation(String newHref) {
captureCurrentScrollPositions();

Browser.getWindow().getHistory().replaceState(
Expand All @@ -292,46 +288,27 @@ public void beforeNavigation(String newHref,
// move to page top only if there is no fragment so scroll position
// doesn't bounce around
if (!newHref.contains("#")) {
if (triggersServerSideRoundtrip) {
resetScrollAfterResponse();
} else {
setScrollPosition(new double[] { 0, 0 });
}
resetScroll();
}

currentHistoryIndex++;

if (triggersServerSideRoundtrip) {
// store new index
Browser.getWindow().getHistory().pushState(
createStateObjectWithHistoryIndexAndToken(), "", newHref);
}

// remove old stored scroll positions
xPositions.splice(currentHistoryIndex,
xPositions.length() - currentHistoryIndex);
yPositions.splice(currentHistoryIndex,
yPositions.length() - currentHistoryIndex);
}

private void resetScrollAfterResponse() {
if (resetScrollRegistration == null) {
resetScrollRegistration = registry.getRequestResponseTracker()
.addResponseHandlingEndedHandler(event -> {
resetScrollRegistration.removeHandler();
resetScrollRegistration = null;

setScrollPosition(new double[] { 0, 0 });
});
}

private void resetScroll() {
setScrollPosition(new double[] { 0, 0 });
}

private void captureCurrentScrollPositions() {
double[] xAndYPosition = getScrollPosition();

xPositions.set(currentHistoryIndex, Double.valueOf(xAndYPosition[0]));
yPositions.set(currentHistoryIndex, Double.valueOf(xAndYPosition[1]));
xPositions.set(currentHistoryIndex, xAndYPosition[0]);
yPositions.set(currentHistoryIndex, xAndYPosition[1]);
}

private JsonObject createStateObjectWithHistoryIndexAndToken() {
Expand All @@ -341,6 +318,48 @@ private JsonObject createStateObjectWithHistoryIndexAndToken() {
return state;
}

/**
* Store scroll positions when there has been navigation triggered by a
* click on a link element and a server round-trip was needed. This method
* is called after server-side part is done.
*
* @param state
* includes scroll position of the previous page and the complete
* href of the router link that was clicked and caused this
* navigation
*/
public void afterServerNavigation(JsonObject state) {
if (!state.hasKey("scrollPositionX") || !state.hasKey("scrollPositionY")
|| !state.hasKey("href"))
throw new IllegalStateException(
"scrollPositionX, scrollPositionY and href should be available in ScrollPositionHandler.afterNavigation.");
xPositions.set(currentHistoryIndex, state.getNumber("scrollPositionX"));
yPositions.set(currentHistoryIndex, state.getNumber("scrollPositionY"));

Browser.getWindow().getHistory().replaceState(
createStateObjectWithHistoryIndexAndToken(), "",
Browser.getWindow().getLocation().getHref());

String newHref = state.getString("href");
// move to page top only if there is no fragment so scroll position
// doesn't bounce around
if (!newHref.contains("#")) {
resetScroll();
}

currentHistoryIndex++;

// store new index
Browser.getWindow().getHistory().pushState(
createStateObjectWithHistoryIndexAndToken(), "", newHref);

// remove old stored scroll positions
xPositions.splice(currentHistoryIndex,
xPositions.length() - currentHistoryIndex);
yPositions.splice(currentHistoryIndex,
yPositions.length() - currentHistoryIndex);
}

private void restoreScrollPosition(boolean delayAfterResponse) {
// in case there is another event while waiting for response (?)
if (responseHandlingEndedHandler != null) {
Expand Down Expand Up @@ -389,7 +408,12 @@ private static native void disableNativeScrollRestoration()
}
}-*/;

private static native double[] getScrollPosition()
/**
* Gets the scroll position of the page as an array.
*
* @return an array containing scroll position x (left) and y (top) in order
*/
public static native double[] getScrollPosition()
/*-{
if ($wnd.Vaadin.Flow.getScrollPosition) {
return $wnd.Vaadin.Flow.getScrollPosition();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,26 +202,30 @@ private native JsonObject getContextExecutionObject(
JsMap<Object, StateNode> nodeParameters, Runnable stopApplication)
/*-{
var object = {};
object.getNode = $entry(function (element){
object.getNode = $entry(function (element) {
var node = nodeParameters.get(element);
if ( node == null ){
if (node == null) {
throw new ReferenceError("There is no a StateNode for the given argument.");
}
return node;
});
object.$appId = this.@ExecuteJavaScriptProcessor::getAppId()().replace(/-\d+$/, '');
object.attachExistingElement = $entry(function(parent, previousSibling, tagName, id){
object.registry = this.@ExecuteJavaScriptProcessor::registry;
object.attachExistingElement = $entry(function(parent, previousSibling, tagName, id) {
@com.vaadin.client.ExecuteJavaScriptElementUtils::attachExistingElement(*)(object.getNode(parent), previousSibling, tagName, id);
});
object.populateModelProperties = $entry(function(element, properties){
object.populateModelProperties = $entry(function(element, properties) {
@com.vaadin.client.ExecuteJavaScriptElementUtils::populateModelProperties(*)(object.getNode(element), properties);
});
object.registerUpdatableModelProperties = $entry(function(element, properties){
object.registerUpdatableModelProperties = $entry(function(element, properties) {
@com.vaadin.client.ExecuteJavaScriptElementUtils::registerUpdatableModelProperties(*)(object.getNode(element), properties);
});
object.stopApplication = $entry(function(){
object.stopApplication = $entry(function() {
stopApplication.@java.lang.Runnable::run(*)();
});
object.scrollPositionHandlerAfterServerNavigation = $entry(function(state) {
@com.vaadin.client.ExecuteJavaScriptElementUtils::scrollPositionHandlerAfterServerNavigation(*)(object.registry, state);
});
return object;
}-*/;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.Objects;

import com.vaadin.client.Registry;
import com.vaadin.client.ScrollPositionHandler;
import com.vaadin.client.URIResolver;
import com.vaadin.client.WidgetUtil;
import com.vaadin.flow.shared.ApplicationConstants;
Expand All @@ -28,6 +29,8 @@
import elemental.events.EventTarget;
import elemental.events.MouseEvent;
import elemental.html.AnchorElement;
import elemental.json.Json;
import elemental.json.JsonObject;

/**
* Handler for click events originating from application navigation link
Expand Down Expand Up @@ -87,9 +90,10 @@ private static void handleClick(Registry registry, Event clickEvent) {
// there is no pop state if the hashes are exactly the same
String currentHash = Browser.getDocument().getLocation().getHash();
if (!currentHash.equals(anchor.getHash())) {
registry.getScrollPositionHandler().beforeNavigation(href,
false);
registry.getScrollPositionHandler()
.beforeClientNavigation(href);
}

// the browser triggers a fragment change & pop state event
registry.getScrollPositionHandler()
.setIgnoreScrollRestorationOnNextPopStateEvent(true);
Expand All @@ -116,9 +120,19 @@ private static void handleRouterLinkClick(Event clickEvent, String baseURI,
// don't send hash to server
location = location.split("#", 2)[0];
}
registry.getScrollPositionHandler().beforeNavigation(href, true);

sendServerNavigationEvent(registry, location, null, true);
JsonObject state = createNavigationEventState(href);

sendServerNavigationEvent(registry, location, state, true);
}

private static JsonObject createNavigationEventState(String href) {
double[] scrollPosition = ScrollPositionHandler.getScrollPosition();
JsonObject state = Json.createObject();
state.put("href", href);
state.put("scrollPositionX", scrollPosition[0]);
state.put("scrollPositionY", scrollPosition[1]);
return state;
}

/**
Expand Down Expand Up @@ -249,10 +263,10 @@ public static void sendServerNavigationEvent(Registry registry,
assert registry != null;
assert location != null;

// If the server tells us the session has expired, we refresh (using the
// new location) instead.
// If the server tells us the session has expired, we redirect to the
// new location.
registry.getMessageHandler().setNextResponseSessionExpiredHandler(
() -> WidgetUtil.refresh());
() -> WidgetUtil.redirect(location));
registry.getServerConnector().sendNavigationMessage(location,
stateObject, routerLinkEvent);

Expand Down
12 changes: 2 additions & 10 deletions flow-server/src/main/java/com/vaadin/flow/component/UI.java
Original file line number Diff line number Diff line change
Expand Up @@ -929,16 +929,8 @@ public void navigate(String location, QueryParameters queryParameters) {
Objects.requireNonNull(location, "Location must not be null");
Objects.requireNonNull(queryParameters, "Query parameters must not be null");

Location navigationLocation = new Location(location, queryParameters);
if (!internals.hasLastHandledLocation()
|| !navigationLocation.getPathWithQueryParameters()
.equals(internals.getLastHandledLocation()
.getPathWithQueryParameters())) {
// Enable navigating back
getPage().getHistory().pushState(null, navigationLocation);
}
getRouter().navigate(this, navigationLocation,
NavigationTrigger.PROGRAMMATIC);
getRouter().navigate(this, new Location(location, queryParameters),
NavigationTrigger.UI_NAVIGATE);
}

/**
Expand Down