Skip to content

Instantly share code, notes, and snippets.

@fritz-gerneth
Created March 16, 2013 09:44
Show Gist options
  • Save fritz-gerneth/5175698 to your computer and use it in GitHub Desktop.
Save fritz-gerneth/5175698 to your computer and use it in GitHub Desktop.
Suggested router API/Interface
class MVCRoute
{
public function getController();
}
class MVCDispatcher
{
var template = router.match(request).getController();
// render template into view, or pass it to view-package, ...
}
The request and the router are decoupled. For the router it doesn't matter from where the current URI to match is actually from. This enables server-side routing for the same setup as well. (Using a different dispatcher of course). On a long-term the request probably would be a package itself abstracting client/server differences in a request.
The dispatcher is depending on the `doesMatch` method, and trigger recalculation of the view uppon invalidation. As we receive a callback we can invalidate controllers (being destructors for those, to unsubscribe/....).
The implementation of the routes doesn't matter for the dispatchers, why should they care about how the data was gathered.
/**
* Common interface for any kind of routes
*/
interface RouteInterface
{
/**
* Does this route match the request
*
* @reactive
*/
public function doesMatch(request);
/**
* Match the route for the request
*
* @return RouteMatch
*/
public function match(request);
/**
* Assemble a URI according matching this route (similiar to "to")
*/
public function assemble(params = array());
}
interface RouteStackInterface
{
public function addRoute(RouteInterface);
public function doesMatch(request);
public function match(request);
}
/**
* Data container for information about the current route match.
* Changes in here do not trigger rebuild of the actual URI.
* This object is not reactive (as the layer above should be).
*/
class RouteMatch
{
/**
* Constructor
*/
public RouteMatch(params);
/**
* Set the matching route
*/
public function setMatchingRoute(RouteInterface route);
/**
* Get the matching route
*/
public function getMatchingRoute();
/**
* Params are those matched, i.e :id, :controller, ...
* routeMatch->getParam("id", 5);
*/
public function setParam(name, value);
public function getParams();
public function getParam(name, default = null);
}
For pagejs to direct navigation this might be:
class PageJSRouter
{
// implementation
}
var router = RouteStack();
router.addRoute(...);
// another package
class SimpleDispatcher
{
var matchObj = router.match(request);
// render matchObj.getParam('template');
}
@fritz-gerneth
Copy link
Author

Let's assume there are the following components:

  • Request (Object, containing all information about the current http-request)
  • The Router instance, having a set of routes and matching the current request against the routes (instance of RouteStackInterface
  • Other components on top of the router, listening for the current matching route

The router is an instance of RouteStackInterface. It's not singletone (remember, singletone is bad). In theory there can be multiple instances of the same router or different routers at the same time. Other packages just access the variable containing the router instance they want to use. How they gain access to it doesn't matter for the router, that's up to the developer.

With the your approach (and your new API) there's the problem that the router actually has multiple responsibilities (matching routes, callbacks, templating,.. ). With the suggested API the router only does match routes and makes the current matching route available. Other components can access it and decide on what to do on their own way (e.g templating, layout).

Looking at my suggested API: the RouteStack is what currently the router is, but in a more general way. In therory (later on) we could build up tree-like routes, sub-routes and others. Other packages can add their own routes to it if they have access to the instance (remember, it's not singletone, so it actively has to be passed).

Other components can rely on the current matching route by calling match() (returning the route and further route-related information). As this function is reactive it's easy to be notified when the routing result changes.

Templating and views would be build on top of the router. They'd call the match function of the Router(RouterStack probably), look at the route that matched and decide which layout / template to render. This way the router isn't aware of the templating stuff at all. A bonus: it can be used on the server as well. More sophisticated applications might decide not to the templating directly as described but have a component that maps the current matching route to a function. That'd be a real FrontController then. Effectively it splits up your router into several smaller packages with only one responsibility.

Now I know Meteor wants to be beginners friendly. Making thinks simple for beginners is important, sure. However the architecture should be flexible enough for more complex use-cases. Therefore I'd suggest several packages built on top of each other:

  • The router as described above (for experts having a complex application stack)
  • Simple FrontController directly mapping routes -> templates (as done in your API), dependent on the router package (for advanced users not relying on singletone or global variables)
  • Simplify package: providing some simplifying helpers (e.g. putting a router instance into the Meteor namespace). Beginners would use this package and have almost the same API as you described.
  • FrontController package for Controller applications (not done in your API), directly depending on the router only for experts

That'd look like

                Router
            /            \
FrontController       Simple FrontController
                                |
                          Simplify package

@tmeasday
Copy link

tmeasday commented Apr 1, 2013

Hi @fritz-gerneth (did that make you get an email?).

Sorry again for my slow response!

I think I'm understanding what you are saying, but I'm confused about two things.

  1. I'm not sure what a Front Controller is, but I think you are saying it makes a certain function run every time a certain URL matches?
  2. It's not clear in all this how the actual active window (or request)'s URL (which has to be a singleton) is involved.

I've started to write up a "split" API based on your ideas here: https://github.com/tmeasday/meteor-router/wiki/Split-API

What do you think? The concepts match across pretty cleanly to what you are saying here if I've understood you correctly.

@fritz-gerneth
Copy link
Author


var FrontController = function (routeStack, dispatcher) {
    "use strict";

    Meteor.autorun(function () {
        var currentRoute = routeStack.match();
        // pull information on what to call from where-ever, e.g. a dispatcher or for simplicity, from the route itself
        currentRoute.controller();
    })
}

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