Skip to content

Instantly share code, notes, and snippets.

@Clickys
Last active January 19, 2019 10:55
Show Gist options
  • Save Clickys/569101aa05068ac59c0fb5d14aa9e0df to your computer and use it in GitHub Desktop.
Save Clickys/569101aa05068ac59c0fb5d14aa9e0df to your computer and use it in GitHub Desktop.
A render that doesn't lie #6
// Naive solution #Solution 1
// Created a new function and run it each time a user change the list. so the local storage is updated synchronize with render();
/*global jQuery, Handlebars, Router */
jQuery(function ($) {
'use strict'; // Why this = document ?
Handlebars.registerHelper('eq', function (a, b, options) {
return a === b ? options.fn(this) : options.inverse(this); // Case 4
});
var ENTER_KEY = 13;
var ESCAPE_KEY = 27;
var util = {
uuid: function () {
/*jshint bitwise:false */
var i, random; // Case 2
var uuid = ''; // Case 2
for (i = 0; i < 32; i++) {
random = Math.random() * 16 | 0;
if (i === 8 || i === 12 || i === 16 || i === 20) {
uuid += '-';
}
uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)).toString(16);
}
return uuid;
},
pluralize: function (count, word) {
return count === 1 ? word : word + 's'; // Case 2
},
};
var App = {
init: function () {
this.todos = localStorage.getItem('todo-list') ? JSON.parse(localStorage.getItem('todo-list')) : [];
this.todoTemplate = Handlebars.compile($('#todo-template').html()); // Case 2
this.footerTemplate = Handlebars.compile($('#footer-template').html()); // Case 2
this.bindEvents(); // Case 2
new Router({
'/:filter': function (filter) {
this.filter = filter; // Case 4
this.render(); // Case 4
}.bind(this) // Case 2
}).init('/all');
},
bindEvents: function () {
$('#new-todo').on('keyup', this.create.bind(this)); // Case 4
$('#toggle-all').on('change', this.toggleAll.bind(this)); // Case 4
$('#footer').on('click', '#clear-completed', this.destroyCompleted.bind(this)); // Case 4
$('#todo-list')
.on('change', '.toggle', this.toggle.bind(this)) // Case 4
.on('dblclick', 'label', this.edit.bind(this)) // Case 4
.on('keyup', '.edit', this.editKeyup.bind(this)) // Case 4
.on('focusout', '.edit', this.update.bind(this)) // Case 4
.on('click', '.destroy', this.destroy.bind(this)); // Case 4
},
render: function () {
var todos = this.getFilteredTodos(); // Case 2
$('#todo-list').html(this.todoTemplate(todos)); // Case 5
$('#main').toggle(todos.length > 0);
$('#toggle-all').prop('checked', this.getActiveTodos().length === 0); // Case 5
this.renderFooter(); // Case 2
$('#new-todo').focus();
// util.store('todos-jquery', this.todos); // Case 2
},
renderFooter: function () {
var todoCount = this.todos.length; // Case 2
var activeTodoCount = this.getActiveTodos().length; // Case 2
var template = this.footerTemplate({ // Case 2
activeTodoCount: activeTodoCount,
activeTodoWord: util.pluralize(activeTodoCount, 'item'),
completedTodos: todoCount - activeTodoCount,
filter: this.filter // Case 2
});
$('#footer').toggle(todoCount > 0).html(template);
},
toggleAll: function (e) {
var isChecked = $(e.target).prop('checked');
this.todos.forEach(function (todo) { // Case 2
todo.completed = isChecked;
});
this.saveToStorage();
this.render(); // Case 2
},
getActiveTodos: function () {
return this.todos.filter(function (todo) { // Case 2
return !todo.completed;
});
},
getCompletedTodos: function () {
return this.todos.filter(function (todo) { // Case 2
return todo.completed;
});
},
getFilteredTodos: function () {
if (this.filter === 'active') { // Case 2
return this.getActiveTodos();
}
if (this.filter === 'completed') {
return this.getCompletedTodos(); // Case 2
}
return this.todos;
},
destroyCompleted: function () {
this.todos = this.getActiveTodos(); // Case 2
this.filter = 'all';
this.saveToStorage();
this.render();
},
// accepts an element from inside the `.item` div and
// returns the corresponding index in the `todos` array
indexFromEl: function (el) {
var id = $(el).closest('li').data('id');
var todos = this.todos; // Case 2
var i = todos.length;
while (i--) {
if (todos[i].id === id) {
return i;
}
}
},
create: function (e) {
var $input = $(e.target);
var val = $input.val().trim();
if (e.which !== ENTER_KEY || !val) {
return;
}
// Case 2
this.todos.push({
id: util.uuid(),
title: val,
completed: false
});
$input.val('');
this.saveToStorage();
this.render();
},
toggle: function (e) {
var i = this.indexFromEl(e.target);
this.todos[i].completed = !this.todos[i].completed;
this.saveToStorage();
this.render();
},
edit: function (e) {
var $input = $(e.target).closest('li').addClass('editing').find('.edit');
$input.val($input.val()).focus();
},
editKeyup: function (e) {
if (e.which === ENTER_KEY) {
e.target.blur();
}
if (e.which === ESCAPE_KEY) {
$(e.target).data('abort', true).blur();
}
},
update: function (e) {
var el = e.target;
var $el = $(el);
var val = $el.val().trim();
if (!val) {
this.destroy(e);
return;
}
if ($el.data('abort')) {
$el.data('abort', false);
} else {
this.todos[this.indexFromEl(el)].title = val;
}
this.saveToStorage();
this.render();
},
destroy: function (e) {
this.todos.splice(this.indexFromEl(e.target), 1);
this.render();
},
saveToStorage: function() {
let localStorageArray = [];
localStorage.setItem('todo-list', '[]');
this.todos.forEach((todo) => {
localStorageArray.push(todo);
})
localStorage.setItem('todo-list', JSON.stringify(localStorageArray));
}
};
App.init();
});
// Solution #2
// Refactor the whole app, using MVC architecture model.
/*global jQuery, Handlebars, Router */
jQuery(function ($) {
'use strict'; // Why this = document ?
Handlebars.registerHelper('eq', function (a, b, options) {
return a === b ? options.fn(this) : options.inverse(this); // Case 4
});
var ENTER_KEY = 13;
var ESCAPE_KEY = 27;
var util = {
uuid: function () {
/*jshint bitwise:false */
var i, random; // Case 2
var uuid = ''; // Case 2
for (i = 0; i < 32; i++) {
random = Math.random() * 16 | 0;
if (i === 8 || i === 12 || i === 16 || i === 20) {
uuid += '-';
}
uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)).toString(16);
}
return uuid;
},
pluralize: function (count, word) {
return count === 1 ? word : word + 's'; // Case 2
},
};
var App = {
getActiveTodos: function () {
return this.todos.filter(function (todo) { // Case 2
return !todo.completed;
});
},
getCompletedTodos: function () {
return this.todos.filter(function (todo) { // Case 2
return todo.completed;
});
},
saveToStorage: function() {
let localStorageArray = [];
localStorage.setItem('todo-list', '[]');
this.todos.forEach((todo) => {
localStorageArray.push(todo);
})
localStorage.setItem('todo-list', JSON.stringify(localStorageArray));
}
};
const view = {
todoTemplate: Handlebars.compile($('#todo-template').html()), // Case 2
footerTemplate: Handlebars.compile($('#footer-template').html()), // Case 2
render: function () {
var todos = controller.getFilteredTodos(); // Case 2
$('#todo-list').html(this.todoTemplate(todos)); // Case 5
$('#main').toggle(todos.length > 0);
$('#toggle-all').prop('checked', App.getActiveTodos().length === 0); // Case 5
this.renderFooter(); // Case 2
$('#new-todo').focus();
// util.store('todos-jquery', this.todos); // Case 2
},
renderFooter: function () {
var todoCount = App.todos.length; // Case 2
var activeTodoCount = App.getActiveTodos().length; // Case 2
var template = this.footerTemplate({ // Case 2
activeTodoCount: activeTodoCount,
activeTodoWord: util.pluralize(activeTodoCount, 'item'),
completedTodos: todoCount - activeTodoCount,
filter: this.filter // Case 2
});
$('#footer').toggle(todoCount > 0).html(template);
},
};
const controller = {
init: function () {
App.todos = localStorage.getItem('todo-list') ? JSON.parse(localStorage.getItem('todo-list')) : [];
this.bindEvents(); // Case 2
new Router({
'/:filter': function (filter) {
this.filter = filter; // Case 4
view.render(); // Case 4
}.bind(this) // Case 2
}).init('/all');
},
bindEvents: function () {
$('#new-todo').on('keyup', this.create.bind(this)); // Case 4
$('#toggle-all').on('change', this.toggleAll.bind(this)); // Case 4
$('#footer').on('click', '#clear-completed', this.destroyCompleted.bind(this)); // Case 4
$('#todo-list')
.on('change', '.toggle', this.toggle.bind(this)) // Case 4
.on('dblclick', 'label', this.edit.bind(this)) // Case 4
.on('keyup', '.edit', this.editKeyup.bind(this)) // Case 4
.on('focusout', '.edit', this.update.bind(this)) // Case 4
.on('click', '.destroy', this.destroy.bind(this)); // Case 4
},
toggleAll: function (e) {
var isChecked = $(e.target).prop('checked');
App.todos.forEach(function (todo) { // Case 2
todo.completed = isChecked;
});
App.saveToStorage();
view.render(); // Case 2
},
getFilteredTodos: function () {
if (this.filter === 'active') { // Case 2
return App.getActiveTodos();
}
if (this.filter === 'completed') {
return App.getCompletedTodos(); // Case 2
}
return App.todos;
},
destroyCompleted: function () {
App.todos = App.getActiveTodos(); // Case 2
this.filter = 'all';
App.saveToStorage();
view.render();
},
indexFromEl: function (el) {
var id = $(el).closest('li').data('id');
var todos = App.todos; // Case 2
var i = todos.length;
while (i--) {
if (todos[i].id === id) {
return i;
}
}
},
create: function (e) {
var $input = $(e.target);
var val = $input.val().trim();
if (e.which !== ENTER_KEY || !val) {
return;
}
// Case 2
App.todos.push({
id: util.uuid(),
title: val,
completed: false
});
$input.val('');
App.saveToStorage();
view.render();
},
toggle: function (e) {
var i = this.indexFromEl(e.target);
App.todos[i].completed = !App.todos[i].completed;
App.saveToStorage();
view.render();
},
edit: function (e) {
var $input = $(e.target).closest('li').addClass('editing').find('.edit');
$input.val($input.val()).focus();
},
editKeyup: function (e) {
if (e.which === ENTER_KEY) {
e.target.blur();
}
if (e.which === ESCAPE_KEY) {
$(e.target).data('abort', true).blur();
}
},
update: function (e) {
var el = e.target;
var $el = $(el);
var val = $el.val().trim();
if (!val) {
this.destroy(e);
return;
}
if ($el.data('abort')) {
$el.data('abort', false);
} else {
App.todos[this.indexFromEl(el)].title = val;
}
App.saveToStorage();
view.render();
},
destroy: function (e) {
App.todos.splice(this.indexFromEl(e.target), 1);
view.render();
},
}
controller.init();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment