I have JQuery ajax POST operation which saves some data to a database. The operation takes about less than 20 milliseconds. This piece of javascript is embedded inside a .NET Web Forms application (out of my control). This .NET application triggers my POST inside a submit button and redirects to a another page. Now for the following browsers the POST is never executed:
FF on Windows
Safari on MAC
Chrome on MAC
IE works fine and even Chrome on Windows.
So I did some tests and tried to mimic the .NET Web Forms website which I luckily could reproduce by adding 'window.location = "go to another site"' directly after my save action. Here is my (stripped) code:
//BUTTON
$('#someSaveButton').click(function () {
GlobalObject.save();
});
// GLOBAL OBJECT CONTAINING ALL MY ACTIONS (delete, check, save, get)
var GlobalObject = (function () {
return {
save: function () {
...
someMode.save(); //NOTE: Depending on an url parameter different save methods are supported
window.location.href = "http://www.google.com"; //MIMIC-ING REQUEST ABORTING
}
};
})();
//EVENTUALLY THE SAVE:
function save() {
$.ajax({
type: "POST",
cache: false,
url: ...,
data: ...,
contentType: "application/json",
dataType: "json",
success: function (response) {
...
},
error: function (msg) {
...
}
});
}
This above reproduces the same POST-aborting behaviour. NOTE: the browser debugger reaches the $.ajax statement but never returns into 'success' or 'error'.
SOUTION I FINALLY FOUND:
I added 'async : false,' to the ajax call and everything seems to work fine!
Now I want to be sure if this is a correct solution because when I read on the JQuery site:
As of jQuery 1.8, the use of async: false with jqXHR ($.Deferred) is deprecated; you must use the success/error/complete callback options instead of the corresponding methods of the jqXHR object such as jqXHR.done() or the deprecated jqXHR.success().
I get a feeling the 'async: false' is maybe not a good solution. Is my Javascript construction OK? My background is C# and I am still learning/struggling on how to correctly embed/use functions inside javascript class definitions. So maybe my errors are related to a wrong construction of my functions (and the javascript garbage collector is cleaning things up or something).
alternatively, instead of settings async as false, you can do the redirect (window.location.href = "http://www.google.com";) within the success callback of the $.ajax call (and leave async: true):
//BUTTON
$('#someSaveButton').click(function () {
GlobalObject.save();
});
// GLOBAL OBJECT CONTAINING ALL MY ACTIONS (delete, check, save, get)
var GlobalObject = (function () {
return {
save: function () {
// ...
someMode.save(); //NOTE: Depending on an url parameter different save methods are supported
}
};
})();
//EVENTUALLY THE SAVE:
function save() {
$.ajax({
type: "POST",
cache: false,
url: ...,
data: ...,
contentType: "application/json",
dataType: "json",
success: function (response) {
// do stuff
// more stuff
// ... redirect when completed stuff
window.location.href = "http://www.google.com"; //MIMIC-ING REQUEST ABORTING
},
error: function (msg) {
// ...
}
});
}
hope that helps.
Your use of async: false is justified and correct in this instance.
The deprecation note refers to setting callback handlers on the jqXHR object, but you're already supplying them through the options object anyway.
Using async: false in this situation would not be incorrect, since the page is closing anyway and the user wouldn't expect it to be responsive, so the execution blocking goes mostly unnoticed (aside from the fact that 20 milliseconds are near inconceivable).
From the information provided, I don't see a cleaner solution than this.
Related
I'm making a MVC C# Web App, and I began wondering whether you could open other pages that need parameters to function, without actually sending them through the URL, which is unsafe, and can lead to some user messing up another registry of the database.
My issue is though, I've never done such a thing, and I cannot find any example of code that does such a thing. The project is run with C# and JS, and the main things I've tried include:
-Doing so with Ajax:
First of all I have a button that calls for a function:
Link Text|
function openHorario(id, id_schedule, id_tool) {
alert(oid, id_schedule, id_tool);
$.ajax({
type: 'POST',
url: '/Schedules/actionEditStuff',
data: {
id: id,
id_schedule: id_schedule,
id_tool: id_tool
},
async: 'false',
success: function (data) {
//???
}
});
}
I know there's a way to go to a new page with the success Ajax return, but... That also requires for you to send the parameters through URL.
Obviously, that didn't work, because what the action does in the controller is to return a view, not a page. So... I realized that my idea was not very smart, and moved onto somewhere else: Link, but those always end up having to send the parameters visibly through URL.
Is there any way at all to do this in a proper, clean manner?
Thank you!
#Layan - just to illustrate my comment above, - you could implement something along these lines:
your client side invokes via ajax
...
var data = {
id: id,
id_schedule: id_schedule,
id_tool: id_tool
};
$.ajax({
url: '/Schedules/actionEditStuff',
type: "POST",
data: data,
contentType: 'application/json; charset=utf-8',
success: function (view) {
//load returned data into div? or popup?
}
, error: function (xhr, status, error) {
...
}
});
...
and your controller action
public ActionResult actionEditStuff(....parameters...)
{
...
...do your magic ...
return PartialView("~/Views/_PartialViewThatShowsSomething.cshtml", ...pass model...);
}
I have this code for loading a page in another :
$(".container").load(url, function () {
// do stuff
});
When this code executes, all of GET requests has _={timestamp} at the end of their links, like this : http://localhost:2208/Scripts/jquery-1.9.1.min.js?_=1399788658418
I want to enable cache for my requests, also i need this only for this line.
I searching for some solutions and i guess using ajaxSetup do this, but i need to enable only for 1 request in my website.
What's your idea?
You need to use $.ajax() if you want to use extra settings.
$.ajax({
url: url,
dataType: 'html',
success: function (data) {
$(".container").html(data);
// do stuff
},
cache: false
});
I have the following code. The function is called multiple times depending on the user checking or unchecking checkboxes.
This works in all browsers except IE10/11. In IE, the ajax call is only made once for a particular ID. Subsequent calls are not actually sent to the server, but appear to be fetched from the cache.
In F12 developer tools, the call appears to be being made to the server, but Fiddler shows that it is not actually happening.
F12 also shows a 304 response to the call.
How do I ensure that the call is always made to the server?
function updateReportTypes(event) {
var value = event.currentTarget.value;
if (event.currentTarget.checked) {
$.ajax({
url: "/PropertySearch/Order/AddReportType?id=" + value,
dataType: 'html',
success: function (data) {
$('#reportTypes').html(data);
hideProgress();
}
});
}
else {
$.ajax({
url: "/PropertySearch/Order/RemoveReportType?id=" + value,
dataType: 'html',
success: function (data) {
$('#reportTypes').html(data);
hideProgress();
}
});
}
}
Simple set the:
cache: false
argument to $.ajax(). When you do that, jQuery will automatically add a unique paramter onto the URL which prevents any caching of the request.
Using that option would look like this:
$.ajax({
url: "/PropertySearch/Order/AddReportType?id=" + value,
dataType: 'html',
cache: false,
success: function (data) {
$('#reportTypes').html(data);
hideProgress();
}
});
jQuery doc on this option: http://api.jquery.com/jquery.ajax/
I'm not familiar with this specific issue, but if all else fails, you should be able to add a dynamic, cache-busting value, such as a timestamp, to make your URL unique:
url: "/PropertySearch/Order/RemoveReportType?id=" + value + "&" + Date.now().toString()
In a JS file I have this:
$.ajaxSetup({
type: 'POST',
contentType: "application/json; charset=utf-8",
dataType: "json",
data: {},
error: function (jqXHR, textStatus, errorThrown) {
debugger;
}
});
Then further on in the file I instantiate an object that has an AJAX call in it's constructor to fill out some values.
function RequestViewModel() {
var self = this;
(...)
// Initalization Methods
$.ajax({
url:ajaxAddress + 'LoadStates',
success: function (data) {
debugger;
}
});
}
var model = new RequestViewModel();
However, when the ajax call is made in the code, 'xml' is being used as the dataType instead of JSON. This is causing my web service call to break and I always get sent to the error callback of the AJAX call. If I move the settings inside the actual AjAX call, the call works and data is returned from the server. For some reason, the global setting isn't being honored.
My question is why isn't this working? I've used this same technique several other times without this problem.
I'm using jQuery version 1.7.1.
UPDATE
It seems like the problem is on line 7517 of the jQuery file. It is doing an IF statement that is being evaulated to false and is skipping over setting the correct Content Type in the request header.
Try putting your .ajaxSetup inside a document ready wrapper.(NOT likely the cause though)
Try using jQuery.ajaxSetup instead of $.ajaxSetup
It is recommended that global event handlers not be in the ajaxSetup. move error: to $.ajaxError( instead:
jQuery.ajaxError( function (e, jqxhr, settings, exception) {
alert(settings.url + " Failed");
});
Example if you have a div with a log class (puts some text in if any error occurs:
$("div.log").ajaxError(function() {
$(this).text( "Triggered ajaxError handler." );
});
NOTE: when you refactor, make sure to remove the LAST comma.
Also, if you are using the latest version of jQuery (1.7.1 at the moment) you can simplify:
contentType: "application/json; charset=utf-8",
to
contentType: "application/json",
EDIT: quick, dirty global handler:
$(document).ajaxError(function(e, xhr, settings, exception) {
alert('error in: ' + settings.url + ' \\n'+'error:\\n' + exception);
});
EDIT2: SOME resources also put an empty data set sent as: (with quotes)
data: "{}",
Where is .ajaxSetup() being called? Are you using any other plugins? It's possible some other library is misbehaving and overwriting your options.
I am trying to use Jasmine to write some BDD specs for basic jQuery AJAX requests. I am currently using Jasmine in standalone mode (i.e. through SpecRunner.html). I have configured SpecRunner to load jquery and other .js files. Any ideas why the following doesn't work? has_returned does not become true, even thought the "yuppi!" alert shows up fine.
describe("A jQuery ajax request should be able to fetch...", function() {
it("an XML file from the filesystem", function() {
$.ajax_get_xml_request = { has_returned : false };
// initiating the AJAX request
$.ajax({ type: "GET", url: "addressbook_files/addressbookxml.xml", dataType: "xml",
success: function(xml) { alert("yuppi!"); $.ajax_get_xml_request.has_returned = true; } });
// waiting for has_returned to become true (timeout: 3s)
waitsFor(function() { $.ajax_get_xml_request.has_returned; }, "the JQuery AJAX GET to return", 3000);
// TODO: other tests might check size of XML file, whether it is valid XML
expect($.ajax_get_xml_request.has_returned).toEqual(true);
});
});
How do I test that the callback has been called? Any pointers to blogs/material related to testing async jQuery with Jasmine will be greatly appreciated.
I guess there are two types of tests you can do:
Unit tests that fake the AJAX request (using Jasmine's spies), enabling you to test all of your code that runs just before the AJAX request, and just afterwards. You can even use Jasmine to fake a response from the server. These tests would be faster - and they would not need to handle asynchronous behaviour - since there isn't any real AJAX going on.
Integration tests that perform real AJAX requests. These would need to be asynchronous.
Jasmine can help you do both kinds of tests.
Here is a sample of how you can fake the AJAX request, and then write a unit test to verify that the faked AJAX request was going to the correct URL:
it("should make an AJAX request to the correct URL", function() {
spyOn($, "ajax");
getProduct(123);
expect($.ajax.mostRecentCall.args[0]["url"]).toEqual("/products/123");
});
function getProduct(id) {
$.ajax({
type: "GET",
url: "/products/" + id,
contentType: "application/json; charset=utf-8",
dataType: "json"
});
}
For Jasmine 2.0 use instead:
expect($.ajax.calls.mostRecent().args[0]["url"]).toEqual("/products/123");
as noted in this answer
Here is a similar unit test that verifies your callback was executed, upon an AJAX request completing successfully:
it("should execute the callback function on success", function () {
spyOn($, "ajax").andCallFake(function(options) {
options.success();
});
var callback = jasmine.createSpy();
getProduct(123, callback);
expect(callback).toHaveBeenCalled();
});
function getProduct(id, callback) {
$.ajax({
type: "GET",
url: "/products/" + id,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: callback
});
}
For Jasmine 2.0 use instead:
spyOn($, "ajax").and.callFake(function(options) {
as noted in this answer
Finally, you have hinted elsewhere that you might want to write integration tests that make real AJAX requests - for integration purposes. This can be done using Jasmine's asyncronous features: waits(), waitsFor() and runs():
it("should make a real AJAX request", function () {
var callback = jasmine.createSpy();
getProduct(123, callback);
waitsFor(function() {
return callback.callCount > 0;
});
runs(function() {
expect(callback).toHaveBeenCalled();
});
});
function getProduct(id, callback) {
$.ajax({
type: "GET",
url: "data.json",
contentType: "application/json; charset=utf-8"
dataType: "json",
success: callback
});
}
Look at the jasmine-ajax project: http://github.com/pivotal/jasmine-ajax.
It's a drop-in helper that (for either jQuery or Prototype.js) stubs at the XHR layer so that requests never go out. You can then expect all you want about the request.
Then it lets you provide fixture responses for all your cases and then write tests for each response that you want: success, failure, unauthorized, etc.
It takes Ajax calls out of the realm of asynchronous tests and provides you a lot of flexibility for testing how your actual response handlers should work.
here is a simple example test suite
for an app js like this
var app = {
fire: function(url, sfn, efn) {
$.ajax({
url:url,
success:sfn,
error:efn
});
}
};
a sample test suite, which will call callback based on url regexp
describe("ajax calls returns", function() {
var successFn, errorFn;
beforeEach(function () {
successFn = jasmine.createSpy("successFn");
errorFn = jasmine.createSpy("errorFn");
jQuery.ajax = spyOn(jQuery, "ajax").andCallFake(
function (options) {
if(/.*success.*/.test(options.url)) {
options.success();
} else {
options.error();
}
}
);
});
it("success", function () {
app.fire("success/url", successFn, errorFn);
expect(successFn).toHaveBeenCalled();
});
it("error response", function () {
app.fire("error/url", successFn, errorFn);
expect(errorFn).toHaveBeenCalled();
});
});
When I specify ajax code with Jasmine, I solve the problem by spying on whatever depended-on function initiates the remote call (like, say, $.get or $ajax). Then I retrieve the callbacks set on it and test them discretely.
Here's an example I gisted recently:
https://gist.github.com/946704
Try jqueryspy.com
It provides an elegant jquery like syntax to describe your tests and allows callbacks to test after the ajax has complete. Its great for integration testing and you can configure maximum ajax wait times in seconds or milleseconds.
I feel like I need to provide a more up-to-date answer since Jasmine is now at version 2.4 and a few functions have changed from the version 2.0.
So, to verify that a callback function has been called within your AJAX request, you need to create a spy, add a callFake function to it then use the spy as your callback function. Here's how it goes:
describe("when you make a jQuery AJAX request", function()
{
it("should get the content of an XML file", function(done)
{
var success = jasmine.createSpy('success');
var error = jasmine.createSpy('error');
success.and.callFake(function(xml_content)
{
expect(success).toHaveBeenCalled();
// you can even do more tests with xml_content which is
// the data returned by the success function of your AJAX call
done(); // we're done, Jasmine can run the specs now
});
error.and.callFake(function()
{
// this will fail since success has not been called
expect(success).toHaveBeenCalled();
// If you are happy about the fact that error has been called,
// don't make it fail by using expect(error).toHaveBeenCalled();
done(); // we're done
});
jQuery.ajax({
type : "GET",
url : "addressbook_files/addressbookxml.xml",
dataType : "xml",
success : success,
error : error
});
});
});
I've done the trick for the success function as well as the error function to make sure that Jasmine will run the specs as soon as possible even if your AJAX returns an error.
If you don't specify an error function and your AJAX returns an error, you will have to wait 5 seconds (default timeout interval) until Jasmine throws an error Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.. You can also specify your own timeout like this:
it("should get the content of an XML file", function(done)
{
// your code
},
10000); // 10 seconds