javascriptdomdom-events

How to target a specific element of an array?


I'm currently working on a To-do-list from the odin project. As per the instructions, my todolist can create a new project, which we can then add tasks to the project. I now have a sidebar listing all existing projects, and when I click on any of the projects, it renders the details of the project onto the main body. I've attached screenshots of the page

before I click on a project

after I click on a project

I'm currently having trouble trying to add new tasks to the project that's active on the main body as I can't get its index.

In my addTaskModal.js module, I've created a dialog popup for users to input the details of the tasks to be added to the active project. But I've been stuck for hours and I can't figure out how to get the index of the project that's rendered on the main page.

Here's my code that renders the project to the body:

import {
  addTaskModal
} from './addTaskModal.js';
import {
  myProjects
} from './myProjects.js';

const mainContent = document.querySelector('#main');

export default function renderMain(event) { //render project from sidebar to mainContent body

  mainContent.textContent = '';
  mainContent.appendChild(addTaskModal); //dialog to input new task and its priority

  let index = parseInt(event.target.id.split('-')[1]);



  const projectCard = document.createElement('div');
  projectCard.classList.add('card');

  const projectCardHeader = document.createElement('h3');
  projectCardHeader.classList.add('cardheader');
  projectCardHeader.textContent = myProjects[index].name
  projectCard.appendChild(projectCardHeader);

  const projectDue = document.createElement('div');
  projectDue.textContent = myProjects[index].due;
  projectDue.classList.add('date');
  projectCard.appendChild(projectDue);

  for (let i = 0; i < myProjects[index].tasks?.length; i++) {

    const tasks = document.createElement('div');
    const deleteBtn = document.createElement('button');
    deleteBtn.textContent = 'Delete Task';
    deleteBtn.addEventListener('click', () => {
      myProjects[index].tasks.splice(i, 1);
      localStorage.setItem('projects', JSON.stringify(myProjects));
      renderMain(event);
    })

    //when we want to update after delete, just store with same "key" and the "key" will be updated.
    tasks.textContent = myProjects[index].tasks[i].name;
    tasks.classList.add('tasks');
    tasks.appendChild(deleteBtn);
    projectCard.appendChild(tasks);
  }
  const addTaskBtn = document.createElement('button');
  addTaskBtn.textContent = "Add Task";
  mainContent.appendChild(addTaskBtn);
  addTaskBtn.addEventListener('click', () => {
    addTaskModal.showModal();
  });
  mainContent.appendChild(addTaskBtn);
  mainContent.appendChild(projectCard)
}

Here's the code for myProjects array for context:

let myProjects = JSON.parse(localStorage.getItem('projects')) || [{
    name: "Go to supermarket",
    due: "12/2/25",
    tasks: [{
        name: "Buy apples",
        priority: "high"
      },
      {
        name: "Buy Eggs",
        priority: "medium"
      }
    ]
  },
  {
    name: "Clean the house",
    due: "13/2/25",
    tasks: [{
        name: "Sweep and mop the floor",
        priority: "high"
      },
      {
        name: "Do laundry",
        priority: "high"
      }
    ]
  }
];

export {
  myProjects
};

Here's the addTaskModal.js to create a popup dialog for users to input details of the new task

import { myProjects } from "./myProjects";

const addTaskModal = document.createElement('dialog');

addTaskModal.classList.add('modal');
//const addTaskModal = document.createElement('div');

//addTaskModal.classList.add('hidden');

//create label and input box for user to input new task title
const newTask = document.createElement('div');
newTask.classList.add('modallabel');
newTask.textContent = "New Task :";
const newTaskInput = document.createElement('input');
newTaskInput.setAttribute('placeholder', 'Add New Task Here');
newTaskInput.setAttribute('id', 'newtaskinput');
newTaskInput.classList.add('inputbox');
newTask.appendChild(newTaskInput);

