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