Search code examples
javascriptphpajaxformspreventdefault

AJAX infinite preventDefault() on AJAX-responded form (vanilla JS)


I have reviewed several great Questions along the way, so many that I earned my badge for 40 votes in one day. But, nearly all Questions use a non-Vanilla dependency like jQuery or don't address my specific need. Here are two examples of wonderful, yet to-my-sorrow unrelated, Questions:

I'm surprised this Question has not been asked because I don't regard myself to be so imaginative. If this Question is already out there, I want to please open a Meta discussion on how to update that Question so searches can find it.

The situation:

This is Vanilla JS, no dependencies.

The script uses preventDefault() to interrupt a form-submit button identified by id and call AJAX instead. AJAX works and changes content correctly. Then, the problem begins...

This AJAX responds with a replaced div containing the same form that preventDefault() interrupts. But, AJAX doesn't recognize that updated form-submit button after the AJAX response JS-alters the div containing that form with the same id.

Workflow:

  1. HTML -> <form id="same"> (success)
  2. <form id="same"> -> AJAX (success, first AJAX call)
  3. AJAX -> <form id="same"> (success, first AJAX response)
  4. <form id="same"> -> AJAX (broken, second AJAX call)

It works the first time only; I need it to work infinitely.

Note, I need to process this specific form. Other elements, including possible other forms, may be included in this div that AJAX changes.

The code:

index.php:

(AJAX adapted from MDN - Sending forms through JavaScript: Using FormData bound to a form element)

  <script>
  window.addEventListener( "load", function () {
    function sendData() {
      const AJAX = new XMLHttpRequest(); // AJAX handler
      const FD = new FormData( form ); // Bind to-send data to form element

      AJAX.addEventListener( "load", function(event) {
        document.getElementById("ajax_changes").innerHTML = event.target.responseText;
      } ); // Change HTML on successful response
      AJAX.addEventListener( "error", function( event ) {
        document.getElementById("ajax_changes").innerHTML =  'Oops! Something went wrong.';
      } );
      AJAX.open( "POST", "ajax_responder.php" ); // Send data, ajax_responder.php can be any file or URL
      AJAX.send( FD ); // Data sent is from the form

    } // sendData() function

    const form = document.getElementById( "ajaxForm" ); // Access <form id="ajaxForm">, id="ajaxForm" can be anything
    form.addEventListener( "submit", function ( event ) { // Takeover <input type="submit">
      event.preventDefault();
      sendData();
    } );

  } );
  </script>

  <div id="ajax_changes">Replace me with AJAX<br>
    <form id="ajaxForm">
      <input type="text" value="AJAX" name="foo">
      <input type="text" value="5" name="bar">
      <input type="submit" value="Form AJAX!">
    </form>
  <!-- Possible other HTML elements and/or forms I do not want to process with that same AJAX -->
  </div>

ajax_responder.php :

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $foo = $_POST['foo'];
  $bar = $_POST['bar'];

echo '
'.$foo.'
<br>
'.$bar.'
<br>
<form id="ajaxForm">
  <input type="text" value="'.$foo.'" name="foo">
  <input type="text" value="'.$bar.'" name="bar">
  <input type="submit" value="Form AJAX!">
</form>
<!-- Possible other HTML elements and/or forms -->
';
}

The question

What is the "right" way to get AJAX to keep acting on the same response-contained form-submit element?

I have considered ways that do not respond with the same form:

  • Having AJAX only respond and change specific, small elements
  • Relocating the form outside of the AJAX-changed content

But both of those seem complicated.

There must be some way to tell AJAX to preventDefault() on a form-submit button after that button has been updated by AJAX. I just don't know what that way is.

Or, is there another solution that still involves Vanilla JS?


Solution

  • You are binding the eventListener to the <form> ajaxForm. Then you replace it (= remove <form> from DOM and add new <form> to the DOM). In that case the eventListener is also removed.

    To have an eventListener on the new form, you can either:

    • Replace only the content of the <form>. In that case the DOM element isn't removed and so the eventListener keeps intact (don't forget to remove the <form> tag in the php response). This is imho the easiest way.

    document.getElementById("ajaxForm").innerHTML=
                                 event.target.responseText;
    

    • Bind a new eventListener everytime the <form> is changed:

    AJAX.addEventListener( "load", function(event) {
            document.getElementById("ajax_changes").innerHTML =
                                 event.target.responseText;
    //=====> bind new eventListener here <======
          } );
    

    You can create a function that can be called on load as well in the ajax response

    function addListenerToForm(){
        document.getElementById( "ajaxForm" ).addEventListener( "submit", function ( event ) {
           event.preventDefault();
           sendData();
        });
      }
    

    • bind the eventListener once to the container <div>

    document.getElementById( "ajax_changes" ).addEventListener( "submit", function ( event ) {
      event.preventDefault();
      sendData();
    });
    
    function sendData(){
       const AJAX = new XMLHttpRequest(); // AJAX handler
       const form = document.getElementById( "ajaxForm");               
       const FD = new FormData( form );
       ...
       }