Root Controller Paradigm

by Evin

In this blog post, I will spend some time talking about using the power of bindings in the controller layer with the Root Controller Paradigm. This is a really powerful paradigm where you do most of your work in one controller and then let the bindings cascade all the data through a controller chain. This provides several points to bind your view layer in order to present the data in whatever way that your want. I will also touch on adding search functionality that you can use. For the purposes of example, I will be constructing an example controller layer that you would use to build an iTunes-like library application…

The Basics

In more detail, the Root Controller Paradigm is based off the notion that you need a controller that is the source or root of where all the other controllers get their content. This makes maintenance much easier because all the work is usually done in one place for the records and the other controllers have maybe 2-3 lines of code to set up the bindings correctly to populate the content. Then, if you need to do some specialized work (like searching) you do it in the appropriate layer. Your controller chain is segmented in the proper object-oriented manner saving you time in development, documentation, and debugging.

I usually follow these steps to make it easier:

  1. Draw a Picture of the Root Controller, Controller Chain and Bindings
  2. Create the Root Controller
  3. Create the Controller Chain with the Bindings
  4. Add any specialty code that is needed in the Controller Chain

Step #1: Draw a Picture

I find it extremely useful to draw a picture of the Controller Chain and the Bindings. This is the best way to review your work and make sure you haven’t forgotten anything. It is also helpful to diagram out what functionality you need at what layer. It is also a wonderful documentation tool for future reference. For this iTunes-like functionality, the Controller Chain looks like the following:

SproutTunes Controller

You can see that the Root Controller is the sourceCategoriesController. This is where most of the work will be done and where we will spend most of our time and where most of the complex stuff will be done. Then you can see how the Controller Chain will cascade the data through the structure. You can see we are going to use an interaction of a TreeController, ArrayControllers, and ObjectControllers to accomplish what we want to do.

Step #2: Create the Root Controller

When we develop the sourceCategoriesController (Root Controller), we are going create a TreeController that will cascade the data through the structure. This particular controller will drive the view on the left side of our iTunes-like app. The structure is lifted from iTunes to give a point of reference. Here is a sample of what the code would look like:

  // ==========================================================================
  // SproutTunes.sourceCategoriesController
  // ==========================================================================
 
  sc_require('core');
 
  /** @static
 
    This is the Root Controller...
    @extends SC.TreeController
    @author Evin Grano [EG]
  */
  SproutTunes.sourceCategoriesController = SC.TreeController.create(
  /** @scope SproutTunes.sourceCategoriesController.prototype */ {
 
    treeItemIsGrouped: YES,
    content: null,
 
    /**
      @public function for contructing the source list on the left side
    */
    refreshSources: function(){
 
      var librarySources = SC.Object.create({
        treeItemIsExpanded: YES,
        hasContentIcon: NO,
        displayName: 'Libraries',
        treeItemChildren: [
          SC.Object.create({
            contentValueKey: 'displayName',
            displayName: "Music"
            items: function(){
              var collection = [];
              var q = SC.Query.create({recordType: SproutTunes.Music});
              collection = store.findAll(q);
 
              return collection;
            }.property().cacheable()
          }),
          SC.Object.create({
            contentValueKey: 'displayName',
            displayName: "Movies",
            assets: function(){
              var collection = [];
              var q = SC.Query.create({recordType: SproutTunes.Movie});
              collection = store.findAll(q);
 
              return collection;
            }.property().cacheable()
          }),
 
          // ADD MORE LIBRARY SOURCES: TV SHOWS, PODCASTS, ETC...
        ]
      });
 
      var playlists = SC.Object.create({
        treeItemIsExpanded: YES,
        hasContentIcon: NO,
        displayName: "Playlists",
        treeItemChildren: [
          SC.Object.create({
            displayName: "SproutTunes DJ".loc(),
            items: function(){
              var collection = [];
              // CODE FOR CREATING DJ MUSIC PLAYLIST
              return collection;
            }.property().cacheable()
          }),
 
          // Enter a List of SC.Objects for Playlists
        ]
      });
 
      var allSources = SC.Object.create({
        treeItemIsExpanded: YES,
        treeItemChildren:[librarySources, playlists]
      });
 
      this.set('content', allSources);
      this.set('selection', SC.SelectionSet.create());
    }
  });

So you can see that SC.Object that is selected will drive the content of the sourceController.

Step #3: Create the Controller Chain with the Bindings

This is the first controller that gets the benefits of the Root Controller Paradigm. The code would look something like this:

  // ==========================================================================
  // SproutTunes.sourceController
  // ==========================================================================
 
  sc_require('core');
  /** @static
 
    @extends SC.ObjectController
    @author Evin Grano
  */
  SproutTunes.sourceController = SC.ObjectController.create(
  /** @scope SproutTunes.sourceController.prototype */ {
    contentBinding: 'SproutTunes.sourceCategoriesController.selection',
    contentBindingDefault: SC.Binding.single()
  });

