From fbcb4e5f462416f19a94a9b3943a51ae0c99d914 Mon Sep 17 00:00:00 2001 From: Matt Mo Date: Thu, 10 Sep 2020 22:45:28 -0700 Subject: [PATCH] Added route matching and initial working functionality for the router link and router view. Next up is getting afterRender working correctly for Ignite so that the correct router view can be displayed once the page is loaded. --- ignite-router.js | 160 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 146 insertions(+), 14 deletions(-) diff --git a/ignite-router.js b/ignite-router.js index ef10813..c068215 100644 --- a/ignite-router.js +++ b/ignite-router.js @@ -4,11 +4,18 @@ import { IgniteTemplate, slot, div, html } from "../ignite-html/ignite-template. class RouterLink extends IgniteElement { constructor() { super(); + + this.pushStateListener = () => this.update(); + this.popStateListener = () => this.update(); + + window.addEventListener("popstate", this.popStateListener); + window.addEventListener("pushstate", this.pushStateListener); } get properties() { return { - active: false + active: false, + route: null }; } @@ -21,8 +28,25 @@ class RouterLink extends IgniteElement { ); } + update() { + var routeMatches = RouteMatcher.matches(this.route); + + if (routeMatches && !this.active) { + this.active = true; + } else if (!routeMatches && this.active) { + this.active = false; + } + } + onClick(event) { - console.log("Router link was clicked, event:", event); + event.preventDefault(); + window.history.pushState(this.route, this.route, this.route); + window.dispatchEvent(new Event("pushstate")); + } + + cleanup() { + window.removeEventListener("popstate", this.popStateListener); + window.removeEventListener("pushstate", this.pushStateListener); } } @@ -30,33 +54,141 @@ class RouterView extends IgniteElement { constructor() { super(); - console.log("Added pop & push state events"); - window.addEventListener("popstate", (event) => this.popState(event)); - window.addEventListener("pushstate", (event) => this.pushState(event)); + this.pushStateListener = () => this.update(); + this.popStateListener = () => this.update(); + + window.addEventListener("popstate", this.popStateListener); + window.addEventListener("pushstate", this.pushStateListener); + } + + get properties() { + return { + show: false, + route: null + }; } render() { - return this.template; + return this.template.child( + new slot(this) + ).style("display", this.show, null, (value) => { return value ? null : "none"; }); } - pushState(event) { - console.log("Window pushState:", event); + update() { + var routeMatches = RouteMatcher.matches(this.route); + + if (routeMatches && !this.show) { + this.show = true; + } else if (!routeMatches && this.show) { + this.show = false; + } } - popState(event) { - console.log("Window popState:", event); + cleanup() { + window.removeEventListener("popstate", this.popStateListener); + window.removeEventListener("pushstate", this.pushStateListener); } } +class RouteMatcher { + static matches(route) { + //Get the path parts from the window + var pathParts = window.location.pathname.split("/").splice(1); + + //Get the route parts + var fromRoot = (route.trim().startsWith("/")); + var routeParts = (fromRoot ? route.trim().split("/").splice(1) : route.trim().split("/")); + + //Check to see if we have a trailing route part, if so, remove it. + if (pathParts.length > 0 && pathParts[pathParts.length - 1] == "") { + pathParts.pop(); + } + + if (routeParts.length > 0 && routeParts[routeParts.length - 1] == "") { + routeParts.pop(); + } + + //If path parts is 0 and route parts is 0 we have a match. + if (pathParts.length == 0 && routeParts.length == 0) { + return true; + } + //If path parts is 0, and the route part is ** then this is a match. + else if (pathParts.length == 0 && routeParts.length == 1 && (routeParts[0] == "**" || routeParts[0] == "*")) { + return true; + } + //If path parts is 0 and the route starts with ! and is a length of 1 then this is a match. + else if (pathParts.length == 0 && routeParts.length == 1 && routeParts[0].startsWith("!")) { + return true; + } + //If the path parts is 0 and the route is !*/** then this is a match + else if (pathParts.length == 0 && routeParts.length == 2 && routeParts[0].startsWith("!") && routeParts[1] == "**") { + return true; + } + + //Check the route parts against the path parts. + var max = Math.min(pathParts.length, routeParts.length); + if (fromRoot) { + for (var i = 0; i < max; i++) { + if (routeParts[i].startsWith("!") && pathParts[i] == routeParts[i].substring(1)) { + return false; + } else if (routeParts[i] == "**") { + return true; + } else if (routeParts[i] != pathParts[i] && routeParts[i] != "*" && !routeParts[i].startsWith("!")) { + return false; + } else if (i + 2 == routeParts.length && i + 1 == pathParts.length && routeParts[i + 1] == "**") { + return true; + } + } + + if (routeParts.length > pathParts.length) { + return false; + } else if (pathParts.length > routeParts.length && routeParts[routeParts.length - 1] != "**") { + return false; + } else { + return true; + } + } else { + for (var offset = 0; offset < pathParts.length; offset++) { + for (var i = 0; i < max; i++) { + if (i + offset >= pathParts.length) { + return false; + } + + if (routeParts[i].startsWith("!") && pathParts[i + offset] == routeParts[i].substring(1)) { + break; + } else if (routeParts[i] == "**") { + return true; + } + else if (routeParts[i] != pathParts[i + offset] && routeParts[i] != "*" && !routeParts[i].startsWith("!")) { + break; + } else if (i + 1 == routeParts.length && offset + routeParts.length == pathParts.length) { + return true; + } else if (i + 2 == routeParts.length && offset + i + 1 == pathParts.length && routeParts[i + 1] == "**") { + return true; + } + } + } + + return false; + } + } +} + +window.RouteMatcher = RouteMatcher; + class RouterLinkTemplate extends IgniteTemplate { - constructor(...children) { - super("router-link", children); + constructor(route, element) { + super("router-link", [element]); + + this.property("route", route); } } class RouterViewTemplate extends IgniteTemplate { - constructor(...children) { - super("router-view", children); + constructor(route, element) { + super("router-view", [element]); + + this.property("route", route); } }