I have a search form with inputs and selects, and when any input/select is changed i run some js and then make an ajax query with jquery. I want to stop the user from making further changes to the form while the request is in progress, as at the moment they can initiate several remote searches at once, effectively causing a race between the different searches.
It seems like the best solution to this is to prevent the user from interacting with the form while waiting for the request to come back. At the moment i'm doing this in the dumbest way possible by hiding the form before making the ajax query and then showing it again on success/error. This solves the problem but looks horrible and isn't really acceptable. Is there another, better way to prevent interaction with the form? To make things more complicated, to allow nice-looking selects, the user actually intera开发者_Go百科cts with spans which have js hooked up to them to tie them to the actual, hidden, selects. So, even though the spans aren't inputs, they are contained in the form and represent the actual interactive elements of the form.
Grateful for any advice - max. Here's what i'm doing now:
function submitQuestionSearchForm(){
//bunch of irrelevant stuff
var questionSearchForm = jQuery("#searchForm");
questionSearchForm.addClass("searching");
jQuery.ajax({
async: true,
data: jQuery.param(questionSearchForm.serializeArray()),
dataType: 'script',
type: 'get',
url: "/questions",
success: function(msg){
//more irrelevant stuff
questionSearchForm.removeClass("searching");
},
error: function(msg){
questionSearchForm.removeClass("searching");
}
});
return true;
}
where the 'searching' class currently has just {display: none}
I use this plugin http://jquery.malsup.com/block/, it implement a semi-transparent img that can be apply to a Div or the full page, it's pretty good and cross browser supported.
EDIT: As some people stated this plugin does not block the form inputs, selects, etc... If you really want it to be bulletproof and don't allow people mess around with the form you need to implement further blocking, I suggest using a pure HTML solution something like this https://stackoverflow.com/a/36586206/118447
None of the posted solutions account for keyboard interaction.
You could simply add a disabled
attribute to your submit button when you submit (and remove it when the server responds). But you could still submit by textfield focus keyboard return.
(adding disabled
is still good UX though)
A more robust solution to prevent user interaction to the form is by preventing the events from ever reaching the elements.
A quick and dirty test shows you only have to preventDefault()
the events keydown
, click
, dragstart
and drop
. Also for mobile we need to blur()
any input element that gets focussed (only tested Android).
Here is a quick example (the last two methods are the gist of it):
const form = document.querySelector('form')
const status = document.querySelector('.status')
const showStatus = s=>status.textContent=s||''
const events = ['keydown','click','dragstart','drop','focus']
form.addEventListener('submit',e=>{
e.preventDefault()
lockForm(form,true)
showStatus('submitting')
setTimeout(fakeResponse,10000)
})
function fakeResponse(){
lockForm(form,false)
showStatus()
}
function lockForm(form,lock){
lock
?events.forEach(event=>form.addEventListener(event,preventDefault,false))
:events.forEach(event=>form.removeEventListener(event,preventDefault,false))
}
function preventDefault(e){
e.target.blur()
e.preventDefault()
}
body {
font-family: Arial, sans;
line-height: 100%;
}
label {
display: block;
margin-bottom: 10px;
box-shadow: 0 -1px 0 0 #EEE inset;
}
label:after {
content: '';
display: table;
clear: both;
}
input, textarea {
width: 50%;
float: right;
}
.status {
margin-top: 20px;
line-height: 200%;
text-align: center;
background-color: #FF0;
}
<form method="GET" action="https://httpbin.org/get">
<h3>form disables while submitting by stopping events</h3>
<label>text<input></label>
<label>text disabled<input disabled></label>
<label>textarea<textarea></textarea></label>
<label>textarea disabled<textarea disabled></textarea></label>
<label>checkbox<input type="checkbox"></label>
<label>checkbox disabled<input type="checkbox" disabled></label>
<button type="submit">submit</button>
</form>
<div class="status"></div>
<h3>elements outside the form still function normally</h3>
<label>text<input></label>
You could display a div taking all the page space when an AJAX request start (you can put a loading background in the middle and made it transparent) and hide it when done.
I think that you can also made the calls synchronous and not asynchronous, i think it will "block" the navigator until the request is done.
How about using a boolean to prevent multiple AJAX request from firing? You can also use the boolean to disable/enable elements.
Something like the following (see comments for my additions):
// set initial boolean state
var isRequestFinished = true;
function submitQuestionSearchForm(){
// check if previous request has finished
if(isRequestFinished){
// request just started, so set boolean to false
isRequestFinished = false;
jQuery.ajax({
async: true,
data: jQuery.param(questionSearchForm.serializeArray()),
dataType: 'script',
type: 'get',
url: "/questions",
success: function(msg){
questionSearchForm.removeClass("searching");
// request just finished, so set boolean to true
isRequestFinished = true;
},
error: function(msg){
questionSearchForm.removeClass("searching");
}
});
}
return true;
};
I didn't test this code, but it should point you in the right direction. Before you call jQuery.ajax()
you can disabled whatever elements you want, and then re-enable them in the success callback.
I actually ended up solving this problem in a different way to how i'd envisioned: instead of stopping the user from interacting with the form, i instead kick off a new ajax request and abort any previous ones. So, the server is still getting hit with multiple searches, but i ignore the response for all except the most recent.
Here's how i set the form up to make this happen (i've added a lot of whitespace to make it clearer):
<form
onsubmit="if(typeof(remoteSearch) != "undefined"){
remoteSearch.abort();
}
remoteSearch = jQuery.ajax({
async:true,
data:jQuery.param(jQuery(this).serializeArray()),
dataType:'script',
type:'get',
url:'/my-search-url'
});
return false;"
method="get"
action="/my-search-url"
>
There's no success block in here because i have a separate handler to deal with the results of an ajax call, but you could add one.
精彩评论