bootstrap-vue
Version:
BootstrapVue provides one of the most comprehensive implementations of Bootstrap 4 components and grid system for Vue.js and with extensive and automated WAI-ARIA accessibility markup.
249 lines (239 loc) • 6.85 kB
JavaScript
import idMixin from '../../mixins/id';
import formMixin from '../../mixins/form';
import formStateMixin from '../../mixins/form-state';
import formCustomMixin from '../../mixins/form-custom';
import { from as arrayFrom } from '../../utils/array';
export default {
mixins: [idMixin, formMixin, formStateMixin, formCustomMixin],
render: function render(h) {
var t = this;
// Form Input
var input = h('input', {
ref: 'input',
class: [{
'form-control-file': t.plain,
'custom-file-input': t.custom,
focus: t.custom && t.hasFocus
}, t.stateClass],
attrs: {
type: 'file',
id: t.safeId(),
name: t.name,
disabled: t.disabled,
required: t.required,
capture: t.capture || null,
accept: t.accept || null,
multiple: t.multiple,
webkitdirectory: t.directory,
'aria-required': t.required ? 'true' : null,
'aria-describedby': t.plain ? null : t.safeId('_BV_file_control_')
},
on: {
change: t.onFileChange,
focusin: t.focusHandler,
focusout: t.focusHandler
}
});
if (t.plain) {
return input;
}
// Overlay Labels
var label = h('label', {
class: ['custom-file-label', t.dragging ? 'dragging' : null],
attrs: {
id: t.safeId('_BV_file_control_')
}
}, t.selectLabel);
// Return rendered custom file input
return h('div', {
class: ['custom-file', 'b-form-file', t.stateClass],
attrs: { id: t.safeId('_BV_file_outer_') },
on: { dragover: t.dragover }
}, [input, label]);
},
data: function data() {
return {
selectedFile: null,
dragging: false,
hasFocus: false
};
},
props: {
accept: {
type: String,
default: ''
},
// Instruct input to capture from camera
capture: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: undefined
},
multiple: {
type: Boolean,
default: false
},
directory: {
type: Boolean,
default: false
},
noTraverse: {
type: Boolean,
default: false
},
noDrop: {
type: Boolean,
default: false
}
},
computed: {
selectLabel: function selectLabel() {
// No file choosen
if (!this.selectedFile || this.selectedFile.length === 0) {
return this.placeholder;
}
// Multiple files
if (this.multiple) {
if (this.selectedFile.length === 1) {
return this.selectedFile[0].name;
}
return this.selectedFile.map(function (file) {
return file.name;
}).join(', ');
}
// Single file
return this.selectedFile.name;
}
},
watch: {
selectedFile: function selectedFile(newVal, oldVal) {
if (newVal === oldVal) {
return;
}
if (!newVal && this.multiple) {
this.$emit('input', []);
} else {
this.$emit('input', newVal);
}
}
},
methods: {
focusHandler: function focusHandler(evt) {
// Boostrap v4.beta doesn't have focus styling for custom file input
// Firefox has a borked '[type=file]:focus ~ sibling' selector issue,
// So we add a 'focus' class to get around these "bugs"
if (this.plain || evt.type === 'focusout') {
this.hasFocus = false;
} else {
// Add focus styling for custom file input
this.hasFocus = true;
}
},
reset: function reset() {
try {
// Wrapped in try in case IE < 11 craps out
this.$refs.input.value = '';
} catch (e) {}
// IE < 11 doesn't support setting input.value to '' or null
// So we use this little extra hack to reset the value, just in case
// This also appears to work on modern browsers as well.
this.$refs.input.type = '';
this.$refs.input.type = 'file';
this.selectedFile = this.multiple ? [] : null;
},
onFileChange: function onFileChange(evt) {
var _this = this;
// Always emit original event
this.$emit('change', evt);
// Check if special `items` prop is available on event (drop mode)
// Can be disabled by setting no-traverse
var items = evt.dataTransfer && evt.dataTransfer.items;
if (items && !this.noTraverse) {
var queue = [];
for (var i = 0; i < items.length; i++) {
var item = items[i].webkitGetAsEntry();
if (item) {
queue.push(this.traverseFileTree(item));
}
}
Promise.all(queue).then(function (filesArr) {
_this.setFiles(arrayFrom(filesArr));
});
return;
}
// Normal handling
this.setFiles(evt.target.files || evt.dataTransfer.files);
},
setFiles: function setFiles(files) {
if (!files) {
this.selectedFile = null;
return;
}
if (!this.multiple) {
this.selectedFile = files[0];
return;
}
// Convert files to array
var filesArray = [];
for (var i = 0; i < files.length; i++) {
if (files[i].type.match(this.accept)) {
filesArray.push(files[i]);
}
}
this.selectedFile = filesArray;
},
dragover: function dragover(evt) {
evt.preventDefault();
evt.stopPropagation();
if (this.noDrop || !this.custom) {
return;
}
this.dragging = true;
evt.dataTransfer.dropEffect = 'copy';
},
dragleave: function dragleave(evt) {
evt.preventDefault();
evt.stopPropagation();
this.dragging = false;
},
drop: function drop(evt) {
evt.preventDefault();
evt.stopPropagation();
if (this.noDrop) {
return;
}
this.dragging = false;
if (evt.dataTransfer.files && evt.dataTransfer.files.length > 0) {
this.onFileChange(evt);
}
},
traverseFileTree: function traverseFileTree(item, path) {
var _this2 = this;
// Based on http://stackoverflow.com/questions/3590058
return new Promise(function (resolve) {
path = path || '';
if (item.isFile) {
// Get file
item.file(function (file) {
file.$path = path; // Inject $path to file obj
resolve(file);
});
} else if (item.isDirectory) {
// Get folder contents
item.createReader().readEntries(function (entries) {
var queue = [];
for (var i = 0; i < entries.length; i++) {
queue.push(_this2.traverseFileTree(entries[i], path + item.name + '/'));
}
Promise.all(queue).then(function (filesArr) {
resolve(arrayFrom(filesArr));
});
});
}
});
}
}
};