-
-
Save fritz-gerneth/5175698 to your computer and use it in GitHub Desktop.
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'); | |
} |
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
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.
- 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?
- 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.
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();
})
}
Hi Fritz, sorry it took so long to respond, I've been way too busy this week.
Ok, I think if I am understanding this correctly, what you are saying is that you'd like a low-level package providing a
RouteStack
, which would get informed by some higher level package (the router for instance) of the route map, and would provide an interface for other packages to query and find the low-level details on how routes match.I wonder about the following things:
RouteStack
be a singleton instance accessed by many different packages? It could be a huge mess if they are all adding their own routes to it. Or are you imagining your package would access it in a readonly fashion with something likeMeteor.Router.Stack.match(request)
?RouteMatch
for the current route? would the router need to return instances? (perhapsMeteor.Router.currentMatch()
or the like).Some more information that may be interesting for you: