Refresh data table event after file upload - javascript

I am beginner web developer. I make my first project in Vue.
I make form with files upload in vue 2 and laravel.
My full code:
View: https://pastebin.com/QFrBfF74
Data table user file: https://pastebin.com/sGQH71XZ
This code work fine, nut I have small problem with reload ata-table-user-files after files upload.
Modal where I have file uploader:
<div>
<CModal
title="Dodaj plik"
color="info"
:show.sync="filesModal"
size="xl"
:closeOnBackdrop=true
:centered="true"
>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Dodaj plik w formacie: jpg, jpeg, png, bmp, pdf, xml, csv, doc, docx,
txt, rtf
</div>
<div class="card-body">
<CRow>
<CCol md="12">
<CSelect id="dispatcher_id"
label="Wybierz kategorię pliku"
v-model.trim="$v.form.file_category.$model"
:options="filesCategory"
>
</CSelect>
</CCol>
<CCol md="12">
<CTextarea
label="Opis pliku"
placeholder="Wpisz opis dodawanego pliku"
rows="9"
v-model.trim="$v.form.file_content.$model"
/>
</CCol>
</CRow>
<form enctype="multipart/form-data" #submit="formFileSubmit">
<input type="file" class="form-control" v-on:change="onFileChange" name="file_name" ref="inputFile">
<button class="btn btn-primary btn-block">Dodaj plik</button>
</form>
</div>
</div>
</div>
</div>
</div>
</CModal>
</div>
Refresh action I need after "Sukces Plik dodany poprawnie!" I need reload files list:
<data-table-user-files
:fetch-url="datatTableUrl5"
:columns="['id', 'description', 'file_category','user_id' ]"
:headers="{'id': 'ID','description': 'Opis','file_category': 'Kategoria','user_id': 'Twórca'}"
:routeName="routeAddName5"
></data-table-user-files>
I try do this in function:
getFilesList()
{
let self = this;
axios.get(this.$apiAdress + '/api/tasks-file-list?token=' + localStorage.getItem("api_token") + '&taskId=' + self.form.taskId)
.then(function (response) {
self.addedFiles = [];
self.addedFiles = response.data
}).catch(function (error) {
console.log(error);
self.$router.push({path: '/login'});
});
},
But it's not working :(
How can I repair it?
Please help me.

Changing the URL in order to trigger re-fetching of the data:
<template>
<data-table-user-files
:fetch-url="datatTableUrl5"
:columns="['id', 'description', 'file_category','user_id' ]"
:headers="{'id': 'ID','description': 'Opis','file_category': 'Kategoria','user_id': 'Twórca'}"
:routeName="routeAddName5"
/>
<script>
export default
{
data()
{
return {
triggerRefetch: 0,
};
},
computed:
{
datatTableUrl5()
{
return `https://my.api.example.com/api/endpoint?t=${triggerRefetch}`;
},
},
methods:
{
filesSuccessfullyUploaded()
{
this.triggerRefetch++;
},
}
}
</script>
import Axios from 'axios';
export default
{
props:
{
fetchUrl:
{
type: String,
required: true
},
},
data()
{
files: [],
},
watch:
{
fetchUrl:
{
immediate: true,
handler()
{
this.getFilesList();
}
}
},
methods:
{
getFilesList()
{
Axios.get(this.fetchUrl).then(response => this.files = response.data || []);
}
}
}

Related

How can I use vue validation with Jest?

