A small Vue.js app
Selecting a local file with <input type="file">
In my previous post, I used Eli Grey’s FileSaver.js
library to allow my Vue app’s data (an array called tiddlers
) to be downloaded to a local file. The next obvious thing to do is load it back from the file.
I had planned to do that in a single post, but it turns out that selecting a file and reading its contents are each interesting enough to warrant thinking about separately.
Goal
Here I will add an interface that uses an <input type="file">
element to let the user choose a local file to load.
Breakdown
- Add an
<input type="file">
element to the template - Apply consistent styling to the interface by hiding the
<input>
element and adding a button to activate it - Add a handler that watches for changes in the file list (i.e. when the user picks a file) and invokes a method,
handleFile()
, which will be written in the next post
1. Add an <input type="file">
element to choose a file
This element is a bit strange, UI-wise. The whole element is clickable, even though it looks like it has a button. Browsers choose the text on the “button” part. Firefox puts “Browse…” on it. I can add a <label>
element to clarify what the “button” is for. The <label>
is clickable too. The "main-buttons"
part of the template
now looks like this:
<div id="main-buttons">
<div id="file-load-input">
<label for="load-tiddlers">Load tiddlers from file:</label>
<input type="file" id="load-tiddlers" name="load-tiddlers" accept="application/JSON">
</div>
<button id="save-tiddlers" @click="saveFile">Save tiddlers to file</button>
</div>
I’ve specified with the accept
attribute that the <input>
element should only accept JSON files.
2. Style the file-opening interface
The <input type="file">
element interface is really inconsistent with the rest of the app, so I want to customize it.
I tried the MDN suggested solution of visually hiding the input element, and using CSS to put a dotted line around the label element when the <input>
element has focus, but the dotted line didn’t show up in Firefox. It showed up in Chrome, but Chrome’s default indicator of focus seems to be a thick blue glowing outline, which (aside from looking goofy), is inconsistent with the dotted outline.
So I’ll go with the gist of the other MDN suggestion of activating the <input>
element with a click from a real <button>
element.
Write a method of the Vue instance that clicks the "load-tiddlers"
<input>
element:
fileSelect() {
document.getElementById("load-tiddlers").click()
}
Add the button in the template
, with a click handler pointing to the fileSelect()
method.
<button id="file-select" @click="fileSelect">Load tiddlers from file</button>
In CSS, make the <input type="file">
element hidden with display: none;
. I’ve also taken out the <label>
element, and to fit the “Load” and “Save” buttons side-by-side, I’ve shortened their text.
Now the user can select a file. The app does nothing with it, yet.
Current code: minapp_v17b.js
3. Add an event handler to the <input>
element in the template
When the user chooses a file, this commits a change to the value
of the <input>
element, and a change
event is emitted. I’ll attach a handler for that event in the template:
<input type="file" id="load-tiddlers" name="load-tiddlers" @change="handleFile" accept="application/JSON">
Now the app is a bit broken, because it’s invoking handleFile()
when the user selects a file, but handleFile()
does not exist yet.