passing data from methods in javascript - javascript

Totally new to OOP in javascript, but Im trying and reading all I can.
Ive created a simple test javascript class called Invoices. Invoices only has two methods. One method fires the other. And this part seems to be working fine.
My problem lies with getting data objects from one method to another. I put a alert in the first method, (from my understanding) this alert should show data returned from the second method... but its not.
Any help would be greatly appreciated. Oh.. and I am using jquery as well.
Here is my codez.
function Invoices()
{
this.siteURL = "http://example.com/";
this.controllerURL = "http://example.com/invoices/";
this.invoiceID = $('input[name="invoiceId"]').val();
}
invoice = new Invoices;
Invoices.prototype.initAdd = function()
{
//load customers json obj
this.customersJSON = invoice.loadCustomers();
alert(this.customersJSON);
//create dropdown
}
Invoices.prototype.loadCustomers = function ()
{
$.post(this.controllerURL + "load_customers"),
function(data)
{
return data;
}
}

There are two problems with that. First of all, $.post is asynchronous; you'll have to adopt a callback scheme or use $.ajax to make it synchronous. Secondly, you probably meant to do this:
$.post(this.controllerURL + "load_customers", function(data) {
return data;
});
Note how the closure is in the parentheses of the function call.

As you were told the AJAX call is asynchronous, you would have to implement your initAdd in 2 step:
BeginInitAdd which would initiate the AJAX call
EndInitAdd which would be the callback for your AJAX call and perform the action depending on the data returned.
Invoices.prototype.initAdd = function()
{
//load customers json obj
this.xhrObj = invoice.loadCustomers();
alert(this.customersJSON);
}
Invoices.prototype.createDropdown = function (data) {
//create dropdown
this.customersJSON=data
}
Invoices.prototype.loadCustomers = function ()
{
return $.post(this.controllerURL + "load_customers"),
function(data)
{
//return data;
this.createDropdown(data)
}
}

Related

Wait for list of functions to complete then reload page

I have lots of different functions that send AJAX requests to save different parts of the page. When the user clicks save, All these functions are run like so.
function savePage() {
if (!confirm('Save changes?')) return false;
saveSortOrder();
saveAllWidth();
saveAllTinyMCE();
saveAllWidgetRm();
location.reload();
}
Once everything is saved, I want to reload the page but location.reload() runs before everything is finished.
A typical save function looks like this but some are much bigger and there are lots of them
function saveAllPublic() {
$('.widget').each( function(){
var parentID = $(this).attr('id');
var publicState = $(this).attr('data-public');
$.post('widgets/manage_widgets.php', {
update: 'publicity',
wd_parent: parentID,
public: publicState
});
});
}
Basically, I want all the POSTs to complete before reloading the page.
First of all, you will want the save functions to return an array of promises. We can do that by simply using .map(), and within in return the AJAX call you have made, i.e.:
// saveAllPublic() will return an array of promises
function saveAllPublic() {
return $('.widget').map(function(){
var parentID = $(this).attr('id');
var publicState = $(this).attr('data-public');
return $.post('widgets/manage_widgets.php', {
update: 'publicity',
wd_parent: parentID,
public: publicState
});
}).get();
}
Note: Remember to use .get() at the end of .map(), in order to obtain a true array and not a jQuery collection (which is an array-like object). See a more thorough explanation here: map() get() confusion
When you want to check if all the requests made by saveAllPublic() is done, you can simply do this:
var saveAllPublicAJAX = saveAllPublic();
$.when.apply($, saveAllPublicAjax).then(function() {
// Callback when all POST requests in saveAllPublic() is completed
});
And let's say based on your example you have refactored all the save functions to use the array push method I have mentioned above, you can simply concatenate all these returned arrays into a single one, and pass it to $.when:
function savePage() {
if (!confirm('Save changes?')) return false;
var saveAJAX = [].concat.apply([], [
saveSortOrder(),
saveAllWidth(),
saveAllTinyMCE(),
saveAllWidgetRm()
]);
$.when.apply($, saveAJAX).then(function() {
// When all the requests are successful
location.reload();
}, function() {
// When one or more requests have failed
// ...
});
}
There are, of course, other more verbose way of constructing the array, such as:
var saveAJAX = saveSortOrder().concat(saveAllWidth()).concat(...)
var saveAJAX = []; saveAJAX.push(saveSortOrder()); ...

How do I use a function inside a loop inside a .then?

