Second Wind for Backbone

I've been working with Backbone for years and it suited me pretty well. I do love its conciseness. It gives the desired abstraction and yet leaves you at the full control of your app. When you need to know what exactly is happening in your app behind your code, it's matter of minutes to go though the Backbone annotated sources and figure out the flow. That's something you hardly can afford with other frameworks such as Angular, React, Vue or Amber. Besides it's ridiculously small - it's less than 8Kb without dependencies where the dependencies can be dropped if you go with Exoskeleton and Backbone.NativeView. I know that few today care about download size, but I do as it still affects the user response time.

What I do not like about Backbone that it provides no data binding out of the box. We have to do it manually, repeating ourselves with every single view, resulting in verbose and clumsy app code. Also Backbone has no embedded template engine. View simply binds to existing node or to immediately created one. By default it doesn't render a component, but we can populate the bounding node manually. Canonically we use text-based template engines like mustache.js, Handlebars or _.template. So every view rendering means obtaining of new HTML text and replacing view content with it. Actually we kill the current DOM-subtree and replace it with a new one. This is a problem as we lose this way internal node references and DOM event handlers. Fortunately Backbone can delegate DOM events to the bounding node, which doesn't get destroyed during renderings. In particular the bindings specified in `this.events` property still work after rendering. So we are good until it comes to web forms. After rendering we lose the form state. Just imagine a typical task. You have a form where user input is validated on typing. The form shows an error message under the field with every typed character unless the filed value is valid. Here you either go with a separate micro-template per a message container or do it without any template at all. I think that despite the unbeatable performance of text-based template engines they are a wrong choice for the Web. Unsubscribing listeners, removing DOM-subtree, injection a new one, re-subscribing on every state change seems too me as overkill. Just compare it to a DOM-based template, which doesn't kill anything, but updates properties of the target node when the associated data changes.

One of the greatest things about Backbone - it is open for extension. You can find tons of plugins and extensions on Backbone wiki. When it comes to data binding many of extensions (e.g. Backbone.Declarative, Backbone.BindTo, backbone-dom-view, Backbone.EasyBind) provide helpers to automate binding:

Backbone.declarative

var TodoView = Backbone.View.extend({
  events: {
    "click .toggle"   : "toggleDone",
    "dblclick .view"  : "edit",
    //...
  },
  modelEvents: {
    'change': 'render',
    'destroy': 'remove',
    //...
  }
  //...
});

As you can see the suggested solutions are more about a declarative way (like Backbone events hash) to bind model/collection. But what if view has multiple data sources? E.g. it's a form where there is an input, which datalist is populated from one collection and a select, which options pulled in by another collection. But what if the requirements to validate the form on user input? Remember, we deal still with a text based template.

Superset Epoxy.js provides more sophisticated approach. They allow to bind multiple sources and take advantages of inline binding:

<div id="app-han" class="demo">
  <label>First:</label>
  <input type="text" data-bind="value:firstName,events:['keyup']">

  <label>Last:</label>
  <input type="text" data-bind="value:lastName,events:['keyup']">

  <b>Full Name:</b>
  <span data-bind="text:firstName"></span>
  <span data-bind="text:lastName"></span>
</div>

Apparently they solve template state problem by micro-templating:

<div id="my-view" data-bind="template:['firstName','lastName']">
   <template><%= firstName %> <%= lastName %></template>
</div>

It looks like a working solution, but what would rather go still with a DOM-based template like Angular. And here a Backbone-extension ngBackbone that I would like to talk about. It extends Backbone.View with an abstract View, which enables ngTemplate-powered binding. It brings also a further abstraction level FormView that unlocks HTML5 Form API in the template. And it's written in TypeScript...

As for me TypeScript is the next big thing about JavaScript. It feels like shift to version 5 in PHP world a decade ago that brought abstract classes, interfaces and members visibility. Besides Type Script is a pretty good ES.Next transpiler. So if you're not familiar with the language I would would encourage you to get started with it. Especially while Backbone turns out to be quite friendly with TypeScript.

Components

