skip to Main Content

The Context

I have a form that contains image components which are outputted via a MySQL database with PHP. I have some javascript fetch() functionality on different pages of the site to make the user experience better, but in these instances the functionality relates to functionality of the individual forms. In the instance below I have multiple components inside a single <form> instance.

The components are outputted with a while loop inside the form (it is done in this manner so that multiple images can be uploaded on a single form submission).

The Problem

I would like to use the javascript fetch() functionality when an image (i.e. image component) is deleted, in other words, have the component deleted without the page doing a hard refresh.

I’ve included an amended version of the javascript that worked when I initially had each image inside its own form (not a practical user experience in reality). I thought if I was selecting the image component inside the form i.e. the .upload-details-component with a forEach loop this would work? I can’t get this to work though.

Here is a screenshot of the grid itself. The ‘delete’ word is the delete button:

[![enter image description here][1]][1]

HTML

<form class="upload-details-form upload-form-js" method="post" enctype="multipart/form-data">
  <div class="image-component-wrapper">
    <!-- image-component-wrapper start -->

    <?php
      $user_id = $db_id; // database id imported from header.php
      $stmt = $connection->prepare("SELECT * FROM lj_imageposts WHERE user_id = :user_id"); $stmt->execute([ ':user_id' => $user_id ]); while ($row = $stmt->fetch()) { $db_image_id = htmlspecialchars($row['image_id']); ?>

      <div class="upload-details-component">
        <img src="image.jpg" />
        <div class="form-row">
          <input id="title-id-<?php echo $db_image_id; ?>" value="Image Title" type="text" name="image-title[]" placeholder="Image Title" />
        </div>
        <div class="form-row">
          <!-- BUTTON THAT DELETES AND IMAGE -->
          <button name="upload-details-delete" value="<?php echo $db_image_id; ?>" style="background: #cc1f1f;" class="remove-image">DELETE</button>
          <input type="hidden" name="image-id[]" value="<?php echo $db_image_id; ?>" />
        </div>
      </div>

    <?php } ?>
  </div>
  <!-- image-component-wrapper end -->

  <div class="form-row">
    <button id="upload-submit" type="submit" name="upload-submit">COMPLETE UPLOADS</button>
  </div>
</form>

JAVASCRIPT

// ---- FETCH
var forms = document.querySelectorAll(".upload-form-js"),
  // image component
  uploadDetailsComponent = document.querySelectorAll(".upload-details-component"),
  // delete button
  deleteButton = document.querySelectorAll(".remove-image");
// URL details
var myURL = new URL(window.location.href),
  pagePath = myURL.pathname;

if (uploadDetailsComponent) {

    uploadDetailsComponent.forEach((item) => {
      deleteButton.forEach((button) => {
        button.addEventListener("click", (e) => (item._button = button)); //store this button in the form element
      });

      item.addEventListener("submit", function (evt, btn) {
        evt.preventDefault();

        var formData = new FormData(this);
        if (this._button) {
          // submitted by a button?
          formData.set(this._button.name, this._button.value);
          // delete this._button; //this only needed if form can be submitted without submit button (aka submitted by javascript)
        }

        fetch(pagePath, {
          method: "post",
          body: formData,
        })
          .then(function (response) {
            return response.text();
            // }).then(function(data){
            //     console.log(data);
          })
          .catch(function (error) {
            console.error(error);
          });

        item.remove(); // removes component from HTML
      });
    });
} // end of if (upload-details-component)

3

Answers


  1. So what you can do is pass the ‘event’ object, or ‘e’, when a user submits the form.
    You can then use e.preventDefault() to stop the form from doing it’s default behaviour of submitting the form and then refreshing the page
    It’s a very common thing to do in React with from submissions

    What you can then do is have a function run in the background that does the fetch function that makes the HTTP DELETE request. On the frontend, you can then delete the HTML node that contains the image you want to delete.

    Just make sure you wait for the response of your fetch, using asycn/await, to determine if you should delete the HTML node from the DOM

    Instead of doing this:

    fetch(pagePath, {
          method: "post",
          body: formData,
        })
          .then(function (response) {
            return response.text();
            // }).then(function(data){
            //     console.log(data);
          })
          .catch(function (error) {
            console.error(error);
          });
    

    Rather do it so:

    const fetchData = async () => {
      try {
        const res = await fetch(pagePath, {
          method: "post",
          body: formData,
        })
    
        if(res.status === 200) {
          console.log(res.data)
        }
      } catch(err) {
        console.error(err)
      }
    }
    
    Login or Signup to reply.
  2. I made an event listener on the form. If one of the "delete image" buttons are clicked, I call the deleteImage() function with the id. In any other case (clicking on the submit button) the form will do an old school POST request.

    The deleteImage() function will do a fetch request. Here I use a fake data URL to make the example run here on ST. The URL should of course be your real URL endpoint for deleting. In the call back function (the last then()) I find the component with the right id and delete the component from the UI.

    const form = document.querySelector('form');
    
    const deleteImage = id => {
      let deleteReq = new Request(`data:application/json,{"delete":${id},"status":"ok"}`, {
        method: 'POST',
        body: `{"delete":${id} }`
      });
      fetch(deleteReq)
        .then(res => res.json())
        .then(json => {
          if (json.status == 'ok') {
            // get the image component first by getting the button
            // and then using closest() to get the div element
            let deleteBtn = document.querySelector(`button.remove-image[value="${json.delete}"]`);
            let uploadComponent = deleteBtn.closest('div.upload-details-component');
            // remove the component by setting innerHTML empty
            uploadComponent.innerHTML = '';
          }
        });
    };
    
    form.addEventListener('submit', e => {
      // that button was clicked
      let button = e.submitter;
      // there could be more buttons in this form
      // switch by the name of the button
      switch (button.name) {
        case 'upload-details-delete':
          // stop the form from submitting
          e.preventDefault();
          // call deleteImage with value from the button
          deleteImage(button.value);
          break;
      }
      // in any other situation continue the submit event
      e.preventDefault(); // for testing ONLY! this line should be removed in production
    });
    <form class="upload-details-form upload-form-js" method="post" enctype="multipart/form-data">
      <div class="image-component-wrapper">
        <!-- image-component-wrapper start -->
        <div class="upload-details-component">
          <img src="image.jpg" alt="Image 1" />
          <div class="form-row">
            <input id="title-id-1" value="Image Title" type="text" name="image-title[]" placeholder="Image Title" />
          </div>
          <div class="form-row">
            <!-- BUTTON THAT DELETES AND IMAGE -->
            <button name="upload-details-delete" value="1" style="background: #cc1f1f;" class="remove-image">DELETE</button>
            <input type="hidden" name="image-id[]" value="1" />
          </div>
        </div>
        <div class="upload-details-component">
          <img src="image.jpg" alt="Image 2" />
          <div class="form-row">
            <input id="title-id-2" value="Image Title" type="text" name="image-title[]" placeholder="Image Title" />
          </div>
          <div class="form-row">
            <!-- BUTTON THAT DELETES AND IMAGE -->
            <button name="upload-details-delete" value="2" style="background: #cc1f1f;" class="remove-image">DELETE</button>
            <input type="hidden" name="image-id[]" value="2" />
          </div>
        </div>
      </div>
      <!-- image-component-wrapper end -->
      <div class="form-row">
        <button id="upload-submit" type="submit" name="upload-submit">COMPLETE UPLOADS</button>
      </div>
    </form>
    Login or Signup to reply.
  3. If your desired outcome is to have the form data submitted/saved when the delete button is clicked under an image (i.e. the same behaviour as submitting the form), then the following code should achieve close to what you want.

    When a user clicks delete, it will:

    1. disable any inputs on the image card being deleted
    2. gather the FormData (skipping over any of the disabled inputs as we don’t want to include the current image data in payload)
    3. post the formData using fetch
    4. if POST request is successful, it will remove the card from the DOM
    5. if POST request is unsuccessful, it will reenable the disabled inputs
      const form = document.querySelector("form");
      var myURL = new URL(window.location.href),
        pagePath = myURL.pathname;
    
      // *** Delete button logic ***
      [...document.getElementsByClassName("upload-details-component")].forEach(
        (card) => {
          const deleteButton = card.querySelector("button.remove-image");
    
          deleteButton.addEventListener("click", async (event) => {
            event.preventDefault();
    
            const cardInputs = card.querySelectorAll("input");
    
            // Disable inputs on card so they aren't included in formData
            // (don't want to remove until we know POST request works)
            cardInputs.forEach((input) => {
              input.disabled = true;
            });
    
            try {
              // Get current formData
              const formData = new FormData(form);
    
              // Update request
              await fetch(pagePath, {
                method: "post",
                body: formData,
              });
    
              // Remove card from DOM if POST request succeeds
              card.remove();
    
            } catch (error) {
              // Enable inputs again if POST request fails
              cardInputs.forEach((input) => {
                input.disabled = false;
              });
              console.error(error);
            }
          });
        }
      );
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search