Skip to content

Commit 0836cdd

Browse files
committed
Added hashRouter class with dynamic route matching and navigation
1 parent 192bca8 commit 0836cdd

2 files changed

Lines changed: 101 additions & 2 deletions

File tree

src/routing/hash-router.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { makeRouteMatcher } from './route-matchers'
2+
3+
export class HashRouter {
4+
#matchers = []
5+
#onPopState = () => this.#matchCurrentRoute()
6+
#isInitialized = false
7+
8+
#matchedRoute = null
9+
get matchedRoute() {
10+
return this.#matchedRoute
11+
}
12+
13+
#params = {}
14+
get params() {
15+
return this.#params
16+
}
17+
18+
#query = {}
19+
get query() {
20+
return this.#query
21+
}
22+
23+
get #currentRouteHash() {
24+
const hash = document.location.hash
25+
26+
if (hash === '') {
27+
return '/'
28+
}
29+
30+
return hash.slice(1)
31+
}
32+
33+
constructor(routes = []) {
34+
this.#matchers = routes.map(makeRouteMatcher)
35+
}
36+
37+
async init() {
38+
if (this.#isInitialized) {
39+
return
40+
}
41+
42+
if (document.location.hash === '') {
43+
window.history.replaceState({}, '', '#/')
44+
}
45+
46+
window.addEventListener('popstate', this.#onPopState)
47+
await this.#matchCurrentRoute()
48+
49+
this.#isInitialized = true
50+
}
51+
52+
destroy() {
53+
if (!this.#isInitialized) {
54+
return
55+
}
56+
57+
window.removeEventListener('popstate', this.#onPopState)
58+
this.#isInitialized = false
59+
}
60+
61+
#matchCurrentRoute() {
62+
return this.navigateTo(this.#currentRouteHash)
63+
}
64+
65+
async navigateTo(path) {
66+
const matcher = this.#matchers.find((matcher) =>
67+
matcher.checkMatch(path)
68+
)
69+
70+
if (matcher == null) {
71+
console.warn(`[Router] No route matches path "${path}"`)
72+
73+
this.#matchedRoute = null
74+
this.#params = {}
75+
this.#query = {}
76+
77+
return
78+
}
79+
80+
if (matcher.isRedirect) {
81+
return this.navigateTo(matcher.route.redirect)
82+
}
83+
84+
this.#matchedRoute = matcher.route
85+
this.#params = matcher.extractParams(path)
86+
this.#query = matcher.extractQuery(path)
87+
this.#pushState(path)
88+
}
89+
90+
#pushState(path) {
91+
window.history.pushState({}, '', `#${path}`)
92+
}
93+
94+
back() {
95+
window.history.back()
96+
}
97+
98+
forward() {
99+
window.history.forward()
100+
}
101+
}

src/routing/route-matchers.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ function extractQuery(path) {
6565
// matcher.extractQuery('/home') // {}
6666
// matcher.extractQuery('/home?tab=profile') // { tab: 'profile' }
6767

68-
69-
7068
// /user/:id/orders/:orderId
7169
// \^/user/(?<id>)/orders/(?<orderId>[^/])$
7270
function makeRouteWithParamsRegex({ path }) {

0 commit comments

Comments
 (0)