Passing parameters to eventListener function - javascript

I have this function check(e) that I'd like to be able to pass parameters from test() when I add it to the eventListener. Is this possible? Like say to get the mainlink variable to pass through the parameters. Is this even good to do?
I put the javascript below, I also have it on jsbin: http://jsbin.com/ujahe3/9/edit
function test() {
if (!document.getElementById('myid')) {
var mainlink = document.getElementById('mainlink');
var newElem = document.createElement('span');
mainlink.appendChild(newElem);
var linkElemAttrib = document.createAttribute('id');
linkElemAttrib.value = "myid";
newElem.setAttributeNode(linkElemAttrib);
var linkElem = document.createElement('a');
newElem.appendChild(linkElem);
var linkElemAttrib = document.createAttribute('href');
linkElemAttrib.value = "jsbin.com";
linkElem.setAttributeNode(linkElemAttrib);
var linkElemText = document.createTextNode('new click me');
linkElem.appendChild(linkElemText);
if (document.addEventListener) {
document.addEventListener('click', check/*(WOULD LIKE TO PASS PARAMETERS HERE)*/, false);
};
};
};
function check(e) {
if (document.getElementById('myid')) {
if (document.getElementById('myid').parentNode === document.getElementById('mainlink')) {
var target = (e && e.target) || (event && event.srcElement);
var obj = document.getElementById('mainlink');
if (target!= obj) {
obj.removeChild(obj.lastChild);
};
};
};
};

Wrap your event listener into a function:
document.addEventListener(
'click',
function(e,[params]){
check(e,[params]);
}
);

One solution would be to move the "check" function up inside your test() function. As an inner function, it would automatically be able to refer to variables in its outer scope. Like this:
function test() {
if (!document.getElementById('myid')) {
var mainlink = document.getElementById('mainlink');
var newElem = document.createElement('span');
mainlink.appendChild(newElem);
var linkElemAttrib = document.createAttribute('id');
linkElemAttrib.value = "myid";
newElem.setAttributeNode(linkElemAttrib);
var linkElem = document.createElement('a');
newElem.appendChild(linkElem);
var linkElemAttrib = document.createAttribute('href');
linkElemAttrib.value = "jsbin.com";
linkElem.setAttributeNode(linkElemAttrib);
var linkElemText = document.createTextNode('new click me');
linkElem.appendChild(linkElemText);
if (document.addEventListener) {
document.addEventListener('click', function(e) {
if (document.getElementById('myid')) {
if (document.getElementById('myid').parentNode === mainlink) {
var target = (e && e.target) || (event && event.srcElement);
if (target!= mainlink) {
mainlink.removeChild(mainlink.lastChild);
};
};
};
});
};

What I typically do in this situation is save arguments to the object (whenever it's convenient), and then retrieve them in the function, like this:
// Listener function receives e (the event object) by default.
function eventReceiver(e) {
var obj;
// Find object which triggered the event
e.srcElement ? obj = e.srcElement : obj = e.target;
// obj.someProperty has been set elsewhere, replacing a function parameter
alert(obj.someProperty);
}
This is cross browser, and allows you to pass objects and values through the properties of the event target.
I initially started with the this keyword, but that behaves differently cross-browser. In FF, it's the object that the event was triggered on. In IE, it's the event itself. Thus, the srcElement / target solution was born. I'm interested to see the other solutions though - have a +1.

Related

How do I stopPropagation when changing an event inside an event?

So I have got the following javascript code:
function showSearch() {
var ev = new Event("onkeydown");
ev.keyCode = 13;
document.getElementById("search").onclick = search(ev);
}
The function showSearch() is called in the onclick-event of the Element #search. So when assigning the new event, the new one triggers immediately.
I want to prevent that using stopPropagation().
How do I do that? How else could I solve this?
onclick accepts a function as a parameter - you're providing it undefined (the result of showSearch() ; your current showSearch(), being a function call, executes immediately. Assign onclick a function instead of a function call:
document.getElementById("search").onclick = showSearch;
const search = () => console.log('searching');
function showSearch() {
var ev = new Event("onkeydown");
ev.keyCode = 13;
document.getElementById("search").onclick = () => search(ev);
}
<div id="search">text</div>
But using on-handlers is pretty bad practice. It would be better to add an event listener instead:
const searchDiv = document.getElementById("search");
searchDiv.addEventListener('click', showSearch);
const search = () => console.log('searching');
function showSearch() {
var ev = new Event("onkeydown");
ev.keyCode = 13;
searchDiv.removeEventListener('click', showSearch);
searchDiv.addEventListener('click', () => search(ev));
}
<div id="search">text</div>

Remove All Event Listeners of Specific Type

I want to remove all event listeners of a specific type that were added using addEventListener(). All the resources I'm seeing are saying you need to do this:
elem.addEventListener('mousedown',specific_function);
elem.removeEventListener('mousedown',specific_function);
But I want to be able to clear it without knowing what it is currently, like this:
elem.addEventListener('mousedown',specific_function);
elem.removeEventListener('mousedown');
That is not possible without intercepting addEventListener calls and keep track of the listeners or use a library that allows such features unfortunately. It would have been if the listeners collection was accessible but the feature wasn't implemented.
The closest thing you can do is to remove all listeners by cloning the element, which will not clone the listeners collection.
Note: This will also remove listeners on element's children.
var el = document.getElementById('el-id'),
elClone = el.cloneNode(true);
el.parentNode.replaceChild(elClone, el);
If your only goal by removing the listeners is to stop them from running, you can add an event listener to the window capturing and canceling all events of the given type:
window.addEventListener(type, function(event) {
event.stopImmediatePropagation();
}, true);
Passing in true for the third parameter causes the event to be captured on the way down. Stopping propagation means that the event never reaches the listeners that are listening for it.
Keep in mind though that this has very limited use as you can't add new listeners for the given type (they will all be blocked). There are ways to get around this somewhat, e.g., by firing a new kind of event that only your listeners would know to listen for. Here is how you can do that:
window.addEventListener('click', function (event) {
// (note: not cross-browser)
var event2 = new CustomEvent('click2', {detail: {original: event}});
event.target.dispatchEvent(event2);
event.stopPropagation();
}, true);
element.addEventListener('click2', function(event) {
if (event.detail && event.detail.original) {
event = event.detail.original
}
// Do something with event
});
However, note that this may not work as well for fast events like mousemove, given that the re-dispatching of the event introduces a delay.
Better would be to just keep track of the listeners added in the first place, as outlined in Martin Wantke's answer, if you need to do this.
You must override EventTarget.prototype.addEventListener to build an trap function for logging all 'add listener' calls. Something like this:
var _listeners = [];
EventTarget.prototype.addEventListenerBase = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener)
{
_listeners.push({target: this, type: type, listener: listener});
this.addEventListenerBase(type, listener);
};
Then you can build an EventTarget.prototype.removeEventListeners:
EventTarget.prototype.removeEventListeners = function(targetType)
{
for(var index = 0; index != _listeners.length; index++)
{
var item = _listeners[index];
var target = item.target;
var type = item.type;
var listener = item.listener;
if(target == this && type == targetType)
{
this.removeEventListener(type, listener);
}
}
}
In ES6 you can use a Symbol, to hide the original function and the list of all added listener directly in the instantiated object self.
(function()
{
let target = EventTarget.prototype;
let functionName = 'addEventListener';
let func = target[functionName];
let symbolHidden = Symbol('hidden');
function hidden(instance)
{
if(instance[symbolHidden] === undefined)
{
let area = {};
instance[symbolHidden] = area;
return area;
}
return instance[symbolHidden];
}
function listenersFrom(instance)
{
let area = hidden(instance);
if(!area.listeners) { area.listeners = []; }
return area.listeners;
}
target[functionName] = function(type, listener)
{
let listeners = listenersFrom(this);
listeners.push({ type, listener });
func.apply(this, [type, listener]);
};
target['removeEventListeners'] = function(targetType)
{
let self = this;
let listeners = listenersFrom(this);
let removed = [];
listeners.forEach(item =>
{
let type = item.type;
let listener = item.listener;
if(type == targetType)
{
self.removeEventListener(type, listener);
}
});
};
})();
You can test this code with this little snipper:
document.addEventListener("DOMContentLoaded", event => { console.log('event 1'); });
document.addEventListener("DOMContentLoaded", event => { console.log('event 2'); });
document.addEventListener("click", event => { console.log('click event'); });
document.dispatchEvent(new Event('DOMContentLoaded'));
document.removeEventListeners('DOMContentLoaded');
document.dispatchEvent(new Event('DOMContentLoaded'));
// click event still works, just do a click in the browser
Remove all listeners on a global event
element.onmousedown = null;
now you can go back to adding event listeners via
element.addEventListener('mousedown', handler, ...);
This solution only works on "Global" events. Custom events won't work. Here's a list of all global events: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers
I know this is old, but I had a similar issue with no real answers, where I wanted to remove all keydown event listeners from the document. Instead of removing them, I override the addEventListener to ignore them before they were even added, similar to Toms answer above, by adding this before any other scripts are loaded:
<script type="text/javascript">
var current = document.addEventListener;
document.addEventListener = function (type, listener) {
if(type =="keydown")
{
//do nothing
}
else
{
var args = [];
args[0] = type;
args[1] = listener;
current.apply(this, args);
}
};
</script>
A modern way to remove event listeners without referencing the original function is to use AbortController. A caveat being that you can only abort the listeners that you added yourself.
const buttonOne = document.querySelector('#button-one');
const buttonTwo = document.querySelector('#button-two');
const abortController = new AbortController();
// Add multiple click event listeners to button one
buttonOne.addEventListener(
'click',
() => alert('First'),
{ signal: abortController.signal }
);
buttonOne.addEventListener(
'click',
() => alert('Second'),
{ signal: abortController.signal }
);
// Add listener to remove first button's listeners
buttonTwo.addEventListener(
'click',
() => abortController.abort()
);
<p>The first button will fire two alert dialogs when clicked. Click the second button to remove those listeners from the first button.</p>
<button type="button" id="button-one">Click for alerts</button>
<button type="button" id="button-two">Remove listeners</button>
Remove all listeners in element by one js line:
element.parentNode.innerHTML += '';
You cant remove a single event, but all? at once? just do
document.body.innerHTML = document.body.innerHTML
In the extreme case of not knowing which callback is attached to a window listener, an handler can be wrapper around window addEventListener and a variable can store ever listeners to properly remove each one of those through a removeAllEventListener('scroll') for example.
var listeners = {};
var originalEventListener = window.addEventListener;
window.addEventListener = function(type, fn, options) {
if (!listeners[type])
listeners[type] = [];
listeners[type].push(fn);
return originalEventListener(type, fn, options);
}
var removeAllEventListener = function(type) {
if (!listeners[type] || !listeners[type].length)
return;
for (let i = 0; i < listeners[type].length; i++)
window.removeEventListener(type, listeners[type][i]);
}
So this function gets rid of most of a specified listener type on an element:
function removeListenersFromElement(element, listenerType){
const listeners = getEventListeners(element)[listenerType];
let l = listeners.length;
for(let i = l-1; i >=0; i--){
removeEventListener(listenerType, listeners[i].listener);
}
}
There have been a few rare exceptions where one can't be removed for some reason.
You could alternatively overwrite the 'yourElement.addEventListener()' method and use the '.apply()' method to execute the listener like normal, but intercepting the function in the process. Like:
<script type="text/javascript">
var args = [];
var orginalAddEvent = yourElement.addEventListener;
yourElement.addEventListener = function() {
//console.log(arguments);
args[args.length] = arguments[0];
args[args.length] = arguments[1];
orginalAddEvent.apply(this, arguments);
};
function removeListeners() {
for(var n=0;n<args.length;n+=2) {
yourElement.removeEventListener(args[n], args[n+1]);
}
}
removeListeners();
</script>
This script must be run on page load or it might not intercept all event listeners.
Make sure to remove the 'removeListeners()' call before using.
var events = [event_1, event_2,event_3] // your events
//make a for loop of your events and remove them all in a single instance
for (let i in events){
canvas_1.removeEventListener("mousedown", events[i], false)
}

DOM pass argument to javascript function

I need to found other way how to use javascript function.
var b = ce("input"); // Here I create element.
b.setAttribute("name", "g4");
b.value = "Centimetrais(pvz:187.5)";
b.onfocus = function() { remv(this); };
b.onchange = function() { abs(this); };
b.onkeypress = function() { on(event); }; // I need to change this place becose then I pass "event" argument function doesn't work.
ac(1, "", b); // Here I appendChild element in form.
Here is the function:
function on(evt) {
var theEvent = evt|| window.event;
var key = theEvent.keyCode || theEvent.which;
key = String.fromCharCode( key );
var regex = /^[0-9.,]+$/;
if( !regex.test(key) ) {
theEvent.returnValue = false;
if(theEvent.preventDefault) theEvent.preventDefault();
}
}
In IE and chrome it work but in mozilla doesn't. Any alternative how to fix it for firefox?
Also at this path other function working in mozilla if pass other argument like "car","dog",this. For example:
firstFunction();
function firstFunction() {
var b = ce("input"); // Here I create element.
b.onkeypress = function() { on("hi!"); };
ac(1, "", b); // Here I appendChild element in form.
}
function on(evt) {
alert(evt);
}
If I understand your question correctly, the problem is that you're not accepting the event argument in the main handler. E.g., in your first example:
b.onkeypress = function() { on(event); };
it should be
b.onkeypress = function(e) { on(e || window.event); };
// Changes here --------^ and --^^^^^^^^^^^^
You're doing that in on, but in on, it's already too late, you've lost the argument provided to the onXYZ function.
The reason your code works in Chrome and IE is that IE uses a global event object instead of (or in modern versions, in addition to) the one it actually passes into the handler, and Chrome replicates that behavior for maximum compatibility with websites that expect that. Firefox does not.

Javascript context in inner event function

I am trying to access the properties of the MainObj inside the onclick of an elem.
Is there a better way to design it so the reference wont be to "MainObj.config.url"
but to something like this.config.url
Sample code:
var MainObj = {
config: { url: 'http://www.mysite.com/'},
func: function()
{
elem.onclick = function() {
var url_pre = MainObj.config.url+this.getAttribute('href');
window.open(url_pre, '_new');
};
}
}
'this' inside the object always refers to itself (the object). just save the context into a variable and use it. the variable is often called '_this', 'self' or '_self' (here i use _self):
var MainObj = {
config: { url: 'http://www.mysite.com/'},
func: function()
{
var _self = this;
elem.onclick = function() {
var url_pre = _self.config.url+this.getAttribute('href');
window.open(url_pre, '_new');
};
}
}
You can use the Module pattern:
var MainObj = (function () {
var config = { url: 'http://www.mysite.com/'};
return {
func: function() {
elem.onclick = function() {
var url_pre = config.url+this.getAttribute('href');
window.open(url_pre, '_new');
};
}
};
}());
First we define the config object in the local function scope. After that we return an object literal in the return statement. This object contains the func function which later can be invoked like: MainObj.func.
There is, most certainly, a better way... but I must say: binding an event handler in a method is -and I'm sorry for this- a terrible idea.
You might want to check MDN, about what it has to say about the this keyword, because this has confused and tripped up many a man. In your snippet, for example, this is used correctly: it'll reference elem. Having said that, this is what you could do:
var MainObj = (function()
{
var that = {config: { url: 'http://www.google.com/'}};//create closure var, which can be referenced whenever you need it
that.func = function()
{
elem.onclick = function(e)
{
e = e || window.event;
window.open(that.config.url + this.getAttribute('href'));
};
};
return that;//expose
}());
But as I said, binding an event handler inside a method is just not the way to go:
MainObj.func();
MainObj.func();//shouldn't be possible, but it is
Why not, simply do this:
var MainObj = (function()
{
var that = {config: { url: 'http://www.google.com/'}};
that.handler = function(e)
{
e = e || window.event;
window.open(that.config.url + this.getAttribute('href'));
};
that.init = function(elem)
{//pass elem as argument
elem.onclick = that.handler;
delete that.init;//don't init twice
delete that.handler;//doesn't delete the function, but the reference too it
};
return that;//expose
}());
MainObj.init(document.body);
Even so, this is not the way I'd write this code at all, but then I do tend to over-complicate things every now and then. But do look into how the call context is determined in JS, and how closures, object references and GC works, too... it's worth the effort.
Update:
As requested by the OP - an alternative approach
(function()
{
'use strict';
var config = {url: 'http://www.google.com/'},
handlers = {load: function(e)
{
document.getElementById('container').addEventListener('click',handlers.click,false);
},
click: function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
//which element has been clicked?
if (target.tagName.toLowerCase() === 'a')
{
window.open(config.url + target.getAttribute('href'));
if (e.preventDefault)
{
e.preventDefault();
e.stopPropagation();
}
e.returnValue = false;
e.cancelBubble = true;
return false;//overkill
}
switch(target.id)
{
case 'foo':
//handle click for #foo element
return;
case 'bar': //handle bar
return;
default:
if (target.className.indexOf('clickClass') === -1)
{
return;
}
}
//treat elements with class clickClass here
}
};
document.addEventListener('load',handlers.load,false);//<-- ~= init
}());
This is just as an example, and it's far from finished. Things like the preventDefault calls, I tend to avoid (for X-browser compatibility and ease of use, I augment the Event.prototype).
I'm not going to post a ton of links to my own questions, but have a look at my profile, and check the JavaScript questions. There are a couple of examples that might be of interest to you (including one on how to augment the Event.prototype in a X-browser context)

Get the caller of the event from attachEvent

I am trying to do something simple: I have a bunch of Images which are being load through JS.
I attach an event listener to the load event, and after the Image is being loaded, in the listener function I would like to get the calling Image and retrieve properties from it.
Here is my code, simplified:
function loadImages() {
for (var i = 0; i < arrDownloadQueueBasic.length; i++) {
var path = arrDownloadQueueBasic[i].path;
var img = new Image();
img.type = arrDownloadQueueBasic[i].type;
img.attachEvent(img, 'load', setBasicElement);
img.src = path;
}
}
function setBasicElement(e) {
var caller = e.target || e.srcElement;
alert(caller); // THIS DOESNT WORK - RETURN NULL
alert(caller.type) // OF COURSE THIS DOESNT WORK AS WELL...
}
There are a couple of things that you need to correct. First, the attachEvent method should not be used for browsers other than IE. You should structure your code to check if the method is implemented and then act accordingly like so:
if(img.addEventListener) {
img.addEventListener('load', setBasicElement, false);
}
else if(img.attachEvent) {
img.attachEvent('onload', setBasicElement);
}
else {
img.onload = setBasicElement;
}
The other issue is that you need to prefix the event name with "on" when using attachEvent.
EDIT
You can get the caller by using the following code in the setBasicElement function:
var caller = e.target || e.srcElement || window.event.target || window.event.srcElement;
Here is a working example - http://jsfiddle.net/BMsXR/3/
Try this:
var caller = window.event ? window.event.srcElement : e.target;
If I remember rightly IE doesn't pass the event object as a parameter when you've used attachEvent(), but it has a global event object.

Categories