Applying OOP with jQuery - javascript

I'm working with jQuery and trying to apply some basic Javascript OOP principles to a set of functions that control hover behavior. However, I can't figure out how to get the "this" keyword to refer to the instance of the object I'm creating. My sample code is:
var zoomin = new Object();
zoomin = function() {
// Constructor goes here
};
zoomin.prototype = {
hoverOn: function() {
this.hoverReset();
// More logic here using jQuery's $(this)...
},
hoverReset: function() {
// Some logic here.
}
};
// Create new instance of zoomin and apply event handler to matching classes.
var my_zoomin = new zoomin();
$(".some_class").hover(my_zoomin.hoverOn, function() { return null; });
The problematic line in the above code is the call to this.hoverReset() inside the hoverOn() function. Since this now refers to element that was hovered on, it does not work as intended. I would basically like to call the function hoverReset() for that instance of the object (my_zoomin).
Is there any way to do this?

Only assigning a function to a property of an object does not associated this inside the function with the object. It is the way how you call the function.
By calling
.hover(my_zoomin.hoverOn,...)
you are only passing the function. It will not "remember" to which object it belonged. What you can do is to pass an anonymous function and call hoverOn inside:
.hover(function(){ my_zoomin.hoverOn(); },...)
This will make the this inside hoverOn refer to my_zoomin. So the call to this.hoverReset() will work. However, inside hoverOn, you will not have a reference to the jQuery object created by the selector.
One solution would be to pass the selected elements as parameter:
var zoomin = function() {
// Constructor goes here
};
zoomin.prototype = {
hoverOn: function($ele) {
this.hoverReset($ele);
// More logic here using jQuery's $ele...
},
hoverReset: function($ele) {
// Some logic here.
}
};
var my_zoomin = new zoomin();
$(".some_class").hover(function() {
my_zoomin.hoverOn($(this)); // pass $(this) to the method
}, function() {
return null;
});
As a next step, you could consider making a jQuery plugin.

You can "bind" the event handler to the object (see Mootools bind code for example).
You can pass the object as a parameter in the anonymous function and use that instead of this in the event handler
As for 1, you add the bind method to function
bind: function(bind){
var self = this,
args = (arguments.length > 1) ? Array.slice(arguments, 1) : null;
return function(){
if (!args && !arguments.length) return self.call(bind);
if (args && arguments.length) return self.apply(bind, args.concat(Array.from(arguments)));
return self.apply(bind, args || arguments);
};
}
Not sure though how well it will interact with JQ stuff.

please see my answers to these questions:
where is my "this"?
why is "this" not this?
this confusion comes up all the time.
when you pass a function in as a callback, it's invoked as a standalone function, so its "this" becomes the global object.
"bind" is a native part of ecmascript 5, and is part of the function prototype. If you go to the end of my second answer up there, you get a link to the mozilla website, which has a "compatibility" version of the bind function. Use use myfunction.bind(myobject), and it'll use the native function if it's available, or the JS function if it is not.

Related

What is the best practice to add functions to a function in JavaScript?

I have an occurence where I want to have a main js-file with one resize function and specific files that can add workload to the main file without changing the mainfile and manually calling functions.
Lets say I have an object literal
var App = {
resize: function(){
// Code should be executed here
},
addResize: function(){
// ?
}
}
and I want a function to add code to the resize function which dynamically adds workload to the resize function (which gets called on window resize):
App.addResize(function(){ ... });
The first thing that came to my mind is to store the anonymous functions from addResize to an array and iterating over it in the resize function, but that doesn't feel like doing a best-practice:
var App = {
resizeFunctions = [];
resize: function(){
// iterate over resizeFunctions and call each one
// here I define throttling/debouncing ONCE
},
addResize: function(fn){
this.resizeFunctions.push(fn);
}
}
window.onresize = App.resize();
App.addResize(fn1);
App.addResize(fn2);
Is there a better way?
as you are referring to one function, ie. a resize function, I assume that you are looking for function overloading:
Function overloading in Javascript - Best practices
http://ejohn.org/blog/javascript-method-overloading/
If you want to extend the functionality of a set of methods that are all related to a single parent-object into different child objects, I would look into prototypal inheritance.
It allows you to define re-define the parent methods for each of the child-objects.
Do you want to overwrite the existing function?
Then you can just do this:
App.addResize = function(){}
App.addResize(function(){ ... });
would pass the function to addResize as an attribute but not add it to it. You could do
App.addResize.newFunction = function(){ ... };
Where newFunction is the name of the function
You can treat your object literal as array.
App["resize"] = function(){
//Code goes here
}
__
Or
App.resize = function(){
//Code here
}
Both are equally good. These will update the definition of resize
If you want to add some new method, then too the same syntax will work.
App["myNewMethod"] = new function(){
//Code here
}
Edit after OP's comment
var oldFun = App["resize"]; // or may be store this in App itself
App["resize"] = function(){
//Pre-processing
// Now call original resize method
oldFun(); //If resize method used method argument then use oldFun.apply( this, arguments );
//Post processing
}

