33

I am writing a vanilla JavaScript tool, that when enabled adds event listeners to each of the elements passed into it.

I would like to do something like this:

var do_something = function (obj) {
        // do something
    };

for (var i = 0; i < arr.length; i++) {
    arr[i].el.addEventListener('click', do_something(arr[i]));
}

Unfortunately this doesn't work, because as far as I know, when adding an event listener, parameters can only be passed into anonymous functions:

for (var i = 0; i < arr.length; i++) {
    arr[i].el.addEventListener('click', function (arr[i]) {
        // do something
    });
}

The problem is that I need to be able to remove the event listener when the tool is disabled, but I don't think it is possible to remove event listeners with anonymous functions.

for (var i = 0; i < arr.length; i++) {
    arr[i].el.removeEventListener('click', do_something);
}

I know I could easily use jQuery to solve my problem, but I am trying to minimise dependencies. jQuery must get round this somehow, but the code is a bit of a jungle!

3
  • 1
    So name your listeners, then you can use removeEventListener. Commented Feb 26, 2013 at 11:33
  • 1
    Since you know jQuery can solve your problem, you just need to read the source code how it works. Commented Feb 26, 2013 at 11:41
  • 3
    +1 at least for bolding the words 'vanilla JavaScript' and also italicising your point about jQuery! (Hopefully no-one will actually suggest jQuery as a solution now...) But also +1 because this is a good question. Commented Feb 26, 2013 at 11:43

8 Answers 8

30

This is invalid:

arr[i].el.addEventListener('click', do_something(arr[i]));

The listener must be a function reference. When you invoke a function as an argument to addEventListener, the function's return value will be considered the event handler. You cannot specify arguments at the time of listener assignment. A handler function will always be called with the event being passed as the first argument. To pass other arguments, you can wrap the handler into an anonymous event listener function like so:

elem.addEventListener('click', function(event) {
  do_something( ... )
}

To be able to remove via removeEventListener you just name the handler function:

function myListener(event) {
  do_something( ... );
}

elem.addEventListener('click', myListener);

// ...

elem.removeEventListener('click', myListener);

To have access to other variables in the handler function, you can use closures. E.g.:

function someFunc() {
  var a = 1,
      b = 2;

  function myListener(event) {
    do_something(a, b);
  }
  
  elem.addEventListener('click', myListener);
}
Sign up to request clarification or add additional context in comments.

3 Comments

This is fine when I don't need to remove the listener or remove it in the same scope as someFunc(). Unfortunately this isn't the case (although maybe it should be?)
By making the listener function a global variable or one in a context that you can access from where you need, this can be solved. E.g. window.listeners.myListener = function() { ... }. This way you can any time say elem.removeEventListener('click', window.listeners.myListener)
I solved the problem by applying the listener functions to arr[i].I was then able to refer to the whole arr[i] object without needing to pass in parameters to the listener.
3

To pass arguments to event handlers bind can be used or handler returning a function can be used

// using bind
var do_something = function (obj) {
  // do something
}

for (var i = 0; i < arr.length; i++) {
  arr[i].el.addEventListener('click', do_something.bind(this, arr[i]))
}


// using returning function
var do_something = obj => e {
  // do something
}

for (var i = 0; i < arr.length; i++) {
  arr[i].el.addEventListener('click', do_something(arr[i]))
}

But in both the cases to remove the event handlers it is not possible as bind will give a new referenced function and returning function also does return a new function every time for loop is executed.

To handle this problem we need to store the references of the functions in an Array and remove from that.

// using bind
var do_something = function (obj) {
  // do something
}
var handlers = []

for (var i = 0; i < arr.length; i++) {
  const wrappedFunc = do_something.bind(this, arr[i])
  handlers.push(wrappedFunc)
  arr[i].el.addEventListener('click', wrappedFunc);
}
//removing handlers
function removeHandlers() {
  for (var i = 0; i < arr.length; i++) {
    arr[i].el.removeEventListener('click', handlers[i]);
  }
  handlers = []
}

7 Comments

Unfortunately e.currentTarget would actually point to arr[i].el
yes in question you have posted the same in second code snippet for anonymous function arr[i].el.addEventListener('click', function (arr[i])... so what do you want to pass as argument actually?
Argument needs to be arr[i] as opposed to arr[i].el which the listener is attached to.
so if u want arr[i] to be passed to which listener is attached to. the event which is by default passed to the function contains the arr[i]
@Roko Thanks for motivating me :)
|
3
// Define a wrapping function
function wrappingFunction(e) {
  // Call the real function, using parameters
  functionWithParameters(e.target, ' Nice!')
}
// Add the listener for a wrapping function, with no parameters
element.addEventListener('click', wrappingFunction);
// Save a reference to the listener as an attribute for later use
element.cleanUpMyListener = ()=>{element.removeEventListener('click', wrappingFunction);}

// ...

element.cleanUpMyListener ()

Step 1) Name your function.

Step 2) Save a reference to your function (in this case, save the reference as an attribute on the element itself)

Step 3) Use the function reference to remove the listener

// Because this function requires parameters, we need this solution
function addText(element, text) {
  element.innerHTML += text
}

// Add the listener
function addListener() {
  let element = document.querySelector('div')
  if (element.removeHoverEventListener){
    // If there is already a listener, remove it so we don't have 2
    element.removeHoverEventListener()
  }
  // Name the wrapping function
  function hoverDiv(e) {
      // Call the real function, using parameters
      addText(e.target, ' Nice!')
  }
  // When the event is fired, call the wrapping function
  element.addEventListener('click', hoverDiv);
  // Save a reference to the wrapping function as an attribute for later use
  element.removeHoverEventListener = ()=>{element.removeEventListener('click', hoverDiv);}
}

// Remove the listener
function removeListener() {
  let element = document.querySelector('div')
  if (element.removeHoverEventListener){
    // Use the reference saved before to remove the wrapping function
    element.removeHoverEventListener()
  }
}
<button onclick="addListener()">Turn Listener on</button>
<button onclick="removeListener()">Turn Listener off</button>
<div>Click me to test the event listener.</div>

Comments

0

This can be done quite easily, just not as you have it right now.

Instead of trying to add and remove random anonymouse functions, you need to add or remove a function that handles the execution of your other functions.

var
    // Here we are going to save references to our events to execute
    cache = {},

    // Create a unique string to mark our elements with
    expando = String( Math.random() ).split( '.' )[ 1 ],

    // Global unique ID; we use this to keep track of what events to fire on what elements
    guid = 1,

    // The function to add or remove. We use this to handler all of other 
    handler = function ( event ) {

        // Grab the list of functions to fire
        var handlers = ( cache[ this[ expando ] ] && cache[ this[ expando ] ][ event.type ] ) || false;

        // Make sure the list of functions we have is valid
        if ( !handlers || !handlers.length ) {
            return;
        }

        // Iterate over our individual handlers and call them as we go. Make sure we remeber to pass in the event Object
        handlers.forEach( function ( handler ) {
            handler.call( this, event );
        });

    },

    // If we want to add an event to an element, we use this function
    add = function ( element, type, fn ) {

        // We test if an element already has a guid assigned
        if ( !element[ expando ] ) {
            element[ expando ] = guid++;
        }

        // Grab the guid number
        var id = element[ expando ];

        // Make sure the element exists in our global cache
        cache[ id ] = cache[ id ] || {};

        // Grab the Array that we are going to store our handles in
        var handlers = cache[id ][ type ] = cache[ id ][ type ] || [];

       // Make sure the handle that was passed in is actually a function
        if ( typeof fn === 'function' ) {
            handlers.push( fn );
        }

        // Bind our master handler function to the element
        element.addEventListener( type, handler, false );

    };

// Add a click event to the body element
add( document.body, 'click', function ( event ) {
    console.log( 1 );
});

This is just a cut down version of what I've written before, but you can get the gist of it I hope.

Comments

0

Maybe its not perfect solution, but near to ideal, in addition I dont see other ways

Thanks to Kostas Bariotis

Solution key here is:

So what do we do when we need to remove our attached event handlers at some point at runtime? Meet handleEvent, the default function that JavaScript looks for when tries to find a handler that has been attached to an event.

In cas link is broken (I placed first way)

let Button = function () {

  this.el = document.createElement('button');
  this.addEvents();
}

Button.prototype.addEvents = function () {
  this.el.addEventListener('click', this);
}

Button.prototype.removeEvents = function () {
  this.el.removeEventListener('click', this);
}

Button.prototype.handleEvent = function (e) {
  switch(e.type) {
    case 'click': {
     this.clickHandler(e);
    }
  }
}

Button.prototype.clickHandler = function () {
  /* do something with this */
}

P.S:

Same tehnics in JS class implementation.

If you develop in typescript you have to implement handleEvent method from EventListenerObject interface

Comments

0

What worked for me:

I needed to add event listeners to HTML elements in a loop, and give the handler functions a unique parameter, and later I wanted to remove the listeners. In my case it was the index of the element. I created an array to store the functions and then from this array I could remove the listeners if needed.

In the example below, if you turn on the event listeners, you can highlight multiple rows, with mouse drag. You can disable the highlight function with the remove event listeners button. You can also access both the event, and the index of the row in the event handlers onMouseDown and onMouseUp.

It's easy to remove the listeners as seen in the removeEventListeners function.

var mouseDownListeners = [];
var mouseUpListeners = [];
var selStart = null;
var selEnd = null;

document.getElementById('button1').addEventListener('click', () => addEventListeners());
document.getElementById('button2').addEventListener('click', () => removeEventListeners());

function addEventListeners() {
  removeEventListeners();
  let rows = getRows();

  rows.forEach((row, i) => {
    let mouseUpListener = function(e) {
      onMouseUp.bind(null, e, i)();
    }

    let mouseDownListener = function(e) {
      onMouseDown.bind(null, e, i)();
    }

    mouseUpListeners[i] = mouseUpListener;
    mouseDownListeners[i] = mouseDownListener;

    row.addEventListener('mouseup', mouseUpListener);
    row.addEventListener('mousedown', mouseDownListener);
  })
}

function removeEventListeners() {
  let rows = getRows();

  rows.forEach((row, i) => {
    row.removeEventListener('mouseup', mouseUpListeners[i]);
    row.removeEventListener('mousedown', mouseDownListeners[i]);
  })
  mouseUpListeners = [];
  mouseDownListeners = [];
}

function getRows() {
  let rows = document.querySelectorAll('div.row');
  return rows;
}

var onMouseDown = function(e, i) {
  selStart = i;
}

var onMouseUp = function(e, i) {
  selEnd = i;

  assignClasses(selStart, selEnd);

  selStart = null;
  selEnd = null;
}

function assignClasses(start, end) {
  let rows = getRows();
  rows.forEach((row, i) => {
    if (start <= i && i <= end) {
      row.classList.add('highlighted-row');
    } else {
      row.classList.remove('highlighted-row');
    }
  })
}
.row {
  border: 1px solid black;
  user-select: none; // chrome and Opera
  -moz-user-select: none; // Firefox
  -webkit-text-select: none; // IOS Safari
  -webkit-user-select: none; // Safari
}

.highlighted-row {
  background-color: lightyellow;
}
<div>
  <div>
    <button id='button1'>Add listeners</button>
    <button id='button2'>Remove listeners</button>
  </div>

  <div>
    <div class='row'>Row1</div>
    <div class='row'>Row2</div>
    <div class='row'>Row3</div>
    <div class='row'>Row4</div>
    <div class='row'>Row5</div>
    <div class='row'>Row6</div>
    <div class='row'>Row7</div>
    <div class='row'>Row8</div>
    <div class='row'>Row9</div>
    <div class='row'>Row10</div>
  </div>
  <div>

Comments

0

Here's another variation in typescript. Add an on mouse down event with startResize, ie in some element, dragging the mouse will then trigger resize so you can calculate the size of the container, then when mouse up is triggered, the event listeners are removed. Handy for a resizeable sidebar for example.

let mouseMoveCallback;

function startResize(event: MouseEvent){
    mouseMoveCallback = (e) => resize(e, initialX+offset())
    window.addEventListener('mousemove', mouseMoveCallback);
    window.addEventListener('mouseup', stopResize);
}

function resize(event: MouseEvent, initialX: number) {
    // Do stuff 
}

function stopResize() {

    window.removeEventListener('mousemove', mouseMoveCallback);
    window.removeEventListener('mouseup', stopResize);

}

Comments

-2

To 'addEventListener' with some parameters, you can use the following code:

{
   myButton.addEventListener("click",myFunction.bind(null,event,myParameter1,myParameter2)); 
}

And the function 'myFunction' should be something like this:

{
   function myFunction(event, para1, para2){...}
}

2 Comments

bind will change the function reference thus removing the event listener for the named function will fail
This is a completely incorrect answer. And does not provide any way to removeEventListener - which in contrast will fail as pointed out by @Craveiro

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.