I have an Angularjs project that uses Restangular to access the database. I have three layers of data (say mydata, mysubdata, mysubsubdata) and there is a one-to-many relationship between each layer. My problem is that, for my display, I need to concatenate the mysubsubdata to the mysubdata. When I try to get data back from the database, I am hitting a complaint in the compiler that says I can't have a function in a loop. Here is what I am trying to do:
DataService.one(mydata.id).getList('mysubdata')
.then(function(data) {
var dataList = data;
for (returnedData in dataList) {
DataService.one(mydata.id).one('mysubdata',returnedData.id).getList('mysubsubdata')
.then(returnedSubData) {
dataList = angular.extend(dataList, returnedSubData);
});
}
});
All the examples I've found have loops inside the .then function or are trying to get a bunch of promises back first. I don't think those apply. I'm still pretty new to Angular, so I may be flailing a bit. Not sure about the extend either, but that's likely a separate question.
Edit: I suspect this should be done with a $q.all but haven't grasped the method yet.
Adding a then() method inside your loop won't work because of the simple reason that loop does not wait for the promises to be resolved. You can achieve this using recursive method.
var myCustomData = null,
dataList = null,
dataListCounter = 0;
DataService.one(mydata.id).getList('mysubdata')
.then(function (data){
dataList = data;
myCustomData = mydata;
$scope.getSubSubData();
});
$scope.getSubSubData = function () {
if (dataList.length >= dataListCounter)
return;
DataService.one(myCustomData.id).one('mysubdata',dataList[dataListCounter].id).getList('mysubsubdata')
.then(function (returnedSubData) {
dataList = angular.extend(dataList, returnedSubData);
dataListCounter++;
$scope.getSubSubData();
});
};
Please let me know if this helps!
Minor corrections to #Anadi Sharma's response.
$scope.getSubSubData = function () {
if (dataList.length == dataListCounter)
return;
DataService.one(myCustomData.id).one('mysubdata',dataList[dataListCounter].id).getList('mysubsubdata')
.then(function (returnedSubData) {
dataList[dataListCounter].mysubsubdata = returnedSubData;
dataListCounter++;
$scope.getSubSubData();
});
};
Note that I then use a filter when I display the data to concatenate the subsubdata values.

Execute dynamic number of ajax request sequentially

Have the following scenario :
I have to display the graphs for a given interval (startDate,endDate)
Because the interval might be quite big , the data is retrieved per day so I need
to do multiple ajax calls sequentially and to append the data to the graph(highcharts)
Example interval is n days ==>
ajax request day 1
when is (done) ready ajax request day 2
when is (done) ready ajax request day 3
....
ajax request day n
I read about deferred and promises BUT I found difficult to with dynamic number of days and the requirement to get the responses sequentially
Thanks
If you're able to store the list of dates in an array, you can use something like this:
var items = ['Apple', 'Orange', 'Banana', 'Alphalpha'];
//replaceable with any function that returns a promise
function asyncFunction(item) {
return $.ajax({
url: '/echo/html',
type: 'POST',
data : item
})
.then(function(data){
$('body').append('<div>Got the response from '+item+'</div>');
//stuff stuff stuff
});
}
function sequence(arr, callback) {
var i=0;
var request = function(item) {
return callback(item).then(function(){
if (i < arr.length-1)
return request(arr[++i]);
});
}
return request(arr[i]);
}
sequence(items, asyncFunction).then(function(){
$('body').append('<div>Done with all!</div>');
});
https://jsfiddle.net/7ojy9jnx/2/
Basically, sequence takes an Array of items and runs a function on all of them (in this case asyncFunctions, which can be replaced with any function), a function that returns a promise.
This is very basic implementation, you'll notice, for example, it has no error handling. Libraries like async.js have an exhaustive list of tools that accomplish tasks like this, but who knows, maybe this will suffice.
Not sure if you already figured it out, but a good way to tackle your problem would be using a combination of jQuery.Deferred and recursion. Check out this sample code and see if it helps clarify things:
function getData(dayLimit) {
var allDone = $.Deferred();
var getDataForDay = function(day) {
doAsyncThing(day).done(function() {
if (day < dayLimit) {
getDataForDay(day + 1);
} else {
allDone.resolve();
}
}).fail(function(){
/*
Reject the deferred if one of your operations fails.
Useful if you're binding "fail" or "always" callbacks
to the promise returned by getData.
*/
allDone.reject();
});
};
getDataForDay(1); //start with first day
return allDone.promise();
}
Let me know if you need more clarification, happy to help!
What about recursively calling. Create a parameterized function and pass the day to the function like,
function getDetails(day) {
// ajax call
// In the callbacks call the getDetails function by updating the date
}
If you are using Jquery in your Application try pushing all the ajax to an array
ex
[ajax,ajax,...]
and then user
$.when([ajax,ajax,...]).then(function(){
console.log(arguments);// you will get the success messages in arguments array
})

javascript MVC structure, map model to rest api

