As more people start to adopt statecharts to help organize application logic and manage the app’s current states, a question begins to appear about what, if anything, are SproutCore’s controllers useful for? After all, application logic that used to be located in controllers is now being yanked out and placed into individual state objects. This would appear to make controllers redundant. But is that really the case? If so, then what does that mean for classes such as SC.ObjectController, SC.ArrayController and SC.TreeController? Should they simply be removed from SproutCore altogether? To really answer these questions and know where a statechart begins and controllers end, we first have to understand what controllers really are, and, more specifically, what controllers are with regard to SproutCore.

Controllers and MVC

The model-view-controller (MVC) design pattern is kind of the granddaddy of design patterns that is used to organize an application’s classes into specific groups (or, rather, layers) in order to provide better modularization of code and separation of concerns. The model layer contains objects that are responsible for representing the application’s data and business logic. The view layer contains objects that are responsible for visualizing model objects and other data and handling user interactions that get converted into application specific actions. The controller layer contains objects representing the application’s glue code, so to speak. By “glue”, controllers, in the traditional MVC sense, are responsible for a few things:

  1. Connecting views with model objects
  2. Handling actions sent by views
  3. Managing the application’s current state or states
  4. Updating model objects and views when responding to actions and state changes
  5. Acting as a delegate
  6. Handling events raised by other mechanisms that make up the application

In other words, the glue code contained within controllers is, in a nutshell, what is responsible for managing the application logic. Therefore, based on the defined roles models, views and controllers play in traditional MVC, you often get a picture that looks like this:

In SproutCore, controllers take on another but very important responsibility, and that is to act as a direct mediator between views and model objects. Compared to traditional MVC where views are more directly coupled to model objects, views and model objects in SproutCore are intended to be completely decoupled from each other via a controller. There are two primary reasons for this. The first is to insulate change between views and model objects. The second is to allow controllers to be able to transform and filter information passed between views and models. Therefore, looking at SproutCore’s MVC architecture, you instead get the following picture:

This idea of placing a controller directly between views and models is not original to SproutCore. Instead, SproutCore takes this controller concept right from the play book of Apple’s Cocoa framework.

Compared to traditional type-safe languages that are rigid with respect to how methods and properties are invoked and accessed, both SproutCore and Cocoa remove this rigid nature by using KVC/KVO. This is why a view can work with a controller due to the get() and set() methods in order to access some backing content’s data and not care about the underlying type. (Granted with SproutCore and it being built on JavaScript, type checking is already pretty fluid). In addition, since bindings provide seamless and automatic flow of data propagation, controllers are designed to leverage that in order to filter and transform data that pass through them.

Mediating and Coordinating Controllers

Knowing that SproutCore and Cocoa add another role to controllers, the idea of controllers then take on two specific forms. That of mediating controllers and coordinating controllers.

Mediating controllers are responsible for mediating the exchange of data between views and models, or, more generally, any kind of backing content, not just model objects. SproutCore comes stock with three mediating controllers: SC.ObjectController, SC.ArrayController and SC.TreeController. Both SC.ArrayController and SC.TreeController already provide built-in data transformation and filtering logic so that you don’t have to write the code yourself. Oh, and as you may have guessed, these three mediating controllers directly orginate from Cocoa’s controller equivalents.

Coordinating controllers do everything else other than mediate the flow of data. This means that coordinating controllers contain the rest of the application logic, which is to say, they handle actions forwarded by views, act as a delegate based on a specific interface protocol, manage an application’s states, and set mediating controllers’ backing content where necessary.

Whereas all mediating controllers having some backing content, coordinating controllers do not, which is why it is perfectly legitimate to make a coordinating controller a regular old SC.Object. In fact a SproutCore application’s root application object (SC.Application) can itself be a coordinating controller.

Controller Madness

As with all reality, things can get messy, and the idea of mediating controllers and coordinating controllers often get intertwined. This is why it is so common to see mediating controllers often containing logic that belongs to a coordinating controller. Not to say that this is a bad thing to do, but the lack of separation of concerns can reduce a controller’s cohesiveness and possibly make things harder to maintain.

