Tuesday, August 6, 2013

Client-Side Chaos - Part I : Design & API

I coded some JavaScript back in 2000, off and on thereafter. The advances in this area keep on coming but practice still seems raw, lacking and in need of some patterns, layers, architecture etc. etc. Why does most of it encountered at various companies seem so freaking chaotic? Maybe I'm just working at the wrong places.

Anyway, JQuery was a big step forward. I like KnockoutJS too. It was pretty much a no-brainer and not so hard to understand how it was designed and implemented. The obtrusive dependence on binding attributes might bother some people but for me, the good seems to outweigh the evil.

Having used MVVM for WPF, KnockoutJS had immediate appeal but the examples on the site gave me a vague uneasy feeling because examples seemed to address small ViewModels and nowhere did they provide a working sample of a moderately complex page. Questions immediately came to mind, such as:


  • How can ViewModels and data access best work together?
  • How can encapsulation, hiding and OO be leveraged?
  • How can composition of simple ViewModels such as the ones in the examples scale to more complex pages?
  • How can the separation of concern and layering inherent in MVVM best be implemented?
  • How can all of these worthwhile things be accomplished without tools that are more complicated than the typical client-side chaos we'd all like to escape?
  • What sort of simple tool might be devised which would satisfy most common scenarios without the sort of bloat that happens when you try to do everything for everybody?
There were some basic goals :
  1. A generic or universal ViewModel should be possible. In other words, one object to suit any and all data. Without that there will be more code to write for each View in need of a ViewModel which seems unnecessary. JavaScript offers some advantage for ViewModels that can be had with C# in .Net only with the expense and complexity of reflection, CodeDom etc.
  2. Avoid tedious, repetitive code dedicated to moving values from point A to point B. Even COBOL had the old "move corresponding" feature and surely we've advanced a little bit since the days of old. So ViewModels must populate their own properties to accomplish this.
  3. Small ViewModels are OK and nice for control-like widgets but it would also be nice for a single instantiated object to be able to support the entire page to avoid having to manage potentially dozens of ViewModel instances. Aggregation to the rescue -- any ViewModel can be made  to expose multiple models...sub-View Models, if you will,  which can be creared and destroyed in response to events. These sub-View Models can be extended with methods.
  4. Sometimes I need to asynchronously obtain server data as POJOs (Plain Old JavaScript Object) without binding. Suppose I have an observableArray, a foreach binding and a need to push newly created items into the observableArray. A callback will be sufficient notification that the new entity has been saved.  No databinding is caled for. Therefore my ideal tool would support both databinding and callbacks.
  5. Being lazy by nature and always looking for ways to leverage design to make the job easier and faster, why not incorporate data access into the tool in order to avoid repetitive plumbing work needed to call DAC then pass model Data to ViewModel?
  6. My goal was not to be everything to everybody because fulfilling that need often leads to complexity and bloat. There's always a sweet spot to be found between the extremes of flexibility and simplicity. I've done some Java and in that parallel universe I'd always been impressed by how configuration could make the code simpler but if carried to the extreme could lead to configuration hell. I do like Microsoft's "convention over configuration" perspective with ASP.Net MVC but with a commercial product it is incumbent on the creator to also provide optional configuration alternatives or overrides to the convention which also leads to complexity. However, I could afford to favor convention over configuration.
  7. Despite high speed Internet access client-side bloat is still a concern. Add JQuery, KnockoutJS, possibly Backbone, a wide variety of plugins for all these things and your own custom scripts and it can get out of hand PDQ even with minified scripts. So I thought that maybe KnockoutJS map functionality might be achievable in 100 characters rather than 8kb. 2kb minified seemed a reasonable goal for a generic ViewModel that included map/unmap and data access functionality.
Well, that's my story and I'm sticking to it. It took several iterations and some false starts but after several days an initial cut that more or less attains these goals emerged. Sure there are a few rough spots but without more widespread use in a variety of situations I hesitate to leap further without looking more. Others can judge for themselves but enough of this blogificating, let's step through the code, why don't we?

Here's the API with a grand total of  4 public methods :

ViewModel(alertOnError[, errorCallback])

The constructor is easy enough. The ViewModel can issue alerts or leave all error notification to a global (i.e. for all requests to the ViewModel) errorHandler.


post(url, input[, successCallback[, shouldMap[, element]]])

The post method posts to a partial url that gets appended to location.host. Input is optional and can be null or empty string if not provided. If server-side processing code does not need input then use null or empty string. If you want to use a callback then specify successCallback accepting two parameters, one for the url and another for the retrieved model. The callback approach is more do-it-yourself and does not require that databinding be used. To use databinding pass true for shouldMap which will create a property of the ViewModel object having the same name (minus any "/" characters) passed as url. That property serves as a sub-View Model and is initialized to an object graph equivalent to the Model returned by the post request, but rather than consisting of basic types, objects and arrays it is comprised of observables and observableArrays suitable for binding. In order to use multiple of these "sub-View Models" , each can be associated with non-intersecting element hierarchies. When shouldMap is true  then ko.applyBindings(sub-viewmodel, element) is called internally after initializing the sub-View Model property of the ViewModel instance.

The caller is free to delete the sub-View Model property once it is no longer needed. ViewModel also extends jQuery with a serializeObject method that can be used to create a post input object from a jQuery selector that resolves to a single element or a list of elements. In the former case the object properties are derived from child elements and in the latter from the selected elements.


map(modelObject)

This method accepts a POJO and returns the quivalent object graph in a new object being comprised of observables and observableArrays rather than objects, arrays and simple types.

unMap(viewModelObject, modelObject)

The unMap method does the reverse of map, taking a viewModelObject consisting of observable and observableArray properties and an empty modelObject (i.e. {})that is initialized with the equivalent object graph with properties as objects, arrays and simple types. unMap returns the fully populated modelObject.


Continue to Client-Side Chaos - Part II : The View
       
 
 

No comments:

Post a Comment