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/
Related
In browser (Chrome) javascript:
var DataModler = {
Data: { Something: 'value' },
Process: function() { alert('not implemented.'); },
Render: function() { alert('not implemented.'); }
}
DataModler.Process = function() {
// do some data processing, then render it.
this.Render(); // this == Window, so an exception is thrown.
};
DataModler.Render = function() {
// render the data.
};
The problem I'm having is, if I set a breakpoint in DataModler.Process(), this is set to Window. I expect this to be set to the object that defines the function, which to my mind is DataModler. If I implement the function within the definition of DataModler, then this == DataModler as I would expect.
So I suppose the question is, how can I allow a function on my object to be replaced but still have this refer to the object on which the function is defined (DataModler)?
As mentioned in c69's comment, you are experiencing this problem due to how the function is actually called.
You can write
DataModler.Process = function() {
...
}.bind(DataModler);
to ensure that this withing Process is DataModler.
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);
Here is the jsfiddle to illustrate the problem.
http://jsfiddle.net/mabqc2zs/4/
(same code below):
JavaScript
var namespace = namespace || {};
namespace.ExampleClass = function () {
this.eventAdded = false;
this.secondValue = null;
this.doSomething = function() {
var secondValue = this.secondValue;
};
this.event = {
eventAddedParam: this.eventAdded,
addListener: function(el, type, eAP2) {
if (!this.eventAddedParam) { // Ensures addEventListener registers only once.
var eAP = this.eventAddedParam;
el.addEventListener(type, function() { // Unable to add arguments to anonymous function
console.log("eAP2 value: " + eAP2); // false
console.log("eAP value: " + eAP); // true
try {
console.log("eventAddedParam value: " + eventAddedParam);
}
catch(e) {
console.log("Error: eventAddedParam variable is undefined");
}
});
eAP = true;
this.eventAddedParam = true;
}
}
};
};
var inst = new namespace.ExampleClass();
inst.event.addListener(document.getElementById("id1"), "focus", inst.eventAdded);
inst.event.addListener(document.getElementById("id1"), "focus", inst.eventAdded);
HTML
<input id="id1" type="text" />
I'm integrating addEventListener to some Object Oriented JavaScript code.
I wanted to add arguments to the anonymous function, i.e. el.addEventListener(type, function()... What I want to do is ensure addEventListener gets registered once. Hence, in the code, I check if this.eventAddedParam is true. It does work. Here is the problem. this.eventAddedParam refers to eventAddedParam, which is a literal object of this.event. When I set eventAddedParam to true, eventAdded, which is a public variable of the ExampleClass, is still set to false. This does does me no good.
Ultimately, what I want to do is change the value of eventAdded to true within the addListener function. I have not been able to figure this out.
And to further that, if possible, I'd like to make eventAdded a private variable, yet access it within the addListener function, but I have been able to figure that out, either.
Any help would be appreciated.
Update
I have an updated jsfiddle, after making the necessary changes.
http://jsfiddle.net/mabqc2zs/8/
For some reason, my IE 11 Developer Toolbar was not registering local variables. But it does work.
This object implements a pattern to provide event listeners. It works in at least IE 11 and Chrome.
But I don't understand why and I have 2 questions.
In the keypress event listener, there is an alert that shows that this is equal to [object HTMLInputElement] and this.element is undefined.
Why is this not equal to Object?
Why is this.element undefined? (Note it is initialized in the init method.)
See this JSFiddle
Here is the JavaScript:
function CustomEditor(id) {
'use strict';
this.element = null;
this.id = id;
this.init();
};
CustomEditor.prototype.addEvent = function (event, callback) {
'use strict';
return this.element.addEventListener(event, callback, false);
};
CustomEditor.prototype.init = function () {
'use strict';
this.element = document.getElementById(this.id);
this.addEvent('keypress', this.onCustomKeyPress);
};
CustomEditor.prototype.onCustomKeyPress = function () {
'use strict';
// alert("keypress event handler");
this.style.backgroundColor = "#000";
this.style.color = "#ff0";
alert('this = ' + this + '\n\nthis.element = ' + this.element);
};
// create and initialize custom editor
ce = new CustomEditor('myInput1');
document.getElementById('myInput1').value = 'a';
alert('ce = ' + ce + '\n\nce.element = ' + ce.element);
EDIT: From the comments by #Bergi & #lombausch, I understand the misconception I had had around this and context (weekend warrior here). I made the following modification to my object and now this has the context I need. (I'm using call rather than bind so the code works with older browsers.)
MyObj.prototype.addEvent = function (event, callback, caller) {
'use strict';
if (typeof window.addEventListener === 'function') {
return this.element.addEventListener(event, function () {
callback.call(caller);
}, false);
}
// for older versions of IE, order of test is important
return this.element.attachEvent('on' + event, function () {
callback.call(caller);
});
};
New JSFiddle
But a new Question: What changes have to be made to the pattern for onCustomKeypress to have access to the event interface / object?
The event interface is the first argument of the event listener, but I can't seem to pass it to the callback function. For example, this does not work:
MyObj.prototype.onCustomKeyPress = function (e) {
(I'm using call rather than bind so the code works with older browsers.)
Why? bind is a lot simpler apparently. If you want it to work with older browsers, just include this simple polyfill somewhere in your code.
What changes have to be made to the pattern for onCustomKeypress to have access to the event interface / object?
None. The problem is still your addEvent function, which might now invoke the callback with the correct context but without the arguments. Two solutions:
Use the arguments object and apply instead:
…(…, function() {
callback.apply(caller, arguments);
} …
Just pass the event argument to call - it takes any number of normal arguments after the context:
MyObj.prototype.addEvent = function(event, callback, caller) {
'use strict';
if (typeof window.addEventListener === 'function') {
this.element.addEventListener(event, function(e) {
callback.call(caller, e);
}, false);
} else {
// for older versions of IE, order of test is important
this.element.attachEvent('on' + event, function() {
callback.call(caller, window.event);
});
}
};
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.