Know Your File Input

It always starts with messy code (part 1)

Hello and welcome to a blog post that is actually about code. It's been a while but I'm still aliveđź‘‹.

Over the weekend, I wrote a small web app called Knitlify which let you import colorwork chart (or pixel art) to visualize as a knit fabric. It was a seemingly simple app that I've written 90% functionality in one way or the other in the past, but it tuned out to be lots of learning experience. In other words, I thought I can glue together bits and pieces of code from my old projects and be done in a few hours (LOL, no). In reality, I discovered things I didn't know and learned some of the code I've been writing was not good.

Over the next few posts, I'm gonna share what I learned. Tentatively calling this series "It always starts with messy code". Because it's the truth for every single project I do. I am extremely not great web developer. My strength is in explaining things, not in making tidy sophisticated code.

Some of the things I plan to write are

Today's post is about events!

change and input insonsitency in File Input

Takeaways

Fighting with File Input

My app has minimal functionalities based on the state of <input> elements just like normal web apps do.

I use <input type="file"> to accept image file from user, use <input type="number"> for size configuration, and <input type="checkbox"> for toggling display mode.

One of the first things I added to the app is <input type="file">. Now here is the confession and messy start of this app. I copy and paste following code snippet from other projects. This is what I've been using ever since I learned to write JavaScript. (Seriously, File API was the first Web API I used.)

<input type="file" name="file" id="file" accept="image/*" >
<label id="filename" for="file" >select an image file</label>
const $fileInput = document.getElementById('file')
$fileInput.addEventListener('change', function (event) {
  // event.target.files[0] is selected file
  // you'll need to pass that to FileRader :)
}, false)

Issue 1: change event fires inconsistently across browsers.

One thing I got really annoyed this time was the fact this change event did not fire in Chrome or Safari if I selected a file with the same name again even though contents of a file is completely different. I was generating different test images and overwriting in the same file name.

While FireFox triggered change event everytime I selected a file from prompt, I had to re-load the app every time I want to test different image (with the same filename) for Chrome and Safari.

At first, I thought it is logical because the name of a file is the same, so the file has not "changed". But who is it to decide what "change" means? the file size and binary for the file were completely different, yet Chrome and Safari treated like "oh that file you selected is the same file as the one before, so not gonna tell you it's changed."

Enter, input event.

Issue 2: input event doesn't work either

Upon reading MDN, I learned <input type="file"> should trigger input and change events. If change is ignored (in my logic at the time: because of the same filename) then surely input should be fired everytime user interacts with input element right???

The answer is no.

Chrome and Safari do not fire both input and change events if you select a file with the same filename as the previous selection. Furthermore, Safari does not fire input altogether, so switching to input event meant I would not be able to handle image select action from Safari user.

Solution: Clean up after your file input element

There is one line of code you can add to make sure browser thinks you are not selecting the same file again. It's simple. just delete the fact the file was selected.

const $fileInput = document.getElementById('file')
$fileInput.addEventListener('change', function (event) {
  // -- busy doing application work --

  // clean up after file input
  $fileInput.value = ''
}, false)

In the -- busy doing application work -- section, I record filename in state object and load the data onto Image object. Both are data I need for this app, but once those two are in other places, I don't need to refer back to the value attached to $fileInput again. So just clear the value with an empty string.

The issue that I never really knew it was an issue is now solved in just one line of code. Now I should update dozens of more apps I've made with this code snippet.

I still think that the FireFox does the right thing by firing input and change events every time user hit open on file prompt. I've filed bug to Chromium and Webkit, and asked WHATWG to clarify this in HTML Specification.

Next post: Is this change committed?