未加星标

Second Wind for Backbone

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二05 | 时间 2016 | 作者 红领巾 ] 0人收藏点击收藏

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 inphp 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.


Second Wind for Backbone

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.


Second Wind for Backbone

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.


Second Wind for Backbone

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.


Second Wind for Backbone

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 [email protected]` 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

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

主题: HTMLHTML5ReactJavaScriptPHPJava
分页:12
转载请注明
本文标题:Second Wind for Backbone
本站链接:http://www.codesec.net/view/480919.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(35)