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
});
Related
I'm helping with an open source project. It's a small Go webserver running on a device containing a Raspberry Pi. I want to be able to have a user click a button on an html screen, which calls a routine in Go, which returns 2 values, a boolean and a string.
What we are wanting to do is see which network interfaces are up on the raspberry pi e.g. is the lan connection up?
To do this I really need to ping a site from each interface. This takes a few seconds for each of 3 interfaces: Lan, WiFi, and 3G.
I can do this when the page is requested and fill in an html template as the page loads, but it means waiting maybe 10 to 15 secs for the page to load, so it seems like something is broken.
So I want to be able to list each of the 3 interfaces on the page and have the user click 'test' which then calls a routine in the underlying Go webserver.
I then need to be able to display the results from the call in a couple of text areas for each interface.
What I have tried:
I have tried registering a Go function (in this case IsLANConnectionUp) using funcmap from the net/html package and calling it from the html template from a JavaScript function, like this:
<button onclick = "getLANStatus()" class="btn btn-primary">Test</button>
<script>
function getLANStatus() {
var status = document.getElementById('status');
{{ if IsLANConnectionUp }}
status.innerHTML = "Lan is up!"
{{ else }}
status.innerHTML = "Lan is down!"
{{ end }}
}
</script>
But having the template code inside the javascript code doesn't seem to work. Also, I'd like the text output from the ping command (which my Go function getLANStatus and I don't know how to extract that data from the function call. The documentation says only one value can be returned.
Searching on StackOverflow I see this: calling Golang functions from within javascript code
$.ajax({
url: "http://www.example.com/signup",
data: {username: "whatever"} //If the request needs any data
}).done(function (data) {
// Do whatever with returned data
});
But it says things like "// Do whatever with the returned data" I'm new to web programming and so don't know how to use that code. If this is the way to go, could someone please expand on this a little?
Any help would be much appreciated.
So couple different concepts here.
Render: On the initial request to your html that generates the Test button. Your go server will render that html 1 time and return it to your browser. It does not re-request dynamically unless you wire some stuff up to make the web page change.
Client: So when someone clicks your button, the function getLANStatus will be ran. You will want that function to do a few things
Through ajax, communicate with your go server through an api that will return the status of your connections as a json object. Something like
{
"3g": "up",
"lan": "down",
"wifi": "up"
}
Second, in the done part of your ajax, you will manipulate something in the DOM in order to convey that the status of the interfaces is what it is. You could do that by finding the element, then changing the text to what is returned by the object.
As a simple first step, you can alert the payload in the function that would look like this
$.ajax({
url: "http://YOUR_GO_SERVER_IP_OR_DNS:PORT/interfaces_status.json"
}).done(function (data) {
alert(data);
console.log(data);
debugger;
});
Then if you request that with the console open in chrome, you will be able to directly play with the returned data so that you know what all it reponds to.
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.
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/
Is there any way to intercept an ajax request being made via jquery, to insert additional variables into it?
I know that .ajaxStart() lets you register a callback which triggers an event whenever an ajax request begins, but what I'm looking for is a way to cancel that ajax request if it meets certain criteria (e.g url), insert some more variables into its content, and then submit it.
This is for a plugin for a 3rd party software whose own code can't be changed directly.
Edit: Seems like .ajaxSetup() lets you set some global variables related to ajaxRequests. If I registered a beforeSend function, would that function be able to cancel the request to make a different one on meeting certain criteria?
Figured it out, this was the code I used:
jQuery(document).ready(function()
{
var func = function(e, data)
{
//data.data is a string with &seperated values, e.g a=b&c=d&.. .
//Append additional variables to it and they'll be submitted with the request:
data.data += "&id=3&d=f&z=y";
return true;
};
jQuery.ajaxSetup( {beforeSend: func} );
jQuery.post('example.php', {a : 'b'}, 'json');
} );
To cancel the request, returning false from func seemed to work.
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.