I am trying to make a model (the M in MVC) in my web application, to understand the concept. I am struggling with the async problem that occurs in my code below:
function FruitModel(api) {
this._api = api;
}
FruitModel.prototype = {
getFruit: function(fruit_id) {
$.getJSON(this._api + fruit_id, function(data){
return data;
});
}
}
$(function(){
var fruits = new FruitModel("/fruit/");
console.log(fruits.getFruit(123))
});
I get that this is wrong because the console.log will happen before the getJSON has finished, be cause of that this is asynchronous. How should I design this instead to prevent the problem?
I suppose that getting the actual data for a front end MVC is to access it from within the model from a RESTapi equal to mine. Is there a preferred solution instead of doing this inside the model as I do?
You can send a callback to the function and execute it when you done with your async code:
FruitModel.prototype = {
getFruit: function(fruit_id,callback) {
$.getJSON(this._api + fruit_id, function(data){
callback(data);
});
}
}
$(function(){
var fruits = new FruitModel("/fruit/");
fruits.getFruit(123, function(data){
console.log(data);
});
});
To avoid that problem you can do something like this ( because from jQuery 1.5 .getJSON implements Promise interface )
function FruitModel(api) {
this._api = api;
}
FruitModel.prototype = {
getFruit: function(fruit_id) {
return $.getJSON(this._api + fruit_id);
}
}
//Call
fruits.getFruit(123).done(function(data){
console.log(data);
})

Javascript rendering. How to write Javascript so it doesn't continue executing code before a function call has ended

Not sure if my question is subjective/objective but as a JavaScript newbie i'm encountering this problem quite a lot. So here I go.
I'm used to write C#, so my JavaScript structure looks like C#. And just that, that gives problems I think ;-)
Let's give a simple example where I met my problem again today:
MyLibrary.fn.InitAddEntityForm = function () {
$('a#btnAddEntity').click(function () {
//post data and receive object with guid and isPersisted boolean
var persistedObject = MyLibrary.fn.CheckAndSendAddEntityForm("name", "avatarurl.png");
console.log("test");
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject.gdEntityId);
} else {
alert("Oops, something went wrong. Please call 911");
}
});
};
//////*****/////
//SOME FUNCTION THAT SENDS MY FORM AND RETURNS AN OBJECT WITH TRUE VALUE AND POSTED ENTITY ID
/////*****//////
MyLibrary.fn.CheckAndSendAddForm = function (txtName, ImageUrl) {
var postUrl = "/admin/add";
var persistedObject = new Object();
$.post(
postUrl,
{ Name: txtName, ImageUrl: txtImageUrl},
function (data) {
if (data.Status == 200) {
console.log("Post status:" + data.Message);
persistedObject.isPersisted = true;
persistedObject.gdEntityId = data.Data;
} else if (data.Status == 500) {
console.log("Failed to post entitiy");
} else {
console.log("Fault with Javascript");
}
}, "json"
);
return persistedObject;
};
Okay, thats it. Everything looks okay right? Browser says no.
I tried to debug it using firebug, looping over my code line by line, and that way the browser does what I want: Execute a new function to show the next panel in my wizard.
After placing a lot of Console.logs() in my code I figured out that this must be something about timing in JavaScript. In C# the code executes line by line, but apparently JavaScript doesn't.
By placing that Console.log("test") I noticed that "test" appeared in my console before "Post status: Success!".
So here's my question, how should I write my JavaScript code so I have control over the way the browser executes my code?
Should I really replace the code below to the end of my CheckAndSendAddEntityForm()?
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject.gdEntityId);
} else {
alert("fout");
}
Is this how I have to write JavaScript: One big domino effect or am I just doing something wrong?
$.post is a shortcut for an AJAX call, AJAX is by definition asynchronous, which means it won't wait on a response before continuing processing. If you switch it to a regular AJAX() method, there is an async option you can set to false, which will make it behave as you are expecting.
Alternatively you can also define a function to execute on successful return of the AJAX request, in which you can call the next step in your process chain.
The AJAX call is asychronous; that means that the callback method exposes by $.post will be executed when the request completes, but your javascript will continue executing as soon as the invoke to $.post finishes. If you want to do something after the ajax call is done, you need to provide a callback method and do something else, ex:
MyLibrary.fn.CheckAndSendAddForm = function (txtName, ImageUrl, callback) {
var postUrl = "/admin/add";
var persistedObject = new Object();
$.post(
postUrl,
{ Name: txtName, ImageUrl: txtImageUrl},
function (data) {
if (data.Status == 200) {
console.log("Post status:" + data.Message);
persistedObject.isPersisted = true;
persistedObject.gdEntityId = data.Data;
} else if (data.Status == 500) {
console.log("Failed to post entitiy");
} else {
console.log("Fault with Javascript");
}
callback(); // This is where you return flow to your caller
}, "json"
);
};
Then you invoke like so:
var persistedObject = MyLibrary.fn.CheckAndSendAddEntityForm("name", "avatarurl.png", function()
{
console.log("test");
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject .gdPronoId);
} else {
alert("Oops, something went wrong. Please call 911");
}
});
JavaScript is single-threaded. If you have asynchronous functionality, a simple boolean semaphore variable will help not to allow invocations of a function while some processes are running.
If you want to execute asynchronous tasks one by one (like a domino line), you will need to use callback functions.
What you're encountering is the "asynchronous" bit of AJAX. If you want to physically (as in the line line by line in the Javascript file) you can use the .success,.pipe or .done jQuery methods to add a callback to process the data further. Don't embed your callbacks if you can help it, or you will get a "domino effect" as you call it.

Categories

Resources