Atl There Was a Problem Accessing the File to Upload
Introduction
In this article, we volition talk about how to handle file uploads with VueJs. We volition create an images uploader that allow user to upload single or multiple images file by drag and drop or select file dialog.
Nosotros volition then upload the selected images and display them appropriately. We will as well learn to filter the upload file blazon, for instance, nosotros just let images, do not allow file type like PDF.
- Sourcecode: https://github.com/chybie/file-upload-vue
- Demo: https://vue-file-upload-1126b.firebaseapp.com/
File Upload UI & API
File upload consists of 2 parts: the UI (front-stop) and the API (back-cease). We will be using VueJs to handle the UI role. We need a backend application to accept the uploaded files. You may follow the backend tutorials or download and run either one of these server side application to handle file upload for your backend:-
- File upload with Hapi.js: https://scotch.io/bar-talk/handling-file-uploads-with-hapi-js, or
- File upload with Express + Multer: https://scotch.io/tutorials/limited-file-uploads-with-multer, or
- Switch to any deject solution of your choice (Amazon S3, Google Bulldoze, etc).
Nosotros will be using File upload with Hapi.js equally our backend throughout this articles. We will also larn the tricks to enable fake upload on the front-end.
Setup Project with Vue-Cli
We volition be using vue-cli to scaffold Vue.js projects. We will be using the webpack-simple
project template.
# install cli npm install vue-cli -grand # so create project, with sass # follow the instructions to install all necessary dependencies vue init webpack-elementary file-upload-vue
Alright, all set. Let'southward go along to create our component.
File Upload Component
We will write our code in App.vue
. Remove all the auto-generated lawmaking in the file.
<!-- App.vue --> <!-- HTML Template --> <template > <div id = "app" > <div form = "container" > <!--UPLOAD--> <form enctype = "multipart/form-data" novalidate 5-if = "isInitial || isSaving" > <h1 > Upload images </h1 > <div class = "dropbox" > <input type = "file" multiple :name = "uploadFieldName" :disabled = "isSaving" @alter = "filesChange($outcome.target.proper name, $event.target.files); fileCount = $event.target.files.length" accept = "image/*" class = "input-file" > <p five-if = "isInitial" > Drag your file(s) here to begin <br > or click to scan </p > <p v-if = "isSaving" > Uploading {{ fileCount }} files... </p > </div > </grade > </div > </template > <!-- Javascript --> <script > </script > <!-- SASS styling --> <style lang = "scss" > </style >
Notes:-
- Our
App.vue
component consists of 3 part: template (HTML), script (Javascript) and styles (SASS). - Our template has an upload form.
- The form attribute
enctype="multipart/class-data"
is of import. To enable file upload, this attribute must exist set. Larn more than about enctype here. - We have a file input
<input type="file" />
to accept file upload. The propertymultiple
indicate it'due south allow multiple file upload. Remove it for single file upload. - We will handle the file input
change
issue. Whenever the file input change (someone drop or select files), we will trigger thefilesChange
role and pass in the control name and selected files$event.target.files
, and so upload to server. - We limit the file input to accept images but with the attribute
have="image/*"
. - The file input will exist disabled during upload, and so user tin but drib / select files again after upload complete.
- We capture the
fileCount
of the when file changes. We apply thefileCount
variable in displaying number of files uploadingUploading {{ fileCount }} files...
.
Way our File Upload Component
Now, that's the interesting office. Currently, our component expect similar this:
We need to transform it to look similar this:
Let's style it!
<!-- App.vue --> ... <!-- SASS styling --> <way lang="scss"> .dropbox { outline : 2px dashed grey; / * the dash box * / outline-first : -10px; background : lightcyan; color : dimgray; padding : 10px 10px; min-height : 200px; / * minimum height * / position : relative; cursor : pointer; } .input-file { opacity : 0; / * invisible only it's there! * / width : 100%; top : 200px; position : accented; cursor : pointer; } .dropbox : hover { groundwork : lightblue; / * when mouse over to the driblet zone, change color * / } .dropbox p { font-size : 1.2em; text-align : center; padding : 50px 0; } </style>
With only few lines of scss, our component looks prettier now.
Notes:-
- Nosotros make the file input invisible by applying
opacity: 0
style. This doesn't hide the file input, it just brand it invisible. - Then, we fashion the file input parent element, the
dropbox
css class. We make it look like a drib file zone surround with dash. - And then, we align the text inside dropbox to center.
File Upload Component Lawmaking
Let'south go along to code our component.
< ! -- App.vue -- > ... < ! -- Javascript -- > <script> import { upload } from './file-upload.service' ; const STATUS_INITIAL = 0 , STATUS_SAVING = one , STATUS_SUCCESS = 2 , STATUS_FAILED = 3 ; export default { name : 'app' , information ( ) { render { uploadedFiles : [ ] , uploadError : cipher , currentStatus : zippo , uploadFieldName : 'photos' } } , computed : { isInitial ( ) { return this .currentStatus === STATUS_INITIAL ; } , isSaving ( ) { return this .currentStatus === STATUS_SAVING ; } , isSuccess ( ) { return this .currentStatus === STATUS_SUCCESS ; } , isFailed ( ) { return this .currentStatus === STATUS_FAILED ; } } , methods : { reset ( ) { // reset form to initial state this .currentStatus = STATUS_INITIAL ; this .uploadedFiles = [ ] ; this .uploadError = null ; } , save ( formData ) { // upload data to the server this .currentStatus = STATUS_SAVING ; upload (formData) . so ( x => { this .uploadedFiles = [ ] . concat (x) ; this .currentStatus = STATUS_SUCCESS ; } ) . catch ( err => { this .uploadError = err.response; this .currentStatus = STATUS_FAILED ; } ) ; } , filesChange ( fieldName, fileList ) { // handle file changes const formData = new FormData ( ) ; if ( !fileList.length) return ; // append the files to FormData Array . from ( Assortment (fileList.length) . keys ( ) ) . map ( 10 => { formData. append (fieldName, fileList[10] , fileList[x] .name) ; } ) ; // salve information technology this . save (formData) ; } } , mounted ( ) { this . reset ( ) ; } , } < /script>
Notes:-
- Our component will have a few statuses: STATUS_INITIAL, STATUS_SAVING, STATUS_SUCCESS, STATUS_FAILED, the variable proper noun is pretty expressive themselves.
- Subsequently, we will call the Hapi.js file upload API to upload images, the API accept a field call
photos
. That's our file input field name. - We handle the file changes with the
filesChange
part.FileList
is an object returned by the files property of the HTML <input> element. It allow us to access the list of files selected with the <input type="file"> element. Learn more than [here]((https://developer.mozilla.org/en/docs/Web/API/FileList). - We then create a new
FormData
, and append all ourphotos
files to it.FormData
interface provides a fashion to hands construct a ready of fundamental/value pairs representing form fields and their values. Learn more here. - The
save
function will call our file upload service (hang on, nosotros will create the service side by side!). We likewise set the status according to the issue. -
mount()
is the vue component life cycle hook. During that bespeak, we will prepare our component condition to initial state.
File Upload Service
Let'due south proceed to create our service. We will be using axios to make HTTP calls.
Install axios
# install axios npm install axios --relieve
Service
// file-upload.service.js import * as axios from 'axios' ; const BASE_URL = 'http://localhost:3001' ; office upload ( formData ) { const url = ` ${ BASE_URL } /photos/upload ` ; return axios. postal service (url, formData) // get data . and then ( x => x.data) // add url field . then ( x => x. map ( img => Object. assign ( { } , img, { url : ` ${ BASE_URL } /images/ ${img.id} ` } ) ) ) ; } export { upload }
Naught much, the code is pretty expressive itself. Nosotros upload the files, wait for the consequence, map it appropriately.
You may run the application now with npm run dev
command. Try uploading a couple of images, and it's working! (Remember to starting time your backend server)
Display Success and Failed Result
We can upload the files successfully now. However, there's no indication in UI. Let's update our HTML template.
<!-- App.vue --> <!-- HTML Template --> <template > <div id = "app" > <div class = "container" > ...grade... <!--SUCCESS--> <div v-if = "isSuccess" > <h2 > Uploaded {{ uploadedFiles.length }} file(southward) successfully. </h2 > <p > <a href = "javascript:void(0)" @click = "reset()" > Upload once more </a > </p > <ul class = "listing-unstyled" > <li v-for = "particular in uploadedFiles" > <img :src = "detail.url" class = "img-responsive img-thumbnail" :alt = "item.originalName" > </li > </ul > </div > <!--FAILED--> <div five-if = "isFailed" > <h2 > Uploaded failed. </h2 > <p > <a href = "javascript:void(0)" @click = "reset()" > Endeavour again </a > </p > <pre > {{ uploadError }} </pre > </div > </div > </div > </template >
Notes:-
- Brandish the uploaded prototype when upload successfully.
- Display the error message when upload failed.
Faux the Upload in Front-end
If you are lazy to get-go the back-end awarding (Hapi, Limited, etc) to handle file upload. Hither is a false service to replace the file upload service.
// file-upload.fake.service.js function upload ( formData ) { const photos = formData. getAll ( 'photos' ) ; const promises = photos. map ( ( x ) => getImage (x) . then ( img => ( { id : img, originalName : x.name, fileName : x.name, url : img } ) ) ) ; return Hope. all (promises) ; } function getImage ( file ) { return new Promise ( ( resolve, reject ) => { const fReader = new FileReader ( ) ; const img = document. createElement ( 'img' ) ; fReader. onload = ( ) => { img.src = fReader.outcome; resolve ( getBase64Image (img) ) ; } fReader. readAsDataURL (file) ; } ) } office getBase64Image ( img ) { const sail = document. createElement ( 'canvas' ) ; canvas.width = img.width; sheet.pinnacle = img.height; const ctx = canvas. getContext ( '2nd' ) ; ctx. drawImage (img, 0 , 0 ) ; const dataURL = canvas. toDataURL ( 'paradigm/png' ) ; return dataURL; } consign { upload }
Came beyond this solution in this Stackoverflow post. Pretty useful. My online demo is using this service.
Basically, what the code do is read the source, describe information technology in sail, and salvage it equally data url with the sail toDataURL
function. Learn more virtually canvass here.
Now you can swap the real service with the fake one.
< ! -- App.vue -- > ... < ! -- Javascript -- > <script> // swap as y'all need import { upload } from './file-upload.fake.service' ; // imitation service // import { upload } from './file-upload.service'; // real service < /script> ...
Done! Finish your backend API, refresh your browser, you should see our app is yet working, calling faux service instead.
Bonus: Delay Your Promises
Sometimes, you lot may desire to filibuster the promises to run into the state changes. In our case, the file upload may complete as well fast. Let's write a helper part for that.
// utils.js // utils to filibuster hope role wait ( ms ) { render ( x ) => { return new Promise ( resolve => setTimeout ( ( ) => resolve (x) , ms) ) ; } ; } export { wait }
Then, you can use information technology in your component
< ! -- App.vue -- > ... < ! -- Javascript -- > <script> import { await } from './utils' ; ... save ( formData ) { ... . upload (formData) . so ( wait ( 1500 ) ) // DEV Simply: await for 1.5s . then ( x => { this .uploadedFiles = [ ] . concat (10) ; this .currentStatus = STATUS_SUCCESS ; } ) ... } , < /script>
Conclusion
That'due south it. This is how yous can handle file upload without using any third party libraries and plugins in Vue. It isn't that difficult right?
Happy coding!
The UI (Front-cease)
- Sourcecode: https://github.com/chybie/file-upload-vue
- Demo: https://vue-file-upload-1126b.firebaseapp.com/
The API (Back-end) Tutorials and Sourcode
- File upload with Hapi.js: https://scotch.io/bar-talk/handling-file-uploads-with-hapi-js, or
- File upload with Express + Multer: https://scotch.io/tutorials/express-file-uploads-with-multer, or
- Switch to whatever cloud solution of your selection (Amazon S3, Google Drive, etc).
Source: https://www.digitalocean.com/community/tutorials/how-to-handle-file-uploads-in-vue-2