The question, in brief:
In MVC, how do you distinguish between a checkbox click (or a selectbox or listbox change) from a human meaning "Controller, modify the model", and a checkbox click (or a selectbox or listbox change) from the Controller meaning "I'm updating the view because the model has changed"?
The example:
I have a JS app (all one big HTML+JS page; there's a server behind it, and AJAX going on, but it's not important to the example) which has the notion of "Vertices" connected by "Edges". The UI lets you add and remove Vertices on a map, and enable or disable Edges between pairs of Vertices.
There are two ways to disable an Edge from Vertex A to Vertex B:
- click on the Edge to make the "Edge Details" window provide you with a "Disable This Edge" button; or
- click on Vertex A (or B) to make the "Vertex Details" window provide you with a checklist of nearby Vertices, from which you can uncheck Vertex B (or A).
Here's how this works under the hood in MVC (but see the end of this post for an update, where I correct problems in my understanding):
- Model: a list of Vertex objects and a list of Edge objects.
- View: a GMaps UI, with markers and polylines, plus checkboxes and buttons and "Vertex Details" and "Edge Details" DIVs.
- Controller:
- JS functions that update the model when events on the checkboxes and buttons fire; and
- JS functions that update the view when events on the models fire.
Here's the specific inelegance:
- The user has the Vertex Details Window focused on Vertex A, and the Edge Details Window focused on the Edge from Vertex A to Vertex B.
- The user clicks "Disable This Edge" in the Edge Details window.
- Controller function 1 gets the click event, and calls disable() on the Edge model object.
- The Edge model object fires the "I just got disabled" event.
- Controller function 2 receives the "I just got disabled" event, and
- redraws the Edge Details Window to say "I'm disabled!" and
- unchecks Vertex B in the Vertex Details Window.
- Crap! This fires Controller function 1 again, which was listening for UI events that mean an edge was disabled!
So there's an unnecessary re-update of the Model, and re-update of the View. In a more complex view with events that fire events that fire events, this can make for dozens of extraneous updates!
Update: a great answer.
I misunderstood MVC a bit. I don't have just one View, as I described above: I have several Views into several Models. In particular, I have a checkbox-list View of Edges to a particular Node, and a separate, "detailed window-style" View of an Edge.
Furthermore, I shouldn't have one controller function updating all views when the Model changes: each View should modify itself when the Model changes.
So if each View registers for "state updated" events on the Model, and each View updates itself upon receipt of those events, then the answer to my circular events question is simply this:
The checkbox-list View will disable checkbox events for the moment that it is updating the checkboxes after a Model state change.
Now if a user disables an Edge via the Edge Detail window, the Controller updates the Edge Model, the checkbox-list View receives notification of the update, and the checkbox-list View is smart enough to silence checkbox events while changing the status of the appropriate checkbox.
This is much more palatable than my original solution, where one Controller updates ALL Views -- and thus has to know which views need special care and feeding to avoid loops. Instead, only the single View with troublesome UI elements has to deal with the problem.
Thanks to those who answered my question!