A simple, light weight and intuitive upload control for Vue.js.
Features:
- Drag and drop file zone.
- Progress bars
- Synchronous or asynchronous modes.
- File validation
- Max files check (with current).
NOTE This plugin has only been tested with Vue 2.x.
$ sudo npm install @websanova/vue-upload
Require in the project.
Vue.use(require('@websanova/vue-upload'));
At a minimum you will need to at least provide a url
when creating a new $upload
instance.
this.$upload.new('profile-avatar', {
url: 'users/1/avatar'
});
It's likely things like the id
would not be static so use the reset
function to update any options.
created() {
this.$upload.new('profile-avatar', {
onSuccess(res) {
// Update user
}
});
},
mounted() {
this.$upload.reset('profile-avatar', {
url: 'users/' + this.$user.id + '/avatar'
});
},
Check the Examples section below for more use case scenarios.
All files will be in an array even if multiple
is set to false
.
For single items the first file in the object can be taken directly.
$upload.files('profile-avatar')[0];
For a large set they can be looped through.
<div v-for="file in $upload.files('product-gallery').all">
{{ file.name }} <br/>
{{ file.size }} <br/>
{{ file.type }} <br/>
{{ file.status }} <br/>
{{ file.percentComplete }} <br/>
{{ file.errors | json }}<br/>
</div>
All files contain the following meta data:
name
The name of the file.
size
The size of the file.
type
The mime type of the file.
status
Current status of the file which will be either ready
, sending
, error
or success
.
percentComplete
Percent progress indicator for the file as an integer (0 to 100);
errors
Errors from file.
Note that errors can come internally from the module itself or externally from an error on the server end.
Use parseErrors
option when installing the plugin to format errors from the server.
The internal default format is: [{rule: 'somerule', message: 'There was an error.''}]
preview
The preview
options is a function that can be set with a callback that will locally load the file (not uploaded).
this.$upload.new('brand-logo', {
onSelect(files) {
files[0].preview((file) => {
// Do something with `file.raw`.
});
}
});
The plugin comes with two tiers of errors. It's important to know the difference between them.
The top level error is designed to prevent the upload from even beginning.
This is primarily for cases where for instance a max amount of files has been reached or too many files have been selected.
Some important things to note:
- If there any errors in single files it will log ONE error here saying so.
- If it's global conditions are met it will still fire off individual uploads.
- It has statuses:
ready
,sending
,error
,complete
which can be found in themeta
object. - The
error
status will only get triggered if the global condition fails. - The
complete
status will still trigger for individual file errors. An global error will be logged (more as a warning) but the state will NOT be inerror
.
This one is pretty much self explanatory. If an error is caught it will be tracked and status updated accordingly.
Notes:
- The status for single file uploads can be
ready
,sending
,error
,success
. - Errors are tracked in an array
[{rule: 'somerule', message: 'There is an error.'}]
. - Use the
parseError
option to change the way errors are parsed from the server. - If an error is caught the file will be moved into the
files.error
array (local or server).
Different ways to deal with errors are covered in the Examples section below.
This is used to create a new "upload" instance.
- An upload instance is tracked in a global state.
- Existing states will not be overwritten so you can call
new
multiple times without worry (usereset
for updating options). - It will always contain the current components instance when using
this
.
Typically a combination of new
and reset
will be used. It's mostly semantic but it's nice to keep the separation.
created() {
this.$upload.new('profile-avatar', {
onSuccess(res) {
this.$msgbag.success('Avatar uploaded successfully.');
this.$auth.user(res.data.data);
},
onError() {
this.$msgbag.error('Error uploading avatar.');
}
});
},
mounted() {
this.$upload.reset('profile-avatar', {
url: 'users/' + this.$auth.user().id + '/avatar'
});
},
This will completely reset the instance.
Also check the new
option for more details.
- An instance using
new
must be created first. - Additional options can be passed in to override any existing ones (useful for updating the url).
mounted() {
this.$upload.reset('profile-avatar', {
url: 'users/' + this.$auth.user().id + '/avatar'
});
},
It's also useful to call reset()
for clearing an upload when using multiple
.
For instance a "clear" button.
<button v-on:click="$upload.reset('product-gallery')">
Clear
</button>
This would not affect any existing options, but only clear out the files
arrays and reset some meta
values.
This is used to trigger the browsers file select popup/dialog.
- It's important to note that by default the uploads will start once files are selected.
- Set
startOnSelect
option tofalse
to prevent uploads from beginning after selection.
<button v-on:click="$upload.select('brand-logo')">
Update Photo
</button>
This option is to be used in conjunction with the startOnSelect
option when it's set to false
.
- It allows manual triggering of the upload.
- Good to use with previews.
<img :src="brandLogo" />
<button v-on:click="$upload.select('brand-logo')">
Select Photo
</button>
<button v-on:click="$upload.start('brand-logo')">
Upload Photo
</button>
this.$upload.new('brand-logo', {
onSelect(files) {
files[0].preview((file) => {
this.brandLogo = file.raw;
});
}
});
Contains arrays of files currently being processed.
- It contains multiple queues that can be used to display file data for viewing.
- The queues are
all
,queued
,progress
,success
,error
,complete
. - Note in some cases there is overlap like in
success
,error
,complete
. - The array will not reset after completing (use
reset
andonEnd
for that).
<div v-for="file in $upload.files('product-gallery').progress">
{{ file.name }}: {{ file.percentComplete }}%
</div>
<div v-for="file in $upload.files('product-gallery').queued">
{{ file.name }}: Queued for upload
</div>
Fetches some meta info about the current uploads.
The meta info is fully reactive an can be used directly in the templates.
{{ $upload.meta('product-gallery').percentComplete }}%
It contains the following properties:
status
It will be either ready
, sending
, error
, complete
. Check the Errors section above for more info.
totalFiles
For keeping track of the total files in the current upload set.
- It's important to note that once all uploads complete it will be reset to 0.
- This primarily used to keep track of current upload progress percentage.
- If there are still items in queue or in progress and more files are added this will increment.
percentComplete
Keeps track of progress for the currently uploading files.
dropzoneActive
For use with drag/drop files to upload into a "drop zone".
- Primarily used to be able to trigger an overlay when using a drop container.
- This does NOT use the "dropzone" library.
<el-overlay v-show="$upload.meta('product-gallery').dropzoneActive">
Drop files anywhere here to begin upload.
</el-overlay>
This returns the global error state for the upload instance.
- This will not return the individual file errors.
<div v-if="$upload.errors('product-gallery').length">
<div v-for="error in $upload.errors('product-gallery')">
{{ error.rule }}: {{ error.message }}
</div>
</div>
Used for removing a single file from the files
arrays.
- This will remove the file from all files arrays where it exists.
$upload.remove('product-gallery', file);
This is required and sets the end point for the upload.
The "name" to use for the upload file.
Any additional form data to be sent with the upload.
Triggered when files are selected.
Triggered once upload begins.
Triggered once a file is moved into queue.
Triggered once a file begins to upload.
Triggered if file has an error (front or back end).
Triggered when server sends back a success.
Triggered after either error
or success
.
Triggered when currently uploading files all complete.
Used to parse errors from server end.
It must return an array of objects in the format: [{rule: 'someerror', message: 'There was an error.'}]
By default the plugin "assumes" Vue.http
from vue-resource
is used.
However this can be easily overridden.
It contains five parameters: url
, body
, progress
, success
, error
.
function _http(data) {
this.Vue.http.post(data.url, data.body, {progress: data.progress}).then(data.success, data.error);
}
For multiple file uploads this must be set to true
.
For asynchronous uploads set this to true
.
Can also be used in conjunction with maxFilesInProgress
.
Set the maximum number of uploads that can run in parallel when async
is set to true
.
To disable the upload from beginning automatically set this to false
.
The set of valid extensions allowed for upload.
The maximum number of files allowed for upload.
Note that this works in conjunction with currentFiles
for a set maximum.
If this value is set to an integer it will subtract from maxFiles
to create a set maximum of uploads.
Note that if it's set to an integer (not null) the value will automatically increment on each upload success
.
Set the maximum size per file.
Set the dropzone container which will automatically create the dropzone.
Important to note that (to be safe) the dropzone should always be unloaded when leaving the component it is called in.
created() {
this.$upload.new('product-gallery', {
async: true,
multiple: true
});
},
mounted() {
this.$upload.reset('product-gallery', {
url: `products/${this.product.id}/gallery`,
dropzoneId: 'product-gallery-dropzone',
});
},
beforeDestroy() {
this.$upload.reset('product-gallery', {
dropzoneId: null
});
},
Some common scenarios likely to be encountered when doing an upload and how $upload
can handle them.
A simple profile avatar upload with button, status and errors.
<img :src="$auth.user().avatar || '//www.gravatar.com/avatar/?d=mysteryman&s=200'" />
<div>
<button v-on:click="$upload.select('profile-avatar')" :disabled="$upload.meta('profile-avatar').status === 'sending'">
<span v-show="$upload.meta('profile-avatar').status === 'sending'">Updating...</span>
<span v-show="!$upload.meta('profile-avatar').status === 'sending'">Update Photo</span>
</button>
</div>
<div v-if="$upload.files('profile-avatar').error.length">
{{ $upload.files('profile-avatar').error[0].errors[0].message }}
</div>
created() {
this.$upload.new('profile-avatar', {
onSuccess(res) {
this.$msgbag.success('Avatar uploaded successfully.');
this.$auth.user(res.data.data);
},
onError() {
this.$msgbag.error('Error uploading avatar.');
}
});
},
mounted() {
this.$upload.reset('profile-avatar', {
url: `users/${this.$auth.user().id}/avatar`
});
},
Selecting an image with preview before uploading.
<img :src="brandImage || '//www.gravatar.com/avatar/?d=mysteryman&s=200'" />
<div>
<button type="button" v-on:click="$upload.select('brand-logo')" :disabled="$upload.meta('brand-logo').status === 'sending'">
Select Logo
</button>
<button type="button" v-on:click="$upload.start('brand-logo')" :disabled="$upload.meta('brand-logo').status === 'sending'">
<span v-show="$upload.meta('brand-logo').status === 'sending'">Saving...</span>
<span v-show="!$upload.meta('brand-logo').status === 'sending'">Save Logo</span>
</button>
</div>
<div v-if="$upload.files('brand-logo').error.length" class="text-danger">
{{ $upload.files('brand-logo').error[0].errors[0].message }}
</div>
data() {
return {
brandImage: null
};
},
created() {
this.$upload.new('brand-logo', {
startOnSelect: false,
onSuccess(res) {
this.$msgbag.success('Brand logo uploaded successfully.');
this.brand = res.data.data;
},
onError() {
this.$msgbag.error('Error uploading brand logo.');
},
onSelect(files) {
files[0].preview((file) => {
this.brandImage = file.raw;
});
}
});
},
mounted() {
this.$upload.reset('brand-logo', {
url: 'brands/' + this.brand.id + '/logo'
});
this.brandImage = this.brand.logo || '//www.gravatar.com/avatar/?d=identicon&s=100';
}
Multiple file upload with async, dropzone and file list.
<div>
<button v-on:click="$upload.select('product-gallery')" :disabled="$upload.meta('product-gallery').status === 'sending'">
<span v-show="$upload.meta('product-gallery').status === 'sending'">Uploading...</span>
<span v-show="!$upload.meta('product-gallery').status === 'sending'">Select Photos</span>
</button>
<button v-on:click="$upload.reset('product-gallery')" :disabled="$upload.meta('product-gallery').status === 'sending'">
Clear
</button>
</div>
<div class="progress">
<div class="progress-bar" :style="'width: ' + $upload.meta('product-gallery').percentComplete + '%;'">
{{ $upload.meta('product-gallery').percentComplete }}% Complete
</div>
</div>
<div v-if="$upload.errors('product-gallery').length" class="text-danger">
{{ $upload.errors('product-gallery')[0].message }}
</div>
<div>
<div v-if="!$upload.files('product-gallery').all.length">
No uploads here yet.
</div>
<div v-for="file in $upload.files('product-gallery').progress">
<div>
{{ file.name }}
</div>
<div class="progress">
<div class="progress-bar" :style="'width: ' + file.percentComplete + '%;'">
{{ file.percentComplete }}%
</div>
</div>
</div>
<div v-for="file in $upload.files('product-gallery').queued">
<div>
{{ file.name }}
<br/>
Queued for upload
</div>
</div>
<div v-for="file in $upload.files('product-gallery').complete">
<div v-if="file.errors.length">
{{ file.name }}
<br/>
{{ file.errors[0].message }}
</div>
<div v-if="!file.errors.length">
{{ file.name }}
<br/>
Uploaded successfully.
</div>
</div>
</div>
created() {
this.$upload.new('product-gallery', {
async: true,
maxFiles: 20,
multiple: true,
onStart() {
this.$toggle.show('product:media:uploads');
},
onSuccess(res) {
this.product.gallery.push(res.data.data);
},
onEnd() {
this.$msgbag.success('File upload complete.');
}
});
},
mounted() {
this.$upload.reset('product-gallery', {
url: 'products/' + this.product.id + '/gallery',
currentFiles: this.product.gallery.length,
dropzoneId: 'product-gallery-dropzone',
});
},
beforeDestroy() {
this.$upload.reset('product-gallery', {
dropzoneId: null
});
},