Статьи

Сравнение платформ MVC на стороне клиента

Месяц назад Гордон Л. Хэмптон написал около двенадцати фреймворков JavaScript в пространстве MVC на стороне клиента . Его рейтинговые критерии отличались от моих. Что действительно выделяется, так это то, что мне нравится логика, не заставляющая шаблон HTML мигрировать в теги <script>. В зависимости от сложности приложения, мне нравится иметь возможность видеть приложение в браузере или DreamWeaver, когда среда не запущена . Это дает мне возможность оценить состав приложения. Это обращается к WYSIWYG склонности, которую я имею. Мне нравится, когда мои структуры пользовательского интерфейса создаются для удобства проектирования, если хотите.

У Адди Османи есть ряд реализаций приложения TODO на сайте github . Для композиционных целей это действительно определенное место в настоящее время. Используя их, я собираюсь изучить HTML и то, как приложение выглядит без JavaScript. Я проверил репозиторий Addy, затем рекурсивно удалил все файлы javascript перед загрузкой главной страницы каждого из них в браузер.

угловатый

В браузере:

В DreamWeaver:

Что мне нравится, так это то, что я вижу повторяющийся элемент в стиле шаблонов усов: {{todo.content}} . Что мне не нравится, так это то, что я не вижу сообщения «X item left (s) left», которые обновляются в реальном времени. Angular оставляет ваш шаблон HTML на месте — там, где он был бы, если бы с ним не было угловой логики. Вот список задач в коде (ng: repeat — это циклическая конструкция):

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

позвоночник

Отсутствует повторяющиеся ТОДО. Вместо этого на странице есть заполнитель <ul id = «todo-list»> </ ul> для Backbone, в который потом можно вставить дочерние элементы. Вот шаблон в каноническом Backbone (скрытый внутри тега <script>):

<!-- 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>

Там нет нг: повторить эквивалентный код в HTML , это сделано в JavaScript.

Нокаутировать

Я не уверен, что способ, которым приложение TODO было закодировано с помощью нокаута, является единственным способом, но строка, которая будет отображать отдельный элемент TODO из списка TODO, выглядит как <div class = «todo-content» data-bind = «text: content, event: {dblclick: edit}» style = «курсор: указатель;»> </ div>. Без прикрепленных фреймворков для этого DIV ничего не видно . Если я жестко закодирую TODO-CONTENT в этом DIV и перезагружаю, то это больше похоже на Angular:

Приятно видеть встроенный шаблонизатор, а не шаблон в теге <script>, таком как 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>

Knockback

Скриншот — как Backbone’s

Knockback — это очень прагматичное слияние лучших функций Backbone и Knockout. В отличие от многих сайтов, посвященных этим технологиям, сайт Knockback пытается мгновенно продать эту технологию разработчику. Как и Backbone, Knockback хранит шаблоны отдельно. Вот их список задач: <ul class = «список задач» data-bind = «template: {name: ‘item-template’, foreach: todo_list.todos}»> </ ul>. Вот шаблон для этого:

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

Сломался

Скриншот — как Backbone’s

Логика шаблона находится в файлах JavaScript, которые мне действительно не нравятся:

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

Сэмми

Скриншот — как Backbone’s

Логика шаблона находится в отдельном файле .template, который кажется ненужным, учитывая, что это просто расширенный формат HTML .

<h2 data-type="list" data-id="<%= list.id %>"></h2>
<% $.each(todos, function(index, todo) { %>
  <li data-type="todo" data-id="<%= todo.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="<%= todo.id %>"></span>
        <span contenteditable="true" data-type="todo" data-id="<%= todo.id %>" class="todo-item"><%= todo.name %></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>

Тебе уже следует знать, что мне действительно нравятся шаблоны Эмбер. С моей точки зрения, это последняя позиция!

ExtJs

Скриншот — как Backbone’s

Логика шаблона находится в файлах JavaScript. ExtJs не так уж и дополнен HTMLвозможностями на стороне клиента MVC , как остальные. Это скорее отступление от HTML и реальность, построенная на его собственном DSL, который сам по себе может быть очень привлекательным. Я включил его только потому, что у Адди есть в его списке. В этом случае грамматика ExtJS показывает встроенный HTML в соответствующем списке раздела TODO:

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}
    )
});

Фидель

Скриншот — как 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">{!= todo.name !}</div>
      <span data-action="destroyTodo" class="todo-destroy"></span>
    </div>
    <div class="edit">
      <input class="todo-input" type="text" value="" />
    </div>
  </li>
</script>

Как и в Backbone или в этом примере, в HTML отсутствует эквивалентный код ng: repeat . Вместо этого это делается в JavaScript.

JavaScriptMVC

Скриншот — как в Backbone’s. Вложение шаблона (внутри тегов <script>) выглядит мощно:

<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 с рулем

Скриншот — как в Backbone’s. Отдельный шаблон снова:

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

корешок

Скриншот — как Backbone’s. Шаблон снова использует <script> снова, но встраивается в HTML, который его содержит. Циклы не входят в грамматику, поэтому они выполняются JavaScript при работе с шаблоном.

<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 class="destroy"></a>
      </div>

      <form class="edit">
        <input type="text" name="name" value="${name}">
      </form>
    </div>
  </script>
</div>

Библиотека YUI

Скриншот как Backbone’s. Как правило, шаблон находится в теге & lt; script> с зацикливанием в 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>

Бэтмен

(добавлено 14 февраля)

TODO Бэтмена находится в его собственном Github Repo , а не в Addy (пока). Бэтмен использует CoffeeScript вместо JavaScript, что само по себе привлекательно. Вот скриншот:

Как и Knockout, связанное поле не отображается в режиме разработки. Точно так же отсутствует статистика количества предметов. Наконец, тег <a> для «delete» отсутствует a и поэтому не отображается как ссылка. Другие фреймворки использовали для этого причудливые изображения, так что, вероятно, то же самое и для них, но не видно в их демонстрациях. Я взломал эти вещи (жестко запрограммирован) и сделал еще один скриншот для вашего удовольствия:

Хорошая новость (для меня) заключается в том, что Бэтмен расширяет HTML, как Angular и Knockout.

Вывод.

Только Angular, Knockout и Batman позволяют мне видеть, что к чему в режиме дизайна, и видеть механизм зацикливания в расширенной грамматике HTML . Это противоположно одному из «хороших» критериев Гордона Л. Хэмптона.

Также интересно, что у одного из них есть покровительство Microsoft, а у другого — поддержка Google. Интересно, есть ли в будущем порт Dart «Dangular» от сотрудников Google ?

После мысли.

Реализация списка TODO в Angular могла бы быть еще лучше в режиме разработки, если бы он кодировал статистику следующим образом:

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

Вместо того, как сейчас:

<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: