diff --git a/index copy.html b/index copy.html new file mode 100644 index 0000000..230580a --- /dev/null +++ b/index copy.html @@ -0,0 +1,68 @@ + + + + + + + + My 1st TODO App with Vue + + + +
+
+

My 1st TODO-APP with Vue

+

Getting things done…

+ + + +
+ + + + + + +
+ + + +
+
+ + +
+
+ + + + diff --git a/index.html b/index.html index 15d9f85..b92a5ad 100644 --- a/index.html +++ b/index.html @@ -5,26 +5,33 @@ - My 1st TODO App + My 1st TODO App with Vue
-
-

My 1st TODO-APP(I)

+
+

My Todo-App with Vue

Getting things done…

+
+ - - + +
-
    +
      +
    • +
      + + + +
      +
    • +
    - - + + diff --git a/script.js b/script.js index fb642a3..071a7cb 100644 --- a/script.js +++ b/script.js @@ -1,266 +1,140 @@ +/* +BUG: Test 2x, nach Alert F5 kommt doch rein +*/ "use strict"; - -import { fetchData, postData } from "./apiutils.js"; - -/* State and initializing -========================================================================== */ - -// DOM elements -const taskForm = document.getElementById("task-form"); -const input = document.getElementById("input"); -const taskList = document.getElementById("task-list"); -const clearButton = document.getElementById("clear-btn"); -/* const apiUrl = "http://localhost:4730/todos"; - */ -// Modal Styling - -const corner = Swal.mixin({ - toast: true, - position: "top-end", - title: "Sorry, API is offline!", - text: "Start server with: npm run start", - showConfirmButton: false, - timer: 3000, - timerProgressBar: true, - didOpen: (toast) => { - toast.onmouseenter = Swal.stopTimer; - toast.onmouseleave = Swal.resumeTimer; +Vue.createApp({ + data() { + return { + todos: [], + newTodoText: "", + apiUrl: "http://localhost:4730/todos/", + filter: "all", // Initial filter "all" + }; }, -}); - -// State -const state = { - currentFilter: "all", // "all", "done", "open" - tasks: [], -}; - -initialize(); - -/* FUNCTION - to render filtered data from state -========================================================================== */ -function render() { - taskList.innerHTML = ""; - - // Filtering todos - const filteredTodos = state.tasks.filter((task) => { - if (state.currentFilter == "done") { - return task.done; // set task to done - } - if (state.currentFilter == "open") { - return !task.done; // set task to NOT done - } - return true; // unfiltered tasks - }); - - for (const task of filteredTodos) { - taskList.appendChild(generateTodoItemTemplate(task)); - } -} - -/* FUNCTION - to get todos from the API -========================================================================== */ -function getTodosFromApi() { - fetchData() // Nochmal anschauen - .then((todosFromApi) => { - state.tasks = todosFromApi; - render(); - }) - .catch((error) => { - console.error("Error getting todos from API:", error); - corner.fire({ - icon: "error", - title: "Sorry, API is offline!", - }); - }); - //.finally(() => console.log("test if api is online and offline")); -} - -/* FUNCTION - to save a new todo to the API -========================================================================== */ -function saveTodoToApi() { - const newTodoText = input.value.trim(); - const newTodo = { description: newTodoText, done: false }; - - postData(newTodo) - .then((newTodoFromApi) => { - state.tasks.push(newTodoFromApi); - render(); - }) - .catch((error) => console.error("Error saving todo to API:", error)); -} - -/* FUNCTION - to update a todo in the API -========================================================================== */ -function updateTodoToApi(task) { - fetch(apiUrl + `/${task.id}`, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(task), - }) - .then((response) => response.json()) - .then((updatedTodoFromApi) => { - // finding the position from the changed task in the local state - const index = state.tasks.findIndex( - (task) => task.id === updatedTodoFromApi.id - ); - // -1 means: not in this array - if (index !== -1) { - state.tasks[index] = updatedTodoFromApi; - render(); + computed: { + filteredTodos() { + // Je nach ausgewähltem Filter die Todos filtern + if (this.filter === "open") { + return this.todos.filter((todo) => !todo.done); + } else if (this.filter === "done") { + return this.todos.filter((todo) => todo.done); } else { - console.warn("Updated task not found in local state."); + // "all" oder anderer Filter, alle Todos anzeigen + return this.todos; } - }) - .catch((error) => console.error("Error updating todo in API:", error)); -} - -/* FUNCTION - to delete a todo from the API -========================================================================== */ -function deleteTodoFromApi(taskId) { - fetch(apiUrl + `/${taskId}`, { - method: "DELETE", - headers: { "Content-Type": "application/json" }, - }) - .then(() => { - // Remove the task with the specified id from the state.tasks array - state.tasks = state.tasks.filter((task) => task.id !== parseInt(taskId)); - render(); - }) - .catch((error) => console.error("Error deleting todo from API:", error)); -} - -/* FUNCTION - to remove all completed todos from the API -========================================================================== */ -function removeDoneTodosFromApi() { - // Filter out completed todos from the current state - const doneTodos = state.tasks.filter((task) => task.done); - - // Use Promise.all for multiple API requests and delete many completed todos - //Promise All Settled - Promise.all( - doneTodos.map((doneTodo) => - fetch(apiUrl + `/${doneTodo.id}`, { - method: "DELETE", + }, + }, + watch: { + // Watch for changes in the filter and update localStorage + filter(newValue) { + localStorage.setItem("todoFilter", newValue); + }, + }, + methods: { + addTodo() { + const newTodoObject = { + description: this.newTodoText.trim(), + done: false, + }; + + fetch(this.apiUrl, { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify(newTodoObject), }) - ) - ) - .then(() => { - // Update the local state by filtering out completed todos - state.tasks = state.tasks.filter((task) => !task.done); - render(); - }) - .catch((error) => - console.error("Error removing done todos from API:", error) - ); -} - -/* FUNCTION - to generate HTML template for a todo item -========================================================================== */ -function generateTodoItemTemplate(task) { - const li = document.createElement("li"); - li.classList.add("task-item"); - li.id = `task-${task.id}`; - - const div = document.createElement("div"); - div.classList.add("checkbox-wrapper"); - - const checkbox = document.createElement("input"); - checkbox.type = "checkbox"; - checkbox.id = `checkbox-toogle-${task.id}`; - checkbox.classList.add("toggle-complete"); - checkbox.checked = task.done; - - /* EVENT LISTENER - for changing the checkbox status - ========================================================================== */ - checkbox.addEventListener("change", () => { - task.done = checkbox.checked; - checkbox.checked - ? checkbox.setAttribute("checked", "") - : checkbox.removeAttribute("checked"); - - // Check if the task is already in the API - const existingTask = state.tasks.find((t) => t.id === task.id); - - if (existingTask) { - // If present, update the task in the API - updateTodoToApi(task); - } else { - // If not present, it's a new task. Handle this case if needed. - console.warn("Task not found in API."); + .then((response) => response.json()) + .then((jsonData) => { + const isDuplicate = this.todos.some( + (task) => + task.description.toLowerCase() === + this.newTodoText.trim().toLowerCase() + ); + if (this.newTodoText === "") { + alert("Write something down!"); + } else if (isDuplicate) { + alert("You can't add a duplicate task!"); + } else { + this.todos.push(jsonData); + this.newTodoText = ""; + } + }); + }, + updateTodo(todo) { + const cloneCurrentTodo = { + id: todo.id, + description: todo.description, + done: !todo.done, + }; + fetch(this.apiUrl + cloneCurrentTodo.id, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(cloneCurrentTodo), + }) + .then((response) => response.json()) + .then((jsonDataUpdatedTodo) => { + todo.done = !todo.done; + }) + .catch((error) => { + console.error( + "Fehler beim Aktualisieren des Todos in der API:", + error + ); + }); + }, + deleteTodo(todoId) { + fetch(this.apiUrl + todoId, { + method: "DELETE", + }) + .then(() => { + this.todos = this.todos.filter((todo) => todo.id !== todoId); + }) + .catch((error) => { + console.error("Fehler beim Löschen des Todos:", error); + }); + }, + + deleteDoneTodos() { + if (confirm("Do you really want to delete all completed tasks?")) { + // Filter out completed todos from the current state + const doneTodos = this.todos.filter((todo) => todo.done); + + if (doneTodos.length === 0) { + // No done tasks + console.log("No completed tasks to delete"); + + return; + } + + // Use Promise.all for multiple API requests and delete many completed todos + Promise.all( + doneTodos.map((doneTodo) => + fetch(this.apiUrl + `/${doneTodo.id}`, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + }) + ) + ) + .then(() => { + // Update the local state by filtering out completed todos + this.todos = this.todos.filter((todo) => !todo.done); + console.log("Completed tasks deleted successfully"); + }) + .catch((error) => + console.error("Error removing done todos from API:", error) + ); + } + }, + }, + created() { + // Laden des gespeicherten Filters beim Initialisieren der App + const storedFilter = localStorage.getItem("todoFilter"); + if (storedFilter) { + this.filter = storedFilter; } - }); - - const label = document.createElement("label"); - label.innerText = task.description; - label.htmlFor = `checkbox-toggle-${task.id}`; - - const button = document.createElement("button"); - button.type = "button"; - button.textContent = "X"; - button.classList.add("delete-task"); - button.id = `delete-btn-${task.id}`; - - /* EVENT LISTENER - for delete one task - ========================================================================== */ - button.addEventListener("click", (event) => { - const taskId = parseInt(event.target.id.replace("delete-btn-", ""), 10); - deleteTodoFromApi(taskId); - }); - - div.append(checkbox, label); - li.append(div, button); - return li; -} - -/* EVENT LISTENER - for submit form -========================================================================== */ -taskForm.addEventListener("submit", (event) => { - event.preventDefault(); - - const isDuplicate = state.tasks.some( - (task) => - task.description.toLowerCase() === input.value.trim().toLowerCase() - ); - - if (isDuplicate) { - Swal.fire({ - position: "center", - popup: "swal2-show", - title: "Sorry", - text: "You can't add a duplicate task!", - icon: "warning", - confirmButtonText: "OK, thanks", - }); - } else if (input.value.trim() !== "") { - render(); - saveTodoToApi(); - input.value = ""; - input.focus(); - } -}); - -/* EVENT LISTENER - for filter radio buttons -========================================================================== */ -const filterRadios = document.querySelectorAll('input[name="filter"]'); -filterRadios.forEach((radio) => { - radio.addEventListener("change", () => { - state.currentFilter = radio.value; - render(); - }); -}); - -/* EVENT LISTENER - for clearing completed tasks -========================================================================== */ -clearButton.addEventListener("click", () => { - if (confirm("Do you really want to delete all completed tasks?")) { - removeDoneTodosFromApi(); - } -}); - -/* Function to initialize the application -========================================================================== */ -function initialize() { - getTodosFromApi(); - render(); -} + fetch(this.apiUrl) + .then((response) => response.json()) + .then((jsonData) => (this.todos = jsonData)); + }, +}).mount("#app"); diff --git a/script_joe.js b/script_joe.js new file mode 100644 index 0000000..950b0d2 --- /dev/null +++ b/script_joe.js @@ -0,0 +1,248 @@ +"use strict"; +import { fetchData, postData } from "./apiutils.js"; + +Vue.createApp({ + data() { + return {}; + }, + computed: {}, + methods: {}, +}).mount("#app"); + +/* State and initializing +========================================================================== */ + +// DOM elements +const taskForm = document.getElementById("task-form"); +const input = document.getElementById("input"); +const taskList = document.getElementById("task-list"); +const clearButton = document.getElementById("clear-btn"); +/* const apiUrl = "http://localhost:4730/todos"; + */ + +// State +const state = { + currentFilter: "all", // "all", "done", "open" + tasks: [], +}; + +initialize(); + +/* FUNCTION - to render filtered data from state +========================================================================== */ +function render() { + taskList.innerHTML = ""; + + // Filtering todos + const filteredTodos = state.tasks.filter((task) => { + if (state.currentFilter == "done") { + return task.done; // set task to done + } + if (state.currentFilter == "open") { + return !task.done; // set task to NOT done + } + return true; // unfiltered tasks + }); + + for (const task of filteredTodos) { + taskList.appendChild(generateTodoItemTemplate(task)); + } +} + +/* FUNCTION - to get todos from the API +========================================================================== */ +function getTodosFromApi() { + fetchData() // Nochmal anschauen + .then((todosFromApi) => { + state.tasks = todosFromApi; + render(); + }) + .catch((error) => { + console.error("Error getting todos from API:", error); + alert("Sorry, API is offline!"); + }); + //.finally(() => console.log("test if api is online and offline")); +} + +/* FUNCTION - to save a new todo to the API +========================================================================== */ +function saveTodoToApi() { + const newTodoText = input.value.trim(); + const newTodo = { description: newTodoText, done: false }; + + postData(newTodo) + .then((newTodoFromApi) => { + state.tasks.push(newTodoFromApi); + render(); + }) + .catch((error) => console.error("Error saving todo to API:", error)); +} + +/* FUNCTION - to update a todo in the API +========================================================================== */ +function updateTodoToApi(task) { + fetch(apiUrl + `/${task.id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(task), + }) + .then((response) => response.json()) + .then((updatedTodoFromApi) => { + // finding the position from the changed task in the local state + const index = state.tasks.findIndex( + (task) => task.id === updatedTodoFromApi.id + ); + // -1 means: not in this array + if (index !== -1) { + state.tasks[index] = updatedTodoFromApi; + render(); + } else { + console.warn("Updated task not found in local state."); + } + }) + .catch((error) => console.error("Error updating todo in API:", error)); +} + +/* FUNCTION - to delete a todo from the API +========================================================================== */ +function deleteTodoFromApi(taskId) { + fetch(apiUrl + `/${taskId}`, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + }) + .then(() => { + // Remove the task with the specified id from the state.tasks array + state.tasks = state.tasks.filter((task) => task.id !== parseInt(taskId)); + render(); + }) + .catch((error) => console.error("Error deleting todo from API:", error)); +} + +/* FUNCTION - to remove all completed todos from the API +========================================================================== */ +function removeDoneTodosFromApi() { + // Filter out completed todos from the current state + const doneTodos = state.tasks.filter((task) => task.done); + + // Use Promise.all for multiple API requests and delete many completed todos + //Promise All Settled + Promise.all( + doneTodos.map((doneTodo) => + fetch(apiUrl + `/${doneTodo.id}`, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + }) + ) + ) + .then(() => { + // Update the local state by filtering out completed todos + state.tasks = state.tasks.filter((task) => !task.done); + render(); + }) + .catch((error) => + console.error("Error removing done todos from API:", error) + ); +} + +/* FUNCTION - to generate HTML template for a todo item +========================================================================== */ +function generateTodoItemTemplate(task) { + const li = document.createElement("li"); + li.classList.add("task-item"); + li.id = `task-${task.id}`; + + const div = document.createElement("div"); + div.classList.add("checkbox-wrapper"); + + const checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.id = `checkbox-toogle-${task.id}`; + checkbox.classList.add("toggle-complete"); + checkbox.checked = task.done; + + /* EVENT LISTENER - for changing the checkbox status + ========================================================================== */ + checkbox.addEventListener("change", () => { + task.done = checkbox.checked; + checkbox.checked + ? checkbox.setAttribute("checked", "") + : checkbox.removeAttribute("checked"); + + // Check if the task is already in the API + const existingTask = state.tasks.find((t) => t.id === task.id); + + if (existingTask) { + // If present, update the task in the API + updateTodoToApi(task); + } else { + // If not present, it's a new task. Handle this case if needed. + console.warn("Task not found in API."); + } + }); + + const label = document.createElement("label"); + label.innerText = task.description; + label.htmlFor = `checkbox-toggle-${task.id}`; + + const button = document.createElement("button"); + button.type = "button"; + button.textContent = "X"; + button.classList.add("delete-task"); + button.id = `delete-btn-${task.id}`; + + /* EVENT LISTENER - for delete one task + ========================================================================== */ + button.addEventListener("click", (event) => { + const taskId = parseInt(event.target.id.replace("delete-btn-", ""), 10); + deleteTodoFromApi(taskId); + }); + + div.append(checkbox, label); + li.append(div, button); + return li; +} + +/* EVENT LISTENER - for submit form +========================================================================== */ +taskForm.addEventListener("submit", (event) => { + event.preventDefault(); + + const isDuplicate = state.tasks.some( + (task) => + task.description.toLowerCase() === input.value.trim().toLowerCase() + ); + + if (isDuplicate) { + alert("You can't add a duplicate task!"); + } else if (input.value.trim() !== "") { + render(); + saveTodoToApi(); + input.value = ""; + input.focus(); + } +}); + +/* EVENT LISTENER - for filter radio buttons +========================================================================== */ +const filterRadios = document.querySelectorAll('input[name="filter"]'); +filterRadios.forEach((radio) => { + radio.addEventListener("change", () => { + state.currentFilter = radio.value; + render(); + }); +}); + +/* EVENT LISTENER - for clearing completed tasks +========================================================================== */ +clearButton.addEventListener("click", () => { + if (confirm("Do you really want to delete all completed tasks?")) { + removeDoneTodosFromApi(); + } +}); + +/* Function to initialize the application +========================================================================== */ +function initialize() { + getTodosFromApi(); + render(); +} diff --git a/script_lw.js b/script_lw.js deleted file mode 100644 index fccfa35..0000000 --- a/script_lw.js +++ /dev/null @@ -1,276 +0,0 @@ -"use strict"; - -/* SETTING UP DOM ELEMENTS -========================================================================== */ -const taskForm = document.getElementById("task-form"); // reference to form -const input = document.getElementById("input"); // reference to inputfield -const taskList = document.getElementById("task-list"); // reference to task list -const clearButton = document.getElementById("clear-btn"); // reference to clear button -const checkboxes = document.querySelectorAll(".toggle-complete"); // get all checkboxes - -/* API ELEMENTS */ -const apiUrl = "http://localhost:4730/todos"; - -/* SETTING UP STATE -========================================================================== */ -const state = { - currentFilter: "all", // "all", "done", "open" - tasks: [], -}; - -initialize(); - -/* FUNCTION - GET TODOS FROM API -========================================================================== */ - -function getTodosFromApi() { - fetch(apiUrl) - .then((response) => response.json()) - .then((todosFromApi) => { - state.tasks = todosFromApi.map((apiTodo) => { - return { - id: apiTodo.id, - description: apiTodo.description, - done: apiTodo.done, - }; - }); - render(); - }) - .catch((error) => { - console.error("Error getting todos from API:", error); - }); -} - -/* FUNCTION - SAVE TODOS TO API -========================================================================== */ -function saveTodosToApi() { - //localStorage.setItem("tasks", JSON.stringify(state.tasks)); // save to localStorage - - const newTodoText = input.value; - const newTodo = { - description: newTodoText, - done: false, - }; - - fetch(apiUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(newTodo), - }) - .then((res) => res.json()) - .then((newTodoFromApi) => { - state.tasks.push(newTodoFromApi); // Beachten Sie die Änderung hier - render(); - }) - .catch((error) => { - console.error("Error saving todo to API:", error); - }); -} - -/* FUNCTION - UPDATE TASKS TO API -========================================================================== */ -function updateTodoToApi(task) { - fetch(apiUrl + `/${task.id}`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(task), - }) - .then((res) => res.json()) - .then((updatedTodoFromApi) => { - // Aktualisieren Sie den lokalen Zustand mit dem aktualisierten Task aus der API - let taskUpdated = false; - - for (let i = 0; i < state.tasks.length; i++) { - if (state.tasks[i].id === updatedTodoFromApi.id) { - state.tasks[i] = updatedTodoFromApi; - taskUpdated = true; - break; - } - } - - if (taskUpdated) { - render(); // Rendern Sie die Änderungen auf der Benutzeroberfläche - } else { - console.warn("Updated task not found in local state."); - } - }) - .catch((error) => { - console.error("Error updating todo in API:", error); - }); -} - -/* FUNCTION - DELETE TASK FROM API -========================================================================== */ -function deleteTodoFromApi(taskId) { - fetch(apiUrl + `/${taskId}`, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - }) - .then(() => { - state.tasks = state.tasks.filter((task) => task.id !== parseInt(taskId)); - render(); - }) - .catch((error) => { - console.error("Error deleting todo from API:", error); - }); -} - -/* FUNCTION - DELETE ALL TASKS FROM API -========================================================================== */ -function removeDoneTodosFromApi() { - const doneTodos = state.tasks.filter((task) => task.done); - - Promise.all( - doneTodos.map((doneTodo) => - fetch(apiUrl + `/${doneTodo.id}`, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - }) - ) - ) - .then(() => { - // Filtern Sie die erledigten Todos aus dem lokalen Zustand heraus - state.tasks = state.tasks.filter((task) => !task.done); - render(); - }) - .catch((error) => { - console.error("Error removing done todos from API:", error); - }); -} - -/* FUNCTION - GENERATE HTML TEMPLATE -========================================================================== */ -function generateTodoItemTemplate(task) { - const li = document.createElement("li"); - li.classList.add("task-item"); - li.id = "task-" + task.id; // Setzen Sie die ID für das li-Element - - const div = document.createElement("div"); - div.classList.add("checkbox-wrapper"); - - const checkbox = document.createElement("input"); - checkbox.type = "checkbox"; - checkbox.id = "checkbox-toogle-" + task.id; - checkbox.classList.add("toggle-complete"); - - checkbox.checked = task.done; // set "checked" attribut based on task.done - - // Event-Listener für das Ändern des Checkbox-Status hinzufügen - checkbox.addEventListener("change", function () { - task.done = checkbox.checked; - - if (checkbox.checked) { - checkbox.setAttribute("checked", ""); - } else { - checkbox.removeAttribute("checked"); - } - updateTodoToApi(task); - }); - - const label = document.createElement("label"); - label.innerText = task.description; - label.htmlFor = "checkbox-toggle-" + task.id; - - const button = document.createElement("button"); - button.type = "button"; - button.textContent = "X"; - button.classList.add("delete-task"); - button.id = "delete-btn-" + task.id; - - div.append(checkbox, label); - li.append(div, button); - return li; -} - -/* EVENT LISTENER - SUBMIT BUTTON -// ========================================================================== */ -taskForm.addEventListener("submit", (event) => { - event.preventDefault(); - - // Check for duplicates, if there are some tasks which - const isDuplicate = state.tasks.some( - (task) => - task.description.toLowerCase() === input.value.trim().toLowerCase() // check if task in array === inputValue - ); - - if (isDuplicate) { - alert("Sorry, you can't add a duplicate task!"); - } else { - if (input.value.trim() !== "") { - // Render state - render(); - saveTodosToApi(); - - input.value = ""; // clear input field - input.focus(); // focus on input field for the next task - } else { - return; // do nothing - } - } -}); - -/* EVENT LISTENER - FILTERING -========================================================================== */ -const filterRadios = document.querySelectorAll('input[name="filter"]'); // select radio inputs -filterRadios.forEach((radio) => { - radio.addEventListener("change", function () { - state.currentFilter = this.value; // set currentfilter to this value - render(); - }); -}); - -/* EVENT LISTENER - CLEAR DONE TASKS -========================================================================== */ -clearButton.addEventListener("click", function () { - if (confirm("Do you really want to delete all completed tasks?")) { - removeDoneTodosFromApi(); - } -}); - -/* FUNCTION - RENDER DATA FROM STATE -========================================================================== */ -function render() { - taskList.innerHTML = ""; - - // Filtering - const filteredTodos = state.tasks.filter((task) => { - if (state.currentFilter == "done") { - return task.done; // set task to done - } - if (state.currentFilter == "open") { - return !task.done; // set task to NOT done - } - return true; // unfiltered tasks - }); - - for (const task of filteredTodos) { - const newTodoItem = generateTodoItemTemplate(task); - taskList.appendChild(newTodoItem); - } - - /* EVENT-LISTENER - REMOVE BUTTON -========================================================================== */ - const deleteButtons = document.querySelectorAll(".delete-task"); - deleteButtons.forEach((button) => { - button.addEventListener("click", () => { - const buttonId = button.id; // save the button ID - const taskId = buttonId.replace("delete-btn-", ""); // extract the ID - deleteTodoFromApi(taskId); - }); - }); -} - -/* FUNCTION - GET TODOS AND RENDER -========================================================================== */ -function initialize() { - getTodosFromApi(); - render(); -} diff --git a/script_old.js b/script_old.js new file mode 100644 index 0000000..4e54013 --- /dev/null +++ b/script_old.js @@ -0,0 +1,269 @@ +"use strict"; + +/* State and initializing +========================================================================== */ + +// DOM elements +const taskForm = document.getElementById("task-form"); +const input = document.getElementById("input"); +const taskList = document.getElementById("task-list"); +const clearButton = document.getElementById("clear-btn"); +const apiUrl = "http://localhost:4730/todos"; + +// Modal Styling + +const corner = Swal.mixin({ + toast: true, + position: "top-end", + title: "Sorry, API is offline!", + text: "Start server with: npm run start", + showConfirmButton: false, + timer: 3000, + timerProgressBar: true, + didOpen: (toast) => { + toast.onmouseenter = Swal.stopTimer; + toast.onmouseleave = Swal.resumeTimer; + }, +}); + +// State +const state = { + currentFilter: "all", // "all", "done", "open" + tasks: [], +}; + +initialize(); + +/* FUNCTION - to render filtered data from state +========================================================================== */ +function render() { + taskList.innerHTML = ""; + + // Filtering todos + const filteredTodos = state.tasks.filter((task) => { + if (state.currentFilter == "done") { + return task.done; // set task to done + } + if (state.currentFilter == "open") { + return !task.done; // set task to NOT done + } + return true; // unfiltered tasks + }); + + for (const task of filteredTodos) { + taskList.appendChild(generateTodoItemTemplate(task)); + } +} + +/* FUNCTION - to get todos from the API +========================================================================== */ +function getTodosFromApi() { + fetch(apiUrl) + .then((response) => response.json()) + .then((todosFromApi) => { + state.tasks = todosFromApi; + render(); + }) + .catch((error) => { + console.error("Error getting todos from API:", error); + corner.fire({ + icon: "error", + title: "Sorry, API is offline!", + }); + }); + //.finally(() => console.log("test if api is online and offline")); +} + +/* FUNCTION - to save a new todo to the API +========================================================================== */ +function saveTodoToApi() { + const newTodoText = input.value.trim(); + const newTodo = { description: newTodoText, done: false }; + + fetch(apiUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(newTodo), + }) + .then((response) => response.json()) + .then((newTodoFromApi) => { + state.tasks.push(newTodoFromApi); + render(); + }) + .catch((error) => console.error("Error saving todo to API:", error)); +} + +/* FUNCTION - to update a todo in the API +========================================================================== */ +function updateTodoToApi(task) { + fetch(apiUrl + `/${task.id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(task), + }) + .then((response) => response.json()) + .then((updatedTodoFromApi) => { + // finding the position from the changed task in the local state + const index = state.tasks.findIndex( + (task) => task.id === updatedTodoFromApi.id + ); + // -1 means: not in this array + if (index !== -1) { + state.tasks[index] = updatedTodoFromApi; + render(); + } else { + console.warn("Updated task not found in local state."); + } + }) + .catch((error) => console.error("Error updating todo in API:", error)); +} + +/* FUNCTION - to delete a todo from the API +========================================================================== */ +function deleteTodoFromApi(taskId) { + fetch(apiUrl + `/${taskId}`, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + }) + .then(() => { + // Remove the task with the specified id from the state.tasks array + state.tasks = state.tasks.filter((task) => task.id !== parseInt(taskId)); + render(); + }) + .catch((error) => console.error("Error deleting todo from API:", error)); +} + +/* FUNCTION - to remove all completed todos from the API +========================================================================== */ +function removeDoneTodosFromApi() { + // Filter out completed todos from the current state + const doneTodos = state.tasks.filter((task) => task.done); + + // Use Promise.all for multiple API requests and delete many completed todos + Promise.all( + doneTodos.map((doneTodo) => + fetch(apiUrl + `/${doneTodo.id}`, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + }) + ) + ) + .then(() => { + // Update the local state by filtering out completed todos + state.tasks = state.tasks.filter((task) => !task.done); + render(); + }) + .catch((error) => + console.error("Error removing done todos from API:", error) + ); +} + +/* FUNCTION - to generate HTML template for a todo item +========================================================================== */ +function generateTodoItemTemplate(task) { + const li = document.createElement("li"); + li.classList.add("task-item"); + li.id = `task-${task.id}`; + + const div = document.createElement("div"); + div.classList.add("checkbox-wrapper"); + + const checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.id = `checkbox-toogle-${task.id}`; + checkbox.classList.add("toggle-complete"); + checkbox.checked = task.done; + + /* EVENT LISTENER - for changing the checkbox status + ========================================================================== */ + checkbox.addEventListener("change", () => { + task.done = checkbox.checked; + checkbox.checked + ? checkbox.setAttribute("checked", "") + : checkbox.removeAttribute("checked"); + + // Check if the task is already in the API + const existingTask = state.tasks.find((t) => t.id === task.id); + + if (existingTask) { + // If present, update the task in the API + updateTodoToApi(task); + } else { + // If not present, it's a new task. Handle this case if needed. + console.warn("Task not found in API."); + } + }); + + const label = document.createElement("label"); + label.innerText = task.description; + label.htmlFor = `checkbox-toggle-${task.id}`; + + const button = document.createElement("button"); + button.type = "button"; + button.textContent = "X"; + button.classList.add("delete-task"); + button.id = `delete-btn-${task.id}`; + + /* EVENT LISTENER - for delete one task + ========================================================================== */ + button.addEventListener("click", (event) => { + const taskId = parseInt(event.target.id.replace("delete-btn-", ""), 10); + deleteTodoFromApi(taskId); + }); + + div.append(checkbox, label); + li.append(div, button); + return li; +} + +/* EVENT LISTENER - for submit form +========================================================================== */ +taskForm.addEventListener("submit", (event) => { + event.preventDefault(); + + const isDuplicate = state.tasks.some( + (task) => + task.description.toLowerCase() === input.value.trim().toLowerCase() + ); + + if (isDuplicate) { + Swal.fire({ + position: "center", + popup: "swal2-show", + title: "Sorry", + text: "You can't add a duplicate task!", + icon: "warning", + confirmButtonText: "OK, thanks", + }); + } else if (input.value.trim() !== "") { + render(); + saveTodoToApi(); + input.value = ""; + input.focus(); + } +}); + +/* EVENT LISTENER - for filter radio buttons +========================================================================== */ +const filterRadios = document.querySelectorAll('input[name="filter"]'); +filterRadios.forEach((radio) => { + radio.addEventListener("change", () => { + state.currentFilter = radio.value; + render(); + }); +}); + +/* EVENT LISTENER - for clearing completed tasks +========================================================================== */ +clearButton.addEventListener("click", () => { + if (confirm("Do you really want to delete all completed tasks?")) { + removeDoneTodosFromApi(); + } +}); + +/* Function to initialize the application +========================================================================== */ +function initialize() { + getTodosFromApi(); + render(); +} diff --git a/style.css b/style.css index 68b50f7..4bc19fa 100644 --- a/style.css +++ b/style.css @@ -52,7 +52,7 @@ main { justify-content: space-between; } -#task-list { +.task-list { padding-left: 0; } .task-item { @@ -71,24 +71,29 @@ main { } .checkbox-wrapper { + width: 100%; display: flex; - justify-content: flex-start; + justify-content: space-between; align-items: center; - gap: 1rem; + gap: 2rem; } /* Description */ -label { +.hidden { + position: absolute; + left: -9999px; +} +.checkbox-wrapper label { cursor: pointer; color: black; + flex-grow: 2; } input[type="checkbox"]:checked + label { text-decoration: line-through; font-style: italic; color: var(--gradient1-color); - flex-grow: 1; } .task-item:has(input[type="checkbox"]:checked) {