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)
- Automatically add form handlers to every
<form>
that has class.download-form-js
- 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.