1개의 모달창을 공유하기 위한 이벤트 중복 방지

HYERI ·2023년 4월 16일
0
post-thumbnail

문제

인터넷 밴딩머신를 만들던 중 여러가지 alert를 주기 위해 새로 custom 모달창을 만들었다. 기본 alert에서 모달창으로 모든 것을 옮기던 중 문제가 발생했다. 입금 버튼을 눌렀을 시나 음료수가 품절되었을 경우는 사용자에게 그저 정보를 알려주기 위해서 모달창을 사용했지만 음료수를 현재 장바구니에서 뺄때 사용자에게 묻는 것은 cancel 버튼을 눌렀을 경우와 yes 버튼을 눌렀을 경우 다른 이벤트가 생겼다.

그리고 - 버튼을 통해 현재 장바구니에서 음료수를 삭제할 경우와 x 버튼을 통해 음료수를 삭제할 경우의 이벤트가 달랐다.


첫 onModal 함수

/* 모달창 처리 */ 
// isRemove == true: 현재 장바구니에서 아이템 삭제할 때
// isSub == true: btn-sub을 통해서만 아이템 삭제할 때
const onModal = function(title, itemCart, itemCount, drinkName, btnDrink, isRemove=false, isSub=false) {
    modal.classList.add("on");
    modal.querySelector("h2").textContent = title;
    modal.querySelector("p").textContent = msgWarning.get(title);
    modal.querySelector(".btn-cancel").addEventListener("click", () => {
        console.log("cancel");
        if(isSub) {
            currentCart.set(drinkName, 1);
            itemCount.textContent = currentCart.get(drinkName);
        }
        modal.classList.remove("on");
    });
    modal.querySelector(".btn-yes").addEventListener("click", () => {
        console.log("yes");
        if(isRemove && !isSub) {
            drinkInfo.get(drinkName).amount += currentCart.get(drinkName);
            btnDrink.querySelector(".drink-amount").textContent = parseInt(btnDrink.querySelector(".drink-amount").textContent) + currentCart.get(drinkName);
            insertedMoney = insertedMoney + drinkInfo.get(drinkName).price * currentCart.get(drinkName);
            inserted.textContent = numberToMoney(insertedMoney);
        }
        if(isRemove) {
            listCurrent.removeChild(itemCart);
            currentCart.delete(drinkName);
            btnDrink.classList.remove("active");
        }
        modal.classList.remove("on");
    });
}
  • 모달창을 불러와야할 경우 onModal 함수를 불러왔다.
  • isRemove와 isSub가 모두 false일 경우는 case 1: 단순 정보 전달 목적의 모달창을 구현했고 작동이 잘됐다.
    • btn-cancel과 btn-yes 눌렀을 경우 modal의 클래스 "on"을 삭제하므로써 모두 모달창을 껐다.
  • 문제는 case 2였다. onModal 함수를 부를때 마다 각 btn-cancel과 btn-yes에 똑같은 이벤트를 계속 추가하였고 단순히 "on" 클래스를 삭제하는 것을 넘어 list의 child를 삭제하는 등의 이벤트는 여러번 반복하면 에러가 났다.

  • btn-cancel을 연속 3번 눌렀을 경우 똑같은 6번의 이벤트가 btn-cancel에 추가됐고, 그 다음 btn-yes를 한번 눌렀을 경우 똑같은 4번의 이벤트가 btn-yes에 추가됐다.
  • 모달창을 열기 위해 - 버튼을 눌렀을 때 onModal 함수를 불러오고 그때마다 btn-cancel과 btn-yes에 계속 해서 이벤트를 추가했다.

{once: true}과 removeEventListner

  • 해결하기 위해 onModal 함수를 부를 때 마다 이벤트를 add 해주고 remove하는 것을 반복하기로 했다.

{once: true}

  • addEventlistner의 세번째 파라미터로 {once: true}를 넘겨줄 경우 한번만 이벤트가 일어난다.
  • btn-cancel을 연속 3번 눌렀을 경우 정상적으로 이벤트가 한번씩만 작동을 했지만 그 다음 btn-yes를 눌렀을 때 전과 같이 똑같은 4번의 이벤트 btn-yes에 추가된 것을 알 수 있었다.
  • btn-cancel을 눌렀을 때 btn-cancel의 이벤트는 {once: true}로 인해 한번만 작동하고 삭제되었지만 btn-yes의 경우는 눌러지지 않았기 때문에 계속 추가되었다.

removeEventListner

https://stackoverflow.com/questions/67691159/addeventlistenerclick-function-once-true-firing-multiple-times-in-the

  • 이 질문처럼 addEventListner 안에서 이벤트를 삭제하는 것이 아닌 바깥에서 처리하기로 했다.
