Correct way to upload and save image in Laravel with vue.js and Element-ui - laravel

I'm making a recipe book and I want the recipes to have the choice to upload images, I'm using Element-UI in this project and they have an upload component. However, I'm not super sure how to use it correctly. I'm basing this on some code I found but it's not really working the $request I receive in the controller always has the image: null. I'm using $intertia.post but I could change to $http.post if needed.
This is what I'm trying
<el-upload
class="avatar-uploader"
action="/api/vendors/fake-upload"
accept="image/*"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="form.image" :src="form.image" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
<div class="buttonImage">
<el-button v-if="form.image" class="img-button mt-1" type="warning">
Change Picture
</el-button>
</div>
</el-upload>
Relevant data() in my vue <script>
loadingImage: false,
imageFile: null,
form: {
name: '',
description: '',
image: ''
},
These are the methods that go with the <el-upload>
handleAvatarSuccess(res, file) {
this.form.image = URL.createObjectURL(file.raw);
this.loadingImage = false;
},
beforeAvatarUpload(file) {
this.imageFile = file;
const isJPG = file.type === 'image/jpeg';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error('This picture must be a JPG!');
}
if (!isLt2M) {
this.$message.error('This image is bigger than 2MB!');
}
this.loadingImage = true;
return isLt2M && isJPG;
},
This is how I'm sending it to the controller
submit() {
this.$refs.form.validate((valid) => {
if (valid) {
this.loading = true;
if (!this.form.id) {
this.$inertia.post(this.baseUrl, {
name: this.form.name,
description: this.form.description,
category: this.category,
steps: this.steps,
ingredient: this.ingredient,
measurements: this.measurements,
image: this.imageFile
}).then(
() => {
this.recipe = this.$page.recipe;
this.$message({
type: 'success',
message: 'Created correctly.'
});
this.loading = false
},
(res) => {
this.$message.error(parseError(res)[0]);
this.loading = false;
})
}
} else {
return false;
}
this.reset();
});
},
What's the correct way to do this, or is there an easier way?

Related

How to delete data using Vue js?

I'm trying delete data but I'm getting this error:
this.jobPosts.filter is not a function
PostJobIndex.vue file:
deleteJobPost: async function(jobPost) {
if (!window.confirm('Are you sure you want to delete this Job Post?')) {
return;
}
try {
await employerService.deleteJobPost(jobPost.id);
this.jobPosts = this.jobPosts.filter(obj => {
return obj.id != jobPost.id;
});
console.log(this.jobPosts);
this.$toast.success("Job Post deleted Successfully!");
} catch (error) {
console.log(error);
this.$toast.error(error.response.data.message);
}
},
I had this same issue with my Update method and I beleive it was because I was trying to map through an object or something instead of an array. In the end I used Object.keys(this.jobPosts).map for my update method and it worked:
Object.keys(this.jobPosts).map(jobPost => {
if (jobPost.id == response.data.id) {
for (let key in response.data) {
jobPost[key] = response.data[key];
}
}
});
But when I do this for Update it doesn't work:
this.jobPosts = Object.keys(this.jobPosts).filter(obj => {
return obj.id != jobPost.id;
});
UPDATED
Here is the code for loading the job posts:
loadJobPosts: async function() {
try {
const response = await employerService.loadJobPosts();
this.jobPosts = response.data;
console.log(this.jobPosts);
} catch (error) {
this.$toast.error('Some error occurred, please refresh!');
}
},
Im using Vuex for state management and I'm using services, that simply contain the axios http requests. That's where this line comes from employerService.loadJobPosts() loadJobPosts() is a function inside my employerService.js file.
I'm also using Laravel for my back end. Here is my JobPostsController.php file:
public function index()
{
$jobPosts = JobPost::all()->where('user_id', Auth::user()->id);
return response()->json($jobPosts, 200);
}
From what I've understood from your code,
this should work for removing jobPost from jobPosts
this.jobPosts = this.jobPosts.filter(obj => {
return obj.id != jobPost.id;
});
I don't know what you're expecting this to do, but it won't do anything useful and will either error or return false for everything.
this.jobPosts = Object.keys(this.jobPosts).filter(obj => {
return obj.id != jobPost.id;
});
filter exists on array types, so I would check where it's getting set and make sure it's an array.
I've included a small snippet in case it's any help.
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: "#app",
data: () => {
return {
jobPosts: [],
deleteJobId: 1
};
},
methods: {
getJobPosts() {
this.jobPosts = [{
id: 1
}, {
id: 2
}, {
id: 3
}, {
id: 4
}, {
id: 5
}];
},
deleteJob() {
if (!this.deleteJobId)
return;
this.jobPosts = this.jobPosts.filter(x => x.id !== this.deleteJobId);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button type="button" #click="getJobPosts">Get Jobs</button>
<div>
<button type="button" #click="deleteJob">Delete Job #</button>
<input type="number" v-model.number="deleteJobId" />
</div>
<ul>
<li v-for="jobPost in jobPosts">
Job Post #{{jobPost.id}}
</li>
</ul>
</div>
You have already answered your own question:
in my data() object, I have this jobPosts: [], but in the console it says Object
As for your second question:
I don't know how to return the data as an array
There are similiar topics here on SO.
I am not familiar with Laravel but assuming you have an eloquent model with JobPost in your index-function according to the docs you should use the .toArray-method:
$jobPosts = JobPost::all()->where('user_id', Auth::user()->id).toArray();
When working with plain collections the values method should do the trick of returning an array instead of an object:
$collection = collect([
10 => ['product' => 'Desk', 'price' => 200],
11 => ['product' => 'Desk', 'price' => 200]
]);
$values = $collection->values();
$values->all();
UPDATE
I just realized that your result is just a stringified JSON object that needs to be converted into an array. Just parse it before processing (take out the JSON.parse(...) if you are already taking care of it in your service), return the object properties as an array and you are good to go:)
this.jobPosts = Object.values(JSON.parse(this.jobPosts)).filter(obj => {
return obj.id != jobPost.id;
});

Filepond, DELETE request, and filename

I've got a server that accepts the DELETE request from Filepond just fine. However, I am having a hard time trying to find the filename in the request. After reading, I found that it is in the Body of the DELETE request. Is that true? If so, how does my server access it? req.filename?
if(typeofreq === 'DELETE'){
var nameoffile = req.body; //<---??? How do I pull out the filename?
if(nameoffile === undefined){
res.err;
}else{
var fs = require('fs');
fs.unlinkSync('./assets/images/' + req.params(filename));
}
}
---------------------FilePond React -----------
<> <div className="Fileupload">
<FilePond ref={ref => this.pond = ref}
files={this.state.files}
name="avatar"
allowImageCrop={true}
allowMultiple={true}
method="POST"
maxFiles={5}
allowImageExifOrientation={true}
allowImagePreview={true}
imageResizeTargetWidth={600}
imageCropAspectRatio={1}
allowFileTypeValidation={true}
maxFileSize={10485760}
fetch={null}
revert={null}
allowFileEncode={true}
imageTransformVariants={{
"thumb_medium_": transforms => {
transforms.resize.size.width = 384;
return transforms;
},
'thumb_small_': transforms => {
transforms.resize.size.width = 128;
return transforms;
}
}
}
fileRenameFunction={ file => new Promise(resolve => {
resolve(window.prompt('Enter new filename', file.name)) })}
acceptedFileTypes="image/jpeg, image/png"
server= {{
url: "/photocontroller",
process: {
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}'
//"X-CSRF-TOKEN": $('input[name="_token"]').val()
}
},
load:'/user/getphoto'
}
}
oninit={() => this.handleInit() }
onupdatefiles={(fileItems) => {
// Set current file objects to this.state
this.setState({
files: fileItems.map(fileItem => fileItem.file)
});
}}>
</FilePond>
</div>
The problem was due to not using/understanding that axios doesn't use the body of the request but uses "data".

BeforeUpload do not trigger upload on promise resolved

Using React, and antd
I have the following code in my component:
<Upload
action={HttpService.getBaseUrl(`post_import_csv`, HttpService.AuditcoreAPIBasePath)}
headers={{"Authorization": `Bearer ${AuthHelper.getAuthKey()}`}}
showUploadList={false}
multiple={false}
beforeUpload={(file: RcFile): PromiseLike<any> => {
this.setCSV(file);
return new Promise((resolve) => {
this.state.requestUpload.pipe(take(1)).subscribe(() => {
resolve(file);
console.log('resolved')
});
})
}}></Upload>
Basically I want my beforeUpload to wait for the user to click on a button before uploading the file. I did so by returning a Promise and waiting for a rxjs Suject that is triggered on button click to resolve the promise. Pretty much following the doc
Here is the button code :
<Button
onClick={(e): void => {
this.state.requestUpload.next(true);
}}
>
Upload
</Button>
It works nice, but the file is never uploaded, I do see my log resolved but there is no trace of network call in my console.
I fixed using this approach which is cleaner :
https://codesandbox.io/s/xvkj90rwkz
Basically, having a custom function that handle upload. It doesn't explain why my tricky solution was not working, but I got it working.
import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Upload, Button, Icon, message } from 'antd';
import reqwest from 'reqwest';
class Demo extends React.Component {
state = {
fileList: [],
uploading: false,
};
handleUpload = () => {
const { fileList } = this.state;
const formData = new FormData();
fileList.forEach(file => {
formData.append('files[]', file);
});
this.setState({
uploading: true,
});
// You can use any AJAX library you like
reqwest({
url: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
method: 'post',
processData: false,
data: formData,
success: () => {
this.setState({
fileList: [],
uploading: false,
});
message.success('upload successfully.');
},
error: () => {
this.setState({
uploading: false,
});
message.error('upload failed.');
},
});
};
render() {
const { uploading, fileList } = this.state;
const props = {
onRemove: file => {
this.setState(state => {
const index = state.fileList.indexOf(file);
const newFileList = state.fileList.slice();
newFileList.splice(index, 1);
return {
fileList: newFileList,
};
});
},
beforeUpload: file => {
this.setState(state => ({
fileList: [...state.fileList, file],
}));
return false;
},
fileList,
};
return (
<div>
<Upload {...props}>
<Button>
<Icon type="upload" /> Select File
</Button>
</Upload>
<Button
type="primary"
onClick={this.handleUpload}
disabled={fileList.length === 0}
loading={uploading}
style={{ marginTop: 16 }}
>
{uploading ? 'Uploading' : 'Start Upload'}
</Button>
</div>
);
}
}
ReactDOM.render(<Demo />, document.getElementById('container'));

Avoid unnecessary http requests on identical images - vuejs