I'm writing a unit test with vue 3 using vee-validate 4 and Jest. But I'm new to this and I'm stuck at one place.
I have a TextInput component that I use validations, and when I call this component, I do validation when submit is done.
There is a separate component where I write the form where I use these textInputs.
First let me show you the code in my TkTextInput component.
<template>
<div
class="tk-Input"
:class="{ 'has-error': !!errorMessage, success: meta.valid }"
>
<label class="tk-label" :for="name">{{ label }}</label>
<input
id="demos"
class="col-12"
v-model="inputValue"
:name="name"
:type="type"
:value="inputValue"
:placeholder="placeholder"
#input="handleChange"
#blur="handleBlur"
v-bind="$attrs"
/>
<p class="help-message" v-show="errorMessage || meta.valid">
{{ errorMessage }}
</p>
</div>
</template>
<script>
import {useField} from "vee-validate";
import {watch} from "vue";
export default {
props: {
type: {
type: String,
default: "text",
},
modelValue: String,
value: {
type: String,
default: "",
},
name: {
type: String,
required: true,
},
label: {
type: String,
},
placeholder: {
type: String,
default: "",
},
},
emits: ['update:modelValue'],
setup(props, {emit}) {
const {
value: inputValue,
errorMessage,
handleBlur,
handleChange,
meta,
} = useField(props.name, undefined, {
initialValue: props.value,
});
watch(inputValue, (val) => {
emit('update:modelValue', val);
});
watch(() => props.modelValue, (val) => {
if (val !== inputValue.value) {
inputValue.value = val;
}
})
return {
handleChange,
handleBlur,
errorMessage,
meta,
inputValue,
};
},
};
</script>
Then in my form component where I call these textInputs is as follows.
<Form
#submit="onSubmit"
:validation-schema="schema">
<div class="grid ">
<div class="col-12 lg:col-6 lg:mb-0">
<tk-text-input v-model.trim="vehicleInfo.Plate" label="Plaka*" name="Plate" type="text"/>
</div>
<div class="grid ">
<div class="col-12 lg:col-6 lg:mb-0">
<tk-text-input v-model.trim="vehicleInfo.PhoneNumber" label="Cep Telefonu*" name="PhoneNumber"/>
</div>
<div class="col-12 lg:col-6 lg:mb-0">
</div>
</div>
<Button #click="clicked" class=" p-button-success" type="submit">{{buttonLabel}}</Button>
</Form>
Now I want to test the validation process in the TkTextInput component when the button clicks when I click the button in the component where I wrote the form with gesture. But I couldn't do it.
The test I wrote in the .spec file is as follows.
describe('TkTextInput.vue', () => {
it('when validation is done', async() => {
const wrapperVehicle = mount(VehicleInfoDialog, {
global:{
plugins: [PrimeVue]
},
})
const button = wrapperVehicle.find("button")
button.trigger('submit')
await button.trigger("click")
expect(wrapperVehicle.html()).toContain("Boş Geçilemez.")
});
})

Vue.js iterating over image src

Trying to iterate over a list of (imagePath, href) & display them with vue.js.
The href value of the images work, the link opens on click. Though the images aren't showing.
My code is as follows:
<div
v-for="(image,index) in socials"
:key="index"
>
<a :href="image.href">
<v-avatar
size="48"
tile
>
<img
:src="image.src"
/>
</v-avatar>
</a>
</div>
export default {
name: 'App',
data: () => ({
socials: [
{
id:"1",
src:"../assets/socials/discord.png",
href:"https://discord.gg/link_to_discord"
},
{
id:"2",
src:"../assets/socials/telegram.png",
href:"https://t.me//link_to_telegram"
},
{
id:"3",
src:"../assets/socials/medium.png",
href:"https://medium.com/#link_to_medium"
}
],
})
};
The images are named correctly and are in the correct dir. How can I change my code so that the images are shown properly ?
This code belongs to a footer, so the template & js is in App.vue
SOLUTION
Thanks to #Nikola and this question, I was able to solve it via getImgUrl method. Here's the updated code:
template
<div
v-for="image in socials"
:key="image.id"
>
<a :href="image.href">
<v-avatar
size="48"
tile
>
<img
:src="getImgUrl(image.src)"
/>
</v-avatar>
</a>
</div>
script
<script>
export default {
name: 'App',
data: function() {
return {
socials: [
{
id:"1",
src:"socials/discord.png",
href:"https://discord.gg/link_to_discord"
},
{
id:"2",
src:"socials/telegram.png",
href:"https://t.me//link_to_telegram"
},
{
id:"3",
src:"socials/medium.png",
href:"https://medium.com/#link_to_medium"
}
],
};
},
methods:{
getImgUrl: function (path) {
return require('#/assets/' + path);
}
}
};
</script>
You can make method:
export default {
name: 'App',
data: () => ({
socials: [
{
id:"1",
src:"socials/discord.png",
href:"https://discord.gg/link_to_discord"
},
{
id:"2",
src:"socials/telegram.png",
href:"https://t.me//link_to_telegram"
},
{
id:"3",
src:"socials/medium.png",
href:"https://medium.com/#link_to_medium"
}
],
}),
methods: {
getImgUrl: function (path) {
return require('#/assets/' + path);
}
}
};
Then in template call that method:
<div
v-for="(image,index) in socials"
:key="index"
>
<a :href="image.href">
<v-avatar
size="48"
tile
>
<img
:src="getImgUrl(image.src)"
/>
</v-avatar>
</a>
</div>