const onModal = function(title, itemCart, itemCount, drinkName, btnDrink, isRemove=false, isSub=false) {
    modal.classList.add("on");
    modal.querySelector("h2").textContent = title;
    modal.querySelector("p").textContent = msgWarning.get(title);
    const btnCancel = modal.querySelector(".btn-cancel");
    const btnYes = modal.querySelector(".btn-yes");
    const onCancelClicked = () => {
        console.log("cancel");
        if(isSub) {
            currentCart.set(drinkName, 1);
            itemCount.textContent = currentCart.get(drinkName);
        }
        modal.classList.remove("on");
    }
    const onYesClicked = () => {
        console.log("yes");
        if(isSub) {
            currentCart.set(drinkName, currentCart.get(drinkName) + 1);

        }
        if(isRemove) {
            drinkInfo.get(drinkName).amount += currentCart.get(drinkName);
            btnDrink.querySelector(".drink-amount").textContent = parseInt(btnDrink.querySelector(".drink-amount").textContent) + currentCart.get(drinkName);
            insertedMoney = insertedMoney + drinkInfo.get(drinkName).price * currentCart.get(drinkName);
            inserted.textContent = numberToMoney(insertedMoney);
        }
        if(isRemove) {
            listCurrent.removeChild(itemCart);
            currentCart.delete(drinkName);
            btnDrink.classList.remove("active");
        }
        modal.classList.remove("on");
    }
    btnCancel.removeEventListener("click", onCancelClicked, false);
    btnYes.removeEventListener("click", onYesClicked, false);
    btnCancel.addEventListener("click", onCancelClicked, {once: true});
  	btnYes.addEventListener("click", onYesClicked, {once: true});
  • 익명함수대신 함수 안에서 각각의 이벤트들을 따로 정의해주고 addEventListner를 불러오기 전 removeEventListner을 해주었다.
  • 그러나 {once: true}를 사용했을때랑 같은 에러가 생겼다.
  • {once: true}의 경우 처럼 btn-cancel을 눌렀을 경우 btn-cancel의 이벤트는 add와 remove이 잘 반복되지만 btn-yes의 경우는 반복되지 않고 계속 이벤트가 추가되는 것처럼 보였다.

removeEventListener의 활용

첫번째 시도

btnCancel.removeEventListener("click", onCancelClicked, false); 
btnCancel.addEventListener("click", () => {
	onCancelClicked();
  	btnYes.removeEventListener("click", onYesClicked, false);
}, {once: true});
btnYes.addEventListener("click", onYesClicked, {once: true});
  • btnCancel이 클릭됐을 경우 btnYes의 이벤트를 삭제해주기로 하였다.
  • 일단은 btnCancel이 클릭됐을 경우만 btnYes의 이벤트를 삭제했기 때문에 btnCancel을 연속 3번 누르고 btnYes을 눌렀을 경우는 작동이 잘 되었다.
  • 하지만 btnYes로 아이템을 삭제하고 다시 담아서 연속으로 btnCancel을 3번 눌렀을 경우를 반복했을때 yes를 누를때 마다 btnCancel의 이벤트가 추가되는 것을 확인할 수 있었다.
  • btnCancel => btnYes의 경우는 작동이 잘 되었기에 똑같이 btnYes가 클릭되었을 시 btnCancel의 이벤트를 삭제해주기로 했다.

두번째 시도

btnCancel.addEventListener("click", () => {
	onCancelClicked();
  	btnYes.removeEventListener("click", onYesClicked, false);
}, {once: true});
btnYes.addEventListener("click", () => {
	onYesClicked();
  	btnCancel.removeEventListener("click", onCancelClicked, false); 
}, {once: true});
  • 전과 같은 에러가 일어났다.
  • btnCancel의 이벤트 리스너 안에서 onYesClick를 삭제하는데 다시 새로운 익명함수를 정의함으로써 삭제되지 않았다.

마지막 시도

btnCancel.addEventListener("click", onCancelClicked, {once: true});
btnCancel.addEventListener("click", () => {
   btnYes.removeEventListener("click", onYesClicked, false);
}, {once: true});
btnYes.addEventListener("click", onYesClicked, {once: true});
btnYes.addEventListener("click", () => {
    btnCancel.removeEventListener("click", onCancelClicked, false);
}, {once: true});
  • 두번째 시도와 같이 익명함수의 늪을 피하기 위해 각각 버튼에 2개의 이벤트를 따로 추가해주었다.
  • 다행히도 이번에는 여분의 이벤트가 더 추가되지 않고 잘 작동이 되는 것을 확인할 수 있었다.

결론

한 마크업을 공유하기 위해 이벤트를 추가하고 삭제하는 것을 반복해야 하는 경우가 있다는 것을 알게되었다. 이것이 최선이 아닐 것 같지만 현재로써는 내 선에서 해결할 수 있는 정도인 것 같다.

아직은 addEventListener와 removeEventListener에 대해 제대로 파악하지 못하고 있는 것 같다. 어떻게 작동은 시켰지만 바깥에서 removeEventListener를 작동시켰음에도 삭제가 되지 않은 이유를 알기 어렵다. 다음 시간에 다시 정확히 addEventListener와 removeEventListener의 작동 방식과 옵션 등등을 학습해서 정확한 이유를 알아야겠다.

나중에 target에 어떤 이벤트가 있는지 알기 위해서target.getEventListeners( target )을 이용할 수 있을 것 같다.

다음에 강사님과 멘토님께 물어봐 실무에서는 어떤 기술을 사용해 한 마크업을 공유하는지도 물어봐야겠다.

0개의 댓글