Reputation: 1368
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!
Upvotes: 33
Views: 37256
Reputation: 463
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);
}
Upvotes: 0
Reputation: 330
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>
Upvotes: 0
Reputation: 1798
// 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>
Upvotes: 3
Reputation: 501
Maybe its not perfect solution, but near to ideal, in addition I dont see other ways
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
Upvotes: 0
Reputation: 15351
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);
}
Upvotes: 30
Reputation: 2121
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 = []
}
Upvotes: 3
Reputation: 1
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){...}
}
Upvotes: -2
Reputation: 1226
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.
Upvotes: 0