"use strict";
var Todos = Class({
'extends': MK.Array,
todos.js - самый большой файл в этом проекте, который содержит большую часть логики. В проектах большего объема лучше разделать приложения на мелкие составляющие.
"use strict";
var Todos = Class({
'extends': MK.Array,
Model определяет класс элементов, которые будут входить в коллекцию. В данном слуае, элементами коллекции будут экземпляры класса Todo
Model: Todo,
Определяя свойство itemRenderer вы создаете рендерер для каждого добавленного элемента массива. В данном случае, шаблон содержится в элементе с id=todo_item_template
(см. HTML код).
itemRenderer: '#todo_item_template',
constructor: function() {
var self = this
Добавляем зависимость свойства "leftLength"
от свойств "length"
и "completedLength"
, и используем их разность в качестве значения. Приложение “слушает” изменения в этих свойствах, вычисляя "leftLength"
при каждом их изменении.
.linkProps('leftLength', 'length completedLength', function(length, completedLength) {
return length - completedLength;
})
Метод "bindings"
добавляет привязки между свойствами экземпляра класса и DOM элементами. Метод "events"
, как можно догадаться, добавляет обработчики событий. Эти имена методов не являются специальными, они группируют разные действия для чистоты кода. После их вызова, вынимаем данные из локального хранилища и создаем из него элементы todo с помощью метода recreate. Затем, инициализируем роутер.
.bindings()
.events()
.recreate(JSON.parse(localStorage['todos-matreshka'] || '[]'))
.initRouter('/state/');
},
bindings: function() {
var binders = MK.binders;
return this
Объявляем песочницу
.bindNode('sandbox', '.todoapp')
Привязываем несколько других элементов (main, footer и т. д.). Селектор :sandbox
не используется потому что мы обращаемся к элементам по ID.
.bindNode({
main: '.main',
footer: '.footer',
newTodo: '.new-todo',
container: '.todo-list',
allCompleted: '.toggle-all',
clearCompleted: '.clear-completed'
})
Следующий вызов bindNode делает видимость элементов зависимым от значений соответствующих свойств (если значение проходит не-строгую проверку на равенство true
, элемент будет показан, иначе - спрятан).
.bindNode({
completedLength: ':bound(clearCompleted)',
length: ':bound(main), :bound(footer)'
}, binders.visibility())
Следующие две привязки меняют HTML привязанных элементов в зависимости от значения соответствующего свойства.
.bindNode('completedLength', ':bound(clearCompleted) .completed-length', binders.html())
.bindNode('leftLength', '.todo-count', {
setValue: function(v) {
$(this).html('<strong>' + v + '</strong> item' + (v !== 1 ? 's' : '') + ' left');
}
})
Эта привязка контролирует, какая именно ссылка (“All”, “Active”, “Completed”) будет выделена жирным шрифтом. Здесь использован небольшой приём для демонстрации работы bindNode
: элемент #filters
связываем со свойством "state"
, но в привязчике манипулируем ссылками внутри этого элемента.
.bindNode('state', '.filters', {
setValue: function(v) {
$(this).find('a').each(function() {
var $this = $(this);
$this.toggleClass('selected', $this.attr('href') === '#!/' + v);
});
}
});
},
events: function() {
return this
Добавляем обработчик события на изменение свойства "JSON"
, которое хранит представление списка todo в виде JSON строки. Для того, чтоб реже обращаться к жесткому диску (который работает медленнее, чем оперативная память), используется метод onDebounce, который предотвращает многократный вызов обработчика за промежуток времени.
.onDebounce('change:JSON', function(evt) {
localStorage['todos-matreshka'] = evt.value;
})
Если в инпуте, привязанном к свойству "newTodo"
нажата клавиша Enter
и если очищенное от пробелов значение этого свойства не является пустой строкой, добавляем новый пункт todo, используя метод push
).
.on('keyup::newTodo', function(evt) {
var newTodo;
if (evt.which === ENTER_KEY) {
if (newTodo = this.newTodo.trim()) {
this.push({
title: newTodo
});
}
this.newTodo = '';
}
})
Когда меняется значение свойства allCompleted
, мы меняем "completed"
для всех todo на то же самое значение. Флаг "silent"
говорит о том, что событие "change:completed"
не должно быть вызвано.
.on('change:allCompleted', function(evt) {
this.forEach(function(todo) {
todo.set('completed', evt.value, {
silent: true
});
});
this.completedLength = evt.value ? this.length : 0;
})
.on('click::clearCompleted', function() {
for (var i = 0; i < this.length; i++) {
if (this[i].completed) {
this.pull(this[i--]);
}
}
})
Следующий обработчик вызывается по двум событиям. Первое событие - "modify"
, которое срабатывает, когда MK.Array
меняется (когда элементы добавляются или удаляются). Второе - *@change:completed
означает, что мы слушаем событие "change:completed"
для каждого пункта todo. Получается, обработчик срабатывает, когда пункт добавлен или удален и когда у одного из пунктов меняется свойство "completed"
. Код обработчика говорит сам за себя: "allCompleted"
становится равным true
, если каждый пункт выполнен и наоборот - false
, когда какой-либо из пунктов не выполнен. Затем вычисляется значение свойства "completedLength"
, которое содержит количество выполненных пунктов.
.on('modify *@change:completed', function() {
this.set('allCompleted', this.every(function(todo) {
return todo.completed;
}), {
silent: true
});
this.completedLength = this.filter(function(todo) {
return todo.completed;
}).length;
})
Если пункты добавлены или удалены или если свойство "completed"
помеялось у какого-нибудь пункта или если изменилось значение свойства "allCompleted"
, готовим представление нашего списка todo для того, чтоб затем поместить его в локальное хранилище (localStorage
).
.on('modify *@change:completed change:allCompleted', function() {
this.JSON = JSON.stringify(this);
})
Следующие строки контролируют, как видимость пунктов списка дел контролируется location.hash
(или свойства "state"
). Эта часть может быть реализована несколькими способами. Здесь выбран способ добавления зависимостей одного свойства от других, используя метод linkProps. Что здесь происходит? Мы слушаем событие "addone"
, срабатывающее, когда новый пункт добавляется в список дел. Обработчик события получает объект (evt
) в качестве аргумента, который содержит свойство "added"
, являющеесяя добавленным пунктом. Мы добавляем зависимость свойства "visible"
для добавленного пункта от todos.state
и от собственного свойства "completed"
.
.on('addone', function(evt) {
var todo = evt.added;
todo.linkProps('visible', [
todo, 'completed',
this, 'state'
], function(completed, state) {
return !state || state === 'completed' && completed || state === 'active' && !completed;
});
});
}
});