In another case, coordinating controllers have a tendency to quickly fall victim in how its logic is organized to handle actions and manage an application’s state. Managing an application’s state either gets spread out across many coordinating controllers or gets all jumbled up into a few monolithic coordinating controllers. In addition, the idea of an application’s states are typically not centralized into individual, cohesive objects, but instead end up being represented as types of primitive values that are checked when a controller’s various methods are invoked. And often, those methods turn into brutish monsters containing many if-else or switch-case statements to know what state the application is currently in. Again, this all leads towards hard to maintain code.

Here Comes Statecharts to the Rescue!

The idea of separating application logic between mediating controllers and coordinating controller is a good idea in principle, but as discussed, reality steps in and can make things more challenging. Then what to do? Well, we recognized that we want to try and keep mediating controller code separate from coordinating controller code. As well, we realize we need a better way of organizing the coordinating controller logic so that we can better represent an application’s various states as cohesive, individual objects, and reduce, or simply remove, having to check what state we are in in order to perform some action.

Separating mediating logic from coordinating logic is more a matter of practice and discipline in assuring you have controllers to properly handle each instead of smashing the logic together. Therefore, to start, all your mediating controllers should be updated so they contain no coordinating logic, which will mean that the majority of your mediating controllers end up being very small with respect to the amount of code they contain. But what now happens to the coordinating logic? Where does it all go? Do you make a lot of coordinating controllers? The answer to all of that is statecharts.

To put it simply, statecharts supplant much of a coordinating controller’s use. Statecharts have the advantage over coordinating controllers since an application’s states are represented by actual objects and get organized in a hierarchy to provide state clustering, abstraction, and proper transitioning between states, among other benefits. In addition, all the checks a coordinating controller have to do to know what state the application is in and what logic to execute is effectively removed. This is because each state in a statechart knows what actions it is responsible for handling. If a state becomes a current state of the application then it will only handle those actions it is responsible for (or one of its parent states) and ignore all other actions. Therefore no need to check what the current state is.

Where statecharts take over for coordinating controllers, they do not for mediating controllers. A statechart’s states are not responsible for mediating the exchange of data between models and views via the use of bindings. Therefore you continue to make use of SproutCore’s stock mediating controllers.

What About Using the State Design Pattern?

Some of you reading this might be taking a bit of a pause and thinking to yourself that instead of using statecharts you could continue to use coordinating controllers and manage all the state and action complexity via the Gang of Four‘s (GoF) state design pattern. That’s certainly an interesting idea, so let’s take a look at this approach.

The GoF’s state design pattern is used to decouple an object from its internal state so that the object’s state can be switched without changing the object’s type. To put it another way, some or all of the logic that is executed based on the object’s current state gets pulled out into separate state objects. This instead of keeping all that logic together and using various conditional checks to know what logic to execute. This provides a nice separation of concerns and keeps specific state logic grouped together to offer better cohesion and maintainable code. The following figure provides an illustration of how the GoF’s state design pattern works.

state design pattern

As you can see from the figure above, all of object foo‘s logic that represents state A goes into the StateA class, and all of the logic that represents state B goes into the StateB class. Both states derive from a common state that the foo object interacts with. When foo switches state, the current state object will get switched. Therefore any object that interacts with foo, foo‘s interface remains exactly the same and only its underlying logic changes. The following code provides an basic example of how the state design pattern could be implemented:

MyApp.GameBoardState = SC.Object.extend({

  owner: null,

  playing: NO,

  enterState: function() { /** no-op */ },

  exitState: function() { /** no-op */ }

  moveCharacter: function(x, y) { /** no-op */ }

});

