Reputation: 947
I have a form that has multiple image components inside it that are outputted onto the page with a while
loop. When the form is submitted after the image details have been added they are uploaded to a MySQL database via PHP (along with some usage of the JavaScript fetch API to prevent a hard page refresh).
Because of the design of the form I've used multiple image components inside one form instead of having multiple forms: this is so the user can upload multiple images and can do the final submission all in one go (a far better user experience).
The Problem
Because the components also each have an individual delete button so that the individual images can be deleted prior to the final set of images being uploaded, this currently has an unwanted side effect: when a user hits the 'enter/return' key on a keyboard, the browser goes to the nearest submit button in the form, which is the delete button for that particular component.
What I want to do is have it so if the 'Image Title' input field is in focus, and the user presses the 'return' key, it defaults to the main image submission button at the very bottom of the form with the class .upload-details-submit
. It's not an issue with the <textarea>
element because hitting return when that is in focus just takes you down a line inside the <textarea>
field and obviously if the 'delete' button has focus then hitting return will carry out the desired action of deleting the image component.
My Question
The question I have is essentially two-fold:
a) Is there away to fix this with the HTML tabindex
attribute?
b) If not, and I have to use JavaScript, what is the best way to approach this? I understand you can use the .hasFocus()
property on an element to check if it has focus, but how would I go about triggering certain events when the user has given the input element (the Image Title input) focus? Or more specifically when they have filled in the 'Image Title' field and hit the return key, it ignores the nearest 'Delete' button and fires the main .upload-details-submit
button ?
I have included my JavaScript as well, but I can't seem to get this to work.
HTML
<form class="upload-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="image-wrapper">
<img src="image.jpg">
</div>
<div class="edit-zone">
<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>
</div>
<!-- IMAGE DETAILS COMPONENT - END -->
<div class="form-row upload-details-submit-row">
<button id="upload-submit" class="upload-details-submit" type="submit" name="upload-submit">COMPLETE UPLOADS</button>
</div>
</form>
JAVASCRIPT
Note: I'm using a forEach
loop because although there is only one component in the HTML example above, there will invariably be more than one outputted via the while
loop on the live site.
let inputTitle = document.querySelectorAll('.input-title')
inputTitle.forEach((title) => {
if (title.hasFocus()) {
title.addEventListener('keyup', (e) => {
if (e.keycode === 13) {
e.target.closest('form').querySelector('.upload-details-submit').click()
}
})
}
})
Upvotes: 1
Views: 1201
Reputation: 2917
** update **
(This solution works for desktop and touchscreen (mobile) devices)
My original solution and the solution by @bravo don't support touchscreen keyboards on mobile and tablet devices. There are ways to solve this (with JS) using:
readonly
on textareadisabled
the submit buttonsubmit
attribute on the form.
Still alot of fiddling about will be needed.I think the best approach for solving this is to toggling the tabindex
You want to avoid keypress as it's depreciated. Here's the solution with keydown
and activeElement
const uploadSubmit = document.querySelector('#upload-submit')
const form = document.querySelector('#form')
let currentImageTab
let activeElement
document.addEventListener('keydown', (e) => {
// Check the input element is the image-title
const hasImageTitle = document.activeElement.hasAttribute('data-image-title')
if (hasImageTitle) {
// The current component
if (e.key === 'Enter') {
/*
** Only using click for testing on SO **
*/
uploadSubmit.click()
e.preventDefault()
/*
Use `form.submit()` instead of `uploadSubmit.click()` as it is
less hacky and less prone to any propagation issues.
Submit will submit the form but will not fire the submit event */
// form.submit()
} else if (e.key === 'Tab') {
// Re-enables tab
e.preventDefault()
currentImageTab.focus()
}
}
})
// Gets the tab element of the component
const getTab = () => document.activeElement.closest('[data-upload-details-component]')
.querySelector('[data-tab]')
document.addEventListener('focus', () => {
// Updates current image tab
if (document.activeElement.hasAttribute('data-image-title')) {
// Each new image title is updated
currentImageTab = getTab()
// This prevents textarea being jumped to
currentImageTab.setAttribute('tabindex', '-1')
} else {
// This re-enables textarea to be focus-able.
// Dosen't have to until the current image title has been accessed
if (currentImageTab) {
currentImageTab.removeAttribute('tabindex')
}
}
}, true)
// Use the click event or attributes to manage the behavior of delete
/*
Just as @bravo's example, this is just for testing, not required
*/
form.addEventListener('submit', e => {
e.preventDefault()
document.body.insertAdjacentHTML('afterend', `<div>Form submitted</div>`)
})
<form id="form" class="upload-form-js" method="post" enctype="multipart/form-data">
<!-- IMAGE DETAILS COMPONENT. THESE COMPONENTS ARE OUTPUTTED WITH A WHILE LOOP-->
<div class="upload-details-component" data-upload-details-component>
<div class="image-wrapper">
<!-- <img src="image.jpg"> uncomment -->
</div>
<div class="edit-zone">
<div class="form-row">
<label for="image-title">Image Title</label>
<input data-image-title 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 data-tab id="tags" class="text-area" type="text" name="image-tags[]"></textarea>
</div>
<div class="form-row">
<!-- DELETE BUTTON -->
<button data-delete name="upload-details-delete" value="12" class="remove-image">DELETE</button>
<input type="hidden" name="image-id[]" value="12">
</div>
</div>
</div>
<!-- IMAGE DETAILS COMPONENT - END -->
<div class="form-row upload-details-submit-row">
<button id="upload-submit" class="upload-details-submit" type="submit" name="upload-submit">COMPLETE UPLOADS</button>
</div>
</form>
Upvotes: 0
Reputation: 6254
I've simplified the HTML for readability (and added a second "component")
Add a keypress
handler to document
, then check which element is the target
on the keypress
- then it's really simple
document.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && e.target.classList.contains('input-title')) {
e.preventDefault();
e.target.closest('form').querySelector('.upload-details-submit').click()
}
})
/// just to prevent actual form submission for the answer - you wouldn't do this
document.querySelectorAll('form').forEach(form => form.addEventListener('submit', e => {
console.log('form would submit', e.submitter.id)
e.preventDefault();
}))
<form>
<div class="upload-details-component">
<label for="image-title">Image Title</label>
<input id="title-title" class="input-title" type="text" name="image-title[]">
<label for="tags">Comma Separated Image Tags</label>
<textarea id="tags" class="text-area" type="text" name="image-tags[]"></textarea>
<!-- DELETE BUTTON -->
<button id='r1' name="upload-details-delete" value="12" class="remove-image">DELETE</button>
<input type="hidden" name="image-id[]" value="12">
</div>
<div class="upload-details-component">
<label for="image-title2">Image Title 2</label>
<input id="title-title2" class="input-title" type="text" name="image-title[]">
<label for="tags">Comma Separated Image Tags</label>
<textarea id="tags2" class="text-area" type="text" name="image-tags[]"></textarea>
<!-- DELETE BUTTON -->
<button id='r2' name="upload-details-delete" value="13" class="remove-image">DELETE</button>
<input type="hidden" name="image-id[]" value="13">
</div>
<button id="upload-submit" class="upload-details-submit" type="submit" name="upload-submit">COMPLETE UPLOADS</button>
</form>
Upvotes: 1