Skip to content

Instantly share code, notes, and snippets.

@joakimbeng
Last active March 15, 2024 06:18
Show Gist options
  • Save joakimbeng/7918297 to your computer and use it in GitHub Desktop.
Save joakimbeng/7918297 to your computer and use it in GitHub Desktop.
A really simple Javascript router
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Building a router</title>
<script>
// Put John's template engine code here...
(function () {
// A hash to store our routes:
var routes = {};
// An array of the current route's events:
var events = [];
// The element where the routes are rendered:
var el = null;
// Context functions shared between all controllers:
var ctx = {
on: function (selector, evt, handler) {
events.push([selector, evt, handler]);
},
refresh: function (listeners) {
listeners.forEach(function (fn) { fn(); });
}
};
// Defines a route:
function route (path, templateId, controller) {
if (typeof templateId === 'function') {
controller = templateId;
templateId = null;
}
var listeners = [];
Object.defineProperty(controller.prototype, '$on', {value: ctx.on});
Object.defineProperty(controller.prototype, '$refresh', {value: ctx.refresh.bind(undefined, listeners)});
routes[path] = {templateId: templateId, controller: controller, onRefresh: listeners.push.bind(listeners)};
}
function forEachEventElement(fnName) {
for (var i = 0, len = events.length; i < len; i++) {
var els = el.querySelectorAll(events[i][0]);
for (var j = 0, elsLen = els.length; j < elsLen; j++) {
els[j][fnName].apply(els[j], events[i].slice(1));
}
}
}
function addEventListeners() {
forEachEventElement('addEventListener');
}
function removeEventListeners() {
forEachEventElement('removeEventListener');
}
function router () {
// Lazy load view element:
el = el || document.getElementById('view');
// Remove current event listeners:
removeEventListeners();
// Clear events, to prepare for next render:
events = [];
// Current route url (getting rid of '#' in hash as well):
var url = location.hash.slice(1) || '/';
// Get route by url or fallback if it does not exist:
var route = routes[url] || routes['*'];
// Do we have a controller:
if (route && route.controller) {
var ctrl = new route.controller();
if (!el || !route.templateId) {
// If there's nothing to render, abort:
return;
}
// Listen on route refreshes:
route.onRefresh(function () {
removeEventListeners();
// Render route template with John Resig's template engine:
el.innerHTML = tmpl(route.templateId, ctrl);
addEventListeners();
});
// Trigger the first refresh:
ctrl.$refresh();
}
}
// Listen on hash change:
this.addEventListener('hashchange', router);
// Listen on page load:
this.addEventListener('load', router);
// Expose the route register function:
this.route = route;
})();
</script>
<script type="text/html" id="home">
<h1>Router FTW!</h1>
</script>
<script type="text/html" id="template1">
<h1>Page 1: <%= greeting %></h1>
<p><%= moreText %></p>
<button class="my-button">Click me <%= counter %></button>
</script>
<script type="text/html" id="template2">
<h1>Page 2: <%= heading %></h1>
<p>Lorem ipsum...</p>
</script>
<script type="text/html" id="error404">
<h1>404 Not found</h1>
</script>
</head>
<body>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#/page1">Page 1</a></li>
<li><a href="#/page2">Page 2</a></li>
</ul>
<div id="view"></div>
<script>
route('/', 'home', function () {});
route('/page1', 'template1', function () {
this.greeting = 'Hello world!';
this.moreText = 'Bacon ipsum...';
this.counter = 0;
this.$on('.my-button', 'click', function () {
this.counter += 1;
this.$refresh();
}.bind(this));
});
route('/page2', 'template2', function () {
this.heading = 'I\'m page two!';
});
route('*', 'error404', function () {});
</script>
</body>
</html>
@rlynjb
Copy link

rlynjb commented Mar 1, 2016

hello. i was wondering if there is a way to reload a current template and its controller?

@joakimbeng
Copy link
Author

@rlynjb I've updated the gist to include event handling and refreshing of routes. I've removed the Object.observe as well because it's already deprecated.

You register event listeners with the $on function in a controller, and you can trigger a refresh/rerender with the $refresh function. See the /page1 route in the example code above.

I still don't consider this a production ready solution though. For a more complete but still small router library have a look at: page

@dannycallaghan
Copy link

@Shyam-Chen

Put John's template engine code where it says:

// Put John's template engine code here...

The link to the code is in the first comment.

@vitiral
Copy link

vitiral commented Apr 6, 2018

@ganesh-vellanki
Copy link

Thank you, its really good.

@naxo25
Copy link

naxo25 commented Feb 27, 2021

You can put this code to show you a 404 message when it can't find the route

try {
  if (el && route.controller) {
      el.innerHTML = tmpl(route.templateId, new route.controller());
  }
} catch(e) {
  console.error('ruta no defined')
  el.innerHTML = ` <p style='
                      display: flex;
                      position: fixed;
                      justify-content: center;
                      align-items: center;
                      width: 100%;
                      height: 100%;
                      top: 0;
                      left: 0;
                      background: white;
                      z-index: 10000;
                      font-weight: bold;
                      color: red;
                      margin: -10px 20px;'>
                        404 page not Found, <a href="#"> go / </a>
                  </p>`
}

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment