primevue-mirror/src/components/fileupload/FileUpload.vue

456 lines
14 KiB
Vue
Raw Normal View History

2019-02-12 13:51:28 +00:00
<template>
<div class="p-fileupload p-fileupload-advanced p-component" v-if="isAdvanced">
2019-02-12 13:51:28 +00:00
<div class="p-fileupload-buttonbar">
2020-07-16 13:37:24 +00:00
<span :class="advancedChooseButtonClass" @click="choose" @keydown.enter="choose" @focus="onFocus" @blur="onBlur" v-ripple tabindex="0">
2020-07-04 10:01:30 +00:00
<input ref="fileInput" type="file" @change="onFileSelect" :multiple="multiple" :accept="accept" :disabled="chooseDisabled" />
2020-05-02 09:19:17 +00:00
<span class="p-button-icon p-button-icon-left pi pi-fw pi-plus"></span>
2020-07-01 17:52:25 +00:00
<span class="p-button-label">{{chooseLabel}}</span>
2019-10-01 13:00:26 +00:00
</span>
2020-07-04 10:01:30 +00:00
<FileUploadButton :label="uploadLabel" icon="pi pi-upload" @click="upload" :disabled="uploadDisabled" />
<FileUploadButton :label="cancelLabel" icon="pi pi-times" @click="clear" :disabled="cancelDisabled" />
2019-02-12 13:51:28 +00:00
</div>
<div ref="content" class="p-fileupload-content" @dragenter="onDragEnter" @dragover="onDragOver" @dragleave="onDragLeave" @drop="onDrop">
<FileUploadProgressBar :value="progress" v-if="hasFiles" />
2019-02-12 14:06:47 +00:00
<FileUploadMessage v-for="msg of messages" severity="error" :key="msg">{{msg}}</FileUploadMessage>
2019-02-12 13:51:28 +00:00
<div class="p-fileupload-files" v-if="hasFiles">
<div class="p-fileupload-row" v-for="(file, index) of files" :key="file.name + file.type + file.size">
<div v-if="isImage(file)">
<img role="presentation" :alt="file.name" :src="file.objectURL" :width="previewWidth" />
</div>
<div>{{file.name}}</div>
<div>{{formatSize(file.size)}}</div>
<div>
<FileUploadButton type="button" icon="pi pi-times" @click="remove(index)" />
2019-02-12 13:51:28 +00:00
</div>
</div>
2019-10-01 13:00:26 +00:00
</div>
<div class="p-fileupload-empty" v-if="$slots.empty && !hasFiles">
<slot name="empty"></slot>
2020-04-07 12:04:25 +00:00
</div>
2019-02-12 13:51:28 +00:00
</div>
</div>
2020-07-16 13:37:24 +00:00
<div class="p-fileupload p-fileupload-basic p-component" v-else-if="isBasic">
<FileUploadMessage v-for="msg of messages" severity="error" :key="msg">{{msg}}</FileUploadMessage>
<span :class="basicChooseButtonClass" @mouseup="onBasicUploaderClick" @keydown.enter="choose" @focus="onFocus" @blur="onBlur" v-ripple tabindex="0" >
<span :class="basicChooseButtonIconClass"></span>
<span class="p-button-label">{{basicChooseButtonLabel}}</span>
<input ref="fileInput" type="file" :accept="accept" :disabled="disabled" @change="onFileSelect" @focus="onFocus" @blur="onBlur" v-if="!hasFiles" />
</span>
</div>
2019-02-12 13:51:28 +00:00
</template>
<script>
import Button from '../button/Button';
import ProgressBar from '../progressbar/ProgressBar';
import Message from '../message/Message';
2020-04-07 12:04:25 +00:00
import DomHandler from '../utils/DomHandler';
2020-06-27 09:36:55 +00:00
import Ripple from '../ripple/Ripple';
2019-02-12 13:51:28 +00:00
export default {
props: {
name: {
type: String,
default: null
},
url: {
type: String,
default: null
},
mode: {
type: String,
default: 'advanced'
},
2019-04-02 11:44:58 +00:00
multiple: {
type: Boolean,
default: false
},
2019-02-12 13:51:28 +00:00
accept: {
type: String,
default: null
},
disabled: {
type: Boolean,
default: false
},
auto: {
type: Boolean,
default: false
},
maxFileSize: {
type: Number,
default: null
},
invalidFileSizeMessage: {
type: String,
2019-02-12 14:06:47 +00:00
default: '{0}: Invalid file size, file size should be smaller than {1}.'
2019-02-12 13:51:28 +00:00
},
2020-07-04 10:01:30 +00:00
fileLimit: {
type: Number,
default: null
},
invalidFileLimitMessage: {
type: String,
default: 'Maximum number of files exceeded, limit is {0} at most.'
},
2019-02-12 13:51:28 +00:00
withCredentials: {
type: Boolean,
default: false
},
previewWidth: {
type: Number,
default: 50
},
chooseLabel: {
type: String,
2019-10-01 13:00:26 +00:00
default: 'Choose'
2019-02-12 13:51:28 +00:00
},
uploadLabel: {
type: String,
2019-10-01 13:00:26 +00:00
default: 'Upload'
2019-02-12 13:51:28 +00:00
},
cancelLabel: {
type: String,
2019-10-01 13:00:26 +00:00
default: 'Cancel'
2020-07-04 10:01:30 +00:00
},
customUpload: {
type: Boolean,
default: false
2019-02-12 13:51:28 +00:00
}
},
duplicateIEEvent: false,
2020-07-04 10:01:30 +00:00
uploadedFileCount: 0,
2019-02-12 13:51:28 +00:00
data() {
return {
2019-10-01 13:00:26 +00:00
files: null,
2019-02-12 13:51:28 +00:00
messages: null,
focused: false,
progress: null
}
},
methods: {
onFileSelect(event) {
if (event.type !== 'drop' && this.isIE11() && this.duplicateIEEvent) {
this.duplicateIEEvent = false;
return;
}
2019-02-12 13:51:28 +00:00
this.messages = [];
this.files = this.files || [];
let files = event.dataTransfer ? event.dataTransfer.files : event.target.files;
for (let file of files) {
if (!this.isFileSelected(file)) {
if (this.validate(file)) {
if (this.isImage(file)) {
file.objectURL = window.URL.createObjectURL(file);
}
this.files.push(file);
}
}
}
2019-10-01 13:00:26 +00:00
this.$emit('select', {originalEvent: event, files: files});
2019-02-12 14:06:47 +00:00
2020-07-04 10:01:30 +00:00
if (this.fileLimit) {
this.checkFileLimit();
}
if (this.auto && this.hasFiles && !this.isFileLimitExceeded()) {
2019-02-12 13:51:28 +00:00
this.upload();
}
if (event.type !== 'drop' && this.isIE11()) {
this.clearIEInput();
}
else {
2019-02-13 10:52:55 +00:00
this.clearInputElement();
}
2019-02-12 13:51:28 +00:00
},
choose() {
this.$refs.fileInput.click();
},
2019-02-12 13:51:28 +00:00
upload() {
2020-07-04 10:01:30 +00:00
if (this.customUpload) {
if (this.fileLimit) {
this.uploadedFileCount += this.files.length;
}
2019-10-01 13:00:26 +00:00
2020-07-04 10:01:30 +00:00
this.$emit('uploader', {files: this.files});
2019-02-12 13:51:28 +00:00
}
2020-07-04 10:01:30 +00:00
else {
let xhr = new XMLHttpRequest();
let formData = new FormData();
2019-02-12 13:51:28 +00:00
2020-07-04 10:01:30 +00:00
this.$emit('before-upload', {
'xhr': xhr,
'formData': formData
});
for (let file of this.files) {
formData.append(this.name, file, file.name);
2019-02-12 13:51:28 +00:00
}
2020-07-04 10:01:30 +00:00
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
this.progress = Math.round((event.loaded * 100) / event.total);
}
this.$emit('progress', {
originalEvent: event,
progress: this.progress
});
2019-02-12 13:51:28 +00:00
});
2020-07-04 10:01:30 +00:00
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
this.progress = 0;
2019-10-01 13:00:26 +00:00
2020-07-04 10:01:30 +00:00
if (xhr.status >= 200 && xhr.status < 300) {
if (this.fileLimit) {
this.uploadedFileCount += this.files.length;
}
2019-02-12 13:51:28 +00:00
2020-07-04 10:01:30 +00:00
this.$emit('upload', {
xhr: xhr,
files: this.files
});
}
else {
this.$emit('error', {
xhr: xhr,
files: this.files
});
}
2019-10-01 13:00:26 +00:00
2020-07-04 10:01:30 +00:00
this.clear();
}
};
2019-10-01 13:00:26 +00:00
2020-07-04 10:01:30 +00:00
xhr.open('POST', this.url, true);
2019-02-12 13:51:28 +00:00
2020-07-04 10:01:30 +00:00
this.$emit('before-send', {
'xhr': xhr,
'formData': formData
});
xhr.withCredentials = this.withCredentials;
2019-02-12 13:51:28 +00:00
2020-07-04 10:01:30 +00:00
xhr.send(formData);
}
2019-02-12 13:51:28 +00:00
},
clear() {
this.files = null;
2019-02-12 14:06:47 +00:00
this.messages = null;
2019-02-12 13:51:28 +00:00
this.$emit('clear');
2019-02-13 10:52:55 +00:00
if (this.isAdvanced) {
this.clearInputElement();
2019-10-01 13:00:26 +00:00
}
2019-02-12 13:51:28 +00:00
},
onFocus() {
this.focused = true;
},
onBlur() {
this.focused = false;
},
isFileSelected(file) {
if (this.files && this.files.length) {
for (let sFile of this.files) {
if ((sFile.name + sFile.type + sFile.size) === (file.name + file.type + file.size))
return true;
}
2019-10-01 13:00:26 +00:00
}
2019-02-12 13:51:28 +00:00
return false;
},
isIE11() {
return !!window['MSInputMethodContext'] && !!document['documentMode'];
},
2019-02-12 13:51:28 +00:00
validate(file) {
if (this.maxFileSize && file.size > this.maxFileSize) {
2019-02-12 14:06:47 +00:00
this.messages.push(this.invalidFileSizeMessage.replace('{0}', file.name).replace('{1}', this.formatSize(this.maxFileSize)));
2019-02-12 13:51:28 +00:00
return false;
}
2019-10-01 13:00:26 +00:00
2019-02-12 13:51:28 +00:00
return true;
},
2020-04-07 12:04:25 +00:00
onDragEnter(event) {
if (!this.disabled) {
event.stopPropagation();
event.preventDefault();
}
2019-02-12 13:51:28 +00:00
},
onDragOver() {
2020-04-07 12:04:25 +00:00
if (!this.disabled) {
DomHandler.addClass(this.$refs.content, 'p-fileupload-highlight');
event.stopPropagation();
event.preventDefault();
}
},
onDragLeave() {
if (!this.disabled) {
DomHandler.removeClass(this.$refs.content, 'p-fileupload-highlight');
}
2019-02-12 13:51:28 +00:00
},
onDrop() {
2020-04-07 12:04:25 +00:00
if (!this.disabled) {
DomHandler.removeClass(this.$refs.content, 'p-fileupload-highlight');
event.stopPropagation();
event.preventDefault();
2019-02-12 13:51:28 +00:00
2020-04-07 12:04:25 +00:00
const files = event.dataTransfer ? event.dataTransfer.files : event.target.files;
const allowDrop = this.multiple || (files && files.length === 1);
if (allowDrop) {
this.onFileSelect(event);
}
}
2019-02-13 10:52:55 +00:00
},
onBasicUploaderClick() {
if (this.hasFiles)
2019-02-13 10:52:55 +00:00
this.upload();
else
this.$refs.fileInput.click();
2019-02-12 13:51:28 +00:00
},
remove(index) {
this.clearInputElement();
this.files.splice(index, 1);
this.files = [...this.files];
2019-02-12 13:51:28 +00:00
},
isImage(file) {
return /^image\//.test(file.type);
},
clearInputElement() {
this.$refs.fileInput.value = '';
},
clearIEInput() {
if (this.$refs.fileInput) {
this.duplicateIEEvent = true; //IE11 fix to prevent onFileChange trigger again
this.$refs.fileInput.value = '';
}
},
2019-02-12 13:51:28 +00:00
formatSize(bytes) {
if (bytes === 0) {
return '0 B';
}
let k = 1000,
dm = 3,
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
i = Math.floor(Math.log(bytes) / Math.log(k));
2019-10-01 13:00:26 +00:00
2019-02-12 13:51:28 +00:00
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
2020-07-04 10:01:30 +00:00
},
isFileLimitExceeded() {
if (this.fileLimit && this.fileLimit <= this.files.length + this.uploadedFileCount && this.focused) {
this.focused = false;
}
return this.fileLimit && this.fileLimit < this.files.length + this.uploadedFileCount;
},
checkFileLimit() {
if (this.isFileLimitExceeded()) {
this.msgs.push({
severity: 'error',
summary: this.invalidFileLimitMessageSummary.replace('{0}', this.fileLimit.toString()),
detail: this.invalidFileLimitMessageDetail.replace('{0}', this.fileLimit.toString())
});
}
2019-02-12 13:51:28 +00:00
}
},
computed: {
isAdvanced() {
return this.mode === 'advanced';
},
isBasic() {
return this.mode === 'basic';
},
advancedChooseButtonClass() {
2020-07-16 13:37:24 +00:00
return ['p-button p-component p-fileupload-choose', {
2019-02-12 13:51:28 +00:00
'p-disabled': this.disabled,
'p-focus': this.focused
}
];
},
2019-02-13 10:52:55 +00:00
basicChooseButtonClass() {
2020-07-16 13:37:24 +00:00
return ['p-button p-component p-fileupload-choose', {
2019-02-13 10:52:55 +00:00
'p-fileupload-choose-selected': this.hasFiles,
'p-disabled': this.disabled,
'p-focus': this.focused
}];
},
basicChooseButtonIconClass() {
2020-07-01 17:52:25 +00:00
return ['p-button-icon p-button-icon-left pi', {
2019-10-01 13:00:26 +00:00
'pi-plus': !this.hasFiles || this.auto,
2019-02-13 10:52:55 +00:00
'pi-upload': this.hasFiles && !this.auto
}];
},
basicChooseButtonLabel() {
return this.auto ? this.chooseLabel : (this.hasFiles ? this.files[0].name : this.chooseLabel);
},
2019-02-12 13:51:28 +00:00
hasFiles() {
return this.files && this.files.length > 0;
2020-07-04 10:01:30 +00:00
},
chooseDisabled() {
return this.disabled || (this.fileLimit && this.fileLimit <= this.files.length + this.uploadedFileCount);
},
uploadDisabled() {
return this.disabled || !this.hasFiles;
},
cancelDisabled() {
return this.disabled || !this.hasFiles;
2019-02-12 13:51:28 +00:00
}
},
components: {
'FileUploadButton': Button,
'FileUploadProgressBar': ProgressBar,
'FileUploadMessage': Message
},
2020-06-27 09:36:55 +00:00
directives: {
'ripple': Ripple
}
2019-02-12 13:51:28 +00:00
}
</script>
<style>
2019-05-25 21:13:31 +00:00
.p-fileupload-content {
position: relative;
}
.p-fileupload-row {
2020-04-24 16:15:39 +00:00
display: flex;
align-items: center;
2019-05-25 21:13:31 +00:00
}
2020-06-27 10:10:43 +00:00
.p-fileupload-row > div {
flex: 1 1 auto;
width: 25%;
}
.p-fileupload-row > div:last-child {
text-align: right;
}
2019-05-25 21:13:31 +00:00
.p-fileupload-content .p-progressbar {
width: 100%;
position: absolute;
2020-04-28 09:34:12 +00:00
top: 0;
left: 0;
2019-05-25 21:13:31 +00:00
}
2019-05-25 21:26:29 +00:00
.p-button.p-fileupload-choose {
2019-05-25 21:13:31 +00:00
position: relative;
overflow: hidden;
}
2019-05-25 21:26:29 +00:00
.p-button.p-fileupload-choose input[type=file] {
2020-06-27 09:36:55 +00:00
display: none;
2019-05-25 21:13:31 +00:00
}
.p-fileupload-choose.p-fileupload-choose-selected input[type=file] {
display: none;
}
.p-fluid .p-fileupload .p-button {
width: auto;
}
2019-02-12 13:51:28 +00:00
</style>