Model-View-View Adapter-Controller with AtomicJS

So I’ve found it hard to accept the approaches taken by a lot of the MVVM and other MV* JavaScript frameworks and libraries that have come out over the last few years primarily due to the way that they mix logical directives and markup. This mixing of concerns is reminiscent of the very coding practices of mixing JavaScript inside of HTML that the industry fought hard to end during the early part of this century. Today we have frameworks that not only mix “binding” templates like Mustache or Handlebars but also that introduce new languages that provide flow, filter and execution directives inside of attributes within HTML elements. Instead of “onclick” we implement things like “ng-click”. While this initially seems like a simple approach, I worry that we are introducing maintainability headaches. And like most “new shinies” in JS, even these practices are being superseded by newer practices that are equally concerning. Now, instead of placing code in our markup, there are libraries like ReactJS and Imba that seek to place markup inside of code.

A few years ago, I was having a conversation with another developer, Cory House, about the mixing of concerns that occur when using these libraries and frameworks. I mentioned to him that I had been using a different technique that allow for the creation of views and controls, without directly using JQuery but still allowing for unobtrusive JavaScript. At the time I was using a framework that I had built exclusively for the company that I was working for. I have however since moved on and have now begun work on a completely new library that fully encapsulates the concepts that I had discussed with Cory and others back in 2012-2013.

Today, I would like to announce that I have begun development of a new MV* library named AtomicJS. This library provides an engine to build web applications based on a design pattern that I have been referring to as Model – View – View Adapter – Controller. In this pattern, the View is completely abstracted away from the Controller via the View Adapter. In the case of AtomicJS, the “View” can be built in any language/markup including HTML. The “View Adapter” is constructed from View Adapter Definitions written in plain JavaScript, usually as a single POJO, with definitions and initializers for the controls found within the “View Adapter” definition. The “Controller” is also written in JavaScript and the “Model” can be a simple JSON object or other POJO. Other supporting constructs such as Service Proxies and Observers are employed as desired and are generally written in JavaScript.

Model – View – View Adapter – Controller

The entire library is configurable using Dependency Injection and one or more composition roots. You can inject the view adapter support “engine” that provides the functional interfacing between the “View Adapters” built from the View Adapter definitions and the HTML DOM provided by a web browser, or you can inject a different engine to provide a different set rendering/interfacing adapter methods. Since all dependencies are injected, “mockist style” unit testing the components of the pattern is very simple.

Check out the following constructs from the current TodoMVC.com based demo for AtomicJS.

The following is the view adapter definition that defines the functional layout for the entire TodoMVC demo app:

