简体   繁体   中英

Javascript "removeEventListener" not doing anything with anonymous function, no easy way to do this?

I saw a lot of questions on the site for the same topic ("removeEventListener not working), but I read everything I could find, and none of them resolved my issue. I did find a few that suggested some workarounds (like creating an object in the scope that's responsible for adding and deleting listeners) but none of them really worked for me, or I had no idea how to implement them to my code specifically. This is why I'm allowing myself to post this. If someone finds a post that specifically resolves my problem, I'll remove this.


To put things briefly, I have a HTML table with "contenteditable" set to true on all "td" tags. I have a button under it which calls the deleteMode() function:

        function deleteMode(){
            let tds = document.getElementsByTagName('td');
            if (delete_mode == true){
                table.style.border = null;
                for(let i = 0; i < tds.length; i++){
                    tds[i].style.cursor = null;
                    tds[i].setAttribute("contenteditable", "true");

                    tds[i].removeEventListener("mouseover", function(){ setDeleteBGC(i); });
                    tds[i].removeEventListener("mouseout", function(){ removeDeleteBGC(i); });
                    tds[i].removeEventListener("click", function(){ deleteEvent(i); });

                }
                delete_mode = false;
            }
            else{
                table.style.border = "solid 5px red";
                                
                for(let i = 0; i < tds.length; i++){
                    tds[i].style.cursor = "pointer";
                    tds[i].removeAttribute("contenteditable");

                    tds[i].addEventListener("mouseover", function(){ setDeleteBGC(i); });
                    tds[i].addEventListener("mouseout", function(){ removeDeleteBGC(i); });
                    tds[i].addEventListener("click", function(){ deleteEvent(i); });

                }
                delete_mode = true;
            }
        }

(delete_mode variable is set to "false" on page load) For my eventListeners, since I had to send a parameter to the function, I used an anonymous function to call the specified function with the parameters. If I understand correctly, this is why there is a problem.

For those that need to see the full code (HTML + Javascript functions that are being called), here is a JS fiddle:

https://jsfiddle.net/67p5kLze/6/

As you can see in the fiddle, as a user "enters" "delete mode", he is able to click to empty cells 4 by 4. However, as he "exits" it, he can still empty them by clicking, and there is still the orange "preview", which I think is because the EventHandlers are not being removed.

Any help is greatly appreciated. Thanks in advance!


Following @Yousaf 's answer, I modified my code to something like this:

        function deleteMode(){
            let tds = document.getElementsByTagName('td');
            if (delete_mode == true){
                table.style.border = null;
                for(let i = 0; i < tds.length; i++){
                    tds[i].style.cursor = null;
                    tds[i].setAttribute("contenteditable", "true");

                    bindFunc = deleteEvent.bind(null, i);

                    tds[i].removeEventListener("mouseover", function(){ setDeleteBGC(i); });
                    tds[i].removeEventListener("mouseout", function(){ removeDeleteBGC(i); });
                    tds[i].removeEventListener("click", bindFunc);

                }
                delete_mode = false;
            }
            else{
                table.style.border = "solid 5px red";
                                
                for(let i = 0; i < tds.length; i++){
                    tds[i].style.cursor = "pointer";
                    tds[i].removeAttribute("contenteditable");



                    var bindFunc = deleteEvent.bind(null, i);

                    tds[i].addEventListener("mouseover", function(){ setDeleteBGC(i); });
                    tds[i].addEventListener("mouseout", function(){ removeDeleteBGC(i); });
                    tds[i].addEventListener("click", bindFunc);

                }
                delete_mode = true;
            }
            function deleteEvent(index){
                let tds = document.getElementsByTagName('td');
                console.log(index);
                index = Math.floor(index/4) * 4;
                let selectInCell = tds[index].getElementsByTagName('select');
                selectInCell[0].selectedIndex = 0;
                tds[index+1].innerHTML = '';
                tds[index+2].innerHTML = '';
                tds[index+3].innerHTML = '';
            }
        }

However, its not working, I'm thinking because the addEventListener and removeEventListener are not in a loop. It's the only way my code was different to his.

If anyone sees the mistake I'm doing, I'd appreciate the help!

.removeEventListener() takes the same callback function as a second argument that was used to add the event listener, ie that was passed as a second argument to .addEventListener() method.

In your case, you are using anonymous function, so the one you pass to .addEventListener() method is different from the callback function that you have passed to .removeEventListener() method. Hence, registered event listeners are not removed.

Since you mentioned in your question that you used anonymous functions because you need to pass an argument to the event handler function. You can create a function that you want to use as a event handler, then use Function.prototype.bind() to get another function, save a reference to this function returned by Function.prototype.bind() somewhere and then use this new function as a event handler.

Later, when you need to remove the event listeners, you can remove them easily because references of the event handler functions were already saved.

Following code snippet shows an example:

 const btn = document.querySelector('#one'); const removeBtn = document.querySelector('#two'); function handleClick(val) { console.log(val); } // 123 is the argument that we want to pass to the event handler const bindFunc = handleClick.bind(null, 123); btn.addEventListener('click', bindFunc); removeBtn.addEventListener('click', () => { btn.removeEventListener('click', bindFunc) });
 <button id="one">Click</button> <button id="two">Remove Click Listener</button>

Edit

Here's the demo of your code with the changes suggested in my answer.

PS: I suggest that you use the second code snippet which is below this one. This is because your code contains a-lot of code duplication and is unnecessarily complicated. I have added this code snippet just to show you how you could remove the event listeners using the approach mentioned in my answer.

 var delete_mode = false; var table = document.getElementById('main-table'); var setBgcEventListeners = []; var removeBgcEventListeners = []; var deleteEventListeners = []; function deleteMode() { let tds = document.getElementsByTagName('td'); if (delete_mode == true) { table.style.border = null; for (let i = 0; i < tds.length; i++) { tds[i].style.cursor = null; tds[i].setAttribute('contenteditable', 'true'); tds[i].removeEventListener('mouseover', setBgcEventListeners[i]); tds[i].removeEventListener('mouseout', removeBgcEventListeners[i]); tds[i].removeEventListener('click', deleteEventListeners[i]); } delete_mode = false; } else { table.style.border = 'solid 2px red'; for (let i = 0; i < tds.length; i++) { tds[i].style.cursor = 'pointer'; tds[i].removeAttribute('contenteditable'); let setDelBGC = setDeleteBGC.bind(null, i, tds); let removeDelBGC = removeDeleteBGC.bind(null, i, tds); let removeTdContent = deleteEvent.bind(null, i, tds); setBgcEventListeners.push(setDelBGC); removeBgcEventListeners.push(removeDelBGC); deleteEventListeners.push(removeTdContent) tds[i].addEventListener('mouseover', setDelBGC); tds[i].addEventListener('mouseout', removeDelBGC); tds[i].addEventListener('click', removeTdContent); } delete_mode = true; } } function setDeleteBGC(index, tds) { index = Math.floor(index / 4) * 4; tds[index].style.backgroundColor = 'orange'; tds[index + 1].style.backgroundColor = 'orange'; tds[index + 2].style.backgroundColor = 'orange'; tds[index + 3].style.backgroundColor = 'orange'; } function removeDeleteBGC(index, tds) { index = Math.floor(index / 4) * 4; tds[index].style.backgroundColor = null; tds[index + 1].style.backgroundColor = null; tds[index + 2].style.backgroundColor = null; tds[index + 3].style.backgroundColor = null; } function deleteEvent(index, tds) { index = Math.floor(index / 4) * 4; tds[index].innerHTML = ''; tds[index + 1].innerHTML = ''; tds[index + 2].innerHTML = ''; tds[index + 3].innerHTML = ''; }
 td, tr, table { border: 1px solid black; border-collapse: collapse; }
 <table id="main-table"> <tr> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> </tr> <tr> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> </tr> </table> <button onclick="deleteMode()">Delete Mode</button>

A better solution

Your code is unnecessarily complicated. You are adding and removing the event listeners based on whether you are in delete mode or not.

You could free yourself from the responsibility of removing the event listeners and hence solving your original problem, by adding the mouseover , mouseout and click event listeners on the table element instead of on each td element.

Following code snippet shows an example of how you could improve your code, make it more readable and remove code duplication.

 var deleteMode = false; var tds = [...document.querySelectorAll('td')]; var deleteBtn = document.getElementById('delBtn'); var table = document.getElementById('main-table'); var hoverColor = 'orange'; const groupLength = 4; table.addEventListener('mouseover', handleEvent); table.addEventListener('mouseout', handleEvent); table.addEventListener('click', handleEvent); deleteBtn.addEventListener('click', function () { deleteMode = !deleteMode; let cursor, border; if (deleteMode) { border = 'solid 2px red'; cursor = 'pointer'; tds.forEach(td => td.removeAttribute('contenteditable')); } else { border = cursor = null; tds.forEach(td => td.setAttribute('contenteditable', 'true')); } table.style.border = border; table.style.cursor = cursor; }); function handleEvent(event) { if (!deleteMode) return; const target = event.target; if (target.matches('td')) { const baseIndex = getTdBaseIndex(target); const eventType = event.type; if (eventType == 'mouseover' || eventType == 'mouseout') { setBgColor(eventType, baseIndex); } else if (eventType == 'click') { deleteTdContent(baseIndex); } } } function changeBgColor(el, color = null) { el.style.backgroundColor = color; } function setBgColor(eventType, baseIndex) { const bgColor = eventType == 'mouseover' ? hoverColor : null; for (let i = baseIndex; i < baseIndex + 4; i++) { changeBgColor(tds[i], bgColor); } } function deleteTdContent(baseIndex) { for (let i = baseIndex; i < baseIndex + 4; i++) { tds[i].innerHTML = ''; } } function getTdBaseIndex(targetTdElm) { let tdIndex = tds.findIndex(td => td == targetTdElm); return Math.floor(tdIndex / groupLength) * groupLength; }
 td, tr, table { border: 1px solid black; border-collapse: collapse; }
 <table id="main-table"> <tr> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> </tr> <tr> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> <td contenteditable="true">Hey</td> </tr> </table> <button id="delBtn">Delete Mode</button>

After intensive research, I finally thought of typing something along the lines of "Remove all eventListerners". This is impossible to do in plain Javascript but I did find out about the JQuery "on()" and "off()" functions.

The "on()" function works basically like "addEventListener()" (example in code below)

The "off()" function is somewhat different to the "removeEventListener()" though. Basically, using the "off()" function, you can remove all EventListeners of a specific element, or of all elements of a tag (in my example, the "td" tag). This is what I was trying to acheive.

This is the final form of the function, which now works correctly :

        function deleteMode(){
            let tds = document.getElementsByTagName('td');
            if (delete_mode == true){
                table.style.border = null;
                for(let i = 0; i < tds.length; i++){
                    tds[i].style.cursor = null;
                    tds[i].setAttribute("contenteditable", "true");
                }
                $( "td" ).off();
                delete_mode = false;


            }
            else{
                table.style.border = "solid 5px red";
                
                for(let i = 0; i < tds.length; i++){
                    tds[i].style.cursor = "pointer";
                    tds[i].removeAttribute("contenteditable");
                    $(tds[i]).on("click", someNameHere);
                    $(tds[i]).on("mouseover", someNameHere2);
                    $(tds[i]).on("mouseout", someNameHere3);

                    function someNameHere(){ deleteEvent(i); }
                    function someNameHere2(){ setDeleteBGC(i); }
                    function someNameHere3(){ removeDeleteBGC(i); }

                }
                delete_mode = true;
            }
        }

My deleteEvent() function, setDeleteBCG() function, and removeDeleteBCG() functions are fired correctly.

Note that doing it this way, you don't even have to separate the function from the "on()" declarations. In other words, my someNameHere functions are somewhat unnecessary, and they could be replaced with anonymous functions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM