Use case:
For an administration UI, I want an interactive table widget with a number of behaviors:
- Collapse / expand (yes, this means the rows are a hierarchy)
- Update of data in a child cell based on a parent cell.
- Hiding or revealing of a row or an entire sub-tree based on a value update.
- Two or more form elements in each row, that may depend on each other. E.g. a checkbox that will mess with the value of a text field in the same row.
- DOM manipulation on newly created rows. E.g. the checkbox might be turned into a more visual kind of widget. Or
In another use case, I have the following additional behaviors:
- Adding and removing rows, manually or automatically.
- Drag and drop of rows. (probably not in combination with the collapse/expand, but who knows)
The problem:
- Ideally, we want each of these behaviors to be implemented as a separate and independent module / plugin. BUT
- If one behavior changes the DOM, then other behaviors need to know about it. E.g. if you insert a row, then the collapse/expand needs to know, to decide whether the new row should be shown or hidden. And the newly added row needs new event listeners from various other behaviors.
- There might be behaviors that are triggered by the state of other behaviors. E.g. each row might contain a text field, and depending on the value, the row must be moved up or down (drag/drop).
The question:
I had to deal with this kind of problem a few times and I generally managed to get it work. However, I always hated the architecture that came out of it.
What I’d like is an approach that allows to create the different behaviors as independent modules, and then somehow plug them together.
Some more specific questions:
- Where should each of the behaviors keep its state information
- What about state information for a specific table row? Should it be stored as a variable on the DOM element? (e.g.
$('tr')[5].isExpanded = true
) or rather in a data container in the module itself? - How can modules know about DOM updates?
- How should modules talk to each other?
- How should the code of each module be structured? What kind of API should it expose?
- How to handle multiple instances of this kind of widget on one page? (In this case, global instances don’t work)
Example: https://drupal.org/project/multicrud (sorry, all I have atm is Drupal modules)
2
I recently had to develop something vaguely similar. I ended up using jQuery UI’s widget factory, as in, I developed widgets.
You can read a lot about how jQuery UI widgets work. If you want a really complicated example, look at http://silk.codeplex.com/. This is all about a very modern single page interface built on jQuery (and an ASP.NET MVC server backend) and they answer all kinds of architectural questions. Despite of it being ASP.NET MVC, there are lots of cool JavaScript to learn.
Here it is in a few words of my own. A widget is a jQuery plugin, but it has some added jQuery UI functionality. A widget instance cannot be created out of “thin air”, it is added on top of a pre-existing DOM element. The widget adds a certain behavior on top of that DOM element. This is a nice two-layer architecture actually: the DOM element is the presentation, the widget instance is the logic behind it.
jQuery UI widgets provide the separation you need. Each widget is a different module, a different class, with its own constructor/destructor lifecycle, custom events, properties, options, etc.
The fact that you want to listen for DOM updates sounds bad. The DOM shouldn’t drive your widgets, it should be the other way around. You shouldn’t just add new DOM elements, instead you should create a DOM element, add a widget on top of it, and then let the widget do the work: put its element somewhere inside the document, notify other widgets, etc.
jQuery UI widgets support event triggering, DOM events and your custom events as well. It allows you to loosely couple your widgets. For example, when you create widget A, you might want to notify widget B about it, this is tight coupling. Instead, widget A could trigger an event, and if there is a widget B, it will get the message, this is loose coupling. This makes life a lot easier, and it also makes testing easier. There is a similar approach in Silk, they created a very cool event notification system, but I found that it’s way too complex for my needs when I have very few events and subscriptions.
A widget instance is also a perfect place to store its state.
I guess that’s most if not all of your questions. Please feel free to ask more specific questions. I spent a lot of time thinking about the architecture of my rich application, and I really love what I ended up with.
6