Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project 2 add remove #2

Merged
merged 11 commits into from
Feb 16, 2022
9 changes: 7 additions & 2 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ <h4>Today's To Do</h4>
</a>
</div>
<form id="todo-form">
<input type="text" name="todo-title" placeholder="Add new todo..." />
<input
type="text"
name="description"
required
placeholder="Add new todo..."
/>
<button type="submit">
<svg
class="icon"
Expand All @@ -48,7 +53,7 @@ <h4>Today's To Do</h4>
</button>
</form>
<ul id="todo-list"></ul>
<button type="button" id="clear-all">Clear all</button>
<button type="button" id="clear-all">Clear all completed</button>
</main>
</body>
</html>
26 changes: 11 additions & 15 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,16 @@ import('./style.css');

const todoListWrapper = document.getElementById('todo-list');

const todos = [
{
index: 2,
description: 'finish this task',
completed: false,
},
{
index: 1,
description: 'Implement completed style',
completed: true,
},
];
const todoList = new TodoList(todoListWrapper);

const todoList = new TodoList(todoListWrapper, todos);
const form = document.getElementById('todo-form');
form.addEventListener('submit', (event) => {
event.preventDefault();
todoList.addNewItem(form.description.value);
form.description.value = '';
});

// useless line to pass linter for now
if (!todoList) todoList.init();
const clearAllCompletedBtn = document.getElementById('clear-all');
clearAllCompletedBtn.addEventListener('click', () => {
todoList.removeAllCompleted();
});
14 changes: 14 additions & 0 deletions src/modules/isStorageAvailable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const isStorageAvailable = (type = 'localStorage') => {
let storage;
try {
storage = window[type];
const x = '__storage_test__';
storage.setItem(x, x);
storage.removeItem(x);
return true;
} catch (e) {
return false;
}
};

export default isStorageAvailable;
228 changes: 190 additions & 38 deletions src/modules/todoList.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,213 @@
import isStorageAvailable from './isStorageAvailable.js';

export default class TodoList {
constructor(wrapper, todos = []) {
constructor(wrapper, storageName = 'todos') {
this.wrapper = wrapper;
this.todos = todos;
this.storageName = storageName;
this.isStorageAvailable = isStorageAvailable('localStorage');
this.init();
}

init() {
this.todos = this.sortList();
this.todos.forEach((todo) => {
this.addTodo(todo);
});
if (this.isStorageAvailable) {
const storage = window.localStorage.getItem(this.storageName);
this.todos = JSON.parse(storage) || [];
this.todos = this.sortList();
window.localStorage.setItem(this.storageName, JSON.stringify(this.todos));
} else {
this.todos = [];
}

this.addAllToPage();
}

updateStorage() {
if (this.isStorageAvailable) {
window.localStorage.setItem(this.storageName, JSON.stringify(this.todos));
}
}

sortList() {
const sortedTodos = this.todos.sort((a, b) => a.index - b.index);
let sortedTodos = this.todos.sort((a, b) => a.index - b.index);
// recreate indexes from 0
sortedTodos = sortedTodos.map((todo, index) => ({
index,
description: todo.description,
completed: todo.completed,
}));
return sortedTodos;
}

addTodo(todo) {
addAllToPage() {
this.todos.forEach((todo) => {
this.addToPage(todo);
});
}

removeAllFromPage() {
this.wrapper.innerHTML = '';
}

addToPage(todo) {
const li = document.createElement('li');
li.classList.add('todo');
li.setAttribute('id', 'todo-'.concat(todo.index));

const completionIcon = document.createElement('button');
completionIcon.setAttribute('type', 'button');
completionIcon.classList.add('completionIcon', 'icon');
completionIcon.innerHTML = `
<svg
class="icon"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
></path>
</svg>
<svg
class="icon"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"
></path>
</svg>
`;

completionIcon.addEventListener('click', (event) => {
event.preventDefault();
this.toggleCompleteStatus(li);
});

const description = document.createElement('input');
description.setAttribute('type', 'text');
description.setAttribute('name', 'description');
description.readOnly = true;
description.setAttribute('value', todo.description);
description.classList.add('description');

description.addEventListener('focusin', () => {
description.readOnly = false;
li.classList.add('active');
});

const label = document.createElement('label');
const chkbox = document.createElement('input');
description.addEventListener('focusout', () => {
description.readOnly = true;
li.classList.remove('active');
});

const actionIcon = document.createElement('div');
actionIcon.classList.add('actionIcon', 'icon');

const dragIcon = document.createElement('button');
dragIcon.setAttribute('type', 'button');
dragIcon.innerHTML = `
<svg
class="icon"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z"
></path>
</svg>
`;

chkbox.setAttribute('type', 'checkbox');
const deleteIcon = document.createElement('button');
deleteIcon.setAttribute('type', 'button');
deleteIcon.innerHTML = `
<svg
class="icon"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
></path>
</svg>
`;
deleteIcon.addEventListener('click', () => this.removeItem(li));

actionIcon.appendChild(dragIcon);
actionIcon.appendChild(deleteIcon);

if (todo.completed) {
chkbox.setAttribute('checked', 'checked');
label.classList.add('checked');
li.classList.add('completed');
}

label.appendChild(chkbox);
label.innerHTML += todo.description;

const dragBtn = document.createElement('button');
dragBtn.setAttribute('type', 'button');
dragBtn.innerHTML = `
<button type="button">
<svg
class="icon"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z"
></path>
</svg>
</button>`;

li.appendChild(label);
li.appendChild(dragBtn);
li.appendChild(completionIcon);
li.appendChild(description);
li.appendChild(actionIcon);
this.wrapper.appendChild(li);
}

addNewItem(description) {
const index = this.todos.length;
const completed = false;

const newTodo = {
index,
description,
completed,
};

// save in the storage
this.todos.push(newTodo);
if (this.isStorageAvailable) {
window.localStorage.setItem(this.storageName, JSON.stringify(this.todos));
}

// show on the page
this.addToPage(newTodo);
}

refreshTodosOnPage() {
this.updateStorage();
this.removeAllFromPage();
this.addAllToPage();
}

removeItem(element) {
const todoId = Number(element.id.match(/\d+$/));
this.todos = this.todos.filter((todo) => todo.index !== todoId);
this.todos = this.sortList();
this.refreshTodosOnPage();
}

removeAllCompleted() {
this.todos = this.todos.filter((todo) => !todo.completed);
this.todos = this.sortList();
this.refreshTodosOnPage();
}

toggleCompleteStatus(element) {
element.classList.toggle('completed');

const todoIndex = Number(element.id.match(/\d+$/));
this.todos[todoIndex].completed = element.classList.contains('completed');
this.updateStorage();
}
}