PDA

View Full Version : TO "this" or NOT to "this"



xmrcivicboix
6 Apr 2007, 11:07 AM
the title explains it all. So, my question is when should this be used? Let's say I have an object:



var object = function() {
var message = "hello";

return {
getFoo : function() {
return message;
},

setFoo : function(message) {
this.message = message; // is 'this' correctly used? I would assume so.
},

thisFoo : function() {
setFoo("HELLO WORLD");
alert(getFoo());

// OR

this.setFoo("HELLO WORLD");
alert(this.getFoo);
}
}
}();


Sometime I get confused. Please help me clarify. :((

mikegiddens
6 Apr 2007, 1:14 PM
Since I am still learning JS now that Ext has inspired me to program again the best way I have dealt with the scoping of things is to reference my Firebug Dom to see where my vars and functions are being created. Normally I only want to have one controller in my global env and the rest are defined as private or public actions inside my controller. So I just watch as my vars are being created and make sure they are being put in the right spots. :)

neongrau
7 Apr 2007, 12:16 AM
in your example getFoo will return the value from you private var "message" while setFoo will work with the public porperty .message.

use "this" when accessing public properties and use variable names directly when dealing with private vars (all that have been declared outside of the returned object).

neongrau
7 Apr 2007, 12:25 AM
one more thing: try to avoid using the names of vars or properties as function attributes. this will just make your code unreadable.


var object = function() {
var message = "hello";

return {
getFoo : function() {
return message;
},

setFoo : function(msg) {
message = msg; // 'this' wasn't correctly used ;).
return message;
},

thisFoo : function() {
setFoo("HELLO WORLD"); // wrong
alert(getFoo()); // wrong

alert(this.setFoo("HELLO WORLD")); // right!
}
}
}();

xmrcivicboix
7 Apr 2007, 8:08 AM
use "this" when accessing public properties and use variable names directly when dealing with private vars


Thanks!! that sure helped clarified a whole lot of things. :))

xmrcivicboix
7 Apr 2007, 8:24 AM
return {
destroyDialog: function() {
dialog.hide(function () {
dialog.destroy(true);
Ext.get(Ext.select('.x-shadow')).remove();
});
},

anotherFunction: function() {
this.destroyDialog(); //Error: this.destroyDialog is not a function.
}
}


Any idea why this would be?

Animal
7 Apr 2007, 8:24 AM
There's a difference between



MyWidget() = function {
// Do setup
};
MyWidget.prototype = {
init: function() {
}
};

var myObject = new MyWidget();


and



myObject = function() {
var privateData;
return {
publicData: "foo",

publicMethod: function() {
}
};
}();


The former defines a new class called MyWidget which you can instantiate as many times as you like using the "new" statement. Each new instance may have member variables.

The second creates a one off object. "myObject" only contains the object returned by the "return" statement. It does not contain a property called "privateData". However functions that are defined within it (like "publicMethod") are able to refer to the variable "privateData" because they are defined in the same scope.

Animal
7 Apr 2007, 8:28 AM
return {
destroyDialog: function() {
dialog.hide(function () {
dialog.destroy(true);
Ext.get(Ext.select('.x-shadow')).remove();
});
},

anotherFunction: function() {
this.destroyDialog(); //Error: this.destroyDialog is not a function.
}
}


Any idea why this would be?

Yes. When you send "anotherFunction" as a handler for an event, there is no "this" bound up with that, all you are passing is a function. No information whatsoever about any potential "this" object is passed.

Unless you pass it.

So



myWidget.on("click", myOtherWidget.anotherFunction);


Will fail, while



myWidget.on("click", myOtherWidget.anotherFunction, myOtherWidget);


Passes in the scope in which to execute the function, ie what to use as the "this" context.

xmrcivicboix
7 Apr 2007, 8:44 AM
Let me post a snippet of my code and see if it's correctly used: :">



var AdminLoginForm = function(){
var title = '<span style="background:url(images/window_dialog.png) no-repeat; padding-left:20px;">Administrator Login</span>';
var error_title = "Please Complete All Required Fields";
var connection_error = "Unable to Connect";
var dialog, showBtn, loginBtn;

// return a public interface
return {
init : function(){
Ext.DomHelper.applyStyles('admin-login-panel', "display:none");

showBtn = Ext.get('btn_login');
showBtn.on('click', this.showDialog, this);
},

/* Remove the login dialog and create the Admin Dialog */
destroyDialog: function() {
dialog.hide(function () {
dialog.destroy(true);
Ext.get(Ext.select('.x-shadow')).remove();
Ext.get('btn_login').set({id:'btn_logged_in'});
AdminPanel.init();
});
},

/* Submit the login form */
verifyIdentity : function() {
loginBtn.disable();

var ajaxLoginCallbackSuccess = function(o) {
var result = new Ext.util.JSON.decode(o.responseText);
if (result.message == "Identiy Verified") {
AdminPanel.title = '<span style="background:url(images/window_dialog.png) no-repeat; padding-left:20px;">' + result.title + '</span>';
AdminPanel.user = result.name;
AdminLoginForm.destroyDialog(); // this is where the this is using this.destroyDialog()
}
else {
dialog.setTitle('<span style="background:url(images/forbidden.png) no-repeat; padding-left:20px;">' + result.code + '</span>');
}
};

var ajaxLoginCallbackFailure = function(res) {
loginBtn.enable();
dialog.setTitle(connection_error);
};

Ext.lib.Ajax.formRequest('admin_login_form', 'admin/login.php',
{success: ajaxLoginCallbackSuccess, failure: ajaxLoginCallbackFailure});
},

/* Crete the login dialog panel */
showDialog : function(){
if(!dialog){ // lazy initialize the dialog and only create it once
dialog = new Ext.BasicDialog("admin-login-panel", {
resizable: false,
modal: false,
width:400,
height:200,
shadow:false,
minWidth:400,
minHeight:200,
draggable: true,
autoScroll: false,
fixedcenter: true
});
dialog.addKeyListener(27, dialog.hide, dialog);
loginBtn = dialog.addButton('Login', this.verifyIdentity, dialog);
dialog.addButton('Close', dialog.hide, dialog);
dialog.setTitle(title);
Ext.EventManager.addListener('username', 'blur', this.validateLoginField);
Ext.EventManager.addListener('password', 'blur', this.validateLoginField);
Ext.EventManager.addListener('username', 'focus', this.setBorderColor);
Ext.EventManager.addListener('password', 'focus', this.setBorderColor);
}
dialog.show(showBtn.dom);
loginBtn.disable();
},

/* Set the input field's border color to a light blue on focus. */
setBorderColor : function() {
Ext.DomHelper.applyStyles(this, "border: 1px solid #0f84aa");
},

/* Validate each form field blur event. Also, it sets the background of the input field and disable/enable the login button. */
validateLoginField : function(e) {
Ext.DomHelper.applyStyles(this, "border: 1px solid #000");
if (e.target.value == "") {
Ext.DomHelper.applyStyles(e.target, "background:url(images/error_field.png) no-repeat right;");
}
else {
Ext.DomHelper.applyStyles(e.target, "background:url(images/pass_field.png) no-repeat right;");
}

/* Enable and Disable the login button */
if (Ext.get('username').dom.value != "" && Ext.get('password').dom.value != "") {
loginBtn.enable();
}
else {
loginBtn.disable();
}
}
};
}();

Animal
7 Apr 2007, 9:39 AM
OK, I think I get it.

You want ajaxLoginCallbackSuccess to be executed in as a method of the object



/* Submit the login form */
verifyIdentity : function() {
loginBtn.disable();


var ajaxLoginCallbackFailure = function(res) {
loginBtn.enable();
dialog.setTitle(connection_error);
};

Ext.lib.Ajax.formRequest('admin_login_form', 'admin/login.php',
{success: this.ajaxLoginCallbackSuccess, failure: ajaxLoginCallbackFailure, scope: this});
},

ajaxLoginCallbackSuccess: function(o) {
var result = new Ext.util.JSON.decode(o.responseText);
if (result.message == "Identiy Verified") {
AdminPanel.title = '<span style="background:url(images/window_dialog.png) no-repeat; padding-left:20px;">' + result.title + '</span>';
AdminPanel.user = result.name;
this.destroyDialog(); // callback's "scope" gives you a "this"
}
else {
dialog.setTitle('<span style="background:url(images/forbidden.png) no-repeat; padding-left:20px;">' + result.code + '</span>');
}
},
...


Or you could use createDelegate to bind a "this" of your choosing to a function.

Read Ext.js from the source directory. Concentrate on the extensions to Function. Read it over and over until you understand it, it will be worth your while.

xmrcivicboix
7 Apr 2007, 10:40 AM
I see why it went out of scope because 'this' in this case refers to ajaxLoginCallbackSuccess object. I will take your advice and read over Ext.js.

xmrcivicboix
7 Apr 2007, 11:18 AM
Animal, after play with the createDelegate() I think I have figured it out.



Ext.lib.Ajax.formRequest('admin_login_form', 'admin/login.php',
{success: ajaxLoginCallbackSuccess.createDelegate(AdminLoginForm), failure: this.ajaxLoginCallbackFailure});


so then 'this' will be scoped to AdminLoginForm in which I can now use this to access all the function inside the return {}. You're truly an Animal! =D>

Animal
7 Apr 2007, 12:42 PM
Well done. Create delegate allows you to attach a "this" object to a function.

That is one way of specifying the scope. Without doing that, the "this" object would be the browser window, which wouldn't be much use.

You can also specify the scope using the "scope" property of the callback object of an Ajax request. It's a bit more efficient doing it this way because it won't generate a new function, it will just use that scope when calling the success or failure handler. The Function method createDelegate generates a brand new function which wraps the original.

xmrcivicboix
7 Apr 2007, 12:49 PM
I was just about to post this up after digging around:



Ext.lib.Ajax.formRequest('admin_login_form', 'admin/login.php',
{success: ajaxLoginCallbackSuccess,
failure: ajaxLoginCallbackFailure,
scope: AdminLoginForm});


obviously less code and easier to read. Thanks Animal! Rawllll!

Animal
7 Apr 2007, 12:52 PM
That's what I like to hear! The sound of some digging around!