!function()
{"use strict";root.define("todoMVC.appView",
function()
{return function todoMVCAppView(appViewAdapter)
{
    var adapterDefinition   =
    {
        controls:
        {
            newTodoTextbox:
            {
                onenter:
                function(
                {
                    if (this.value().trim() !== "")
                    {
                        appViewAdapter.on.addNewTodo(this.value().trim());
                    }
                    this.value("");
                }
            },
            todosView:
            {
                controls:
                {
                    toggleAllCompleted:
                    {
                        onchange:
                        function()
                        {
                            appViewAdapter.on.toggleAllCompleted(this.value());
                        }
                    },
                    todoList:
                    {
                        repeat:
                        {
                            todoListItemTemplate:
                            {
                                getKey:
                                function(item)
                                {
                                    return "todoListItem-"+item().id
                                },
                                controls:
                                {
                                    toggleCompletedCheckbox:
                                    {
                                        bindTo:     "completed",
                                        onchange:
                                        function()
                                        {
                                            appViewAdapter.on.saveTodo(this.boundItem());
                                        } 
                                    },
                                    todoLabel:
                                    {
                                        bindTo:     "todo",
                                        ondblclick:
                                        function()
                                        {
                                            this.boundItem.beginTransaction();
                                            this.parent.addClass("editing");
                                            this.parent.controls.editTodoTextbox.focus().select();
                                        } 
                                    },
                                    deleteTodoButton:
                                    {
                                        bindTo:     "id",
                                        onclick:
                                        function()
                                        {
                                            appViewAdapter.on.deleteTodo(this.boundItem().id);
                                        }
                                    },
                                    editTodoTextbox:
                                    {
                                        bindTo:     "todo",
                                        onenter:
                                        function()
                                        {
                                            this.value(this.value().trim());
                                            this.boundItem.commit();
                                            if (this.value() == "") appViewAdapter.on.deleteTodo(this.boundItem().id);
                                            else                    appViewAdapter.on.saveTodo(this.boundItem());
                                        },
                                        onescape:
                                        function()
                                        {
                                            this.boundItem.rollback();
                                            this.parent.removeClass("editing");
                                        },
                                        updateon:   ["change", "keyup"]
                                    }
                                },
                                onbind:
                                function(data)
                                {
                                    this.toggleClass("completed", data().completed);
                                },
                            }
                        }
                    }
                },
                hidden:     true,
                onbind:     function(data)
                {
                    var items           = data();
                    this.toggleDisplay(items.length>0);
                    var allCompleted    = true;
                    for(var itemCounter=0;itemCounter<items.length;itemCounter++)
                    {
                        allCompleted        = allCompleted && (items[itemCounter].completed||false);
                    }
                    this.controls.toggleAllCompleted.value(allCompleted);
                },
                onunbind:   function(data) { this.hide(); }
            },
            todosFooter:
            {
                controls:
                {
                    todosCountLabel:        { bindAs: function(todos){return todos().length;} },
                    todosCountDescription:  { bindAs: function(todos){return todos().length == 0 || todos().length > 1 ? " items left" : " item left";} },
                    allTodosLink:           { onclick: function(){appViewAdapter.attribute("filter", "none");} },
                    activeTodosLink:        { onclick: function(){appViewAdapter.attribute("filter", "active");} },
                    completedTodosLink:     { onclick: function(){appViewAdapter.attribute("filter", "completed");} },
                    deleteCompletedTodos:   { onclick: function(){appViewAdapter.on.deleteCompletedTodos();} },
                },
                hidden: true,
                onbind: function(data)
                {
                    this.toggleDisplay(data().length>0);
                }
            }
        },
        events:["addNewTodo", "deleteTodo", "saveTodo", "toggleAllCompleted", "deleteCompletedTodos"]
    };
    return adapterDefinition;
}});}();

The following is the TodoMVC app controller:

!function()
{
    root.define
    (
        "todoMVC.appController",
        function todoMVCAppController(appView, appProxy, observer)
        {
            // todosObserver is the model observer that wraps
            // the todo list "model"
            var todosObserver;
            function rebindTodoList(todos)
            {
                if (todosObserver === undefined)
                {
                    todosObserver   = new observer(todos);
                    appView.bindData(todosObserver);
                    return;
                }
                todosObserver("", todos);
            }
            appView.on.addNewTodo.listen
            (function(value)
            {
                appProxy.addTodo({todo: value}, rebindTodoList);
            });
            appView.on.deleteTodo.listen
            (function(value)
            {
                appProxy.deleteTodo(value, rebindTodoList);
            });
            appView.on.saveTodo.listen
            (function(todo)
            {
                appProxy.saveTodo(todo, rebindTodoList);
            });
            appView.on.toggleAllCompleted.listen
            (function(value)
            {
                appProxy.toggleAllTodos(value, rebindTodoList);
            });
            appView.on.deleteCompletedTodos.listen
            (function()
            {
                appProxy.deleteCompletedTodos(rebindTodoList);
            });
            this.launch =
            function()
            {
                appProxy.getTodos(rebindTodoList);
            }
        }
    );
}();

And finally, the following is the application composition root that assembles together the various components and launches the app:

!function()
{
    window.onload   =
    function ComposeApp()
    {
        var atomic  = root.atomic.htmlCompositionRoot();
        var app =
        new root.todoMVC.appController
        (
            atomic.viewAdapterFactory.create
            (
                new root.todoMVC.appView(), 
                document.querySelector("#todoMVCApp")
            ),
            new root.todoMVC.appProxy
            (
                window.localStorage, 
                root.utilities.removeFromArray
            ),
            atomic.observer
        );
        app.launch();
    };
}();

As you can see, there is no direct reference to the HTMLDOM outside of the composition root.  This enables a principled approach to building front end web applications without sacrificing modularity.

Check out the AtomicJS documentation here feel free to download the source code for AtomicJS from the AtomicStack project on Github.

Atomic Stack

So, I’ve been thinking about how I wanted to try to jump start my blog for a very long time.  The problem is, that for as much as I like technology, I’ve never really taken to the typed word.  I’ve always preferred to make a phone call rather than write up an email or send a text. For as fast as I can type, it seems that I can never really type fast enough to get my ideas recorded, except generally in perhaps the case of programming which is good, given that I’m a programmer.  But when it comes to free-flowing thoughts like those generally relayed via speech, I would definitely prefer to dictate than to actually type.  So, I’m going to attempt to use the Voice Memos iOS application as a way to stage the content for my blog.

Recently I decided to start an open source project, based on some opinions I had received at the St. Louis Days of .Net which echoed similar sentiments from the previous year’s conference.  You see, over the years I’ve been fortunate to have been tasked with solving some of the most difficult challenges faced by the various teams that I have been a part of.  I’ve also been fortunate to have worked with some very talented and skilled individuals on those teams.  Some I collaborated with and others I was literally schooled by.  I’ve somehow managed to hold on to the practices that have proven useful and advantageous in the various projects that I’ve worked on and assimilated them as recurring patterns.  I’ve described some of these patterns to certain individuals over the last few years, and nearly every time, I’ve been asked if any of it was embodied in an open source implementation.  The unfortunate answer has always been nothing that I’m involved with and nothing that I was aware of.

So the purpose of this open source project is to embody the set of tools that I will be implementing and leveraging during the course of the development of a personal closed source project of my own.  As such, requirements will flow from my personal project to the tool-set project.  The tool-set will be comprised of a stack of technologies that I will leverage to build an n-tier web application.  These technologies will be new implementations based upon the patterns that I have successfully leveraged over the years.  These will include things in the following list, which I plan to go into further detail of in future posts:

  • Configurable tier implementations across 8 tiers (data storage, data access/io, business enforcement, application services, service hosting, client side server access/io, client side controllers and client side views)
  • Three independent storage/transport schemas (data storage, entity model, and use case schemas)
  • Entity Model in memory indexing
  • Entity Model domain specific querying language built as a fluent api on top of .Net classes
  • Singleton/Multiton supporting services with lifetimes managed by an Abstract Factory implementation
  • Vertically integrated independent entity tiers relationally bound in the business tier with optimized distributed data access/io execution
  • Expansive configuration
  • Web server host abstraction (with IIS integration implementation, future implementations may include integration on top of OWIN)
  • Built in user account management and security authentication/authorization (including access challenge/negotiation)
  • Built in/expandable service hosting options (default web service implementation with multiple/expandable content negotiation options)
  • Subclassable Enums (and conversely enumerated subclasses, an excellent alternative to using switch statements)
  • Additional supporting data types
  • Advanced .net generics tricks (including subclass constraints/contexts and generic namespaces via nested classes)
  • General object based data storage services for supporting the practice of high fidelity functional prototyping
  • Interchangeable client side server proxies (supporting functional prototyping via an elaborate proxy implementation simulating future remote services)
  • Classical inheritance implementation on top of ECMAScript 5/JavaScript 1.8.5 with base class method call dispatching, public and protected access modifiers, instance and static scopes with constructors
  • Pure client side MVC solution implemented using JSON/HTML/JavaScript
  • and of course more…

The name of this set of tools is Atomic Stack.  The server side tiers (Atomic.Net) are being implemented in .Net using C# due its amazing generics support.  The client side tiers (AtomicWeb/AtomicJS) are being built upon HTML5/ECMAScript 5/CSS 3.  The project goals will including adherence to development practices including:

  • Separation of Concerns (including Unobtrusive JavaScript)
  • Clean Coding Principles
  • Tier/Down Design and Development (with wide client side development cycles with narrow vertical server side development sprints)

In addition I will be looking at employing additional practices not currently in use including the following:

  • Design by Contract (at least for application hosted services)
  • Test Driven Development

I’m sure that if anyone actually comes across this blog, that some may point out that there are a ton of frameworks and libraries out there.  They may question, do we really need yet another framework or library, much less a stack of them?  Frankly, I’m not sure.  I do know however that I have ideas and I would like to contribute those ideas in a tangible way to the community.  And due to my past experience, there is a certain degree of independent yet cohesiveness among these ideas which therefore are compelling me to attempt to start from scratch and create these new tools with minimal constraints solely upon the raw platforms that they are to be built upon (.Net, JavasScript, HTML, CSS, etc.).  Ofcourse I will very likely be incorporating additional dependencies upon things that are already well written and tested (like Mike Woodring’s DevelopMentor ThreadPool, HtmlAgilityPack and jQuery).  But it is very likely that I will avoid some tools whose implementations I find lacking (for example the MS Entity Framework).

Anyway, I droned on long enough and I’m not quite sure how to end this post.  If you are interested in checking out the project, please visit http://atomicstack.com.