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
}
}
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 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();
};
};
})();
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
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..
var JavascriptHelper = Backbone.Model.extend("JavascriptHelper",
{}, // never initialized as an instance
{
myFn: function() {
$('.selector').live('click', function() {
this.anotherFn(); // FAIL!
});
},
anotherFn: function() {
alert('This is never called from myFn()');
}
}
);
The usual _.bindAll(this, ...) approach won't work here because I am never initializing this model as an instance. Any ideas? Thanks.
You could do it by hand:
myFn: function() {
$('.selector').live('click', function() {
JavascriptHelper.anotherFn();
});
}
Or, if anotherFn doesn't care what this is when it is called (or if it wants this to be what live uses):
myFn: function() {
$('.selector').live('click', JavascriptHelper.anotherFn);
}
As an aside, live has been deprecated in favor of on. Also, if you're not instantiating your JavascriptHelper, then why is it a Backbone.Model at all? Why not use a simple object literal:
var JavascriptHelper = {
myFn: function() {
//...
},
anotherFn: function() {
//...
}
};
And what are you expecting this construct:
var JavascriptHelper = Backbone.Model.extend(string, {}, {...})
to leave you in JavascriptHelper? Extending a string is strange but passing three arguments to Backbone.Model.extend is pointless, it only cares about two arguments. If you want static properties then you should be passing them as the second argument:
var JavascriptHelper = Backbone.Model.extend({}, { myFn: ... });