Client-Side MVC frameworks compared

A month ago Gordon L. Hempton wrote about twelve JavaScript frameworks in the Client-Side MVC space. His rating criteria were different to mine. One that really sticks out is that I like the logic not forcing the template HTML to migrate to <script> tags. Depending on the sophistication of the app, I like to be able to see the app in a browser or DreamWeaver when the framework is not running. It gives me a way of gauging the composition of the app. It appeals to a WYSIWYG leaning that I have. I like my UI frameworks to be built for designability if you like.

Addy Osmani has a number of implementation of a TODO app on a github pages site. For composition purposes, this really is the definitive place presently. Using those, I’m going to scrutinize the HTML and how it the app looks without JavaScript. I checked out Addy’s repo, then recursively deleted all javascript files, before loading the main page for each into a browser.


In the browser:

In DreamWeaver:

What I like is that I can see the repeating item in it’s mustache templating style: {{todo.content}}. What I don’t like is that I can’t see the “X item(s) left” message, that live updates. Angular leaves your template HTML in-situ – where it would be if there were no angular logic happening to it. Here’s the todo list in code (ng:repeat is the looping construct):

<div id="todos"> <ul id="todo-list"> <li class="todo" ng:class="'editing-' + todo.editing + ' done-' + todo.done" ng:repeat="todo in todos"> <div class="display"> <input class="check" type="checkbox" name="todo.done" / > <div ng:click="editTodo(todo)" class="todo-content"> {{ todo.content }} </div> <span class="todo-destroy" ng:click="removeTodo(todo)"></span> </div> <div class="edit"> <form ng:submit="finishEditing(todo)"> <input class="todo-input" my:focus="todo.editing" my:blur="finishEditing(todo)" name="todo.content" type="text"> </form> </div> </li> </ul> </div> 


Missing is the repeating TODOs. Instead, in the page, there is a <ul id="todo-list"></ul> placeholder for Backbone to insert child elements to later. Here’s the template, in canonical Backbone (hidden inside a <script> tag):

 <!-- Templates --> <script type="text/template" id="item-template"> <div class="todo <%= done ? 'done' : '' %>"> <div class="display"> <input class="check" type="checkbox" <%= done ? 'checked="checked"' : '' %> /> <label class="todo-content"><%= content %></label> <span class="todo-destroy"></span> </div> <div class="edit"> <input class="todo-input" type="text" value="<%= content %>" /> </div> </div> </script> 

There’s no ng:repeat equivalent code in the HTML, that’s done in JavaScript.


I’m not sure if the way the TODO app was coded with knockout is the only way, but the line that would show an individual TODO item from the list of TODOs looks like <div data-bind="text: content, event: { dblclick: edit }" style="cursor: pointer;"></div>. Without the frameworks attached, there’s nothing to see for that DIV. If I hard-code TODO-CONTENT in that DIV, and reload, then it looks more like Angular:

It is nice to see the inline templating rather than the template in a <script> tag like Angular:

<div id="todos"> <div data-bind="visible: todos().length"> <input id="check-all" class="check" type="checkbox" data-bind="checked: allCompleted" /> <label for="check-all">Mark all as complete</label> </div> <ul id="todo-list" data-bind="foreach: todos"> <li data-bind="css: { editing: editing }"> <div class="todo" data-bind="css: { done : done }"> <div class="display"> <input class="check" type="checkbox" data-bind="checked: done" /> <div class="todo-content" data-bind="text: content, event: { dblclick: edit }" style="cursor: pointer;"></div> <span class="todo-destroy" data-bind="click: $root.remove"></span> </div> <div class="edit"> <input class="todo-input" data-bind="value: content, valueUpdate: 'afterkeydown', enterKey: stopEditing, event: { blur: stopEditing }"/> </div> </div> </li> </ul> </div> 


Screenshot – as Backbone’s

Knockback is a very pragmatic merger of the best features of Backbone and Knockout. Unlike many of the websites for these technologies, Knockback’s site tries to instantly sell the technology to the developer. Like Backbone, Knockback keeps templates separate. Here’s their todo list: <ul data-bind="template: {name: 'item-template', foreach: todo_list.todos}"></ul>. Here is the template for that:

