After going back and forth with Dan G. Switzer, II in the comments of my blog post on Finite State Machines and UI widgets, I wanted to try and factor out the "stateful" aspects of my code into some sort of reusable, lightweight framework. Furthermore, I wanted to create a way for states to have access to more static configuration; that is, rather than binding and unbind every event handler with every state change, I wanted some states to be able to permanently bind event handlers if possible. And, while this would cut down on processing overhead, I found that it was not so applicable to my particular experiment.
As a testing context for this refactoring, I wanted to create a button that could be either clicked or dragged. I thought this was a good demo since the mouse-up gesture would put the button into two completely different states depending on the user's previous actions: A mouse-up after a drag would simply stop the drag; a mouse-up after a mouse-down would alert a message for the user.
To make the code more reusable, I encapsulated the state management within a state machine class, Stateful(). The Stateful class has two public methods:
defineState( name, options )
gotoState( name )
Each state is defined by a set of options (object / hash). While no one option is required, the Stateful() instance will check for three different properties:
init() - This gets called once when the state is first defined. It provides the state with a one-time setup event in which the state can cache DOM elements and bind static event handlers.
setup() - This gets called every time the state machine gets put in the given state. This can be used to setup state-specific event bindings and DOM alterations.
teardown() - This gets called every time the state machine leaves the given state. This can be used to remove any state-specific event bindings and undo any DOM alterations.
This demo has five distinct states:
Default - The button is just siting there.
Hover - The user has moused into the button but has not performed any further action.
PreDrag - The user has depressed the mouse; at this point, it is unclear as to whether the user will start a drag motion or an activation motion.
Drag - The user has moved the mouse (in the PreDrag state). The button will now be moved in accordance with the mouse until the user releases the mouse button.
Activated - The user released the mouse button directly after pressing it. This alerts a message to the user. This state is transient and will move directly into the Default state after the message has been alerted.
When I was writing the code for this Stateful widget, I thought I would be able to statically bind more event handlers; that is, I thought I would be able to bind more event handlers once, regardless of the state of the UI widget. As it turns out, however, the complexity of the demo and the slow speed of mouse tracking (by the browser) required that I keep most event bindings state-specific. I did find out, however, that the init() method was a great place to cache DOM elements.
Anyway, let's take a look at the code:
There's not too much to explain here; mostly, this is just a refactoring of the ideas that I've covered previously in my last few posts on Finite State Machines. I did, however, enjoy the creation of a Stateful() class in order to abstract the state management. It felt cleaner and more cohesive. And, I liked that the complexity of user interactions and workflow in this particular demo seemed more appropriate to the obvious verbosity of a state machine approach.
I do think this makes the creation of the states more manageable and easier to digest. However, there's a few things I'd change:
1) I think I'd change my possible state events to:
setup: Your current init() handler
begin: Your current setup() handler
end: Your current teardown() handler
teardown: Runs when you want to complete remove a state
Since I think you used setup/teardown terminology based upon jQuery's custom events, I think this make the meaning closer to jQuery's event handlers.
In my mind, "setup" occurs once at the initial creation and "teardown" occurs when the state would be completely removed. The "begin" and "end" for me are more intuitive in that they occur when a the state changes. You could also use enter/leave instead of begin/end.
2) I'd probably execute my state changes like this:
That way you can easily reference the options supplied to a state--for when you want to pass additional state values.
3) You might consider adding options to the Stateful() object itself. It seems like the odds are a single Stateful object would only refer to a single object your tracking, so it might make sense to store information about that object in a single object. You could then pass that State's options as an argument like in suggestion #2.
4) Also, if you wanted to be able to have something in multiple states, you could an option for each state which indicates whether or not it's state should be cancelled if the state changes. You'd obviously have to track multiple states, but it's definitely doable and something you can manage via your Stateful object.
5) Lastly, and admittedly more of a personal preference than anything, I'd leave off the "State" nomenclature from your methods. Since the object only deals with states to begin with--I think it's unnecessarily verbose.
To me this is perfectly and easily understandable:
(Although I like "set" better than "goto" because it's more consistent with what you'd use in other objects and it's just as meaningful.)
Definitely, the term setup / teardown is from things that I have previous read or experimented with jQuery custom events. But, I wouldn't say that I applied it with any real discretion. I don't feel particularly attached to the naming and wouldn't be against changing any of it.
And, like you, I would probably just go with define() and goto(). I think I left State on for 1) fear that "define" and "goto" were some sort of reserved word (and, even though its ok as properties, my IDE sometimes gives me red-squiggles no matter what) and 2) it was a hold-over from when the methods were stand-alone and not part of a cohesive unit.
But, more than anything, I am stuck right now with the idea of storing additional options in the Stateful() instance itself. In my original vision, an object (ex. UI controller / delegate) would extend the Stateful() class to become "stateful."
But then, I ended up composing, rather than extending. So, there is a controller object which happens to have, as part of its internal properties, this state-management system.
So, on the one hand, I can see stateful-instance-options being nice; but on the other hand, I feel like maybe that should just be stored at the Controller level along with whatever controller-level properties would be necessary for proper functionality.
Right now, I'm trying to apply some of this to a non-trivial part of an existing project. This includes sandbox-based communication, as in this Script Junkie article:
Though, as I say that, I am realizing I haven't read that article in a LONG time... probably should go back and re-read it before I get too deep into this code.
Anyway, thanks for all the feedback!
Out of curiosity, how would youmcompare that model to something more generic like the dojo.stateful
Where you are listening to not events, but states - ie simple or complex values and doing the acts needed for "your part" based on them? ie draagable until dropped triggered by mouse over set to true
Guessing it would be easier to reuse draggable logic with a triggered listener as opposed to predefined state? Or would/could you just as easily configure the states to use predefined functions / libraries as custom code?
To be honest, I don't know much about the other libraries and how they handle things like this. Already, I am seeing some problems with these ideas as I have applied them to the work I am currently doing. Some of these are simply architectural decisions that probably have little to do with the underlying concept; but, clearly, this stuff needs to fit together nicely.
I think it's time I start looking at some good Backbone.js exmaples to see how they deal with statefulness and inter-module communication.
looking at it (backbone) a bit myself for the fun and learning (at least initially)
nice blog post over here http://backbonefu.com/2011/08/front-end-developer-to-backbone-js-what-you-need-to-know/
on what the use case is (single page app w/o serious backend architecture), and links to specific tuts to dive deeper
and Cody Lindley share this on twitter;
Backbone.js Screencast - Introduction and Views http://t.co/WTWU53b
50min, havent seen yet - but the intro says;
"basic introduction on how to bootstrap a new Backbone.js application and go in-depth on how to use Backbone Views in particular"
Thanks for the links. Still top on my list of things to do, but haven't looked into it yet. Right now, I'm loving RequireJS for dependency management. I think this will all go towards making something like Backbone more digestible.