I am working on modifying a web application built by someone else a few years ago. In it, he built an API function in JS which when called, will pull data from SharePoint. I am adding a feature to the application, and will need to do another API call to retrieve some different data. So far, I haven't been able to figure out how to modify the code so that it waits for the ajax call to complete. All of the research that I have done indicates that I should be using a callback to accomplish this, but I am not sure how to implement it properly.
The existing code looks like this:
API = function(apiFunction, dataToPost, successCallback) {
var baseApiUrl = '/SomeWebApp/API/';
var apiUrl = baseApiUrl + apiFunction;
$.ajax({
url: apiUrl,
data: JSON.stringify(dataToPost),
dataType: "json",
type: "POST",
contentType: "application/json; charset=utf-8",
dataFilter: function(data) { return data; },
success: successCallback,
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert('Error calling webservice: ' + apiFunction);
}
});
}
And the call to the API is:
API('Lists.asmx/GetStuff', dataToPost, function(data) {
var options = [];
$.each(data.d, function(index, value) {
options.push(new Category(value.Field, value.AnotherField, value.YetAnotherField));
});
var viewModel = new ViewModel(options);
ko.applyBindings(viewModel);
});
What I need to do is perform the second API call to retrieve the rest of the data, and then create the view model, passing it both sets of data.
What I've tried:
Moving options outside of the callback function, but it seems that because it is asynchronous, the script isn't waiting for the data to be returned. If it did work, I could move ko.ApplyBindings outside of the callback function, and just create the new view model with both sets of data
Assigning the API call to a variable, and having the callback function return options. For example:
var x = API('Lists.asmx/GetStuff', dataToPost, function(data) {
var options = [];
$.each(data.d, function(index, value) {
options.push(new Category(value.Field, value.AnotherField, value.YetAnotherField));
});
return options;
});
What would be the best way to modify the code to accomplish this? Should I create a wrapper function that includes the API function as a callback? How would I put that together?
The quick and dirty solution is to put the second API call within the callback function of the first API call. Return statements really don't do anything in an async function unless it's returning a "promise" (see option 2).
API('Lists.asmx/GetStuff', dataToPost, function(data) {
var options = [];
$.each(data.d, function(index, value) {
options.push(new Category(value.Field, value.AnotherField, value.YetAnotherField));
});
API('Lists/asmx/GetStuff2', secondDataToPost, function(data2){
var viewModel = new ViewModel(options, data2);
ko.applyBindings(viewModel);
});
});
Option 2 - since your API function is already using jQuery to handle the ajax you can change it to return the result of the ajax call, a type of deferred object, on which you can call .done to attach a callback method instead. jquery-deferred-and-promise walkthrough
API = function(apiFunction, dataToPost, successCallback) {
var baseApiUrl = '/SomeWebApp/API/';
var apiUrl = baseApiUrl + apiFunction;
return $.ajax({
...
});
}
The returned deferred object can be used similarly to the passed in callback by using the .done method on the object.
API('Lists.asmx/GetStuff', dataToPost).done(function(data) {
callback stuff...
});
This is a little more flexible and can let you do chaining and simultaneous execution like the following where both api calls get sent at the same time instead of having to wait for the response from the first call before sending the second one.
$.when(
API('Lists.asmx/GetStuff', dataToPost),
API('Lists.asmx/GetStuff2', dataToPost2)
).done(function(data1, data2) {
var options = [];
$.each(data.d, function(index, value) {
options.push(new Category(value.Field, value.AnotherField, value.YetAnotherField));
});
var viewModel = new ViewModel(options, data2);
ko.applyBindings(viewModel);
});
Related
I have an observablearray called documents which I load with JSON from the result of an API call. I then use document to load up the currently selected document. Code is as follows:
self.documents = ko.observableArray();
self.document = {
DocID: ko.observable(),
DocName: ko.observable(),
isDocumentEdit: ko.observable(false)
}
function populateDocumentLocations() {
ajaxHelper(url + '/api/Documents/' + self.businessList.busID().busID, 'GET').done(function (data) {
self.documents(data);
});
}
function ajaxHelper(uri, method, data) {
return $.ajax({
type: method,
url: uri,
dataType: 'json',
contentType: 'application/json',
data: data ? JSON.stringify(data) : null
}).fail(function (jqXHR, textStatus, errorThrown) {
alert(errorThrown);
});
}
The two properties that the API returns are DocID and DocName. This all works fine. I added a third field to document (as above), "isDocumentEdit" so I know if the user has clicked the "edit" button or not (to change the GUI accordingly).
However, the value of isDocumentEdit remains as "undefined" rather than false.
I've also tried looping through "documents" and adding "isDocumentEdit = false" for each element but that didn't work.
Is it possible to add another parameter to an observable after it has been loaded? Or is there a way to load it when it makes the API call (this is where I tried to loop through and add in but didn't work)?
When the data comes back from the ajax call it needs to be mapped to your model that contains the observables.
First declare the Document viewModel:
function Document (document) {
var self = this;
self.DocID = ko.observable(document.DocID);
self.DocName = ko.observable(document.DocName);
self.isDocumentEdit = ko.observable(false);
}
Then you can use the jQuery $.map function to map the json response to your documents:
function populateDocumentLocations() {
ajaxHelper(url + '/api/Documents/' + self.businessList.busID().busID, 'GET').done(function (data) {
var mappedDocuments = $.map(data, function(document) { return new Document(document) });
self.documents(mappedDocuments);
});
}
This is also in the Knockout tutorial which is a good reference: http://learn.knockoutjs.com/#/?tutorial=loadingsaving
I currently have an AJAX call out to a PHP file that works, that is the following:
//Load map from database
function getMap(){
return $.ajax({
url: "getMap.php",
type: "POST",
dataType: 'JSON',
});
};
getMap().done(function(r) {
if (r) {
loadedMap(JSON.parse(r.mapArray), JSON.parse(r.mapProperties)); //call loadedMap(r) if loading a map from DB
} else {
console.log("No data");
}
}).fail(function(x) {
console.log("error");
});
That works within a single javascript file that successfully passes r.mapArray and r.mapProperties to my main loadedMap function.
I'm trying to learn about the IIFE Javascript Module model, and split my work up into separate files.
So, I currently have main.js:
(function() {
// let's get our map
var gameMap = mapGen.getMap().done();
console.log(gameMap);
})();
and mapGen.js:
var mapGen = function() {
return {
getMap: function() {
return $.ajax({
url: "getMap.php",
type: "POST",
dataType: 'JSON',
});
}
};
}()
If I console.log(gameMap), I see the responseText JSON object with my data. I just can't seem to access it. console.log(gameMap.responseText) is undefined. as is gameMap.responseJSON (though I see both of them in the console).
Looking the code over it looks as the the separation of the files was not the issue and that looks to be implemented correctly. The issue is with how you are handling the AJAX request. The function mapGen.getMap() actually returns a jqXHR Object as opposed to the response that you are trying to access.
Just as you had in your previous file, you will need handle the response of your request.
(function() {
// let's get our map request
var gameMap = mapGen.getMap();
gameMap.done(function(r){ ... }).
fail(function(x){ ... });
})();
You will be able to access the response data you are looking for within the done() function.
I am trying to create a database handler class in javascript. I would like to call the class by simply using:
var databaseHandler = new DatabaseHandler();
result = databaseHandler.getResult("SELECT * FROM login");
I have created the class and used a callback for the ajax function (so as to wait for the ajax result to be returned). But all I am still receiving "undefined" as my result. If I use console.log(a) inside of the onComplete function, I get an array of the intended results.
(function(window){
//Database class
function DatabaseHandler(){
//Query
this.query = function(query, whenDone){
request = $.ajax({
url: "../optiMizeDashboards/php/DatabaseQuery.php",
type: "POST",
data: {query : query},
dataType: "JSON"
});
request.done(function(output) {
whenDone(output);
});
request.fail(function(jqXHR, textStatus) {
console.log(textStatus);
});
};
//Get result
this.getResult = function(query){
this.query(query, this.onComplete);
};
//Ajax callback
this.onComplete = function(a){
return a;
};
}
//Make available to global scope
window.DatabaseHandler = DatabaseHandler;
}(window))
My question is: Is this something to do with the variable scope, or the way that ajax works? I have read all the answers explaining that ajax is ASYNC and I thought I had handled that by using a callback function "onComplete"
Any help on this topic would be greatly appreciated!
You will not be able to return result immediately from calling getResult because underlying jQuery POST request is Asynchronous, instead you need to be passing a callback function which eventually will receive a result from server.
something like that:
(function(window){
//Database class
function DatabaseHandler(){
//Query
this.query = function(query, whenDone){
request = $.ajax({
url: "../optiMizeDashboards/php/DatabaseQuery.php",
type: "POST",
data: {query : query},
dataType: "JSON"
});
request.done(function(output) {
whenDone(output);
});
request.fail(function(jqXHR, textStatus) {
console.log(textStatus);
});
};
//Get result
this.getResult = function(query, callback){
this.query(query, callback);
};
}
//Make available to global scope
window.DatabaseHandler = DatabaseHandler;
}(window))
// then use it like so
var databaseHandler = new DatabaseHandler();
result = databaseHandler.getResult("SELECT * FROM login", function(data) {
//do something with data
});
PS: exposing direct SQL access to the databse on the client is very dangerous though, and I would not recommend doing that
I have an issue with a method ive created for an object ive created. one of the methods requires a callback to another method. the problem is i cant add the data to the object that called the method. it keeps coming back as undefined. otherwise when i send the data to the console it is correct. how can i get the data back to the method?
var blogObject = new Object();
var following = [...];
//get posts from those blogs
blogObject.getPosts = function () {
var followersBlogArray = new Array();
for (var i = 0; i < this.following.length;i++){
var followersBlog = new Object();
// get construct blog url
var complete_blog_url = ...;
i call the getAvatar function here sending the current user on the following array with it.
followersBlog.avatar = blogObject.getAvatar(this.following[i]);
that part goes smoothly
followersBlogArray.push(followersBlog);
}
this.followersBlogArray = followersBlogArray;
}
here is the function that gets called with the current user in following array
this function calls an ajax function
blogObject.getAvatar = function (data) {
console.log("get avatar");
var url = "..."
this ajax function does its work and has a callback function of showAvatar
$(function() {
$.ajax({
type: "GET",
dataType: "jsonp",
cache: false,
url: url,
data: {
jsonp:"blogObject.showAvatar"
}
});
});
}
this function gets called no problem when getAvatar is called. i cant however get it to add the data to the followersBlog object.
blogObject.showAvatar = function (avatar) {
return avatar
}
everything in here works fine but i cant get the showAvatar function to add to my followersBlog object. ive tried
blogObject.showAvatar = function (avatar) {
this.followersBlog.avatar = avatar;
return avatar
}
that didnt work of course. it shows up as undefined. can anyone help?
so somethings like...
$(function() {
$.ajax({
type: "GET",
dataType: "jsonp",
cache: false,
url: url,
complete: function () {
this.avatar = data;
}
data: {
jsonp:"blogObject.showAvatar"
}
});
});
}
Welcome to the world of asynchronous programming.
You need to account for the fact that $.ajax() will not return a value immediately, and Javascript engines will not wait for it to complete before moving on to the next line of code.
To fix this, you'll need to refactor your code and provide a callback for your AJAX call, which will call the code that you want to execute upon receiving a response from $.ajax(). This callback should be passed in as the complete argument for $.ajax().
The correct option for setting the JSONP callback is jsonpCallback. The recommendation from the API for .ajax(...) is to set it as a function.
{
// ...
jsonpCallback: function (returnedData) {
blogObject.showAvatar(returnedData);
},
// ...
}
I want to create a separate function to get specific data from Facebook graph JSON.
For example, I have the load() and called getNextFeed() function.
The getNextFeed works correctly. Except that returning value of aString is not successful.
When I pop alert(thisUrl). It said undefined.
Note: I am new to Javascript and Jquery. Please give me more information where I did wrong. Thank you.
function load()
{
$(document).ready(function() {
var token = "AccessToken";
var url = "https://graph.facebook.com/me/home?access_token=" + token;
var thisUrl = getNextFeed(url);
alert(thisUrl); // return undefined
});
function getNextFeed(aUrl)
{
$.ajax({
type: "POST",
url: aUrl,
dataType: "jsonp",
success: function(msg) {
alert(msg.paging.next); // return correctly
var aString = msg.paging.next;
alert(aString); // return correctly
return aString;
}
});
}
The problem is that $.ajax() is an ansynchronous function, means, when called, it returns in the same instance, but an ajax call is done in a separate thread. So your return vaule of $.ajax() is always undefined.
You have to use the ajax callback function to do whatever you need to do: Basically you already did it correctly, just that return aString does not return to your original caller function. So what you can do is to call a function within the callback (success()), or implement the logic directly within the success() function.
Example:
function load()
{
$(document).ready(function() {
var token = "AccessToken";
var url = "https://graph.facebook.com/me/home?access_token=" + token;
getNextFeed(url);
alert('Please wait, loading...');
});
function getNextFeed(aUrl)
{
$.ajax({
type: "POST",
url: aUrl,
dataType: "jsonp",
success: function(msg) {
alert(msg.paging.next); // return correctly
var aString = msg.paging.next;
alert(aString); // return correctly
do_something_with(aString);
}
});
}