UNPKG

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.

256 lines (248 loc) 6.58 kB
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 (h) { const t = this // Form Input const 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 const 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 () { 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 () { // 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(file => file.name).join(', ') } // Single file return this.selectedFile.name } }, watch: { selectedFile (newVal, oldVal) { if (newVal === oldVal) { return } if (!newVal && this.multiple) { this.$emit('input', []) } else { this.$emit('input', newVal) } } }, methods: { 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 () { 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 (evt) { // 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 const items = evt.dataTransfer && evt.dataTransfer.items if (items && !this.noTraverse) { const queue = [] for (let i = 0; i < items.length; i++) { const item = items[i].webkitGetAsEntry() if (item) { queue.push(this.traverseFileTree(item)) } } Promise.all(queue).then(filesArr => { this.setFiles(arrayFrom(filesArr)) }) return } // Normal handling this.setFiles(evt.target.files || evt.dataTransfer.files) }, setFiles (files) { if (!files) { this.selectedFile = null return } if (!this.multiple) { this.selectedFile = files[0] return } // Convert files to array const filesArray = [] for (let i = 0; i < files.length; i++) { if (files[i].type.match(this.accept)) { filesArray.push(files[i]) } } this.selectedFile = filesArray }, dragover (evt) { evt.preventDefault() evt.stopPropagation() if (this.noDrop || !this.custom) { return } this.dragging = true evt.dataTransfer.dropEffect = 'copy' }, dragleave (evt) { evt.preventDefault() evt.stopPropagation() this.dragging = false }, 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 (item, path) { // Based on http://stackoverflow.com/questions/3590058 return new Promise(resolve => { path = path || '' if (item.isFile) { // Get file item.file(file => { file.$path = path // Inject $path to file obj resolve(file) }) } else if (item.isDirectory) { // Get folder contents item.createReader().readEntries(entries => { const queue = [] for (let i = 0; i < entries.length; i++) { queue.push( this.traverseFileTree(entries[i], path + item.name + '/') ) } Promise.all(queue).then(filesArr => { resolve(arrayFrom(filesArr)) }) }) } }) } } }