Skip to content

Commit 7f22ab3

Browse files
committed
28
1 parent 405a4dd commit 7f22ab3

File tree

9 files changed

+626
-0
lines changed

9 files changed

+626
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# vanilla-client-side-routing-examples
2+
3+
Created with CodeSandbox
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* eslint-disable */
2+
3+
const render = () => {
4+
const route = location.hash.replace("#", "") || "/";
5+
document.getElementById("root").innerHTML = `<h2>"${route}" page</h2>`;
6+
};
7+
8+
// 1. Handle initial page load
9+
window.addEventListener("load", () => {
10+
render(); // 👈
11+
});
12+
13+
// 2. Handle hash changes
14+
window.addEventListener("hashchange", () => {
15+
render(); // 👈
16+
});
17+
18+
// 3. Catch <a> tag clicks
19+
document.body.addEventListener("click", (event) => {
20+
if (!event.target.matches("a")) {
21+
return;
22+
}
23+
event.preventDefault();
24+
const url = event.target.getAttribute("href");
25+
location.hash = url; // doesn't reload page
26+
// location.href = url; // reloads page
27+
// location.replace(url); // reloads page
28+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* eslint-disable */
2+
3+
const render = () => {
4+
const route = location.pathname;
5+
document.getElementById("root").innerHTML = `<h2>"${route}" page</h2>`;
6+
};
7+
8+
// 1. Handle initial page load
9+
window.addEventListener("load", () => {
10+
render(); // 👈
11+
});
12+
13+
// 2. Handle history navigations. alternative "window.onpopstate"
14+
window.addEventListener("popstate", (event) => {
15+
render();
16+
});
17+
18+
// 3. Catch <a> tag clicks + trigger change handler
19+
document.body.addEventListener("click", (event) => {
20+
if (!event.target.matches("a")) {
21+
return;
22+
}
23+
event.preventDefault();
24+
let url = event.target.getAttribute("href");
25+
history.pushState({ foo: "bar" }, document.title, url);
26+
// history.replaceState({ foo: "bar" }, url, url);
27+
render(); // 👈
28+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* eslint-disable */
2+
3+
/**
4+
* TODO: modify router.js to support
5+
* 1. unsubscribe function.
6+
* Hint: inside Router.go function return unsubscribe function,
7+
* which will remove listener by id
8+
* 2. onLeave callback
9+
* Hint: Add 3rd 'onLeave' parameter to Router.on + save in listener object
10+
* Check in Router.handleListener if previousPath matches listener
11+
*/
12+
13+
const render = (content) =>
14+
(document.getElementById("root").innerHTML = `<h2>${content}</h2>`);
15+
16+
const createLogger =
17+
(content, shouldRender = true) =>
18+
(...args) => {
19+
console.log(`LOGGER: ${content} args=${JSON.stringify(args)}`);
20+
if (shouldRender) {
21+
render(content);
22+
}
23+
};
24+
25+
const router = Router();
26+
27+
const unsubscribe = router.on(/.*/, createLogger("/.*"));
28+
router.on(
29+
(path) => path === "/contacts",
30+
createLogger("/contacts"), // onEnter
31+
createLogger("[leaving] /contacts", false) // onLeave
32+
);
33+
router.on("/about", createLogger("/about"));
34+
router.on("/about/us", createLogger("/about/us"));
35+
36+
document.body.addEventListener("click", (event) => {
37+
if (!event.target.matches("a")) {
38+
return;
39+
}
40+
event.preventDefault();
41+
let url = event.target.getAttribute("href");
42+
router.go(url);
43+
unsubscribe();
44+
});
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/* eslint-disable */
2+
3+
// IMPLEMENTATION
4+
function Router() {
5+
let listeners = [];
6+
let currentPath = location.pathname;
7+
let previousPath = null;
8+
9+
const isMatch = (match, path) =>
10+
(match instanceof RegExp && match.test(path)) ||
11+
(typeof match === "function" && match(path)) ||
12+
(typeof match === "string" && match === path);
13+
14+
const handleListener = ({ match, onEnter, onLeave }) => {
15+
const args = { currentPath, previousPath, state: history.state };
16+
17+
isMatch(match, currentPath) && onEnter(args);
18+
onLeave && isMatch(match, previousPath) && onLeave(args);
19+
};
20+
21+
const handleAllListeners = () => listeners.forEach(handleListener);
22+
23+
const generateId = () => {
24+
const getRandomNumber = () =>
25+
Math.floor(Math.random() * listeners.length * 1000);
26+
const doesExist = (id) => listeners.find((listener) => listener.id === id);
27+
28+
let id = getRandomNumber();
29+
while (doesExist(id)) {
30+
id = getRandomNumber();
31+
}
32+
return id;
33+
};
34+
35+
const on = (match, onEnter, onLeave) => {
36+
const id = generateId();
37+
38+
const listener = { id, match, onEnter, onLeave };
39+
listeners.push(listener);
40+
handleListener(listener);
41+
42+
return () => {
43+
listeners = listeners.filter((listeners) => listeners.id !== id);
44+
};
45+
};
46+
47+
const go = (url, state) => {
48+
previousPath = currentPath;
49+
history.pushState(state, url, url);
50+
currentPath = location.pathname;
51+
52+
handleAllListeners();
53+
};
54+
55+
window.addEventListener("popstate", handleAllListeners);
56+
57+
return { on, go };
58+
}
59+
60+
// USAGE
61+
const render = (content) =>
62+
(document.getElementById("root").innerHTML = `<h2>${content}</h2>`);
63+
64+
const createLogger =
65+
(content, shouldRender = true) =>
66+
(...args) => {
67+
console.log(`LOGGER: ${content} args=${JSON.stringify(args)}`);
68+
if (shouldRender) {
69+
render(content);
70+
}
71+
};
72+
73+
const router = Router();
74+
75+
const unsubscribe = router.on(/.*/, createLogger("/.*"));
76+
router.on(
77+
(path) => path === "/contacts",
78+
createLogger("/contacts"), // onEnter
79+
createLogger("[leaving] /contacts", false) // onLeave
80+
);
81+
router.on("/about", createLogger("/about"));
82+
router.on("/about/us", createLogger("/about/us"));
83+
84+
document.body.addEventListener("click", (event) => {
85+
if (!event.target.matches("a")) {
86+
return;
87+
}
88+
event.preventDefault();
89+
let url = event.target.getAttribute("href");
90+
router.go(url);
91+
unsubscribe();
92+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
7+
<title>Client-Side URL change examples</title>
8+
</head>
9+
<body>
10+
<header>
11+
<h1>Client-side URL change examples</h1>
12+
<nav style="display: flex; justify-content: space-around">
13+
<a href="/">Home</a>
14+
<a href="/contacts">Contacts</a>
15+
<a href="/about">About</a>
16+
<a href="/about/us">About / Us</a>
17+
</nav>
18+
</header>
19+
<article id="root"></article>
20+
21+
<!-- <script src="/examples/hash-api.js"></script> -->
22+
<!-- <script src="/examples/history-api.js"></script> -->
23+
<script src="/examples/router.js"></script>
24+
<!-- <script src="/examples/practice.js"></script> -->
25+
<script>
26+
/* Debug */
27+
window.addEventListener("load", () =>
28+
console.log(`PAGE FULLY RELOADED ${Date.now()}`)
29+
);
30+
</script>
31+
</body>
32+
</html>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "vanilla-client-side-routing-examples",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.html",
6+
"scripts": {
7+
"start": "serve",
8+
"build": "echo This is a static template, there is no bundler or bundling involved!"
9+
},
10+
"repository": {
11+
"type": "git",
12+
"url": "git+https://github.com/codesandbox-app/static-template.git"
13+
},
14+
"keywords": [],
15+
"author": "Ives van Hoorne",
16+
"license": "MIT",
17+
"bugs": {
18+
"url": "https://github.com/codesandbox-app/static-template/issues"
19+
},
20+
"homepage": "https://github.com/codesandbox-app/static-template#readme",
21+
"devDependencies": {
22+
"serve": "^11.2.0"
23+
}
24+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"template": "static"
3+
}

0 commit comments

Comments
 (0)