Javascript/jQuery object scope issues - javascript

I am having some issues with scope in JS, which I am just picking up.
I've defined an object and am making a .getJSON() call within it, but I don't seem to be able to correctly refer to the calling object's attributes:
// Vehicle object
function vehicle(id) {
this.id = id;
var that = this;
// Fetch some JSON
$.getJSON("json.php?act=vehicleInfo&id=" + this.id, function (json) {
that.vehicleInfo = json
that.icon = L.AwesomeMarkers.icon({ icon: that.vehicleInfo.icon, color: that.vehicleInfo.colour });
that.polyline = new L.Polyline([[that.vehicleInfo.latitude, that.vehicleInfo.longitude]]);
that.marker = L.marker([that.vehicleInfo.latitude, that.vehicleInfo.longitude], {icon: that.icon});
that.marker.bindPopup("Test point");
that.marker.addTo(map);
that.polyline.addTo(map);
});
}
// Vehicle move method
vehicle.prototype.move = function(latlng){
this.marker.setLatLng(latlng);
this.polyline.addLatLng(latlng);
}
When I call .move(), this.marker is undefined. Where am I going wrong here?

Unfortunately, Ajax doesn't work that way. You can't depend on the $.getJSON callback completing at any particular time or even at all. One possibility is to make the request synchronous, but this is not recommended because it locks up the browser.
The only two solutions that are possible are:
Do not depend on ajax
Make anything that depends on the result of an ajax callback depend on the callback itself.
That is any code that calls .move for a vehicle has to be done as a result of the $.getJSON call. You can make it look a bit more elegant, though:
this.jqxhr = $.getJSON(...
/* snip */
vehicle.prototype.move = function (latlng) {
var veh = this;
this.jqxhr.done(function () {
veh.marker.setLatLng(latlng);
veh.polyline.setLatLng(latlng);
});
}

Related

Javascript Objects / Prototypes. Is my understanding wrong

I have been reading about Javascript Classes / Objects / Prototypes and come from a OOP background so I now wish to use Objects in Javascript.
However I think I am misunderstanding something. When using objects in the past in VB.net for example you created your object and was able to populate it by using dataclasses.
But here in the land of javascript things dont execute in the way I was expecting due to this async thing.
So I create my prototype as below and call the appropriate function but the function says it has fnished (but hasnt because it hasnt had a response from the $.post that is taking place, my code continues as null values and I dont get the info I want.
<!DOCTYPE html>
<html>
<body>
<button onclick="gogetperson()">Hello</button>
<p id="demo"></p>
<script>
function Person() {
this.firstName = '-';
this.surname= '-';
this.alias = 0;
}
Person.prototype.name = function() {
return this.firstName + " " + this.surname
};
Person.prototype.getPerson = function(personid)
{
var query = $.param({personid: personid});
var url = 'custom/person_get.php';
$.post(url, query, function (response) {
var obj = $.parseJSON(response);
this.firstname= obj['rFirstName'];
this.surname = obj['rLastName'];
this.alias = obj['rAlias'];
console.log(this.firstname);
});
}
function gogetperson()
{
var myPerson = new Person();
myPerson.getPerson(1)
console.log(myPerson.firstName);
}
</script>
<script src="plugins/jQuery/jQuery-2.1.4.min.js"></script>
</body>
</html>
So when my button runs the gogetperson function it finishes but has not got the data yet.
The console.log in the $.post section returns to the console the first name of my person but too late for me to use it.
Am I using prototypes in the wrong way?
I want to be able to use javascript as an object when getting data.
Or am I totally wrong.
My reason for wanting to use it this way was it seems the better choice over php objects.
Should I use PHP objects instead?
I ultimately want to get data from a mysql database and easily change my webpage with jquery and javascript.
You are missing one thing ,that is http call using $.post is asynchronous and it is not a blocking call. In order to get values back from getPerson() method, you have to pass a callback ( or function) in that function to get the result back but it is not a recomended approach . You can read about promises and use them because they are great.
However using a callback, you can do something like.
Person.prototype.getPerson = function(personid, cb)
{
var query = $.param({personid: personid});
var url = 'custom/person_get.php';
$.post(url, query, function (response) {
var obj = $.parseJSON(response);
var person = new Person();
person.firstname= obj['rFirstName'];
person.surname = obj['rLastName'];
person.alias = obj['rAlias'];
console.log(person.firstname);
cb(person);
});
}
And then you can call it like,
var myPerson = new Person();
function showPersonDetails(person){
console.log(person.name())
}
myPerson.getPerson(1, showPersonDetails);

need help understanding closures usage in this code

Here is a simplified snippet from some code I wrote for managing tablet gestures on canvas elements
first a function that accepts an element and a dictionary of callbacks and register the events plus adding other features like 'hold' gestures:
function registerStageGestures(stage, callbacks, recieverArg) {
stage.inhold = false;
stage.timer = null;
var touchduration = 1000;
var reciever = recieverArg || window;
stage.onLongTouch = function(e) {
if (stage.timer) clearTimeout(stage.timer);
stage.inhold = true;
if (callbacks.touchholdstart) callbacks.touchholdstart.call(reciever, e);
};
stage.getContent().addEventListener('touchstart', function(e) {
e.preventDefault();
calcTouchEventData(e);
stage.timer = setTimeout(function() {
stage.onLongTouch(e);
}, touchduration);
if (callbacks.touchstart) callbacks.touchholdstart.call(reciever, e);
});
stage.getContent().addEventListener('touchmove', function(e) {
e.preventDefault();
if (stage.timer) clearTimeout(stage.timer);
if (stage.inhold) {
if (callbacks.touchholdmove) callbacks.touchholdmove.call(reciever, e);
} else {
if (callbacks.touchmove) callbacks.touchmove.call(reciever, e);
}
});
stage.getContent().addEventListener('touchend', function(e) {
e.preventDefault();
if (stage.timer) clearTimeout(stage.timer);
if (stage.inhold) {
if (callbacks.touchholdend) callbacks.touchholdend.call(reciever, e);
} else {
if (callbacks.touchend) callbacks.touchend.call(reciever, e);
}
stage.inhold = false;
});
}
later I call registerStageGestures on a few elements (represented by 'View' objects) in the same page. Something like:
function View() {
var self=this;
..
function InitView() {
...
registerStageGestures(kineticStage, {
touchstart: function(e) {
// do something
},
touchmove: function(e) {
// do something
},
touchendunction(e) {
// do something
},
touchholdstart: function(e) {
// do something
},
touchholdmove: function(e) {
// do something
},
touchholdend: function(e) {
// do something
},
}, self);
Everything works fine, however I'm left wondering about two things in the implementation of registerStageGestures:
First, is it necessary to make inhold, timer and onLongTouch members of the stage ? or will closures make everything works well if they are local vars in registerStageGestures ?
Second, is it necessary to call the callbacks with '.call(receiver,' syntax ? I'm doing this to make sure the callback code will run in the context of the View but I'm not sure if it's needed ?
any input is much appreciated
Thanks!
First, is it necessary to make inhold, timer and onLongTouch members
of the stage ? or will closures make everything works well if they are
local vars in registerStageGestures ?
As far as registerStageGestures() is concerned, var inhold, var timer and function onLongTouch(e) {...}. would suffice. The mechanism by which an inner function has automatic access to its outer function's members is known as "closure". You would only need to set stage.inhold, stage.timer and stage.onLongTouch if some other piece of code needs access to these settings as properties of stage.
Second, is it necessary to call the callbacks with '.call(receiver,'
syntax ? I'm doing this to make sure the callback code will run in the
context of the View but I'm not sure if it's needed ?
Possibly, depending on how those callbacks are written. .call() and .apply() are sometimes used when calling functions that use this internally. In both cases, the first parameter passed defines the object to be interpreted as this. Thus, javascript gives you the means of defining general purpose methods with no a priori assumption about the object to which those methods will apply when called. Similarly, you can call a method of an object in such a way that it acts on another object.
EDIT:
For completeness, please note that even in the absence of this in a function, .apply() can be very useful as it allows multiple parameters to be specified as elements of a single array, eg the ubiquitous jQuery.when.apply(null, arrayOfPromises)...
There are some simple answers, here.
First, closure:
Closure basically says that whatever is defined inside of a function, has access to the rest of that function's contents.
And all of those contents are guaranteed to stay alive (out of the trash), until there are no more objects left, which ere created inside.
A simple test:
var testClosure = function () {
var name = "Bob",
recallName = function () { return name; };
return { getName : recallName };
};
var test = testClosure();
console.log(test.getName()); // Bob
So anything that was created inside can be accessed by any function which was also created inside (or created inside of a function created in a function[, ...], inside).
var closure_2x = function () {
var name = "Bob",
innerScope = function () {
console.log(name);
return function () {
console.log("Still " + name);
}
};
return innerScope;
};
var inner_func = closure_2x();
var even_deeper = inner_func(); // "Bob"
even_deeper(); // "Still Bob"
This applies not only to variables/objects/functions created inside, but also to function arguments passed inside.
The arguments have no access to the inner-workings(unless passed to methods/callbacks), but the inner-workings will remember the arguments.
So as long as your functions are being created in the same scope as your values (or a child-scope), there's access.
.call is trickier.
You know what it does (replaces this inside of the function with the object you pass it)...
...but why and when, in this case are harder.
var Person = function (name, age) {
this.age = age;
this.getAge = function () {
return this.age;
};
};
var bob = new Person("Bob", 32);
This looks pretty normal.
Honestly, this could look a lot like Java or C# with a couple of tweaks.
bob.getAge(); // 32
Works like Java or C#, too.
doSomething.then(bob.getAge);
? Buh ?
We've now passed Bob's method into a function, as a function, all by itself.
var doug = { age : 28 };
doug.getAge = bob.getAge;
Now we've given doug a reference to directly use bobs methid -- not a copy, but a pointer to the actual method.
doug.getAge(); // 28
Well, that's odd.
What about what came out of passing it in as a callback?
var test = bob.getAge;
test(); // undefined
The reason for this, is, as you said, about context...
But the specific reason is because this inside of a function in JS isn't pre-compiled, or stored...
this is worked out on the fly, every time the function is called.
If you call
obj.method();
this === obj;
If you call
a.b.c.d();
this === a.b.c;
If you call
var test = bob.getAge;
test();
...?
this is equal to window.
In "strict mode" this doesn't happen (you get errors really quickly).
test.call(bob); //32
Balance restored!
Mostly...
There are still a few catches.
var outerScope = function () {
console.log(this.age);
var inner = function () {
console.log("Still " + this.age);
};
inner();
};
outerScope.call(bob);
// "32"
// "Still undefined"
This makes sense, when you think about it...
We know that if a function figures out this at the moment it's called -- scope has nothing to do with it...
...and we didn't add inner to an object...
this.inner = inner;
this.inner();
would have worked just fine (but now you just messed with an external object)...
So inner saw this as window.
The solution would either be to use .call, or .apply, or to use function-scoping and/or closure
var person = this,
inner = function () { console.log(person.age); };
The rabbit hole goes deeper, but my phone is dying...

Solution to "this" reference changing inside callbacks?

One of the issues I've been encountering again and again is the reference to the this pointer changing. Take the below example. I want to create a Server object, and store the resolution of the camera as a property. This is not possible, as this.resolution applies to a property in the camera callback object instead of the Server object.
function Server(options) {
this.settings = options.settings;
this.camera = options.camera;
// Grab camera resolution
this.camera.getImageResolution(function(err, data) {
this.resolution = data;
});
}
Server.prototype.start = function() {
console.log(this.resolution); // This outputs an undefined variable error
}
In the past, I have worked around this by renaming this to self temporarily to call functions. This does not work when I am storing a value. I would need to to pass this into the callback, which I obviously cannot do.
Additionally, I cannot use apply, as that would not allow camera.getImageResolution to call its own methods.
What is the best route to take in solving this issue? Please ask for clarification if my question is vague.
function Server(options) {
var self = this;
self.settings = options.settings;
self.camera = options.camera;
// Grab camera resolution
this.camera.getImageResolution(function(err, data) {
self.resolution = data;
});
}
Server.prototype.start = function () {
return this.resolution;
}
var server = new Server({options: {...}, settings: {...}});
server.camera.getImageResolution();
// after getImageResolution's asynch method has completed
server.start() // === data parameter from getImageResolution's asynch method callback

How to always call a function from a JavaScript object literal, not only when the object is created

I hope my title isn't too confusing. An example first. I have the following code that configures the read operation for a Kendo UI data source. I am trying to filter all reads based on the selected company id, but my getSelectedCompanyId function is only ever called once, when the page loads. The code below is too long to include all here, so just an excerpt.
$(function () {
function getSelectedCompanyId() {
var id = $("#CompanyId").val();
return id;
}
$("#CompanyId").kendoDropDownList({
change: function () {
grid.dataSource.read();
}
});
var departmentIndexDataSource = new kendo.data.DataSource({
transport: {
read: {
url: '#Url.Action("ListForCompanyIdJson", "Department")' + '?companyId=' + getSelectedCompanyId(),
type: "GET"
},
The ListForCompanyIdJson action is always called with the value selected in the dropdown when $("#CompanyId").kendoDropDownList() is called. I want this function to be called whenever I call grid.dataSource.read().
I realize this might be highly specific to the Kendo stuff, but maybe it's something I can solve with plain JavaScript closures and some help.
You could install a replacement function for grid.dataSource.read() that always calls your function first and then calls grid.dataSource.read().
For example:
grid.dataSource.oldRead = grid.dataSource.read;
grid.dataSource.read = function() {
// call your function here
return grid.datasource.oldRead.apply(this, arguments);
}
Or, if you want to be able to call one sometimes and one other times, you can just make a new method that provides the new behavior and use it when you want to:
grid.dataSource.myread = function() {
// call your function here
return grid.datasource.read.apply(this, arguments);
}

Variable scope in Javascript Object

I'm discovering the concept of "objects" in JavaScript. I'm making an RSS Parser, and I have an error (commented).
function MyParser (feed_url) { // Construct
"use strict";
this.feedUrl = feed_url;
this.pubArray = [];
if (typeof (this.init_ok) == 'undefined') {
MyParser.prototype.parse = function () {
"use strict";
var thisObj = this;
$.get(this.feedUrl, function (data, textStatus, jqXHR) {
if (textStatus == 'success') {
var xml = jqXHR.responseXML,
//lastBuildDate = new Date($(xml).find('lastBuildDate').text());
items = $(xml).find('item');
items.each(function () {
var pubSingle = thisObj.makeObj($(this).find('pubDate').text(),
$(this).find('link').text(),
$(this).find('title').text(),
$(this).find('description').text(),
$(this).find('encoded').text(),
$(this).find('commentRss').text(),
$(this).find('comments').last().text());
thisObj.pubArray.push(pubSingle);
});
console.log(thisObj.pubArray); // OK
}
}, 'xml');
console.log(this.pubArray); // Empty
return (this.pubArray);
};
MyParser.prototype.makeObj = function (pubDate, pubLink, pubTitle, pubDesc, pubContent, pubComCount, pubComLink) {
"use strict";
var pubSingle = {};
pubSingle.pubDate = new Date(pubDate);
pubSingle.pubLink = pubLink;
pubSingle.pubTitle = pubTitle;
pubSingle.pubDesc = pubDesc;
pubSingle.pubContent = pubContent;
pubSingle.pubComCount = pubComCount;
pubSingle.pubComLink = pubComLink;
return (pubSingle);
};
}
this.init_ok = true;
}
If you look at the console.log(), you'll see that the line // OK is outputting my array correctly.
But later, when returning from $.get, my array is empty.
Does anybody have an idea why, and how to correct that please?
This is not a problem with variable-scope. The problem here is that you're working with asynchronous flow and you're not thinking correctly the flow.
Let me explain:
When you do your .get, you fire a parallel asynchronous process that will request information from the browser, but your main program's flow keeps going, so when you get to your "return" statement, your array has not been filled yet with the response from your get method.
You should use your array from inside the get callback and not outside of it, since you can't guarantee that the array will have the information you need.
Does it make any sense?
Let me know!
Further explanation
According to your comments, you're still doing something like this:
var results = MyParser(feed_url);
//code that uses results.pubArray
And you cannot do that. Even though you're setting your "pubArray" inside your .get callback, you're trying to use pubArray right after you called MyParser and that's before the .get callback is called.
What you have to do, is call your next step on your program's logic from within the .get callback... that's the only way you can be sure that the pubArray is filled with proper data.
I hope that makes it clearer.
This is because your line
console.log(this.pubArray); // Empty
is being called directly after you issue your Ajax request; it hasn't had time to fetch the data yet. The line
console.log(thisObj.pubArray); // OK
is being called inside the Ajax callback, by which time the data has been fetched.
Thank you all, and particulary #Deleteman .
Here is what I did:
$.get(this.feedUrl, 'xml').success(function () {
thisObj.handleAjax(arguments[0], arguments[1], arguments[2]);
$(document).trigger('MyParserDone');
}).error(function () {
$(document).trigger('MyParserFailed');
});
Then, when i enter "HandleAjax", i'm back in my object context, so "this" refers to my object and the right properties. The only "problem" is that I have to set a listener (MyParserDone) to make sure the parsing is finished.

Categories

Resources