Better Bootstrap File Upload Button
Tags:
Bottom Line: Here’s how to make an attractive file upload button for Bootstrap.
I put a lot of work into the new version of icsConverterWebapp. Here’s how my file upload buttons turned out:
I’ve been learning to use Bootstrap for a few Flask projects I’ve been working on lately. I was surprised to see that the default Bootstrap file upload buttons are pretty ugly:
To cut to the chase, I did tons of Googling, and this is the most satisfying solution I came up with. It is a combination of this popular solution by Cory LaViska, and this stackoverflow answer by Kirill Fuchs.
Further, I’m using Flask with wtforms, Flask-wtf, and Flask-bootstrap, so my solution uses code from all of these. If you’re not using these, skip to the bottom for a HTML and JS solution, and a link to a demo.
The basic idea is this:
- Use a basic file input element in a WTF form (the input element is ugly).
- Add a pretty Bootstrap
label
(basically a clickable descriptive item for a button) as aninput-group-addon
to the input. - Hide the input, leaving only the pretty label.
- Add a place to display the uploaded filename for user feedback.
- Use some javascript to display the filename once uploaded (for user feedback).
- Add just a touch of CSS.
#1 — 4
Relevant part of index.html
(a Jinja2 template)
{% block content %}
<div class="row">
<form class="form-inline center-block" action="/" method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div class="input-group">
<label id="browsebutton" class="btn btn-default input-group-addon" for="my-file-selector">
{{ form.input_file(id="my-file-selector") }}
Browse...
</label>
<input type="text" class="form-control" readonly>
</div>
{{ form.submit(class_="btn btn-primary") }}
</form>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script src="{{ url_for('.static', filename='js/inputFileButton.js') }}" ></script>
{% endblock %}
Note that the pretty label is for
the ID of the (ugly) input button.
Relevant part of views.py
:
from forms import UploadForm
from flask import request
@app.route('/', methods=['GET', 'POST'])
def index():
form = UploadForm()
if request.method == 'POST' and form.validate_on_submit():
input_file = request.files['input_file']
# Do stuff
else:
return render_template('index.html', form=form)
Not a whole lot of surprises here, a GET
request returns the form, a POST
to the same URL gets the uploaded file for processing.
forms.py
:
from flask_wtf import Form
from flask_wtf.file import FileField, FileAllowed, FileRequired
from wtforms import SubmitField
class UploadForm(Form):
validators = [
FileRequired(message='There was no file!'),
FileAllowed(['txt'], message='Must be a txt file!')
]
input_file = FileField('', validators=validators)
submit = SubmitField(label="Upload")
#5
And inputFileButton.js
(taken directly from Cory LaViska at the link above):
$(document).on('change', '#browsebutton :file', function() {
var input = $(this),
numFiles = input.get(0).files ? input.get(0).files.length : 1,
label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
input.trigger('fileselect', [numFiles, label]);
});
$(document).ready( function() {
$('#browsebutton :file').on('fileselect', function(event, numFiles, label) {
var input = $(this).parents('.input-group').find(':text'),
log = numFiles > 1 ? numFiles + ' files selected' : label;
if( input.length ) {
input.val(log);
} else {
if( log ) alert(log);
}
});
});
#6
Now we just use some CSS (SCSS in my case) to hide the ugly input button and give its new Browse...
button the nice white color of Bootstrap’s btn-default
in style.css
:
#browsebutton {
background-color: white;
}
#my-file-selector {
display: none;
}
That’s all there is to it! I found Bootply really helpful in getting this all figured out. For those of you not using Flask, below is a simplified version using just Bootstrap, HTML, and the same JS as above, and here’s a link to a runnable demo at Bootply.
<form class="form-inline center-block" action="/" method="POST" enctype="multipart/form-data">
<div class="input-group">
<label id="browsebutton" class="btn btn-default input-group-addon" for="my-file-selector" style="background-color:white">
<input id="my-file-selector" type="file" style="display:none;">
Browse...
</label>
<input type="text" class="form-control" readonly="">
</div>
<button type="submit" class="btn btn-primary">Convert</button>
</form>