Now, we start the chain so the next controller in our chain is the itemsController. This is driven by the ‘items’ property that we set up in the Root Controller objects. The code for this would look like this:

  // ==========================================================================
  // SproutTunes.itemsController
  // ==========================================================================
 
  sc_require('core');
  /** @static
 
    @extends SC.ArrayController
    @author Evin Grano
  */
  SproutTunes.itemsController = SC.ArrayController.create(
  /** @scope SproutTunes.itemsController.prototype */ {
    contentBinding: 'SproutTunes.sourceController.items',
 
    // search stuff
    search: null,
    searchContent: null,
 
    _searchHasChanged: function(){
      var c = this.get('content');
      this.set('searchContent', c);
    }.observes('search'),
 
    _contentHasChanged: function(){
      var c = this.get('content');
      this.set('searchContent', c);
    }.observes('[]')
  });

Next, we have the next part of the chain which is the itemsSearchController that is what the ‘song’ list will show. The code will look like this:

  // ==========================================================================
  // SproutTunes.itemsSearchController
  // ==========================================================================
 
  sc_require('core');
 
  /** @static
 
    @extends SC.ArrayController
    @author Evin Grano
  */
  SproutTunes.itemsSearchController = SC.ArrayController.create(
  /** @scope SproutTunes.itemsSearchController.prototype */ {
    contentBinding: 'SproutTunes.itemsController.searchContent'
 
  });

and now the final controller in the chain, itemController and this is what the code would look like:

  // ==========================================================================
  // SproutTunes.itemController
  // ==========================================================================
 
  sc_require('core');
  /** @static
 
    @extends SC.ObjectController
    @author Evin Grano
  */
  SproutTunes.itemController = SC.ObjectController.create(
  /** @scope SproutTunes.itemController.prototype */ {
    contentBinding: 'SproutTunes.itemsSearchController.selection',
    contentBindingDefault: SC.Binding.single()
  });

Step #4: Add Specialty code

Now that we have our completed Controller Chain, we could hook up a ListView to the sourceCategoriesController to get a list on the left side and we could hook up another ListView to the itemsSearchController to show the list of items once the source is chosen. But, we want to add searching to the itemsController. The code would look like this:

  // ==========================================================================
  // SproutTunes.itemsController
  // ==========================================================================
 
  sc_require('core');
  /** @static
 
    @extends SC.ArrayController
    @author Evin Grano
  */
  SproutTunes.itemsController = SC.ArrayController.create(
  /** @scope SproutTunes.itemsController.prototype */ {
    contentBinding: 'SproutTunes.sourceController.items',
 
    // search stuff
    search: null,
    searchContent: null,
 
    _updateSearchContent: function(){
      var c = this.get('content');
      var search = this.get('search');
 
      if (!search || !c){
        this.set('searchContent', c);
      }
      else {
        search = search.toLowerCase();
        var currItem, currSearchFields;
        var searchArray = [];
        for(var i = 0, len = c.get('length'); i < len; i++){
          currItem = c.objectAt(i);
          currSearchFields = currItem.get('searchFields');
          if (currSearchFields.match(search)) searchArray.push(currItem);
        }
 
        this.set('searchContent', searchArray);
      }
    },
 
    _searchHasChanged: function(){
      this._updateSearchContent();
    }.observes('search'),
 
    _contentHasChanged: function(){
      this._updateSearchContent();
    }.observes('[]')
  });

Conclusion

Now, you have the very clear separation of the all the functionality in controller layer and you can trust that the bindings are going to be set correctly. So most of your work is done in the Root Controller and then you just add functionality only at the levels that make sense. This will keeps all the information in the correct layer of the MVC structure as well as increase your ability to maintain the code and you get the benefits of the bindings in the controller layer.

This entry was posted on Thursday, July 30th, 2009 at 8:02 am and is filed under Best Practices, Coding Tutorials, Sproutcore. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

3 Responses to “Root Controller Paradigm”

  1. Martin Says:

    Hi,

    THX for your tutorials.
    I am new at Sproutcore 1.0 and want to digger depper. But have difficults to put all pieces proberly together. Could you share the comlete app? I think that is very helpfull.

  2. Evin Says:

    @Martin:

    You’re welcome. I am reviewing the following code on github for these paradigms:
    http://github.com/suvajitgupta/Tasks/tree/master
    I would look at that for a more complete app.

  3. Nifty Trick with Root Controller Paradigm « The Codeaholic Says:

    [...] if you have not read Evin’s post about the “Root Controller Paradigm” click here, I will [...]

Leave a Reply