I'm having issues with Javascript properties and "this" keyword. Forgive me here for asking my third and final JS OOP question. OOP in Javascript has been a headache for me today.
I'm trying to set the property 'source' but the error console is saying it's undefined in parseSource method.
After a little research I believe this.source is is referring to window.source? The code is a boilerplate from Mozilla. When creating extensions init is called by FireFox when the plugin is initialized.
What's the best way to go about setting the properties when creating objects using literal notation?
var myExtension = {
source: null,
init: function() {
// The event can be DOMContentLoaded, pageshow, pagehide, load or unload.
if(gBrowser) {
gBrowser.addEventListener("DOMContentLoaded", this.onPageLoad, false);
}
},
onPageLoad: function(aEvent) {
doc = aEvent.originalTarget; // doc is document that triggered the event
win = doc.defaultView; // win is the window for the doc
// Skip frames and iFrames
if (win.frameElement) return;
this.source = win.document.getElementById('facebook').innerHTML;
myExtension.parseSource();
},
parseSource: function() {
if(this.source == null) {
// So something
} else {
// Do something else
}
}
}
window.addEventListener("load", function() { myExtension.init(); }, false);
When you pass a callback function to gBrowser.addEventListener like this:
gBrowser.addEventListener("DOMContentLoaded", this.onPageLoad, false);
you are passing a reference to the function which is essentially "detached" from the this object where it is defined. So, you need to do something like the following in order to correctly maintain what this references:
init: function() {
var self = this;
if(gBrowser) {
gBrowser.addEventListener("DOMContentLoaded", function () {
self.onPageLoad();
}, false);
}
},
In newer browsers (you did say this is a FF extension), you can use Function.bind to the same effect:
init: function() {
if(gBrowser) {
gBrowser.addEventListener("DOMContentLoaded", this.onPageLoad.bind(this), false);
}
},
Once that's cleared up, you can change the onPageLoad function to:
onPageLoad: function(aEvent) {
doc = aEvent.originalTarget; // doc is document that triggered the event
win = doc.defaultView; // win is the window for the doc
// Skip frames and iFrames
if (win.frameElement) return;
this.source = win.document.getElementById('facebook').innerHTML;
this.parseSource();
},
Edit
A stripped-down demo: http://jsfiddle.net/mattball/bDe6N/
The problem is that methods in Javacript forget about their this if you pass them as a parameter. They only work if you pass them looking like a method
//this doesn't work in JS
f = obj.method
f()
//wtf man! You have to call it looking like a method
obj.method()
In your case this happens because you pass this.onPageLoad as a parameter. Function parameters act like the variable from the last example.
The workaround is to use a wrapper function in order to preserve the method-call appearance
addEventListener( ..., function(){ return this.onPageLoad(); }, ...)
except that this is not lexicaly scoped and the inner function gets a wrong copy as well. After another quick fix we obtain
var that = this;
addEventListener(..., function(){ that.onPageLoad(); }, ...);
This should do the job now.
Related
I try to change some way to call methods into namespace.
Calling parent methods (I dont think its possible)
Creating and call inheritance function
Calling inside another method (mostly jquery onReady event function) (this.MyFunction() not working)
I split every namespace in files (want to keep it that way)
I try How to call function A from function B within the same namespace? but I didn't succed to split namespaces.
my fiddle sample got only 1 sub-namespace but could be more.
https://jsfiddle.net/forX/kv1w2rvc/
/**************************************************************************
// FILE Master.js
***************************************************************************/
if (!Master) var Master = {};
Master.Print= function(text){
console.log("master.Print :" + text);
$("body").append("<div>master.Print : " + text + "</div>");
}
/**************************************************************************
// FILE Master.Test1.js
***************************************************************************/
if (!Master) var Master = {};
if (!Master.Test1) Master.Test1 = {};
/**************************************************************************
* Descrition :
* Function for managing event load/documentReady
**************************************************************************/
Master.Test1.onReady = function () {
$(function () {
Master.Test1.Function1(); //try to replace because need all namespace.
try {
this.Function2(); //not working
}
catch(err) {
console.log("this.Function2 not working");
$("body").append("<div>this.Function2 not working</div>");
}
try {
this.Print("onReady"); //not working
}
catch(err) {
console.log("this.Print not working");
$("body").append("<div>this.Print not working</div>");
}
try {
Print("onReady"); //not working
}
catch(err) {
console.log("Print not working");
$("body").append("<div>Print not working</div>");
}
});
}
Master.Test1.Function1 = function () {
console.log("Function1");
$("body").append("<div>Function1</div>");
this.Function3(); //working because not inside another function
}
Master.Test1.Function2 = function () {
$("body").append("<div>Function2</div>");
console.log("Function2");
}
Master.Test1.Function3 = function () {
$("body").append("<div>Function3</div>");
console.log("Function3");
Master.Print("Function3"); //try to replace because need all namespace.
}
Master.Test1.onReady();
I use Master.Test1.Function1(); and I want to change that because Function1 is inside the same namespace.
I use Master.Print("Function3"); I dont think I can change that. the way I try to use it, it's more an inheritance function. but I dont know if theres a way to do that?
Maybe I should change the my namespace methode? maybe prototype will do what I want?
You can capture the this in a variable because this inside $(function() {}) will point to document object. The below will work provided you never change the calling context of onReady -- i.e. it is always called on the Test1 object and not called on other context:
Master.Test1.onReady = function () {
var self = this;
$(function () {
self.Function1();
// ..
});
}
To access Print you have to reference using the Master object like: Master.Print() as it won't be available in the Test1 object
this is document within .ready() or jQuery() alias for .ready() where function(){} is parameter $(function() {}). this at this.Function2() will reference document.
"Objects" in javascript are not built the same way as in most object-oriented languages. Essentially, what you are building is a hierarchy of static methods that have no real internal state in-and-of themselves. Therefore, when one of the defined methods is invoked, the context (or state) of that method depends on what object invoked the method.
If you want to have any internal context, you will need to create an "instance" of an "object prototype". At that point, you can use "this.otherFunction" within your other functions. Here is a small example:
var MyObject = function() {};
MyObject.functionOne = function() {
console.log("Function 1");
this.functionTwo();
};
MyObject.functionTwo = function() {
console.log("Function 2");
};
var instanceOne = new MyObject();
instanceOne.functionOne();
You might get some more information about object definition here
I am trying to work with ubonstrustive dom ready that looks something like this,
var app = {
common : {
init: function() {
//Initialisate the request object
app.request.init();
}
},
request: {
users : "helloworld",
init: function() {
alert("...request started...");
$('form').on('change', '#workbase', this.workBaseChange);
},
workBaseChange: function(e) {
var that = this;
console.log(this);
}
},
validation : {}
}
UTIL = {
fire : function(func, funcname, args) {
var namespace = app;
funcname = (funcname === undefined) ? 'init' : funcname;
if(func !== '' && namespace[func] && typeof namespace[func][funcname] == 'function') {
namespace[func][funcname](args);
}
},
loadEvents: function() {
UTIL.fire('common');
$.each(document.body.className.split(/\s+/), function(i, classnm){
UTIL.fire(classnm);
});
}
};
$(document).ready(UTIL.loadEvents);
request.workBaseChange fires when a select menu is changed, but if log this in that function it return the changed select not the request object as I would expect, am I doing something incorrectly? How do I get the context of request{}?
Try
$('form').on('change', '#workbase', this.workBaseChange.bind(this));
You can find more info here: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
Note if using IE this is only supported in IE9 or later. There is a polyfill on the link above however.
this.workBaseChange is the callback supplied to the jquery event. In the callback, the 'this' value is set to the element that triggered the event which is the select. The bind function returns a new function with the value of 'this' scoped to that function which then sets the 'this' property to the value that you supplied.
Also see here in the jquery docs (Context, Call and Apply):
http://api.jquery.com/Types/#Function
In JavaScript, the variable "this" always refers to the current
context. By default, "this" refers to the window object. Within a
function this context can change, depending on how the function is
called.
All event handlers in jQuery are called with the handling element as
the context.
See this working here: https://jsfiddle.net/mzxrhz8t/
I feel my whole understanding of this has been thrown up in the air.
I have a Quiz object which holds the necessary variables and methods required to play the quiz.
I am trying to reference a method of Quiz from another method in Quiz (getQuestion in skipQuestion()) however, I am seeing a message in the console saying that this.getQuestion is not defined. I was under the impression that this in this case refers to the object it is in, hence the function in question should be referred to as this.getQuestion().
The error message I am getting is script.js:18 Uncaught TypeError: this.getQuestion is not a function
Can anyone explain what is going wrong here?
In my init function it seems that this refers to the Quiz object, but in skip question it seems to change. Is this down to query having a different definition of this? where do you draw the line, and when is the context of this changed?
(function(window){
$(document).ready(function(){
var Quiz = {
score : 0,
question: '',
answer: '',
init: function() {
this.getQuestion();
this.checkAnswer();
this.skipQuestion();
},
skipQuestion: function() {
$('#skip').click(function(){
this.getQuestion();
})
},
getQuestion: function() {
$.get('http://jservice.io/api/random', function(data){
$('#question').html(data[0].question);
this.answer = data[0].answer.toLowerCase();
});
},
checkAnswer: function() {
if($('#answer').val() === this.answer) {
this.score += 1;
}
}
}
Quiz.init();
});
})(window);
Because you are nesting inside another function, the this context changes to that function, so the methods you look for are no longer available. You can try to solve it by either storing the this inside a variable that will be within the scope of the function you are defining, or by using Double Arrow Functions, which have no associated this context themselves (and therefor also don't support bind or call). Here are your options:
Declare a variable:
skipQuestion: function() {
var that = this;
$('#skip').click(function(){
that.getQuestion();
})
}
or a Double Arrow Function:
skipQuestion: function() {
var that = this;
$('#skip').click(() => that.getQuestion())
}
Your init function is considered a method of your Quiz object, while the anonymous function passed to the click event is not a method of your Quiz, it is a method of an anonymous object created in the background, and shares no methods or variables with your Quiz. This is important to consider!
The thing is you are using this inside the click event and it refers to the event rather than you context. To work around you need to assign this to another variable and then use that;
skipQuestion: function() {
var self = this;
$('#skip').click(function(){
self.getQuestion();
})
},
$.get and .click event create their own context and thus this refers to their context instead of the context of quiz.
JS
(function(window){
$(document).ready(function(){
var Quiz = {
score : 0,
question: '',
answer: '',
init: function() {
this.getQuestion();
this.checkAnswer();
this.skipQuestion();
},
skipQuestion: function() {
var self = this;
$('#skip').click(function(){
that.getQuestion();
})
},
getQuestion: function() {
var self = this;
$.get('http://jservice.io/api/random', function(data){
$('#question').html(data[0].question);
self.answer = data[0].answer.toLowerCase();
});
},
checkAnswer: function() {
if($('#answer').val() === this.answer) {
this.score += 1;
}
}
}
Quiz.init();
});
})(window);
JSFiddle: http://jsfiddle.net/M2ALY/3/
My goal is to make a module that I can use and distribute. Therefore I must not pollute the global namespace. The module I'm making is also going to be used multiple times on one web page. That's why I chose to use OOP, but this introduced a problem.
I want my object to bind a function to be run when the user clicks an element in the DOM. In this simplified example I made, I want an alert box to pop up when the user clicks a paragraph. As an example, one of the things I need in the real project I'm working on is: The user clicks a canvas, the function figures out where the user clicked and saves it to this.clientX and this.clientY.
Instead of doing
this.bind = function() {
$("p1").bind('click', function() {
// code here
});
}
I figured it would work if I did:
this.bind = function() {obj.codeMovedToThisMethod()}
The problem is that this isn't a good design. Inside the "class" you shouldn't need to know the name of the object(s) that is going to be made of this "class". This doesn't get better when I'm making multiple objects of the "class"...
So I figured I could do
$("p1").bind('click', function(this) {
// code here
});
}
But it didn't work because sending this into the function didn't work as I thought.
How should I solve this problem?
Here is a simplified sample problem. (Same as JSFiddle.)
var test = function() {
this.alert = function() {
alert("Hi");
}
this.bind = function() {
$("#p1").bind('click', function() {
obj.alert();
});
}
}
window.obj = new test();
obj.bind();
// What if I want to do this:
var test2 = function() {
// Private vars
this.variable = "This secret is hidden.";
this.alert = function() {
alert(this.variable);
}
this.bind = function() {
$("#p2").bind('click', function(this) {
obj2.alert();
this.alert();
});
}
}
window.obj2 = new test2();
obj2.bind();
Thanks!
Read MDN's introduction to the this keyword. As it's a keyword, you can't use it as a parameter name.
Use either
this.bind = function() {
var that = this;
$("#p2").on('click', function(e) {
that.alert();
// "this" is the DOM element (event target)
});
}
or $.proxy, the jQuery cross-browser equivalent to the bind() function:
this.bind = function() {
$("#p2").on('click', $.proxy(function(e) {
this.alert();
}, this));
}
Is it ok to bind jQuery events to plain, non-DOM Javascript objects:
var myobject = {};
$(myobject).bind("foobar", function() { alert("daa"); });
$(myobject).trigger("foobar");
What are the implications for
Garbage collection (no new references created preventing object to GC'ed)
Object attributes (new attributes assigned to the object)?
Performance
Some things I have noted
Event name must not conflict with a function name on the object, e.g. you cannot have function init and event named init and trigger it correclty
Instead of using the jquery event system, I would implement one that mimics it using the jQuery.Callbacks method.
var myClass = function(){
this._callbacks = {};
};
myClass.prototype = {
addEvent: function(evname,callback) {
if (!this._callbacks[evname]) {
this._callbacks[evname] = $.Callbacks();
}
this._callbacks[evname].add(callback);
},
removeEvent: function(evname) {
if (!this._callbacks[evname]) {
return;
}
this._callbacks[evname].remove();
//Might need this too:
//this._callbacks[evname] = null;
},
triggerEvent: function(evname) {
if (this._callbacks[evname]) {
this._callbacks[evname].fire();
}
}
};
var foo = new myClass();
foo.addEvent("foo",function(){
console.log('foo');
});
foo.triggerEvent("foo");
foo.removeEvent("foo");
// event was removed, the below line won't do anything.
foo.triggerEvent("foo");
http://jsfiddle.net/kEuAP/
However, to answer your question, I don't see any immediate problems with what you are doing other than it isn't documented and may change functionality from version to version (although it works in all currently available versions 1.2.6+).
Seeing as jQuery support alteration of object properties via animate also, this is definitely fine.
var obj = {'test':0};
var interval = setInterval(function(){console.log(obj);}, 250);
$(obj).on("fun", function(){this.test++});
$(obj).animate(
{'test':100},
3000,
function (){
console.log(obj);
clearInterval(interval);
$(obj).trigger("fun")
console.log("increment",obj);
}
);
//will console log {test: 1.5}, {test: 6.4} etc then {test: 100}
//and finally "interval" {test: 101}
Quickredfox's backup comment is a pretty good source too:
http://forum.jquery.com/topic/triggering-custom-events-on-js-objects