Populate input fields with data from vuex state in vue

I have the following code snippet from my app component:
<template>
<div>
<h3>Basic</h3>
<div v-for="(field, index) in basics" :key="index">
<input v-model="basics.name" placeholder="Name" type="text">
<br>
<br>
<input v-model="basics.email" placeholder="Email" type="email">
<br>
<hr/>
<button #click.prevent="addField">Add</button>
<button #click.prevent="removeField(index)">Remove</button>
<br>
<button #click.prevent="back">Back</button>
<button #click.prevent="toNext">Next</button>
</div>
</div>
</template>
<script>
import { mapActions } from "vuex";
export default {
name: "Basics",
data() {
return {
basics: [{
name: "",
email: ""
}]
};
},
methods: {
...mapActions(["addBasicData"]),
addFied(){
this.basics.push({
name: "",
email: ""
});
},
removeField(index){
this.basics.splice(index, 1);
},
toNext() {
this.addBasicData(this.basics);
this.$router.push({ name: "Location" });
},
back() {
this.$router.back();
}
}
};
</script>
In the code above when I finish filling up the form and click next button the data is sent to the state and we are guided to another route named "Location".
When I click back button in the "Location" route I'm back to route named "Basic".
The issue here is when I'm brought back to the route named "Basic" the form fields are empty although they are binded with the data object.
How do I populate these input fields when I return back to same route ?
Here is the working replica of the app: codesandbox
<div v-for="(field, index) in basics" :key="index">
<input v-model="basic.name" placeholder="Name" type="text">
<input v-model="basic.email" placeholder="Email" type="email">
<button #click.prevent="removeField(index)">Remove</button>
</div>
<hr/>
<button #click.prevent="addField">Add</button>
<br>
<button #click.prevent="back">Back</button>
<button #click.prevent="toNext">Next</button>
methods: {
addField() {
this.$store.commit('addBasic',{name:"",email:""} )
},
removeField(index) {
this.$store.commit('removeField',index )
},
toNext() {
this.$router.push({ name: "Location" });
}
},
computed: {
basic:{
get() {
return this.$store.getters.getBasic;
}
}
}
store.js
// ...
state: {
basic:[{name:"Jonny",email:"jonny#mail.com"},
{name:"Bonny",email:"Bonny#mail.com"}]
}
mutations: {
addBasic(state,value) {
state.basic.push(value)
},
removeField(state,index ){
state.basic.splice(index,1);
}
}
Thats just one of two versions how you can do it.
Or you can map the mutatations and call them directly in the click event.
https://vuex.vuejs.org/guide/mutations.html
https://vuex.vuejs.org/guide/forms.html
The add field button makes only sense outside of the loop.
addBasicData you dont need it
This method somehow works:
mounted() {
// eslint-disable-next-line no-unused-vars
let fromState = this.$store.state.Basics.basics;
if (fromState) {
this.basics.name = fromState.name;
this.basics.email = fromState.email;
}
}
I will really appreciate if there are any other convenient method to achieve this.
Tried mapState but didn't work

The problem of data transfer between two child components

Good afternoon, I have two child components Header and Pagination. In Header, I have an input search engine and two inputs (title and body) in order to be able to add a post to Pagination. I managed to transfer the search value to the Pagination component, but I don’t know how to transfer the value from two inputs (title, body). I use to transfer the event bus. Help me please pass the value of the two inputs (title, body) into the Pagination component when you click the AddPost button.
My code on GitHub
Screenshot of app
My code of component Header:
<template>
<div class="header">
<input type="text" v-model="search" class="header_input_search" placeholder="Search" #input="saveMessage" />
<img src="src/assets/milk.png">
<div class="header_div_inputs">
<input type="text" v-model="createTitle" class="created"/>
<p><input type="text" v-model="createBody" class="createBody"/></p>
</div>
<button #click="addPost()" class="addPost">AddPost</button>
</div>
</template>
<script>
import axios from 'axios';
import {eventEmitter} from './main'
export default {
name: 'Header',
data () {
return {
search: '',
createTitle: '',
createBody: '',
}
},
methods:{
saveMessage(){
eventEmitter.$emit('messageSave', this.search)
},
}
}
</script>
My code of component Pagination:
<template>
<div class = "app">
<ul>
<li v-for="(post, index) in paginatedData" class="post" :key="index">
<router-link :to="{ name: 'detail', params: {id: post.id, title: post.title, body: post.body} }">
<img src="src/assets/nature.jpg">
<p class="boldText"> {{ post.title }}</p>
</router-link>
<p> {{ post.body }}</p>
</li>
</ul>
<div class="allpagination">
<button type="button" #click="page -=1" v-if="page > 0" class="prev"><<</button>
<div class="pagin">
<button class="item"
v-for="n in evenPosts"
:key="n.id"
v-bind:class="{'selected': current === n.id}"
#click="page=n-1">{{ n }} </button>
</div>
<button type="button" #click="page +=1" class="next" v-if="page < evenPosts-1">>></button>
</div>
</div>
</template>
<script>
import {mapState} from 'vuex'
import {eventEmitter} from './main'
export default {
name: 'app',
data () {
return {
current: null,
page: 0,
visiblePostID: '',
pSearch: ''
}
},
mounted(){
this.$store.dispatch('loadPosts')
},
computed: {
...mapState([
'posts'
]),
evenPosts: function(posts){
return Math.ceil(this.posts.length/6);
},
paginatedData() {
const start = this.page * 6;
const end = start + 6;
return this.filteredPosts.slice(start, end);
},
filteredPosts() {
return this.posts.filter((post) => {
return post.title.match(this.pSearch);
});
},
},
created(){
eventEmitter.$on('messageSave', (string) => {
this.pSearch = string
})
}
}
</script>
You can wrap title and body in an object
addPost() {
const post = {
title: this.createTitle,
body: this.createBody
}
eventEmitter.$emit('postAdd', post)
}
and then listen as normal
created(){
eventEmitter.$on('postAdd', (post) => {
console.log(post)
// do whatever you want
})
}
I have not worked on vue js but agreed with #ittus answer. You can make an object consisting of your required data which you want to share across the component and pass it as an event data.

Trying to pass File object as a property to a component and getting an empty object. I'm also wondering if this is safe?