Well, how does ngBackbone work? Like in Angular or in ReactJs we operate with components. So we can start with a simple HTML:

<html>
  <head>
    <title>Hello world!</title>
    <meta charset="utf-8">

  </head>
  <body>
    <ng-hello>Loading ...</ng-hello>
    
    <!-- Backbone -->
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
  </body>
</html>

where we define a component ng-hello with a placeholder content. As the very first example we describe the component with a very simple view:

import { Component, View } from "ng-backbone";

@Component({
  el: "ng-hello",
  template: `Hello World!`
})

class HelloView extends View {
}

let hello = new HelloView();
hello.render();

As you see, very like in Angular 2 we use a decorator @Component for the view declarations. We bind the view to the elements ng-hello and make it generating the content from the provided template string. When running this page we get `Hello World!` content rendered within out bounding element.

As it expected from components we can easily create a tree of them by attaching components one to another:

Component Foo.ts

import { Component, View } from "ng-backbone";

@Component({
  el: "ng-foo",
  template: `Hello, it's Foo`
})

export class FooView extends View {
  initialize(){
    this.render();
  }
}

Component App.ts

import { Component, View } from "ng-backbone";
import { FooView } from "./foo";


@Component({
  el: "ng-hello",
  views: {
    foo: FooView,
    bar: BarView
  },
  template: `Hello, it's App, <ng-foo></ng-foo>`
})

class AppView extends View {
  initialize(){
    this.render();
  }
}

new AppView();

You can find more about communication between components, lazy component initialization, injectable options and shared state in the documentation.

Data-binding

ngBackbone advocates a very simple idea of multi-source bounding. The we can use models and collections options that contain maps of named data sources. For example we can bind a model under scope name hero as follows:

import { Component, View, Model } from "ng-backbone";

@Component({
  el: "ng-hello",
  models: {
    hero: new Model({ name: "Superman" })
  },
  template: `<p><b data-ng-text="hero.name"></b> says:</p>
  <h1>Hello World!</h1>
  `
})

class HelloView extends View {
 initialize(){
    this.render();
 }
}

new HelloView();

This makes the model available within the template. So we can access its properties with ngTemplate directives as hero.propertyName. ngTemplate supports a number of directives where `ng-text` simply replaces the node text with actual state of the bound data.

Let's see what happens if we dynamically change the model:

import { Component, View, Model } from "ng-backbone";

@Component({
  el: "ng-hello",
  models: {
    hero: new Model({ name: "Superman" })
  },
  events: {
    "input input": "onInput"
  },
  template: `<p>
  <label>Who is the hero? <input /></label>
  <b data-ng-text="hero.name"></b><p> 
  `
})
class HelloView extends View {  
 onInput( e ) {
   let el = e.target as HTMLInputElement; 
   this.hero.set( "name", el.value );
 }
 initialize(){
    this.hero = this.models.get( "hero" );
    this.render();
 }
}

new HelloView();

With every typed in character the view changes the model name property and it triggers the template to synchronize, so we get the latest value displayed.

Note that this.models is an instance of Map and therefore we can access a concrete model as this.models.get( "hero" ).

What about collections? The principle is pretty much the same:

import { Component, View, Model, Collection } from "ng-backbone";

@Component({
  el: "ng-hello",
  collections: {
    worlds: new Collection([
      new Model({ name: "Post-Crisis Earth" }),
      new Model({ name: "Red Son" }),
      new Model({ name: "The Fourth World" }),
      new Model({ name: "The Dakotaverse" })
    ])
  },
  template: `<h1 data-ng-for="let world of worlds">
    Hello <i data-ng-text="world.name"></i>!
  </h1>
  `
})

class HelloView extends View {
  initialize(){  
    this.render();
 }
}

new HelloView();

We declare a named collection worlds and address it from `ng-for` template directive. Similar to models we can access the collection within view body as this.collections.get( "worlds" ).

Web-Forms

With the plain Backbone we usually manually bind form inputs to a model and run model's validate method by saving or setting the model. It's a minimalistic and very limited approach. That's why there are so many extensions like

