Home > front end >  Strange behaviour shown when updating a card with a click event that is within another click event
Strange behaviour shown when updating a card with a click event that is within another click event

Time:10-03

I have a Create Word Card button that brings up a form where the user inputs a word to create a card with the word on it. The card also has an edit and delete button. Assuming 2 cards are created, clicking the edit card on the first card will bring up a form to edit the word on the card. Once the word is changed and the button on the edit form is clicked, the word on the card is changed to the new word. However, trying to edit only the second card after the first card has been edited causes both the first and the second card to be edited. Please, how do I fix this? The code is below.

// DOM Element Selectors
const  cardContainer = document.querySelector('#card-container');
const createCardBtn = document.querySelector('#create-card-btn');
const createCardOverlay = document.querySelector('#create-card-overlay');
const createCardModal = document.querySelector('#create-card-modal');
const createCardModalTitle = document.querySelector('#create-card-modal h3');
const form = document.querySelector('form');
const formInput = document.querySelector('.form-control input');
const addCardBtn = document.querySelector('#create-card-modal button');

// Event Listeners
createCardBtn.addEventListener('click', showForm);
createCardOverlay.addEventListener('click', hideForm);
createCardModal.addEventListener('click', event => {
  event.stopPropagation();
});
addCardBtn.addEventListener('click', addCardToArray);

cardArray = [];

function showForm() {
  form.reset();
  createCardOverlay.style.display = 'flex';
}

function hideForm() {
  createCardModalTitle.textContent = 'Create Word Card'
  addCardBtn.textContent = 'Create Word Card'
  createCardOverlay.style.display = 'none';
}

function addCardToArray(event) {
  event.preventDefault();
  if (formInput.value === '' || createCardModalTitle.textContent === 'Edit Word Card') return;

  let cardWord = formInput.value;
  cardArray.push(cardWord);
  console.log(cardArray);

  hideForm();
  displayCard();
}

function displayCard() {
  cardContainer.innerHTML = '';

  cardArray.forEach(card => {
    let btnContainer = document.createElement('div');
    btnContainer.className = 'btn-container';
    
    html = 
    `<h2>${card}</h2>
    <button >Edit</button>
    <button >Delete</button>`;

    btnContainer.innerHTML = html;
    cardContainer.appendChild(btnContainer);
  });

  deleteButton();
  editButton();
}

function deleteButton() {
  const deleteBtns = document.querySelectorAll('.delete-btn');

  [...deleteBtns].forEach(deleteBtn => {
    deleteBtn.addEventListener('click', () => {
      const dbIndex = [...cardContainer.children].indexOf(deleteBtn.parentElement);
      cardArray.splice(dbIndex, 1);
      deleteBtn.parentElement.remove();

      console.log(cardArray);
    });
  });
}

function editButton() {
  const editBtns = document.querySelectorAll('.edit-btn');
  
  [...editBtns].forEach(editBtn => {
    editBtn.addEventListener('click', event => {
      event.stopPropagation();
      const card = editBtn.parentElement;
      const cardIndex = [...cardContainer.children].indexOf(card);
      
      showForm();
      createCardModalTitle.textContent = 'Edit Word Card'
      addCardBtn.textContent = 'Edit Word Card'
      formInput.value = cardArray[cardIndex];

      addCardBtn.addEventListener('click', event => {
        event.stopPropagation();
        event.preventDefault();
        cardArray[cardIndex] = formInput.value;

        [...card.children][0].textContent = formInput.value;
        hideForm();
        console.log(cardArray);
      });
    });
  })
}
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6rem;
  padding: 5rem 0;
}

#create-card-btn-container {
  display: flex;
  justify-content: center;

}

#create-card-btn {
  padding: .5rem 2rem;
}

#card-container {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  justify-content: center;
  align-items: center;
  row-gap: 4rem;
}

.btn-container {
  background: #c4c4c4;
  text-align: center;
  border: 3px solid #666666;
  padding: 4rem 6rem;
  margin: 0rem 2rem;
}

.btn-container h2 {
  margin-bottom: 1rem;
}

.edit-btn {
  padding: .2rem 1.5rem;
  margin-bottom: .5rem;
}

.delete-btn {
  padding: .2rem 1.5rem;
}

#create-card-overlay {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: none;
  justify-content: center;
  align-items: center;
  background-color: #00000088;
}

#create-card-overlay > #create-card-modal {
  padding: 1rem 1.2rem;
  background-color: #c4c4c4;
  border: 3px solid #333333;
}

#create-card-overlay h3 {
  font-size: 1.5rem;
  margin-bottom: 1rem;
}

#create-card-overlay form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

#create-card-overlay form button {
  padding: .2rem 0;
}

