Skip to content

Instantly share code, notes, and snippets.

@richard-to
Last active August 29, 2015 14:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save richard-to/89b34a18c7e74f8ecc2d to your computer and use it in GitHub Desktop.
Save richard-to/89b34a18c7e74f8ecc2d to your computer and use it in GitHub Desktop.
Simple HTML5 history router. No dependencies. No fallback if HTML5 history is not supported. Just ignored. Work in progress. Untested.
(function() {
// Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
var _delim = '/';
var _regexDelim = "\\/";
var _defaultPlaceholder = '(\\w+)';
var _tokenPrefix = ':';
var _toRegexRoute = function(route) {
var modifiedRouteParts = [];
var routeParts = route.split(_delim);
for (var i = 0; i < routeParts.length; ++i) {
if (routeParts[i][0] === _tokenPrefix) {
modifiedRouteParts.push(_defaultPlaceholder);
} else if (routeParts[i] !== '') {
modifiedRouteParts.push(routeParts[i]);
}
}
return "^" + _regexDelim + modifiedRouteParts.join(_regexDelim) + "/?$";
};
var _urlFor = function(route, params) {
var routeParts = route.split(_delim);
var concreteRoute = [];
var param_index = 0;
for (var i = 0; i < routeParts.length; ++i) {
if (routeParts[i][0] === _tokenPrefix) {
concreteRoute.push(params[param_index++]);
} else if (routeParts[i] !== '') {
concreteRoute.push(routeParts[i]);
}
}
return _delim + concreteRoute.join(_delim) + _delim;
};
var _buildRouteName = function(route) {
var routeParts = route.split(_delim);
var route = '';
for (var i = 0; i < routeParts.length; ++i) {
if (routeParts[i] !== '' && routeParts[i][0] !== _tokenPrefix) {
route += '_' + routeParts[i];
}
}
return route.substring(1);
};
var Kingpin = function(routes) {
this.baseURL = location.protocol + "//" + location.hostname;
if (location.port !== 80 && location.port !== 443) {
this.baseURL += ":" + location.port;
}
this.routes = [];
this.regexRoutes = {};
this.actions = {};
this.go = {};
this.urlFor = {};
this.route = {};
if (routes) {
if (Array.isArray(routes)) {
for (var i = 0; i < routes.length; ++i) {
this.on(routes[i][0], routes[i][1], routes[i][2]);
}
} else {
this.on(routes[0], routes[1], routes[2]);
}
}
};
Kingpin.prototype._onPopState = function(e) {
this.setRoute(location.pathname);
};
Kingpin.prototype._onLinkClick = function(e) {
var node = e.target;
while (node.tagName !== 'A' && node.parentNode) {
node = node.parentNode;
}
if (node && node.tagName === 'A' && node.href.indexOf(this.baseURL) === 0) {
if (this.setRoute(node.href.substring(this.baseURL.length))) {
e.preventDefault();
}
}
};
Kingpin.prototype.on = function(route, action, scope) {
var self = this;
var routeName = _buildRouteName(route);
if (this.route[routeName]) {
return;
}
this.routes.push(routeName);
this.route[routeName.toUpperCase()] = route;
this.urlFor[routeName] = function() {
return _urlFor(route, arguments);
};
this.actions[routeName] = action;
this.go[routeName] = function() {
action.apply(scope, arguments);
history.pushState(null, null, self.urlFor[routeName].apply(self, arguments));
};
this.actions[routeName] = action;
this.regexRoutes[routeName] = new RegExp(_toRegexRoute(route));
return this;
};
Kingpin.prototype.setRoute = function(pathname) {
var numRoutes = this.routes.length;
for (var i = 0; i < numRoutes; ++i) {
var routeName = this.routes[i];
var result = pathname.match(this.regexRoutes[routeName]);
if (result) {
result.shift();
this.go[routeName].apply(this, result);
return true;
}
}
return false;
};
Kingpin.prototype.startListening = function() {
if (document.body.addEventListener) {
document.body.addEventListener('click', this._onLinkClick.bind(this));
}
if (history.pushState) {
window.addEventListener("popstate", this._onPopState.bind(this));
}
return this;
};
Kingpin.prototype.stopListening = function() {
if (document.body.addEventListener) {
document.body.removeEventListener('click', this._onLinkClick.bind(this));
}
if (history.pushState) {
window.removeEventListener("popstate", this._onPopState.bind(this));
}
return this;
};
window.Kingpin = Kingpin;
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment