I need help with my javaScript file in my To Do List project, I thought i could handle it but there is too much to think about in this type of project and i am overwhelmed, if someone could help with the list saving logic then reach me out and I will send the details of what I would like to implement.
project repo: https://github.com/Dudek10/To-Do-List-Advanced-Project
I have tried to implement the logic of renaming each list but it got too complicated and I don’t want to mess things up even more, I will additionaly send the JS file:
let tasks = [];
// Constants LocalStorage keys
const TASKS_KEY = 'tasks';
const CONTAINER_KEY = 'containerOpenState';
const TASK_INPUT_KEY = 'taskInputDraft';
const SAVED_LISTS_KEY = 'SavedLists';
const SAVE_LIMIT = 5;
// Constants for functions like 'addTask()' etc.
const tasksDiv = document.getElementById("tasks");
const input = document.getElementById("TaskInput");
const addTaskButton = document.getElementById("AddTaskButton");
//main DOM content
document.addEventListener('DOMContentLoaded', () => {
// the list cotainer logic
const OpenListBtn = document.getElementById("OpenList");
const DeleteAllTaskBtn = document.getElementById("TaskDelete");
const NameForm = document.querySelector(".name-form");
const Container = document.querySelector(".container");
const SaveBtn = document.querySelector(".save-button");
const IconClose = document.querySelector(".icon-close");
// Checks whether container is open
const savedState = localStorage.getItem(CONTAINER_KEY);
if (Container && NameForm && SaveBtn) {
if (savedState === 'open') {
Container.classList.add('visible');
NameForm.classList.add('visible');
SaveBtn.classList.add('visible');
} else {
Container.classList.remove('visible');
NameForm.classList.remove('visible');
SaveBtn.classList.remove('visible');
}
}
// Opens the list and sets the state to local storage
if (OpenListBtn && Container && NameForm && SaveBtn) {
OpenListBtn.addEventListener('click', () => {
Container.classList.add('visible');
NameForm.classList.add('visible');
SaveBtn.classList.add('visible');
localStorage.setItem(CONTAINER_KEY, 'open');
});
}
if (IconClose && Container && NameForm && SaveBtn) {
IconClose.addEventListener('click', () => {
Container.classList.remove('visible');
NameForm.classList.remove('visible');
SaveBtn.classList.remove('visible');
localStorage.setItem(CONTAINER_KEY, 'closed');
});
}
// draft input retrieve
const draft = localStorage.getItem(TASK_INPUT_KEY);
if (draft !== null) input.value = draft;
input.addEventListener('input', () => {
localStorage.setItem(TASK_INPUT_KEY, input.value);
});
// Load tasks on startup
loadTasks();
renderSavedListsDropdown();
// Saved list logic
const SavedListBtn = document.getElementById("SavedLists");
const SavedListsDropdown = document.querySelector('.saved-lists-dropdown');
const ListSavingBtn = document.querySelector('.save-button');
// Toggle dropdown visibility when clicking SavedLists button
SavedListBtn.addEventListener('click', (e) => {
e.stopPropagation();
SavedListsDropdown.classList.toggle('show');
});
// Save the current tasks under the typed name
ListSavingBtn.addEventListener('click', () => {
//first we declare the things we want to save
const nameInput = document.getElementById("NameBtn");
const name = (nameInput?.value || '').trim();
if (!name) return; // don’t save if no name
if (tasks.length === 0) return;
//makes a map of all the elements
const map = getSavedListsMap();
//sets a variable for the names and then checks their number
const existingNames = Object.keys(map);
if (!map[name] && existingNames.length >= SAVE_LIMIT) {
return;
}
map[name] = [...tasks]; // store a copy of tasks under given name
localStorage.setItem(SAVED_LISTS_KEY, JSON.stringify(map));
//render the saved lists
renderSavedListsDropdown();
SavedListsDropdown.classList.add('show');
})
// Load a saved list when clicking its button in the dropdown
SavedListsDropdown.addEventListener('click', (e) => {
const btn = e.target.closest('.saved-list-button');
if (!btn) return;
const name = btn.dataset.name;
const map = getSavedListsMap();
// Show the list and fill the name field
document.getElementById('NameBtn').value = name;
tasks = map[name] ? [...map[name]] : [];
Container.classList.add('visible');
NameForm.classList.add('visible');
SaveBtn.classList.add('visible');
renderTasks(); // render loaded tasks
});
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!SavedListBtn.contains(e.target) && !SavedListsDropdown.contains(e.target)) {
SavedListsDropdown.classList.remove('show');
}
});
// mobile toogle button for nav menu
const toggleBtn = document.querySelector('.ToggleBtn');
const dropdownMenu = document.querySelector('.DropdownMenu');
const toggleBtnIcon = toggleBtn?.querySelector('i');
let isOpen = false;
// on button click change its icon to appropriate one
if (toggleBtn && dropdownMenu && toggleBtnIcon) {
toggleBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (!isOpen) {
dropdownMenu.classList.add('show');
dropdownMenu.classList.remove('hide');
toggleBtnIcon.className = 'fa-solid fa-xmark';
} else {
dropdownMenu.classList.add('hide');
dropdownMenu.classList.remove('show');
toggleBtnIcon.className = 'fa-solid fa-bars';
}
isOpen = !isOpen;
});
}
// Delete all tasks
if (DeleteAllTaskBtn) {
DeleteAllTaskBtn.addEventListener('click', () => {
const items = tasksDiv.querySelectorAll('.task');
if (items.length === 0) return;
let remaining = items.length;
items.forEach((el, i) => {
// set delay for each item
el.style.transitionDelay = `${i * 0.3}s`;
el.classList.add('erasing');
el.addEventListener('transitionend', () => {
remaining--;
if (remaining === 0) {
tasks.length = 0;
renderTasks();
saveTasks();
}
}, { once: true }); // the transition listener will only run once (the css have two transitions so it would double the iteration number)
});
});
}
// Add task event
addTaskButton.addEventListener('click', addTask);
});
//helper for saved lists
function getSavedListsMap() {
try {
return JSON.parse(localStorage.getItem(SAVED_LISTS_KEY)) || {};
} catch {
return {};
}
}
function renderSavedListsDropdown() {
const SavedListsDropdown = document.querySelector('.saved-lists-dropdown');
const map = getSavedListsMap();
SavedListsDropdown.innerHTML = ''; //clears the template buttons
Object.keys(map).forEach(name => {
const btn = document.createElement('button');
btn.className = 'saved-list-button';
btn.textContent = name;
btn.dataset.name = name;
SavedListsDropdown.appendChild(btn);
})
}
//tasks persistence
function loadTasks() {
const oldTasks = localStorage.getItem(TASKS_KEY);
if (oldTasks) {
tasks = JSON.parse(oldTasks);
renderTasks();
}
}
function saveTasks() {
localStorage.setItem(TASKS_KEY, JSON.stringify(tasks));
}
// tasks allingment and style
function renderTasks() {
tasksDiv.innerHTML = "";
tasks.forEach((task, idx) => {
const div = document.createElement('div');
div.classList.add('task');
div.style.marginBottom = '10px';
const text = document.createElement('p');
text.textContent = task;
Object.assign(text.style, {
display: 'inline',
marginRight: '10px',
fontWeight: 'bold',
fontSize: '1.3rem'
});
const button = document.createElement('button');
button.innerHTML = '<ion-icon name="trash-outline" style="font-size: 1rem; color: white;"></ion-icon>';
Object.assign(button.style, {
backgroundColor: '#2F1E2E',
color: '#ffffff',
border: 'none',
fontSize: '1.5rem',
cursor: 'pointer',
padding: '6px 8px',
borderRadius: '8px',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
height: '1.9rem'
});
button.addEventListener('mouseenter', () => button.style.backgroundColor = '#3C2E3B');
button.addEventListener('mouseleave', () => button.style.backgroundColor = '#2F1E2E');
// Trigger CSS animation and remove the task after the transition end
button.onclick = () => {
div.classList.add('erasing');
div.addEventListener('transitionend', () => {
removeTask(idx);
}, { once: true }); // same case as for the delete all tasks button
};
div.appendChild(text);
div.appendChild(button);
tasksDiv.appendChild(div);
});
}
function addTask() {
const value = input.value.trim();
const OriginalPlaceholder = input.placeholder;
// Helper: show placeholder error messages briefly
const ShowPlaceholderWarning = (msg) => {
input.value = "";
input.placeholder = msg;
// clear previous timer
if (input._phTimer) clearTimeout(input._phTimer);
// Restore original placeholder when user is typing
const onType = () => {
input.placeholder = OriginalPlaceholder;
input.removeEventListener('input', onType);
if (input._phTimer) {
clearTimeout(input._phTimer);
input._phTimer = null;
}
};
input.addEventListener('input', onType, { once: true });
//restore after 2s
input._phTimer = setTimeout(() => {
input.placeholder = OriginalPlaceholder;
input.removeEventListener('input', onType);
input._phTimer = null;
}, 2000);
};
// validations
if (!/^[a-zA-Z0-9 ]*$/.test(value)) return ShowPlaceholderWarning("Task contains invalid characters!");
if (!value) return ShowPlaceholderWarning("Please enter a task!");
if (value.length > 45) return ShowPlaceholderWarning("Task exceeds 45 characters!");
if (tasks.includes(value)) return ShowPlaceholderWarning("Task already exists!");
if (tasks.length >= 9) return ShowPlaceholderWarning("Maximum number of tasks reached");
tasks.push(value);
renderTasks();
input.value = "";
localStorage.removeItem(TASK_INPUT_KEY);
saveTasks();
}
function removeTask(idx) {
tasks.splice(idx, 1);
renderTasks();
saveTasks();
}
// input events
input.addEventListener('keyup', (event) => {
if (event.key === 'Enter') addTask();
});