-
-
Save cferdinandi/f6400d510a7ef2220e5db58ca73284fd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Todos - Save and Delete</title> | |
<style type="text/css"> | |
body { | |
margin: 1em auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
form { | |
margin-bottom: 2em; | |
} | |
label { | |
display: block; | |
width: 100%; | |
} | |
[type="checkbox"] { | |
margin-right: 0.5em; | |
} | |
.todos { | |
list-style: none; | |
margin: 0 0 1em; | |
padding: 0; | |
} | |
.todos li { | |
margin-bottom: 0.5em; | |
} | |
/** | |
* @bugfix Prevent webkit from removing list semantics | |
* 1. Add a non-breaking space | |
* 2. Make sure it doesn't mess up the DOM flow | |
*/ | |
.todos li:before { | |
content: "\200B"; /* 1 */ | |
position: absolute; /* 2 */ | |
} | |
.todo-completed { | |
text-decoration: line-through; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Todos - Save and Delete</h1> | |
<form id="add-todos"> | |
<label for="new-todo">What do you want to do?</label> | |
<input type="text" id="new-todo" autofocus> | |
<button>Add Todo</button> | |
</form> | |
<div id="app"></div> | |
<script src="https://cdn.jsdelivr.net/gh/cferdinandi/reef@4/dist/reef.min.js"></script> | |
<script> | |
// | |
// Variables | |
// | |
// The new todo input field | |
var newTodo = document.querySelector('#new-todo'); | |
// Save the localStorage ID to a variable for easier configuration later | |
var storageID = 'todos'; | |
// | |
// Methods | |
// | |
/** | |
* Create a todo component | |
*/ | |
var app = new Reef('#app', { | |
data: { | |
todos: [] | |
}, | |
template: function (props) { | |
// If there are no todos, ask the user to add some | |
if (props.todos.length < 1) { | |
return '<p>You don\'t have any todos yet. Add some using the form above.</p>'; | |
} | |
// Generate markup for todo items | |
return '<ul class="todos">' + props.todos.map(function (todo, index) { | |
var todoHTML = | |
'<li ' + (todo.completed ? 'class="todo-completed"' : '') + '>' + | |
'<label for="todo-' + index + '">' + | |
'<input type="checkbox" id="todo-' + index + '" data-todo="' + index + '" ' + (todo.completed ? 'checked' : '') + '>' + | |
todo.item + | |
' <button data-delete-todo="' + index + '" aria-label="Delete ' + todo.item + '">🗑</button>' + | |
'</label>' + | |
'</li>'; | |
return todoHTML; | |
}).join('') + '</ul>'; | |
} | |
}); | |
/** | |
* Handle form submit events | |
* @param {Event} event The Event object | |
*/ | |
var submitHandler = function (event) { | |
// Only run for #add-todos form | |
if (event.target.id !== 'add-todos') return; | |
// Stop the form from reloading the page | |
event.preventDefault(); | |
// If the #new-todo input has no value, do nothing | |
if (newTodo.value.length < 1) return; | |
// Get a copy of the todos | |
var todos = app.getData().todos; | |
// Update data object | |
todos.push({ | |
item: newTodo.value, | |
completed: false | |
}); | |
// Render fresh UI | |
app.setData({todos: todos}); | |
// Clear the field and return focus | |
newTodo.value = ''; | |
newTodo.focus(); | |
}; | |
/** | |
* Mark todo item as complete | |
* @param {Event} event The event object | |
*/ | |
var completeTodo = function (event) { | |
// Only run on todo items | |
var todo = event.target.getAttribute('data-todo'); | |
if (!todo) return; | |
// Get a copy of the todos | |
var todos = app.getData().todos; | |
if (!todos[todo]) return; | |
// Update the todo state | |
todos[todo].completed = event.target.checked; | |
// Render a fresh UI | |
app.setData({todos: todos}); | |
}; | |
/** | |
* Delete a todo item from the list | |
* @param {Event} event The event object | |
*/ | |
var deleteTodo = function (event) { | |
// Only run on delete button clicks | |
var todo = event.target.getAttribute('data-delete-todo'); | |
if (!todo) return; | |
// Get a copy of the todos | |
var todos = app.getData().todos; | |
if (!todos[todo]) return; | |
// Confirm with the user before deleting | |
if (!window.confirm('Are you sure you want to delete this todo item? This cannot be undone.')) return; | |
// Remove the item from the todo state | |
todos.splice(todo, 1); | |
// Render a fresh UI | |
app.setData({todos: todos}); | |
}; | |
/** | |
* Handle click events | |
* @param {Event} event The Event object | |
*/ | |
var clickHandler = function (event) { | |
// Mark todo item as complete | |
completeTodo(event); | |
// Delete todo item | |
deleteTodo(event); | |
}; | |
/** | |
* Save todo items to localStorage | |
*/ | |
var saveTodos = function () { | |
localStorage.setItem(storageID, JSON.stringify(app.getData())); | |
}; | |
/** | |
* Load todos into state on page load | |
*/ | |
var loadTodos = function () { | |
// Check for saved data in localStorage | |
var saved = localStorage.getItem(storageID); | |
var data = saved ? JSON.parse(saved) : { | |
todos: [] | |
}; | |
// Update the state and run an initial render | |
app.setData(data); | |
}; | |
// | |
// Inits & Event Listeners | |
// | |
// Render the initial UI | |
loadTodos(); | |
// Listen for form submit events | |
document.addEventListener('submit', submitHandler, false); | |
// Listen for click events | |
document.addEventListener('click', clickHandler, false); | |
// On render events, save todo items | |
document.addEventListener('render', saveTodos, false); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment