4

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 interacts 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}

Max Williams
  • 32,435
  • 31
  • 130
  • 197

5 Answers5

6

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

JOBG
  • 4,544
  • 4
  • 26
  • 47
  • Hello @Sjeiti as the same plugin suggest it just `adds elements to the DOM to give it both the appearance and behavior of blocking user interaction` it's your responsibility to implement further blocking =). I suggest a cleaner complement solution using just html like: https://stackoverflow.com/a/36586206/118447 – JOBG Mar 16 '18 at 16:32
1

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>
Sjeiti
  • 2,468
  • 1
  • 31
  • 33
  • At last someone takes keyboard inputs into account. Not everyone is using a mouse or touch. The keyboard is a often overlooked tool. An overlay does not block that. The question remains if one should block links too ... – habsi Jan 14 '19 at 18:28
0

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.

Max Williams
  • 32,435
  • 31
  • 130
  • 197
0

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.

Adirael
  • 9,398
  • 2
  • 22
  • 15
0

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.

Intelekshual
  • 7,444
  • 1
  • 21
  • 28
  • Thanks Intelekshual. Wouldn't this mean though that the user was making changes to the form which were just being ignored? So, it literally stops them submitting another request, but they would just think it was broken? I'm not attacking your proposal, just discussing :) thanks, max – Max Williams May 24 '10 at 09:33
  • Well, yeah. That's why I left it up to you to visually change what the form looks like during the request! ;) But now I see that you were asking for advice on how best to visually disable the form (which is essentially what the BlockUI plugin that Omar suggested does) and not necessarily disable further requests. Personally I'd just throw an activity indicator from http://ajaxload.info/ somewhere (maybe near the button used to submit the form?) and call it a day. – Intelekshual May 24 '10 at 15:02