Kevin Rodrigues
Kevin Rodrigues

Reputation: 73

Stop next event listener from firing when one type of event is handled

I have 2 click listeners set up, these handle showing a modal and removing a note. These listeners use event delegation and is set up on parent container. The Note is rendered afters when users inputs one. The issue is, when user clicks on a note , a modal pops up to show its content but at the same time the remove note listener is also fired. How do I stop next listener from firing when one is handled.

/*Controller.js*/

async function removeNote(ele) {
  let noteId = { id: ele.dataset.id };
  let deletedNote = await sendAPIRequest("deleteNote", "DELETE", noteId);
  let localDeletedNote = deleteNoteFromLocal(deletedNote);
  NoteView.removeNotefromView(localDeletedNote);
}

function closeModal(ele) {
  let modal = document.querySelector(".modal");

  if (!document.querySelector("body").contains(ele)) {
    modal.style.display = "none";
  } else {
    modal.style.display = "none";
  }
}
function showModal(ele) {
  let noteId = { id: ele.dataset.id };
  Modal.addDisplayModalHandler();
}

function init() {

  /* NoteView.detectNoteChange(updateNoteDetails); */
  NoteView.addHandlerShowModalOnClick(showModal);
  NoteView.addHandlerRemoveCard(removeNote);
  /* Modal.addDisplayModalHandler(displayModal); */
  Modal.addHandlerCloseModal(closeModal);
}

init();

/Task.js/

import { callCorrectFunc } from "../helper.js";
import { Note as noteData } from "../model.js";

class NoteView {
  _parentElement = document.querySelector(".task_card_container");

  /* constructor(title, body, date, priority) {
    this.title = title;
    this.body = body;
    this.date = date;
    this.priority = priority;
  } */

  addHandlerRemoveCard(handler) {
    document.querySelector(".notes_list").addEventListener("click", (e) => {
      let ele = e.target.closest(".task_card") || null;
      if (ele !== null) {
        let action = getDataAttr(ele);
        if (action === "open_modal") {
          handler(ele);
        }
      }
    });
  }

  /*   addHandler(handler){
    document.querySelector('.task_card_container').addEventListener('click', (e)=>{
      callCorrectFunc(e);
    })
  } */


  addHandlerShowModalOnClick(handler) {
    document.querySelector(".notes_list").addEventListener("click", (e) => {
     
      let ele = e.target.closest(".task_card") || null;
      if (ele !== null) {
        let action = getDataAttr(ele);
        if (action === "open_modal") {
          handler(ele);
        }
      }
    });
  }

  detectNoteChange(handler) {
    document
      .querySelector(".task_card_container")
      .addEventListener("keyup", (e) => {
        console.log(e.target);
        let ele = e.target.closest("div");
        if (!ele) return;
        handler(ele);
      });
  }

  renderUI() {
    let badgeColor =
      noteData.currentNote.priority === "1"
        ? "text-bg-danger"
        : noteData.currentNote.priority === "2"
        ? "text-bg-warning"
        : "text-bg-success";
    let template = this.#generateNoteTemplate(noteData.currentNote, badgeColor);
    this._parentElement.insertAdjacentHTML("beforeend", template);

    this.clearTaskModal();
  }


  removeNotefromView(note) {
    document.querySelector(`div[data-id="${note.objectId}"]`).remove();
  }

  #generateNoteTemplate({ id, title, body, priority, objectId }, badgeColor) {
    return `
        <div class="task_card" data-id=${objectId} data-action="open_modal">
                <div class="task_card_header">
                  <p id="task_card_header_text" contenteditable="true" >${title}</p>
                  <span class="material-symbols-outlined" id="delete_card_button" data-id=${objectId} data-action="close" role="button">disabled_by_default</span>
                </div>
                <div class="task_card_body">
                  <div class="task_card_body_content" contenteditable="true">
                  ${body}
                  </div>
                </div>
                <div class="card_metadata">
                  <p class="card_metadata_body ${badgeColor}">
                    <span class="material-symbols-outlined">
                      priority_high
                    </span> 
                    <span class="card_metadata_priority">${
                      priority === "1"
                        ? "High"
                        : priority === "2"
                        ? "Medium"
                        : "Low"
                    }</span>
                  </p>
                  <p class="card_metadata_body text-bg-secondary">
                    <span class="material-symbols-outlined">
                      schedule
                      </span>
                    <span class="card_metadata_date"> ${new Date().toLocaleDateString()}</span>
                  </p>
                </div>
              </div>`;
  }

  clearTaskModal() {
    task_title.value = "";
    task_body.value = "";
    task_priority.value = "3";
  }
}

export default new NoteView();


Index.HTML

   <div class="task_card_container">
              <div class="notes_list">

              </div>
          </div>
          

UI

Upvotes: 0

Views: 79

Answers (2)

Peter Seliger
Peter Seliger

Reputation: 13432

You do mention event-delegation, but it seems that both functions addHandlerShowModalOnClick and addHandlerRemoveCard do event-delegation not entirely correct. At least addHandlerRemoveCard needs to be implemented in a way that it identifies the click-target as a remove-button and not alike addHandlerShowModalOnClick where one generically targets ...

e.target.closest(".task_card");

For addHandlerRemoveCard it rather should be something like ...

e.target.closest(".remove_button");

Identifying the remove-button click correctly is crucial for addHandlerRemoveCard and every other action that is going to follow.

E.g. something like that ...

addHandlerRemoveCard(handler) {
  document
    .querySelector(".notes_list")
    .addEventListener("click", ({ target }) => {

      // - targeting a remove-button first, assuring
      //   that the note actually has to be removed.
      //
      // let ele = evt.target.closest(".task_card") || null;

      const  elmRemove = target.closest(".remove_button");
      const  elmCard = elmRemove && target.closest(".task_card");

      if (elmCard !== null) {
        handler(elmCard);
      }
    });
}

async function removeNote(ele) {
  let noteId = { id: ele.dataset.id };
  let deletedNote = await sendAPIRequest("deleteNote", "DELETE", noteId);
  let localDeletedNote = deleteNoteFromLocal(deletedNote);
  NoteView.removeNotefromView(localDeletedNote);
}

NoteView.addHandlerRemoveCard(removeNote);

Upvotes: 0

mplungjan
mplungjan

Reputation: 178375

Pass the event to your handlers

handler(ele,e);

and use stopPropagation

async function removeNote(ele, e) {
  e.stopPropagation();
  // ... rest of your code
}

function showModal(ele, e) {
  e.stopPropagation();
  // ... rest of your code
}

I personally prefer one event handler on the closest static container so something like this, then you do not need to pass the event and do stop propagastion in the function

addHandlerNoteActions(showModalHandler, removeNoteHandler) {
  this._parentElement.addEventListener("click", (e) => {
    const ele = e.target.closest(".task_card");
    if (!ele) return;

    const action = ele.dataset.action;

    switch (action) {
      case "open_modal":
        e.stopPropagation();
        showModalHandler(ele);
        break;
      case "close":
        e.stopPropagation();
        removeNoteHandler(ele);
        break;
      // ... other cases ...
    }
  });
}

Lastly I do not see the need for async operations other than for the removeNote

Upvotes: 1

Related Questions