From 4ac6d0e229cb5ce989160d082f1bb050e05282cc Mon Sep 17 00:00:00 2001 From: ScraM Team Date: Wed, 12 Aug 2020 22:07:23 -0700 Subject: [PATCH 1/5] Refactored hash routing logic into a strategy and added path routing strategy. Updated LinkComponent to utilize routing strategy. --- .../flavour/routing/HashRoutingStrategy.java | 66 +++++++++++++ .../flavour/routing/PathRoutingStrategy.java | 94 +++++++++++++++++++ .../java/org/teavm/flavour/routing/Route.java | 8 +- .../org/teavm/flavour/routing/Routing.java | 29 +++++- .../flavour/routing/RoutingListener.java | 23 +++++ .../flavour/routing/RoutingStrategy.java | 38 ++++++++ templates/pom.xml | 5 + .../components/html/LinkComponent.java | 5 +- .../teavm/flavour/widgets/RouteBinder.java | 11 ++- 9 files changed, 264 insertions(+), 15 deletions(-) create mode 100644 routing/src/main/java/org/teavm/flavour/routing/HashRoutingStrategy.java create mode 100644 routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java create mode 100644 routing/src/main/java/org/teavm/flavour/routing/RoutingListener.java create mode 100644 routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java diff --git a/routing/src/main/java/org/teavm/flavour/routing/HashRoutingStrategy.java b/routing/src/main/java/org/teavm/flavour/routing/HashRoutingStrategy.java new file mode 100644 index 00000000..7a90766f --- /dev/null +++ b/routing/src/main/java/org/teavm/flavour/routing/HashRoutingStrategy.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020 ScraM-Team. + * + * 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 org.teavm.flavour.routing; + +import static org.teavm.flavour.routing.Routing.build; +import org.teavm.jso.browser.Location; +import org.teavm.jso.browser.Window; +import org.teavm.jso.dom.html.HTMLElement; + +/** + * Handles routing via URL hashes. + */ +class HashRoutingStrategy implements RoutingStrategy { + @Override + public void notifyListeners() { + } + + @Override + public void addListener(RoutingListener listenerNew) { + } + + @Override + public T open(Window window, Class routeType) { + return build(routeType, hash -> window.getLocation().setHash(hash)); + } + + @Override + public T replace(Window window, Class routeType) { + return build(routeType, hash -> { + window.getHistory().replaceState(null, null, "#" + Window.encodeURI(hash)); + }); + } + + @Override + public String parse(Window window) { + Location location = window.getLocation(); + String hash = location.getHash(); + if (hash.startsWith("#")) { + hash = hash.substring(1); + } + return hash; + } + + @Override + public boolean isBlank(Window window) { + return window.getLocation().getHash().isEmpty() || window.getLocation().getHash().equals("#"); + } + + @Override + public String makeUri(HTMLElement element, String hash) { + return "#" + hash; + } +} diff --git a/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java b/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java new file mode 100644 index 00000000..c83bdb10 --- /dev/null +++ b/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java @@ -0,0 +1,94 @@ +/* + * Copyright 2020 ScraM-Team. + * + * 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 org.teavm.flavour.routing; + +import java.util.ArrayList; +import java.util.List; +import static org.teavm.flavour.routing.Routing.build; +import org.teavm.jso.JSBody; +import org.teavm.jso.browser.Location; +import org.teavm.jso.browser.Window; +import org.teavm.jso.dom.html.HTMLDocument; +import org.teavm.jso.dom.html.HTMLElement; + +/** + * Handles routing via URL path changes. + */ +class PathRoutingStrategy implements RoutingStrategy { + private List listListeners = new ArrayList<>(); + + @JSBody(params = {"document"}, script = "return document.baseURI;") + native public static String getBaseUri(HTMLDocument document); + + private String trim(String path) { + if (path.startsWith("/")) { + return path.substring(1); + } + else return path; + } + + @Override + public void notifyListeners() { + for (RoutingListener listener : listListeners) { + listener.handleLocationChange(); + } + } + + @Override + public void addListener(RoutingListener listenerNew) { + listListeners.add(listenerNew); + } + + @Override + public T open(Window window, Class routeType) { + return build(routeType, hash -> { + window.getHistory().pushState(null, null, getBaseUri(window.getDocument()) + Window.encodeURI(trim(hash))); + notifyListeners(); + }); + } + + @Override + public T replace(Window window, Class routeType) { + return build(routeType, hash -> { + window.getHistory().replaceState(null, null, getBaseUri(window.getDocument()) + Window.encodeURI(trim(hash))); + notifyListeners(); + }); + } + + @Override + public String parse(Window window) { + Location location = window.getLocation(); + String baseUri = getBaseUri(window.getDocument()); + String fullUrl = location.getFullURL(); + String path = fullUrl.substring(baseUri.length()); + if (path.startsWith("#")) { + path = path.substring(1); + } + return "/" + path; + } + + @Override + public boolean isBlank(Window window) { + String path = parse(window); + return path.isEmpty(); + } + + @Override + public String makeUri(HTMLElement element, String path) { + String pathTrimmed = path.startsWith("/") ? path.substring(1) : path; + return getBaseUri(element.getOwnerDocument()) + pathTrimmed; + } +} diff --git a/routing/src/main/java/org/teavm/flavour/routing/Route.java b/routing/src/main/java/org/teavm/flavour/routing/Route.java index 9f92a223..b6e6bc7e 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/Route.java +++ b/routing/src/main/java/org/teavm/flavour/routing/Route.java @@ -17,7 +17,6 @@ import org.teavm.flavour.routing.emit.PathImplementor; import org.teavm.flavour.routing.emit.RoutingImpl; -import org.teavm.jso.browser.Location; import org.teavm.jso.browser.Window; public interface Route { @@ -26,12 +25,7 @@ default boolean parse() { } default boolean parse(Window window) { - Location location = window.getLocation(); - String hash = location.getHash(); - if (hash.startsWith("#")) { - hash = hash.substring(1); - } - return parse(hash); + return parse(Routing.parse(window)); } default boolean parse(String path) { diff --git a/routing/src/main/java/org/teavm/flavour/routing/Routing.java b/routing/src/main/java/org/teavm/flavour/routing/Routing.java index 8a69a9d9..f5c4690d 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/Routing.java +++ b/routing/src/main/java/org/teavm/flavour/routing/Routing.java @@ -19,8 +19,31 @@ import org.teavm.flavour.routing.emit.PathImplementor; import org.teavm.flavour.routing.emit.RoutingImpl; import org.teavm.jso.browser.Window; +import org.teavm.jso.dom.html.HTMLElement; public final class Routing { + private static RoutingStrategy routingStrategy = new HashRoutingStrategy(); + + public static void usePathStrategy() { + routingStrategy = new PathRoutingStrategy(); + } + + public static void addListener(RoutingListener listenerNew) { + routingStrategy.addListener(listenerNew); + } + + static String parse(Window window) { + return routingStrategy.parse(window); + } + + public static boolean isBlank(Window window) { + return routingStrategy.isBlank(window); + } + + public static String makeUri(HTMLElement element, String path) { + return routingStrategy.makeUri(element, path); + } + private Routing() { } @@ -38,7 +61,7 @@ public static T build(Class routeType, Consumer con } public static T open(Window window, Class routeType) { - return build(routeType, hash -> window.getLocation().setHash(hash)); + return routingStrategy.open(window, routeType); } public static T open(Class routeType) { @@ -46,9 +69,7 @@ public static T open(Class routeType) { } static T replace(Window window, Class routeType) { - return build(routeType, hash -> { - window.getHistory().replaceState(null, null, "#" + Window.encodeURI(hash)); - }); + return routingStrategy.replace(window, routeType); } static T replace(Class routeType) { diff --git a/routing/src/main/java/org/teavm/flavour/routing/RoutingListener.java b/routing/src/main/java/org/teavm/flavour/routing/RoutingListener.java new file mode 100644 index 00000000..2e599015 --- /dev/null +++ b/routing/src/main/java/org/teavm/flavour/routing/RoutingListener.java @@ -0,0 +1,23 @@ +/* + * Copyright 2020 ScraM-Team. + * + * 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 org.teavm.flavour.routing; + +/** + * Implement this to be ready to receive notifications that the route has changed. + */ +public interface RoutingListener { + void handleLocationChange(); +} diff --git a/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java b/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java new file mode 100644 index 00000000..e490aa2c --- /dev/null +++ b/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 ScraM-Team. + * + * 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 org.teavm.flavour.routing; + +import org.teavm.jso.browser.Window; +import org.teavm.jso.dom.html.HTMLElement; + +/** + * Methods required of a pluggable routing implementation. + */ +public interface RoutingStrategy { + void notifyListeners(); + + void addListener(RoutingListener listenerNew); + + T open(Window window, Class routeType); + + T replace(Window window, Class routeType); + + String parse(Window window); + + boolean isBlank(Window window); + + String makeUri(HTMLElement element, String path); +} diff --git a/templates/pom.xml b/templates/pom.xml index dd552065..60883db6 100644 --- a/templates/pom.xml +++ b/templates/pom.xml @@ -33,6 +33,11 @@ teavm-flavour-expr ${project.version} + + org.teavm.flavour + teavm-flavour-routing + ${project.version} + org.teavm teavm-jso-apis diff --git a/templates/src/main/java/org/teavm/flavour/components/html/LinkComponent.java b/templates/src/main/java/org/teavm/flavour/components/html/LinkComponent.java index 6023eccd..e9be22de 100644 --- a/templates/src/main/java/org/teavm/flavour/components/html/LinkComponent.java +++ b/templates/src/main/java/org/teavm/flavour/components/html/LinkComponent.java @@ -16,6 +16,7 @@ package org.teavm.flavour.components.html; import java.util.function.Consumer; +import org.teavm.flavour.routing.Routing; import org.teavm.flavour.templates.BindAttributeComponent; import org.teavm.flavour.templates.BindContent; import org.teavm.flavour.templates.ModifierTarget; @@ -34,7 +35,7 @@ public LinkComponent(ModifierTarget target) { } private Consumer linkConsumer = str -> { - value = str; + value = Routing.makeUri(element, str); setHref(element, value); }; @@ -52,6 +53,6 @@ public void render() { public void destroy() { } - @JSBody(params = { "elem", "value" }, script = "elem.href = '#' + value;") + @JSBody(params = { "elem", "value" }, script = "elem.href = value;") private static native void setHref(HTMLElement elem, String value); } diff --git a/widgets/src/main/java/org/teavm/flavour/widgets/RouteBinder.java b/widgets/src/main/java/org/teavm/flavour/widgets/RouteBinder.java index 35922429..df67ec21 100644 --- a/widgets/src/main/java/org/teavm/flavour/widgets/RouteBinder.java +++ b/widgets/src/main/java/org/teavm/flavour/widgets/RouteBinder.java @@ -20,12 +20,13 @@ import java.util.function.Consumer; import org.teavm.flavour.routing.Route; import org.teavm.flavour.routing.Routing; +import org.teavm.flavour.routing.RoutingListener; import org.teavm.flavour.templates.Templates; import org.teavm.jso.browser.Window; import org.teavm.jso.dom.events.EventListener; import org.teavm.jso.dom.events.HashChangeEvent; -public class RouteBinder { +public class RouteBinder implements RoutingListener { Window window; private List routes = new ArrayList<>(); private Runnable errorHandler; @@ -38,6 +39,7 @@ public RouteBinder() { public RouteBinder(Window window) { attach(window); + Routing.addListener(this); } public Window getWindow() { @@ -71,7 +73,7 @@ public RouteBinder remove(Route route) { } public void update() { - if (window.getLocation().getHash().isEmpty() || window.getLocation().getHash().equals("#")) { + if (Routing.isBlank(window)) { defaultAction.accept(defaultRoute); return; } @@ -94,4 +96,9 @@ public RouteBinder withDefault(Class routeType, Consumer } EventListener listener = evt -> update(); + + @Override + public void handleLocationChange() { + update(); + } } From 93b083c8f9fdd6acc4d78b0eec07907a042ee142 Mon Sep 17 00:00:00 2001 From: ScraM Team Date: Mon, 17 Aug 2020 22:20:28 -0700 Subject: [PATCH 2/5] Added back button support to path-based routing; Cleaned up checkstyle warnings --- .../flavour/routing/HashRoutingStrategy.java | 29 +++++++++------ .../flavour/routing/PathRoutingStrategy.java | 35 ++++++++++++------- .../org/teavm/flavour/routing/Routing.java | 10 ++++-- .../flavour/routing/RoutingListener.java | 20 +++++------ .../flavour/routing/RoutingStrategy.java | 26 ++++++++------ .../teavm/flavour/widgets/RouteBinder.java | 6 ++-- 6 files changed, 77 insertions(+), 49 deletions(-) diff --git a/routing/src/main/java/org/teavm/flavour/routing/HashRoutingStrategy.java b/routing/src/main/java/org/teavm/flavour/routing/HashRoutingStrategy.java index 7a90766f..8bc75926 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/HashRoutingStrategy.java +++ b/routing/src/main/java/org/teavm/flavour/routing/HashRoutingStrategy.java @@ -1,29 +1,31 @@ /* - * Copyright 2020 ScraM-Team. + * Copyright 2020 ScraM-Team. * - * 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 + * 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 + * 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. + * 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 org.teavm.flavour.routing; import static org.teavm.flavour.routing.Routing.build; import org.teavm.jso.browser.Location; import org.teavm.jso.browser.Window; +import org.teavm.jso.dom.events.EventListener; +import org.teavm.jso.dom.events.HashChangeEvent; import org.teavm.jso.dom.html.HTMLElement; /** * Handles routing via URL hashes. */ -class HashRoutingStrategy implements RoutingStrategy { +class HashRoutingStrategy implements RoutingStrategy { @Override public void notifyListeners() { } @@ -63,4 +65,9 @@ public boolean isBlank(Window window) { public String makeUri(HTMLElement element, String hash) { return "#" + hash; } + + @Override + public void addListener(Window window, EventListener listener) { + window.listenHashChange(listener); + } } diff --git a/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java b/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java index c83bdb10..262ea4e7 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java +++ b/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java @@ -1,17 +1,17 @@ /* - * Copyright 2020 ScraM-Team. + * Copyright 2020 ScraM-Team. * - * 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 + * 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 + * 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. + * 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 org.teavm.flavour.routing; @@ -21,18 +21,23 @@ import org.teavm.jso.JSBody; import org.teavm.jso.browser.Location; import org.teavm.jso.browser.Window; +import org.teavm.jso.dom.events.Event; +import org.teavm.jso.dom.events.EventListener; import org.teavm.jso.dom.html.HTMLDocument; import org.teavm.jso.dom.html.HTMLElement; /** * Handles routing via URL path changes. */ -class PathRoutingStrategy implements RoutingStrategy { +class PathRoutingStrategy implements RoutingStrategy { private List listListeners = new ArrayList<>(); @JSBody(params = {"document"}, script = "return document.baseURI;") native public static String getBaseUri(HTMLDocument document); + @JSBody(params = {"window", "listener"}, script = "window.onpopstate = listener;") + native public static void listenPopState(Window window, EventListener listener); + private String trim(String path) { if (path.startsWith("/")) { return path.substring(1); @@ -63,7 +68,8 @@ public T open(Window window, Class routeType) { @Override public T replace(Window window, Class routeType) { return build(routeType, hash -> { - window.getHistory().replaceState(null, null, getBaseUri(window.getDocument()) + Window.encodeURI(trim(hash))); + window.getHistory().replaceState(null, null, + getBaseUri(window.getDocument()) + Window.encodeURI(trim(hash))); notifyListeners(); }); } @@ -91,4 +97,9 @@ public String makeUri(HTMLElement element, String path) { String pathTrimmed = path.startsWith("/") ? path.substring(1) : path; return getBaseUri(element.getOwnerDocument()) + pathTrimmed; } + + @Override + public void addListener(Window window, EventListener listener) { + listenPopState(window, (EventListener) listener); + } } diff --git a/routing/src/main/java/org/teavm/flavour/routing/Routing.java b/routing/src/main/java/org/teavm/flavour/routing/Routing.java index f5c4690d..67355164 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/Routing.java +++ b/routing/src/main/java/org/teavm/flavour/routing/Routing.java @@ -19,6 +19,8 @@ import org.teavm.flavour.routing.emit.PathImplementor; import org.teavm.flavour.routing.emit.RoutingImpl; import org.teavm.jso.browser.Window; +import org.teavm.jso.dom.events.Event; +import org.teavm.jso.dom.events.EventListener; import org.teavm.jso.dom.html.HTMLElement; public final class Routing { @@ -44,6 +46,10 @@ public static String makeUri(HTMLElement element, String path) { return routingStrategy.makeUri(element, path); } + public static void addListener(Window window, EventListener listener) { + routingStrategy.addListener(window, listener); + } + private Routing() { } @@ -61,7 +67,7 @@ public static T build(Class routeType, Consumer con } public static T open(Window window, Class routeType) { - return routingStrategy.open(window, routeType); + return (T) routingStrategy.open(window, routeType); } public static T open(Class routeType) { @@ -69,7 +75,7 @@ public static T open(Class routeType) { } static T replace(Window window, Class routeType) { - return routingStrategy.replace(window, routeType); + return (T) routingStrategy.replace(window, routeType); } static T replace(Class routeType) { diff --git a/routing/src/main/java/org/teavm/flavour/routing/RoutingListener.java b/routing/src/main/java/org/teavm/flavour/routing/RoutingListener.java index 2e599015..45bec64a 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/RoutingListener.java +++ b/routing/src/main/java/org/teavm/flavour/routing/RoutingListener.java @@ -1,17 +1,17 @@ /* - * Copyright 2020 ScraM-Team. + * Copyright 2020 ScraM-Team. * - * 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 + * 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 + * 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. + * 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 org.teavm.flavour.routing; diff --git a/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java b/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java index e490aa2c..b63708d7 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java +++ b/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java @@ -1,27 +1,29 @@ /* - * Copyright 2020 ScraM-Team. + * Copyright 2020 ScraM-Team. * - * 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 + * 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 + * 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. + * 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 org.teavm.flavour.routing; import org.teavm.jso.browser.Window; +import org.teavm.jso.dom.events.Event; +import org.teavm.jso.dom.events.EventListener; import org.teavm.jso.dom.html.HTMLElement; /** * Methods required of a pluggable routing implementation. */ -public interface RoutingStrategy { +public interface RoutingStrategy { void notifyListeners(); void addListener(RoutingListener listenerNew); @@ -35,4 +37,6 @@ public interface RoutingStrategy { boolean isBlank(Window window); String makeUri(HTMLElement element, String path); + + void addListener(Window window, EventListener listener); } diff --git a/widgets/src/main/java/org/teavm/flavour/widgets/RouteBinder.java b/widgets/src/main/java/org/teavm/flavour/widgets/RouteBinder.java index df67ec21..b43781bc 100644 --- a/widgets/src/main/java/org/teavm/flavour/widgets/RouteBinder.java +++ b/widgets/src/main/java/org/teavm/flavour/widgets/RouteBinder.java @@ -23,8 +23,8 @@ import org.teavm.flavour.routing.RoutingListener; import org.teavm.flavour.templates.Templates; import org.teavm.jso.browser.Window; +import org.teavm.jso.dom.events.Event; import org.teavm.jso.dom.events.EventListener; -import org.teavm.jso.dom.events.HashChangeEvent; public class RouteBinder implements RoutingListener { Window window; @@ -51,7 +51,7 @@ public void attach(Window window) { throw new IllegalStateException("This dispatcher is already attached to a window"); } this.window = window; - window.listenHashChange(listener); + Routing.addListener(window, listener); } public void detach() { @@ -95,7 +95,7 @@ public RouteBinder withDefault(Class routeType, Consumer return this; } - EventListener listener = evt -> update(); + EventListener listener = evt -> update(); @Override public void handleLocationChange() { From fd7fe006c22b7946fd66577931d74b689f4fef8a Mon Sep 17 00:00:00 2001 From: ScraM Team Date: Thu, 17 Sep 2020 20:23:11 -0700 Subject: [PATCH 3/5] Added html:link support to path-based routing --- .../flavour/routing/HashRoutingStrategy.java | 7 ++++++- .../flavour/routing/PathRoutingStrategy.java | 15 ++++++++++++--- .../java/org/teavm/flavour/routing/Routing.java | 8 ++++++++ .../teavm/flavour/routing/RoutingStrategy.java | 2 ++ .../flavour/components/html/LinkComponent.java | 17 +++++++++++++++-- 5 files changed, 43 insertions(+), 6 deletions(-) diff --git a/routing/src/main/java/org/teavm/flavour/routing/HashRoutingStrategy.java b/routing/src/main/java/org/teavm/flavour/routing/HashRoutingStrategy.java index 8bc75926..d045e5da 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/HashRoutingStrategy.java +++ b/routing/src/main/java/org/teavm/flavour/routing/HashRoutingStrategy.java @@ -25,7 +25,7 @@ /** * Handles routing via URL hashes. */ -class HashRoutingStrategy implements RoutingStrategy { +public class HashRoutingStrategy implements RoutingStrategy { @Override public void notifyListeners() { } @@ -70,4 +70,9 @@ public String makeUri(HTMLElement element, String hash) { public void addListener(Window window, EventListener listener) { window.listenHashChange(listener); } + + @Override + public void open(String path) { + // HashRoutingStrategy doesn't use this method, hrefs are used instead + } } diff --git a/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java b/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java index 262ea4e7..bf5743c3 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java +++ b/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java @@ -29,7 +29,7 @@ /** * Handles routing via URL path changes. */ -class PathRoutingStrategy implements RoutingStrategy { +public class PathRoutingStrategy implements RoutingStrategy { private List listListeners = new ArrayList<>(); @JSBody(params = {"document"}, script = "return document.baseURI;") @@ -60,11 +60,15 @@ public void addListener(RoutingListener listenerNew) { @Override public T open(Window window, Class routeType) { return build(routeType, hash -> { - window.getHistory().pushState(null, null, getBaseUri(window.getDocument()) + Window.encodeURI(trim(hash))); - notifyListeners(); + open(window, getBaseUri(window.getDocument()) + Window.encodeURI(trim(hash))); }); } + public void open(Window window, String path) { + window.getHistory().pushState(null, null, path); + notifyListeners(); + } + @Override public T replace(Window window, Class routeType) { return build(routeType, hash -> { @@ -102,4 +106,9 @@ public String makeUri(HTMLElement element, String path) { public void addListener(Window window, EventListener listener) { listenPopState(window, (EventListener) listener); } + + @Override + public void open(String path) { + open(Window.current(), path); + } } diff --git a/routing/src/main/java/org/teavm/flavour/routing/Routing.java b/routing/src/main/java/org/teavm/flavour/routing/Routing.java index 67355164..467b2e4b 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/Routing.java +++ b/routing/src/main/java/org/teavm/flavour/routing/Routing.java @@ -30,6 +30,10 @@ public static void usePathStrategy() { routingStrategy = new PathRoutingStrategy(); } + public static RoutingStrategy getRoutingStrategy() { + return routingStrategy; + } + public static void addListener(RoutingListener listenerNew) { routingStrategy.addListener(listenerNew); } @@ -74,6 +78,10 @@ public static T open(Class routeType) { return open(Window.current(), routeType); } + public static void open(String path) { + routingStrategy.open(path); + } + static T replace(Window window, Class routeType) { return (T) routingStrategy.replace(window, routeType); } diff --git a/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java b/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java index b63708d7..fd19fe79 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java +++ b/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java @@ -39,4 +39,6 @@ public interface RoutingStrategy { String makeUri(HTMLElement element, String path); void addListener(Window window, EventListener listener); + + public void open(String path); } diff --git a/templates/src/main/java/org/teavm/flavour/components/html/LinkComponent.java b/templates/src/main/java/org/teavm/flavour/components/html/LinkComponent.java index e9be22de..5f3018b8 100644 --- a/templates/src/main/java/org/teavm/flavour/components/html/LinkComponent.java +++ b/templates/src/main/java/org/teavm/flavour/components/html/LinkComponent.java @@ -16,16 +16,19 @@ package org.teavm.flavour.components.html; import java.util.function.Consumer; +import org.teavm.flavour.routing.HashRoutingStrategy; import org.teavm.flavour.routing.Routing; import org.teavm.flavour.templates.BindAttributeComponent; import org.teavm.flavour.templates.BindContent; import org.teavm.flavour.templates.ModifierTarget; import org.teavm.flavour.templates.Renderable; import org.teavm.jso.JSBody; +import org.teavm.jso.dom.events.Event; +import org.teavm.jso.dom.events.EventListener; import org.teavm.jso.dom.html.HTMLElement; @BindAttributeComponent(name = "link", elements = "a") -public class LinkComponent implements Renderable { +public class LinkComponent implements Renderable, EventListener { private HTMLElement element; private String value; private Consumer> path; @@ -36,7 +39,6 @@ public LinkComponent(ModifierTarget target) { private Consumer linkConsumer = str -> { value = Routing.makeUri(element, str); - setHref(element, value); }; @BindContent @@ -46,7 +48,13 @@ public void setPath(Consumer> path) { @Override public void render() { + if (Routing.getRoutingStrategy() instanceof HashRoutingStrategy) { + path.accept(linkConsumer); + setHref(element, value); + } else { path.accept(linkConsumer); + element.addEventListener("click", this); + } } @Override @@ -55,4 +63,9 @@ public void destroy() { @JSBody(params = { "elem", "value" }, script = "elem.href = value;") private static native void setHref(HTMLElement elem, String value); + + @Override + public void handleEvent(Event e) { + Routing.open(value); + } } From acc042f7ca2afd766b3d80c3275ca485b4c285b3 Mon Sep 17 00:00:00 2001 From: ScraM Team Date: Sat, 19 Sep 2020 17:46:29 -0700 Subject: [PATCH 4/5] Added base href auto-configuration for path-based routing --- .../flavour/routing/PathRoutingStrategy.java | 26 +++++++++++++++++-- .../org/teavm/flavour/routing/Routing.java | 4 +++ .../flavour/routing/RoutingStrategy.java | 2 +- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java b/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java index bf5743c3..f5c2b4e7 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java +++ b/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java @@ -17,7 +17,6 @@ import java.util.ArrayList; import java.util.List; -import static org.teavm.flavour.routing.Routing.build; import org.teavm.jso.JSBody; import org.teavm.jso.browser.Location; import org.teavm.jso.browser.Window; @@ -25,6 +24,8 @@ import org.teavm.jso.dom.events.EventListener; import org.teavm.jso.dom.html.HTMLDocument; import org.teavm.jso.dom.html.HTMLElement; +import org.teavm.jso.dom.html.HTMLHeadElement; +import org.teavm.jso.dom.xml.NodeList; /** * Handles routing via URL path changes. @@ -38,6 +39,27 @@ public class PathRoutingStrategy implements RoutingStrategy { @JSBody(params = {"window", "listener"}, script = "window.onpopstate = listener;") native public static void listenPopState(Window window, EventListener listener); + public PathRoutingStrategy() { + setupBaseHref(""); + } + + public PathRoutingStrategy(String rootPath) { + setupBaseHref(rootPath); + } + + public void setupBaseHref(String rootPath) { + final HTMLDocument document = Window.current().getDocument(); + HTMLHeadElement head = document.getHead(); + NodeList baseNodes = head.getElementsByTagName("base"); + if (baseNodes.getLength() == 0) { + HTMLElement base = document.createElement("base"); + head.appendChild(base); + final Location location = Window.current().getLocation(); + base.setAttribute("href", location.getProtocol() + "//" + location.getHost() + "/" + + rootPath + (rootPath.isEmpty() ? "" : "/")); + } + } + private String trim(String path) { if (path.startsWith("/")) { return path.substring(1); @@ -71,7 +93,7 @@ public void open(Window window, String path) { @Override public T replace(Window window, Class routeType) { - return build(routeType, hash -> { + return Routing.build(routeType, hash -> { window.getHistory().replaceState(null, null, getBaseUri(window.getDocument()) + Window.encodeURI(trim(hash))); notifyListeners(); diff --git a/routing/src/main/java/org/teavm/flavour/routing/Routing.java b/routing/src/main/java/org/teavm/flavour/routing/Routing.java index 467b2e4b..c15f7f99 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/Routing.java +++ b/routing/src/main/java/org/teavm/flavour/routing/Routing.java @@ -30,6 +30,10 @@ public static void usePathStrategy() { routingStrategy = new PathRoutingStrategy(); } + public static void usePathStrategy(String rootPath) { + routingStrategy = new PathRoutingStrategy(rootPath); + } + public static RoutingStrategy getRoutingStrategy() { return routingStrategy; } diff --git a/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java b/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java index fd19fe79..b4c3538d 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java +++ b/routing/src/main/java/org/teavm/flavour/routing/RoutingStrategy.java @@ -40,5 +40,5 @@ public interface RoutingStrategy { void addListener(Window window, EventListener listener); - public void open(String path); + void open(String path); } From 7dfd2315580b04a8e45fe7a291d5beb6dc484459 Mon Sep 17 00:00:00 2001 From: ScraM Team Date: Sat, 19 Sep 2020 17:49:07 -0700 Subject: [PATCH 5/5] Fixed typo in path-based routing checkin --- .../java/org/teavm/flavour/routing/PathRoutingStrategy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java b/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java index f5c2b4e7..12fd3ae4 100644 --- a/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java +++ b/routing/src/main/java/org/teavm/flavour/routing/PathRoutingStrategy.java @@ -81,7 +81,7 @@ public void addListener(RoutingListener listenerNew) { @Override public T open(Window window, Class routeType) { - return build(routeType, hash -> { + return Routing.build(routeType, hash -> { open(window, getBaseUri(window.getDocument()) + Window.encodeURI(trim(hash))); }); }