Skip to content

Instantly share code, notes, and snippets.

@amasad
Last active December 10, 2015 07:28
Show Gist options
  • Save amasad/4401728 to your computer and use it in GitHub Desktop.
Save amasad/4401728 to your computer and use it in GitHub Desktop.
A quick and dirty yet powerful JS router.
// requires underscore.js for `_.clone`, although come to think about it could be done without it.
// The router module.
var router = (function () {
// Get the location array and filter out empty strings.
var path = window.location.pathname.split('/').filter(Boolean)
// The param arg stack.
, arg_stack = [];
// Main match function. Which is called by all api's to match a certain path.
// with params and regular expression.
var _match = function (path, param, regHash, args) {
var parts = param.split('/')
, passed = true;
// For every part of our path to match the window path.
for (var i = 0, p; p = parts[i]; i++) {
// A part is optional if its starts with a paran.
var isOptional = !!p.match(/^\(.+\)$/)
// The current path to match against.
, cur = path.shift();
// Pull up the actual path to match.
p = p.match(/\(?([^\)]+)\)?/)[1];
// If its a param.
if (cur && p.charAt(0) === ':') {
var argName = p.substring(1);
if (regHash[argName] && !cur.match(regHash[argName])) {
if (isOptional) {
path.unshift(cur);
continue;
} else {
passed = false;
break;
}
}
args[argName] = cur;
continue;
}
// Its not a param then its must match the current path.
if (p !== cur) {
// If path part to match is optional then we unshift the current
// location path we're matching against.
if (isOptional) {
if (cur) path.unshift(cur);
} else {
// fail.
passed = false;
break;
}
}
}
return passed ? path : null;
};
// Wraps every api call of our module and abstracts the common
// argument juggling.
var api = function (call) {
return function (param, regHash, fns) {
if (typeof regHash === 'function' || Array.isArray(regHash)) {
fns = regHash;
regHash = {};
}
var fn;
if (Array.isArray(fns)) {
fn = function () {
var args = arguments;
fns.forEach(function (f) {
f.apply(null, args);
});
};
} else {
fn = fns;
}
return call(param, regHash, fn);
};
};
// Just match part of the location path.
// Takes care of creating a scope (context for other calls).
var scope = function (param, regHash, fn) {
var args = arg_stack[arg_stack.length - 1] || {};
arg_stack.push(args);
var passed = _match(_.clone(path), param, regHash, args)
, _path = path;
if (passed) {
path = passed;
fn();
}
// Back up after we finished executing our context function.
path = _path;
arg_stack.pop();
};
// This is a terminal match. Check that all path parts is succesfully
// matched.
var match = function (param, regHash, fn) {
var args = _.clone(arg_stack[arg_stack.length - 1])
, passed = _match(_.clone(path), param, regHash, args);
if (passed && !passed.length) {
fn(args);
}
};
// Just like the scope function but don't have to do post exec
// stuff.
var part = function (param, regHash, fn) {
var args = _.clone(arg_stack[arg_stack.length - 1])
, passed = _match(_.clone(path), param, regHash, args);
if (passed) {
fn(args);
}
};
return {
scope: api(scope)
, match: api(match)
, part: api(part)
};
})();
// path: '/en/sections/123abc/1'
var scope = router.scope, match = router.match, part = router.part;
scope('(:locale)', {locale: /^[a-z]{2}$/g}, function (args) {
scope('sections', function () {
part(':section_id', function (args) {
console.log('init something awesome to do with section %s', args.section_id);
});
scope(':section_id', function () {
match(':section_no', {section_no: /[0-9]+/}, function (args) {
console.log('some action to do with section #%d', args.section_no);
});
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment