Created
July 10, 2013 20:53
-
-
Save briancavalier/5970232 to your computer and use it in GitHub Desktop.
A recent wire router plugin sample. Totally untested :) Uses new plugin format (>= 0.10). Publishes one new facet, "routes", to the DSL.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define({ | |
myComponent: { | |
create: { /*...*/ }, | |
routes: { | |
'<regex as string>': '<any usual wire pipeline>', | |
'/people/\\d+': 'handlePeople' | |
} | |
}, | |
$plugins: ['wire/routing'] | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** @license MIT License (c) copyright 2010-2013 original author or authors */ | |
/** | |
* Licensed under the MIT License at: | |
* http://www.opensource.org/licenses/mit-license.php | |
* | |
* @author: Brian Cavalier | |
* @author: John Hann | |
*/ | |
(function(define) { 'use strict'; | |
define(function(require) { | |
var pipeline, when; | |
pipeline = require('wire/lib/pipeline'); | |
when = require('when'); | |
return function(/* options */) { | |
var routeMap = []; | |
return { | |
context: { | |
ready: function(resolver) { | |
processRoutes(getUrl(window)); | |
window.addEventListener('hashchange', handleHashChange); | |
resolver.resolve(); | |
}, | |
destroy: function(resolver) { | |
window.removeEventListener('hashchange', handleHashChange); | |
routeMap = null; | |
resolver.resolve(); | |
} | |
}, | |
facets: { | |
route: { | |
connect: routeFacet | |
} | |
} | |
}; | |
function handleHashChange(e) { | |
processRoutes(getUrl(e.target)); | |
} | |
function processRoutes(url) { | |
routeMap.forEach(function(route) { | |
if(route.matcher.test(url)) { | |
route.handler(url); | |
} | |
}); | |
} | |
function routeFacet(resolver, proxy, wire) { | |
var promises, options; | |
options = proxy.options; | |
promises = when.map(Object.keys(options), function(route) { | |
return parseRoute(route, options[route], proxy, wire); | |
}).then(function(routes) { | |
routeMap = routeMap.concat(routes); | |
}); | |
resolver.resolve(promises); | |
} | |
} | |
function getUrl(target) { | |
return target.location.hash.slice(1); | |
} | |
function parseRoute(rx, handlerSpec, proxy, wire) { | |
return pipeline(proxy, handlerSpec, wire).then(function(h) { | |
return { | |
matcher: new RegExp(rx), | |
handler: h | |
}; | |
}); | |
} | |
}); | |
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ok here's some thoughts:
Routing/History protocol
Basic routing format
Basic routing mechanism: A "routes" facet you can add to any object. E.g:
Accepted routes
Routes follow the following patterns (this deviates a bit from the regexp approach):
'object/edit'
'object/:id'
. Multiple such parameters allowed'object/*rest'
'*'
will match all routes.Question 1: Are there other conditions that would make sense? For example specifying that a match needs to be an integer. Perhaps allow semi-arbitrary regexs with a format like:
':id:regex'
. Or perhaps better to keep it simple.Question 1': Perhaps add a way for optional parameters, and a way to match query strings? Or perhaps matching query strings should be automatic?
Question 2: Should these obey a "first to match activates" rule, or should we let all that match run? If the former, what guarantees do we have on different javascript environments that the order in which the routes are specified is the same as the order in which they will be accessed through a "for in" loop? If the latter, do we mind the fact that there is no easy way to specify a "default" action, only to be ran if others don't match? And what sense would "default action" make, if developers are allowed to attach routes facets on different components, and have different routes rules on each? Would we be talking about "one rule per component"? If not, how do we specify priority amongst components? If we go with "one rule per component", perhaps we can allow a
'$default'
route option, to run only if no other routes in that component matched.Question 3: How much do we care about trying to offer a behavior that exactly matches what popular packages that offer routing currently do?
Question 4: Should a route be activated the first time a page loads, if it matches the current URI? Or only on subsequent changes to the URI? Should this be a configurable option?
Question 5: What support should we offer for programmatically adjusting the routes?
Method signatures
Question 6: How should the methods handling routes receive the matched route information? Some possibilities:
$route
in it designates the full route.Option 4 is probably what I would lean towards.
Question 7: Regardless, it is worth thinking as to whether extra optional arguments should be allowed, one matching the "previous/current route", a "from" field so to speak, and possibly another matching the state one could have stored via pushState, and gotten back via popState. But more on that later.
History.pushState
Opt-in or Opt-out
Question 8: Should using the HTML5 History API be opt-in or opt-out? This should likely be a configuration option on the plugin, something akin to:
where
legacy: false
means that we do NOT try to use the new stuff if it is available. Or perhapsforceHash: true
instead?To me it makes sense to try to use the new stuff if it is available. An issue with that might be someone using a "new url", without hashes, that they got from a new browser, on an older browser. The new stuff sort of also implies your server is in a position to support direct requests to the corresponding URIs. Perhaps then it should be an opt-in thing, i.e.
forceHash
defaults totrue
(orlegacy
defaults tofalse
). Kinda torn on this. I guess it depends on who we expect would be using this.One possibility: Keep the history stuff as a separate plugin. Let
'wire/route'
do its thing as usual, using hashes. But if'wire/history'
is also loaded, it tries to use the HTML5 API. I rather think a single module makes more sense.Here's how I envision the history plugin might act:
www.foo.com/bar#baz
would turn intowww.foo.com/baz
(if there is no anchor named 'baz' that is).route!
reference resolver meant to be used on its own, returning an object with aset
method which either tries to change the hash or usesHistory.pushState
depending on the setup, and still triggers the necessary routes. Use for programmatically setting the route. It also offers areplace
method for when you want to change the hash/location/trigger routes without creating a new history item.setRoute!
andreplaceRoute!
resolvers that directly return functions.