I am using jQuery ajax to load data into a jQuery tab. It works fine in Chrome and FireFox. In IE8 the data is sometimes not loaded. If I clear cache or reload the page it apparently works fine.
As far as I can tell it fails after shutting down IE and then starting it up again some time later. It has failed within hours, but succeeds if the delay is only minutes. At least that is what I think the failure mode is, I have not rigorously determined a magic time.
ETA: It works if I clear the cache or refresh the page.
I have put a superfluous time parameter in the post data, and set cache:false in the ajax call.
The data is not cached since if I change the expected data it will fill it in properly.
Another update:
A missing piece of data. This is a Facebook app. That turns out to be crucial.
I sniffed both the working and not working sessions with Wireshark. It turns out the difference is that the working session submits the Facebook cookies and the not working one doesn't.
So the question is now how to force the ajax call to include cookies. The descriptions I have found about the ajax call is that it includes cookies. Is the behaviour I am seeing a bug?
ETA:
The javascript:
$.ajaxSetup
(
{
// Disable caching of AJAX responses
cache: false
}
);
$(document).ready
(
function()
{
$('#shopTabs').tabs();
thing.create();
thing.editPicture();
$('#shopTabs').bind
(
'tabsselect',
function(event, ui)
{
thing.setReload(ui.index);
thing.setActive(ui.index);
}
);
}
);
// Must be global for Java to call
function reload()
{
thing.create();
thing.editPicture();
}
var thing =
{
reload : 0,
active : 0,
noOp : function()
{
},
create : function()
{
date = new Date();
$('#shopTabs1').load('create.php', {time : date.getTime()}, thing.linkform);
},
editPicture : function()
{
date = new Date();
$('#shopTabs2').load('editPicture.php', {time : date.getTime()}, thing.noOp);
},
linkform : function()
{
$('#upload').ajaxForm({target : '#shopTabs1'});
},
setReload : function
(
index
)
{
this.reload = this.reloadList[index];
},
setActive : function
(
index
)
{
this.active = this.activeList[index];
},
load : function
(
php,
args,
loadFn
)
{
var settings =
{
type : "POST",
cache : false,
url : php,
data : args,
context : this,
success : function (data)
{
$(this.active).html(data);
loadFn();
}
}
$.ajax(settings);
}
};
thing.activeList = ['#ui-tabs-1', '#shopTabs1', '#shopTabs2'];
thing.reloadList = [thing.noOp, thing.create, thing.editPicture];
In your thing.create function, add a changing query parameter, current date is good, or use a random number.
$('#shopTabs1').load('create.php?r='+escape(new Date().toString()), {time : date.getTime()}, thing.linkform);
or
$('#shopTabs1').load('create.php?r='+new Date().valueOf(), {time : date.getTime()}, thing.linkform);
same with your editPicture.
That will prevent IE from caching, as mentioned by Omu's answer
It turns out the problem was that IE officially expects a P3P header to load an iframe from a third third party site. Facebook implements apps using iframes from the app provider.
IE does not consistently fail if there is no P3P header.
Related
I want to solve following issue. I have a Spring-MVC Application with Thymeleaf, with a post request (sent by a form) I trigger a simulation task, what could take several minutes. The task process big number of data and we would like to have a progress bar via JavaScript. If there are two sessions, the simulation should be triggered independently and each browser shows its progress status.
Currently we have a solution, what is not really working well all the time.
The MVC Controller gets the Post request:
#Autowired SimulatorView view; // SESSION SCOPE
#PostMapping("/view")
public String run(#ModelAttribute(CHECKS) ChecksDto checksWrapper, Model model) throws InterruptedException, ExecutionException {
view.setStatisticDto(simulate(checksWrapper)); // Can take several minutes
return "simulation/result :: simulated";
}
When I trigger the simulation on my WebGUI, a progress bar has been displayed and via JavaScript I am calling Rest Methods frequently to ask for the status of the progress.
RestController
#RequestMapping("simulation/api")
public class SimulatorApi {
#Autowired SimulatorView view; // SESSION SCOPE
#RequestMapping("/progressStream")
public double progressStream() {
return view.getProgress().progressStream();
}
#RequestMapping("/progressInvoice")
public double progressInvoice() {
return view.getProgress().progressInvoice();
}
}
My JavaScript code snippet looks like:
function registerSimulationRunEvent() {
// this is the id of the form
$("#simulatorForm").submit(function(e) {
handleSimulationStarted();
var url = location.protocol + "//" + location.host + "/fdsclient/simulation/view";
$.ajax({
type: "POST",
url: url,
data: $("#simulatorForm").serialize(), // serializes the form's elements.
success: function(data) { handleSimulationFinished(); },
error: function(xhr, error) { handleSimulationError(); }
});
e.preventDefault(); // avoid to execute the actual submit of the form.
});
}
function handleSimulationStarted() {
replaceResultPanelRunning(); // THYMELEAF FRAGMENT EXCHANGE
}
function handleSimulationFinished() {
stopResultPanelAnimation(); // STOP PROGRESS BAR ANIMATION
replaceResultPanelSimulated(); // EXCHANGE THYMELEAF FRAGMENT
}
function handleSimulationError() {
stopResultPanelAnimation();
replaceResultPanelError();
}
function replaceResultPanelRunning() {
var url = // URL;
$("#resultDiv").load(url);
startResultPanelAnimation();
}
// ANIMATION
var animationInterval = null;
function startResultPanelAnimation() {
animationInterval = setInterval(animateResultPanel,4000);
}
function stopResultPanelAnimation() {
clearInterval(animationInterval); // stop the interval
}
function animateResultPanel() {
$("#simulatorProgressLabel").animate({opacity: '0.4'}, "slow");
$("#simulatorProgressLabel").animate({opacity: '1.0'}, "slow");
}
I know using session scope for rest services is a bad thing, but I didn`t know yet what is a good and easy solution. On the other hand currently different browser can simulate independently, but not always the progress bar works (especially when trigger first time mostly doesnt work). The IE11 only works when the Developer Tools are activated. When deactivating the tool while progress, the progress bar stops to grow.
What I would like to know is, how a good solution looks like when using template engine with Spring-MVC and Thymeleaf for triggering the process and displaying the status of progress via Javascript (as JQUery). Thank you in advance.
I have done a similar thing using Jquery AJAX POST submission. You can do something like this. This will submit POST request as a JSON format to the controller and wait for a response. A progress UI component can be shown during this waiting period.
//Start Progress display
function setStatistic(){
var data = JSON.stringify(//build your ChecksDto)
if (data) {
$.ajax({
url : '/view',
headers : {
'Content-Type' : 'application/json'
},
method : 'POST',
dataType : 'json',
data : data,
success : function(data) {
if (data.status == 200) {
// Stop Progress display
// Handle success status
}
},
error : function(xhr, status, error) {
// Stop Progress display
// Handle errors here
}
});
}
}
You also need to change Controller method to retrieve ajax requests as follows,
#ResponseBody
#PostMapping("/view")
public String run(#RequestBody ChecksDto checksWrapper, Model model) throws InterruptedException, ExecutionException
At least I found the solution in another Stackoverflow Page. The magic word is setting ajax cache to false.
$.ajaxSetup ({
// Disable caching of AJAX responses */
cache: false
});
I use router-ui for my angular single page
I hear the $stateChangeSuccess event to store in localStorage the current state and his params :
$scope.$on('$stateChangeSuccess', function(event, toState) {
localStorage.lastState = toState.name;
localStorage.lastStateParams = JSON.stringify($stateParams);
});
My question is :
When the client open the webapp, I want to restore his personnal last state but where can I put this code ?
$state.go(localStorage.lastState, JSON.parse(localStorage.lastStateParams));
// UPDATE
Actually i put this code in $stateChangeSuccess event like this :
$scope.$on('$stateChangeSuccess', function(event, toState) {
if (toState.name !== 'welcome') {
localStorage.lastState = toState.name;
localStorage.lastStateParams = JSON.stringify($stateParams);
}
if (toState.name !== localStorage.lastState) {
$state.go(localStorage.lastState, JSON.parse(localStorage.lastStateParams));
}
});
The problem with this solution is the process is like this at opening app :
localhost (user open the app)
localhost/#/welcome ( because the $urlRouterProvider.otherwise('/welcome') proc before the $stateChangeSuccess event)
and then : localhost/#/articles/423 (on $stateChangeSuccess event)
So i'm sure i do bad, someone know the good method ?
Thanks very much,
As always the solution is always easier than you think !
In my main controller :
$scope.$on('$stateChangeSuccess', function(event, toState) {
// Timeout is required because the document.location.hash is not upadated immediatly
$timeout(function() {
localStorage.hash = document.location.hash.substr(1);
}, 100);
});
At end of my routes :
// Default
$urlRouterProvider.otherwise(localStorage.hash || '/welcome');
I am trying to send data to server using ajax, but the problem is that I have a consuming process before sending the data.
The process takes about 5 seconds and the spinner has to run in the process.
So in my code the spinner doesnt show until the ajax call starts (probably because the process is blocking everything)
If I move the call "consumingprocess" into "beforesend", then it doesnt work and I am not sure why.
So the question is how to show the spinner, while everything is beeing called (the consumingprocess and the ajax call)
Thanks
This is my code:
$("#btnAccept").bind("click", function(event, ui) {
//start spinner, works fine but only shows after consumingprocess has finished
$.mobile.loading( 'show' );
console.log("btnAccept");
var data = consmuingprocess();
console.log(data);
// data is fine
$.ajax({
type : "POST",
url : url,
dataType : "xml",
contentType : "text/xml;charset=UTF-8",
data : data,
requestHeaders : {
Origin : '*'
},
crossDomain : true,
beforeSend : function(xhr) {
xhr.setRequestHeader("Authorization", "Basic xxxxxxxxxxxxxxx");
console.log("beforeSend");
},
error : errorAJAX,
success : parseXml
});
});
});
What you can do is
call your loading window
delay so the loading window has a chance to display
run the rest of your code.
You would do this using an interval:
$("#btnAccept").bind("click", function(event, ui) {
var intervalId;
function delayedStuff = function() {
// make sure we only run this once
window.clearInterval(intervalId);
var data = consmuingprocess();
$.ajax({
// set up your ajax request and handlers
});
};
$.mobile.loading( 'show' );
// wait 1/2 second, then run delayedStuff
intervalId = window.setInterval(delayedStuff, 500);
});
But this technique comes with an important caveat: while your very expensive consumingProcess function is running, all animations and javascript still comes to a halt. On Chrome, even animated gifs stop running. All we've done here is just given your page changes a chance to display.
There are a couple of possible solutions available:
Take a closer look at your consumingprocess() function and see if it can be optimized. There is probably a faster way to do whatever it is you're doing that's taking so long.
Use WebWorkers. The downside is compatibility: IE and most older browsers don't support it. I haven't done multi-threaded programming with JavaScript at all, so I don't know how effective this is.
So, I've used Backbone.js to write a messaging system. It works fine in Chrome and FF but IE9 has issues with a particular fetch call that kills it. (I'm working in MVC3).
I have a poll that checks for new messages coming in, which sends the date to the server. The poll is called with this method:
DoMessageFetch = function() {
var now = new Date().toUTCString();
Chat.mymessages.fetch({
cache: false,
data: {
Now: now
},
success: function (response) {
// if there are messages ...
// for each message, open a chat window
if (Chat.mymessages.length > 0) {
for (var i = 0; i < Chat.mymessages.length; i++) {
var useridto = Chat.mymessages.at(i).get("UserId");
var name = Chat.mymessages.at(i).get("ScreenName");
// a chat-window with this useridto is NOT on the page
if (!($('#chat-window-' + useridto).is(':visible'))) {
Chat.doChatMessageFetch(name, useridto, null); // this constructs a Backbone view
}
}
}
},
error: function () { console.log('ERROR: fetching general poll messages failed.'); }
});
Chat.mymessages.reset();
}
In IE9 the Now param is null when I watch breakpoints in my Controller. This means the request follows the wrong code path on the server...
I don't understand where my Now parameter went in IE. Can any one help?
This problem is due to the different behaviour of
new Date().toUTCString()
between IE , Google Chrome and Firefox.
For example the result in Chrome is :
"Thu, 20 Sep 2012 20:19:15 GMT"
while in IE you will get
"Thu, 20 Sep 2012 20:19:15 UTC"
MVC3 ModelBinder will ignore IE Format and leave your Now parameter null.The solution to this problem is to replace
new Date().toUTCString()
with
new Date().toJSON()
The only thing to note with this solution is that it will not work by default on IE7 due to the lack of the toJSON() function but this problem can be solved using Douglas Crockford json2.js library which is also recommended when using Backbone.js
I am wondering what is the best way to stop duplciate submissions when using jquery and ajax?
I come up with 2 possible ways but not sure if these are the only 2.
On Ajax start disable all buttons till request is done. 2 problems I see with this though is I use jquery model dialog so I don't know how easy it would be to disable those button as I not sure if they have id's. Second I if the the request hangs the user has really no way to try again since all the buttons are disabled.
I am looking into something called AjaxQueue at this time I have no clue if it is what I need or how it works since the site where the plugin is apparently down for maintenance.
http://docs.jquery.com/AjaxQueue
Edit
I think this is a spin off of what I was looking at.
http://www.protofunc.com/scripts/jquery/ajaxManager/
The only problem I see with this ajaxManager is that I think I have to change all my $.post, $.get and $.ajax ones to their type.
But what happens if I need a special parameter from $.ajax? Or that fact I like using .post and .get.
Edit 2
I think it can take in all $.ajax options. I am still looking into it. However what I am unsure about now is can I use the same constructor for all requests that will use the same options.
First you have to construct/configure a new Ajaxmanager
//create an ajaxmanager named someAjaxProfileName
var someManagedAjax = $.manageAjax.create('someAjaxProfileName', {
queue: true,
cacheResponse: true
});
Or do I have to make the above every single time?
How about setting a flag when the user clicks the button? You will only clear the flag when the AJAX request completes successfully (in complete, which is called after the success and error callbacks), and you will only send an AJAX request if the flag is not set.
Related to AJAX queuing there is a plugin called jQuery Message Queuing that is very good. I've used it myself.
var requestSent = false;
jQuery("#buttonID").click(function() {
if(!requestSent) {
requestSent = true;
jQuery.ajax({
url: "http://example.com",
....,
timeout: timeoutValue,
complete: function() {
...
requestSent = false;
},
});
}
});
You can set a timeout value for long-running requests (value is in milliseconds) if you think your request has a possibility of hanging. If an timeout occurs, the error callback is called, after which the complete callback gets called.
You could store an active request in a variable, then clear it when there's a response.
var request; // Stores the XMLHTTPRequest object
$('#myButton').click(function() {
if(!request) { // Only send the AJAX request if there's no current request
request = $.ajax({ // Assign the XMLHTTPRequest object to the variable
url:...,
...,
complete: function() { request = null } // Clear variable after response
});
}
});
EDIT:
One nice thing about this, is that you could cancel long running requests using abort().
var request; // Stores the XMLHTTPRequest object
var timeout; // Stores timeout reference for long running requests
$('#myButton').click(function() {
if(!request) { // Only send the AJAX request if there's no current request
request = $.ajax({ // Assign the XMLHTTPRequest object to the variable
url:...,
...,
complete: function() { timeout = request = null } // Clear variables after response
});
timeout = setTimeout( function() {
if(request) request.abort(); // abort request
}, 10000 ); // after 10 seconds
}
});
$.xhrPool = {};
$.xhrPool['hash'] = []
$.ajaxSetup({
beforeSend: function(jqXHR,settings) {
var hash = settings.url+settings.data
if ( $.xhrPool['hash'].indexOf(hash) === -1 ){
jqXHR.url = settings.url;
jqXHR.data = settings.data;
$.xhrPool['hash'].push(hash);
}else{
console.log('Duplicate request cancelled!');
jqXHR.abort();
}
},
complete: function(jqXHR,settings) {
var hash = jqXHR.url+jqXHR.data
if (index > -1) {
$.xhrPool['hash'].splice(index, 1);
}
}
});