jQuery: $.when().done() doesn't wait / never fires [duplicate] - javascript

I want to first render a view with Backbone.js that displays an Article pulled from the server. I then want to mark this as "seen" and return the count of unseen messages to the router as it needs to be made available to other views.
So in my Router, I have:
getArticle: function (id) {
require(["app/models/article", "app/views/Article"], function (models, Article) {
var article = new models.Article({id: id});
article.fetch({
success: function (data) {
var articleView = new Article({model: data, message_count:that.message_count});
slider.slidePage(articleView.$el);
$.when(articleView.saveView()).done(function(data){
console.log('in when and data is ');
console.log(data);
});
},
error: function(){
console.log('failed to fecth artcie');
}
});
});
},
saveView() in the Article view is:
saveView: function(){
var viewDetails = [];
viewDetails.device_id = this.options.device_id;
viewDetails.article_id = this.model.id;
viewDetails.project_title = project_title;
var article_view = new models.ArticleView();
article_view.save(viewDetails,
{
success: function(data) {
var count = data.get('count');
console.log('in saveView() success and count is ');
console.log(count);
return count;
},
error: function(model, xhr, options){
console.log(xhr.responseText);
},
});
},
This hits a REST API, records the viewing of the article and then returns a count of unseen Articles. This results in a console output of:
in when and data is router.js:286 undefined router.js:287 in
saveView() success and count is Article.js:45 4
So, somehow, $.when is not working as it's not waiting for the Ajax request to send before executing the .done script. Any ideas?

You need to return a jQuery Deferred object for $.when to work properly:
return article_view.save(viewDetails,
{
success: function(data) {
var count = data.get('count');
console.log('in saveView() success and count is ');
console.log(count);
return count;
},
error: function(model, xhr, options){
console.log(xhr.responseText);
},
});
Backbone's save() method returns a jqXHR object which behaves the same way as a Deferred object in this case. Simply chain the return call as above. This should get $.when() to wait for the request to finish.

Related

Symfony : Add data from Ajax call to js array

I have an Ajax request that fetch data in database.
These data may vary in function of the action that calls the ajax request.
Each time the Ajax request is called, I want some ot these datas to be pushed in a javascript array declared outside of the Ajax function.
The problem is, each time Ajax is called, my function manage to put the wanted data in the array but erases the previous data.
Here is the script :
<script>
let myArray = [];
function fetchWeeksForViewportWidth(startDate) {
//AJAX
$.ajax({
method: "GET",
url: "{{path('days_json')}}",
data: {
//some data
},
success: function (data) {
let trParents = document.getElementsByClassName('project-assignment');
$.each(trParents, function(key, parent) {
let assId = parent.dataset.assignmentId;
myArray[assId] = [];
$.each(data['days'], function(key, value) {
myArray[assId][value.code] = 0;
//some other treatment
if(value.code in data['game']) {
myArray[assId][value.code] = data['game'][value.code];
});
});
});
},
error: function() {
//handle error
console.log("error")
}
});
}
$(document).ready( function () {
function displayArray(){
console.log(myArray);
setTimeout(displayArray, 5000);
}
displayArray();
});
</script>
Any idea why new data don't add to the ones from previous ajax calls but keep replacing them ?
Instead of assignment
myArray[assId][value.code] = 0;
try the push() function of the array
myArray[assId].push(0);
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push

Javascript functions order

I'm making a jquery library to use an application with the json rpc protocol but I'm stuck with a little problem.
This is the fiddle that shows the code (obviously it can't work): https://jsfiddle.net/L9qkkxLe/3/.
;(function($) {
$.lib = function(options) {
var outputHTML = [],
plugin = this;
var APIcall = function(api_method, api_params) {
request = {};
request.id = Math.floor((Math.random() * 100) + 1);
request.jsonrpc = '2.0';
request.method = api_method;
request.params = (api_params) ? api_params : [];
$.ajax({
type: "POST",
url: "http://localhost:8898/jsonrpc",
data: JSON.stringify(request),
timeout: 3000,
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', window.btoa(options.username + ":" + options.password));
},
success: function(data) {
handleData(data, api_method);
},
error: function(jqXHR, textStatus, errorThrown) {
log("Connection time out: can't reach it. Try changing the settings.");
isConnected = "false";
},
dataType: "json"
});
}
var handleData = function(data, method) {
if (method == "getgenres") {
outputHTML = data.result.genres; //I need data.result.genres to return in getgenres function
}
}
var log = function(msg) {
if (options.debug == true) console.log(msg);
}
plugin.getgenres = function() {
APIcall("getgenres");
return outputHTML; //This is sadly empty.
}
};
}(jQuery));
var init = new $.lib();
console.log(init.getgenres());
I need that the getgenres function returns data.result.genres but actually it returns an empty array because getgenres is called for first and only after the handleData function gives to outputHTML the value that I need.
You are performing an asynchronous AJAX request, which means you can't actually get back the data immediately. There are two ways to solve your issue: making it synchronous (easy but ill advised) or using a callback (a little bit more complex but generally accepted):
In your getgenres function, you could accept one more parameter: callback
plugin.getgenres = function(callback) {
/* Dont forget APIcall already took two parameters in, so callback has to be the third in line! */
APIcall("getgenres", false, callback);
}
Now modify your APIcall function to accept your callback:
var APIcall = function(api_method, api_params, callback) { ... }
And call the callback from the successful completion call - instead of having a handler method in between wrapped in a function, you can simply pass the anonymous function. So instead of success: function(data){ handle(data); }, just use:
success: callback
The anonymous function that we will pass to it will receive as its first parameter the data you were passing to the handler. Now you can do the following:
var myGenres = [];
var init = new $.lib();
init.getgenres(function(data){
/* Now your data is actually loaded and available here. */
myGenres = data;
console.log(myGenres);
});
I would like to point out that there are many better ways to handle this, including turning this into a Constructor (More here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) instead of the strange amalgamation of functions and variables you have now, as well as using JS Promises (here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to make this easier. But the basic gist should be here.
Update (potential implementation)
Because I mentioned that this could be done in a way that I think is clearer to read and use. I do not know all use cases for this, but from the provided example I would change the code to something looking like the following. Please also note I am not an expert on jQuery plugins, so I am avoiding plugging into jQuery and just using it as an easy AJAX call.
function getAjax(){
if(!window.jQuery || !window.$) throw("jQuery is required for this plugin to function.");
this.data = [];
this.request = '';
return this;
}
getAjax.prototype = {
createRequest : function(method, parameters){
this.request = {};
this.request.id = Math.floor((Math.random() * 100) + 1);
this.request.jsonrpc = '2.0';
this.request.method = method;
this.request.params = parameters || [];
return this;
},
callRequest : function(options, callback, error){
var self = this;
// We could also `throw` here as you need to set up a request before calling it.
if(!this.request) return this;
else {
$.ajax({
// We will allow passing a type and url using the options and use sensible defaults.
type: options.type || "POST",
url: options.url || "http://localhost:8898/jsonrpc",
// Here we use the request we made earlier.
data: JSON.stringify(this.request),
timeout: options.timeout || 3000,
beforeSend: function(xhr){
xhr.setRequestHeader(
'Authorization',
window.btoa( options.username + ":" + options.password)
);
},
// We will also store all the made request in this object. That could be useful later, but it's not necessary. After that, we call the callback.
success: function(data){
var store = {request:self.request, data: data};
self.data.push(store);
// Call the callback and bind `this` to it so we can use `this` to access potentially pther data. Also, pass the results as arguments.
callback(data, self.request.id).bind(self);
},
// Error function!
error: error,
dataType: options.dataType || "json"
});
}
return this;
}
}
// Example use
new getAjax().createRequest('getgenres').callRequest({
username: 'myusername',
password: 'mypassword'
}, function(data, id){
// Success! Do with your data what you want.
console.log(data);
}, function(e){
// Error!
alert('An error has occurred: ' + e.statusText);
console.log(e);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
What I do in those occasions is this:
You are supplying a method. So put a reference to the a callback function. In this case plugin.getGenresFinalize. When handleData is called it will fire that callBack function. This way you can pass multiple methods to the api call for different types of data.
plugin.getgenres = function() {
APIcall(this.getgenresFinalize);
}
plugin.getgenresFinalize = function(data) {
console.log(data);
}
var handleData = function(data, method) {
method(data);
}

Fetch data on different server with backbone.js

I can't see what the problem with this is.
I'm trying to fetch data on a different server, the url within the collection is correct but returns a 404 error. When trying to fetch the data the error function is triggered and no data is returned. The php script that returns the data works and gives me the output as expected. Can anyone see what's wrong with my code?
Thanks in advance :)
// function within view to fetch data
fetchData: function()
{
console.log('fetchData')
// Assign scope.
var $this = this;
// Set the colletion.
this.collection = new BookmarkCollection();
console.log(this.collection)
// Call server to get data.
this.collection.fetch(
{
cache: false,
success: function(collection, response)
{
console.log(collection)
// If there are no errors.
if (!collection.errors)
{
// Set JSON of collection to global variable.
app.userBookmarks = collection.toJSON();
// $this.loaded=true;
// Call function to render view.
$this.render();
}
// END if.
},
error: function(collection, response)
{
console.log('fetchData error')
console.log(collection)
console.log(response)
}
});
},
// end of function
Model and collection:
BookmarkModel = Backbone.Model.extend(
{
idAttribute: 'lineNavRef'
});
BookmarkCollection = Backbone.Collection.extend(
{
model: BookmarkModel,
//urlRoot: 'data/getBookmarks.php',
urlRoot: 'http://' + app.Domain + ':' + app.serverPort + '/data/getBookmarks.php?fromCrm=true',
url: function()
{
console.log(this.urlRoot)
return this.urlRoot;
},
parse: function (data, xhr)
{
console.log(data)
// Default error status.
this.errors = false;
if (data.responseCode < 1 || data.errorCode < 1)
{
this.errors = true;
}
return data;
}
});
You can make the requests using JSONP (read about here: http://en.wikipedia.org/wiki/JSONP).
To achive it using Backbone, simply do this:
var collection = new MyCollection();
collection.fetch({ dataType: 'jsonp' });
You backend must ready to do this. The server will receive a callback name generated by jQuery, passed on the query string. So the server must respond:
name_of_callback_fuction_generated({ YOUR DATA HERE });
Hope I've helped.
This is a cross domain request - no can do. Will need to use a local script and use curl to access the one on the other domain.

How to deal with a group of deferred's when one of them fails

I have a series of ajax calls that fill columns on a page.
var doneDefers = function(defer) {
// leftColDefer is another deferred that sets some header info
$.when(defer, leftColDefer).done(function(req1, req2){
var data = req1[0],
head = req2[0];
// spit this data out to elements on the page
});
};
for(i=0;i<window.ids.length;i++){
defer[i] = $.ajax({
url: 'api/get_runs_stats.php',
type: 'GET',
dataType: 'json',
data: {
run_id: window.ids[i]
}
});
doneDefers(defer[i]);
}
This works fine. If an ajax call fails, nothing is spit out and all is right with the world.
Now I want to do some calculations based on all the data that got spit out.
$.when.apply(null, defer)
.done(function() {
var args = Array.prototype.slice.call(arguments);
calcDeltas();
})
.fail(function() {
var args = Array.prototype.slice.call(arguments);
console.log('in list fail');
});
The done function works fine none of the ajax calls fail. If one of them fail, I go into the fail function and I don't have access to any of the return data from the other runs. The arguments array only has the failed call's data.
I would like to do my calculations on the data sets that passed. How can I get to the data from the good calls when one of them fails?
I'm not sure this is the simplest solution but it stands a chance of working.
var ajax_always_promises = [],//to be populated with promises that (barring uncaught error) are guaranteed to be resolved.
data_arr = [],//an array to be (sparsely) populated with asynchronously delivered json data.
error_arr = [];//an array to be (sparsely) populated with ajax error messages.
$.each(window.ids, function(i, id) {
var dfrd = $.Deferred();
var p = $.ajax({
url: 'api/get_runs_stats.php',
type: 'GET',
dataType: 'json',
data: {
run_id: window.ids[i]
}
}).done(function(json_data) {
data_arr[i] = json_data;//intentionally not `data_arr.push(json_data);`
}).fail(function(jqXHR, textStatus, errorThrown) {
error_arr[i] = textStatus;//intentionally not `error_arr.push(textStatus);`
}).always(dfrd.resolve);
ajax_always_promises[i] = dfrd.promise();
doneDefers(p);
});
$.when.apply(null, ajax_always_promises).done(function() {
//The data in the (sparsely) populated arrays `data_arr` and `error_arr` is available to be used.
var i, id, success_count=0, error_count=0;
for(i=0; i<Math.max(data_arr.length,error_arr.length); i++) {
//Here, the index i corresponds to the original index of window.ids ...
//...that's the advantage of sparsely populating the arrays.
id = window.ids[i];
if(data_arr[i]) {
//Here, do whatever is required with `data_arr[i]`, and `id` if needed.
success_count++;
}
else if(error_arr[i]) {
//Here, do whatever is required with `error_arr[i]`, and `id` if needed.
error_count++;
}
}
console.log("Success:errors: " + success_count + ':' + error_count);
});
Untested - may well need debugging

Backbone View property not being set

I have a view with a property which i want to update its value when i make a fetch request.
define(['underscore','backbone','models/taskCollection'],
function( _,Backbone,TaskCollection) {
var UserTasksView = Backbone.View.extend({
el:"#user-task-list",
cur_task: undefined,
initialize: function() {
this.collection = new TaskCollection;
this.model = this.collection._model;
_.bindAll(this,'render');
this.collection.bind('reset',this.render);
},
view_task: function( event ) {
var el = $(event.currentTarget);
var task_id = el.attr('data-taskid');
var row = el.parents('td').parents('tr.task-row');
row.addClass("active");
el.hide();
el.next('a').show();
var task = this.collection.fetch({
data: {id:task_id},
silent:true,
success:this._task_fetch_success
});
this._show_task_detail();
event.preventDefault();
},
_task_fetch_success: function(response,status,xhr) {
this.cur_task = JSON.stringify(status);
return status;
},
/**
* Displays the details of a task
**/
_show_task_detail: function() {
var main = $('.app-content');
var detail_view = $('.app-extra');
var task_detail_view = $("#task-detail-view");
//Reduce task list view width
main.animate({
"width":"50%"
},2000);
//Display app extra bar
detail_view.show();
//show task detail view
detail_view.children('active-page').hide().removeClass('active-page').addClass('inactive-page');
task_detail_view.show().removeClass('inactive-page').addClass('active-page');
console.log(this.cur_task);
var template = ich.task_detail(this.cur_task)
$('div.task-details').html(template);
}
The ajax request trigger by the fetch is successful and success callback executes, but when i try to log "cur_task" property, it shows up as undefined;
What am i doing wrong
You have a couple problems that start right here:
var task = this.collection.fetch({
data: {id:task_id},
silent:true,
success:this._task_fetch_success
});
and here:
_task_fetch_success: function(response,status,xhr) {
this.cur_task = JSON.stringify(status);
return status;
}
First of all, the success callback is not a jQuery success callback and doesn't receive the usual jQuery parameters; from the fine manual:
The options hash takes success and error callbacks which will be passed (collection, response) as arguments.
so your _task_fetch_success function is called as f(collection, response) not as f(response, status, xhr) as you're expecting; this is why you have to treat the status parameter as JSON: the status is actually the response.
Your next problem is that this isn't what you think it is inside your _task_fetch_success function. Backbone's fetch just calls success as a plain old function:
var success = options.success;
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp, options); // <--------------- Right here
collection.trigger('sync', collection, resp, options);
};
That means that this will be window, not your view. The easiest way to work around this problem is to add _task_fetch_success to your _.bindAll list in initialize:
initialize: function() {
//...
_.bindAll(this, 'render', '_task_fetch_success');
//...
}

Categories