Skip to content

Instantly share code, notes, and snippets.

@virtuald
Last active October 12, 2018 23:58
Show Gist options
  • Save virtuald/ac2fabcc37aeb00ecfe7a94e29035c72 to your computer and use it in GitHub Desktop.
Save virtuald/ac2fabcc37aeb00ecfe7a94e29035c72 to your computer and use it in GitHub Desktop.
Rewrite of riot.js tag router
(function (riot, route) {
'use strict';
//
// Modified version of the riot-router tags
//
// - Intended to be used recursively. You can even use multiple routers in a tag and they'll work
// - Each 'route' event receives the parent container, and any arguments that it would normally get
// - Child tags receive an attribute 'route_path' which contains the current routing
// - Adding a '/@@' to the end of a route allows the route to have subordinate routers
// - Child routers use relative paths, not absolute paths (this makes tags more composable)
//
riot = riot && riot.hasOwnProperty('default') ? riot['default'] : riot;
// customizes the second parser
// - Returns the full path as the first arg
// - Adds @@ and makes it capturing
function SECOND_PARSER(path, filter) {
var f = filter
.replace(/\?/g, '\\?')
.replace(/\*/g, '([^/?#]+?)')
.replace(/\/@@$/, '/(.*)?')
.replace(/\.\./, '.*');
var re = new RegExp(("^" + f + "$"));
var args = path.match(re);
if (args) { return args.slice(0) }
}
route.parser(null, SECOND_PARSER);
riot.tag2('router', '<yield></yield>', '', '', function(opts) {
var this$1 = this;
// This holds our child route tags as an object
this.$ = [];
// If this tag is mounted underneath another router, use that instead of
// creating our own router context
var parent = this.parent;
while (parent) {
if (parent.subroute) {
break;
}
parent = parent.parent;
}
if (parent) {
this.process = function(path) {
this$1.$.some(function(o) {
var args = SECOND_PARSER(path, o.f);
if (typeof args != 'undefined') {
o.a.apply(o, args)
return true;
}
});
}
this.route = function(filter, action) {
this$1.$.push({f: filter, a: action});
}
this.routestop = parent.subroute(this);
} else {
// no parent? create a brand new route context then!
this.route = route.create();
this.routestop = this.route.stop;
this.process = function(){}
this.on('mount', function () {
// To avoid updating route tag before mount, we use setTimeout here
window.setTimeout(function () { return route.start(true); }, 0);
});
}
this.select = function (target) {
[].concat(this$1.tags.route)
.forEach(function (r) { return r.show = (r === target); });
};
this.on('unmount', function () {
this$1.routestop();
this.$ = [];
});
});
riot.tag2('route', '<virtual if="{show}"><yield></yield></virtual>', '', '', function(opts) {
var this$1 = this;
this$1.show = false;
// contains any routers in child tags
this$1.$ = [];
// The last trailing segment we were asked to process
this.trailer = '';
var path = opts.path;
// if ends with /@@, add an additional path to handle the trailer
if (path.endsWith('/@@')) {
this.subroute = function(o) {
this.$.push(o);
return function() {
this.$.splice(this.$.indexOf(o), 1);
};
}
this.parent.route(path, function() {
var args = [], len = arguments.length - 1;
var lastArg = arguments[len];
while ( len-- ) args[ len ] = arguments[ len ];
// The first argument is the current route's path, which doesn't include the trailer
args[0] = args[0].substring(0, args[0].length - (lastArg.length + 1));
this$1.trailer = lastArg;
onroute(args);
});
// register another route without the trailer
path = path.substring(0, path.length - 3);
}
this.parent.route(path, function () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
this$1.trailer = '';
onroute(args);
});
function onroute(args) {
var container = this$1.parent.parent;
var route_path = (container.route_path ? container.route_path + '/' + args[0] : args[0]);
this$1.route_path = route_path;
function doupdate() {
flatten(this$1.tags).forEach(function (tag) {
tag.route_path = route_path;
tag.trigger.apply(tag, [ 'route', container ].concat( args.slice(1) ));
tag.update();
});
// send the trailer to any router children
this$1.$.forEach(function(router) {
router.process(this$1.trailer);
});
}
this$1.parent.select(this$1);
this$1.parent.update();
// If not mounted, there's no way to intercept the child tags while mounting.
// We need to wait the `updated` event to access them via `this.tags`.
if (this$1.isMounted) {
doupdate();
} else {
this$1.one('updated', doupdate);
}
}
function flatten(tags) {
return Object.keys(tags)
.map(function (key) { return tags[key]; })
.reduce(function (acc, tag) { return acc.concat(tag); }, [])
}
this.on('unmount', function() {
this$1.$ = [];
})
});
})(riot, route);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment