I'm trying to use alter the xhr object on an ajax request. I'm doing this on a fetch call for a collection. But when I alter the xhr I get no data. The purpose of this is to show the loaders percentage but the xhr isn't even working when I return the new xhr object. I did checkout the xhr that is returned and the url points to /admin/categories
require(['views/categories', 'models/categories', 'helpers/helper'], function(CategoriesView, model, helper) {
var categories = new model.CategoriesCollection;
categories.fetch({ url: "/admin/categories/getcategories", xhr: helper.xhr('#main-loader') }).then(function(response) {
console.log(response);
});
});
and here is my helper file
define(['helpers/helper', 'require'], function(Helper, require) {
'use strict';
var $ = require('jquery');
var Backbone = require('backbone');
var xhr = function(loaderId) {
var _xhr = Backbone.$.ajaxSettings.xhr();
_xhr.addEventListener("progress", function(e){
if (e.lengthComputable) {
console.log(e);
}
}, false);
return _xhr;
}
return {
xhr: xhr
}
});
An easy way to pass options to the xhr object (XMLHttpRequest object) is to use the xhrFields option of the jQuery ajax function. Since Backbone.sync uses jQuery.ajax by default in the background, any options passed to a Backbone syncing function is then passed as the ajax options.
Simple one-off solution
The simplest example of checking progress:
myCollection.fetch({
url: root + '/photos/',
xhrFields: {
onprogress: function() {
console.log("options onprogress");
}
}
});
Permanent solution
But a more convinient way would be to override the global Backbone.sync function to add our own progress callback option and a custom progress event.
Overriding Backbone.sync
Warning: don't override Backbone core if you're writing a library or code that will be shared.
Backbone.sync = (function(syncFn) {
return function(method, model, options) {
options = options || {};
var context = options.context,
progress = options.progress,
xhrFields = options.xhrFields || {},
onprogress = xhrFields.onprogress;
xhrFields.onprogress = function(e) {
var params = [model, e.loaded, _.extend({}, options, { event: e })];
if (progress) progress.apply(context, params)
if (onprogress) onprogress.apply(this, arguments);
model.trigger(['progress'].concat(params));
};
options.xhrFields = xhrFields;
return syncFn.apply(this, arguments);
};
})(Backbone.sync);
How to use
It's really straight-forward to use:
var myCollection = new Backbone.Collection(),
root = 'https://jsonplaceholder.typicode.com';
It provides a custom progress event.
myCollection.listenTo(myCollection, 'progess', function(collection, value, options) {
console.log("collection progress event");
});
It also provides a custom progress callback that can be passed to any Backbone functions that calls Backbone.sync in the background, like fetch, save, destroy. Also, passing xhrFields still works as expected.
myCollection.fetch({
url: root + '/photos/',
success: function() {
console.log(myCollection.models);
},
// custom options callback
progress: function(collection, value, options) {
console.log("collection onprogress callback");
},
// this still works
xhrFields: {
onprogress: function() {
console.log("options onprogress");
}
}
});
You receive the collection or model, the loaded data count, and the options object, which contains all the options of the sync, in addition to the native progress event (options.event).
Note that it's not that useful as the total doesn't always work. As an example, in Chrome the total is always zero, but in firefox, the total is correct. You should check the lengthComputable property.
Related
I want to make a convenience method for my Ajax calls as it is used extensively in the project.
As of now a typical call in the project looks like this.
$.post(
"url",
{
param1: value1,
param2: value2
},
function (data) {}
);
This call is repeated multiple times in the project with different parameters so I would like to be able to create a function to which I can pass the parameters and it will handle the entire Ajax call without me having to write the code every time.
Expected Output:
var data= {firstName:"John", lastName:"Doe", age:46};
do_ajax_request(data);
The function do_ajax_request in turn contains the actual Ajax code which makes the actual request and handles the result.
If possible I would also like for it to return a callback in case I need to perform any extra operations, would a promise work for that?
This would be a global function so I can access it from any JavaScript file.
So many complicated answers for something jQuery supports out of the box. Turning my comment to an answer.
You are basically just coding a wrapper for a wrapper so you do no have to recode some basic lines. No harm in that since it is easy to make the change in one place vs many.
So defined your function and just return the Ajax object that jQuery has. You can than use the done, fail, always methods.
function do_ajax_request (data) {
return $.post("url", data);
}
do_ajax_request({"foo":"bar"})
.done( function(){})
.fail(function(){})
do_ajax_request({"foo":"bar"})
.done( function(){})
.fail(function(){})
If you want to have common code inside, you can do that too, basic idea for an error handler...
function do_ajax_request (data) {
var xhr = $.post("url", data);
xhr.fail(function () {
console.log(arguments)
});
return xhr;
}
I have written several jQuery plug-ins for use in my projects, and have brought along my ajax call method in nearly everyone. Here is a snippet of it from one of my projects. Enjoy!
Method Signature:
obj = An object you want to pass to the ajax call in the data parameter. Pass null if not needed.
method = ajax methods: POST, GET, PUT, DELETE, etc. Default is GET.
endPoint = Url to call.
returnType = html, json, text, etc.
success = callback method when the call is successful.
beforesend = method to call before the send. This is useful when you need to set headers before a call.
failure = callback method when the call is unsuccessul.
var _api = {
call: function (obj, method, endPoint, returnType, success, beforesend, failure) {
obj = obj === null || undefined ? {} : obj;
$.ajax({
method: method || 'GET',
data: !$.isEmptyObject(obj) ? JSON.stringify(obj) : null,
contentType: function () {
switch (returnType) {
case 'json':
return 'application/json';
case 'text':
return 'text/plain';
case 'buffer':
return 'arraybuffer';
case 'html':
default:
return 'text/html';
}
}(returnType === 'json' ? 'application/json; charset=utf-8' : ''),
url: endPoint,
dataType: returnType,
beforeSend: function (xhr, obj) {
if (beforesend) {
beforesend(xhr, obj);
} else {
_api.showLoader();
}
}
}).done(function (data, textStatus, xhr) {
if (success) success(data)
}).fail(function (data, textStatus, xhr) {
if (failure) failure()
}).always(function () {
// Implement code here that you want to run whenever the call is complete regardless of success or failure.
});
}
}
You could create a prototype to with a constructor to handle the input - make the request and handle the response:
ajax.requests = function ( data ) {
this.data = data;
return this.doRequest();
};
ajax.requests.prototype = {
doRequest : function () {
var _this = this;
$.ajax({
data: _this.data
}).done(function(data) {
Handle response and return!
});
}
};
// USAGE
var response = new ajax.requests( yourData );
By returning the $.post, you can use a callback like .done(), chain them together with .then(), etc.
function do_ajax_request(data) {
return $.post( ... ); //RETURN the object
}
var myData = { ... };
do_ajax_request(myData).done(function(result) {
console.log("AJAX complete: " + result);
});
Just another take on this that maybe you hadn't considered. Rather than trying to wrap what is essentially already a wrapper, consider encapsulating your common functionality, like handling errors and dealing with results and using this when executing an ajax request with the existing jQuery ajax wrapper(s)
function handleError(e){
// your common error handling
}
function handleResult(result){
// your common result handling
}
// then every time you execute a request, use your shared functionality
$.post(url, data)
.fail(handleError)
.done(handleResult);
Using code below, you'd need to import config object or declare on top of the functions.
I made two versions for POST and GET respectively
function getJSON(param, absoluteRestUrl = '') {
if (!absoluteRestUrl) {
absoluteRestUrl = config.adminRestEndpoint; // defaultUrl
}
return new Promise(async (resolve, reject) => {
let res = null;
res = await $.getJSON(absoluteRestUrl, param);
resolve(JSON.parse(JSON.stringify(res)));
});
}
function postJSON(param, absoluteRestUrl = '') {
if (!absoluteRestUrl) {
absoluteRestUrl = config.adminRestEndpoint; // defaultUrl
}
return new Promise(async (resolve, reject) => {
let res = null;
res = await $.post(absoluteRestUrl, param, null, 'json');
resolve(JSON.parse(JSON.stringify(res)));
});
}
I have been trying for a few days now to convert my tracking pixel JS functionality to use a 204 "no_content" response.
I can easily get this working, but I need to be able to fire a callback function afterwards.
The following doesn't seem to ever get fired once the 204 is returned.
beacon: function (opts) {
var beacon = new Image();
opts = $.extend(true, {}, {
url: pum_vars.ajaxurl || null,
data: {
action: 'pum_analytics',
_cache: (+(new Date()))
},
error: function () {
console.log('error');
},
success: function () {
console.log('success');
}
}, opts);
// Create a beacon if a url is provided
if (opts.url) {
// Attach the event handlers to the image object
if (beacon.onerror) {
beacon.onerror = opts.error;
}
if (beacon.onload) {
beacon.onload = opts.success;
}
$(beacon).on('load', function( response, status, xhr ){
alert(status);
});
// Attach the src for the script call
beacon.src = opts.url + '?' + $.param(opts.data);
}
}
The tracking is logged properly, but no alert or console log messages. Is this possible or am I just wasting my time?
Edit ------
Based on the solution below here is the final version (this assumes that both an error & success will use the same callback.
beacon: function (opts) {
var beacon = new Image();
opts = $.extend(true, {}, {
url: pum_vars.ajaxurl || null,
data: {
action: 'pum_analytics',
_cache: (+(new Date()))
},
callback: function () {
console.log('tracked');
}
}, opts);
// Create a beacon if a url is provided
if (opts.url) {
// Attach the event handlers to the image object
$(beacon).on('error success done', opts.callback);
// Attach the src for the script call
beacon.src = opts.url + '?' + $.param(opts.data);
}
}
You aren't attaching any callbacks to image. Your test if (beacon.onerror) results in false because beacon.onerror is null.
You should use if( "onerror" in beacon ) to tests whether beacon has onerror property.
But why dont you just use jquery's method on?
$(beacon).on("error", function() {
alert("Jquery error");
});
$(beacon).on("done", function() {
alert("Jquery done");
});
How can I load different attributes for a model using different URLs?
E.g. I have 3 different and independent URLs that will return some attributes and I want to add all of those to a single model, suppose that I have the promise returned by all of them like this:
var model = //...
$.when(nameAttributes, addressAttributes, metaAttributes).then(
function(nameData, addressData, metaData) {
return _.extend({}, nameData, addressData, metaData);
})
.done(function(allData) {
model.set(allData);
doStuffWith(model);
});
Is there a way to turn that into this:
model.fetch().done(function(){ doStuffWith(model); });
Well.. Ok, this should do what you want. I STRONGLY suggest you do NOT take this route. Keep the data separately in individual Model()s that way you can update it and pull it when ever you need to. If you take this approach saving data will be a disaster.
http://jsfiddle.net/kjhvwxg4/
PS. I tried to not add any more code then i had to, keep in mind this will probably not handle error handling very well since xhr will never throw an error, you need to catch it yourself -- i didnt want to spend the time coding that since this is just a proof of concept.
var Model1 = Backbone.Model.extend({
fetch: function(options) {
options = _.extend({
parse: true
}, options);
var model = this;
var success = options.success;
var error = options.error;
options.success = function(resp) {
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
if (!model.set(serverAttrs, options)) return false;
if (success) success.call(options.context, model, resp, options);
model.trigger('sync', model, resp, options);
};
options.error = function(resp) {
if (error) error.call(options.context, model, resp, options);
model.trigger('error', model, resp, options);
};
// custom code starts here
var call1 = $.getJSON('http://jsonplaceholder.typicode.com/posts/1');
var call2 = $.getJSON('http://jsonplaceholder.typicode.com/comments/2');
var call3 = $.getJSON('http://jsonplaceholder.typicode.com/albums/3');
var xhr = $.when(call1, call2, call3);
// mimics the same triggers the normal backbone does
model.trigger('request', model, xhr, options);
xhr.done(function(one, two, three){
var resp = _.extend(one[0], _.extend(two[0], _.extend(three[0], {})));
options.success(resp);
});
// we still need to send back an event handler
return xhr;
}
});
var model = new Model1();
model.fetch();
model.on('sync', function(model) {
alert(JSON.stringify(model.toJSON()));
});
I'm looking for a way to intercept the value returned from a server when I fetch a backbone model (a collection, strictly speaking) from the server, then modify it before continuing. I would think that I could do something like this
SessionController.prototype._initPages = function() {
return App.pages.fetch({
reset: true,
success: function(model, response, options) {
//modify the contents of response
}
};
And my modifications would be reflected in the model that's used to initialize the view.
However I was looking at the backbone source and I think I may have misunderstood something.
fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === void 0) options.parse = true;
var success = options.success;
var collection = this;
options.success = function(resp) {
var method = options.reset ? 'reset' : 'set';
collection[method](resp, options); //this line updates the model
if (success) success(collection, resp, options); // my success callback
collection.trigger('sync', collection, resp, options);
};
wrapError(this, options);
return this.sync('read', this, options);
}
For my needs, it seems the two commented lines need to be switched, though I assume I'm just misunderstanding how to use this feature.
How can I modify the server response before it becomes my model?
I think you could just override the parse function to modify your data as needed
http://backbonejs.org/#Model-parse
In every authenticated requests (GET, POST, etc) of my Backbone/Marionette application I must to attach an accessToken.
I store this accessToken and expireDate in the localStorage.
To check if the accessToken is expired I call this method: user.checkToken().
If is expired, the method renew the accessToken with a POST request to my backend.
Where should I put this check? I mean, in which part of the application?
Should I rewrite my on Backbone.sync method or use ajax.setup "beforeSend" ?
Thanks in advance for your advices/idea.
Backbone uses jQuery (see the note for a solution that may work with Zepto) for ajax requests, so you can use (as suggested by Edward) jQuery.ajaxPrefilter.
I did a little test for this task, let me know if there's any problem:
function tokenIsExpired() {
return true;
}
function createPromiseFunction(method, jqXHRsource, jqXHR) {
return function() {
jqXHRsource[method] = function(f) {
if (f) {
jqXHR[method] = function() {
f.apply(this, arguments);
};
}
return this;
};
}
}
function updateToken() {
return $.ajax({
url: '',
method: 'GET',
data: {some:'data', here:'yes'},
success: function() {
// update the token sir
console.log('token call done')
},
skipTokenCheck: true // required
});
}
$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
/*
* check if token is expired every time a new ajax request is made
* if it is expired, aborts the current requests, updated the token
* and eventually does the original request again.
*/
if (!options.skipTokenCheck && tokenIsExpired()) {
// at this point no callback should have be added to the promise object
var methodsNames = [
'done',
'always',
'fail',
'progress',
'then'
];
var methods = {};
// copy the callbacks when they're added to the old request
for (var i = 0; i < methodsNames.length; i++) {
var name = methodsNames[i];
createPromiseFunction(name, jqXHR, methods)();
};
jqXHR.abort();
// TODO: error checks
updateToken().done(function() {
console.log('done');
var newReq = $.ajax($.extend(originalOptions, {skipTokenCheck: true}));
for (var i = 0; i < methodsNames.length; i++) {
var name = methodsNames[i];
var f = methods[name];
if (f) {
newReq[name](f);
}
};
});
}
});
var p = $.get('.');
p.done(function() { console.log(arguments); }).fail(function() {
console.log('fail');
});
Looks like that ajaxPrefilter doesn't work with Zepto. Alternatively you can use the ajaxBeforeSend event.
Returning false in the beforeSend function will cancel the request.
Should be easy to adapt the code I posted above.
Overwrite your model's sync() function and do whatever you need to do.. Something like:
var MyModel = Backbone.Model.extend({
sync: function() {
// Put your code here
Backbone.Model.prototype.sync.apply(this, arguments);
}
});
Edit #1:
Not sure where you get user (as well as other variables) from but here it is:
var MyModel = Backbone.Model.extend({
sync: function() {
user.checkToken().done(_.bind(function(){
Backbone.Model.prototype.sync.apply(this, [ method, model, options ]);
});
}, this);
});