1

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?

Jesse
  • 750
  • 1
  • 9
  • 25
  • 1
    You are binding the eventlistener to the form `ajaxForm`. Then you replace it (= remove and add new), and so also the eventlistener disappears. Try either replacing only the form contents or bind a eventlistener to the container div. – Michel Jun 30 '20 at 07:17
  • You can check in de developper tool. See if there is an event attached to the form after replacing it with the ajax call. – Michel Jun 30 '20 at 07:19
  • @Michel Binding it to the container div breaks the first AJAX call (workflow step 2) – Jesse Jun 30 '20 at 07:23
  • @Michel Is there a way to have the AJAX response trigger an event that makes AJAX listen to the new submit button? – Jesse Jun 30 '20 at 07:25
  • Yes, attach a new listener, like you do the first time. – Michel Jun 30 '20 at 07:28
  • 1
    When you attach it to the `div` it breaks because then you submit the div, not the form. The event bubbles up, so it does reach te div, you only have to apply some logic to submit the form instead of the container div. – Michel Jun 30 '20 at 07:33
  • @Michel What would that logic look like? Could you provide an answer so I can approve it? – Jesse Jun 30 '20 at 07:34
  • 1
    _“Binding it to the container div breaks […]”_ - what _exactly_ did you change then? If you just made `const form = document.getElementById( "ajaxForm" )` into `const form = document.getElementById( "ajax_changes" )` now, then of course you will need to modify this part accordingly, `const FD = new FormData( form );` – CBroe Jun 30 '20 at 07:48
  • @CBroe7 Modify it to what specifically? If I change it to `const FD = new FormData( div );` that no longer works because I need to capture `form` data, not `div` data. Thanks for the idea, but my Question is about how to make AJAX recognize that updated `submit` button, this would simply reprocess the entire `div` along with whatever else may or may not be in it, then I would no longer need the `div` wrapper – Jesse Jun 30 '20 at 07:59
  • I clarified the Question so as to have AJAX isolate the specific `form`. I'm open to creative ways of solving this, but it is a question about recapturing specific `form` data within an AJAX-updated `div`, not the entire `div` itself. Though that was inherent in the Question, from the comments I thought it right to clarify as much. Thank you all for commenting. – Jesse Jun 30 '20 at 08:06
  • 1
    The answer on your chaged question is: no there isn't. As soon as you remove the form from the DOM, the eventListener is removed. – Michel Jun 30 '20 at 08:12
  • @Michel Your answer is very on-point, direct, explanatory, and solves my problem. Your comments helped along the way. You are a model to follow, gentleman, and scholar. – Jesse Jun 30 '20 at 08:55

2 Answers2

1

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 );
   ...
   }
Michel
  • 4,076
  • 4
  • 34
  • 52
0

Given the above answer's solution "Bind a new eventListener everytime the <form> is changed",

I changed const

const form = document.getElementById( "ajaxForm" );

to var

var form = document.getElementById( "ajaxForm" );

Then placed it with its listener in this line

//=====> bind new eventListener here <======

without any declaration as

form = document.getElementById( "ajaxForm" );
  form.addEventListener( "submit", function ( event ) {...

And it worked! Many thanks! I am a happy coding potato.

My new JavaScript was:

  <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

      AJAX.addEventListener( "load", function(event) {
        document.getElementById("ajax_changes").innerHTML =
          event.target.responseText;
          // Bind a new eventListener everytime the <form> is changed:
          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();
          } ); // End new event listener
      } );
    } // sendData() function

    var 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>
Jesse
  • 750
  • 1
  • 9
  • 25