divillysausages divillysausages - 3 months ago 48
Ajax Question

Delayed form submit, ajax, & popup blocker

I have a simple signup form on my site where users enter their email and name.

To avoid having a double signup error, I have a simple Ajax call to see if the email is already there. If so, I do nothing, if not, I submit the form.

My problem is that the form has a target of

_blank
, so the delayed submit call triggers the popup blocker on browsers.

Now, I know why this is happening - I'm no longer in the trusted click event. I changed the Ajax call to be synchronous, but I'm getting a warning that sync Ajax calls on the main thread are deprecated.

So my current options seem to be:


  • Use sync Ajax call anyway, but eventually it'll stop working

  • Remove the target
    _blank
    on my form, so it redirects on the same page (bad UX)

  • ???



Is there a better way to get around the popup blockers seeing my delayed form submit as a popup?

Code:

HTML:

<form action="//external-site.com" method="post" target="_blank" onsubmit="onSubmitSignupForm(event)">
...
</form>


JS:

function onSubmitSignupForm( e )
{
e.preventDefault();

var request = new XMLHttpRequest();
request.open( 'POST', 'checkEmail.php', true );
request.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8' );
request.onload = onLoad;
request.onerror = onError;

// NOTE: params = urlencoded params recovered from form
request.send( params );
}

function onLoad( e )
{
var request = e.target;
if( request.status == 200 )
{
var data = JSON.parse( request.responseText );
if( data != null && data.success )
{
onFinish( request, data );
return;
}
}

// if we get here, then it means that the request failed, or there was a problem
onError( e );
}

function onError( e )
{
var request = e.target;
var data = JSON.parse( request.responseText );
onFinish( request, data );
}

function onFinish( request, data )
{
var success = ( data != null ) ? data.success : false;
if( !success )
$form.submit(); // blocked by popup
}

Answer

OK, so I have a solution, though it may be a little specific to my situation and is helped by the fact that I redirect the user if they're already signed up.

I basically open a new, intermediary page when the form is initially submitted (thus getting around the popup issue as it's in a trusted event) and populate a hidden form on that page (with a link to manually submit it if the user wants). The ajax call is sent, and depending on the results, I either submit the form on page 2, or redirect if they're already signed up.

  1. User submits form on Page1
  2. Stop event, and open Page2, which has the same form, albeit hidden
  3. Populate the form on Page2 with the values from the form on Page1
  4. Show a message on Page2 saying we're working on it, along with a link to manually submit the hidden form (good UX)
  5. On Page1, fire Ajax request to see if the user is already subscribed
  6. When Ajax results come in, 2 things can happen
  7. (a) The user isn't signed up - form on Page2 is submitted (can optionally redirect Page1 to the content)
  8. (b) The user is already signed up - redirect Page2 to the content

The only things to watch out for is to make sure that Page2 is fully loaded before filling the form and the Ajax results come back. For that, I use a simple boolean system with an onload event.

The code for that looks something like this:

function onSubmitSignupForm( e )
{
    e.preventDefault();
    var $form   = e.target;

    var name = ...; // get name value from form
    var email = ...; // get email value from form

    // open a new window that we'll be able to control and redirect in (otherwise, we'll
    // get stung with a trying to open a popup, as when we get the result, we'll no longer
    // be in the trusted click event)
    var win = window.open( '/signup/', '_blank' );
    win.onload = function() {
        win.setDetails( name, email ); // fill in the hidden form
    }

    // check if the email is already signed up
    // NOTE: result might be null if we couldn't reach the server
    checkEmail( name, email, function onFinish( result ){

        // set the result in the other window
        // NOTE: the window might not have loaded yet
        if( typeof win.setResult == 'undefined' )
        {
            var f       = win.onload; // replace the one we already have
            win.onload  = function(){
                if( f )
                    f();
                win.setResult( result );
            }
        }
        else
            win.setResult( result );
    });
}

Both setDetails and setResult will try to call a third function handleResult that will only continue booleans set by both functions are true.