Removing an individual HTML element within a form using JavaScript Fetch() in the presence of multiple components

The Situation

Having a form that includes image components generated from a MySQL database with PHP, I've implemented javascript fetch() functionality on different pages of the website to enhance user experience. However, in cases where the functionality is specific to individual forms, such as having multiple components within a single <form>, I encounter challenges.

Utilizing a while loop to output the components inside the form allows for multiple images to be uploaded in a single submission. The issue arises when attempting to apply the javascript fetch() function for deleting an image component without requiring a full page reload.

I attempted modifying the javascript code originally designed for each image being in its own form, which is not practical from a user experience perspective. My approach involved selecting the image component within the form (i.e., .upload-details-component) using a forEach loop, but this method did not yield the desired results.

A visual representation of the grid can be seen below, with the 'delete' button signifying the delete action:

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

HTML snippet:

<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 snippet:

// ---- 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();
          })
          .catch(function (error) {
            console.error(error);
          });

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

Answer №1

One strategy is to pass the 'event' object, or 'e', when a user submits the form. By using e.preventDefault(), you can prevent the default behavior of the form submitting and refreshing the page, a common practice in React for form submissions.

After preventing the default behavior, you can have a function execute in the background to perform a fetch operation that sends an HTTP DELETE request. Once the response is received, you can then remove the HTML node containing the image you wish to delete from the frontend.

It's important to await the response of the fetch operation using async/await before deciding whether to delete the HTML node from the DOM.

Instead of the traditional approach shown below:

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

Consider utilizing the following approach:

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)
  }
}

Answer №2

If you want the form data to be submitted or saved when the delete button is clicked beneath an image (similar behavior to submitting the form), then the code below can help achieve your goal.

Upon clicking delete, the following steps will be taken:

  1. Any inputs on the image card being deleted will be disabled
  2. The FormData will be collected (excluding any disabled inputs to prevent including current image data in the payload)
  3. The formData will be posted using the fetch function
  4. If the POST request is successful, the card will be removed from the DOM
  5. If the POST request fails, the disabled inputs will be re-enabled
  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);
        }
      });
    }
  );

Answer №3

I have implemented an event listener on the form. If any of the "delete image" buttons are clicked, it triggers the deleteImage() function with the corresponding id. In any other scenario (such as clicking the submit button), the form will carry out a traditional POST request.