<script type="text/x-jquery-tmpl" id="item-template"> <li> <div class="todo" data-bind="css: {done: done, editing: edit_mode}"> <div class="display"> <input class="check" type="checkbox" data-bind="checked: done" /> <div class="todo-text" data-bind="text: text, dblclick: toggleEditMode"></div> <div class="todo-destroy" data-bind="click: destroyTodo"></div> </div> <div class="edit"> <input class="todo-input" type="text" data-bind="value: text, event: {keyup: onEnterEndEdit}" /> </div> </div> </li> </script> 


Screenshot – as Backbone’s

The template logic is in JavaScript files, which I really don’t like:

todo.templates= { list: '<ul>' + '{% for task in taskList %}' + genericTaskTemplate + '{% endfor %}' + '</ul>' ,view: genericTaskTemplate ,create: '' ,update: '<li data-app_label="{{ task.__class__._meta.appLabel }}" data-model="{{ task.__class__._meta.modelName }}" data-pk="{{ }}">'+ '<form action="#/task/update/{{ }}/">' + '<input type="text" name="title" value="{{ task.title }}" />' + '</form>' + '</li>' }; 


Screenshot – as Backbone’s

The template logic is in separate .template file, which seems unnecessary given it’s just an extended HTML format.

<h2 data-type="list" data-id="<%= %>"></h2> <% $.each(todos, function(index, todo) { %> <li data-type="todo" data-id="<%= %>" class="<%= todo.done ? 'done' : '' %>"> <div class="todo"> <div class="display"> <input class="check" type="checkbox" <%= todo.done ? 'checked' : '' %>/> <span class="trashcan" data-type="todo" data-id="<%= %>"></span> <span contenteditable="true" data-type="todo" data-id="<%= %>" class="todo-item"><%= %></span> </div> </div> </li> <% }); %> 


