Tuesday, May 8, 2012

Principles of an MVC design

One of the things that I had to figure out when first using an MVC design was how things slot together. You have collections of models and collections of controls that all fit together somehow and you also have all the dependancies and listeners between them.

One of the things I disagree with in Backbone.js is the way a model has a link to it's collection. To me it felt like spaghetti code, why should an individual model need access to it's collection? It doesn't and this is one of the things that I baked in to PlastronJS.

I'm guessing this link was given so when a change was made to the model you could then pass the change up to it's collection - but this means the model knows it is in a collection. Instead in PlastronJS the collection will register handlers on each new model connected to it and so the collection model is in charge of it's changes and the model only deals with it's own. But how do you pass changes across? You do it in the control.

If you have a look at my todomvc example I have listeners on modelChange and anyModelChange which are fired by the collection when it's listeners on it's models are fired. Because it knows there is a change it can then do the calculations that are needed without having the models in the collection have an explicit link to it's collection.

This is the fundamental idea behind the interaction with the MVC. The user interacts with the view which is listened to by the control. The control then changes the model which alerts anything listening to it. So if I have a completed count on the list and the user ticks a completed button the interaction goes like this:

user click completed on view -> control listener executes and changes the model -> the model changes and alerts all listeners -> the collection which has a listener on that model fires a change event -> the control for the list picks up the event from the collection and updates it's view

As you can see all interactions should go from the view down to the model and back up to the view. Which leads to 2 basic rules when setting up event listeners in a control:

  1. listeners on the view should only update the model
  2. listeners on the model should update the view
There may be times when you want a change in a model to update another model, but I like to think of this as linking up the two models separately and should be done outside of a controller.

The reason to stick to this is simple - you don't want to repeat yourself. If you want to put a class "done" on an object when a checkbox is clicked and change the model to complete you don't want to do both on clicking the checkbox. For this you need two listeners, one that sets the model to complete when the checkbox is checked and a listener on the model that will set the class when it's checked attribute is set. That means that any new interactions you put in will automagically set the done class (say if later you want that item to be shared and can get updated in realtime through a web socket or long polling).

Stick to those rules and you'll write less code, more flexible code and just plain better code.

Now how about when you click a "select all" in the collection's control? Easy, the collection's control should have a reference to all the children controls and pass along the call to them to set it's model as completed.

You can also do things like have a child set itself as the only one to complete by passing up to it's parent (this is allowed because they are controls - not models - and live in a definite tree structure on the page) itself and have the collection have a setOnlyChildComplete() method which could go through it's own children and only set the given one to complete.

1 comment: