Passing parameters in addEventListener - javascript

I am working on a Firefox extension, and I am trying to pass a parameter to addEventListener. I am "listening" for changes in the page title, my code looks something like this:
function Test()
{
this.checkTitle = function( event )
{
var func = function() { this.onTitleChange ( event ); };
var target = content.document.getElementsByTagName('TITLE')[0];
target.addEventListener('DOMSubtreeModified', func, false);
}
this.onTitleChange = function( e )
{
// do stuff with e
alert('test');
}
this.handleEvent = function (event)
{
switch (event.type)
{
case "DOMContentLoaded":
{
this.checkTitle( event );
}
}
}
window.addEventListener ("DOMContentLoaded", this, false);
}
I never get the 'test' alert, if I use func = function() { alert(event); }; it does show an alert with '[object Event]'. Also tried without using this. on func but still wont work.
How can I make this work to be able to access checkTitle parameter "event" into onTitleChange?

When the browser calls the event handler, this will refer to the element, not your instance.
You need to save a copy of the desired this in a separate variable:
var self = this;
var func = function(e) { self.onTitleChange (e); };
var target = content.document.getElementsByTagName('TITLE')[0];
target.addEventListener('DOMSubtreeModified', func, false);

Related

Can't remove event listener for windows object

I am having a lot of trouble trying to remove an event listener.
I have created a website that relies on JavaScript quite heavily. When you navigate on the website it is basically loading in elements dynamically without a page refresh with template literals.
I have to sometimes load in content and add infinite scroll but also be able to remove that event again.
This is the code I use to handle scroll events:
var start = 30;
var active = true;
function yHandler(elem)
{
var oHeight = selectElems('content_main', 'i').offsetHeight;
var yOffset = window.pageYOffset;
var hLimit = yOffset + window.innerHeight;
if (hLimit >= oHeight - 500 && active === true)
{
active = false;
new requestContent({
page: GET.page,
type: returnContentType(GET.page),
scroll: true,
start: start
}, (results) => {
if(results){
setTimeout(()=>{
active = true;
start = start + 30;;
}, 400);
new ContentActive();
}
});
}
}
var scrollRoute =
{
contentScroll: () =>{
yHandler();
}
};
var scrollHandler = function(options)
{
var func = options.func.name;
var funcOptions = options.func.options;
var elem = options.elem;
var flag = options.flag;
this.events = () => {
addEvent(elem, 'scroll', ()=>{
scrollRoute[func](elem, funcOptions);
}, flag);
}
this.clear = () => {
elem.removeEventListener('scroll', scrollRoute[func](), flag);
}
}
I am using this function to set events
function addEvent(obj, type, fn, flag = false) {
if (obj.addEventListener) {
obj.addEventListener(type, fn, flag);
} else if (obj.attachEvent) {
obj["e" + type + fn] = fn;
obj[type + fn] = function () {
obj["e" + type + fn](window.event);
};
obj.attachEvent("on" + type, obj[type + fn]);
} else {
obj["on" + type] = obj["e" + type + fn];
}
}
I am calling this code from whatever code when I need to set the infinite scroll event
new scrollHandler({
func: {
'name':'contentScroll',
},
elem: window,
flag: true,
}).events();
I am calling this code from whatever code when I need to remove the infinite scroll event but without any luck
new scrollHandler({
func: {
'name':'contentScroll',
},
elem: window,
flag: true,
}).clear();
How do I successfully remove the event listener? I can't just name the instances, that will be so messy in the long run when setting and removing the scroll events from various different places.
Two problems:
You have to pass the same function to removeEventListener as you passed to addEventListener. (Similarly, you have to pass the same function to detachEvent as you passed to attachEvent using Microsoft's proprietary stuff — but unless you really have to support IE8 and earlier, you can ditch all that.) Your code isn't doing that.
When trying to remove the handler, you're calling scrollRoute[func]() and passing its return value into removeEventListener. As far as I can tell, that's passing undefined into removeEventListener, which won't do anything useful.
Here's the code I'm referring to above:
this.events = () => {
addEvent(elem, 'scroll', ()=>{ // *** Arrow function you don't
scrollRoute[func](elem, funcOptions); // *** save anywhere
}, flag); // ***
}
this.clear = () => {
elem.removeEventListener('scroll', scrollRoute[func](), flag);
// Calling rather than passing func −−−^^^^^^^^^^^^^^^^^^^
}
Notice that the function you're passing addEvent (which will pass it to addEventListener) is an anonymous arrow function you don't save anywhere, but the function you're passing removeEventListener is the result of calling scrollRoute[func]().
You'll need to keep a reference to the function you pass addEvent and then pass that same function to a function that will undo what addEvent did (removeEvent, perhaps?). Or, again, ditch all that, don't support IE8, and use addEventListener directly.
So for instance:
var scrollHandler = function(options) {
var func = options.func.name;
var funcOptions = options.func.options;
var elem = options.elem;
var flag = options.flag;
var handler = () => {
scrollRoute[func](elem, funcOptions);
};
this.events = () => {
elem.addEventListener('scroll', handler, flag);
};
this.clear = () => {
elem.removeEventListener('scroll', handler, flag);
};
};
(Notice I added a couple of missing semicolons, since you seem to be using them elsewhere, and consistent curly brace positioning.)
Or using more features of ES2015 (since you're using arrow functions already):
var scrollHandler = function(options) {
const {elem, flag, func: {name, options}} = options;
const handler = () => {
scrollRoute[name](elem, options);
};
this.events = () => {
elem.addEventListener('scroll', handler, flag);
};
this.clear = () => {
elem.removeEventListener('scroll', handler, flag);
};
};

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)

Add custom arguments for jQuery special event

I am writing a jQuery event plugin and I need to pass some data to a argument. So if I use it like this:
$(element).on('myevent', function(event, myargument) { console.log(myargument); });
I wan't to get the myargument object and it's properties (e.g. name), which is set in the handler.
So how would this work with the code below?
SmartScroll.myevent = {
setup: function() {
var myargumentData,
handler = function(evt) {
var _self = this,
_args = arguments;
// The data I wan't to get
myargumentData = {
name: "Hello"
};
evt.type = 'myevent';
jQuery.event.handle.apply(_self, _args);
};
jQuery(this).bind('scroll', handler).data(myargumentData, handler);
},
teardown: function() {
jQuery(this).unbind('scroll', jQuery(this).data(myargumentData));
}
};
You can modify an object passed to add.
$.event.special.foo = { add: function(h) {
var hh = h.handler;
h.handler = function(e, a) {
hh.call(this, e, a * 2);
};
} };
Now, let's bind the event:
$("body").on("foo", function(e, a) {
console.log(a);
});
Fire the event and see what happens:
$("body").trigger("foo", [ 10 ]); // 20
on: function( types, selector, data, fn, /*INTERNAL*/ one )
According to the source.
so if i understand your problem , just do :
$(element).on('myevent',{my_data:"foo"},function(event) {
console.log(event.data.my_data);
});
prints "foo"

Removing event listener which was added with bind

In JavaScript, what is the best way to remove a function added as an event listener using bind()?
Example
(function(){
// constructor
MyClass = function() {
this.myButton = document.getElementById("myButtonID");
this.myButton.addEventListener("click", this.clickListener.bind(this));
};
MyClass.prototype.clickListener = function(event) {
console.log(this); // must be MyClass
};
// public method
MyClass.prototype.disableButton = function() {
this.myButton.removeEventListener("click", ___________);
};
})();
The only way I can think of is to keep track of every listener added with bind.
Above example with this method:
(function(){
// constructor
MyClass = function() {
this.myButton = document.getElementById("myButtonID");
this.clickListenerBind = this.clickListener.bind(this);
this.myButton.addEventListener("click", this.clickListenerBind);
};
MyClass.prototype.clickListener = function(event) {
console.log(this); // must be MyClass
};
// public method
MyClass.prototype.disableButton = function() {
this.myButton.removeEventListener("click", this.clickListenerBind);
};
})();
Are there any better ways to do this?
Although what #machineghost said was true, that events are added and removed the same way, the missing part of the equation was this:
A new function reference is created after .bind() is called.
See Does bind() change the function reference? | How to set permanently?
So, to add or remove it, assign the reference to a variable:
var x = this.myListener.bind(this);
Toolbox.addListener(window, 'scroll', x);
Toolbox.removeListener(window, 'scroll', x);
This works as expected for me.
For those who have this problem while registering/removing listener of React component to/from Flux store, add the lines below to the constructor of your component:
class App extends React.Component {
constructor(props){
super(props);
// it's a trick! needed in order to overcome the remove event listener
this.onChange = this.onChange.bind(this);
}
// then as regular...
componentDidMount (){
AppStore.addChangeListener(this.onChange);
}
componentWillUnmount (){
AppStore.removeChangeListener(this.onChange);
}
onChange () {
let state = AppStore.getState();
this.setState(state);
}
render() {
// ...
}
}
It doesn't matter whether you use a bound function or not; you remove it the same way as any other event handler. If your issue is that the bound version is its own unique function, you can either keep track of the bound versions, or use the removeEventListener signature that doesn't take a specific handler (although of course that will remove other event handlers of the same type).
(As a side note, addEventListener doesn't work in all browsers; you really should use a library like jQuery to do your event hook-ups in a cross-browser way for you. Also, jQuery has the concept of namespaced events, which allow you to bind to "click.foo"; when you want to remove the event you can tell jQuery "remove all foo events" without having to know the specific handler or removing other handlers.)
jQuery solution:
let object = new ClassName();
let $elem = $('selector');
$elem.on('click', $.proxy(object.method, object));
$elem.off('click', $.proxy(object.method, object));
We had this problem with a library we could not change. Office Fabric UI, which meant we could not change the way event handlers were added. The way we solved it was to overwrite the addEventListener on the EventTarget prototype.
This will add a new function on objects element.removeAllEventListers("click")
(original post: Remove Click handler from fabric dialog overlay)
<script>
(function () {
"use strict";
var f = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, fn, capture) {
this.f = f;
this._eventHandlers = this._eventHandlers || {};
this._eventHandlers[type] = this._eventHandlers[type] || [];
this._eventHandlers[type].push([fn, capture]);
this.f(type, fn, capture);
}
EventTarget.prototype.removeAllEventListeners = function (type) {
this._eventHandlers = this._eventHandlers || {};
if (type in this._eventHandlers) {
var eventHandlers = this._eventHandlers[type];
for (var i = eventHandlers.length; i--;) {
var handler = eventHandlers[i];
this.removeEventListener(type, handler[0], handler[1]);
}
}
}
EventTarget.prototype.getAllEventListeners = function (type) {
this._eventHandlers = this._eventHandlers || {};
this._eventHandlers[type] = this._eventHandlers[type] || [];
return this._eventHandlers[type];
}
})();
</script>
Here is the solution:
var o = {
list: [1, 2, 3, 4],
add: function () {
var b = document.getElementsByTagName('body')[0];
b.addEventListener('click', this._onClick());
},
remove: function () {
var b = document.getElementsByTagName('body')[0];
b.removeEventListener('click', this._onClick());
},
_onClick: function () {
this.clickFn = this.clickFn || this._showLog.bind(this);
return this.clickFn;
},
_showLog: function (e) {
console.log('click', this.list, e);
}
};
// Example to test the solution
o.add();
setTimeout(function () {
console.log('setTimeout');
o.remove();
}, 5000);
As others have said, bind creates a new function instance and thus the event listener cannot be removed unless it is recorded in some way.
For a more beautiful code style, you can make the method function a lazy getter so that it's automatically replaced with the bound version when accessed for the first time:
class MyClass {
activate() {
window.addEventListener('click', this.onClick);
}
deactivate() {
window.removeEventListener('click', this.onClick);
}
get onClick() {
const func = (event) => {
console.log('click', event, this);
};
Object.defineProperty(this, 'onClick', {value: func});
return func;
}
}
If ES6 arrow function is not supported, use const func = (function(event){...}).bind(this) instead of const func = (event) => {...}.
Raichman Sergey's approach is also good, especially for classes. The advantage of this approach is that it's more self-complete and has no separated code other where. It also works for an object which doesn't have a constructor or initiator.
If you want to use 'onclick', as suggested above, you could try this:
(function(){
var singleton = {};
singleton = new function() {
this.myButton = document.getElementById("myButtonID");
this.myButton.onclick = function() {
singleton.clickListener();
};
}
singleton.clickListener = function() {
console.log(this); // I also know who I am
};
// public function
singleton.disableButton = function() {
this.myButton.onclick = "";
};
})();
I hope it helps.
can use about ES7:
class App extends React.Component {
constructor(props){
super(props);
}
componentDidMount (){
AppStore.addChangeListener(this.onChange);
}
componentWillUnmount (){
AppStore.removeChangeListener(this.onChange);
}
onChange = () => {
let state = AppStore.getState();
this.setState(state);
}
render() {
// ...
}
}
It's been awhile but MDN has a super explanation on this. That helped me more than the stuff here.
MDN :: EventTarget.addEventListener - The value of "this" within the handler
It gives a great alternative to the handleEvent function.
This is an example with and without bind:
var Something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as this is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as this is the binded Something object
};
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}
A problem in the example above is that you cannot remove the listener with bind. Another solution is using a special function called handleEvent to catch any events:

Passing parameters to eventListener function

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.

Categories