diff --git a/HW8(MVC)/images/delete-task.svg b/HW8(MVC)/images/delete-task.svg
new file mode 100644
index 0000000..302135c
--- /dev/null
+++ b/HW8(MVC)/images/delete-task.svg
@@ -0,0 +1,3 @@
+
diff --git a/HW8(MVC)/images/task-done.svg b/HW8(MVC)/images/task-done.svg
new file mode 100644
index 0000000..e0de07c
--- /dev/null
+++ b/HW8(MVC)/images/task-done.svg
@@ -0,0 +1,4 @@
+
diff --git a/HW8(MVC)/images/task.svg b/HW8(MVC)/images/task.svg
new file mode 100644
index 0000000..f618e5a
--- /dev/null
+++ b/HW8(MVC)/images/task.svg
@@ -0,0 +1,3 @@
+
diff --git a/HW8(MVC)/index.html b/HW8(MVC)/index.html
new file mode 100644
index 0000000..83dff9a
--- /dev/null
+++ b/HW8(MVC)/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+ To-do list
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/HW8(MVC)/scripts/Controller.js b/HW8(MVC)/scripts/Controller.js
new file mode 100644
index 0000000..c9d25f8
--- /dev/null
+++ b/HW8(MVC)/scripts/Controller.js
@@ -0,0 +1,114 @@
+import { $V } from './View.js';
+import { $M } from './Model.js';
+
+class Controller {
+ initialize() {
+ $V.tasksContainer.addEventListener('click', this.handleClickOnTasksContainer);
+ $V.showButton.addEventListener('click', $V.toggleShowButton);
+ $V.addButton.addEventListener('click', this.passTaskToModel);
+
+ $V.input.addEventListener('input', () => {
+ if ($V.input.value.length > 0) {
+ $V.addButton.style.backgroundColor = $V.greenColor;
+ } else {
+ $V.addButton.style.backgroundColor = $V.redColor;
+ }
+ });
+
+ if (this.checkIfAnyTasksAlreadySaved()) {
+ $V.toggleShowButton();
+ $V.renderTasks($M.getTasksList(), this.isFiltered());
+ }
+ }
+
+ handleClickOnTasksContainer(e) {
+ const clickedOn = e.target;
+ const clickedOnClassList = clickedOn.classList;
+
+ if (clickedOnClassList.contains('icon')) {
+ let [type, index] = clickedOnClassList[1].split('-');
+
+ if (type === 'doneIcon') {
+ $C.toggleDoneMarker(index);
+ } else if (type === 'deleteIcon'){
+ $C.deleteTask(index);
+ } else {
+ $C.switchFilterMode();
+ }
+ }
+ }
+
+ toggleDoneMarker(index) {
+ const tasks = $M.getTasksList();
+ tasks[index].isCompleted = !tasks[index].isCompleted;
+
+ $M.saveTasksList(tasks);
+ $V.renderTasks(tasks, this.isFiltered());
+ }
+
+ deleteTask(index) {
+ $M.removeTaskFromTheTasksList(index);
+ $V.renderTasks($M.getTasksList(), this.isFiltered());
+ }
+
+ switchFilterMode() {
+ let currentStatus = this.isFiltered();
+
+ if (currentStatus === 'true') {
+ currentStatus = 'false';
+ } else {
+ currentStatus = 'true';
+ }
+
+ $M.setFilter(currentStatus);
+ $V.renderTasks($M.getTasksList(), this.isFiltered());
+ }
+
+ passTaskToModel() {
+ $V.addButton.style.backgroundColor = $V.whiteColor;
+
+ if ($V.input.value.length > 0) {
+ const tasksList = document.querySelector('.tasks__list');
+
+ if (!tasksList.classList.contains('open')) {
+ $V.toggleShowButton();
+ }
+
+ $M.addTaskToTheTasksList($V.input.value);
+
+ $V.input.value = '';
+ $V.showButton.style.backgroundColor = $V.redColor;
+ $V.renderTasks($M.getTasksList(), $C.isFiltered());
+ } else {
+ $V.addInvalidStyleToTheInput();
+ }
+ }
+
+ inputIsValid() {
+ const value = $V.input.value;
+ $V.input.value = value.trim();
+ return value.length > 0;
+ }
+
+ checkIfAnyTasksAlreadySaved() {
+ const currentTasksList = $M.getTasksList();
+
+ if (currentTasksList.length > 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ isFiltered() {
+ return localStorage.getItem('isFiltered') ?? 'false';
+ }
+}
+
+const $C = new Controller;
+
+document.body.onload = () => {
+ $C.initialize();
+}
+
+export { $C };
\ No newline at end of file
diff --git a/HW8(MVC)/scripts/Model.js b/HW8(MVC)/scripts/Model.js
new file mode 100644
index 0000000..10db56a
--- /dev/null
+++ b/HW8(MVC)/scripts/Model.js
@@ -0,0 +1,38 @@
+class Model {
+ addTaskToTheTasksList(title) {
+ const currentTasks = this.getTasksList();
+ currentTasks.push({
+ 'title': title,
+ 'isCompleted': false
+ });
+
+ this.saveTasksList(currentTasks);
+ }
+
+ removeTaskFromTheTasksList(index) {
+ const currentTasks = $M.getTasksList();
+ currentTasks.splice(index, 1);
+ $M.saveTasksList(currentTasks);
+ }
+
+ getTasksList() {
+ const tasks = JSON.parse(localStorage.getItem('savedTasks')) ?? { list: [] };
+ return tasks.list;
+ }
+
+ saveTasksList(tasksList) {
+ let savedTasks = {
+ 'list': tasksList
+ };
+
+ savedTasks = JSON.stringify(savedTasks);
+ localStorage.setItem('savedTasks', savedTasks);
+ }
+
+ setFilter(newStatus) {
+ localStorage.setItem('isFiltered', newStatus);
+ }
+}
+
+const $M = new Model;
+export { $M };
\ No newline at end of file
diff --git a/HW8(MVC)/scripts/View.js b/HW8(MVC)/scripts/View.js
new file mode 100644
index 0000000..cb9918b
--- /dev/null
+++ b/HW8(MVC)/scripts/View.js
@@ -0,0 +1,180 @@
+class View {
+ static h1Text = 'THINGS TO DO ✌️';
+ static showButtonTextOnClose = 'SHOW';
+ static showButtonTextOnOpen = 'CLOSE';
+ static showButtonTitle = 'Show all tasks';
+ static addButtonText = 'ADD';
+ static addButtonTitle = 'Add a new task';
+ static inputPlaceholder = 'Add a new task 👈';
+ static tasksListHeaderText = 'CURRENT TASKS';
+ static tasksListFooterText = 'ADD MORE TASKS';
+ static filterButtonTextOnFiltered = 'NOT DONE';
+ static filterButtonTextOnNotFiltered = 'ALL';
+ static doneIconAltText = 'Press to mark task as done';
+ static deleteIconAltText = 'Press to delete task';
+
+ static rootElementId = 'to-do';
+ static buttonsClassList = 'to-do__button';
+ static showButtonClassList = 'to-do__button--show';
+ static addButtonClassList = 'to-do__button--add';
+ static inputClassList = 'to-do__input';
+ static tasksContainerClassList = 'tasks';
+ static tasksListClassList = 'tasks__list';
+
+ static tasksListItemClassList = 'tasks__list-item';
+ static taskTitleClassList = 'tasks__title';
+ static doneTaskTitleClassList = 'task__title--done';
+ static iconsButtonClassList = 'tasks__button';
+ static filterButtonClassList = 'tasks__button--filter';
+ static deleteButtonClassList = 'tasks__button--delete';
+ static filterButtonOnFilteredClassList = 'tasks__button--done';
+ static iconsClassList = 'icon';
+ static taskClassList = 'task';
+
+ static redColor = '#ff8f87';
+ static greenColor = '#87ff93';
+ static whiteColor = '#fff';
+
+ constructor() {
+ this.toDoContainer = document.getElementById(View.rootElementId);
+ this.invalidInputTimer = null;
+
+ if (this.toDoContainer === null) {
+ throw new Error(`Root HTML element for creating View of this to-do list was not found.`);
+ }
+
+ this.renderUserInerface();
+ }
+
+ renderUserInerface() {
+ const h1 = document.createElement('h1');
+ h1.textContent = View.h1Text;
+ document.body.prepend(h1);
+
+ this.showButton = document.createElement('button');
+ this.showButton.classList.add(View.buttonsClassList, View.showButtonClassList);
+ this.showButton.textContent = View.showButtonTextOnClose;
+ this.showButton.title = View.showButtonTitle;
+ this.toDoContainer.appendChild(this.showButton);
+
+ this.input = document.createElement('input');
+ this.input.classList.add(View.inputClassList);
+ this.input.placeholder = View.inputPlaceholder;
+ this.toDoContainer.appendChild(this.input);
+
+ this.addButton = document.createElement('button');
+ this.addButton.classList.add(View.buttonsClassList, View.addButtonClassList);
+ this.addButton.textContent = View.addButtonText;
+ this.addButton.title = View.addButtonTitle;
+ this.toDoContainer.appendChild(this.addButton);
+
+ this.tasksContainer = document.createElement('div');
+ this.tasksContainer.classList.add(View.tasksContainerClassList);
+ this.toDoContainer.appendChild(this.tasksContainer);
+
+ this.tasksList = document.createElement('ul');
+ this.tasksList.classList.add(View.tasksListClassList);
+ this.tasksContainer.appendChild(this.tasksList);
+ }
+
+ renderTasks(tasks, isFiltered) {
+ /*
+ Here and below 'true' and 'false' can appear as a string
+ because localStorage saves only strings.
+ */
+ if (tasks.length === 0 && isFiltered === 'false') {
+ this.toggleShowButton();
+ this.showButton.style.display = 'none';
+ this.tasksList.style.display = 'none';
+ return;
+ }
+
+ this.createTasksListBody(tasks, isFiltered);
+ }
+
+ toggleShowButton() {
+ this.tasksList = document.querySelector(`.${View.tasksListClassList}`);
+ this.showButton = document.querySelector(`.${View.showButtonClassList}`);
+ this.tasksList.style.display = 'inline-block';
+ this.showButton.style.display = 'inline-block';
+ this.tasksList.classList.toggle('open');
+
+ if (this.tasksList.classList.contains('open')) {
+ this.showButton.textContent = View.showButtonTextOnOpen;
+ this.showButton.style.backgroundColor = '#ff8f87';
+ } else {
+ this.showButton.textContent = View.showButtonTextOnClose;
+ this.showButton.style.backgroundColor = '#87ff93';
+ }
+ }
+
+ addInvalidStyleToTheInput() {
+ this.input.style.border = '1px solid #ff8f87';
+ clearTimeout(this.invalidInputTimer);
+
+ this.invalidInputTimer = setTimeout(() => {
+ this.input.style.border = '1px solid #000';
+ }, 1000);
+ }
+
+ createTasksListBody(tasks, isFiltered) {
+ this.tasksList.innerHTML = `
+
+ ${View.tasksListHeaderText}
+ `;
+
+ this.createTasksListItems(tasks, isFiltered);
+
+ let filterButtonText;
+ if (isFiltered === 'true') {
+ filterButtonText = View.filterButtonTextOnFiltered;
+ } else {
+ filterButtonText = View.filterButtonTextOnNotFiltered;
+ }
+
+ this.tasksList.insertAdjacentHTML('beforeend',
+ `
+ ${View.tasksListFooterText}
+
+
+ `);
+ }
+
+ createTasksListItems(tasks, isFiltered) {
+ let tasksCounter = 0;
+
+ for (const task of tasks) {
+ if (isFiltered === 'true' && tasks[tasksCounter].isCompleted === true) {
+ tasksCounter++;
+ continue;
+ }
+
+ this.tasksList.insertAdjacentHTML('beforeend',
+ `
+
+ ${task.title}
+
+ `
+ );
+
+ if (task.isCompleted === true) {
+ const currentMarkAsDoneIcon = document.querySelector('.doneIcon-' + tasksCounter);
+ currentMarkAsDoneIcon.src = 'images/task-done.svg';
+
+ const currentTitle = document.querySelector('.title-' + tasksCounter);
+ currentTitle.classList.add(View.doneTaskTitleClassList);
+ }
+
+ tasksCounter++;
+ }
+ }
+}
+
+const $V = new View;
+export { $V };
\ No newline at end of file
diff --git a/HW8(MVC)/styles/style.css b/HW8(MVC)/styles/style.css
new file mode 100644
index 0000000..2b86c69
--- /dev/null
+++ b/HW8(MVC)/styles/style.css
@@ -0,0 +1,149 @@
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+}
+
+body,
+button,
+input {
+ font-family: "Roboto", sans-serif;
+ font-weight: 400;
+}
+
+h1 {
+ user-select: none;
+ font-family: "Dela Gothic One", cursive;
+ font-size: 3.5em;
+}
+
+input {
+ background-color: #fff;
+ border: 1px solid #000;
+}
+
+.to-do__input {
+ margin: 15px 10px 15px 0;
+ width: 350px;
+ height: 40px;
+ padding-left: 10px;
+ transition: background-color 1s ease;
+}
+
+.to-do__button {
+ width: 100px;
+ height: 40px;
+ cursor: pointer;
+ font-family: "Dela Gothic One", cursive;
+ border: 2px solid #000;
+ background-color: #fff;
+ transition: all 0.5s ease-out;
+}
+
+.to-do__button:hover {
+ background-color: rgba(0, 0, 0, 0.137);
+}
+
+.to-do__button--show {
+ margin-right: 10px;
+ display: none;
+ transition: 0.25s ease;
+}
+
+.tasks {
+ margin-top: 5px;
+ height: 0;
+ transition: 0.5s ease;
+}
+
+.tasks__list {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ background-color: rgba(32, 32, 32, 0.096);
+ transition: 0.35s ease;
+ border: 1px solid #000;
+ width: 100%;
+ letter-spacing: 1.1px;
+ list-style-type: none;
+ height: 0px;
+ overflow-y: hidden;
+ padding: 0px;
+}
+
+.tasks__list li {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.tasks__list-item {
+ margin: 10px 0;
+ padding: 10px 0;
+ border-bottom: 1px solid rgb(119, 119, 119);
+}
+
+.tasks__list li:first-child,
+.tasks__list li:last-child {
+ justify-content: center;
+ border-bottom: 0;
+ font-family: "Dela Gothic One", cursive;
+ letter-spacing: 0;
+ padding: 0;
+ user-select: none;
+}
+
+.tasks__list li:first-child {
+ margin-top: 0;
+}
+
+.tasks__list li:last-child {
+ margin-bottom: 0;
+}
+
+.tasks__list li:nth-child(2) {
+ margin-top: 0px;
+}
+
+.tasks__title {
+ margin: 0 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.task__title--done {
+ text-decoration: line-through;
+}
+
+.tasks__button {
+ background-color: transparent;
+ cursor: pointer;
+ border: 0;
+ outline: none;
+}
+
+.tasks__button img {
+ width: 25px;
+}
+
+.tasks__button--filter {
+ border: 1px solid #000;
+ padding: 0 5px;
+ font-size: 10px;
+ font-family: "Dela Gothic One", cursive;
+}
+
+.open {
+ height: 25vh;
+ overflow-y: scroll;
+ padding: 15px;
+}
\ No newline at end of file