Search code examples
javascripthtmlajaxfetchform-data

Using JavaScript Fetch() and formData() To Update A Database


I have some javascript fetch() on a project and, although it works, the issue I have is, (although I understand the basics of fetch and formData), the piece of code seems overly complicated for what it is doing, and I don't understand how it is working?

I have some PHP that when a download button is clicked it updates a 'downloads' count in a MySQL database. I've included the PHP at the end, but it isn't really relevant to the issue.

What I don't understand is, if the form button is assigned to a downloadButton variable, why the code is written how it is, particularly on the click event listener?

It would be amazing if someone could explain this to me and possibly show a better way of doing it using the downloadButton variable?

I've added code comments on the parts I don't understand.

Also please feel free to explain this as if you are talking to an idiot - I am new to JavaScript/software development.

Note: Using JSON / URL endpoints is not an option.

JavaScript

let forms = document.querySelectorAll('.download-form-js'),
    downloadButton = document.querySelectorAll('.dl-button')

// URL details
let myURL = new URL(window.location.href), // get URL
pagePath = myURL.pathname // add pathname to get full URL

if (forms) {
        forms.forEach(item => {
            
            // Why is it done like this when a variable is assigned to the download button above?
            item.querySelectorAll('[type="submit"], button').forEach( button => {
                button.addEventListener("click", e => item._button = button); //store this button in the form element?
            })

            // The 'btn' parameter in this function isn't used ?
            item.addEventListener("submit", function(evt, btn) {

                evt.preventDefault();

                const formData = new FormData(this);

                // Can the parameters inside the '.set()' function not be done with the 'downloadButton' variable?
                if (this._button) // submitted by a button?
                {
                    formData.set(this._button.name, this._button.value);
                    delete this._button; // have no idea why this is even used?
                }

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

        })

    })

} // end of if (forms)

HTML This form appears at least twice on any page.

<section>
    <div class="image-wrapper">
        <img src="image.jpg" alt="image">
    </div>
    <form class="download-form-js" enctype="multipart/form-data" method="post">
        <div class="dl-button-wrapper">
                <label for="download-button">Download</label>
                <button id="download-button" type="submit" name="download" title="Download" value="12" class="dl-button"></button>
            <input type="hidden" name="image-id" value="12">
        </div>
    </form>
</section>

PHP (not really relevant, but thought I'd include it)

Below is the PHP function that is used to update the downloads count but isn't really relevant to the above problem.

function downloadCounter($connection, $imageID) {
    if (isset($_POST['download'])) {
         // value from hidden form element
         $imageID = $_POST['image-id'];

         try {
              $sql = "UPDATE lj_imageposts SET downloads = downloads +1 WHERE image_id = :image_id";
              $stmt = $connection->prepare($sql);

              $stmt->execute([
                   ':image_id' => $imageID
              ]);

         } catch (PDOException $e) {
              echo "Error: " . $e->getMessage();
         }
    }
}

Solution

  • At a high level, I see this code as trying to implement the following behavior (albeit in a very strange way)

    1. Automatically add form handlers to every <form> that has class .download-form-js
    2. Add information to the POST request about which button actually submitted the form.

    Most of your questions relate to the odd way that it tries to accomplish goal #2. Peterrabbit did a good job going through these one-by-one, so I won't do that again. Fortunately, there's a much simpler way of doing this - by leveraging the submitter property of the FormEvent (see docs). Check out the refactored code below, which is also in this codesandbox:

    const forms = document.querySelectorAll(".download-form-js");
    forms.forEach((form) => {
      form.addEventListener("submit", function (e) {
        e.preventDefault();
        const formData = new FormData(this);
    
        // see: https://developer.mozilla.org/en-US/docs/Web/API/SubmitEvent/submitter
        if (e.submitter) {
          formData.set(e.submitter.name, e.submitter.value);
        }
    
        fetch(pagePath, {
          method: "post",
          body: formData
        })
          .then(response => console.log(response))
          .catch((e) => console.error(e));
      });
    });
    

    That basically dispenses with the need to register event handlers on every button in the form (whose only purpose was to, when clicked, register themselves on the form object so the form "submit" event could figure which button was clicked, and add to the POST request).

    In the linked codesandbox I have this refactored implementation running side-by-side with your original implementation, as well as a comparison with what would happen if you got rid of javascript altogether and just used a simple <form action="FORM_HANLDER_URL" method="post">. I also tried to respond to your comments inline in the src/original.js file.