2

I'm implementing a 'login to continue' functionality when submitting a form after the user's session has expired, but I've run into something I don't quite understand.

When I add the submit listener to the form, it requires 3 submit attempts before it submits (while logged in): the 1st time, nothing is printed to the console; the second submit prints 'Preventing form submission', and the 3rd finally allows the submit to proceed. After that, every 2 submits will function, with the first displaying the console line above.

However, if I pass 'this' to the onreadystatechange function, it works exactly as I expect each and every time - the only problem then is that I get a 'SyntaxError: missing formal parameter' error in the JS console, obviously because I am providing a value in place of a parameter name.

So my question is: how do I do this properly, and why doesn't it work if I don't provide 'this' (which is the current form element to which the event listener is being attached) to the function?

I don't want to use JQuery or any external libraries for this particular project, so please do not use any in your answers.

form.addEventListener('submit', function(e) {
    var xmlhttp = new XMLHttpRequest();
    // xmlhttp.onreadystatechange = function() { // doesn't work the way I want
    xmlhttp.onreadystatechange = function(this) { // works the way I want
        if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            if (xmlhttp.responseText === 'true') {
                form.setAttribute('allow_submit', 'true');
                form.submit();
            } else {
                form.setAttribute('allow_submit', 'false');
            }
        }
    }
    xmlhttp.open("POST", "xml_request_router.php", true);
    xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    xmlhttp.send('route_id=active_session_check');
    console.log("Preventing form submission");
    if (form.getAttribute('allow_submit') !== 'true') {
        e.preventDefault();
    }
});

I've tried using a callback and passing parameters to that, but behaved exactly the same as the above. Here was my attempt at that:

function stateChanged(xmlhttp, form) {
    if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
        if (xmlhttp.responseText === 'true') {
            form.setAttribute('allow_submit', 'true');
            form.submit();
        } else {
            form.setAttribute('allow_submit', 'false');
        }
    }
}

// and here's how I called it from the event listener:
xmlhttp.onreadystatechange = function() { // adding 'this' results in correct behavior
    stateChanged(xmlhttp, form);
}

EDIT: Here is how I am retrieving the form elements:

var forms = document.getElementsByTagName('form');
for (i = 0; i < forms.length; i++) {
    var form = forms[i];
    // rest of code follows from here

EDIT: Just in case anyone else comes here looking for a solution, the only way I was able to get the closures to work properly was to convert the NodeList to an Array and iterate over that:

function nodeListToArray(nl) {
    var i = nl.length, arr = new Array(i);
    for(; i--; arr[i] = nl[i]);
    return arr;
}
var forms = document.getElementsByTagName('form');
nodeListToArray(forms).forEach(function(form) {
    form.addEventListener('submit', function(e) {
        // this way does NOT work:
        // return function(e, this) {
            // original logic here
        // }
        // but this way DOES:
        return notSureWhyThisIsNecessary(e, this);
    });
});

function notSureWhyThisIsNecessary(e, form) {
    // original logic here, i.e. xmlhttp request etc.
}

So if like myself JavaScript closures (specifically in cases like the above) are still somewhat of a mystery to you even after reading loads of articles on them, perhaps the code snippet above will help. Cheers.

Brian
  • 336
  • 1
  • 9
  • Btw, I'm sure the title could be improved for clarity - I'm open to suggestions. – Brian Apr 11 '16 at 19:53
  • Where do you set `form`? – Barmar Apr 11 '16 at 20:35
  • It's retrieved from the document, actually in a loop as I attach the listener to all forms on the page. – Brian Apr 11 '16 at 20:45
  • It's important that we see how you're doing it, because you may be running into the problem in http://stackoverflow.com/questions/1451009/javascript-infamous-loop-issue – Barmar Apr 11 '16 at 20:46
  • I've edited the question to include it. – Brian Apr 11 '16 at 20:50
  • 1
    I was right, that's the problem you're having. I hope the linked question helps you. – Barmar Apr 11 '16 at 20:51
  • Well... dang it, you're right. I've been reading similar posts all morning but for whatever reason couldn't put 2 and 2 together. Simply changing to 'return function() { var xmlhttp... rest of code }' fixed it. JavaScript and its closure madness strikes again! – Brian Apr 11 '16 at 20:58
  • Hm, doesn't actually seem to be working - it just allows the form to always be submitted. – Brian Apr 11 '16 at 21:33
  • [Converting](http://stackoverflow.com/questions/3199588/fastest-way-to-convert-javascript-nodelist-to-array#3199627) my forms NodeList to an array and using the Arrays.forEach syntax is working correctly. – Brian Apr 11 '16 at 21:41

0 Answers0