#create-card-overlay .form-control {
  display: flex;
  flex-direction: column;
}

#create-card-overlay .form-control input {
  padding: .2rem .3rem; 
}
<body>
    <section id="create-card-btn-container">
      <button id="create-card-btn">Create Word Card</button>
    </section>

    <section id="card-container">
      
    </section>

    <section id="create-card-overlay">
      <div id="create-card-modal">
        <h3>Create Word Card</h3>
        <form>
          <div >
            <label for="word">Add a word</label>
            <input type="text" name="word" id="word">
          </div>
          <button>Create Word Card</button>
        </form>
      </div>
    </section>
    <script src="script.js"></script>
  </body>

CodePudding user response:

I'd advice you to find a better way to handle state of your application and refactor your code because your editButton function introduces a big memory leak issue by constantly adding new event listeners to the same buttons without a clear mechanism for removing them. This can quickly become a recipe for disaster as you add more and more functionalities:

function editButton() {
  const editBtns = document.querySelectorAll(".edit-btn");

  [...editBtns].forEach((editBtn) => {
    // you're adding the same event listener to all edit buttons
    // every time editButton function is called
    editBtn.addEventListener("click", (event) => {
      event.stopPropagation();
      const card = editBtn.parentElement;
      const cardIndex = [...cardContainer.children].indexOf(card);

      showForm();
      createCardModalTitle.textContent = "Edit Word Card";
      addCardBtn.textContent = "Edit Word Card";
      formInput.value = cardArray[cardIndex];

      // on top of that, every time any of the edit buttons gets clicked 
      // you're adding more event listeners to the addCardButton making it even worse
      addCardBtn.addEventListener("click", (event) => {
        event.stopPropagation();
        event.preventDefault();
        cardArray[cardIndex] = formInput.value;

        [...card.children][0].textContent = formInput.value;
        hideForm();
        console.log(cardArray);
      });
    });
  });
}

Here are some suggestions just in case you want to follow another approach (I didn't test this implementation but I think you'll get the idea):

// manage ui changes and events by using a more declarative application state:
const state = {
  currentEditCardId: null, // id of a particular card being edited
  cards: [
    // list of all cards
    {
      id: 0, // adding unique identifier per card could be a good idea
      content: "Text",
    },
  ],
};

function displayCards() {
  cardContainer.innerHTML = "";

  state.cards.forEach((card) => {
    let btnContainer = document.createElement("div");
    btnContainer.className = "btn-container";

    // associate card container with state entity:
    btnContainer.setAttribute("data-id", card.id);

    html = `<h2>${card}</h2>
    <button >Edit</button>
    <button >Delete</button>`;

    btnContainer.innerHTML = html;
    cardContainer.appendChild(btnContainer);
  });
}

// util function to check if target or closest target parent has a given class:
const isElement = (target, className) =>
  target.classList.has(className) || !!target.closest(`.${className}`);

// extract card id from card container's dataset:
const getCardId = (target) =>
  Number(target.closest(".btn-container")?.dataset.id);

// update a given card by its id:
const updateCardById = (id, data) => {
  state.cards = state.cards.map((card) => {
    // if the id is valid, the card gets updated:
    if (card.id === id) return { ...card, ...data };

    return card;
  });

  // update ui:
  displayCards();
}

// add one single click listener for all actions and for the entire lifetime of your app.
// this will take care of dinamically created buttons too:
document.body.addEventListener("click", (event) => {
  if (isElement(event.target, "edit-btn")) {
    // display form modal...
    const id = getCardId(event.target);
    state.currentEditCardId = id;
    showForm();
    // you could toggle some kind of "edit" class on the form button
    // to know if its editing or adding, i.e. "form-modal-btn-edit"
  }

  if (isElement(event.target, "form-modal-btn-edit")) {
    // edit card:
    const content = getInputFromForm();
    updateCardById(state.currentEditCardId, {
      content,
    });
    state.currentEditCardId = null;
  }

  if (isElement(event.target, "delete-btn")) {
    // delete card from state...
  }

  if (isElement(event.target, "form-modal-btn-add")) {
    // add new card to state...
  }
});

CodePudding user response:

Everything works

Change this

addCardBtn.addEventListener("click", (event) => {
 event.stopPropagation();
 event.preventDefault();
 cardArray[cardIndex] = formInput.value;

 [...card.children][0].textContent = formInput.value;
 hideForm();
 console.log(cardArray);

});

to this

addCardBtn.onclick = (event) => {
 event.stopPropagation();
 event.preventDefault();
 cardArray[cardIndex] = formInput.value;

 [...card.children][0].textContent = formInput.value;

 console.log(cardArray);

};

It only adds one event.

