Open afeld opened 11 years ago
Well ... I'd say, the obvious solution to this is defining one state for "postDetail"
(with transition to itself, when navigating from /posts/1
to /posts/2
), one for "postList"
, ... and simply taking the postId
as a parameter. e.g. :
routes = Backbone.StatefulRouter({
transitions: {
postList: {
detail: {enterState: 'postDetail', callbacks: ['showDetail']},
},
postDetail: {
detail: {enterState: 'postDetail', callbacks: ['showDetail']},
list: {enterState: 'postList', callbacks: ['showList']},
}
},
showDetail: function(postId) {
// do stuff
}
});
Or something like that ... what do you think ?
Ah, so you can define transitions to the same state? Theoretically this works in the StateMachine as-is?
showDetail: function(postId){
console.log('visiting ' + postId);
}
...
routes.trigger('detail', 1); // "visiting 1"
routes.currentState; // "showDetail"
routes.trigger('detail', 2); // "visiting 2"
Don't think I saw an example in the tests, so I wasn't sure. Guess I could always write one...
The part in the README that says
event 'hide' while in state 'hidden' -> no transition
made it seem like it was not possible.
Theres no difference in a transition between 2 different states and one state to itself.
event 'hide' while in state 'hidden' -> no transition
The documentation says this, because in that case theres no transition defined. It doesnt say that you cant define it ! Ill make a note for precising this.
Gotcha. Not sure if it's a huge deal, but that means
showDetail: function(postId){
console.log('incrementing view count');
}
...
routes.trigger('detail', 1); // "incrementing view count"
routes.trigger('detail', 1); // "incrementing view count" - but you're already there!
In the router, one workaround is to memoize the route call's args (should be easy since args are strings) :
// Overriding Router's `route` method
route: function(route, name, callback) {
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (!callback) callback = this[name];
var wrappedCb = function() {
var key = this._argsToKey.call(this, arguments);
if (this.key !== key) {
this.key = key;
callback.apply(this, arguments);
} else {
return; // We are already on that route with same arguments
}
}
Backbone.Router.prototype.route(route, name, wrappedCb)
},
_argsToKey: function(args) { /* Just turn args into one string */ }
Of course one could also do :
showDetail: function(postId){
if (postId !== this.currentPostId) {
console.log('incrementing view count');
}
}
Sure - just considering if that should be covered by the StateMachine in a more generalized way.
Hmm ... it might make sense. Though that sounds a bit complicated in practice, and I can't really think of a nice way how to declare this for any state. One solution could be to make a state option, something like :
states: {
postDetail: {
subStates: : function(postId) { return 'postDetail:' + postId; }
}
},
transitions: {
postDetail: {
showDetail: {
enterState: postDetail
}
}
}
But I am not sure how obvious this is ... probably not very obvious. Any better idea ?
Well, when you declare routes in Backbone (or Rails) you can make something static ('/posts'
) or something with a known variable ('/posts/:id'
). What if the state definitions supported a similar syntax, where it could handle variations of a particular state?
var ImageViewer = _.extend({}, Backbone.StateMachine, Backbone.Events, {
states: {
'imageList': {},
'imageDetail/:id': {} // <-- special syntax, maybe in the transition definition too?
},
transitions: {
imageDetail: { enterState: 'imageDetail', callbacks: ['doCrossFade'] },
},
// ...
});
var viewer = ImageViewer();
viewer.trigger('imageDetail/1'); // cross-fades
viewer.trigger('imageDetail/2'); // cross-fades
viewer.trigger('imageDetail/2'); // (no change)
hmm ... this would make the API much more complicated (you forgot the events in your transitions
definiton). How would you then make a good API for mapping variables with event, arg and states ?
Something like this maybe ?
var ImageViewer = _.extend({}, Backbone.StateMachine, Backbone.Events, {
states: {
'imageList': {},
'imageDetail/:id': {}
},
transitions: {
imageDetail: {
'show/:id': { enterState: 'imageDetail/:id', callbacks: ['doCrossFade'] }
}
},
doCrossFade: function(id) { /* cross-fade here */ }
// ...
});
var viewer = ImageViewer();
viewer.toState('imageDetail/0');
viewer.trigger('show/1'); // cross-fades, state before : 'imageDetail/0', after : 'imageDetail/1'
viewer.trigger('show/2'); // cross-fades, state before : 'imageDetail/1', after : 'imageDetail/2'
viewer.trigger('show/2'); // (no change)
Also, I had in mind that events could be regexp (#10) ... and I am not sure how all this can work together !
On the other hand, I like how this would solve a problem that I have very often : "tabs", picture viewers, etc ...
Maybe we can just try implementing this API, and then let's see how it feels. I am not sure when I will have time to do this, but probably next week ; or if you want to try your hand at it, just go ahead !
Thinking on using this to wrap a Router (#16), I'm considering how dynamic routes would work. For example, are
/posts/1
and/posts/2
different states? You don't know all the possible states (in this case, all available the post IDs) up front, and even if you did, you wouldn't want to enumerate them. How would a transition between posts be defined?This question applies to the StateMachine in general but becomes really apparent when dealing with routes.