SOLVED
The problem, alerted by @CodeThing 's comment, was that there was another version of the form on the page. This wasn't obvious: for Django developers encountering this problem, check for any {% include %}
tags in your template, as this other version of the form (containing identically named elements) was inside a modal included in the same page. Neither JS or Django will throw this as an error, and it is not obvious when just looking through the template, which is why it was hard to find: you have to use Inspect and search for it.
Key Principle: If you encounter this issue, use Inspect to check for any other elements with the same 'id', and if they exist remove them. Then the code should work perfectly.
Aim
- I have two inputs: (1) a 'file-input', and (2) a 'text-input'
- I want to listen for a 'change' in 'file-input' (i.e. successful choice of file), and upon such a change set the 'text-input' value to the name of the file.
Problem
- The 'change' event is not triggering when I use
addEventListener
- When I use the input's 'onchange' attribute, it does trigger, but I still don't get access to files
Context: This is for a Django project, with Bootstrap 5 on the frontend (NO JQuery). I am using Django Crispy Forms where possible, though I have removed them from this file (to see whether that was what was causing the problem - alas, it wasn't).
Attempted Solutions
I know this should be easy. I have seen lots of similar posts about this, and I set up a 'dummy' HTML script to ensure I'm not going mad. This is posted below and it works perfectly.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<input type="file" name="file" id="file-input" accept=".mp3,.mp4,.mpeg,.mpga,.m4a,.wav,.webm">
<input type="text" name="text" id="text-input">
<script>
const fileInput = document.getElementById("file-input");
const textInput = document.getElementById("text-input");
console.log(fileInput);
console.log(textInput);
fileInput.addEventListener('change', () => {
console.log('change registered');
if (fileInput.files.length > 0) {
console.log(fileInput.files);
console.log(fileInput.files[0].name);
textInput.value = fileInput.files[0].name;
}
});
</script>
</body>
</html>
The output is as expected for this dummy file - i.e. it works:
- input id="file-input" type="file" name="file" accept=".mp3,.mp4,.mpeg,.mpga,.m4a,.wav,.webm"
- input id="text-input" type="text" name="text"
- change registered
- FileList [ File ]
- interview.mp4*
Problematic Code
Now my Django project's template is set up to replicate as close as possible this dummy set up (see the relevant part below):
<form id="file-form" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="my-4 px-1">
<input type="file" name="file" id="file-input" accept=".mp3,.mp4,.mpeg,.mpga,.m4a,.wav,.webm">
</div>
<div class="my-3 px-1">
<input type="text" name="text" id="text-input">
</div>
<button type="submit" class="btn btn-add">
Transcribe
</button>
<script>
const fileInput = document.getElementById("file-input");
const textInput = document.getElementById("text-input");
console.log(fileInput);
console.log(textInput);
fileInput.addEventListener('change', () => {
console.log('change registered');
if (fileInput.files.length > 0) {
console.log(fileInput.files);
console.log(fileInput.files[0].name);
textInput.value = fileInput.files[0].name;
}
});
</script>
</form>
But the output is incorrect - no change event fires!
i.e. I just get the below:
- input id="file-input" class="form-control form-control-file" type="file" name="file" required=""
- input id="text-input" type="text" name="text"
- NOTHING ELSE
I have attempted to fix this by using an 'onchange' attribute on the input itself. This does register a change event, but the fileInput.files is still an empty list, so that doesn't work.
I have checked these SO answers, but they don't help - they work just like it does in the dummy file: SO Answer 1 SO Answer 2
Am I missing something? Why does this work in the dummy plain HTML version, but not in the Django version? The script is definitely running (it correctly console.logs both inputs); it cannot be crispy forms, I have removed it from the template; it can't be the or csrf_input, it still doesn't work when they're removed. I am so baffled by this!
Any and all help hugely appreciated!