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); } }