I am trying to create an avatar editor following the Build a Forum video series.
I am on Laravel 5.8.34.
The console.log in the method #handleFileUpload(e)# shows the file uploaded.
The uploaded image appears on the page.
The console.log in the method #persist(file)# shows an empty object.
DATA FormData {}
The upload does not persist.
My Controller Method:
public function avatar_upload($id)
{
$validate = request()->validate([
'avatar' => ['required', 'image']
]);
$emp = Employee::with('user')->where('user_id', $id)->first();
$avatar = $emp->user->firstName . $emp->user->lastName . '.png';
Storage::disk('spaces')
->putFileAs('avatars', request()->file('avatar'), $avatar, 'public');
$emp->avatar = $avatar;
$emp->save();
return response([], 204);
} // end function
My Component:
<template>
<div>
<div class="text-center mb-4">
<div class="flex justify-center font-thin text-grey-dark text-2xl">
{{user.office}}
</div>
<div class="text-center">
<img class="relative rounded-lg"
:src="avatar">
</div>
<form #submit.prevent="handleFileUpload"
enctype="multipart/form-data"
v-if="canEdit">
<input
type="file"
name="avatar"
ref="file"
accept="image/png"
class="tw-input"
#change="handleFileUpload">
</form>
</div>
</div>
</template>
<script type="text/babel">
export default {
name: 'AvatarReplace',
data() {
return {
canEdit: true,
avatar: this.user.avatar
};
},
props: ['user'],
methods: {
handleFileUpload(e) {
if(! e.target.files.length) { return; } // end if
let file = e.target.files[0];
console.log('FILE', file);
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = e => {
this.avatar = e.target.result;
};
this.persist(file);
},
persist(file) {
let data = new FormData();
data.append('avatar', file);
console.log('DATA', data);
let path = `/api/staff/avatar_upload/${this.user.id}`;
axios.post(path, data)
.then((rsp) => {
//console.log(rsp);
//this.$toastr.s('File Uploaded');
});
}
}
};
</script>
This is not a normal form, Make axios knows that content-type is multipart/form-data
axios.post(path, data, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then((response) => {
//
});
Related
I'm a newbie and i'm trying to create a rest project with Vue and Laravel and I have a form that allows to send an image, but when I try to store it in the db, I got an error:
"Request failed with status code 422"
and
"The image must be an image"
I can't figure how to solve it, any suggestion?
<script>
export default {
data() {
return {
title: undefined,
year: undefined,
director: undefined,
plot: undefined,
rating: undefined,
image: null,
};
},
methods: {
insertedFile(e) {
this.image = e.target.files[0];
},
addFilm() {
const formData = new FormData;
formData.set('image', this.image)
console.log(formData.get('image'));
//
axios
.post("/api/films", {
title: this.title,
year: this.year,
director: this.director,
plot: this.plot,
rating: this.rating,
image:formData
})
.then((response) => {
console.warn(response)
});
},
},
};
</script>
<template>
<form #submit.prevent="addFilm()" enctype="multipart/form-data" method="post">
<input type="text" name="title" placeholder="title" v-model="title" />
<input type="number" name="year" placeholder="year" v-model="year" />
<input
type="text"
name="director"
placeholder="director"
v-model="director"
/>
<input type="text" name="plot" placeholder="plot" v-model="plot" />
<input
type="number"
name="rating"
placeholder="rating"
v-model="rating"
/>
<input
type="file"
name="image"
id="image"
#change="insertedFile($event)"
/>
<button type="submit">Submit</button>
</form>
</template>
Controller:
public function store(Request $request)
{
$request->validate([
'title' => 'required',
'year' => 'required',
'plot' => 'required',
'director' => 'required',
'rating' => 'required',
'image' => 'image|mimes:jpg,png,jpeg,svg|max:2048'
]);
$film = new Film([
'title' => $request->title,
'year' => $request->year,
'plot' => $request->plot,
'director' => $request->director,
'rating' => $request->rating,
"image" => $request->file('image')->store('images', 'public')
]);
$film->save();
return redirect()->route('home')
->with('success', 'film created successfully!');
}
Try combining your payload (data) with your formData and setting the content-type header of your axios request to multipart/form-data:
const config = {
headers: {
'content-type': 'multipart/form-data'
}
}
let data = new FormData();
data.append('title', this.title);
data.append('year', this.year);
data.append('director', this.director);
data.append('plot', this.plot);
data.append('rating', this.rating);
data.append('image', this.image);
axios.post('api/films', data, config)
.then((response) => {
console.warn(response)
})
.catch((error) => {
console.log(error);
});
you're passing the FormData object as image.
in order to make it work, you should give axios the FormData object containing all the data you want to send.
addFilm method should look like this:
const formData = new FormData;
formData.append('image', this.image)
formData.append('title', this.title)
formData.append('year', this.year)
formData.append('director', this.director)
formData.append('plot', this.plot)
formData.append('rating', this.rating)
formData.append('image', this.image)
axios
.post("/api/films", formData)
.then((response) => {
console.warn(response)
});
I'm trying to render a dynamic FormArray (When "+" is clicked it should add a new), but always when I put some file in the input box the Message ("Nenhum Arquivo Selecionado" which means "File Doesn't Exist") stays on the screen.
However, if I check the info on this.filterForm.get('Documents'), the row is filled correctly.
Does anyone have a sugestion to fix this error?
protocolo.component.ts
items: FormArray;
filterForm = new FormGroup({
IdProtocolo: new FormControl(),
Documentos: this.formBuilder.array([ this.createItem() ]
);
ngOnInit() {
this.items = this.filterForm.get('Documentos') as FormArray;
}
createItem(): FormGroup{
return this.formBuilder.group({
filename: '',
filetype: '',
value: ''
})
}
addItem(){
this.items.push(this.createItem());
}
removeItem(index){
if(this.items.length > 1) {
this.items.removeAt(index);
}
}
onFileChange(event: any, index: any) {
let reader = new FileReader();
if(event.target.files && event.target.files.length > 0) {
let file = event.target.files[0];
reader.readAsDataURL(file);
this.items.at(index).patchValue({
filename: file.name,
filetype: file.type,
value: (reader.result as string).split(',')[1]
})
}
}
protocolo.component.html
<div *ngFor="let item of filterForm.value.Documentos; let i = index;">
<div class="row" style="margin-bottom: 10px;">
<div class="col-md-4">
<input type="file" formControlName="Documentos" (change)="onFileChange($event, i)">
</div>
<div class="col-md-8">
<button class="btn btn-success-tce" (click)="addItem()">+</button>
<button class="btn btn-success-tce" (click)="removeItem(i)"style="margin-left: 5px">-</button>
</div>
</div>
[Updated] Possibly wrong implementation of formArray. I cannot see a formArrayName in your template. I would have implemented this like
In your template
<p> Dynamic File Form </p>
<form [formGroup]="someForm" (submit)="formSubmit()">
<div formArrayName="documents">
<div *ngFor="let item of files?.controls; let i = index;">
<input type="file" placeholder="Upload file" [formControlName]="i" (change)="onFileChange($event, i)"/>
</div>
</div>
<button type="submit"> Submit </button>
</form>
<button type="button" (click)="addFileControl()"> Add File </button>
In your component.
initForm() {
this.someForm = this.fb.group({
documents: this.fb.array([this.fileControl])
})
}
get files() {
return this.someForm.get('documents') as FormArray;
}
get fileControl() {
return this.fb.group({
file_item: [null]
})
}
addFileControl() {
this.files.push(this.fileControl);
}
formSubmit() {
console.log(this.someForm.value);
}
onFileChange(event, i) {
let reader = new FileReader();
if (event.target.files && event.target.files.length) {
const [file] = event.target.files;
reader.readAsDataURL(file);
reader.onload = () => {
this.files.controls[i].get('file_item').setValue(reader.result);
// need to run CD since file load runs outside of zone
this.cd.markForCheck();
};
}
}
Here is the stackblitz example. This will give you the output in base64 format but you can also get it in file format by modifying.
onFileChange(event, i) {
if (event.target.files && event.target.files.length) {
this.files.controls[i].get('file_item').setValue(event.target.files;);
}
}
Note:- It is just a rough code but does the job :).
I am new to Vuejs . Please help me .
This is my LevelApi class for handle CRUD operations for Level object in my database . the create function of this class gets the form data and post it to the Node js Api and then returns the response . In my AddLevel component when the user submits the form , I call this method and pass the form data that contains one file to be uploaded to the Level collection in mongodb .
import axios from 'axios'
const API_URL = 'http://example:8080/api/level/'
export default class LevelApi {
create (formData) {
return axios.post('http://example.com/api/level/', formData, {
headers: { 'Authorization': 'Bearer ' + localStorage.getItem('jwt'), 'content-type': 'multipart/form-data' },
onUploadProgress: function (progressEvent) {
this.uploadPercentage = parseInt(Math.round((progressEvent.loaded * 100) / progressEvent.total))
}.bind(this)
})
}
And below is my AddLevel component .
<template>
<div>
<form>
<div class="form-group">
<label>please choose image for this level</label>
<input type="file" id="file" ref="myFiles" #change="previewFiles">
</div>
<button #click.prevent="storeLevel">add level</button>
</form>
<progress-bar v-bind:val="getPercentage" max="100" :text="uploadPercentageWithPercent"></progress-bar>
</div>
</div>
</template>
<script>
import LevelApi from '../../services/api/level/LevelApi' // importing the LevelApi class that I mentioned above
const levelApi = new LevelApi()
export default {
name: 'AddLevel',
data () {
return {
files: {},
uploadPercentage: 0,
}
},
methods: {
storeLevel () {
var formData = new FormData()
formData.append('file', this.files)
levelApi.create(formData).then((response)=>{
console.log(response.data)
});
},
previewFiles () {
this.files = this.$refs.myFiles.files[0]
},
},
computed: {
uploadPercentageWithPercent () {
return this.uploadPercentage.toString().concat('%')
},
getPercentage () {
return this.uploadPercentage
},
resetPercentage () {
this.uploadPercentage = 0
}
},
}
</script>
<style scoped>
</style
Everything is ok and the file uploads but the progress bar does not show the uploadPercentage data . but when I console log this uploadPercentage it grows up to 100% . I changed my code to below and the problem solved . the below code does not use ApiLevel class now . now the progress bar shows the percent of the upload . Whats the problem with my ApiLevel class . Why progress bar does not show the upload percent although uploadPercentage value changes in the console.log function .
var formData = new FormData()
formData.append('file', this.files)
this.$http.post('http://example.com/api/level/', formData, {
headers: { 'Authorization': 'Bearer ' + localStorage.getItem('jwt'), 'content-type': 'multipart/form-data' },
onUploadProgress: function (progressEvent) {
this.uploadPercentage = parseInt(Math.round((progressEvent.loaded * 100) / progressEvent.total))
}.bind(this)
}).then((response) => {
}
})
Which part of function submit my form on upload image? I won't submit form on upload image. I want on submit button. Which part of code make mi problem? One think this code work okay , but I want submit form on upload photo automation. Also which part of my code maybe not need me for this time?
uploadFile(event) {
const formData = new FormData()
formData.append('image', event.target.files[0])
axios({
method: "post",
url: "linkmyapi",
data: formData,
headers: {
"Content-Type": "multipart/form-data"
}
})
.then(response => {
this.items.push(response.data);
this.image = "";
this.profile_image = ''
this.loading = false
this.dragAndDropUpload = false
this.styleObject.backgroundColor = ''
})
.catch(error => {
this.loading = false;
},
onDropFile(e) {
this.dragAndDropUpload = true
e.stopPropagation()
e.preventDefault()
let files = e.dataTransfer.files
this.createFile(files[0])
},
onChangeFile(e) {
// this.manualUpload = true
let files = e.target.files;
this.createFile(files[0])
},
createFile(file) {
if (!file.type.match('image.*')) {
alert('Select an image')
return
}
let reader = new FileReader()
let vm = this
reader.onload = function (e) {
vm.profile_image = e.target.result
}
reader.readAsDataURL(file)
this.uploadFile(event)
},
removeFile() {
this.profile_image = ''
this.styleObject.backgroundColor = ''
},
onDragOver () {
this.styleObject.backgroundColor = 'rgba(0, 160, 223, 0.4)'
},
onDragLeave () {
this.styleObject.backgroundColor = ''
},
HTML is
<div class="upload-container">
<div
:style="styleObject"
class="drop drop-profile"
id="2"
#dragover.prevent="onDragOver()"
#dragleave.prevent="onDragLeave()"
#drop="onDropFile($event)"
:class="{ 'loading-image': loading }">
<label v-if="!profile_image" class="label-text label-text-profile">
Choose or drag
<br> and drop your
profile image
here
<br>
<input
type="file"
name="profile_image"
#change="onChangeFile($event)">
</label>
<div v-else class="hidden">
<img :src="profile_image" alt="Profile image" class="image-profile" />
<div v-if="!loading" class="lc-loupe-trash-container">
<div #click="removeFile" class="lc-trash"></div>
</div>
</div>
</div>
<div v-if="loading" class="spinner-container">
<i class="fa fa-spinner fa-spin"></i>
</div>
</div>
Your question isn't so clear, can you try editing it to be a little clearer? Do you want to automatically upload onDrop into drop area or you want to upload onClick of submit button?
My vue component like this :
<template>
<section>
...
<img class="media-object" :src="baseUrl+'/storage/banner/thumb/'+photo" alt="" width="64" height="64">
...
</section>
</template>
<script>
export default {
props: ['banners'],
data() {
return {
baseUrl: App.baseUrl,
bannerId: this.banners.id,
photo: this.banners.photo // result : chelsea.png
}
},
methods: {
onFileChange(e) {
let files = e.target.files,
reader = new FileReader(),
formData = new FormData(),
self = this
formData.append('file', files[0])
formData.append('banner_id', this.bannerId)
axios.post(window.App.baseUrl+'/admin/banner/upload-image',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(response) {
if(response.data.status == 'success') {
self.photo = response.data.fileName // result : chelsea.png
}
})
.catch(function(error) {
console.log('FAILURE!!')
})
},
...
}
}
</script>
The result of :src : \my-app\storage\app\public\banner\thumb\chelsea.png
When I upload image, it will call onFileChange method. And the process upload will continue in the backend. It success upload in the folder. And the response will return same filename. So the result of response.data.fileName is chelsea.png
My problem here is : it's not update the image automatic when I upload it. When I refresh the page, the image updated
Why the image is not automatic update/changed when I upload the image?
I fixed it by doing the following, notice I added a variable named rand at the end of the photo url for cache busting. When you get a correct response from your server, simply change that variable to something unique (in this case a timestamp) and voila! your image will refresh.
<template>
<img class="media-object" :src="baseUrl+'/storage/banner/thumb/'+photo + '?rand=' + rand" alt="" width="64" height="64">
</template>
<script>
export default {
data() {
return {
rand: 1
}
},
methods: {
onFileChange(e) {
...
axios.post(url,formData).then(function(response) {
if(response.data.status == 'success') {
self.rand = Date.now()
}
})
},
...
}
}
Your images are cached by the browser.
Try to add any tag to the image like:
chelsea.png?t=<random>
The answer, as provided above, are computed properties as these designed to be reactive, but when it comes to async it best to use promises / observables. However, if you decide not use and are still experiencing problems, then you can use a loading property, like the loading property in the example below to manipulate the DOM i.e. remove the DOM with v-if when you initiate async (axios). Get and set the the image and then restore the DOM element with this.loading = true;. This forces a render of the DOM, which forces a computed property.
<template>
<section>
<div v-if="!loading">
<img class="media-object" :src="getPhoto" :alt="getAlt" width="64" height="64">
</div>
<div v-if="loading">
<!-- OR some spinner-->
<div>Loading image</div>
</div>
</section>
</template>
<script>
export default {
props: ['banners'],
data() {
return {
loading: false,
baseUrl: App.baseUrl,
bannerId: this.banners.id,
photo: {} // result : chelsea.png
}
},
computed: {
getPhoto() {
return App.baseUrl + '/storage/banner/thumb/' + this.photo.fileName;
},
getAlt() {
return photo.alt;
},
},
methods: {
onFileChange(e) {
let files = e.target.files,
reader = new FileReader(),
formData = new FormData(),
self = this
// Set loading to true
this.loading = true;
formData.append('file', files[0])
formData.append('banner_id', this.bannerId)
axios.post(window.App.baseUrl+'/admin/banner/upload-image',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(response) {
if(response.data.status == 'success') {
self.photo = response.data.fileName // result : chelsea.png
this.loading = false;
}
})
.catch(function(error) {
console.log('FAILURE!!')
})
},
...
}
}
</script>
Just use computed property, snippet below used getImageUrl to get the updated path. I added button to trigger the mimic change on the data provided.
new Vue({
el: "#app",
data: {
baseUrl: 'baseURl', //dummy
bannerId: '', //dummy
photo: 'initPhoto.png' // dummy
},
computed: {
getImageUrl: function() {
return this.baseUrl + '/storage/banner/thumb/' + this.photo;
}
},
methods: {
mimicOnChange: function() {
this.photo = "chelsea.png"
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<span>{{ getImageUrl }}</span>
<br/>
<button #click="mimicOnChange">
On change trigger
</button>
</div>
On you above code, just use the computed directly to your src attribute:
<img class="media-object" :src="getImageUrl" alt="" width="64" height="64">
Try binding full photo's path:
<template>
<section>
...
<img v-if="photoLink" class="media-object" :src="photoLink" alt="" width="64" height="64">
<p v-text="photoLink"></p>
...
</section>
</template>
<script>
export default {
props: ['banners'],
data() {
return {
baseUrl: App.baseUrl,
bannerId: this.banners.id,
photo: this.banners.photo, // result : chelsea.png
photoLink: App.baseUrl + '/storage/banner/thumb/' + this.banners.photo
}
},
methods: {
onFileChange(e) {
let files = e.target.files,
reader = new FileReader(),
formData = new FormData(),
self = this
formData.append('file', files[0])
formData.append('banner_id', this.bannerId)
axios.post(window.App.baseUrl+'/admin/banner/upload-image',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(response) {
if(response.data.status == 'success') {
// self.photo = response.data.fileName // result : chelsea.png
console.log('>>>INSIDE SUCCESS');
self.photoLink = self.baseUrl + '/storage/banner/thumb/' + response.data.fileName;
}
})
.catch(function(error) {
console.log('FAILURE!!')
})
},
...
}
}
I've had the same problem where inserting the same image won't trigger any action after the input. I fixed it by clearing the input.
clearInput(e) {
e.target.value = '';
},
I had some weird behaviour with vue, where after upload the img, the base64 data, was on the src img, but the browser somehow did not render it correctly, only doing any action in the form like clicking any button etc.. would magically appear.
So that was solved using a setTimeout.
uploadNewImg () {
let self = this
// Get the selected file
var file = this.$refs.profileImg.files[0]
// Create a new FileReader object
var reader = new FileReader()
reader.onload = function (e) {
// Create a new FormData object
var formData = new FormData()
formData.append('file', file)
setTimeout(function () {
self.profilePic = e.target.result// this is used as src of img tag
}, 1)
}
Looking at your question. I could not see whether you would like the process to be sync or async so I add my solution. I prefer to use Async/await in such cases and This should fix the problem of image render:
async onFileChange(e) {
let formData = new FormData();
formData.append('file', files[0]);
formData.append('banner_id', this.bannerId);
//.... Add headers and payload for post request
const {data} = await axios.post(window.App.baseUrl+'/admin/banner/upload-image', payload);
if(data.status === 'success') {
self.photo = data.fileName // result : chelsea.png
}
}