I have a use case like this:
When user clicks on a radio button I make an ajax call to get some information dependant on the user selection. When user clicks on a link I use the information form AJAX call to display an alert.
I need:
to ensure the result of the ajax calls are written in the correct
order
If the user to open the alert, before then the AJAX call is finished I want to display him a waiting icon, otherwise display him
the alert
If the response from AJAX doesn't comein 2s I will display the alert anyway
I am not really asking how to do this specific use case in JS, since I can figure out how to combine timer, jQuery ajax callbacks and so on.
I see the problem in a more generic way:
we have three events (AJAX success, user click, timeout) and I wanto to associate some code to be triggered by some conditions on the events. Is there any framework that allows to associate actions to a combination of events?
Or am I just using the wrong approach?
1 - If the response order is important, it's not useful to allow multiple parallel requests, which means when the user will select a radio button, you should queue the operations. That's very easy to achieve with promises.
E.g.
var promiseQueue;
function processRequest(radioValue) {
return $.ajax({ ... }).done(/*handle request*/);
}
function onRadioClicked() {
var radioValue = this.value; //assuming this is your radio element
//if the queue was initialized, queue our next operation using "then",
//otherwise process the request directly and initialize the queue with the
//returned promise.
promiseQueue = promiseQueue?
promiseQueue.then(processRequest.bind(null, radioValue)) :
processRequest(radioValue);
}
2 - Do not use alert as it will block your UI. You can use jQuery UI's Dialog plugin or anything similar. Everytime a response is processed, you can check promiseQueue.state() to see if there are other requests to be processed or they all have been processed.
3 - You can pass a timeout configuration to $.ajax to allow a maximum time for the request to complete. Have a look at https://api.jquery.com/jQuery.ajax/
Related
Working on a platform, to enable auto-ticketing functionality. For which a REST API request is used for ticket creation. Unfortunately, there are 2 requests popping simultaneously, which results in creating duplicated tickets.
How to handle such case and send only one of these requests?
Tried adding the 2nd request in the response callback of the first, though this does not seem to work.
if (flag == 1){
logger.debug("Node-down alarm-Request raised - +sitn_id);
clearTimeout(mouseoverTimer);
mouseoverTimer = setTimeout(function(){
logger.debug("Inside Call back function - ");
//function call for ticket creation
incidentRequest(sitn_id,confUtil.config.mule_url);
}, 10);
You really should show more of the code that makes the request, though it seems as if you are doing some ajax inside your 'incidentRequest', so I will presume that (if that isn't what you are doing, then please, show your code....) - and since you tags say javascript and jquery - well, here goes...
To stop the 'double send' in an AJAX call, it is simple:
function incidentRequest(sitn_id,confUtil.config.mule_url){
// stop the double by clearing the cache
$.ajaxSetup({cache: false});
// continue on with the AJAX call
// presuming the url you want is confUtil.config.mule_url
// and the data you want to send is sitn_id
$.post(confUtil.config.mule_url, 'sitn_id=' + sitn_id, function (data) {
// do cool stuff
});
}
Hopefully that will help you get moving. If not, then we will need more code of what is going on around all this.
Suppose I have a page called form.php. I then clicked a button called "add button". This button triggers an event that got detected by a jquery function. The jquery function makes an ajax call to add.php.
Inside add.php, there is code that checks if a particular record exist in the database. If it does find that the record exists, I want to do the following.
Send a response string "exist" to ajax.
The ajax, inside the .done() function, will execute a prompt that says "This record already exist, do you wish to overright"?
If the user canceled the prompt, nothing more should happened and the ajax call should be done.
If the user clicks "ok", I would like the php script to be notified of this and execute an update statement using the data from form.php.
I suspect this is impossible because after receiving a response from php, AFAIK there is no way for ajax to respond back to the php script that is currently executing.
Am I correct or there is a way to do this?
You have to add a parameter to your ajax request, like override with true and false. By default/first request you set it to false. Then the add.php does it's default and returns exists.
The the user makes his decision. If he want to override, you send the ajax request again with the override parameter to true. Your add.php will notice the parameter and does whatever it has to do.
Wrap your ajax handler in an own function with a done callback. So you can reuse the request as often as you want. Pretty easy, no double code needed as well ...
The .done() function of your first ajax call executes when the ajax call has finished successfully, so when your php script has finished completely.
If you want to do something else, you would need to make a new ajax request. That could be to the same or another script, sending in different / new / additional parameters.
Note that you have to make sure that the second script cannot be called without the first one finishing, for example by setting and checking an expiring session variable.
you can do something like this.
$.post('add.php',$(this).serialize())
.done(function(result){
var r = confirm("This record already exist, do you wish to overright");
if(result == 'exist'){
if (r == true) {
$.post('update.php',$(this).serialize()).done(function(r){
console.log(r);
});
} else {
return false;
}
}else{
console.log(result)
}
});
I am currently working on a web based time tracking software. I'm developing in grails, but this question is solely related to javascript and asynchronous requests.
The time tracking tool shall enable users to choose a day for the current month, create one or multiple activities for each day and save the entire day. Each activity must be assigned to a project and a contract.
Upon choosing "save", the partial day is saved to the database, the hours are calculated and a table is updated at the bottom of the page, showing an overview of the user's worked hours per month.
Now to my issue: There may be a lot of AJAX request. Patient users might only click the "create activity" button just once and wait until it is created. Others, however, might just keep clicking until something happens.
The main issue here is updating the view, although i also recognized some failed calls because of concurrent database transaction (especially when choosing "save" and "delete" sequentially). Any feedback on that issue -- requests not "waiting" for the same row to be ready again -- will be apreciated as well, yet this is not my question.
I have an updateTemplate(data, day) function, which is invoked onSuccess of respective ajax calls in either of my functions saveRecord(), deleteRecord(), pasteRecords(), makeEditable() (undo save). Here is the example AJAX call in jquery:
$.ajax({
type: "POST",
url: "${g.createLink(controller:"controller", action:"action")}",
data: requestJson,
contentType:"application/json; charset=utf-8",
async: true,
success: function(data, textstatus) {updateTemplate(data["template"], tag); updateTable(data["table"]);},
});
In the controller action, a JSON object is rendered as a response, containing the keys template and table. Each key has a template rendered as a String assigned to it, using g.render.
Now, what happens when I click on create repeatedly in very short intervalls, due to the asynchronous calls, some create (or other) actions are executed concurrently. The issue is that updateTemplate just renders data from the repsonse; the data to render is collected in the create controller action. But the "last" request action only finds the objects created by itself. I think this is because create actions are run concurrently
I figure there is something I'm either overcomplicating or doing something essentially wrong working with a page that refreshs dynamically. The only thing I found that helps are synchronous calls, which works, but the user experience was awful. What options do I have to make this work? Is this really it or am I just looking for the wrong approach? How can I make this all more robust, so that impatient users are not able to break my code?
*********EDIT:********
I know that I could block buttons or keyboard shortcuts, use synchronous calls or similar things to avoid those issues. However, I want to know if it is possible to solve it with multiple AJAX requests being submitted. So the user should be able to keep adding new activities, although they won't appear immediately. There is a spinner for feedback anyway. I just want to somehow make sure that before the "last" AJAX request gets fired, the database is up to date so that the controller action will respond with the up-to-date gsp template with the right objects.
With help of this Stackoverflow answer, I found a way to ensure that the ajax call -- in the javascript function executed lastly -- always responds with an up-to-date model. Basically, I put the javascript functions containing AJAX calls in a waiting queue if a "critical" AJAX request has been initiated before but not completed yet.
For that I define the function doCallAjaxBusyAwareFunction(callable) that checks if the global variable Global.busy is 'true' prior to executing the callable function. If it's true, the function will be executed again until Global.busy is false, to finally execute the function -- collecting the data from the DOM -- and fire the AJAX request.
Definition of the global Variable:
var Global = {
ajaxIsBusy = false//,
//additional Global scope variables
};
Definition of the function doCallAjaxBusyAwareFunction:
function doCallAjaxBusyAwareFunction(callable) {
if(Global.busy == true){
console.log("Global.busy = " + Global.busy + ". Timout set! Try again in 100ms!!");
setTimeout(function(){doCallAjaxBusyAwareFunction(callable);}, 100);
}
else{
console.log("Global.busy = " + Global.busy + ". Call function!!");
callable();
}
}
To flag a function containing ajax as critical, I let it set Global.busy = true at the very start and Global.busy = false on AJAX complete. Example call:
function xyz (){
Global.busy = true;
//collect ajax request parameters from DOM
$.ajax({
//desired ajax settings
complete: function(data, status){ Global.busy = false; }
}
Since Global.busy is set to true at the very beginning, the DOM cannot be manipulated -- e.g. by deletes while the function xyz collects DOM data. But when the function was executed, there is still Global.busy === true until the ajax call completes.
Fire an ajax call from a "busy-aware" function:
doCallAjaxBusyAwareFunction(function(){
//collect DOM data
$.ajax({/*AJAX settings*/});
});
....or fire an ajax call from a "busy-aware" function that is also marked critical itself (basically what I mainly use it for):
doCallAjaxBusyAwareFunction(function(){
Global.busy = true;
//collect DOM data
$.ajax({
//AJAX SETTINGS
complete: function(data, status){ Global.busy = false; }
});
});
Feedback is welcome and other options too, especially if this approach is bad practice. I really hope somebody finds this post and evaluates it, since I don't know if it should be done like that at all. I will leave this question unanswered for now.
let's say i have the following HTML page.
<A panel to display list of items>
Item A
Item B
Item C
...
<A panel to display details about item>
Details on item X:
I want to write the page so that whenever the user clicks on one of the items, the detail about the selected item will show up on the bottom panel.
However, few problems that I want to solve are:
How should I prevent duplicate requests. (like, user presses links to item A and B before the detail of item A returns from the server).
If I want to display "detail is now loading...", where should I put the code?
you can register click handlers, which fire ajax requests to load the data.
There are multiple ways to handle your concern about not firing multiple requests:
Just unregister the handlers on the other Items when you click one of them, and put them back when your request returns.
You can have an object in the scope of the request handlers that you set a property on, like 'requestInProgress', that you set to true and false appropriately. In other words, use closures.
Displaying 'detail is now loading' is simple -- you can just set the value of the Panel dom, or the innerhtml if you want, to that message before you fire the request, and set the actual returned value when the request returns.
Note that many js libraries, like jQuery, provide an API around making ajax requests that might simplify things. For example, the jQuery.ajax method takes an object literal that can contain the functions 'beforeSend', 'complete', 'success' so you do things before, after, and on the case of success (among other functions, check out http://api.jquery.com/jQuery.ajax/)
You can certainly do what you want with bare-metal js, but libraries can make your life easier.
About duplicate requests, I'd keep the last XMLHttpRequest object inside a lastRequest variable. Whenever I'm sending a new request I'd check this variable. If it exists, call lastRequest.abort(). I'd also check in your success callback that the request object is the same as your lastRequest, otherwise ignore it. In jQuery this would look like:
var lastRequest;
// Your click handler
$(".items").click(function () {
if (lastRequest) lastRequest.abort();
lastRequest = $.ajax({..., success: function (data, status, request) {
if (request != lastRequest) return; // Ignore, it was aborted
// Code to show the detail about the selected item
}});
// Code to show the in-progress message
});
I have a php script that outputs json data. For the purposes of testing, i've put sleep(2) at the start.
I have a html page that requests that data when you click a button, and does $('.dataarea').append(data.html)
(php script returns a json encoded array. data.html has the html that i want to put at the end of <div class="dataarea">...HERE</div>.
The trouble is, if i click the button too fast (ie. more than once within two seconds (due to the sleep(2) in the php script)), it requests the php file again.
how can i make it only do one request at a time?
i've tried this (edited down to show the important parts):
amibusy=false;
$('#next').click('get_next');
function get_next() {
if (amibusy) {
alert('requesting already');
}
else {
amibusy=true;
// do the request, then do the append()
amibusy=false;
}
}
but this doesn't seem to work. i've even tried replacing the amibusy=true|false, with set_busy(), and set_not_busy(). (and made a function am_i_busy() { return amibusy; })
but none of this seems to work. what am i missing?
If you're in jQuery the amibusy would be jQuery.active which contains a count of currently active AJAX requests, like this:
if(jQuery.active > 0) { //or $.active
alert('Request in Progress');
}
Keep in mind that in jQuery 1.4.3 this becomes jQuery.ajax.active.
Disable the button in the click event and enable it again when the request is finished. Note that the request is asynchronous (i.e. "send request" returns immediately), so you must register a function that is called when the answer comes in.
In jQuery, see the load() function and the success method plus the various AJAX events which you can tap into with ajax().
I'm wondering about your "do request" logic. Whenever I've done calls like this they've always been asynchronous meaning I fire the request off and then when the response comes another function handles that. In this case it would finish going through that function after setting the callback handler and set your value of amibusy back to false again before the request actually comes back. You'd need to set that variable in the handler for your post callback.
Could you use the async variable?
http://api.jquery.com/jQuery.ajax/
asyncBoolean Default: true
By default, all requests are sent
asynchronous (i.e. this is set to true
by default). If you need synchronous
requests, set this option to false.
Cross-domain requests and dataType:
"jsonp" requests do not support
synchronous operation. Note that
synchronous requests may temporarily
lock the browser, disabling any
actions while the request is active.