The deleteImage() function performs a fetch request. For demonstration purposes on ST, I am using a fake data URL. Ideally, you should replace this with your actual URL endpoint for deletion. In the callback function (the last then()), I locate the component with the correct id and remove it 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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for sorting through existing data without resorting to a remote call - Material Table repository by mbrn (mbrn/material

Currently I am using the mbrn/material-table library which includes filtering on a column and remote data feature. However, when I apply a filter term, the table sends an API call to the server with the filter criteria in the query object. My desired outco ...

Is it possible to utilize localStorage.getItem within Next.js while using redux toolkit?

While I successfully used localStorage.setItem() inside the redux toolkit slice, I encountered an issue when trying to use localStorage.getItem(). The error message "local storage is not defined" popped up, preventing me from accessing the stored data. imp ...

Angular JS form cloning feature

I'm facing a challenge in creating a dynamic form with multiple sections. At the end of the form, I want to include a "Add New Form" button which will duplicate the existing form below it, each with its own save button. What's the most effective ...

What is the best way to embed a variable within a Directive HTML block for seamless access by the Controller?

I am facing a challenge with my custom directive that inserts an HTML block onto the page. The issue is to have a variable within this block that can be manipulated based on an ng-click function in my controller. This is what my directive looks like: .di ...

SymPress Ajax File Upload

My attempt to upload files using ajax calls is causing issues when I try to save them, only the relationship with 'anagrafic' gets saved. It seems like the object UploadFile is not being loaded properly. I followed the Symfony cookbook instructio ...

How can I activate a function or pass a selected value to a different scope variable using AngularUI Bootstrap Datepicker?

Check out this AngularUI Datepicker demo on Plunker: http://plnkr.co/edit/DWqgfTvM5QaO5Hs5dHco?p=preview I'm curious about how to store the selected value in a variable or trigger another function when a date is chosen in the field. I couldn't ...

Trouble parsing JSON in Classic ASP

After receiving a JSON Response from a remote server, everything looks good. I discovered an helpful script for parsing the JSON data and extracting the necessary values. When attempting to pass the variable into JSON.parse(), I encountered an error which ...

"Enhance Your Online Shopping Experience with Woocommerce Ajax Filters and

I have a structure that looks like this: Company Google Apple Microsoft Material Wood Metal Plastic Essentially, Brand & Material are attributes in the Woocommerce system, while the rest are variables. I am currently working on implementing AJAX fi ...

Achieving a single anchor link to smoothly scroll to two distinct sections within a single page using React

There is a sidebar section alongside a content section. The sidebar contains anchor links that, when clicked, make the corresponding content scroll to the top on the right-hand side. However, I also want the sidebar link that was clicked to scroll to the ...

Obtaining the Value of Input Text

Let's say you have the following HTML code: <form> Enter hash here: <input type="text" name="hash"> <button type="submit" formaction="/tasks/">Retrieve Url</button> </form> Is there a way ...

Fade out the div element when clicked

For my game project, I needed a way to make a div fade out after an onclick event. However, the issue I encountered was that after fading out, the div would reappear. Ideally, I wanted it to simply disappear without any sort of fade effect. Below is the co ...

The JQuery Twitter client is experiencing issues in Firefox and is currently not functioning properly

I have created a small script to retrieve my most recent tweets using JQuery's $.getJSON() method. Interestingly, this script functions perfectly in Chrome and Safari, but it fails to display anything in Firefox! Take a look at the code snippet belo ...

Initiate an AJAX request when the dropdown button is clicked

I have successfully created a form that is populated with values from the Category model in a dropdown box. It's functioning as expected. The form is displayed in the template like this: {{ language_form }}. This also worked as intended. Now, I would ...

Creating wrapped text in Three.js

Is there a way to wrap a Text3d object around a 3D or 2D Path in Three.js? I have looked into some tutorials for r.49 of Three.js and it appears that the current version does not have support for this feature. While I am able to create the text and extru ...

Is there a way to retrieve the current route on a custom 404 page in Next.JS?

I have set up a custom 404 page for my Next.JS application (404.js). I want to display a message stating The route <strong>/not-a-route</strong> does not exist, but when I use Next.js's useRouter() and router.pathname, it incorrectly ident ...

The Modal Component remains unchanged despite being connected to useContext for data, as the Array is being modified through the useReducer Hook

In search of an elegant solution, the title speaks for itself - I am on a mission to create a practice food order App (consider me a novice in this field). This App boasts a Cart feature where users can add items for ordering. The nav-bar conveniently disp ...

Next.js deployments on Vercel are encountering issues with resolving local fonts

Currently, I am facing an issue while trying to incorporate next/fonts into my project in next.js 13.3. The setup works perfectly on my local machine, but as soon as I deploy it to Vercel, a build error arises with the message Module not found: Can't ...

Implementing socket.io in a node.js server with express to showcase a welcome message upon user login

Currently, I am in the process of creating a web chat client using socket.io. However, before I can proceed with this project, I need to establish communication between my server and the website on localhost. Despite receiving the 'listening' me ...

What is the most efficient way to transfer a value from the main application file to a router file using the express framework

Currently, I am developing an Express application with multiple routes. To ensure modularity, each route will have its own file stored in a dedicated routes folder. One challenge I encountered is sharing a common value across all routes. Specifically, I n ...

Using React hook form to create a Field Array within a Dialog component in Material UI

I have a form with custom fields added via Field Array from react-hook-form, and everything is functioning properly. However, I recently implemented drag and drop functionality for the property items to allow reordering them. Due to the large number of fie ...