So I have most of my functions and variables organized into small object-based modules, like so:
module1: {
someVariable: false,
someFunction: function(e) {
do some stuff using someVariable
},
someFunction2: function(e) {
do some other stuff
}
}
And I call these functions as callbacks during various events, like so:
$(function() {
$('.thing').on('mouseenter', module1.someFunction);
}
Now, from within someFunction, I would expect the 'this' keyword to refer to the object in which the function is contained. Instead, it refers to the DOM element that triggered the event that fires the function. Is there anyway I can get access to, say the someVariable variable in the function's containing object other than writing module1.someVariable?
The shortest answer is to try this:
$(function() {
$('.thing').on('mouseenter', function(e) {
module1.someFunction(e);
});
}
The 'this' value is only set to the object the method is attached to if the method is invoked directly on the object:
module1.someFunction(); // direct invocation, 'this' will be set properly
var tempFunc = module1.someFunction;
tempFunc(); // the function was removed first. 'this' will NOT be set
In your case, you are pealing the method off of the object and handing it to an event handler. The event handler doesn't know about the object and doesn't perform a direct invocation.
In fact, the event handler explicitly overrides the context because that is how the jQuery API is defined. You have to explicitly override it back if you want the behavior you're talking about.
Using a library like underscore.js you could also bind the function as you pass it off to the event handler.
$(function() {
$('.thing').on('mouseenter', _.bind(module1.someFunction, module1));
}
I believe that Object.bind is supposed to be natively supported in the future with no libraries, but you can't yet rely on the older browsers to support it.
Related
So lets rewrite this to fit the correct answer I now remember asking however long ago.
Jfriend00's answer in the comments is correct.
Given the use case of an emitted event from an object that is derived from eventemitter
I wanted to know how to get a reference to the object itself.
So eg.
var myObj = new EmitterDescendent(); // some descendent of an emitter class
myObj.name="123"; // some form of object tag.
myObj.on('eventofsomesort', ()=>
{
var ref = (some manner of grabbing a reference to the object);
console.log (ref.name);
});
// expected output: 123
Apparrently lambda screws up the 'this' keyword when the event handler is being called, I do not no why as using the function keyword should still be seen as an anonymous function.
Jfriend00's answer was what I was looking for.
Thanks very much.
This should be obvious. You already know which object emitted the event, because you registered an event handler on it. You should have tested your cod and would have found that Object doesn't have an on function:
> Object.on('event', console.log)
Uncaught TypeError: Object.on is not a function
Instead, in node.js you need to extend EventEmitter, and then register listeners on the instance of that class. So you will know inside the handler who emitted the event.
EventEmitter = require('events');
e = new EventEmitter();
e.on('event', () => {
console.log(e, 'emitted event');
});
e.emit('event');
I'm going to ignore your example code using Object because there is no .on() method on Object. That's a method that exists on the EventEmitter class. Perhaps you didn't mean that to indicate the actual Object class in Javascript.
Assuming you had some code that was actually using an EventEmitter, a subclass of EventEmitter or some code that implements that interface, then if you declare your listener with a regular function, NOT with an arrow function, then the value of this will be the object that emitted the event.
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
myEmitter.on("hi", function(data) {
console.log(this === myEmitter); // logs true
});
myEmitter.emit("hi", "hello");
Note, that in addition to using the value of this, if you're using an inline function declaration for the listener function, you also have direct access to the variable that you registered the event listener on. In this case, it's the myEmitter variable. If your listener is an external function, then that option is not available to you and you can use the value of this. I remind you that you must register your listener as a regular function, not an arrow function because the arrow function overrides the value of this.
Here's a doc reference to a discussion about using this in a listener.
I guess that your Object has extended from EventEmitter somehow. Nodejs EventEmitter events are not the same as HTML5 events. There's no target or currentTarget properties as the callback arguments are just the "raw" arguments passed in the emit() call.
You already have the object reference, and there are better ways of doing it, but if you really want to receive the object in the function callback, another way fo doing it, apart from using scopes and closures like the other answer, is to create a function with a binded argument, like so (by the way, I personally prefer the scopes/closures one instead of the binding one):
Object.name="123";
Object.on('event', (function(ref) {
console.log(ref.name);
}).bind(this, Object));
Object.emit('event');
You can even have the same function and bind it each time for each object, like so:
function funccallback(ref) {
console.log(ref.name);
}
var object1 = {}; // Extend from "EventEmitter" somehow or it will not work
object1.name="123";
object1.on('event', funccallback.bind(this, object1));
object1.emit('event');
var object2 = {}; // Extend from "EventEmitter" somehow or it will not work
object2.name="124";
object2.on('event', funccallback.bind(this, object2));
object2.emit('event');
I have written two functions in JavaScript code as follows
Manager = FormManager.extend({
First: function () {
var response = this.Second("Feature"); //I'm able to get the alert
//I have added a click event handler
$('#element').on('click', function(){
var newResponse = this.Second("Bug"); //The alert is not poping
});
}
Second: function (type) {
alert(type);
//Performs certain operation
}
});
Error: Uncaught TypeError: Object #<HTMLButtonElement> has no method 'Second'
I also tried without using this keyword like:
Second("Bug") // Error: There is no method
Whereas this a simplified format (in-order to show a simple example) on my program that I'm playing with. I'm struggling to find out the reason.
Can someone direct me to the right path?
You are using incorrect this. try this way. this inside the handler represents #element not the context of the function itself.
var self = this; //cache the context here
$('#element').on('click', function(){
var newResponse = self.Second("Bug"); //Access it with self
});
Also i think you are missing a comma after First function definision and before Second function.
Fiddle
The reason being the callback you give gets invoked from within the context of the element so your this context changes. this context refers to the context from where the callback was invoked. But there are other ways to get around this like using $.proxy while binding your callback with jquery, using EcmaScript5 Function.prototype.bind etc. But ideally you don't want to do that because most of the cases you would need the context of the element there inside the handler.
Every time you use the this context variable in a function you have to consider what its value is.
Specifically that value will be whatever value the caller specified, whether by using myObj.mymethod(...), or mymethod.call(myObj, ...), or mymethod.apply(myObj, [ ... ]).
When your anonymous function $('#element').on('click', ...) is invoked jQuery will set the context to the HTML DOM element - it's no longer referring to your object.
The simplest work around is to obtain a copy of this outside of the callback, and then refer to that copy inside the closure, i.e.:
var that = this;
$('#element').on('click', function() {
// use that instead of this, here
console.log(this); // #element
console.log(that); // your object
});
Another method is using Function.prototype.bind:
$('#element').on('click', (function() {
console.log(this); // your object
}).bind(this));
or with jQuery you can use $.proxy for the same effect, since .bind is an ES5 function.
I actually prefer the var that = this method, since it doesn't break the jQuery convention that this refers to the element associated with the event.
In traditional event registration model:
function foo(e){console.log(e.type)}
document.getElementById("#id").onclick=foo;//registered event handler
But in inline event registration model:
<a href="#" onclick=foo(event)>clck</a>
console.log(a.click)===function click(){foo(event)}
Can't event object be used directly within the function foo rather than pass as a function argument.Since event object being used within the click function is not passed by the browser we are manually passing it.Why passing event object within the event handler function dont work?
Since event object being used within the click function is not passed by the browser we are manually passing it.
That's not correct. The browser (at least W3C compatible browser) pass the event object to the event handler.
The equivalent to
onclick="foo()"
is
elem.onclick = function(event) {
foo();
};
The browser creates a function with event as first parameter and uses the value of the attribute as body of the function.
You have to pass the event object explicitly to foo because that's how JavaScript functions work. If you call a function inside another function, the outer function's parameters are not automatically passed to the inner function (that would be really confusing IMO).
Simpler example:
function foo(a) {
bar();
}
function bar(a) {
alert(a); // will show `undefined`
}
foo(42);
What is the recommended way to pass cached jQuery references, e.g. $domContainer in var $domContainer = $('#container'); to functions as a callback if the functions are defined before and outside of $(document).ready()?
Example:
<script src="/plugins.js"></script>
In this external file of re-usable functions
function rowAction ( event ) { // how do I get context here?
// how can I access $domTable and $domFilters
// I can access $(event.target) and traverse the dom
// back to $domTable, but $domTable is not defined
// in the external file, although a reference is cached
// in the local scope of $(document).ready();
// likewise, $domTable could be accessed through event.delegateTarget
// however, how can I pass $domFilters, and other vars?
}
In the main script
<script src="/scripts.js"></script>
The standard document ready
$(document).ready(function(){
// Cached References
var $domFilters = $('#form .filter'), // more than 1 reference returned
$domTable = $('#results'); // 1 reference returned
$domTable.on('click','.menu',rowAction);// reference function in plugins.js
// how do I pass $domFilters to rowAction to avoid dom lookups?
// I could pass $domFilters to a plugin like $(...).plugin({f:$domFilters});
// if it had optional arguments, but if it's not a plugin, what's the
// equivalent way to do it?
});
Would the way to approach this be to use an inline function to wrap the callback function name?
Any pointers to a standard practice would be welcome too.
You can follow a modular approach by defining a NameSpace. Then you won't have to use ready.
//These four will be open
var objects, handlers, bindings,
NameSpace = {
//This is where you cache references
objects: {
domcontainer: $('.domcontainer')
},
//Do the events to handlers bindings
bindings: function(){
domcontainer.on('click', handlers.clickCallback)
}
//The actual handlers
handlers: {
clickCallback: function(e){
//Do something
}
},
//Initial things
init: function(){
objects = this.objects;
handlers = this.handlers;
//This will start the handler binding.
this.bindings();
}
};
$(function () {
NameSpace.init();
});
If you are adding objects on the fly, then inside objects you can add references as functions that return the actual object reference. Whenever you need to refer an object, it will be available already hence avoiding DOM look up.
If you are looking to access $domTable you can use the event.delegateTarget property of the event object without having to traverse the dom. You will have to wrap it in a jQuery object though.
Edit
A standard way of passing the context and extra properties to an external function would be to use call() or apply() jQuery has it's own wrapper for that behaviour also called proxy()
In your example with $domTable the context is already passed through as the target of the selector so everything you need would be available to you.
In your $domFilters example there is no event object to pass as the context since event are mapped to actual events in the dom and all you have there is a collection so you couldn't use that function since it relies on the event object.
If I was calling another function though from a collection whilst passing the context I would use something like this.
$domFilters = $('#form .filter');
$domFilters.each(function(){
// Call the external function passing the jQuery wrapped item
// as the context.
externalFunction.call($(this));
});
// Your external function
function externalFunction(){
// 'this' would now refer to the context passed in call.
// i.e your $(.filter) item.
}
Your utility function has to be designed to be able to handle whatever is passed to it as context plus any additional arguments though.
I have an ajax function (not sure if relevant) that updates html and creates a few links:
click me
I'm not sure why, but onclick, if I alert $(this).attr('title') it shows as undefined, and if I alert $(this) it shows [window]
function column_click(){
value = $(this);
console.log(value);
thetitle= $(this).attr('title');
console.log(thetitle);
}
Does anyone know why this is the case?
This should fix the issue.
onclick="column_click.call(this);"
The reason is that your "click handler" is really just a function. The default is to have this refer to the window object.
In my example above, we are saying "execute column_click and make sure this refers to the a element.
You're confusing the obtrusive and unobtrusive styles of JS/jQuery event handling. In the unobtrusive style, you set up click handlers in the JavaScript itself, rather than in an onclick attribute:
$('.clickme').on('click', column_click);
The above will automatically bind this to the clicked element while the event is being handled.
However, this is not standard JavaScript! It's a feature of jQuery. The on method is smart enough to bind the function to the HTML element when it handles the event. onclick="column_click" doesn't do this, because it isn't jQuery. It uses standard JS behavior, which is to bind this to the global object window by default.
By the way, the reason you see [window] is that $(this) has wrapped window in a jQuery object, so it looks like an array with the window object inside it.
There are three main ways to deal with your problem:
Use unobtrusive binding: $('.clickme').on('click', column_click); in a script at the end of the page, or somewhere in the $(document).ready handler
Bind this manually: onclick="column_click.call(this)"
Avoid using this at all:
function column_click(e) {
var value = $(e.target);
//...
Of these, I'd strongly recommend either 1 or 3 for the sake of good coding.
You need to pass the parameter in the function of column_click,
click me
function column_click(obj){
value = $(obj);
console.log(value);
}
Note: this refer window object. so won't work what you expect.
A Short Overview of this*
When you execute a function in JavaScript, the default this is window.
function foo() {
console.log(this);
}
foo(); // => window
The this value can be changed in a number of ways. One way is to call the function as a method of an object:
var x = {
foo: function() {
console.log(this);
}
};
x.foo(); // => This time it's the x object.
Another way is to use call or apply to tell the function to execute in the context of a certain object.
function foo() {
console.log(this);
}
foo.call(x); // => x object again
foo.apply(x); // => x object as well
If you call or apply on null or undefined, the default behavior will occur again: the function will be executed in the context of window:
function foo() {
console.log(this);
}
foo.call(null); // => window
foo.apply(undefined); // => window
However, note that in ECMAScript 5 strict mode, this does not default to window:
(function() {
'use strict';
function foo() {
console.log(this);
}
foo(); // => undefined
foo.call(null); // => null
foo.apply(undefined); // => undefined
})();
You can also set the this by using bind to bind the function to an object before it is called:
function foo() {
console.log(this);
}
var bar = {
baz: 'some property'
};
var foobar = foo.bind(bar);
foobar(); // => calls foo with bar as this
Conclusion
You're using this code:
click me
Which means that when the link is clicked, it executes column_click();. That means the column_click function gets executed as a plain function, not a method, because (1) it's not called as a property of an object (someobject.column_click();), (2) it's not called with call or apply, and (3) it's not called with bind. Since it's not running in strict mode, the default this is window.
How to Fix Your Problem
Therefore, to fix your problem, you can simply use call (or apply) to tell the function to execute in the context of the element. Within the small code inside the attribute value, this refers to the element. So we can use column_click.call(this). It's that easy!
click me
However, it would probably make more sense just to pass the element as an argument:
click me
and change your function to accept the argument:
function column_click(el) {
// Use el instead of this...
}
* Getting Technical
this in JavaScript is dynamically scoped. This behavior differs from all other variables which are lexically scoped. Other variables don't have a different binding depending on how the function is called; their scope comes from where they appear in the script. this however behaves differently, and can have a different binding depending not on where it appears in the script but on how it's called. This can be a source of confusion for people learning the language, but mastering it is necessary in order to become a proficient JavaScript developer.
You're using jQuery right? Why not:
$(".clickme").click(function() {
value = $(this);
console.log(value);
thetitle= $(this).attr('title');
console.log(thetitle);
});
// or
$(".clickme").click(column_click);