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.
Related
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.
Assuming jquery is included:
function FixedPoint(bgWidth) {
this.bgWidth= bgWidth;
this.plot();
$(window).resize(this.plot);
}
FixedPoint.prototype.plot = function() {
console.log(this.bgWidth); //This is undefined when called by resize
}
var pt1 = new FixedPoint(1920);
When plot() is being called in the constructor or after the initialization everything is ok, but when plot() is being called by resize function, it can no longer access instance variables via 'this'.
I can call resize out side of the constructor to fix this, but want to have it in the class for tidiness.
$(window).resize(this.plot); The method this.plot is getting invoked from within the window context. So this is the expected behavior. this will point to the window object instead of FixedPoint. You can use Ecmascript5 function.bind to explicitly bind the context.
$(window).resize(this.plot.bind(this));
With jquery you can use $.proxy to do the same thing.
Just to give more insight, this context is set based on where the method was invoked from (except for the bound functions). Here this would have got invoked from within the resize method of window object, where this refers to window.
Another hacky way is to use anonymous callback function and use cached this variable.
function FixedPoint(bgWidth) {
this.bgWidth = bgWidth;
var self = this; //cache it here
$(window).resize(function () {
self.plot(); //invoke with self.
});
}
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);
Problem & Reason
One of my team mate ended up in messy situtaion implementing function hooking in javascript. this is the actual code
function ActualMethod(){
this.doSomething = function() {
this.testMethod();
};
this.testMethod = function(){
alert("testMethod");
};
}
function ClosureTest(){
var objActual= new ActualMethod();
var closeHandler = objActual.doSomething;
closeHandler();
closeHandler.apply(objActual,arguments); //the fix i have added
this.ActualTest = function() {
alert("ActualTest");
};
}
In the above code, var closeHandler is created in the context of ClosureTest(), but it holds the handler of the ActualMethod.doSomething. Whenever calling the closeHandler() ended up in "object doesnt support this method" error.
This is because doSomething() function calls another method inside called this.testMethod();. Here this refers to the context of the caller not callee.so i assume the closeHandler is bound to the environment(ClosureTest) actually created.Even though it holds the handler to the another context, it just exposes the properties of its own context.
Solution
To avoid this i suggest to use apply to specify the conext in which it needs to execute.
closeHandler.apply(objActual,arguments);
Questions
is it perfect scenario for closures..??
What are the intersting places you have encountered closures in javascript..?
UPDATE
Yes its simple i can call the method directly. but the problem is, in a particular scenario I need to intercept the call to actuall method and run some code before that, finally execute the actual method..
say for an example, am using 3rd party aspx grid library, and all the mouseclick events are trapped by their controls. In particular group by mouse click i need to intercept the call to their ilbrary method and hook my mthod to execute instead and redirect the call to actual library method
hope this helps
Update: Because you probably left out some details in your code, it is difficult to adapt it into something workable without missing the point of your actual code. I do think I understand your underlying problem as you describe it. I hope the following helps.
Suppose the following simple example:
// Constructor function.
function Example() {
// Method:
this.method = function() {
alert("original method");
}
}
// You would use it like this:
var obj = new Example();
obj.method(); // Calls original method.
To intercept such a method call, you can do this:
function wrap(obj) {
var originalMethod = obj.method;
obj.method = function() {
alert("intercepted call");
originalMethod.apply(this, arguments);
}
return obj;
}
var obj = wrap(new Example());
obj.method(); // Calls wrapped method.
Unfortunately, because method() is defined in the constructor function, not on a prototype, you need to have an object instance to wrap the object.
Answer to original question: The doSomething() function is used as a method on objects created with ActualMethod(). You should use it as a method, not detach it and use it as a function in a different context. Why don't you just call the method directly?
function ClosureTest(){
var objActual = new ActualMethod();
// Call method directly, avoid messy apply() calls.
objActual.doSomething();
this.ActualTest = function() {
alert("ActualTest");
};
}
If you assign a method (a function on some object) to a local variable in Javascript and call it, the context will be different (the value of this changes). If you don't want it to happen, don't do it.
When I want to hook a function, I use the following Function method which is also a fine piece of Closure demonstration:
Function.prototype.wrap = function (wrapper) {
var __method = this;
return function() {
var __obj = this;
var args = [ __method.bind(__obj) ];
for(var i=0; i<arguments.length; i++) args.push(arguments[i]);
return wrapper.apply(__obj, args);
}
};
Then do something like:
ActualMethod = ActualMethod.wrap(function (proceed, option) {
// ... handle option
proceed(); // calls the wrapped function
});
proceed is bound to its initial object, so you can safely call it.