<script type="text/x-handlebars"> {{#view id="todos"}} {{#collection id="todo-list" contentBinding="Todos.todosController" tagName="ul" itemClassBinding="content.isDone"}} {{view Ember.Checkbox titleBinding="content.title" valueBinding="content.isDone"}} {{/collection}} {{/view}} </script> 

You should know by now that I’m really going to like Ember’s templating. From my point of view this one is in last position!


Screenshot – as Backbone’s

The template logic is in JavaScript files. ExtJs is not so much HTML augmented with Client-Side MVC capability like the rest are. It’s more of a departure from HTML and a reality built on it’s own DSL which can be highly attractive in its own right. I’m only including it because Addy has it on his list. In this case, the ExtJS grammar is showing inlined HTML in the pertinent list of TODOs section:

Ext.define('Todo.view.TaskList' , { store: 'Tasks', loadMask: false, itemSelector: 'div.row', extend: 'Ext.view.View', alias : 'widget.taskList', tpl: Ext.create('Ext.XTemplate', '<tpl for=".">', '<div class="row">', '<input type="checkbox" {[values.checked ? "checked" : ""]} />', '<span class="{[values.checked ? "checked" : ""]}">{label}</span>', '</div>', '</tpl>', {compiled: true} ) }); 


Screenshot – as Backbone’s

<script type="text/template" id="item-template"> <li class="todo {!= todo.done ? 'done' : '' !}" data-todoid="{!= todo.guid !}"> <div class="display"> <input class="check" type="checkbox" {!= todo.done ? 'checked="checked"' : '' !} /> <div class="todo-content">{!= !}</div> <span data-action="destroyTodo" class="todo-destroy"></span> </div> <div class="edit"> <input class="todo-input" type="text" value="" /> </div> </li> </script> 

Like Backbone, or this example, there’s no ng:repeat equivalent code in the HTML. It is done in JavaScript instead.


Screenshot – like Backbone’s. Template nesting (inside <script> tags) looks powerful:

<script type='text/ejs' id='todosEJS'> <% for(var i =0; i < this.length ; i++){ %> <li <%= this[i]%>> <%= $.View('todoEJS',this[i] ) %> </li> <% } %> </script> <script type='text/ejs' id='todoEJS'> <input type='checkbox' name='complete' <%= this.complete ? "checked" : "" %>> <span class=''><%= (this.text || "empty todo ...")%></span> <span class='destroy'></span> </script> 

JQuery with Handlebars

Screenshot – like Backbone’s. Separate template again:

<script type="text/x-handlebars-template" id="todo-template"> {{#this}} <li {{#if done}}class="done"{{/if}} data-id="{{id}}"> <div class="view"> <input class="toggle" type="checkbox" {{#if done}}checked{{/if}}> <label>{{title}}</label> <a class="destroy"></a> </div> <input class="edit" type="text" value="{{title}}"> </li> {{/this}} </script> 


Screenshot – as Backbone’s. Template again leverages <script> again but is inline with the HTML that contains it. Looping is not in the grammar though, so is performed by JavaScript as the template is acted on.

<div class="items"> <script type="text/html" id="task-template"> <div class="item {{if done}}done{{/if}}"> <div class="view" title="Double click to edit..."> <input type="checkbox" {{if done}}checked="checked"{{/if}}> <span>${name}</span> <a></a> </div> <form class="edit"> <input type="text" name="name" value="${name}"> </form> </div> </script> </div> 

YUI Library

Screenshot as Backbone’s. As is the norm the template is in a &lt;script> tag, with the looping in Javascript.

<script type="text/x-template" id="todo-item-template"> <div class="todo-view"> <input type="checkbox" class="todo-checkbox" {checked}> <span class="todo-content" tabindex="0">{text}</span> </div> <div class="todo-edit"> <input type="text" class="todo-input" value="{text}"> </div> <a href="#" class="todo-remove" title="Remove this task"> <span class="todo-remove-icon"></span> </a> </script> 


(added 14 Feb)

Batman’s TODO is in it’s own Github Repo, and not Addy’s one (yet). Batman uses CoffeeScript instead of JavaScript, which is alluring in its own right. Here’s the screenshot:

Like Knockout, the bound field is not visible in design mode. Similarly, the number of items statistic is also missing. Lastly the <a> tag for ‘delete’ is missing a and thus does not appear as a link. The other frameworks were using fancy images for this, so that is probably the same for them too, but not apparent in their demos. I’ve hacked those things in (hard-coded) and taken another screenshot for your enjoyment:

The good news (to me) is that the Batman extends HTML like Angular and Knockout.


Only Angular, Knockout and Batman allow me to see what’s what in design mode, and see the mechanism of looping in the extended HTML grammar. That’s the opposite of one of Gordon L. Hempton’s “good” criteria.

It is also interesting that one of these has Microsoft patronage, and another has Google support. I wonder if there’s a Dart port “Dangular” in the future from the Google fellows 😛

After thought.

Angular’s TODO list implementation could be even better in design mode, if it encoded stats like so:

<div id="todo-stats"> <span class="todo-count" ng:show="hasTodos()"> {{statsCount()}} item{{statsPlural()}} left. </span> <span class="todo-clear" ng:show="hasFinishedTodos()"> <a ng:click="clearCompletedItems()"> Clear {{finishedTodos()}} completed item{{finsihedPlural()}} </a> </span> </div> 

Instead of the way it is now:

<div id="todo-stats"> <span class="todo-count" ng:show="hasTodos()"> <ng:pluralize count="remainingTodos()" when="{'0' : 'No items left.', '1': '1 item left.', 'other' : '{} items left.' }"> </ng:pluralize> </span> <span class="todo-clear" ng:show="hasFinishedTodos()"> <a ng:click="clearCompletedItems()"> Clear <ng:pluralize count="finishedTodos()" when="{'1': '1 completed item', 'other' : '{} completed items' }"></ng:pluralize> </a> </span> </div> 

We’d get a design mode view like so:

此条目发表在ASP.NET, MVC, UI分类目录。将固定链接加入收藏夹。


Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s