view.json

A decoupled, framework-agnostic minimal view layer

Motivation

There are several full stack single page app frameworks which implement a view layer. However, those view layers are tightly coupled with their specific framework and are hard to extend, hack and reuse without pulling in the whole framework.

view.json is a small (~400 LoC runtime), decoupled, framework-agnostic view layer which:

  • accepts input in as a JSON data structure and

  • outputs human-readable view classes which

  • support nested child templates, element-granular updates via change events from a model layer and attaching/detaching DOM events

Note:

Sorry, view.json is not public yet. While the concepts and parts of the system have been defined, the API interfaces are frankly not good enough yet.


Goals

  1. Unobtrusive and simple. The view layer should not make decisions about the rest of the application. How I load my data, how I modularize my app etc. are not decisions that the view layer should impose.

  2. Plain old JS output. I don't want to jump through framework-specific hoops. View.json does not reinvent inheritance (it uses plain old ES3 prototypes); it does not force you to use a particular model layer or force you to put things in particular places in the global scope.

  3. DOM and framework independent and testable. Tests can be written without the DOM and views can be rendered and interacted with on the server-side with Node. Want to make your views search-engine indexable? You can do that. Do not depend on any external libraries (even jQuery is optional) and avoid being specific to any framework (if you can emit events, you can work with view.json)

  4. Easy to hack on. Keep the runtime small and low-level; allow others to add sugar on top.

    High level syntax is implemented in terms of a few low level operations. By accepting JSON directly, improving the user-facing syntax becomes a matter of converting some nicer format into JSON (rather than having to implement a full view layer).

  5. Modern. Support bindings and nested views and other features to make direct DOM manipulation unnecessary. I don't want to write trivial HTML and event binding code, but I do want a mechanism that is simple enough that I can override it when I want to. Allows you to write single page apps and multiple page apps with the same tools.

List of features

Despite its small size, view.json provides a modern view layer, with:

  • declarative data bindings between models and HTML/CSS
  • nested view definition
  • lists of child views (collection views) and conditional view rendering

view.json aims to eliminate the tedious parts of building single-page apps. Instead of writing DOM manipulation code, you describe views in a declarative format, and they are translated into view objects, which are plain-old JS objects that can be rendered into the DOM. Views are bound to models and collections, either on the fly, or as part of the view definition.

view.json handles view instantiation, event binding and deals with child views and conditional views.

It allows you to have nested views. When the top level view is rendered, all the child views are automatically rendered as well.

At the same time, you lose very little control. view.json outputs real objects with ES3-compatible prototypes, not some sort of metadata. It eliminates tedium without introducing magic.

Since the output is just view objects, you can easily extend the generated views by extending their prototype:

ItemView.prototype.toggle = function() {
  this.model.set('done', !this.model.get('done'));
  window.events.emit('model.done');
};

Overview

It is important to understand where view.json stands in the stack. It is the lowest level layer, which generates object classes that represent your application's views.

[  Friendly markup - to JSON  ] (markup-to-JSON converter)
            VVVVV
[     JSON - to View object   ] (view.json)
            VVVVV
[          View object        ] (Javascript code)

To start using view.json, read the [Getting Started guide].

The internals are also somewhat well documented. view.json internally consists of four different parts:

  • the input format (JSON). view.json defines two input formats:

    Normally, you write using the high level markup. Since the high level markup can be reduced into a smaller set of basic operations in the low level markup, you can easily define your own high level syntax, or add more syntactic sugar if you want to (without reinventing the rest of the view layer).

  • the compiler. The compiler and the associated command line tool produces code from JSON. It has two stages. First, the high level markup is translated into low level markup. Then, the low level markup is translated into either view objects (inside Node), or Javascript code.
  • the shim. The shim encapsulates the external interactions of the view layer, including the DOM and model event handling. The shim is an important part of view.json. It isolates the external interactions (with the DOM, and with the model layer). Think of it as a translator, which allows the same code to run in both Node and in the browser. Different shims are used in different environments:

    • the jQuery shim is used to translate DOM operations into jQuery calls
    • the DOM shim uses the DOM APIs directly, making it unnecessary to have jQuery on the page
    • the Node shim emulates a DOM inside Node.js, making it possible to render and interact with views without a real DOM
    • the Debug shim collects statistics about DOM operations and can be used to help understand what is going, and for capturing DOM interactions for view tests

    Observables and scopes In order to interact with a model layer (like Backbone), you override part of the shim so that event registrations are made on the right models.

  • the runtime. The runtime is a set of prototype objects for views containing shared code. You don't need to touch it in normal usage, but all views extend the prototypes it defines.