My code:
function Demo (){
this.name = 'abc';
this.age = 20;
}
var demo = {
init : function(){
$('#test').hover(this.stop, this.start);
},
start: function(){
//here is the error
alert(this.name);
}
stop: function(){
alert(this.age); // 'this' does not mean the Demo object, but $('#test') object.
}
}
Demo.prototype = demo;
Demo.prototype.constructor = Demo;
(new Demo).init();
When the hover event of $('#test') is triggered, the stop method is called. However, 'this' in the method does not point to the demo object, but the $('#test') object. So, the alert is undefined. I need to have access to the attributes in the Demo object. And the stop and start method will be reused in other place, so I don not like to write the whole method code into hover's argument.
How should I solve this problem?
The this in the start and stop methods don't necessarily point to the same this as in the init. This is because they are callback functions. If you want to refer to the same object context then try the following:
var demo = {
init : function(){
$('#test').hover(this.stop.bind(this), this.start.bind(this));
},
start: function(){
//here is the error
alert(this.name);
}
stop: function(){
alert(this.age); // 'this' does not mean the Demo object, but $('#test') object.
}
}
Using bind will pass the this context through to the callbacks.
MDN docs for bind are here.
JQuery uses apply behind the scenes to call the event callbacks, that's why the context changes.
To mitigate this, you can do one of two things:
Use bind
var demo = {
init : function(){
$('#test').hover(this.stop.bind(this), this.start.bind(this));
},
start: function(){
alert(this.name);
}
stop: function(){
alert(this.age);
}
}
call the method directly
var demo = {
init : function(){
// Closure here
var self = this;
$('#test').hover(function() {
self.stop();
}, function() {
self.start();
});
},
start: function(){
alert(this.name);
}
stop: function(){
alert(this.age);
}
}
A None JSON implementation that will allow you to set the variable self
function Demo(){
this.name = 'abc';
this.age = 20;
}
Demo.prototype = new (function(){
var self = this;
this.init = function(){
$('#test').hover(self.stop, self.start);
}
this.start = function(){
//here is the error
alert(self.name);
}
this.stop = function(){
alert(self.age); // 'this' does not mean the Demo object, but $('#test') object.
}
})();
(new Demo()).init()
EDIT:
I have updated to show what i was meaning without the use of var demo = {...} the point i was trying to make was not to use the Object Literal aka JSON style so you could support a variable inside the prototype
Related
I am looking to achieve something along the following.
HTMLSpanElement.prototype.testNS = {
_this: this,
testFunc: function() {
console.log(this.innerHTML) //undefined as expected as this is the testFunc object
},
testFunc2: function() {
console.log(this._this) //Window object
}
}
My goal is to add some helper functions directly to a span element in this case.
So, if I had the following:
<span>test</span>
I could find the span and call this code to return "test"
spanElement.testNS.testFunc()
I know that a function retains scope of it's parent when I do it like so...
HTMLSpanElement.prototype.testFunc = function() {
console.log(this.innerHTML)
}
But I am attempting to organize the code a bit and make it more obvious where the functions are coming from when I add them, and I can't seem to find a way to retain scope, when I do a normal JSON object grab the this scope into _this: this it just returns the global scope of "window".
Disclaimer: You shouldn't be trying to modify the prototypes on built-in types, especially host objects. It's a bad idea.
The reason your approach isn't working for you is that the functions are being called with the testNS object as the this.
You can get this to work if you define testNS as a property with a getter function, using Object.defineProperty. The reason this works is that the get function runs in the context of the object on which the property is being accessed (which would be the span):
Object.defineProperty(HTMLSpanElement.prototype, 'testNS', {
get: function() {
var _this = this;
return {
testFunc: function() {
console.log(_this.innerHTML)
},
testFunc2: function() {
console.log(_this)
}
}
}
});
var span = document.getElementById('mySpan');
span.testNS.testFunc();
span.testNS.testFunc2();
<span id="mySpan">Wah-hoo!</span>
A more "vanilla" approach is to just have testNS be a plain function and call it like one. This works because testNS is called in the context of the object on which it is being called (again, the span):
HTMLSpanElement.prototype.testNS = function() {
var _this = this;
return {
testFunc: function() {
console.log(_this.innerHTML)
},
testFunc2: function() {
console.log(_this)
}
}
}
var span = document.getElementById('mySpan');
span.testNS().testFunc();
span.testNS().testFunc2();
<span id="mySpan">Wah-hoo!</span>
When you call a function as foo.bar() then this inside bar refers to foo. Hence if you call the function as spanElement.testNS.testFunc(), this refers to spanElement.testNS.
_this: this, cannot work because this cannot refer to a <span> element.
To get access to spanElement from testFunc you could implement testNS as a getter:
Object.defineProperty(HTMLSpanElement.prototype, 'testNS', {
get: function() {
var element = this;
return {
testFunc: function() {
console.log(element.innerHTML);
},
};
},
});
document.querySelector('span').testNS.testFunc();
<span>foo</span>
Because it's a strange requirement I wrote a an equivalent strange solution :-)
Basically the createElement has been overriden in order to add a namespace object literal and then define a new function testFunc on top of the namespace using the instance of the element binded to the function
!function(){
var defaultNamespace = "testNS";
var createElement = document.createElement;
document.createElement = function(tag, namespace) {
var element = createElement.apply(document, arguments);
element[namespace || defaultNamespace] = {
testFunc : function() {
console.log(this.innerHTML);
}.bind(element)
};
return element;
}
}();
var span = document.createElement("span");
I am moving some jquery functions into a javascript object to clean up some code. My problem is, when I put methods on my object's constructor, calling this.functionName() returns the error this.functionName is not a function but if my functions are helper methods and are outside of the object's constructor, they work just fine.
Here is my code that does not work
function MyConstructor() {
this.init();
this.selectAllHandler();
}
MyConstructor.prototype = {
init: function() {
var self = this;
$(document).on('click', '#my_element', function() {
self.selectAllHandler.call(this);
});
},
selectAllHandler: function() {
// handler works fine
var ids_array = this.idsArray(checkboxes); // error happening here
},
// helpers
idsArray: function(checkboxes) {
// trying to call
}
}
But, having my object w/ a constructor and then calling the "helper" outside of the object works fine. For example, this works fine.
function MyConstructor() {
this.init();
}
MyConstructor.prototype = {
init: function() {
var self = this;
$(document).on('click', '#my_element', function() {
self.selectAllHandler.call(this);
});
},
selectAllHandler: function() {
// handler works fine
var ids_array = idsArray(checkboxes);
}
}
function idsArray() {
// code that works fine
}
One thing to note as well, is that in this scenario, by running console.log this refers to the element being clicked on, and not the constructor.
I have tried using call, apply, and bind, but have not had success, though I think it's been syntax related.
How can I build this so I can call my "helper" functions inside my object?
Not sure how you were using bind, since you said it didn't work for you.
If you want, you can use bind like below. Also, in your code snippet checkboxes was not defined. This way you don't need to use self.
function MyConstructor() {
this.init();
this.selectAllHandler();
}
MyConstructor.prototype = {
init: function() {
//var self = this;
$(document).on('click', '#my_element', function() {
//self.selectAllHandler.call(self);
this.selectAllHandler();
}.bind(this));
},
selectAllHandler: function() {
// handler works fine
var checkboxes;
var ids_array = this.idsArray(checkboxes); // error happening here
},
// helpers
idsArray: function(checkboxes) {
// trying to call
console.log('test');
}
}
var o = new MyConstructor();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I was able to figure it out. I thought I could call another function in the constructor just using this.functionName(). however, $(this) was referring to the element I was clicking on.
I remembered I defined self (this) in my init function which refers to the window object. Well, inside the window object is my object, and my function is on that object. So i was able to successfully call my object by doing
function MyConstructor() {
this.init();
}
MyConstructor.prototype = {
init: function() {
var self = this;
$(document).on('click', '#my_element', function() {
self.selectAllHandler.call(this);
});
},
selectAllHandler: function() {
// RIGHT HERE
var ids_array = self.MyConstructor.prototype.idsArray(checkboxes);
},
// helpers
idsArray: function(checkboxes) {
// some codes
}
}
I have JS object
var widget = {
check_balance: function(){...},
function_called_in_init: function(){
.....
this.check_balance();
};
};
this is code screenshot for better understanding..
and when it try to call this.check_balance(); it returns me error TypeError: this.check_balanceis not a function
the question would be - how to call function inside object which was also created inside object?
Also I can't init this function at the moment when all object is inited, becouse this is a recursion with ajax callback.
Its a little tricky to see what you are asking but the gist of it is you are looking to have the correct context. The tool for that is the whatever.bind(theContext) function. You pass in theContext to the object and that makes theContext object the context of whatever.
var parent = {
foo: function () {
var widget = {
check_balance: function(){ console.log('checking'); },
function_called_in_init: function(){
this.bar();
}.bind(this),
};
widget.function_called_in_init();
},
bar: function () {
console.log('bar');
},
};
parent.foo();
see fiddle
bind documentation
Use private function and closure
var widget = (function() {
var check_balance = function() {
//do what your check_balance has to do
}
return {
check_balance: check_balance,
function_called_in_init: function(){
.....
check_balance();
};
};
})();
I'm creating an Javascript "class" with prototyping. I don't understand why the first/second block won't work, and the third block will work. For the first/second block I get: "Object # has no method 'validate' ". Why does it do that, and is block 3 the correct way?
--Edit
I have tested this in Chrome/FF
--Edit2
If I call the Test prototype with:
var test = new Test();
And call the test var in de login prototype it will work....
Block 1
function Test(){
this.init();
}
Test.prototype.init = function(){
$(".login").click(this.login);
};
Test.prototype.login = function(event){
event.preventDefault();
this.validate();
console.log("login");
};
Test.prototype.validate = function(){
console.log("validate");
};
new Test();
Block 2
function Test(){
this.init();
}
Test.prototype.init = function(){
$(".login").click(this.login);
};
Test.prototype.login = function(event){
var self = this;
event.preventDefault();
self.validate();
console.log("login");
};
Test.prototype.validate = function(){
console.log("validate");
};
new Test();
Block 3
function Test(){
if(!(this instanceof LoginController)){
return new LoginController();
}
self = this;
this.init();
}
Test.prototype.init = function(){
$(".login").click(this.login);
};
Test.prototype.login = function(event){
event.preventDefault();
self.validate();
console.log("login");
};
Test.prototype.validate = function(){
console.log("validate");
};
new Test();
Test.prototype.init = function(){
$(".login").click(this.login);
};
Here you're attaching this.login as event handler. Done this way, the function will have it's this value reassigned by jquery to the element that triggered the event.
To to keep a reference to the this value you actually want, try:
Test.prototype.init = function(){
var self = this;
$(".login").click(function (evt) {
return self.login(evt);
});
};
or for browsers implementing bind:
Test.prototype.init = function(){
$(".login").click(this.login.bind(this));
};
or jQuery.proxy, which does the same thing as bind:
Test.prototype.init = function(){
$(".login").click($.proxy(this.login, this));
};
demo: http://jsbin.com/ilejiw/1/
Update: No, the 3rd variant is not the correct way, as every instance of Test would overwrite the same global self variable. So self would point to the last instance of Test (as long as it has not been overwritten by something else).
I have the following code:
var dp = dp || {
VERSION : '0.00.02',
startApp : function() {
$(app.init);
$(app.start);
}
};
dp.startApp();
which calls app.init and app.start below:
var app = app || {};
app.init = function() {
this.baseElement = $('div#app');
$('body').css('background-color', 'beige');
};
app.start = function() {
//this.baseElement.html('showing this'); //this works
//this.show(); //error: show is not a function
app.show(); //error: show is a function, but baseElement is undefined
};
app.show = function() {
this.baseElement.html('showing this');
};
why in app.start does:
the first line work
the second line show it is not a function
the third line say that baseelement is undefined
Since you are passing the functions to document.ready, jQuery will call them with this set to document. That means you can set arbitrary properties on document of course, but it's not a jQuery object so it doesn't have the methods you are calling.
You can try this:
$(dp.startApp) //Since `this` doesn't matter here
and
startApp : function() {
app.init(); //Calling the functions as property of `app`, will make `this` set to `app`
app.start();
}
I guess the biggest thing you are missing here is that the binding of this is dynamic and is determined by the way you call functions, not how you define them.
$(app.init); calls the app.init function but the receiver isn't the app object.
So the baseElement variable isn't set in init in the correct object (app).
You may try $(function(){app.init();app.start();});
This is how I would structure your code:
$(function() {
app = {
init: function() {
this.version = '0.00.02';
this.baseElement = $("div#app");
this.bindEvents();
this.start();
},
bindEvents: function() {
$('body').css('background-color', 'beige');
},
start: function() {
this.show();
},
show: function() {
this.baseElement.html('showing this');
}
}
});
$(document).ready(function() {
app.init();
});
Edit: I know this doesn't answer your question but it cleans it up a bit and makes it a bit easier to understand what's going on..