Home > OS >  Using JavaScript Fetch() and formData() To Update A Database
Using JavaScript Fetch() and formData() To Update A Database

Time:04-14

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

<section>
    <div >
        <img src="image.jpg" alt="image">
    </div>
    <form  enctype="multipart/form-data" method="post">
        <div >
                <label for="download-button">Download</label>
                <button id="download-button" type="submit" name="download" title="Download" ></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();
         }
    }
}

CodePudding user response:

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 => {

Because the variable downloadButton is a collection of ALL download buttons in the page, but item.querySelectorAll('[type="submit"], button') takes only the buttons nested inside the form being iterated.

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

The reference of the button HtmlElement is stored as an attribute of the form HtmlElement.

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

That btn argument can't be used, as said in this docs https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#the_event_listener_callback An eventListener callback only support the event as argument. So this btn is just undefined in all cases.

      // 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?
      }

Same answer than above, if the variable downloadButtons is used, the button won't be related to the form. (it actually would be possible but with more operations to identify the parent form)

If I got the logic well, the fact that a _button attribute is present means that a submit button have been clicked, and that same button is referenced in that attribute _button. So we can get the values we want with its name and value.

Then the _button attribute is deleted because if another button is clicked inside the same form it will replace this one (even if in that precise case it's useless to delete the attribute, it could just be reassigned). But maybe the form can also be submitted without any of those buttons being clicked (?), it would explain the if this._button...

Again I think this implementation is quite twisted. But I don't know all details of this implementation so I don't know if I can propose a more relevant way to do it..

CodePudding user response:

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.

  • Related