The journeylism of @yreynhout

On CQRS, DDD(D), ES, …

Viewmodels.js

Over the past couple of weeks I’ve been entrenched in UI development, learning – the good parts of – javascript and using libs like Knockout.js, jQuery and Twitter BootStrap. My relationship with javascript, which started when Netscape Navigator was still cool, has been on and off over the past years. Given its current rise, it seemed like a good idea to get intimate again. Tasked with building the administrative part of an app that has a fair bit of client-side behavior, we set out to build the first version euh … failure. It was monolithic, taking on way too many responsibilities in places you didn’t expect, it had hardly any extensibility points (let alone figuring out where to find them), it was overloaded with technical “look at my neat trick to make it bark” concerns, and – to top it off – it was borderline unreadable. It’s safe to say we weren’t very happy with the result (which would haunt us in the months and years to come). Although, in all fairness, there were some nice gems in there.

So, what were we todo? What any agile team should do … be honest about it and tackle it head on. I started hacking on what I thought “it should be more like” and so did another team member. We sat down as a team, did a code review of the current approach to better understand what the actual problems were, compared it with two more approaches, sollicited feedback, bounced off ideas on how to improve the situation, and settled on a path forward.

It’s this path forward I want to show you a glimpse of. This is a more in depth exploration of what I touched upon in viewmodels like you meant it, but in a slightly different context.

Building blocks

If you want to prevent logic from creeping into your view, if you want to clearly define how visual state relates to a piece of data or to a trigger, you should refrain from using primitive types in your viewmodel, or you’ll end up with something similar to the sample below.

Preventing conditions from creeping into views is easy to solve. You just move the condition into the viewmodel, giving it a descriptive name along the way, maybe using a computed observable if it happens to be dynamic. Easy enough and nothing wrong with doing so (in fact I encourage you to do so). What may be less obvious is how to get rid of the primitives. The answer is simple: building the right abstractions.

Being valid or invalid, having focus, visibility, being enabled or disabled, having a value, change tracking and – in the case of data-binding – being able to pause and dispose subscriptions are all concerns that revolve around that one value most people seem to put as a primitive in their viewmodel. Don’t get me wrong, I’m not suggesting that each and every primitive value requires all these concerns, but I’d rather put them in my abstraction than polluting my viewmodel when I do need them.
Next to TextInput and NumericInput you can imagine DateInput, TimeInput, SelectOneInput, SelectManyInput, but also more domain specific inputs like PercentageInput (constrained numeric input), MinDefaultMaxDurationsInput (composite input), or even HtmlColorInput (formatted text input).

What applies to inputs also applies to triggers, which are most commonly visualized as buttons and links. A trigger could be used to replace the CanSave observable and Save function in the first example.

It’s nice to build and have these abstractions, it’s even better to actually use them. Do realize that, like any abstraction, they emerged from actual viewmodel implementations. For those that think “this is a controls library”, I can only feel pitty, because this has little to do with view specific controls.

Composition is the name of the game

This was one of the most fundamental insights we had during this journey: the ability to compose viewmodels using the above building blocks and other viewmodels (that in turn have been composed using those building blocks) made them easier to define and reason about. It also helps to keep them at the same level of abstraction.

This line of thought can also be applied at other levels. It has a very recursive and composite feel to it.

Behavior and low-carb viewmodels

Something that’s been missing from the previous snippets is any form of actual behavior. How is one going to introduce logic/flow and interact with dialogs and server side resources? I tend to associate that coordination task with a controller. “Viewmodels as controllers” starts to crumble really fast when you’ve applied the composition above. You’ll end up with big fat viewmodels that feel like a blatant SRP violation.
Therefor making both the controller and how it’s bound to viewmodels explicit, pays off in readability, testability, and keeping the viewmodels thin. Don’t get too hung up on the details below, there are many ways to implement these concerns.

Below is a glimpse of what the final binding could look like.

Conclusion

This is still very much a WIP, but with what little abstractions we’ve come up with, we’ve been able to untangle most of the mess we were in before. Dependencies are now easy to replace (adhering to SRP pays off). Composition has simplified the amount of detail we have to worry about at each level of abstraction. Making the controller explicit has kept our viewmodels free of unnecessary dependencies. All in all we’ve been much more productive using this approach.

Advertisements

3 responses to “Viewmodels.js

  1. craigcav August 9, 2012 at 20:49

    I like where this is going – nice post.

    You could probably go further still, cleaning up the view with some custom bindings (http://knockoutjs.com/documentation/custom-bindings.html).

    In place of bindings like data-bind=”click: Save.Command, enable: Save.IsEnabled” you could create, for example, a trigger binding that applies the appropriate bindings for triggers, resulting with a binding like:

    data-bind=”trigger: Save”

  2. Tom McKearney (@tommckearney) March 26, 2013 at 15:33

    What is the “ns.guard” stuff you’re using in here.. It seems interesting.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: