I have the following JavaScript function, which is executed when a button is clicked:
function calculate(resource) {
document.getElementById('loading-label').innerHTML = 'Calculating...';
$.ajax({
url: resource,
type: 'GET',
async: false,
headers: {
'Content-Type': 'application/json'
},
beforeSend: function () {
document.body.style.cursor('wait');
},
complete: function () {
document.body.style.cursor('default');
}
}).done(function (data, textStatus, jqXHR) {
if (data == true) {
document.getElementById('loading-label').innerHTML = 'Done!';
document.getElementById('loading-label').style.color = 'green';
} else {
document.getElementById('loading-label').innerHTML = 'Error!';
document.getElementById('loading-label').style.color = 'red';
}
});
}
But it doesn't work as I want. Maybe because I'm not using beforeSend and complete callbacks properly.
As it can be seen, when the button is clicked, a label changes its content and I would like to change the cursor to waiting until the synchronous call is finished (and then return to default). How could I do that?
You can't when you make a non-async request.
Aside from your trivial error (you need to assign new values to cursor with =, it isn't a function).
Synchronous requests block the event loop (which is why they are deprecated). Since the event loop is blocked, the browser doesn't perform a repaint, and the cursor doesn't change.
Write asynchronous code instead.
function calculate(resource) {
document.getElementById('loading-label').innerHTML = 'Calculating...';
document.body.style.cursor = 'wait';
$.ajax({
url: resource,
}).done(function (data, textStatus, jqXHR) {
if (data == true) {
document.getElementById('loading-label').innerHTML = 'Done!';
document.getElementById('loading-label').style.color = 'green';
document.body.style.cursor = 'default';
} else {
document.getElementById('loading-label').innerHTML = 'Error!';
document.getElementById('loading-label').style.color = 'red';
document.body.style.cursor = 'default';
}
});
}
Related
I have a working javascript block that basically takes user input, and upon each keystroke makes an Ajax POST call.
This works perfectly but I'd like to change it to only fire the ajax 3 seconds after the user starts typing, as opposed to every keystroke. Ideally I'd like to fire it after 3 seconds and if they start typing again it would start over, but the initial delay is most important.
I tried to do a set interval around it but it's didn't make the ajax call, so I'm wondering if there's different approach I need to take.
how can I make this ajax only call 3 seconds after typing in the input starts?
$('#productInput').on('input', function() {
let _this = $(this);
let optSelector = `option[value='${_this.val()}']`;
if (_this.val() === '') {
return;
} else if ($('#returnedProducts').find(optSelector).length) {
//html stuff
} else {
const searchResult = $(this).val();
$.ajax({ url: '/account/autocomplete',
data: {
search_result:searchResult
},
"_token": "{{ csrf_token() }}",
type: "POST",
success: function (response) {
$("#returnedProducts").empty();
var result = response.hits.hits;
for (let i = 0; i < result.length; i++) {
$("#returnedProducts").append($("<option/>",
{
//option stuff
}
));
}
}
});
}
});
Have a persistent variable that holds a setTimeout. On input, clear the current timeout (if there is one), and set another timeout to trigger in 3 seconds (unless another input event occurs). You may also consider putting the ajax call (at least) into its own function, for the sake of less indentation hell:
let timeout;
$('input').on('input', () => {
clearTimeout(timeout);
console.log('timeout set');
timeout = setTimeout(() => console.log('action running'), 3000);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input>
let timeout;
$('#productInput').on('input', function() {
clearTimeout(timeout);
let _this = $(this);
let optSelector = `option[value='${_this.val()}']`;
if (_this.val() === '') {
return;
} else if ($('#returnedProducts').find(optSelector).length) {
//html stuff
} else {
const searchResult = $(this).val();
timeout = setTimeout(ajaxCall, 3000, searchResult);
}
});
function ajaxCall(search_result) {
$.ajax({
url: '/account/autocomplete',
data: { search_result },
"_token": "{{ csrf_token() }}",
type: "POST",
success: successHandler
});
}
function successHandler(response) {
$("#returnedProducts").empty();
var result = response.hits.hits;
for (let i = 0; i < result.length; i++) {
$("#returnedProducts").append($("<option/>", {
//option stuff
}));
}
}
Sorry for the confusing title. I was at loss of words to describe it in a better manner.
So this is what is I'm trying to find out. Let's say
function myClass()
{
$.ajax({
type:'get',
url:'some/path/',
success:function(response)
{
// call has been compeleted, set some flag here
}
});
return this;
}
var obj = new myClass();
jQuery('body').append('<div id="overlay"></div>'); // so now the user will know he has to wait for some operation to be completed...
// now determine on this line whether the Ajax call is still in progress
while(<ajax call still in progress>)
{
// i do not need to do anything here since the overlay is already there in place
}
// now this is where I want to remove the overlay
jQuery('#overlay').remove();
UPDATE
Basically, I'm showing a loading overlay when the ajax call is made and once it finishes, I would like to remove that overlay. I know I can write the code inside the class itself, but I want the control of adding/removing the overlay outside the class.
You should use the promise nature of such a request. So keep a reference to what
$.ajax returns in a property of your created object.
You can then use the then method on that property to know when all is done.
Here is code making a call to a test server which shows a silver overlay while the request is pending (1 second):
function myClass(path) {
this.request = $.ajax({
// ^^^^^^^^^^^^
type: 'get',
url: path,
});
}
var obj = new myClass('https://httpstat.us/200?sleep=1000');
jQuery('body').append('<div id="overlay"></div>');
obj.request.then(function(response) {
console.log('done');
jQuery('#overlay').remove();
});
#overlay {
position: absolute;
width: 100%;
height: 100%;
background: silver;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Any attempt to make such code synchronous is bad practice. You need to embrace the asynchronous nature of Ajax calls, and use a callback system, like available through promises.
This is how I would do it:
/* The constructor. */
function myClass(successCallback) {
$.ajax({
context: this,
type: "get",
url: "some/path/",
success: function(response) {
this.inProgress = false;
successCallback.call(this);
}
});
this.inProgress = true;
}
/* Create a new class and add an overlay to the body. */
var obj = new myClass(removeOverlay);
jQuery('body').append('<div id="overlay"></div>');
/* The function that removes the overlay. */
function removeOverlay () {
if (!this.inProgress) jQuery('#overlay').remove();
}
You can pass callbacks as parameters to your object constructor:
function myClass(onStart, onStop) {
$.ajax({
type:'get',
url:'some/path',
data: {
action: 'test'
},
beforeSend: function() {
if (onStart && typeof onStart == 'function') {
onStart();
}
},
success: function(response) {
//do something
},
complete: function() {
if (onStop && typeof onStop == 'function') {
onStop();
}
}
});
}
var onStart = function () {
console.log('Started');
}
var onStop = function () {
console.log('Stopped');
}
var obj = new myClass(onStart, onStop);
I've got this jQuery in a page:
<script>
$(document).ready(function () {
$("#btnGetData").click(function () {
var _begdate = $("#datepickerFrom").val();
var _enddate = $("#datepickerTo").val();
var _unit = $("#unitName").text();
document.body.style.cursor = 'wait';
$.ajax({
type: 'GET',
url: '#Url.RouteUrl(routeName: "QuadrantData", routeValues: new { httpRoute = true, unit = "un", begdate = "bd", enddate = "ed" })'
.replace("un", encodeURIComponent(_unit))
.replace("bd", encodeURIComponent(_begdate))
.replace("ed", encodeURIComponent(_enddate)),
contentType: 'text/plain',
cache: false,
xhrFields: {
withCredentials: false
},
success: function (returneddata) {
$("body").html(returneddata);
},
error: function () {
console.log('hey, boo-boo!');
}
});
document.body.style.cursor = 'pointer';
});
});
</script>
Note that I try to create a wait cursor early on in the click handler:
document.body.style.cursor = 'wait';
...and then revert back to the default at the end:
document.body.style.cursor = 'pointer';
However, it doesn't work - the cursor never changes. The code does work (the ajax call completes successfully), but the cursor remains stonefaced, causing the user to wonder whether anything is happening.
What do I need to do yet to get the cursor to morph into a waiting attitude?
Because of the nature of the ajax calls (i.e. being async) the code executes the first line to change the cursor
document.body.style.cursor = 'wait';
then executes the ajax call (note: it does not wait for the response).
and then executes the last line:
document.body.style.cursor = 'pointer';
It all happens so fast, you probably don't notice.
At some point later, the ajax response is received and handled by either the success or fail callback handlers.
To change the cursor always after the ajax response is received (either success or failure), you need to add another callback called always - something along the lines of:
...
,
always: function () {
document.body.style.cursor = 'pointer';
});
...
Note, depending of the version of Jquery, this could be called a done() callback instead.
Check documentation here: http://api.jquery.com/jquery.ajax/
UPDATE
I updated the format to current practice and added the missing call...
$(document).ready(function() {
$("#btnGetData").click(function() {
//var _begdate = $("#datepickerFrom").val();
// var _enddate = $("#datepickerTo").val();
// var _unit = $("#unitName").text();
document.body.style.cursor = 'wait';
$.ajax({
type: 'GET',
url: 'http://stackoverflow.com/',
contentType: 'text/plain',
cache: false
})
.done(function() {
console.log('hey, success!');
})
.fail(function() {
console.log('hey, boo-boo!');
})
.always(function() {
console.log('hey, done!');
document.body.style.cursor = 'pointer';
});
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="btnGetData">Get it</button>
Try this:
<script>
$(document).ready(function () {
$("#btnGetData").click(function () {
var _begdate = $("#datepickerFrom").val();
var _enddate = $("#datepickerTo").val();
var _unit = $("#unitName").text();
document.body.style.cursor = 'wait';
$.ajax({
type: 'GET',
url: '#Url.RouteUrl(routeName: "QuadrantData", routeValues: new { httpRoute = true, unit = "un", begdate = "bd", enddate = "ed" })'
.replace("un", encodeURIComponent(_unit))
.replace("bd", encodeURIComponent(_begdate))
.replace("ed", encodeURIComponent(_enddate)),
contentType: 'text/plain',
cache: false,
xhrFields: {
withCredentials: false
}
}).done(function(returneddata){
$("body").html(returneddata);
}).fail(function(){
console.log('hey, boo-boo!');
}).always(function(){
document.body.style.cursor = 'pointer';
});
});
I've also "modernized" your jquery to use done(), fail() which are the newer standards. The always() handler will (maybe obviously) always run after the AJAX call returns.
I hand typed this, so I'm hoping the brackets all line up. I'm sure you'll get the gist.
By looking at this jQuery doc, you will see that success, error are deprecated.
Edit: I've created a working fiddle for you. Once you click the button, the fiddle simulates a 5 second AJAX call. One change is that I changed the style of html and body $('html,body').css('cursor','wait');
I have the following setup:
A document can have multiple forms.
When the input on an input field changes, jQuery will fire an ajax event. And input.data("checking", true) is called.
When the ajax event has been finished, input.data("checking", false) is called.
Now I want to make a custom form submit that waits for all input in this form to be on input.data("checking") === true.
My code so far, without the question applied:
$(document).on("submit", "form", function(event) {
event.target.checkValidity();
event.preventDefault();
event.stopPropagation();
//TODO: prevent this when not everything is checked
var dataSerialized = $(this).serialize();
var service = $(this).attr("action");
$.ajax({
url: "services/" + service + ".php",
data: dataSerialized,
type: "POST",
cache: false,
success: function(html) {
if (html == "1") {
//TODO: load page from json callback
//loadPage(onsuccess);
}
else {
loadPage("error");
}
},
error: function(html, message) {
finalError(message);
}
});
});
How could I make this function wait (non-blocking!) until all ajax events are finished?
Suppose to create a function checkDone which returns true when input.data("checking") == false for all input in the form, you could do:
$(document).on("submit", "form", function(event) {
event.target.checkValidity();
event.preventDefault();
event.stopPropagation();
var that = $(this);
var interval = setInterval(function() {
if(checkDone(that)) {
clearInterval(interval);
var dataSerialized = that.serialize();
var service = that.attr("action");
$.ajax({
url: "services/" + service + ".php",
data: dataSerialized,
type: "POST",
cache: false,
success: function(html) {
if (html == "1") {
//TODO: load page from json callback
//loadPage(onsuccess);
}
else {
loadPage("error");
}
},
error: function(html, message) {
finalError(message);
}
});
}
}, 500);
});
In this way you check every 0.5 seconds if you can submit the form after all inputs are validated, and if so the interval is cleared and the form submitted.
However I would recommend not to remove standard server side validation on post submit.
I use the function below to check on the status of a JSON file. It runs every 8 seconds (using setTimeout) to check if the file has changed. Once the JSON's status becomes 'success' I no longer want to keep calling the function. Can someone please show me how to do this? I suspect it involves the use of clearTimeout, but I'm unsure how to implement this.
Cheers!
$(function() {
var checkBookStatus = function() {
var job_id = "#{#job.job_id}";
var msg = $('.msg');
var msgBuilding = $('#msg-building');
var msgQueuing = $('#msg-in-queue');
var msgSuccessful = $('#msg-successful-build');
var msgError = $('#msg-error');
$.ajax({
url: '/jobs/'+job_id+'/status.json',
datatype: 'JSON',
success:function(data){
if (data.status == "failure") {
msg.hide();
msgError.show();
}
else if (data.status == "#{Job.queue}") {
msg.hide();
msgQueuing.show();
}
else if (data.status == "#{Job.building}") {
msg.hide();
msgBuilding.show();
}
else if (data.status == "#{Job.failure}") {
msg.hide();
msgError.show();
}
else if (data.status == "#{Job.success}") {
msg.hide();
msgSuccessful.show();
}
},
}).always(function () {
setTimeout(checkBookStatus, 8000);
});
};
checkBookStatus();
});
t = setTimeout(checkBookStatus, 8000); when you decide to stop the timeout use this clearTimeout(t);.
use clearTimeout
e.g. you defined :
id = setTimeout(checkBookStatus, 8000);
then you can remove this function by :
clearTimeout(id)
Before your call of checkBookStatus() at the end, put another call: var interval = setInterval(checkBookStatus, 8000);. Then on success you can clearInterval(interval).
Do not use setTimeout for iteration.
A lot of answers are suggesting just to use clearTimeout() however, you are checking the status after the timeout has expired, there is no timeout to clear. You need to not call setTimeout() in your always() function rather than to clear anything. So you could re-inspect the status inside your always() function I suppose, but your data object isn't in scope there. It would be preferable to just use setInterval() outside of your checkBookStatus() function.
$(function() {
var checkBookStatus = function() {
var job_id = "#{#job.job_id}";
var msg = $('.msg');
var msgBuilding = $('#msg-building');
var msgQueuing = $('#msg-in-queue');
var msgSuccessful = $('#msg-successful-build');
var msgError = $('#msg-error');
$.ajax({
url: '/jobs/'+job_id+'/status.json',
datatype: 'JSON',
success:function(data){
if (data.status == "failure") {
msg.hide();
msgError.show();
}
else if (data.status == "#{Job.queue}") {
msg.hide();
msgQueuing.show();
}
else if (data.status == "#{Job.building}") {
msg.hide();
msgBuilding.show();
}
else if (data.status == "#{Job.failure}") {
msg.hide();
msgError.show();
}
else if (data.status == "#{Job.success}") {
msg.hide();
msgSuccessful.show();
clearInterval(interval);
}
}
});
};
var interval = setInterval(checkBookStatus, 8000);
checkBookStatus();
});
Call clearTimeout with the value previously returned by setTimeout. This would give you something like:
$(function() {
var timeoutID;
var checkBookStatus = function () {
[…]
else if (data.status == "#{Job.success}") {
clearTimeout(timeoutID);
[…]
}).always(function () {
timeoutID = setTimeout(checkBookStatus, 8000);
[…]
When you use setTimeout, use like this:
var myTime = setTimeout(checkBookStatus, 8000);
to clear it just:
clearTimeout(myTime);
the following should work...
the setTimeout function return an instance of the setTimeout function. Keep this value in a variable and pass it to the function clearTimeout when you want to prevent the event from firing again.
i.e.
var t = setTimeout(1000, someFunction);
...
//after you no longer need the timeout to fire, call
clearTimeout(t);