First post, woo! Looked around quite a bit, but couldn't find a solution. Thanks for your attention and time. I have a class instance here with an array which I populate with objects from JSON data based on an ajax call. I would like to bind a function which renders the array as HTML, to an HTML button element.
Using when() after the instantiation and ajax call is complete, I can call upon properties of the instance, and even execute the function. But when I attempt to bind that same function to an HTML button click event, when I fire the event, the function references to an undefined 'theStats' object.
$(function ()
{
// Main Stats Object
var theStats = new Stats();
var initPop = theStats.populate();
$.when(initPop).done(function(a1)
{
// Returns defined instance property
console.log(theStats.arr.length);
// Function works properly
theStats.render();
// Why does this bind to an undefined 'theStats', when it executed
// only when the ajax call, as well as object construction is complete?
$("#render-stats").on("click", theStats.render);
});
});
I believe it is my misunderstanding about how asynchronous works? I was looking into event delegation as well, however I'm not creating a new HTML element, but an Object based on ajax. Any and all feedback is much appreciated (including my code practices, heh). Please let me know if I missed a similar solution, or if I've misunderstood how to stackoverflow. Thanks again!
class Stats
{
constructor()
{
// Creates Array for Stat Objects
this.arr = [];
}
/* Populate method requests thru ajax the JSON on the server on execution and
populates this current Stats object's array
*/
populate()
{
var that = this;
var ajaxGet = $.get("php/get.php",
function(jsonData, status)
{
var obj = JSON.parse(jsonData);
for (let key in obj)
{
var newStat = new Stat(obj[key].id, obj[key].distance, obj[key].date);
that.arr.push(newStat);
}
this.arr = that.arr;
console.log("Ajax Complete: " + this.arr);
});
return ajaxGet;
}
/* Render method creates HTML elements and renders them to the page using the data currently stored
within this particular Stats object */
render()
{
$("#test").text("");
for (let i = 0; i < this.arr.length; i++)
{
$("#test").append("<li class='runStat'> Run Number: " + this.arr[i].id + ", Distance: " + this.arr[i].distance + ", on Date: " + this.arr[i].date + "</li> <br/>");
}
}
}
Related
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);
Please bare bear with me, I'm very new to Javascript. I am pulling my hair out trying to figure out why this won't work. Keep in mind I come from a Java background. I have a function 'getCsvData' and I'm essentially trying to parse a CSV file and dynamically add object properties to the datasource object and then return it. As you can see, outside the function 'getCsvData', I try to log the results after calling my function, but the result object is empty and there are no object propeties added to it.
I have a very strong feeling it has to due with closure/scope chain resolution that I'm still trying to learn and understand.
The questions are: Why aren't the properties added dynamically to the datasource object? I believe they actually are added in the scope of the anonymous function 'function(data)' passed as the second argument to '$.get', but they are immediately gone once the outer function 'getCsvData' returns. Why, and how can I fix this? Thanks!!
<script src="js/jquery-1.10.2.min.js"></script>
<script src="js/knockout-3.0.0.js"></script>
<script src="js/globalize.min.js"></script>
<script src="js/dx.chartjs.js"></script>
<script src="js/jquery.parse.js"></script>
$(function () {
function getCsvData(fileName, groupBy, year) {
var datasource = { }
$.get(fileName, function(data) {
var alldata = $.parse(data, { header: true });
for (var i = 0; i<alldata.results.rows.length;i++) {
var key = alldata.results.rows[i][groupBy]
if (key in datasource) {
datasource[key] = datasource[key] + 1
} else {
datasource[key] = 0
}
}
});
return datasource;
};
var results = getCsvData("data/data.csv", "Priority", 2012);
console.log(results)
for (key in results) {
console.log(key)
}
});
This is because get is called async, so datasource is the return value after initiating the get rather than after receiving the result (i.e. it is empty because the get completion has not been called yet). You should rather indicate completion with a callback or use jQuery.ajax() with the sync option to wait for the response to the get before returning from getCsvData. See here.
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);
});
}
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.
On the upside I'm kinda bright, on the downside I'm wracked with ADD. If I have a simple example, that fits with what I already understand, I get it. I hope someone here can help me get it.
I've got a page that, on an interval, polls a server, processes the data, stores it in an object, and displays it in a div. It is using global variables, and outputing to a div defined in my html. I have to get it into an object so I can create multiple instances, pointed at different servers, and managing their data seperately.
My code is basically structured like this...
HTML...
<div id="server_output" class="data_div"></div>
JavaScript...
// globals
var server_url = "http://some.net/address?client=Some+Client";
var data = new Object();
var since_record_id;
var interval_id;
// window onload
window.onload(){
getRecent();
interval_id = setInterval(function(){
pollForNew();
}, 300000);
}
function getRecent(){
var url = server_url + '&recent=20';
// do stuff that relies on globals
// and literal reference to "server_output" div.
}
function pollForNew(){
var url = server_url + '&since_record_id=' + since_record_id;
// again dealing with globals and "server_output".
}
How would I go about formatting that into an object with the globals defined as attributes, and member functions(?) Preferably one that builds its own output div on creation, and returns a reference to it. So I could do something like...
dataOne = new MyDataDiv('http://address/?client');
dataOne.style.left = "30px";
dataTwo = new MyDataDiv('http://different/?client');
dataTwo.style.left = "500px";
My code is actually much more convoluted than this, but I think if I could understand this, I could apply it to what I've already got. If there is anything I've asked for that just isn't possible please tell me. I intend to figure this out, and will. Just typing out the question has helped my ADD addled mind get a better handle on what I'm actually trying to do.
As always... Any help is help.
Thanks
Skip
UPDATE:
I've already got this...
$("body").prepend("<div>text</div>");
this.test = document.body.firstChild;
this.test.style.backgroundColor = "blue";
That's a div created in code, and a reference that can be returned. Stick it in a function, it works.
UPDATE AGAIN:
I've got draggable popups created and manipulated as objects with one prototype function. Here's the fiddle. That's my first fiddle! The popups are key to my project, and from what I've learned the data functionality will come easy.
This is pretty close:
// globals
var pairs = {
{ div : 'div1', url : 'http://some.net/address?client=Some+Client' } ,
{ div : 'div2', url : 'http://some.net/otheraddress?client=Some+Client' } ,
};
var since_record_id; //?? not sure what this is
var intervals = [];
// window onload
window.onload(){ // I don't think this is gonna work
for(var i; i<pairs.length; i++) {
getRecent(pairs[i]);
intervals.push(setInterval(function(){
pollForNew(map[i]);
}, 300000));
}
}
function getRecent(map){
var url = map.url + '&recent=20';
// do stuff here to retrieve the resource
var content = loadResoucrce(url); // must define this
var elt = document.getElementById(map.div);
elt.innerHTML = content;
}
function pollForNew(map){
var url = map.url + '&since_record_id=' + since_record_id;
var content = loadResoucrce(url); // returns an html fragment
var elt = document.getElementById(map.div);
elt.innerHTML = content;
}
and the html obviously needs two divs:
<div id='div1' class='data_div'></div>
<div id='div2' class='data_div'></div>
Your 'window.onload` - I don't think that's gonna work, but maybe you have it set up correctly and didn't want to bother putting in all the code.
About my suggested code - it defines an array in the global scope, an array of objects. Each object is a map, a dictionary if you like. These are the params for each div. It supplies the div id, and the url stub. If you have other params that vary according to div, put them in the map.
Then, call getRecent() once for each map object. Inside the function you can unwrap the map object and get at its parameters.
You also want to set up that interval within the loop, using the same parameterization. I myself would prefer to use setTimeout(), but that's just me.
You need to supply the loadResource() function that accepts a URL (string) and returns the HTML available at that URL.
This solves the problem of modularity, but it is not "an object" or class-based approach to the problem. I'm not sure why you'd want one with such a simple task. Here's a crack an an object that does what you want:
(function() {
var getRecent = function(url, div){
url = url + '&recent=20';
// do stuff here to retrieve the resource
var content = loadResoucrce(url); // must define this
var elt = document.getElementById(div);
elt.innerHTML = content;
}
var pollForNew = function(url, div){
url = url + '&since_record_id=' + since_record_id;
var content = loadResoucrce(url); // returns an html fragment
var elt = document.getElementById(div);
elt.innerHTML = content;
}
UpdatingDataDiv = function(map) {
if (! (this instanceof arguments.callee) ) {
var error = new Error("you must use new to instantiate this class");
error.source = "UpdatingDataDiv";
throw error;
}
this.url = map.url;
this.div = map.div;
this.interval = map.interval || 30000; // default 30s
var self = this;
getRecent(this.url, this.div);
this.intervalId = setInterval(function(){
pollForNew(self.url, self.div);
}, this.interval);
};
UpdatingDataDiv.prototype.cancel = function() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
})();
var d1= new UpdatingDataDiv('div1','http://some.net/address?client=Some+Client');
var d2= new UpdatingDataDiv('div2','http://some.net/otheraddress?client=Some+Client');
...
d1.cancel();
But there's not a lot you can do with d1 and d2. You can invoke cancel() to stop the updating. I guess you could add more functions to extend its capability.
OK, figured out what I needed. It's pretty straight forward.
First off disregard window.onload, the object is defined as a function and when you instantiate a new object it runs the function. Do your setup in the function.
Second, for global variables that you wish to make local to your object, simply define them as this.variable_name; within the object. Those variables are visible throughout the object, and its member functions.
Third, define your member functions as object.prototype.function = function(){};
Fourth, for my case, the object function should return this; This allows regular program flow to examine the variables of the object using dot notation.
This is the answer I was looking for. It takes my non-functional example code, and repackages it as an object...
function ServerObject(url){
// global to the object
this.server_url = url;
this.data = new Object();
this.since_record_id;
this.interval_id;
// do the onload functions
this.getRecent();
this.interval_id = setInterval(function(){
this.pollForNew();
}, 300000);
// do other stuff to setup the object
return this;
}
// define the getRecent function
ServerObject.prototype.getRecent = function(){
// do getRecent(); stuff
// reference object variables as this.variable;
}
// same for pollForNew();
ServerObject.prototype.pollForNew = function(){
// do pollForNew(); stuff here.
// reference object variables as this.variable;
}
Then in your program flow you do something like...
var server = new ServerObject("http://some.net/address");
server.variable = newValue; // access object variables
I mentioned the ADD in the first post. I'm smart enough to know how complex objects can be, and when I look for examples and explanations they expose certain layers of those complexities that cause my mind to just swim. It is difficult to drill down to the simple rules that get you started on the ground floor. What's the scope of 'this'? Sure I'll figure that out someday, but the simple truth is, you gotta reference 'this'.
Thanks
I wish I had more to offer.
Skip