What am I trying to achieve?
There is a I am trying to update a nice jenkins radiator application made by Clayton Lengel-Zigich. It is a backbone.js application that fetches the status of a jenkins master server using its jsonp api and displays job status. I want to change this application to fetch from multiple masters and show the status for many jobs.
What have I tried?
I have tried to update the sync function to do two jsonp requests and then merge the result. The original file can be found here: https://github.com/clayton/jenkins-radiator/blob/master/src/radiator.js
sync: function(method, model, options) {
var paramsA = _.extend({
type: 'GET',
dataType: 'jsonp',
processData: true,
url: "http://serverA:8080/api/json?jsonp=?"
}, options);
var a = $.ajax(paramsA);
var paramsB = _.extend({
type: 'GET',
dataType: 'jsonp',
processData: true,
url: "http://serverB:8080/api/json?jsonp=?"
}, options);
var b = $.ajax(paramsB);
function MergeJobs(o, ob) {
for (var z in ob.jobs) {
o.jobs.push(ob.jobs[z]);
}
return o;
}
var joinedJson = []
a.done(function(jsonA) {
joinedJson = jsonA;
});
b.done(function(jsonB) {
MergeJobs(joinedJson, jsonB);
//joinedJson = jsonB;
})
return joinedJson;
},
What I got
I expected that I would be able to see the merged results in the webpage however I only see the results for one of the servers. Which server gets displayed is random, indicating to me that my merge is overwriting, not merging.
I don't know a lick of javascript so I am at the stage where I don't even know where to look next. Perhaps the _.extend section is what actually updates the model? If so, is there a way to achieve what I want another way?
You can create separate models and display them on the same If the models have the same structure I suggest you create an array containing all your models and then pass them to a collection. If your models are totally different then pass them to a view and have some logic in the view to handle all your models.
This site (http://backbonetutorials.com/) has some great tutorials to get started with models and collections just in case you haven't used backbone that much.
There's no guarantee as to which of your requests finishes first. If B finishes before A, it will merge jsonB into the empty joinedJson (simply setting it to jsonB) and then when A finishes it will overwrite joinedJson with jsonA.
That's not your only problem; at the time you return joinedJson there's no guarantee that either request has finished; you're treating asynchronous requests as if they were synchronous.
Related
I have a peculiar ajax scenario that I just can't wrap my head around.
This is the sequence of events that I am trying to co-ordinate:
A single request returns an array of id numbers.
For each id, a new request is sent.
When all those requests have returned, their data is collected and rendered.
It seems simple enough, but I can't figure out how to express it.
My understanding of chaining deferred objects is that they are all instantiated immediately, and then data flows through them as they are resolved. But how does this work when one of the items is an array, potentially of size zero?
I know I'm going to need $.when().apply(), to watch the array of responses come in.
I think maybe I need a single deferred object that somehow stands in for the array, but I can't figure out how to express this.
Honestly this sounds like a bad pattern to use as you have N+1 requests all being fired. If you have a lot of items in the array and/or lots of users you could end up DDOSing your own server.
Because of this it would be better practice to change point 2 so that all ids are sent in a single request, or even change point 1 to return all the required data from the elements with the given ids.
That being said, you can achieve what you are asking for by saving the promises returned by jQuery's AJAX methods to an array and then applying that to $.when(), like this:
$.ajax({
url: '/endpoint1',
data: { foo: 'bar' },
success: function(data) {
var requests = [];
for (var i = 0; i < data.ids.length; i++) {
requests.push(
$.ajax({
url: '/endpoint2'
data: { id: data.ids[i] }
})
);
}
$.when.apply($, requests).done(function() {
// do something when all AJAX requests have completed here...
});
}
});
Scenario:
So I have a javascript component that basically holds an instance of some global list of data (In my actual application, this resides in a Flux Store, but I'm just referring it as a global variable for simplicity's sake).
It contains functions to ADD/DELETE data by making AJAX calls to a REST API.
Design:
Since I want the users to be able to immediately view the updated list, instead of having to wait until the Ajax success callback, I'm performing an "Optimistic Update."
That is, I'm updating the list before performing the actual AJAX call, while keeping the original copy of the list in localStorage in case the AJAX call fails.
(1) If the AJAX call succeeds, then update the list with the API response (which should basically be the same as the optimistically updated list)
(2) If the AJAX call fails, then undo the optimistic update by retrieving the original copy from the localStorage.
Here is my Implementation:
// Some global data list
var myData = ["data1", "data2", ...];
function addData(dataToAdd) {
// Store original in cache before optimistic update
localStorage.set("MY_DATA", myData);
// Do optimistic update
myData = myData.concat(dataToAdd);
$.ajax({
url: REST_API_ADD,
method: "POST",
data: { data: dataToAdd },
dataType: "json",
success: function(response) {
// API returns the updated list when success
myData = response;
},
error: function(xhr, status, err) {
console.log(err);
// Cancel optimistic update and retrieve old data from cache
myData = localStorage.get("MY_DATA");
}
});
}
function deleteData(dataToDelete) {
// Store original in cache before optimistic update
localStorage.set("MY_DATA", myData);
// Do optimistic update
// I'm using Underscore.js here to delete data from list
myData = _.without(
myData,
_.findWhere(myData, {id: dataToDelete.id})
);
$.ajax({
url: REST_API_DELETE,
method: "DELETE",
data: { data: dataToDelete },
dataType: "json",
success: function(response) {
// API returns the updated list when success
myData = response;
},
error: function(xhr, status, err) {
console.log(err);
// Cancel optimistic update and retrieve old data from cache
myData = localStorage.get("MY_DATA");
}
});
}
Is there anything wrong with this idea?
My primary concern is a race condition that might occur when the user performs ADD and DELETE operations almost simultaneously..
I've thought of possible scenarios, but it seems like concurrency is not a problem in my case, since the callback functions never modify the localStorage. All they do is "get."
Can anyone think of situations where my design might cause problems?
Is this a bad design overall? If so, then can you suggest an alternative approach?
Thanks
My primary concern is a race condition that might occur when the user
performs ADD and DELETE operations almost simultaneously..
There should be no concern here since AJAX executed from the UI thread isn't asynchronous in terms of parallelism. Web browsers use an execution queue where each enqueued action is dequeued synchronously.
For example, it will never happen that you modify the DOM and perform an AJAX request concurrently. Actually, UI tasks have more priority than AJAX, but anyway they will be executed one by one.
AFAIK, there's only one chance that you will run in concurrency problems: Web workers. Unless you use them, you're absolutely safe.
I think this is not a bad design, but you can still have more control over users. At least you can block them from taking another action while an ajax call waiting for success.
I can think of one problem: If you are dealing with lots of data, writing to and reading from the localStorage may lead to performance problems.
Besides these, there should be no problems at all within your approach.
So far I'm having no issue setting up an AngularJS model in my Rails application and giving it data to access on the front-end. I even set it up to be populated with data from an AJAX request using $http. However, I need this this model to contain the data of multiple $http calls. Here's the code I've got thus far:
function DropboxCtrl($scope, $http) {
var $infiniteLoader = $(".infiniteLoader");
var theUIDS = $infiniteLoader.attr('data-dropbox-uids').split(',');
if($infiniteLoader.attr('data-dropbox-uids') != "") {
var theData = {};
$.each(theUIDS, function(key) {
$http({ url: '/dropbox/files/get', method: 'GET', params: { uid: theUIDS[key] }}).success(function(data) {
theData = data;
});
});
$scope.dropboxes = theData;
}
}
I have a method called DropboxCtrl which will start by getting all the UID's that I need to call a GET request on. I loop through each of them and then append data to theData which is a Javascript object. After the each I make my dropboxes model equal to the value of theData. Current I've got the method returning absolutely nothing and no Javascript errors. I am positive that my url works completely and actually did get the code working with just one AJAX request like such:
$.each(theUIDS, function(key) {
$http({ url: '/dropbox/files/get', method: 'GET', params: { uid: theUIDS[key] }}).success(function(data) {
$scope.dropboxes = data;
});
});
However... that code block only returns the last AJAX call because the other ones are overwritten. Maybe what I'm missing is just incorrect Javascript, however, maybe what I'm missing is just a lack of understanding the "Angular way" of things. I'm skilled in Javascript and jQuery, but very new to Angular. Any help?
AngularJs is a high level Javascript framework. The code ultimately is javascript. Within your $each, you can push results to an array or to an initialized collection like
$scope.dropboxes = [{uid:1234}, {uid:2345}] and so on.
within the $each, locate the record for uid and attach the results.
I usually use underscorejs library for operations on collections, arrays etc.
so something like
_.findWhere($scope.dropboxes, {uid: data.uid }).data = data;
assuming the data that is returned has uid in it. If not then there should be another way to map the results to the request. Note that there is no guarantee of the order of responses, so you cannot use array indexes to map results.
I have a collection which has to call 4 external apis Eg: http://www.abc.com, http://www.fgt.com, http://www.jkl.com and http://www.rty.com.
I have a Collection named Todos.js. Is there a way I can fetch the 4 apis together in a single collection since all the four apis would provide me the same model response
So the response I get from the 4 apis has the same data structure i.e. "name" and "link".
Is there a way I can append all the responses in the same collection? What is the best way to achieve this?
I think the way is to override fetch, where you make the Ajax call to each of the APIs. Store the returned partial sets in a temporary array, and when all 4 are complete, create the collection using this.reset. (You could use JQuery's Deferred I suppose, or just keep an internal count of how many calls have returned.)
Something like this:
var Collection = Backbone.Collection.extend({
fetch: function() {
this.completeCount = 0;
this.errorCount = 0;
this.temp = [];
this.urls = [ 'url1', 'url2', 'url3', 'url4' ];
var self = this;
// make a $.get call for each URL and add
_.each(this.urls, function(url) {
$.get(url, { success: function(data) {
console.log("Got partial collection from " + url);
self.addPartial(data);
// alternatively, just call "self.add(data);" here
}, error: function(response) {
console.log("Oops, the Ajax call failed for some reason... ignoring");
self.completeCount ++;
self.errorCount ++;
} });
});
},
// add a JSON array that contains a subset of the collection
addPartial: function(data) {
this.completeCount ++;
var self = this;
// add each item to temp
_.each(data, function(item) {
self.temp.push(item);
});
// if all have been received, then create the collection
if (this.completeCount == this.urls.length) {
this.reset(this.temp);
}
}
});
Here's a Fiddle where I replaced $.get with a method that just returns dummy data after a short delay.
Response to comment
Adding the responses to the collection as they come in is probably better (it's easier anyway). Here's an updated Fiddle.
I know it's an old question, but if someone reaches in here this information may help.
To preserve the data previosly fetched by a collection, you can change the url and call the method fetch() any times needed with this options:
reset: false,
remove: false,
Like this
yourCollection.fetch({reset: false, remove: false, [other]: [wathever]})
And that's all, no need for overriding the method. (Maybe in 2012 it was necesary, dunno. The fact is that those options work for Backbone 1.1.2 or later). Be aware that im not sure if this will merge or just add the new data even if it's reppeated.
The documentation (http://backbonejs.org/#Collection-fetch) is a little confusing about the 'reset' option, because it says is settled false by default, perhaps that may only apply when the url remains static and single.
I am creating a basic piece of functionality to allow users to send their location to a server which then queries a database and returns locations near to them. I am using the below jQuery .ajax wrapper to POST data to the server. This takes the form of a latlon point which is then used as the basis for a geosearch in MongoDB using nodejs and express on the backend. The results of the search are then intended to be returned to the client and rendered by the createMapListings function.
The /find page is initially rendered through a GET request to the database via mongodb separate from the below code. However subsequent to initial rendering, I then want to return results dependent on the location provided.
The POST method works fine and the location is posted to the server, with the search results being returned as I can print contents out through the console log.
However, I then want to render the results on the client-side. As mentioned, the results of the search render in the console, but when I attempt to pass through to the client, I can render the data itself (in the form of an array of objects) in the #output div, but the createMapListings function does not seem to catch the data.
In fact, the below function appears to be called but prints out over a thousand rows with the data that should be caught described as 'undefined'. I have tried to use res.render and res.redirect, but in the first case, the view renders in the div (which I suppose is expected) and the redirect fails.
The createMapListings function works fine when a simple GET request is made to the server, for example, for all objects in a collection, using ejs template. However, I think the issue here may be a combination of a POST request and then wanting to pass the results back to the AJAX request using the complete callback.
I apologise if the below code is somewhat obtuse. I’m definitely what you would call a beginner. I appreciate the above functionality may not possible so if there is a better way, I would of course be open to it (res.direct perhaps).
Here is the relevant client side script:
$(document).ready(function(){
$("#geolocate").click(function(){
navigator.geolocation.getCurrentPosition(geolocate, function(){
});
});
});
function geolocate(pos){
var latlonpt = [];
var x = pos.coords.latitude;
var y = pos.coords.longitude;
latlonpt.push(x);
latlonpt.push(y);
var obj = {
userlocation: latitudelongitudept
};
$.ajax({
url: "/find",
type: "POST",
contentType: "application/json",
processData: false,
data: JSON.stringify(obj),
complete: function (data) {
$('#output').html(data.responseText);
$('#infooutput').children().remove();
createMapListings(data.responseText);
}
});
};
function createMapListings(maps) {
for (var i = 0; i < maps.length; i++) {
var url = maps[i]._id;
var fullurl = "<a href='/show?id=" + url + "'>Route</a></div>";
var title = "<div>" + maps[i].title + " - " + fullurl +"";
$('#infooutput').append(title);
};
};
</script>
Here is the relevant route used in a basic express app to handle the post request made by the above .ajax wrapper.
exports.findbylocation = function(req, res) {
console.log(req.body.userlocation);
var userlocation = req.body.userlocation;
Map.ensureIndexes;
Map.find({loc :{ $near : userlocation }}, function(err, maps) {
if (err) {
console.log(err)
}
else {
var jmaps = JSON.stringify(maps);
console.log(jmaps);
res.send(jmaps);
}
});
};
By convention, the data variable name in an $.ajax callback signature refers to the parsed HTTP response body. Since your callback is on complete, we're actually passed the XMLHttpRequest used, by convention called xhr. You rightly grab the responseText property, but this needs parsing to be useful. So long as we take care over our Content-Type's and don't explicitly disable processData, jQuery will do the encoding/unencoding for us - we just deal with objects. This is a good thing, since the transport format isn't usually of any particular importance to the application logic. If we use res.json(maps) in place of res.send(jmaps), we can write our call more simply:
$.ajax({
url: '/find',
type: 'POST',
data: obj,
success: function(data) {},
error: function(xhr, text, err) {}
});
Here data is a Javascript object already parsed and ready to use. We also use a default application/x-www-form-urlencoded request rather than explicitly setting a contentType. This is the same as far as express is concerned: it will just be parsed by urlencoded instead of json.
Assuming you solved your client-sie problem.
As you are using express there is no need for JSON.stringfy,
you can use res.json(maps).