// DOM Element Selectors
const cardContainer = document.querySelector("#card-container");
const createCardBtn = document.querySelector("#create-card-btn");
const createCardOverlay = document.querySelector("#create-card-overlay");
const createCardModal = document.querySelector("#create-card-modal");
const createCardModalTitle = document.querySelector("#create-card-modal h3");
const form = document.querySelector("form");
const formInput = document.querySelector(".form-control input");
const addCardBtn = document.querySelector("#create-card-modal button");

// Event Listeners
createCardBtn.addEventListener("click", showForm);
createCardOverlay.addEventListener("click", hideForm);
createCardModal.addEventListener("click", (event) => {
    event.stopPropagation();
});
addCardBtn.addEventListener("click", addCardToArray);

cardArray = [];

function showForm() {
    form.reset();
    createCardOverlay.style.display = "flex";
}

function hideForm() {
    createCardModalTitle.textContent = "Create Word Card";
    addCardBtn.textContent = "Create Word Card";
    createCardOverlay.style.display = "none";
}

function addCardToArray(event) {
    event.preventDefault();
    if (
        formInput.value === "" ||
        createCardModalTitle.textContent === "Edit Word Card"
    )
        return;

    let cardWord = formInput.value;
    cardArray.push(cardWord);
    console.log(cardArray);

    displayCard();
}

function displayCard() {
    cardContainer.innerHTML = "";

    cardArray.forEach((card) => {
        let btnContainer = document.createElement("div");
        btnContainer.className = "btn-container";

        html = `<h2>${card}</h2>
    <button >Edit</button>
    <button >Delete</button>`;

        btnContainer.innerHTML = html;
        cardContainer.appendChild(btnContainer);
    });

    deleteButton();
    editButton();
}

function deleteButton() {
    const deleteBtns = document.querySelectorAll(".delete-btn");

    [...deleteBtns].forEach((deleteBtn) => {
        deleteBtn.addEventListener("click", () => {
            const dbIndex = [...cardContainer.children].indexOf(
                deleteBtn.parentElement,
            );
            cardArray.splice(dbIndex, 1);
            deleteBtn.parentElement.remove();

            console.log(cardArray);
        });
    });
}

function editButton() {
    const editBtns = document.querySelectorAll(".edit-btn");

    [...editBtns].forEach((editBtn) => {
        editBtn.addEventListener("click", (event) => {
            event.stopPropagation();
            const card = editBtn.parentElement;
            const cardIndex = [...cardContainer.children].indexOf(card);

            showForm();
            createCardModalTitle.textContent = "Edit Word Card";
            addCardBtn.textContent = "Edit Word Card";
            formInput.value = cardArray[cardIndex];

            addCardBtn.onclick=(event) => {
                event.stopPropagation();
                event.preventDefault();
                cardArray[cardIndex] = formInput.value;

                [...card.children][0].textContent = formInput.value;

                console.log(cardArray);
            };
        });
    });
}
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6rem;
  padding: 5rem 0;
}

#create-card-btn-container {
  display: flex;
  justify-content: center;

}

#create-card-btn {
  padding: .5rem 2rem;
}

#card-container {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  justify-content: center;
  align-items: center;
  row-gap: 4rem;
}

.btn-container {
  background: #c4c4c4;
  text-align: center;
  border: 3px solid #666666;
  padding: 4rem 6rem;
  margin: 0rem 2rem;
}

.btn-container h2 {
  margin-bottom: 1rem;
}

.edit-btn {
  padding: .2rem 1.5rem;
  margin-bottom: .5rem;
}

.delete-btn {
  padding: .2rem 1.5rem;
}

#create-card-overlay {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: none;
  justify-content: center;
  align-items: center;
  background-color: #00000088;
}

#create-card-overlay > #create-card-modal {
  padding: 1rem 1.2rem;
  background-color: #c4c4c4;
  border: 3px solid #333333;
}

#create-card-overlay h3 {
  font-size: 1.5rem;
  margin-bottom: 1rem;
}

#create-card-overlay form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

#create-card-overlay form button {
  padding: .2rem 0;
}

#create-card-overlay .form-control {
  display: flex;
  flex-direction: column;
}

#create-card-overlay .form-control input {
  padding: .2rem .3rem; 
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <section id="create-card-btn-container">
        <button id="create-card-btn">Create Word Card</button>
    </section>

    <section id="card-container">

    </section>

    <section id="create-card-overlay">
        <div id="create-card-modal">
            <h3>Create Word Card</h3>
            <form>
                <div >
                    <label for="word">Add a word</label>
                    <input type="text" name="word" id="word">
                </div>
                <button>Create Word Card</button>
            </form>
        </div>
    </section>
    <script src="script.js"></script>
</body>
</html>

  • Related