Event handler with parameters in dynamically created elements

When I assign the event handler without parameters, it works: http://jsfiddle.net/mUj43/
function show(){
alert('work');
}
var myButton = document.createElement("input");
myButton.type="button";
myButton.value="click";
myButton.onclick=show;
var where = document.getElementById("where");
where.appendChild(myButton); ​
but if I pass parameters, it doesn't work: http://jsfiddle.net/mUj43/1/
myButton.onclick = show('test');
How can I use function with parameters in dynamically created elements?
You can't do that, you could use partial application by creating a new function and then attach that as event handler:
myButton.onclick=show.bind( myButton, 'test');
http://jsfiddle.net/mUj43/2/
Docs (which I recommend you read because this function is useful for many other things as well) and compatibility information: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
You'll have to create your own closure:
myButton.onclick = function () {
show.call(this, 'test');
};
You could also use #Esailija's bind method, but this one has deeper browser support.
try:
myButton.onclick = function(){show("test");}
or :
myButton.onclick = function(){ show.call( this, "test");}
if you want to retain the element object context inside the show function
That's because when you add events you need a function reference.
In your first example, show is a reference to a function.
In your second example, show('test') is a call to the function show, which returns nothing, and nothing isn't a function reference.
That's why when you load the page, it alerts "work" (the function is called), but when you click the button no function is called.
Then, you need a function.
You can declare it:
myButton.onclick=f;
function f(){
show('test')
}
Or you can use an anonymous one:
myButton.onclick=function(){
show('test')
}

How do I redefine `this` in Javascript?

I have a function which is a JQuery event handler. Because it is a JQuery event handler, it uses the this variable to refer to the object on which it is invoked (as is normal for that library).
Unfortunately, I need to manually call that method at this point. How do I make this inside the called function behave as if it were called from JQuery?
Example code:
function performAjaxRequest() {
//Function which builds AJAX request in terms of "this"
}
function buildForm(dialogOfForm) {
var inputItem;
dialogOfForm.html('...');
dialogOfForm.dialog('option', 'buttons', {
"Ok" : performAjaxRequest
});
inputItem = dialogOfForm.children(':not(label)');
//Redirect enter to submit the form
inputItem.keypress(function (e) {
if (e.which === 13) {
performAjaxRequest(); //Note that 'this' isn't the dialog box
//as performAjaxRequest expects here, it's
//the input element where the user pressed
//enter!
}
}
}
You can use the function's call method.
someFunction.call(objectToBeThis, argument1, argument2, andSoOnAndSoOn);
If dialog is the object that you need to be set to this then:
performAjaxRequest.apply(dialog, []);
// arguments (instead of []) might be even better
should do the trick.
Otherwise, in jQuery you can simply call the trigger method on the element that you want to have set to this
Say, for example, that you wanted to have a click event happen on a button and you need it to happen now. Simply call:
$("#my_button").trigger("click");
Your #my_button's click handler will be invoked, and this will be set to the #my_button element.
If you need to call a method with a different this ... say for example, with this referring to the jQuery object itself, then you will want to use call or apply on your function.
Chuck and meder have already given you examples of each ... but to have everything all in one place:
// Call
my_function.call(object_to_use_for_this, argument1, argument2, ... argumentN);
// Apply
my_function.apply(object_to_use_for_this, arguments_array);
SEE: A List Apart's Get Out of Binding Situations
Are you looking for..
functionRef.apply( objectContext, arguments);
You should of course learn to master call() and apply() as people have stated but a little helper never hurts...
In jQuery, there is $.proxy. In pure js, you can re-create that niftyness ;) with something like:
function proxyFn( fn , scope ){
return function(){
return fn.apply(scope,arguments);
}
}
Usage Examples:
var myFunctionThatUsesThis = function(A,B){
console.log(this,arguments); // {foo:'bar'},'a','b'
};
// setTimeout or do Ajax call or whatever you suppose loses "this"
var thisTarget = {foo: 'bar'};
setTimeout( proxyFn( myFunctionThatUsesThis, thisTarget) , 1000 , 'a', 'b' );
// or...
var contextForcedCallback = proxyFn( myAjaxCallback , someObjectToBeThis );
performAjaxRequest(myURL, someArgs, contextForcedCallback );
If you dont abuse it, it's a sure-fire tool to never loose the scope of "this".
use a closure
i.e assign this to that early on; then you can do what you like with it.
var that = this;