//create label and select input for user to input new task priority
const newPriority = document.createElement('div');
newPriority.textContent = "Priority: ";
newPriority.classList.add('prioritylabel');
const newPriorityInput = document.createElement('select');
newPriorityInput.setAttribute('id', 'newpriorityinput');

//add options to select
const selectHigh = document.createElement('option');
selectHigh.textContent = "High";
selectHigh.setAttribute('value', 'High')
newPriorityInput.appendChild(selectHigh);
const selectMedium = document.createElement('option');
selectMedium.textContent = "Medium";
newPriorityInput.appendChild(selectMedium);
const selectLow = document.createElement('option');
selectLow.textContent = "Low";
newPriorityInput.appendChild(selectLow);
newPriority.appendChild(newPriorityInput);

//button to submit new task to the project
const submitNewTaskBtn = document.createElement('button');
submitNewTaskBtn.textContent = "Add Task";




addTaskModal.appendChild(newTask);
addTaskModal.appendChild(newPriority);
addTaskModal.appendChild(submitNewTaskBtn);


export { addTaskModal };
export { submitNewTaskBtn };

Here's the main index.js where I have the renderSidebar() function to populate the sidebar with the projects list:

import './styles.css';
import { myProjects } from './myProjects.js';
import renderMain from './renderMain.js';
import createTask from './addTask.js';
import { addTaskModal } from './addTaskModal.js';





const mainContent = document.querySelector('#main');
const sideBar = document.querySelector('#sidebarproject');






function renderSidebar() {
    let sidebarHTML = '';
    myProjects.forEach((project, index) => {
        sidebarHTML += `<div class="projects" id="project-${index}">${project.name}</div>`

    })
    sideBar.innerHTML += sidebarHTML;
    const projects = document.querySelectorAll('.projects');
    projects.forEach(project => 
        project.addEventListener('click', renderMain)
    )
}




renderSidebar();






mainContent.appendChild(addTaskModal);


Solution

  • I notice

    renderMain(event)
    

    and

    let index = parseInt(event.target.id.split('-')[1]);
    

    This is not going to work if the event you are sending to the rendermain does not have an ID, like your delete button.

    I suggest you move

    localStorage.setItem('projects', JSON.stringify(myProjects)); 
    

    to the rendermain and change

    function renderMain(event) {
      let index = parseInt(event.target.id.split('-')[1]);
    

    to

    function renderMain(index,projectName)
    

    I ALSO suggest you delegate the click to the container, then you can do

    deleteBtn.classList.add('delete');   
    deleteBtn.textContent = 'Delete Task';
    deleteBtn.dataset.index = i;
    deleteBtn.dataset.projectname = projectName;
    

    and change

    deleteBtn.addEventListener('click', () => { 
      myProjects[index].tasks.splice(i, 1);
      localStorage.setItem('projects', JSON.stringify(myProjects)); 
      renderMain(event)
    }) 
    

    to

    mainContent.addEventListener('click', (e) => { 
      const tgt = e.target.closest('button.delete'); 
      if (!tgt) return; 
      myProjects[tgt.dataset.index].tasks.splice(tgt.dataset.index, 1);
      renderMain(tgt.dataset.index,tgt.dataset.projectname);
    });
    

    outside the function

    Here is a simpler version of your render

    const renderSidebar => () {
      sideBar.innerHTML = myProjects
      .map((project, index) => `<div class="projects" data-name="${project.name}" data-index="${index}">${project.name}</div>`).join('');
    };
    sideBar.addEventListener('click', (e) => {
      const tgt = e.target.closest('div.projects');
      if (!tgt) return; // we did not click in a project
      renderMain(tgt.dataset.index, tgt.dataset.name)
    })
    

    In your modal, you need to capture the index

    Here is the changes to addModal

    submitNewTaskBtn.addEventListener('click', () => {
      myProjects.tasks.push(newTask);
      renderMain(myProjects.length-1,newTaskInput.value);
    })