在表单中的一个特定按钮上使用 JavaScript Fetch 和 FormData

Use JavaScript Fetch and FormData On One Specific Button In A Form

我有一个表单可以通过 PHP 将图像文件名和其他信息提交到 MySQL 数据库,该数据库在后端一切正常。在前端,我使用 JavaScript fetch() API 来确保在执行某些功能时不会硬刷新页面(特别是如果在提交表单之前删除图像) .

下面的 JavaScript 防止页面在使用 .remove-image 按钮删除图像组件时进行硬刷新,然后它还会删除图像组件的 HTML 作为想要的。但是,当提交主表单时,此代码会产生副作用:

问题

当通过 .upload-details 按钮提交主表单时,这也受到 fetch() 代码的影响。我只希望在单击删除按钮时触发获取代码,而不是在单击主提交按钮时触发。我想我可以通过将 if (fetchForms) {} 代码包装在 .remove-image 按钮上的点击事件侦听器中来实现这一点,但这也不起作用(我在下面包含了这段代码)。它似乎仍然触发了 e.submitter.closest('.upload-details-component').remove() 行代码?

注意:此问题仅在单击 .remove-image 按钮后发生。如果从未单击 .remove-image 按钮,主 .upload-details 按钮将正常工作。

我收到的错误消息是“TypeError: Cannot read properties of null (reading 'remove') at app.js:234:77”,它与这行代码相关 e.submitter.closest('.upload-details-component').remove()

我的问题

如何才能使 fetch() 代码仅在单击删除 .remove-image 按钮时有效,而不是在单击主 .upload-details 按钮时有效?

当我在按下主 .upload-details-component 按钮后进行硬页面刷新时,虽然它在控制台中抛出错误,但表单提交已经发生。所以我想我需要一种方法来在单击另一个按钮时关闭 fetch()?

JavaScript

let fetchForms = document.querySelectorAll('.fetch-form-js'),
removeImageButton = document.querySelectorAll('.remove-image')

// URL details
var myURL = new URL(window.location.href),
pagePath = myURL.pathname


removeImageButton.forEach((button) => {
    button.addEventListener('click', () => {

        if (fetchForms) {
            fetchForms.forEach((item) => {
                item.addEventListener('submit', function (e) {
                    e.preventDefault();
                    
                    const formData = new FormData(this);

                    if (e.submitter) {
                        formData.set(e.submitter.name, e.submitter.value);
                    }

                    fetch(pagePath, {
                        method: "post",
                        body: formData
                    })
                    .then(function(response){
                        // only remove component if the 'remove-image' button is clicked
                        e.submitter.closest('.upload-details-component').remove()
                        
                        return response.text();
                    // }).then(function(data){
                    //     console.log(data);
                    }).catch(function (error){
                        console.error(error);
                    })

                })

            });

        } // end of if (forms)

    }) // 'remove' button clickEvent
}) // removeImageButton forEach

HTML

注:图像分量输出时用while循环,因为几乎总是不止一个。为了简单起见,我在下面只显示了一个组件。

<form class="fetch-form-js" method="post" enctype="multipart/form-data">

    <!-- IMAGE DETAILS COMPONENT. THESE COMPONENTS ARE OUTPUTTED WITH A WHILE LOOP --> 
    
    <div class="upload-details-component">                
        <div class="form-row">
            <label for="image-title">Image Title</label>
            <input id="title-title>" class="input-title" type="text" name="image-title[]">
        </div>
        <div class="form-row">
            <label for="tags">Comma Separated Image Tags</label>
            <textarea id="tags" class="text-area" type="text" name="image-tags[]"></textarea>
        </div>
        <div class="form-row">
            <!-- DELETE BUTTON -->
            <button name="upload-details-delete" value="12" class="remove-image">DELETE</button>
            <input type="hidden" name="image-id[]" value="12">
        </div>
    </div>

    <!-- IMAGE DETAILS COMPONENT - END -->

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

</form>

根据我的理解,您的 high-level 目标是:

  1. 单击任何 .remove-image 按钮时,通过取消默认浏览器行为(即 post + 页面重定向)来处理它,而是对您的服务器端点执行 fetch 调用, 如果成功,删除与被删除图像对应的表单部分。
  2. upload-submit 行为保留为默认行为(即 post + 页面重定向)

让我先介绍一下您的代码当前的工作方式以及您看到当前行为的原因:

  1. 当页面加载时,您将在页面上的所有 .remove-image 按钮上注册一个“点击”处理程序:

    let removeImageButton = document.querySelectorAll(".remove-image");
    removeImageButton.forEach((button) => {
       button.addEventListener("click", () => {
         // ...
       })
    })
    
  2. 每当单击任何 .remove-image 按钮时,它将在 all .fetch-form-js 表单上注册一个表单 submit 处理程序在页面加载时找到的。单击表单中的 any <button type="submit"> 元素(包括 upload-submit 按钮)时将执行此处理程序。处理程序取消默认的浏览器重定向行为,手动执行 fetch 请求,然后尝试 remove .upload-details-component 元素,该元素是按下提交表单的任何按钮的最近父元素:

    // When the page loads..
    let fetchForms = document.querySelectorAll(".fetch-form-js")
    
    // After any .remove-button is clicked...
    fetchForms.forEach((item) => {
       // ...register this handler on all .fetch-form-js forms.
       item.addEventListener("submit", function (e) {
           // When a .fetch-form-js is submitted (including by pressing and upload-submit button...
    
           // ...prevent browser redirect...
           e.preventDefault() 
    
           // ...
    
           // ...execute a fetch request...
           fetch(/* ... */)
               // ... and, if successful, hide the .upload-details-component element that is
               // the closest parent of whatever button was clicked to submit the form.
               .then( function(response) {
    
                   e.submitter.closest(".upload-details-component").remove()
                   // ...
               })
           // ...
       })
    })
    
    

这里的主要问题是 item.addEventListener("submit") 处理程序将在 所有 提交按钮上执行,包括 upload-submit 按钮(你想保留它默认行为)。这正是您想要的。

第二个问题是执行时,因为单击了upload-submit按钮,e.submitter.closest(".upload-details-component")将是null(因为有没有 .upload-details-componentupload-submit)

的父级

这里有一种重构方法可以避免这两个问题:

const fetchForms = document.querySelectorAll(".fetch-form-js");

// Simplify things by only registering "submit" events.
fetchForms.forEach((item) => {
  item.addEventListener("submit", function (e) {
    if (
      e.submitter &&
      // This will be "true" if the "remove-image" button is clicked to submit the form,
      // and false if the ".upload-submit" button is clicked to submit the form.
      // This way, you'll keep browser default behavior for the ".upload-submit" button 100% of the time.
      e.submitter.classList.contains("remove-image")
    ) {
      e.preventDefault();
      const formData = new FormData(this);
      formData.set(e.submitter.name, e.submitter.value);
      fetch(pagePath, {
        method: "post",
        body: formData
      })
        .then(function (response) {
          e.submitter.closest(".upload-details-component").remove();
          return response.text();
        })
        .catch(function (error) {
          console.error(error);
        });
    }
  });
});

请参阅 this codesandbox 了解工作示例以及 side-by-side 与原始代码的比较。