JavaScript: How to bind a method? - javascript

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));
}

Related

javascript namespace call other function methods

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

Correct way to override this JavaScript class method

How should I best go about overriding a JavaScript class method when it has been set up as per below. In this snippet, if I want to override the _other method from another JS file, loaded after this one, what is the correct way to go about it?
var review = {};
"use strict";
(function ($) {
review.list = {
_init: function () {
// The code I want to leave intact
},
_other: function () {
// The code I want to override
},
init: function () {
$(document).ready(function () {
review.list._init();
review.list._other();
});
}
};
review.list.init();
})(jQuery);
You can just assign to review.list._other. If you want to have access to the previous version, grab that first:
var oldOther = review.list._other;
review.list._other = function() {
// Your new code here, perhaps calling oldOther if you like
console.log("The new other code ran.");
};
Example:
// The original file
var review = {};
"use strict";
(function($) {
review.list = {
_init: function() {
// The code I want to leave intact
},
_other: function() {
// The code I want to override
},
init: function() {
$(document).ready(function() {
review.list._init();
review.list._other();
});
}
};
review.list.init();
})(jQuery);
// Your file after it
(function($) {
var oldOther = review.list._other;
review.list._other = function() {
// Your new code here, perhaps calling oldOther if you like
console.log("The new other code ran.");
};
})(jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You're actually quite lucky it was written that way. It could easily have been written such that you couldn't override _other at all...
Slightly off-topic, but you've asked below:
Actually, does this class structure look reasonably sensible to you? Trying to dip toes into more OOP JS
I don't know your design constraints, so take anything that follows with a grain of salt... I should note that there's no "class" there at all (neither in the ES5 and earlier sense nor the ES2015 and later sense), just an object. (Which is fine.) But it looks like _init and _other are meant to be private; they could be genuinely private instead of pseudo-private without any cost — except then you wouldn't be able to override _other! :-) Separately, I would allow the overall controlling code to determine when the initialization happened instead of doing it on ready. (Separately, on a pure style note, I don't hold at all with this two-spaces-indentation nonsense so many of the l33t types seem to be promoting. If your code is so deeply nested that using only two spaces for an indent is necessary, it needs refactoring; four spaces is a good solid clear indent, in my view, without being so big it pushes your code off the right-hand side.)
So something like this if ES5 is required:
(function($) {
var list = {
init: function() {
_init();
_other();
}
};
function _init () {
// Can use `list` here to refer to the object
}
function _other() {
// Can use `list` here to refer to the object
}
review.list = list;
})(jQuery);
...but again, that makes it impossible (well, unreasonable) to override _other.
Or this if ES2015 and above is okay (for code this short, the differences are quite minor):
(function($) {
let list = {
init() {
_init();
_other();
}
};
function _init () {
// Can use `list` here to refer to the object
}
function _other() {
// Can use `list` here to refer to the object
}
review.list = list;
})(jQuery);
Just add your new override below... It will work...
var review = {};
"use strict";
(function($) {
review.list = {
_init: function() {
console.log('I\'m init')
},
_other: function() {
//This original will be overridden
console.log('Original')
},
init: function() {
$(document).ready(function() {
review.list._init();
review.list._other();
});
}
};
review.list.init();
})(jQuery);
review.list._other = function() {
console.log('overridden')
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

using this on an objects method is returning an error

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);

Manipulate object's property while in event handler

I've learned that for scope reasons the this keyword inside an event listener, which is embedded in an object, doesn't refer to the global object but rather to the element which triggered the event.
Now, I understand that if I want to fetch a property I can save it to a variable before the event handler is called. But what can I do if I want to manipulate the property's value?
In the following piece of code I am trying to manipulate the drugCount property within the removeDrug event listener.
var Drugs = {
drugs: $("#drugs_table"),
drugRow: $("#drug").html(),
drugCount: 0,
init: function() {
this.addDrugRow();
this.removeDrugRowHandler();
},
addDrugRow: function() {
this.drugCount++;
this.drugs.append(this.drugRow.replace(/{{id}}/,this.drugCount));
$(".drugsSelect").select2();
},
removeDrugRowHandler: function() {
drugCount = this.drugCount;
// also a problematic solution, because it only retains the inital drugCount.
// i.e I need a way to access the "live" count from within the event
$(document).on("click",".removeDrug",function(){
if (drugCount>0) {
$(this).parents("tr").remove();
this.drugCount--; // how should I approach this?
}
});
}
}
Try This
var Drugs = function() {
var me = this;
me.drugs = $("#drugs_table");
me.drugRow = $("#drug").html();
me.drugCount = 0;
me.init = function() {
this.addDrugRow();
this.removeDrugRowHandler();
};
me.addDrugRow = function() {
this.drugCount++;
this.drugs.append(this.drugRow.replace(/{{id}}/,this.drugCount));
$(".drugsSelect").select2();
};
me.removeDrugRowHandler= function() {
var drugCount = me.drugCount;
$(document).on("click",".removeDrug",function(){
if (drugCount>0) {
$(this).parents("tr").remove();
me.drugCount--;
}
});
}
}
As it turns out the easy solution is to use the object name instead of the contextual this.
So instead of this.drugCount I used Drugs.drugCount.
However, this solution only works if I am in the context of a single object. If I were to write a "class" (i.e var Drugs = function(){ ... }) this won't work.

Javascript object methods and binding functions

I'm looking for some help because I don't quite think I understand the Javascript scoping rules. What I'm trying to do in the below example is to push a button on a page that then starts listening for keyboard input. Once the keyboard input has started if there is a break in input for two seconds I want to stop capturing the input and pop an alert with the full contents of the input collected to that point. This is an example I made purely for this question.
What I see is that I click the button and start entering input. On each keypress I am alerted to the string collected to that point. After the two second, no-action timeout takes place I see an alert with the contents "undefined". The first alerts listed above come from startLog(). The second alert comes from stopLog(). What am I doing wrong when I call stopLog that it is telling me that this.message is undefined?
function Logger() {
this.message = '';
this.listenTimer;
this.startLog = function() {
this.message = '';
$(document).bind('keypress', {this_obj:this}, function(event) {
event.preventDefault();
var data = event.data;
clearTimeout(data.this_obj.listenTimer);
data.this_obj.message += String.fromCharCode(event.which);
alert(data.this_obj.message);
data.this_obj.listenTimer = setTimeout(data.this_obj.stopLog, 2000);
});
};
this.stopLog = function() {
$(document).unbind("keypress");
alert(this.message);
};
}
var k = new Logger();
$('.logging-button').click(function() {
k.startLog();
});
The issue is this. When you pass an object method as an event handler, it loses its object context; this will refer to the window object.
There are various ways to fix this, but the main issue is that you need to pass setTimeout a closure that will still refer to the correct context:
setTimeout(function() { data.this_obj.stopLog() }, 2000);
On a separate note, you can save yourself some unnecessary code by just using a closure to refer to the object, rather than binding it as event.data:
this.startLog = function() {
this.message = '';
var this_obj = this;
$(document).bind('keypress', function(event) {
event.preventDefault();
clearTimeout(this_obj.listenTimer);
// etc
});
};
var k = new Logger();
$('.logging-button').click(function() {
k.startLog.apply(this);
//Setting context of "this" so that it refers to element even in startLog()
});

Categories