I'm trying to do a custom tabs system using JavaScript. However, in order to reuse the code I want to write it in OOP style. This what I have so far:
function Tabs()
{
this.tabArray = new Array(arguments.length);
this.tabArrayC = new Array(arguments.length);
for(i=0;i<arguments.length;i++)
{
this.tabArray[i] = arguments[i];
this.tabArrayC[i] = arguments[i]+'_content';
}
this.setClickable = setClickable;
function setClickable()
{
for(i=0;i<this.tabArray.length;i++)
{
document.getElementById(this.tabArray[i]).onclick = function()
{
alert(this.tabArray[i]);
}
}
}
}
function init()
{
tab = new Tabs('tab1','tab2','tab3','tab4');
tab.setClickable();
}
window.onload = init();
Now here's the deal. I want to assign the onclick event handler to every tab that has been passed in Tabs 'class' constructor. So later in the code when I write something like:
<div id="tab1">Tab1</div>
<div id="tab2">Tab2</div>
<div id="tab3">Tab3</div>
<div id="tab4">Tab4</div>
The code which has been set up earlier:
document.getElementById(this.tabArray[i]).onclick = function()
{
alert(this.tabArray[i]);
}
... would be executed. I hope I explained that well enough. Any ideas?
There are three issues with your setClickable function (edit: and an issue with how you're calling init):
this will have a different meaning within the event handler you're generating than you expect. (More here: You must remember this)
A closure (a function that closes over data like your i variable) has an enduring reference to the variable, not a copy of its value. So all of the handlers will see i as of when they run, not as of when they're created. (More here: Closures are not complicated)
You're not declaring i, and so you're falling prey to the Horror of Implicit Globals.
Here's one way you can address those:
function setClickable()
{
var i; // <== Declare `i`
var self = this; // <== Create a local variable for the `this` value
for(i=0;i<this.tabArray.length;i++)
{
// v=== Use a function to construct the handler
document.getElementById(this.tabArray[i]).onclick = createHandler(i);
}
function createHandler(index)
{
// This uses `self` from the outer function, which is the
// value `this` had, and `index` from the call to this
// function. The handler we're returning will always use
// the `index` that was passed into `createHandler` on the
// call that created the handler, so it's not going to change.
return function() {
alert(self.tabArray[index]);
};
}
}
...and as goreSplatter and Felix point out, this line:
window.onload = init();
...calls the init function and uses its return value to assign to onload. You mean:
window.onload = init;
...which just assigns init to the onload event.
Off-topic: You might consider using the newer "DOM2" mechanisms for attaching event handlers instead of the old "DOM0" way of using the onXYZ properties and attributes. The new way is called addEventListener, although sadly Internet Explorer has only recently added that (but it has attachEvent which is very similar). If you use a library like jQuery, Prototype, YUI, Closure, or any of several others, they'll smooth out those differences for you (and provide lots of other helpful stuff).
Problematic:
function init()
{
tab = new Tabs('tab1','tab2','tab3','tab4');
tab.setClickable();
}
window.onload = init();
In this case, window.onload will be undefined, since init() returns nothing. Surely you meant
window.onload = init;
?
Related
I have this class in javascript
(function() {
this.testObject = function() {
/*options*/
this.options = arguments[0];
};
/*make object*/
testObject.prototype.make = function(){
this.targetElement = document.getElementById('testDiv');
this.targetElement.addEventListener('mousedown', function(e){
...
});
this.targetElement.addEventListener('mouseup', function(e){
...
});
this.targetElement.addEventListener('mousemove', function(e){
...
});
};
}());
var test; // I need this to be global
function callObject(){
test = new testObject({...});
test.make();
}
This object binds some events. the instantiation is also inside another function. this is because I have situations that adding new elements to DOM, so calling callObject() for every new element to bind events for it.
But I think there is a performance issue here, it's going slow when I call callObject multiple times. I do not know what is the problem in fact.
so how can I delete an object and all it's binded events?
> var test; // I need this to be global
> function callObject(){
> test = new testObject({...});
> test.make();
> }
In the above, test will only reference the last instance of testObject.
The pattern you're using means that every function on the prototype chain has a closure to the execution context of the outer IIFE, and so does every listener added by the make method. That's inefficient if you don't need the closures. If not, then using an IIFE here isn't suitable, consider using a standard approach (it's convention to give constructors a name starting with a capital letter):
function TestObject() {
/*options*/
this.options = arguments[0];
}
TestObject.prototype.make = function (){
this.targetElement = document.getElementById('testDiv');
this.targetElement.addEventListener('mousedown', function (e){
...
};
this.targetElement.addEventListener('mouseup', function (e){
...
};
...
};
As noted elsewhere, adding listeners using function expressions makes it difficult to remove them later. The above pattern also means that each instance has its own copy of the function. An alternative that solves both these issues is to use references. You might add them as properties of the constructor so that they don't create additional global variables and don't need another object, e.g.
TestObject.mousedown = function (e){ ... };
TestObject.mouseup = function (e){ ... };
TestObject.prototype.make = function(){
var TO = TestObject;
this.targetElement = document.getElementById('testDiv');
this.targetElement.addEventListener('mousedown', TO.mousedown, false);
this.targetElement.addEventListener('mouseup', TO.mouseup, false);
...
};
Which avoids a lot of closures and unnecessary copies of functions and means listeners can be removed by name. And you might want to make the test global an object or array so you can keep references to all instances of TestObject, not just the last one.
There are a few things to consider. First, and most importantly, your event listeners have anonymous functions. You CAN'T unbind a listener when you give it an anon function. So go ahead and make actual functions for those. Then you can call removeEventListener the same way you called addEvent... and it will detach those listeners.
What I normally do is make a destroy function that removes all listeners and sets any global vars to null. Then you can call that destroy function whenever you need to.
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.
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);
For a particular listener in my application, I'm using the following code for scope-busting purposes:
// this is all in a prototype of MyClass
var self = this;
myElement.addEventListener("stuff", function(e){self.doStuff(e)});
That will get doStuff to have the desired this binding.
The problem shows up when I try to removeEventListener. I suppose it's because the native function signatures must be different?
// in a different prototype of MyClass
var self = this;
myElement.removeEventListener("stuff", function(e){self.doStuff(e)}); // doesn't work
If I make a separate function that contains all of my scope-busting code, then the this binding in that code will be to the unwanted object of myElement. So the question is: How can I force listener scope and still be able to remove an added event listener?
*note using global / static variables in any way is prohibited due to the nature of the project (otherwise this would be simple!)
This has nothing to do with scope or the way in which you're storing a reference to this. The problem is that removeEventListener expects a reference to a function that's previously been registered as a listener, but you're giving it a brand new function it's never seen before.
You need to do something like this:
var self = this;
var listener = function(e){self.doStuff(e)}
myElement.addEventListener("stuff", listener);
// later
myElement.removeEventListener("stuff", listener);
It doesn't matter that the bodies of your two functions are the same; they're still different functions.
See:
https://developer.mozilla.org/en/DOM/element.removeEventListener
Inline anonymous functions are a very bad practice anyway, so I will suggest the obvious:
function MyClass() {
this.onStuff = this.onStuff.bind(this); //Each instance steals the prototyped function and adds a bound version as their ownProperty
}
MyClass.prototype = {
onStuff: function (e) { //Prototyped, no instance actually uses this very function
this.dostuff()
},
bind: function () {
myElement.addEventListener("stuff", this.onStuff);
},
unbind: function () {
myElement.removeEventListener("stuff", this.onStuff);
}
}
see removeEventListener on anonymous functions in JavaScript
You can't removeEventListener as your using an anonymous function.
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.