MyApp.GameBoardPlayState = MyApp.GameBoardState.extend({
  playing: YES,
  enterState: function() { ... },
  exitState: function() { ... },
  moveCharacter: function(x, y) { ... // move the character }
});

MyApp.GameBoardStopState = MyApp.GameBoardState.extend({
  enterState: function() { ... },
  exitState: function() { ... }
});

MyApp.GameBoard = SC.Object.extend({

  currentState: null,

  playStateExample: MyApp.GameBoardPlayState,

  stopStateExample: MyApp.GameBoardStopState,

  init: function() {
    var example = this.get('playStateExample');
    this._playState = example.create({ owner: this });
    
    example  = this.get('stopStateExample');
    this._stopState = example.create({ owner: this });

    this.stop();
  },

  play: function() {
    this._changeState(this._playState);
  },

  stop: function() {
    this._changeState(this._stopState);
  },

  moveCharacter: function(x, y) { 
    this.get('currentState').moveCharacter(x, y);
  },

  playing: function() {
    return this.getPath('currentState.playing');
  }.property('currentState').cacheable(),

  /** @private */
  _changeState: function(state) {
    var cs = this.get('currentState');
    if (cs) cs.exitState();
    state.enterState();
    this.set('currentState', state);  
  }
});

While the state design pattern is definitely useful, it starts to break down when used by itself under various scenarios, such as: Where states need to be nested and grouped within other states (parent states); there is a need for concurrent states that are active at the same time; when you want to forward actions across a number of states; and handling anything beyond basic state transitioning. You can obviously accomplish all of these goals by enhancing your state logic and combining it with other design patterns, such as the composite pattern, the chain of responsibility pattern, and so on. In addition, you can also make the solution more generic so that it can be applied across many coordinating controllers with less effort. But guess what? When you do all that work, you are basically recreating the statechart framework that already does all of that for you. In some sense, you can think of the statechart framework as being kind of like the state design pattern on steroids!

Where Coordinating Controllers Still Fit In

Earlier on I noted that statecharts supplant much of a coordinating controller’s use. What exactly do I mean by “much”? One area where coordinating controllers are still useful is when they act as a delegate for some other object — most commonly views. A view can make use of a delegate in order to provide some external object with the ability to help direct how parts of the view will operate or to handle more complex actions. The most common example of this is the collection view and how it uses a delegate to help handle things like drag-and-drop or how selected items are to be deleted.

A view that makes use of a delegate will often have a dedicated coordinating controller to take on the role. But if the statechart is now managing the application’s state and raised actions, how does a coordinating controller acting as a delegate for a view fit in? The answer to that is that the controller itself will either fully or partially delegate out to the statechart. Remember that when a view delegates beyond using a simple target-action delegate pair, which a statechart can already handle, it is often providing more information about what the delegate can do and how it should respond. Therefore, the coordinating controller acting as a delegate needs to translate that information into an action which a current state or states in a statechart can then handle. In some cases the translation is minimal and in other cases, such as using drag and drop, the translation can be more involved, depending on what you’re are trying to do.

As a basic example of a coordinating controller acting as a delegate and sending actions on to a statechart, let’s look at the following code:

MyApp.userListDelegateController = SC.Object.create(
  SC.CollectionViewDelegate,
{

  collectionViewDeleteContent: function(view, content, indexes) {
    if (indexes.get('length') === 0) return NO;
    
    var users = [];
        
    indexes.forEach(function(idx) {
      users.push(content.objectAt(idx));
    });
   
    MyApp.statechart.sendEvent('deleteSelectedUsers', this, { 
      users: users 
    });
    
    return YES;
  }

});

First, the MyApp.userListDelegateController is just a basic SC.Object that mixes in the SC.CollectionViewDelegate mixin. The controller in this particular case is handling the delegated action of deleting users from a list. In order for the statechart to handle the action, the controller is responsible for processing the given arguments supplied to collectionViewDeleteContent to then be passed to the statechart’s current states that will handle the deleteSelectedUsers action. The controller itself is not responsible for any other application logic — that is left to the states. The controller simply processes the arguments and then sends on an action to the statechart. This provides a clean separation of concerns and makes code more maintainable. The state that handles the action can then decide what to do, such as showing a confirmation dialog to the user asking if they are sure they want to delete the selected users.

In Conclusion

I ended up providing you with a detailed explanation about the differences between controllers and statecharts and what they are ideally used for when building your SproutCore application. However, although detailed, the central premise is that controllers can be split into two fundamental roles: mediating and coordinating. SproutCore comes stock with mediating controllers that you can use right away, and most of the application logic that did belong to coordinating controllers can now be shifted over to statecharts in order to better organize and manage an application’s states and handle actions. That being said, coordinating controllers still have a place when acting as a delegate and sending events to a statechart.

In the mean time, happy SproutCore coding!

-FC