How to refer to object in JavaScript event handler?

Note: This question uses jQuery but the question has nothing to do with jQuery!
Okay so I have this object:
var box = new BigBox();
This object has a method named Serialize():
box.AddToPage();
Here is the method AddToPage():
function AddToPage()
{
$('#some_item').html("<div id='box' onclick='this.OnClick()'></div>");
}
The problem above is the this.OnClick() (which obviously does not work). I need the onclick handler to invoke a member of the BigBox class. How can I do this?
How can an object refer to itself in an event handler?
You should attach the handler using jQuery:
function AddToPage()
{
var self = this;
$('#some_item').empty().append(
$("<div id='box'></div>")
.click(function() { self.OnClick(someParameter); })
);
}
In order to force the event handler to be called on the context of your object (and to pass parameters), you need to add an anonymous function that calls the handler correctly. Otherwise, the this keyword in the handler will refer to the DOM element.
Don't add event handlers with inline code.
function AddToPage()
{
$('#some_item').html("<div id='box'></div>");
$('#box').click(this.OnClick);
}
EDIT:
Another way (avoids the extra select):
function AddToPage()
{
var div = $('<div id="box"></div>'); // probably don't need ID anymore..
div.click(this.OnClick);
$('#some_item').append(div);
}
EDIT (in response to "how to pass parameters");
I'm not sure what params you want to pass, but..
function AddToPage()
{
var self = this, div = $('<div></div>');
div.click(function (eventObj) {
self.OnClick(eventObj, your, params, here);
});
$('#some_item').append(div);
}
In jQuery 1.4 you could use a proxy.
BigBox.prototype.AddToPage= function () {
var div= $('<div>', {id: box});
div.click(jQuery.proxy(this, 'OnClick');
div.appendTo('#some_item');
}
You can also use a manual closure:
var that= this;
div.click(function(event) { that.OnClick(event); });
Or, most simply of all, but requiring some help to implement in browsers that don't yet support it (it's an ECMAScript Fifth Edition feature):
div.click(this.OnClick.bind(this));
If you are using jQuery, then you can separate your code from your markup (the old seperation of concerns thing) like this
$(document).ready(function() {
var box = new BigBox();
$('#box').click(function() {
box.serialize();
});
});
You only need to add the click handler once for all divs with id of box. And because the click is an anonymous function, it gets the scope of the function it is placed in and therefore access to the box instance.

Using .bind (mootools) with onClick event

I have the code (inside one object)
onclick: this._addX.bind(this)
and then inside another object
onclick: this._addY.bind(this)
Now, _addX() and _addY are nearly identical, except they both end up calling (on the click event) a function with different argument values, say _addX calls foo('x') and _addY calls foo('y'). So I tried:
onclick: this._add.bind(this,'x') and
onclick: this._add.bind(this,'y') in the two objects. And of course I changed _add to accept an argument.
At runtime, when _add is called, it does not see any incoming arguments! I have fumbled around with different syntaxes but nothing works. Any ideas? The original syntax works fine (no arguments) but forces me to duplicate a large function with only one line different, which pains me. Thanks in advance.
_add: function(which) {
var me = this;
var checkFull = function(abk) {
if (abk.isFull) {
alert("full");
} else {
alert(which); // which is always undefined here!
}
};
getAddressBook(checkFull); //checkFull is a fn called by getAddressBook
},
this works and it keeps the scope within an element click event with the scope set to the class and not the element--there is no point in passing scope to the add method, it already has that:
var foo = new Class({
Implements: [Options],
add: function(what) {
alert(what);
},
initialize: function(options) {
this.setOptions(options);
this.options.element.addEvents({
click: function() {
this.add(this.options.what);
}.bind(this)
});
}
});
window.addEvent("domready", function() {
new foo({
element: $("foo"),
what: "nothin'"
});
});
just make an element with id=foo and click it to test (alerts nothin'). if your onclick is a function / event handler within your class as opposed to a normal element click event, then things are going to differ slightly - post a working skeleton of your work on http://mootools.net/shell/
If you read my previous answer, disregard it. The MooTools .bind method supports passing parameters. So something else isn't working as you expect:
onclick: this._add.bind(this, 'y');
Here is a simple setup on JSBin to show how bind truly does pass parameters.
The only purpose of bind is to "tell" the JS what object you mean when you say this. i.e. you pass as a parameter to bind an instance of the object you wish the this key word will refer to inside the function you used the bind on.

Categories