I have a generic avatar preview modal:
<avatar-update :img-file="avatarFile" :show="avatarModalShow" :img-url="url" #close="avatarModalShow = !avatarModalShow" :change-avatar="updateCrop" #destroyUrl="imgUrl = null"> </avatar-update>
When an avatar is submitted I use my root to send a bunch of properties to the AvatarUpdate component.
HTML
<div>
<label for="avatar" class="cursor-pointer thumbnail-link bg-white p-1 rounded-lg" href="">
<img class="thumbnail" src="{{asset('images/avatars/avatar-1.png')}}">
</label>
<input id="avatar" class="hidden" type="file" #change="onFileChange"/>
</div>
Root
onFileChange: function(e) {
const file = e.target.files[0];
this.url = URL.createObjectURL(file);
this.updateCrop = !this.updateCrop;
this.avatarModalShow = !this.avatarModalShow;
this.avatarFile = file;
},
When I console.log the file const in the onFileChange function I get the file object. However, when I try and output the {{imgFile}} property in the AvatarUpdate component I get an empty object.
I'm wondering if this is safe and if the file data can be manipulated between the root and AvatarUpdate component? Also is there something preventing me from being able to send and output the file object as a property? Why is it giving me an empty object on the AvatarUpdate component?
I'm sorry for so many questions, but my reasoning for including them into one post is that I think there could be some security functionality preventing me from sending the file object through a component.
Edit
Here is my AvatarUpload component:
<modal v-show="show" heading="Avatar Preview" #close="close">
<div class="flex flex-col">
<h4 class="text-blue-light mb-5">The avatar will be automatically cropped from the center.</h4>
<div class="flex flex-col items-center">
<img class="w-2/3" :src="imgUrl">
</div>
<p>{{imgFile}}</p>
<button class="mt-4 h-10 self-end text-center bg-third-color hover:bg-secondary-color text-white font-bold py-2 px-4 rounded" v-on:click="submitAvatar()">Submit</button>
</div>
<script>
export default {
props: ['show','imgUrl','changeAvatar','imgFile'],
data() {
return {
image: null,
message: null
}
},
methods: {
close: function(){
this.$emit('close');
},
submitAvatar: function(){
console.log(file);
axios({
method: 'POST',
url: '/profile/avatar',
data: {},
}).then(function (response) {
this.message = "Your avatar has been submitted";
}.bind(this))
.catch(function (error) {
console.log(error);
});
}
}
}
</script>
I already am able to obtain the blob from this.url = URL.createObjectURL(file); in the onFileChange function on the root instance. What I am trying to do is send the whole file object to the AvatarUpdate component using the :img-file="avatarFile" prop.
This way I can send the data in a way that can be accessed on the request in a Laravel controller:
submitAvatar: function(){
//Change here!
var data = new FormData()
var file = this.imgFile;
data.append('avatar', file);
axios({
method: 'POST',
url: '/profile/avatar',
data: data,
}).then(function (response) {
this.message = "Your avatar has been submitted";
}.bind(this))
.catch(function (error) {
console.log(error);
});
}
Laravel UserController
UserController
public function avatar(Request $request)
{
return $request->hasFile('avatar');
}
In your codes, this.avatarFile = file is one File(inherit from Blob) object, it can't be used in image src directly (if open the browser inspector, the value of img:src is [object File], Obviously the value is not what you expected).
You can use Javascript MDN: FileReader.readAsDataURL to reach the goal.
Javascript MDN: URL.createObjectURL() is another solution, but you have to handle the memory management carefully. Check Javascript MDN: URL.createObjectURL() Usage notes
PS: I recommend to convert the File object to (data-url or object-url) first then pass (data-url or object-url) to the child component. directly pass File object may meet reactivity issue.
One simple demo which uses FileReader:
Vue.config.productionTip = false
Vue.component('img-preview', {
template: `<div>
<img :src="imageBlob" alt="test"/>
</div>`,
props: ['imageBlob']
})
new Vue({
el: '#app',
data() {
return {
imageObj: null
}
},
methods:{
onFileChange: function(ev) {
const selectFile = ev.target.files[0]
let reader = new FileReader()
reader.readAsDataURL(selectFile)
reader.addEventListener('load', () => {
this.imageObj = reader.result
//console.log('select image', reader.result)
}, false)
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div>
<label for="avatar">
<img-preview :image-blob="imageObj"></img-preview>
</label>
<input id="avatar" class="hidden" type="file" #change="onFileChange($event)"/>
</div>
</div>
One simple demo which uses createObjectURL:
Vue.config.productionTip = false
Vue.component('img-preview', {
template: `<div>
<img :src="imageBlob" alt="test"/>
</div>`,
props: ['imageBlob']
})
new Vue({
el: '#app',
data() {
return {
imageObj: null
}
},
methods:{
onFileChange: function(ev) {
const selectFile = ev.target.files[0]
this.imageObj = URL.createObjectURL(selectFile)
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div>
<label for="avatar">
<img-preview :image-blob="imageObj"></img-preview>
</label>
<input id="avatar" class="hidden" type="file" #change="onFileChange($event)"/>
</div>
</div>
Below is one demo (directly pass File Object to child component):
Vue.config.productionTip = false
Vue.component('img-preview', {
template: `<div>{{imageBlob}}
<img :src="internalImageObj" alt="test"/>
</div>`,
props: ['imageBlob'],
data() {
return {
internalImageObj: ''
}
},
watch: {
imageBlob: function (newVal) {
let reader = new FileReader()
reader.readAsDataURL(newVal)
reader.addEventListener('load', () => {
this.internalImageObj = reader.result
}, false)
}
}
})
new Vue({
el: '#app',
data() {
return {
selectedFile: null
}
},
methods:{
onFileChange: function(ev) {
this.selectedFile = ev.target.files[0]
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div>
<label for="avatar">
<img-preview :image-blob="selectedFile"></img-preview>
</label>
<input id="avatar" class="hidden" type="file" #change="onFileChange($event)"/>
</div>
</div>

Categories