· 6 years ago · Jul 03, 2019, 11:32 PM
1<!DOCTYPE html>
2<html>
3<head>
4 <meta charset="utf-8">
5 <title>Building a router</title>
6 <script>
7 // Put John's template engine code here...
8
9 (function () {
10 // A hash to store our routes:
11 var routes = {};
12 // An array of the current route's events:
13 var events = [];
14 // The element where the routes are rendered:
15 var el = null;
16 // Context functions shared between all controllers:
17 var ctx = {
18 on: function (selector, evt, handler) {
19 events.push([selector, evt, handler]);
20 },
21 refresh: function (listeners) {
22 listeners.forEach(function (fn) { fn(); });
23 }
24 };
25 // Defines a route:
26 function route (path, templateId, controller) {
27 if (typeof templateId === 'function') {
28 controller = templateId;
29 templateId = null;
30 }
31 var listeners = [];
32 Object.defineProperty(controller.prototype, '$on', {value: ctx.on});
33 Object.defineProperty(controller.prototype, '$refresh', {value: ctx.refresh.bind(undefined, listeners)});
34 routes[path] = {templateId: templateId, controller: controller, onRefresh: listeners.push.bind(listeners)};
35 }
36 function forEachEventElement(fnName) {
37 for (var i = 0, len = events.length; i < len; i++) {
38 var els = el.querySelectorAll(events[i][0]);
39 for (var j = 0, elsLen = els.length; j < elsLen; j++) {
40 els[j][fnName].apply(els[j], events[i].slice(1));
41 }
42 }
43 }
44 function addEventListeners() {
45 forEachEventElement('addEventListener');
46 }
47 function removeEventListeners() {
48 forEachEventElement('removeEventListener');
49 }
50 function router () {
51 // Lazy load view element:
52 el = el || document.getElementById('view');
53 // Remove current event listeners:
54 removeEventListeners();
55 // Clear events, to prepare for next render:
56 events = [];
57 // Current route url (getting rid of '#' in hash as well):
58 var url = location.hash.slice(1) || '/';
59 // Get route by url or fallback if it does not exist:
60 var route = routes[url] || routes['*'];
61 // Do we have a controller:
62 if (route && route.controller) {
63 var ctrl = new route.controller();
64 if (!el || !route.templateId) {
65 // If there's nothing to render, abort:
66 return;
67 }
68 // Listen on route refreshes:
69 route.onRefresh(function () {
70 removeEventListeners();
71 // Render route template with John Resig's template engine:
72 el.innerHTML = tmpl(route.templateId, ctrl);
73 addEventListeners();
74 });
75 // Trigger the first refresh:
76 ctrl.$refresh();
77 }
78 }
79 // Listen on hash change:
80 this.addEventListener('hashchange', router);
81 // Listen on page load:
82 this.addEventListener('load', router);
83 // Expose the route register function:
84 this.route = route;
85 })();
86 </script>
87 <script type="text/html" id="home">
88 <h1>Router FTW!</h1>
89 </script>
90 <script type="text/html" id="template1">
91 <h1>Page 1: <%= greeting %></h1>
92 <p><%= moreText %></p>
93 <button class="my-button">Click me <%= counter %></button>
94 </script>
95 <script type="text/html" id="template2">
96 <h1>Page 2: <%= heading %></h1>
97 <p>Lorem ipsum...</p>
98 </script>
99 <script type="text/html" id="error404">
100 <h1>404 Not found</h1>
101 </script>
102</head>
103<body>
104 <ul>
105 <li><a href="#">Home</a></li>
106 <li><a href="#/page1">Page 1</a></li>
107 <li><a href="#/page2">Page 2</a></li>
108 </ul>
109 <div id="view"></div>
110 <script>
111 route('/', 'home', function () {});
112 route('/page1', 'template1', function () {
113 this.greeting = 'Hello world!';
114 this.moreText = 'Bacon ipsum...';
115 this.counter = 0;
116 this.$on('.my-button', 'click', function () {
117 this.counter += 1;
118 this.$refresh();
119 }.bind(this));
120 });
121 route('/page2', 'template2', function () {
122 this.heading = 'I\'m page two!';
123 });
124 route('*', 'error404', function () {});
125 </script>
126</body>
127</html>