backbone-forms, Backbone.Validation, Backbone.Validator, which suggest a better way to bind the model and enhance the validation API.

ngBackbone doesn't implement own validation functionality, instead it relies on HTML5 Form API. It

exposes a module FormView that automatically creates internal model for any view group marked with `ng-group` directive and synchronizes it the element ValidityStates. Also one state model gets created for the group the represents accumulative states like valid, dirty and validationMessage.

Let's consider the following example:

@Component({
  el: "ng-hello",
  template: `<form data-ng-group="hello">
    <input name="name" placeholder="Name..." required />
    <p data-ng-if="hello.group.dirty" data-ng-text="hello.name.validationMessage"></p>
  </form>
  `
})

We have here a component, which builds a form named `hello`. It contains an input field set as required by following HTML5 notation. Under the field we have a container that shows up only after we've touched the input (started typing) and it displays a validation error for this filed if any available. Note how we access the field state. We start with declared group name `hello`, then goes input name `name` and it ends with state property `validationMessage` (hello.name.validationMessage). You can find all the available state properties in the documentation, but in fact those are properties of HTML5 ValidityState.

If we alternate the example:

@Component({
  el: "ng-hello",
  template: `<form data-ng-group="account">
    <input name="email" type="email" placeholder="Email..." />
    <p data-ng-text="account.email.validationMessage"></p>
  </form>
  `
})

The component will show "Please enter valid email address" error until a valid email is given to the input.

The message can be easily customized:

@Component({
  el: "ng-hello",
  template: `<form data-ng-group="account">
    <input name="email" type="email" placeholder="Email..." />
    <p data-ng-if="account.email.typeMismatch">
      Bitte geben Sie eine gültige E-Mail-Adresse ein
    </p>
  </form>
  `
})

Well, but what about validation beyond HTML5 Form API? With formValidators option we can declare our custom named validators and address them afterwords by using template directive `ng-validate` e.g.:

@Component({
  el: "ng-hello",
  formValidators: {
    hexcolor( value: string ): Promise<void> {
        let pattern = /^#(?:[0-9a-f]{3}){1,2}$/i;
        if ( pattern.test( value  ) ) {
          return Promise.resolve();
        }
        return Promise.reject( "Please enter a valid hexcolor e.g. #EEEAAA" );
      }
  },
  template: `
    <form data-ng-group="hello">
      <input name="color" data-ng-validate="hexcolor" placeholder="Enter a color hex-code..." />
      <p data-ng-if="!account.color.valid"  data-ng-text="account.color.validationMessage"></p>
    </form>
`
})

So we just need to declare a validator with a callback that returns a Promise, resolved when the conditions are met and rejected with validation message content otherwise. This way we can also target multiple validators in a form field:

<input data-ng-validate="foo, bar, baz" />

This promisable validation API makes it easy to validate remotely with asynchronous XHR calls. For example, if we want to validate on-typing a form field against the list of existing on the server emails we can do it like that:

import { Component, FormView, FormValidators, Debounce } from "ng-backbone";

class CustomValidators extends FormValidators {
   @Debounce( 350 )
   name( value: string ): Promise<void> {
      return NamerModel.fetch();
   }
}

@Component({
  el: "ng-hello",
  formValidators: CustomValidators,
  template: `
    <form data-ng-group="hello" novalidate>
      <input name="name" data-ng-validate="name" placeholder="Enter a name..." />
    </form>
`
})

Here we pass custom validator to the component as a class extending built-in one FormValidators. This way we can apply `@Debounce` validator, stating that the validator `name` is called no more then once per 350ms regarding of user typing speed.

Recap

ngBackbone enables Angular-like programming experience by using DOM-based templates and component-driven approach. It separates declarative and imperative view codes by using @Component decorator. It synchronizes HTML5 Form API validity state to component controls and group internal state models available in the template. ngBackbone allows to specify custom asynchronous validators and apply them to form controls declarative way. Eventually it encourages fluent TypeScript programming experience in Backbone.



ngBackbone is a small extension of Backbone.js that unlocks Angular-like programming experience