Based on the absolutely brilliant @claviska solution, to whom all the credit is owed.
Based on the input group example we have a dummy input text field used for displaying the filename to the user, which gets populated from the onchange
event on the actual input file field hidden behind the label button. Aside from including the bootstrap 4 validation support we've also made it possible to click anywhere on the input to open the file dialog.
The three possible states are un-validated, valid and invalid with the dummy html input tag attribute required
set.
We introduce only 2 custom classes input-file-dummy
and input-file-btn
to properly style and wire the desired behaviour. Everything else is standard Bootstrap 4 markup.
<div class="input-group">
<input type="text" class="form-control input-file-dummy" placeholder="Choose file" aria-describedby="fileHelp" required>
<div class="valid-feedback order-last">File is valid</div>
<div class="invalid-feedback order-last">File is required</div>
<label class="input-group-append mb-0">
<span class="btn btn-primary input-file-btn">
Browse… <input type="file" hidden>
</span>
</label>
</div>
<small id="fileHelp" class="form-text text-muted">Choose any file you like</small>
The dummy input needs to be read only, as per the original example, to prevent the user from changing the input which may only be changed via the open file dialog. Unfortunately validation does not occur on readonly
fields so we toggle the editability of the input on focus and blur ( jquery events onfocusin
and onfocusout
) and ensure that it becomes validatable again once a file is selected.
Aside from also making the text field clickable, by triggering the button's click event, the rest of the functionality of populating the dummy field was envisioned by @claviska.
$(function () {
$('.input-file-dummy').each(function () {
$($(this).parent().find('.input-file-btn input')).on('change', {dummy: this}, function(ev) {
$(ev.data.dummy)
.val($(this).val().replace(/\\/g, '/').replace(/.*\//, ''))
.trigger('focusout');
});
$(this).on('focusin', function () {
$(this).attr('readonly', '');
}).on('focusout', function () {
$(this).removeAttr('readonly');
}).on('click', function () {
$(this).parent().find('.input-file-btn').click();
});
});
});
Most importantly we don't want the readonly
field to jump between grey background and white so we ensure it stays white. The span button doesn't have a pointer cursor but we need to add one for the input anyway.
.input-file-dummy, .input-file-btn {
cursor: pointer;
}
.input-file-dummy[readonly] {
background-color: white;
}
nJoy!