Commit b8fb8864 authored by Daniel Hug's avatar Daniel Hug Committed by Sindre Sorhus

More VanillaJS app improvements - closes #863

parent 5c1a0455
/*global app */
/*global app, $on */
(function () {
'use strict';
......@@ -17,10 +17,9 @@
var todo = new Todo('todos-vanillajs');
window.addEventListener('load', function () {
function setView() {
todo.controller.setView(document.location.hash);
}.bind(this));
window.addEventListener('hashchange', function () {
todo.controller.setView(document.location.hash);
}.bind(this));
}
$on(window, 'load', setView);
$on(window, 'hashchange', setView);
})();
......@@ -9,40 +9,41 @@
* @param {object} view The view instance
*/
function Controller(model, view) {
this.model = model;
this.view = view;
var that = this;
that.model = model;
that.view = view;
this.view.bind('newTodo', function (title) {
this.addItem(title);
}.bind(this));
that.view.bind('newTodo', function (title) {
that.addItem(title);
});
this.view.bind('itemEdit', function (item) {
this.editItem(item.id);
}.bind(this));
that.view.bind('itemEdit', function (item) {
that.editItem(item.id);
});
this.view.bind('itemEditDone', function (item) {
this.editItemSave(item.id, item.title);
}.bind(this));
that.view.bind('itemEditDone', function (item) {
that.editItemSave(item.id, item.title);
});
this.view.bind('itemEditCancel', function (item) {
this.editItemCancel(item.id);
}.bind(this));
that.view.bind('itemEditCancel', function (item) {
that.editItemCancel(item.id);
});
this.view.bind('itemRemove', function (item) {
this.removeItem(item.id);
}.bind(this));
that.view.bind('itemRemove', function (item) {
that.removeItem(item.id);
});
this.view.bind('itemToggle', function (item) {
this.toggleComplete(item.id, item.completed);
}.bind(this));
that.view.bind('itemToggle', function (item) {
that.toggleComplete(item.id, item.completed);
});
this.view.bind('removeCompleted', function () {
this.removeCompletedItems();
}.bind(this));
that.view.bind('removeCompleted', function () {
that.removeCompletedItems();
});
this.view.bind('toggleAll', function (status) {
this.toggleAll(status.completed);
}.bind(this));
that.view.bind('toggleAll', function (status) {
that.toggleAll(status.completed);
});
}
/**
......@@ -61,27 +62,30 @@
* todo-list
*/
Controller.prototype.showAll = function () {
this.model.read(function (data) {
this.view.render('showEntries', data);
}.bind(this));
var that = this;
that.model.read(function (data) {
that.view.render('showEntries', data);
});
};
/**
* Renders all active tasks
*/
Controller.prototype.showActive = function () {
this.model.read({ completed: false }, function (data) {
this.view.render('showEntries', data);
}.bind(this));
var that = this;
that.model.read({ completed: false }, function (data) {
that.view.render('showEntries', data);
});
};
/**
* Renders all completed tasks
*/
Controller.prototype.showCompleted = function () {
this.model.read({ completed: true }, function (data) {
this.view.render('showEntries', data);
}.bind(this));
var that = this;
that.model.read({ completed: true }, function (data) {
that.view.render('showEntries', data);
});
};
/**
......@@ -89,35 +93,39 @@
* object and it'll handle the DOM insertion and saving of the new item.
*/
Controller.prototype.addItem = function (title) {
var that = this;
if (title.trim() === '') {
return;
}
this.model.create(title, function () {
this.view.render('clearNewTodo');
this._filter(true);
}.bind(this));
that.model.create(title, function () {
that.view.render('clearNewTodo');
that._filter(true);
});
};
/*
* Triggers the item editing mode.
*/
Controller.prototype.editItem = function (id) {
this.model.read(id, function (data) {
this.view.render('editItem', {id: id, title: data[0].title});
}.bind(this));
var that = this;
that.model.read(id, function (data) {
that.view.render('editItem', {id: id, title: data[0].title});
});
};
/*
* Finishes the item editing mode successfully.
*/
Controller.prototype.editItemSave = function (id, title) {
var that = this;
if (title.trim()) {
this.model.update(id, {title: title}, function () {
this.view.render('editItemDone', {id: id, title: title});
}.bind(this));
that.model.update(id, {title: title}, function () {
that.view.render('editItemDone', {id: id, title: title});
});
} else {
this.removeItem(id);
that.removeItem(id);
}
};
......@@ -125,9 +133,10 @@
* Cancels the item editing mode.
*/
Controller.prototype.editItemCancel = function (id) {
this.model.read(id, function (data) {
this.view.render('editItemDone', {id: id, title: data[0].title});
}.bind(this));
var that = this;
that.model.read(id, function (data) {
that.view.render('editItemDone', {id: id, title: data[0].title});
});
};
/**
......@@ -138,24 +147,26 @@
* storage
*/
Controller.prototype.removeItem = function (id) {
this.model.remove(id, function () {
this.view.render('removeItem', id);
}.bind(this));
var that = this;
that.model.remove(id, function () {
that.view.render('removeItem', id);
});
this._filter();
that._filter();
};
/**
* Will remove all completed items from the DOM and storage.
*/
Controller.prototype.removeCompletedItems = function () {
this.model.read({ completed: true }, function (data) {
var that = this;
that.model.read({ completed: true }, function (data) {
data.forEach(function (item) {
this.removeItem(item.id);
}.bind(this));
}.bind(this));
that.removeItem(item.id);
});
});
this._filter();
that._filter();
};
/**
......@@ -168,15 +179,16 @@
* @param {boolean|undefined} silent Prevent re-filtering the todo items
*/
Controller.prototype.toggleComplete = function (id, completed, silent) {
this.model.update(id, { completed: completed }, function () {
this.view.render('elementComplete', {
var that = this;
that.model.update(id, { completed: completed }, function () {
that.view.render('elementComplete', {
id: id,
completed: completed
});
}.bind(this));
});
if (!silent) {
this._filter();
that._filter();
}
};
......@@ -185,13 +197,14 @@
* Just pass in the event object.
*/
Controller.prototype.toggleAll = function (completed) {
this.model.read({ completed: !completed }, function (data) {
var that = this;
that.model.read({ completed: !completed }, function (data) {
data.forEach(function (item) {
this.toggleComplete(item.id, completed, true);
}.bind(this));
}.bind(this));
that.toggleComplete(item.id, completed, true);
});
});
this._filter();
that._filter();
};
/**
......@@ -199,16 +212,17 @@
* number of todos.
*/
Controller.prototype._updateCount = function () {
this.model.getCount((function(todos) {
this.view.render('updateElementCount', todos.active);
this.view.render('clearCompletedButton', {
var that = this;
that.model.getCount(function (todos) {
that.view.render('updateElementCount', todos.active);
that.view.render('clearCompletedButton', {
completed: todos.completed,
visible: todos.completed > 0
});
this.view.render('toggleAll', {checked: todos.completed === todos.total});
this.view.render('contentBlockVisibility', {visible: todos.total > 0});
}).bind(this));
that.view.render('toggleAll', {checked: todos.completed === todos.total});
that.view.render('contentBlockVisibility', {visible: todos.total > 0});
});
};
/**
......
......@@ -10,6 +10,11 @@
return (scope || document).querySelectorAll(selector);
};
// addEventListener wrapper:
window.$on = function (target, type, callback, useCapture) {
target.addEventListener(type, callback, !!useCapture);
};
// Register events on elements that may or may not exist yet:
// $live('div a', 'click', function (event) {});
window.$live = (function () {
......@@ -31,7 +36,7 @@
return function (selector, event, handler) {
if (!eventRegistry[event]) {
eventRegistry[event] = [];
document.documentElement.addEventListener(event, dispatchEvent, true);
window.$on(document.documentElement, event, dispatchEvent, true);
}
eventRegistry[event].push({
......
......@@ -68,7 +68,7 @@
* @param {function} callback The callback to fire when the update is complete.
*/
Model.prototype.update = function (id, data, callback) {
this.storage.save(id, data, callback);
this.storage.save(data, callback, id);
};
/**
......
......@@ -11,22 +11,19 @@
* real life you probably would be making AJAX calls
*/
function Store(name, callback) {
var data;
var dbName;
callback = callback || function () {};
dbName = this._dbName = name;
this._dbName = name;
if (!localStorage[dbName]) {
data = {
if (!localStorage[name]) {
var data = {
todos: []
};
localStorage[dbName] = JSON.stringify(data);
localStorage[name] = JSON.stringify(data);
}
callback.call(this, JSON.parse(localStorage[dbName]));
callback.call(this, JSON.parse(localStorage[name]));
}
/**
......@@ -50,13 +47,12 @@
var todos = JSON.parse(localStorage[this._dbName]).todos;
callback.call(this, todos.filter(function (todo) {
var match = true;
for (var q in query) {
if (query[q] !== todo[q]) {
match = false;
return false;
}
}
return match;
return true;
}));
};
......@@ -74,33 +70,30 @@
* Will save the given data to the DB. If no item exists it will create a new
* item, otherwise it'll simply update an existing item's properties
*
* @param {number} id An optional param to enter an ID of an item to update
* @param {object} data The data to save back into the DB
* @param {object} updateData The data to save back into the DB
* @param {function} callback The callback to fire after saving
* @param {number} id An optional param to enter an ID of an item to update
*/
Store.prototype.save = function (id, updateData, callback) {
Store.prototype.save = function (updateData, callback, id) {
var data = JSON.parse(localStorage[this._dbName]);
var todos = data.todos;
callback = callback || function () {};
// If an ID was actually given, find the item and update each property
if (typeof id !== 'object') {
if (id) {
for (var i = 0; i < todos.length; i++) {
if (todos[i].id == id) {
for (var x in updateData) {
todos[i][x] = updateData[x];
if (todos[i].id === id) {
for (var key in updateData) {
todos[i][key] = updateData[key];
}
break;
}
}
localStorage[this._dbName] = JSON.stringify(data);
callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
} else {
callback = updateData;
updateData = id;
// Generate an ID
updateData.id = new Date().getTime();
......
/*global qs, qsa, $parent, $live */
/*global qs, qsa, $on, $parent, $live */
(function (window) {
'use strict';
......@@ -135,7 +135,7 @@
View.prototype._itemId = function (element) {
var li = $parent(element, 'li');
return li.dataset.id;
return parseInt(li.dataset.id, 10);
};
View.prototype._bindItemEditDone = function (handler) {
......@@ -173,17 +173,17 @@
View.prototype.bind = function (event, handler) {
var that = this;
if (event === 'newTodo') {
that.$newTodo.addEventListener('change', function () {
$on(that.$newTodo, 'change', function () {
handler(that.$newTodo.value);
});
} else if (event === 'removeCompleted') {
that.$clearCompleted.addEventListener('click', function () {
$on(that.$clearCompleted, 'click', function () {
handler();
});
} else if (event === 'toggleAll') {
that.$toggleAll.addEventListener('click', function () {
$on(that.$toggleAll, 'click', function () {
handler({completed: this.checked});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment