Override a function already attached to an event - javascript

I want to override a function already attached to an event. Like that :
let orFn = () => {
console.log('old');
};
buttonEl.addEventListener('click', orFn);
orFn = () => {
console.log('new');
};
However, when I click on the button, the old function is still called.
I have seen this stackoverflow. I can't use a function wrapper in my case. And I want to understand why this is not working.
My code is available for testing here: jsfiddle.

One way is to remove first listener, then add another one
let orFn = () => {
console.log('old');
};
let orFnNew = () => {
console.log('new');
};
var buttonEl = document.querySelector('button')
buttonEl.addEventListener('click', orFn);
// remove old listener
buttonEl.removeEventListener('click', orFn);
// add another one
buttonEl.addEventListener('click', orFnNew);
<button>button</button>
Another way is to have one listener, that may call different functions inside. Example:
// button
const buttonEl = document.querySelector('button')
const orFn = () => {
console.log('old');
};
const orFnNew = () => {
console.log('new');
};
// function that will be called
let functionToCall = orFn;
buttonEl.addEventListener('click', (event) => { // single listener
functionToCall.call(event); // call a function with event
functionToCall = functionToCall === orFn ? orFnNew : orFn; // change function to be called
});
<button>button</button>

One way of using bind().
const btn = document.getElementById('btn');
let orFn = () => {
alert('old');
};
orFn = () => {
alert('new');
};
orFn.bind(orFn)
btn.addEventListener('click', orFn);
This will bind with new function and show new in alert Popup.

Related

what's the difference in using closure and private class in javascript

const BUTTON = document.querySelector('.clickBtn');
const P = document.querySelector('.click');
const click = (() => {
let click = 0;
return () => ++click;
})();
BUTTON.onclick = () => {
P.textContent = click();
};
const BUTTON2 = document.querySelector('.clickBtn2');
const P2 = document.querySelector('.click2');
class Click {
#click = 0;
click = () => ++this.#click;
}
const c = new Click();
BUTTON2.onclick = () => {
P2.textContent = c.click()
};
<button class="clickBtn">CLOSURE</button>
<p class="click"></p>
<button class="clickBtn2">CLASS</button>
<p class="click2"></p>
say I have a code like above, trying to manage real-time state in vanilla JS and also wanting to hide the actual data from exposing.
I don't need multiple method
nor do I need to copy multiple instances
in case like this, would there be any difference between closure and class except class being slightly more verbose?

Vanilla JS Reusable Dynamic Function taken as a string

I'm trying to make a reusable function that can take in a dynamic "action" for reusability...
In this case, adding the background color is the action.
const button = document.querySelector("#button");
const items = document.querySelectorAll(`*[id^="eventHandlerCreatedItem"]`);
var eventHandler = (refEl, event, focusEls, action) => {
refEl.addEventListener(`${event}`, () => {
focusEls.forEach((focusEl) => {
// focusEl.style.backgroundColor = "orange"; // works
focusEl.`${action}`; // doesn't work
});
});
};
eventHandler(button, "click", items, 'style.backgroundColor = "orange"');
Thanks!
Don't use a string for this. An "action" semantically describes a "function", use a function:
var eventHandler = (refEl, event, focusEls, action) => {
refEl.addEventListener(`${event}`, () => {
focusEls.forEach((focusEl) => {
action(focusEl);
});
});
};
eventHandler(button, "click", items, (el) => el.style.backgroundColor = "orange");

How can I access variable outside event click

Hi everyone as title says I would like to achieve access to my variable called "Year" outside from my event.
Is that possible?
const ModifyYear = () =>
{
const button = document.querySelector(".button");
let Year = 2022;
button.addEventListener("click", () =>
{
Year = 2023;
})
console.log(Year); //2022
}
Another option is to change your program logic. Here we can use callbacks:
// Your function
const ModifyYear = cb => {
const button = document.querySelector("button");
let Year = 2022;
button.addEventListener("click", () => cb(Year+1));
}
// Call with callback function as parameter
ModifyYear(newYear => console.log(newYear));
<button>Click me</button>

how to define eventEmitter properly in javascript

I need to define a helper object in which I need a function that will execute on each "orientationchange" event of window.
My code as below and it is not in correct form. Can you please help me how can I define onRotate properly so that I can use it globally.
<script type="text/javascript">
'use strict';
var GlobalHelper = (function () {
var me = {
onRotate: onRotate // this is where I am struggling
}
function onRotate() {
window.addEventListener("orientationchange", function (event) {
console.log(event.target.screen.orientation.angle);
});
}
return me;
})();
GlobalHelper.onRotate = function (e) {
console.log(e);
}
</script>
I found an answer to my own question. It was actually pretty easy.
<script type="text/javascript">
'use strict';
var GlobalHelper = (function () {
var me = {
onRotate: function (e) { }
}
function _init() {
window.addEventListener("orientationchange", function (event) {
me.onRotate(event);
});
}
_init();
return me;
})();
GlobalHelper.onRotate = function (e) {
console.log(e);
}
</script>
Another option is to add custom programmable method for creating listeners and bonded functions. You can test it by Running code snippet and pressing keyboard keys while focused on results window.
// Set app object
const bindFn = e => {
// Set object to hold your functions
let fns = {};
// Set event listener to run all functions in fns object
window.addEventListener(e, event => {
for(const f in fns) fns[f](event);
});
// Return method to set new functions
// You can extend it with another function for
// deleting or altering, as you wish...
return {
add: (name, fn) => {
if(!fns[name]) fns[name] = fn;
else console.log(`Function with name ${name} already exist, skipping...`);
}
};
};
// Set binder with proper event
// here we do orientation change
const OrCh = bindFn("orientationchange");
// Add some function to execute on this event
OrCh.add('log', event => console.log(event.target.screen.orientation.angle));
//
// Test with another event that easy to trigger on stackoverflow
const KeyP = bindFn("keypress");
// Add logger
KeyP.add('log', event => console.log(`key pressed: ${event.code}`));
// Add something else
KeyP.add('logAlt', event => console.log(`Alternative function: ${event.code}`));

How can I remove an event listener no matter how the callback is defined

For years I ran into problems trying to remove an event listener in JavaScript. Often I would have to create an independent function as the handler. But that is just sloppy and, especially with the addition of arrow functions, just a pain.
I am not after a ONCE solution. This needs to work in all situations no matter HOW the callback is defined. And this needs to be raw JS so anyone can use it.
The following code works fine since the function clickHandler is a unique function and can be used by both addEventListener and removeEventListener:
This example has been updated to show what I have run into in the past
const btnTest = document.getElementById('test');
let rel = null;
function clickHandler() {
console.info('Clicked on test');
}
function add() {
if (rel === null) {
rel = btnTest.addEventListener('click', clickHandler);
}
}
function remove() {
btnTest.removeEventListener('click', clickHandler);
}
[...document.querySelectorAll('[cmd]')].forEach(
el => {
const cmd = el.getAttribute('cmd');
if (typeof window[cmd] === 'function') {
el.addEventListener('click', window[cmd]);
}
}
);
<button cmd="add">Add</button>
<button cmd="remove">Remove</button>
<button id="test">Test</button>
You used to be able to do it with arguments.callee:
var el = document.querySelector('#myButton');
el.addEventListener('click', function () {
console.log('clicked');
el.removeEventListener('click', arguments.callee); //<-- will not work
});
<button id="myButton">Click</button>
But using an arrow function does not work:
var el = document.querySelector('#myButton');
el.addEventListener('click', () => {
console.log('clicked');
el.removeEventListener('click', arguments.callee); //<-- will not work
});
<button id="myButton">Click</button>
Is there a better way??
UPDATE
As stated by #Jonas Wilms this way will work:
var el = document.querySelector('#myButton');
el.addEventListener('click', function handler() {
console.log('clicked');
el.removeEventListener('click', handler); //<-- will work
});
<button id="myButton">Click</button>
Unless you need to using binding:
var obj = {
setup() {
var el = document.querySelector('#myButton');
el.addEventListener('click', (function handler() {
console.log('clicked', Object.keys(this));
el.removeEventListener('click', handler); //<-- will work
}).bind(this));
}
}
obj.setup();
<button id="myButton">Click</button>
The problem is that there are too many ways to provide an event handler to the addEventListener function and your code might break if the way you pass in the function changes in a refactor.
You can NOT use an arrow function or any anonymous function directly and expect to be able to remove the listener.
To remove a listener requires you pass the EXACT SAME ARGUMENTS to removeEventListener as you passed to addEventListener but when you use an anonymous function or an arrow function you do not have access to that function so it's impossible for you to pass it into removeEventListener
works
const anonFunc = () => { console.log("hello"); }
someElem.addEventListener('click', anonFunc);
someElem.removeEventListener('click', anonFunc); // same arguments
does not work
someElem.addEventListener('click', () => { console.log("hello"); });
someElem.removeEventListener('click', ???) // you don't have a reference
// to the anon function so you
// can't pass the correct arguments
// to remove the listener
your choices are
don't use anonymous or arrow functions
use a wrappers that will track the arguments for you
One example is #Intervalia closure. He tracks the function and other arguments you passed in and returns a function you can use the remove the listener.
One solution I often use which often fits my needs is a class that tracks all the listeners and remove them all. Instead of a closure it returns an id but it also allows just removing all listeners which I find useful when I build up something now and want to tear it down something later
function ListenerManager() {
let listeners = {};
let nextId = 1;
// Returns an id for the listener. This is easier IMO than
// the normal remove listener which requires the same arguments as addListener
this.on = (elem, ...args) => {
(elem.addEventListener || elem.on || elem.addListener).call(elem, ...args);
const id = nextId++;
listeners[id] = {
elem: elem,
args: args,
};
if (args.length < 2) {
throw new Error('too few args');
}
return id;
};
this.remove = (id) => {
const listener = listeners[id];
if (listener) {
delete listener[id];
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
}
};
this.removeAll = () => {
const old = listeners;
listeners = {};
Object.keys(old).forEach((id) => {
const listener = old[id];
if (listener.args < 2) {
throw new Error('too few args');
}
const elem = listener.elem;
(elem.removeEventListener || elem.removeListener).call(elem, ...listener.args);
});
};
}
Usage would be something like
const lm = new ListenerManager();
lm.on(saveElem, 'click', handleSave);
lm.on(newElem, 'click', handleNew);
lm.on(plusElem, 'ciick', handlePlusOne);
const id = lm.on(rangeElem, 'input', handleRangeChange);
lm.remove(id); // remove the input event on rangeElem
lm.removeAll(); // remove events on all elements managed by this ListenerManager
note the code above is ES6 and would have to be changed to support really old browsers but the ideas are the same.
Just use a named function expression:
var el = document.querySelector('#myButton');
el.addEventListener('click', function handler() {
console.log('clicked');
el.removeEventListener('click', handler); //<-- will work
});
For sure that can be wrapped in a function:
function once(selector, evt, callback) {
var el = document.querySelector(selector);
el.addEventListener(evt, function handler() {
callback();
el.removeEventListener(evt, handler); //<-- will work
});
}
once("#myButton", "clicl", () => {
// do stuff
});
There is an easy solution using closures.
By moving the code to both addEventListener and removeEventListener into a single function you can accomplish the task easily:
function ael(el, evt, cb, options) {
console.log('Adding', evt, 'event listener for', el.outerHTML);
el.addEventListener(evt, cb, options);
return function() {
console.log('Removing', evt, 'event listener for', el.outerHTML);
el.removeEventListener(evt, cb, options);
}
}
const btnTest = document.getElementById('test');
let rel = null;
function add() {
if (rel === null) {
rel = ael(btnTest, 'click', () => {
console.info('Clicked on test');
});
}
}
function remove() {
if (typeof rel === 'function') {
rel();
rel = null;
}
}
function removeAll() {
rels.forEach(rel => rel());
}
const rels = [...document.querySelectorAll('[cmd]')].reduce(
(rels, el) => {
const cmd = el.getAttribute('cmd');
if (typeof window[cmd] === 'function') {
rels.push(ael(el, 'click', window[cmd]));
}
return rels;
}, []
);
<button cmd="add">Add</button>
<button cmd="remove">Remove</button>
<button id="test">Test</button>
<hr/>
<button cmd="removeAll">Remove All</button>
The function ael above allows the element, the event type and the callback to all be saved in the closure scope of the function. When you call ael it calls addEventListener and then returns a function that will call removeEventListener. Later in your code you call that returned function and it will successfully remove the event listener without worrying about how the callback function was created.
Here is an es6 version:
const ael6 = (el, evt, cb, options) => (el.addEventListener(evt, cb, options), () => el.removeEventListener(evt, cb, options));
You can use the once option of EventTarget.addEventListener():
Note: supported by all browsers but IE.
var el = document.querySelector('#myButton');
el.addEventListener('click', () => {
console.log('clicked');
}, { once: true });
<button id="myButton">Click</button>

Categories

Resources