Situation:
In a page, there are several components that receive a list of users. After receiving the list, there's a foreach cycle that calls an aditional component to fetch the user's image. It's possible that the several components may contain the same user, which would mean repeating the exact same http request's to fetch a "repeated image". To avoid these unecessary requests, I set the information of a user has a certain base64 image in the store of vueX, so that I can validate if I already got the image.
Problem: Happens that when the first component makes the request to fetch the image and save it in the store, the remaining components have already been created and as such, the store is still empty and I can't check if I have the image.
Solution: When I create the component, I force the store to exist by using
this.images[this.user.id] = 'reserved';
However, I'm not sure if this is the right approach to this situation.
Suggestions accepted :'D
Code:
parent component
<template>
<div class="info-cards">
<div class="info-users">
<div class="info-label">{{ $t('global.users') }}</div>
<div class="info-images" v-if="users.length > 0">
<base-users-image
v-for="user in users"
:key="user.name"
:user="user"
/>
</div>
<div v-else class="message">{{ $t('global.noUsersRole') }}</div>
</div>
</div>
</template>
<script>
// import components
const baseUsersImage = () => System.import(/* webpackChunkName: 'usersImage' */ './../../users/baseUsersImage');
export default {
props: {
users: Array,
packages: Array
},
components: {
baseUsersImage: baseUsersImage
},
}
</script>
image component
<template>
<router-link to="user" class="anchor-image">
<img v-if="show" :src="image" :alt="user.name" class="image">
<div v-else class="image-default">t</div>
</router-link>
</template>
<script>
// import requests
import requests from './../../../helpers/requests.js';
// import store
import { mapGetters, mapActions } from 'vuex';
export default {
props: {
user: Object
},
data() {
return {
image: '',
show: false
}
},
created() {
if (this.user.avatar) { // check if user has avatar
if ( this.images[this.user.id] == null) { // check if it already exists in the store
this.images[this.user.id] = 'reserved'; // set as reserved in store
requests.get(this.user.avatar, { responseType: 'arraybuffer' }) // faz o pedido a API da image
.then( (response) => {
this.saveImage( { id: this.user.id, url: `data:${response.headers['content-type']};base64,${Buffer.from(response.data, 'binary').toString('base64')}` } );
}, error => {
console.log(error);
});
}
}
},
methods: {
...mapActions({
saveImage: 'saveImage'
})
},
computed: {
...mapGetters({
images: 'images'
})
},
watch: {
images: {
immediate: true,
deep: true, // so it detects changes to properties only
handler(newVal, oldVal) {
if ( newVal[this.user.id] !=='reserved'
&& this.user.avatar
&& newVal[this.user.id] !== undefined
) {
this.image = newVal[this.user.id];
this.show = true;
}
}
}
}
}
</script>
store
const state = {
images: {}
}
const SAVE_IMAGE = (state, payload) => {
state.images = {
...state.images,
[payload.id] : payload.url
}
}
const saveImage = ({commit}, payload) => {
commit('SAVE_IMAGE', payload);
}
Here is what I would do:
First, I would move all the request logic to VueX and keep my component as simple as possible. It should be achievable by this piece of code:
export default {
props: {
user: Object
},
created () {
if (this.user.avatar) {
this.$store.dispatch('fetchImage', this.user.avatar)
}
}
}
Then, I would use this simple pattern to organize my store. First, let's take a look at how the state should look:
{
images: {
'/users/1/avatar': 'data:png:base64,....', // An image that have been loaded
'/users/2/avatar': null // An image that is supposed to be loading
}
}
As you can see, the images object uses images urls as keys and base64 data as value. If the value of the data is null, it means that the image is already loading.
Let's now see how do we write the action to handle that:
const actions = {
fetchImage ({state, commit}, url) {
if (typeof state.images[url] !== 'undefined') {
return null
}
commit('setImage', {
url,
payload: null
})
return requests.get(url, { responseType: 'arraybuffer'}).then(response => {
commit('setImage', {
url,
payload: `data:${response.headers['content-type']};base64,${Buffer.from(response.data, 'binary').toString('base64')}`
})
})
}
}
Look at the first condition. If the image is not undefined in the store, we just don't do anything. Because if the image is not undefined, it means that it is either null (loading) or has a value and is loaded.
Just after this condition, we set the image to null to prevent other components to load the image.
And at the end we load the content of the image, and commit it to the state.
Let's take a look to the template now:
<template>
<router-link to="user" class="anchor-image">
<img v-if="$store.state.images[user.avatar]" :src="$store.state.images[user.avatar]" :alt="user.name" class="image">
<div v-else class="image-default">t</div>
</router-link>
</template>
In order to check if you should display the image, you just have to use v-if="$store.state.images[user.avatar]". The image will show up as soon as it is loaded.
$store.state.images[user.avatar] will be falsy even if the image is loading (it has the null value.
I hope this can help!
(Here is the complete store:)
const store = {
state: {
images: {}
},
mutations: {
setImage (state, image) {
Vue.set(state.images, image.url, image.payload)
}
},
actions: {
fetchImage ({state, commit}, url) {
if (state.images[url] !== undefined) {
return null
}
commit('setImage', {
url,
payload: null
})
return requests.get(url, { responseType: 'arraybuffer'}).then(response => {
commit('setImage', {
url,
payload: `data:${response.headers['content-type']};base64,${Buffer.from(response.data, 'binary').toString('base64')}`
})
})
}
}
}

laravel vue getting info by hidden field

I need to pass logged user id to back-end and I have vuex store so I can get my user info like {{currentUser.id}} the problem is i cannot pass it to back-end it gives me validation error that user_id is required while i have this hidden input in my form
<input type="hidden" name="user_id" :value="currentUser.id">
for normal inputs i have v-model like v-model="project.title" which is not possible to use on hidden fields.
The question here is how can I pass my user_id to back-end?
Code
<script>
import validate from 'validate.js';
export default {
data: function () {
return {
project: {
title: '',
body: '',
attachment: '',
projectclass: '',
deadline: '',
user_id: '',
csrf: document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
},
errors: null
}
},
computed: {
currentUser() {
return this.$store.getters.currentUser;
}
},
methods: {
add() {
this.errors = null;
const errors = validate(this.$data.project);
if(errors) {
this.errors = errors;
return;
}
axios.post('/api/projects/new', this.$data.project)
.then((response) => {
this.$router.push('/projects');
});
}
}
}
</script>
This happens because user_id in this.$data.project dosn't get updated.
Instead of having hidden input you can just do
add() {
this.errors = null;
const errors = validate(Object.assign(this.$data.project, {user_id: this.currentUser.id}));
if(errors) {
this.errors = errors;
return;
}
axios.post('/api/projects/new', Object.assign(this.$data.project, {user_id: this.currentUser.id}))
.then((response) => {
this.$router.push('/projects');
});
}

Resources