jquery variable scope problem - javascript

I have a chat class with two methods: updateChat and sendChat.
//chat.js
var state;
var room;
function Chat (theRoom) {
this.update = updateChat;
this.send = sendChat;
this.room = theRoom;
}
function updateChat(){
alert('ROOM: '+this.room);
$.ajax({
type: "POST",
url: "/chat/process.php",
data: {
'function': 'update',
'state': state,
'room': this.room
},
dataType: "json",
success: function(data){
if(data.text){
for (var i = 0; i < data.text.length; i++) {
$('#chat-area').append($("<p>"+ data.text[i] +"</p>"));
}
}
if(data.state)
state = data.state;
}
});
}
}
//send the message
function sendChat(message, nickname)
{
alert('A'+state); //20
//XXX
updateChat();
alert('B'+state); //20
$.ajax({
type: "POST",
url: "/live-event/chat/process.php",
data: {
'function': 'send',
'message': message,
'nickname': nickname,
'room': this.room
},
dataType: "json",
success: function(data){
alert('C'+state); //wrong!: 2 //it should be 20!
//XXX
updateChat();
alert('D'+state); //21
},
});
}
The constructor of the chat object:
var chat = new Chat(4); //4 = the number of the chat room
chat.send('test', 'tester');
My problem are the method calls at the locations marked with XXX.
In the updateChat() method, this.room is undefined if I call the updateChat methods like that.
But I need to pass the room number to get the right state (state is simply the number of lines in the chat room's text file).
I think it's a problem with variable scope or with the methods not being called in the context of the object.

You need to maintain this when calling those methods, so instead of this:
updateChat();
You can use .call() to maintain context (so this doesn't revert to window inside the called function), like this:
updateChat.call(this);
Or call the method on the object as #casablanca points out below:
this.update();
There also one more issue, this won't be what you want in your $.ajax() callbacks, it'll be the ajax settings object by default, so you need to set the context option to maintain it, like this:
$.ajax({
context: this,
type: "POST",
//...rest of your current options/methods

You may find this easier to grasp if you forget about classes. What you've written there is not a class. There are no classes in JavaScript. You've written a constructor function, but it's of somewhat dubious value because you're assigning the members individually for every instance. The main purpose of constructor functions is to take advantage of prototype-inheritance, so you'd assign the "methods" to the constructor function's prototype.
function Chat (theRoom) {
this.room = theRoom;
}
Chat.prototype.send = sendChat;
Chat.prototype.update = updateChat;
That way, each object created with new Chat(r) will only need to store the room number, and will not need to store the two methods as properties.
Alternatively, just write a createChatRoom function:
var createChatRoom = function(room) {
return {
update: function() {
alert('updating: ' + room);
// ... other stuff
},
sending: function() {
alert('sending: ' + room);
// ... other stuff
}
};
};
The beauty of that is probably obvious: you don't need to prefix anything with this. The room parameter is in scope to the method definitions, and is also truly private (cannot be modified except through calls to the methods. And the caller doesn't have to remember to put new. They just call the function and get back a fresh object with two methods in it.
You can even safely do this:
setTimeout(chatRoom.update, 10);
The update function "knows" what object it is associated with. It never needs to be told which this to use.
This is so convenient and useful, unless I'm expecting to create very large quantities of objects, I don't bother with constructor functions, new, prototype, etc.

Related

Using parent object's properties inside jQuery function and other methods

First of all: I don't know exactly how to call everyting since I am quite new to the more OOP way of writing javascript, so I'll try to explain everything as good as possible.
My problem is that I want to access properties inside an object ( so I can use the this-keyword. This works fine as long as I am in the scope of the object. When I go outside the scope, I would like to access those properties while I can't use the this-keyword anymore.
My code:
var Octa = Octa || function () {
this._initialize();
};
Octa.prototype = {
string: 'Foo',
_initialize: function () {
console.log(this.string); //Output: "Foo"
this.othermethod();
}
}
var Octa = new Octa();
But when I have a method within an Octa method, so outside the scope where I can't use this anymore to get Octa's properties, I can't reach the properties within Octa.
For example:
othermethod: function () {
$.ajax({
url: this.globalUrl + 'content/language/lang.' + l + '.php',
data: {
ajax: true
},
dataType: 'json',
success: function (response) {
Octa.lang = response;
}
});
console.log(JSON.stringify(this.lang)); //Output: null, which means Octa.lang wasn't reachable in the ajax success event (the ajax request was successful).
}
Is there a way to reach the scope of Octa within other objects? Or within jQuery callbacks since the same problem occurs there.
I hope my problem is understandable and if not, I'll try to give more clarification.
Simply refer back to this inside the function scope:
...,
someMethod: function () {
var self = this,
ajaxOptions = this.settings.ajaxOptions;
// note we can still refer to 'this' at this level
$.ajax(ajaxOptions).done(this.ajaxDone).fail(this.ajaxFail);
// the function scope changes for the deffered handlers so you can access by reference of 'this' => self
$.ajax(ajaxOptions).done(function(data, status, xhr){
self.ajaxDone(data, status, xhr)
}).fail(function(xhr, status, error){
self.ajaxFail(xhr, status, error);
});
},
ajaxDone: function(data, status, xhr) {},
ajaxFail: function(xhr, status, error) {},
...
Hope this makes sense.
Now there's also a .bind() function that can be used to bind function scope to a parameter:
$.ajax(ajaxOptions).done(function(){
this.ajaxDone();
}.bind(this));
You'll have to use a polyfill to support older browsers. It's much more easier to use var self imho.

Javascript Object Prototype falling out of scope

I am currently writing a small Javascript Object which will add click listeners onto certain elements which then trigger an AJAX call to a PHP function. This is all working fine however, I want to call a function when the AJAX responds. I have made this happen by passing a function to the AJAX call which will be triggered when the response is given.
The problem I am having is that I am losing the scope of the object when passing through the protoytype as a call back (in order to stop the aynschronous problems that can occur with AJAX calls). The 'this' object (or self) is set to the window and not the instance of the object I have created. Here is my code:
//Rating Submit
var Rater = function(element, videoId, ratingStars, userId) {
this.userId = userId,
this.element = element;
this.videoId = videoId;
this.ratingStars = ratingStars;
var self = this;
jQuery(this.ratingStars).click(function(e) {
e.preventDefault();
self.ratingClick(this, self.changeStar);
});
}
Rater.prototype.ratingClick = function(item, changeStar) {
jQuery.ajax({
type : 'post',
dataType : 'json',
url : 'api/rate-video',
data : "userId=" + this.userId + "&videoId=" + this.videoId + "&rating=" + jQuery(item).attr("data-score"),
success : function(data) {
changeStar(data, item);
}
});
}
Rater.prototype.changeStar = function(response, item) {
var maxCount = jQuery(item).attr("data-score");
//console.log(self);
jQuery(self.ratingStars).each(function(key, value) {
alert(key);
});
}
As you can see, I am passing the 'self.changestar' prototype function to the AJAX call for this to be called when a response is given. When I try and access any of the variable I set in the constructor for that particular instance though, it says it is the Window object and not an instance of the class. Is it possible to pass through a prototype function as a call back from within the instance? I hope I have explained myself ok....
Thanks
The problem is that when you do this:
self.ratingClick(this, self.changeStar);
you have exactly the same problem you had in Rating with the jQuery click callback, which you solved with your self variable: Only the function reference, changeStar, gets passed, nothing about what value to use as this when calling it.
One solution is to use Function#bind, which you call on a function to get back another function that, when called, will call the original with a specific this value (and optional arguments):
self.ratingClick(this, self.changeStar.bind(self));
Alternately, you could pass the value to use as this separately:
self.ratingClick(this, self.changeStar, self);
...and then use Function#call in the success handler:
Rater.prototype.ratingClick = function(item, changeStar, thisArg) { // <== Change here
jQuery.ajax({
type : 'post',
dataType : 'json',
url : 'api/rate-video',
data : "userId=" + this.userId + "&videoId=" + this.videoId + "&rating=" + jQuery(item).attr("data-score"),
success : function(data) {
changeStar.call(thisArg, data, item); // <=== And here
}
});
}

Firing Events in JavaScript

I have a JavaScript class named 'Item'. 'Item' is defined as shown here:
function Item() { this.create(); }
Item.prototype = {
create: function () {
this.data = {
id: getNewID(),
}
},
save: function() {
$.ajax({
url: getBackendUrl(),
type: "POST",
data: JSON.stringify(this.data),
contentType: "application/json",
success: save_Succeeded,
error: save_Failed
});
},
function save_Succeeded(result) {
// Signal an event here that other JavaScript code can subscribe to.
}
function save_Failed(e1, e2, e3) {
// Signal an event here that other JavaScript code can subscript to.
}
}
Please note, I'm coming from a C# background. So I'm not even sure if what I want to accomplish is possible. But essentially, I want to create an object, subscribe to some event handlers, and attempt to save my object. For instance, I envision doing something like the following throughout my code.
var i = new Item();
i.item_save_succeeded = function() {
// Do stuff when the item has successfully saved
};
i.item_save_failed = function() {
// Do stuff when the item has failed to save
};
i.save(); // start the save process
Is this event-based approach even possible in JavaScript? If so, how? What am I missing? I keep getting a variety of errors that are vague. Because of that, I'm not sure if I'm getting closer or farther away.
If you are using jQuery, you can add an event handler to a custom event type.
The following snippet is taken from the jQuery docs
$('#foo').bind('custom', function(event, param1, param2) {
alert(param1 + "\n" + param2);
});
$('#foo').trigger('custom', ['Custom', 'Event']);
But since jQuery 1.7 deprecates bind, you should use on now. See the jQuery docs for on.
Not 100% sure and I look forward to seeing the answer from a JS pro, but here is what I would do.
Expose some properties within you Item object - namely the functions you wish to be subscribed to.
Upon instancing an item you could then provide callback functions for the events that you wish to be notified of. In your code you could then do something like this:
save: function() {
var self = this;
$.ajax({
url: getBackendUrl(),
type: "POST",
data: JSON.stringify(this.data),
contentType: "application/json",
success: function() { if(typeof(self.success) == "function") self.success(); }
error: function() { if(typeof(self.fail) == "function") self.fail(); }
});
},
In effect, pass the callback functions to the object and let it call them directly when needed. I'm sure someone will now suggest a better way of doing it. :-)

Creating custom JavaScript object from data returned by jQuery AJAX request

I want to create a custom javascript object which contains data returned from a jQuery AJAX request, but I don't know which is the right way to do it. I was thinking maybe one way could be to include the AJAX request inside the constructor function so the object is created like this:
// Constructor function
function CustomObject(dataUrl) {
var that = this;
$.ajax(dataUrl, {
success: function (json) {
that.data = $.parseJSON(json);
}
});
}
// Creating new custom object
var myObject = new CustomObject('http://.....');
Another way may be to use a function which does the AJAX and then returns the new object based on the data from the AJAX response.
function customObject(dataUrl) {
// Constructor function
function CustomObject(data) {
this.data = data;
}
$.ajax(dataUrl, {
success: function (json) {
var data = $.parseJSON(json);
return new CustomObject(data);
}
});
}
// Creating new custom object
var myObject = customObject('http://.....')
I would like to know what is the best practice when doing something like this, as well as advantages/disadvatages of different methods. Maybe you can point me to some article or example on something similiar to what I am trying to do.
Thanks in advance.
I think this would be a better approach, it makes your CustomObject only knowledgeable about the data it contains. Here you delegate the work of creating objects to a factory, and pass in a callback to get a reference to the created object, since ajax is asynchronous. If you don't mind making it synchronous, then the createCustomObject function can just return the instance, and the callback can be removed.
function CustomObject(data) {
this.data = data;
}
var factory = (function(){
function create(dataUrl, objectClass, callback){
$.ajax({
url: dataUrl,
success: function (data) {
callback(new objectClass(data));
}
});
}
return{
createCustomObject: function(dataUrl, callback){
create(dataUrl, CustomObject, callback);
}
};
})();
// Creating new custom object
var myObject = null;
factory.createCustomObject('http://..', function(customObject){
myObject = customObject;
});
I'd argue that the second method is better because then you only create a new CustomObject once the script is actually fully prepared to do so (i.e. it has the data it needs from the AJAX request).

JQuery making ajax options dynamic

right now in my $.ajax({ ..}); call I have the following option:
data: { param0: param0, param1: param1}
Say I want the number of parameters to by dynamic (based on a variable passed to the function in which the ajax call is made). How do I provide data: a dynamic set of parameters? I think I need to somehow construct an object (?) ahead of time (i.e. before the ajax call) and then pass this object to data:..but I'm not sure how to do this.
By variable passed in, I mean optional parameters which will be used as the GET params: param2 and param3 if they are passed in. So:
function myAjaxCall(param0, param1, param2, param3) { // param2/3 are optional
$.ajax({
//...
data: { param0: param0, param1: param1} // this will need param2/3 if passed in
//..
});
}
So depending on if param2 and param3 are passed in (either, none or both is valid) I need the data object constructed accordingly.
As you mentioned you need to make an object out of your parameters and pass it as data:
var mydata = {
name: 'ali',
email: 'ali#example.com',
...
}
$.ajax({
...
data: mydata,
...
});
You should make use of the automatically created arguments array.
function myAjaxCall() {
$.ajax({
data: arguments, // "arguments" is an Array Object created automatically by JS
...
});
}
or, if you want it to be an object literal with the arguments array as a property:
function myAjaxCall() {
$.ajax({
data: {args: arguments}, // "arguments" is an Array Object
... // created automatically by JS
});
}
You can call this with any number of parameters, and the parameters can be any data form.
myAjaxCall({ fruit: "apple"}, "blah", 500, true);
Note that arguments is read only, so if you want to work with it, you have to copy it, and arguments.splice(0) will not work.... you have to use a for loop.
To check how many arguments were passed in, simply look at arguments.length.

Categories

Resources