When I first started investigating Ember.js I wrote a post about an Ember addon called the SC.StateChart that is really part of the old Sproutcore framework. In that post, I wrote that I liked the enterState and exitState handlers of the StateChart and how they would be ideal for switching between views. As of Ember 0.9.6, the SC namespace is no longer supported and I decided to refactor/remove anything that was Sproutcore related. It was during this refactoring that I found a very similar object called the Ember.StateManager that already exists in the Ember core code. It turns out that the Ember StateManager is a very elegant solution to transitioning between views and attaching and removing views from the DOM.
The Ember StateManager is part of Ember’s implementation of a finite state machine. Simply put a state machine can be in only one of a finite number of states. The machine is only in one state at a time; the state at any current time is called the current state. It can change from one state to another when initiated by a triggering event or condition, this is called a transition. The classic example is a state machine that represents a lightbulb transitioning from the off state to the on state.
Let us flesh this out with some code. Below is a StateManager that I am currently working on:
- On line 1, I am defining a definition from which a StateManager instance can be created from. I am actually extending another definition called the RouteManager that extends StateManager. The RouteManager is part of an excellent addon called the ember-routemanager that ties the StateManager and a routing implementation together. More on this later.
- On line 3, the initialState property is set to instruct the StateManager which state to transition to when an instance of the StateManager is created. I am using the Ember path dot syntax to say that the initial state will be a state named home (line 11) that is a child of a parent state named main (line 6).
- On line 6 is the definition of the previously mentioned parent main state. A StateManager is composed of child state objects. In this example, the StateManager is composed of Ember.ViewState objects. Ember.ViewState objects can contain Ember.View objects (as you would expect).
- On line 15, is another state named vault that has a child state named index (line 20).
An Ember.ViewState object extends Ember.State and as the name suggests, Ember.View objects are associated with the Ember.ViewState . All child state objects in the above example are ViewState state objects and each view has a templateName property that is a relative path to a handlebars template.
Anybody from a .NET background will wince at the name ViewState but I can assure you, there is no connection whatsoever.
When a StateManger is composed of instances of ViewState objects, the StateManager will interact with Ember’s view system and manage which views are added and removed from the DOM based on the StateManager’s current state. One way of transitioning between states is to invoke the goToState method:
@routeManager = WZ.ContentRouteManager.create()
@routeManager.start()
@routeManager.goToState 'vault.index'The above example will transition to the index state which is a child of the vault state. Using the Ember path syntax allows you to transition to a child state in one statement, no matter how deep it is. When you transition from one ViewState to another, the view of the currentState is destroyed and removed from the DOM as you exit the state and the view of the ViewState you are transitioning to is created and attached to the DOM as you enter the new state.
The StateManager can also receive and route action messages to its states via the send message, which you can read about in the Ember docs.
I find the StateManager to be a very elegant solution that is bereft of tedious boilerplate code. This is in stark contrast to Backbone.js where the user is left with both the architectural decision and the execution of how this should take place. I mentioned one such solution in my Backbone.js - Lessons learned post.
My first attempts at Backbone were littered with a lot of repetitive code like the following:
In Backbone, I think there is too much boilerplate code required to glue everything together. I have started using Derick Bailey’s excellent backbone.marionette recently with Backbone and you really need to use this if you are using Backbone regularly.
Nested ViewStates
When ViewState objects are nested like in the example below:
Both the StateManager’s currentState and any child states of the currentState will draw into the StateManager’s rootView property (line 2). In the above example, the RouteMangaer will initially transition to the path defined in the initialState property which in this case is the main.home path which points to the home state on line 11. This means that the handlebars template declared with the templateName property in both the main state’s child view object will be compiled and attached to the DOM and the handlebars template defined in the home state’s child view object will be compiled and attached to the DOM.
Below is how the main state’s view and the home state’s view look when they are rendered onto the browser:
You can get quite inventive and compose reusable partial views as and when you need them. I am setting the rootView property which means I can just layer views onto the body element of the html document but you can also specify a rootElement property:
rootElement: '#some-other-element'Routing
This brings us nicely on to routing, as I mentioned earlier, I am using the excellent ember-routemanager addon that ties the StateManager to a routing implementation. This addon allows you to create RouteManager instances that derive from the now infamous Ember StateManager. Below is a reminder of how I am extending the RouteManager:
It is worth noting that I am not creating an instance of the RouteManager but merely extending it which will allow me to create an instance on application start up or create instances in my jasmine specs.
What you should note from the above gist is that I am defining a route property on each child state object (lines 7, 12, 16 and 21). This allows me to use a combination of the route property and the nesting of the child states to define client routes that will transition to the required states and attach and remove the views from the DOM.
Below is how I create an instance of my derived RouteManager:
@routeManager = WZ.ContentRouteManager.create()
@routeManager.start()The start method will start the RouteManger listening for location changes. This allows me to define routes either as the now familiar hash links:
<!--Transition to the vault.new state -->
<li><a href="#vault/new">New</a></li>Or I can programatically set the location property which is useful for testing:
@routeManager.set 'location', 'vault/index'All that you would expect from a routing frameworks seems covered, below is an example of a route with a dynamic parameter:
route: 'vault/:exerciseid'Setting the browser location to #vault/5 would map to the vault state with an exerciseid of 5. Wildcards and regular expressions are also covered:
route: /(\d{4})-(\d{2})-(\d{2})/Conclusion
I got quite excited as I explored this approach (I should get out more). This is a real productivity boom that could be further enhanced by having the route inferred using convention over configuration by dynamically creating the route with respect to the state’s nesting. The RouteManager/StateManager/StateMachine is a very elegant solution for transitioning between views that exists in many other smart client UI frameworks. Being a web developer, I just have not come across it before.
Testing the RouteManager
I ran into a few problems when testing the RouteManager. Below are some initial specs that now all pass:
One of the problems I had was clearing the rootView property and disposing of the RouteManager after each spec. This is solved in the afterEach method on lines 10 to 16 of the above gist where I am removing each view from the DOM and destroying the StateManger.
Please feel free to add any comments below.
While working on the window’s platform, I have often used Microsoft’s
Each row in the table displays the company details that are extracted from a website screen scraping search operation. Each row can be expanded to display any additional child detail. The child detail can contain any additional email addresses and contact details that have been scraped from a web site search and belong to the parent company detail row.
Below is my handlebars template that will render each object added via the pushObject method. This is the template that the Ember view in the previous gist pointed to.
Handlebars comes with the #if helper which does what you would expect with one gotcha. Handlebars does not support conditional statements like
To a more detailed report illustrated in the image below:
The user can go back and forward between these views and drill down into further views of the information but I want to concentrate on this simple scenario outlined above in the two screenshots.
Below is the sass that is used to create the sunken effect for the text of the header and label elements that are outlined in the above image.
I have used the new css3 box-shadow property to achieve this effect. As the name suggests, the box-shadow property is used to apply an inset or drop shadow to a block element. It is also possible to add multiple box-shadows to an element as in this example. As this is a css3 experimental property there are the usual vendor specific prefixes for this property….that is unless you use compass and you can use the
The css3 border-radius property is used to give a block element rounded corners and as we cannot be bothered to type all these vendor specific prefixed properties, we are going to use the compass
Below is the sass that is used to create the box-shadow, linear-gradient and border radius effects:
We can also put a delay on the transition which equates to a tasteful delay of a specified time before the transition occurs.
I have used the
I have applied gradients to both the header section and the navigation bar of the html document. CSS3 comes with a gradient property and most of the modern browsers have their own prefixed slants on this property. As we are using compass and sass, we can forget about the need to create these vendor specific duplicated linear-gradient properties. Yet another reason for using compass is that compass will output the vendor specific cross browser rules. The latest version of compass has the excellent
We are going to use the new semantic <nav> element of html5 to mark up the navigation and then apply css rules to it. Below is the haml for the new navigation bar:
I went on to mention that the application.css.sass file above is used as a manifest and I just use it to point to my application point of entry screen.sass.
I think you will agree that this looks pretty ugly but as we continue, I will progressively make it look better:
As you can see, I am not a designer but this looks reasonable enough to me but probably less so to others. Ok, back to sass…