I have a table that should be changing its data constantly. When a button is clicked in the parent component, a call is made to the server and json file is then loaded into a child component(The table) through a prop.
Whenever a different button is clicked and the table needs to reload the data, it doesnt. I have tried doing:
this.$refs.dmTable.refreshTable();
and
this.$forceUpdate()
Rough structure of my code
Parent.vue
<template>
<Button getData("this")>Get This Data</Button>
<Button getData("that")>Get ThatData</Button>
<MyTable v-if="showTable" :data="data" />
<template>
<script>
export default {
data(){
return{
showTable:false,
data: null
}
},
methods:{
getData(dataType){
getDataFromServer(dataType).then(data =>{
this.data = data.body
this.showTable = true
})
}
}
}
</script>
MyTable.vue
<template>
<b-table :items="data"><b-table/>
</template>
<script>
export default{
props: ["data"]
}
</script>
If you click the first button, the data loads fine into the table. But if you then click the second button and try to load new data into the table nothing happens. I tried creating a method called updateTable() within the child component that contains this.$refs.myTable.update() but it does nothing.
Edit: One thing to note about this, the data that I am loading onto this table is quite large, 5mb json file.
The actual function that gets called:
showTableView(model, type) {
request
.get(
`/table_files/${this.folder_name}/${model}.json`
)
.then(response => {
this.type = type;
this.current_model = model;
if (type === "joins") {
this.tlorderData = response.body.fields.joins;
this.show_joins_table = true;
this.showTable = false;
this.refreshTable()
return false; // MAYBE RETURNING FALSE BREAKS THE RERENDERING?
}
else if (type === "dimension_groups") {
this.show_joins_table = false;
this.showTable = true;
this.tlorderData = response.body.fields.dimension_groups;
this.refreshTable()
return false;
}
})
.catch(err => {
this.response_error = err;
});
},
I don't see where you are defining data and showTable in your main app component.setting this.data to a value is not reactive (it is creating a non-reactive property on the app component).
Try this:
<template>
<Button #click="getData('this')">Get This Data</Button>
<Button #click="getData('that')">Get ThatData</Button>
<MyTable v-if="showTable" :data="data" />
<template>
<script>
export default {
data() {
return {
data: [],
showTable: false
}
},
methods:{
getData(dataType){
getDataFromServer(dataType).then(data =>{
this.data = data.body
this.showTable = true
})
}
}
}
</script>
The data() section will define data and showTable as reactive properties on your app/component instance.
The problem was in something that I did not mention in my code.
The way I was loading in data into the table was like this:
<template>
<b-table :items="reData"><b-table/>
</template>
<script>
export default{
props: ["data"],
data(){
return{
reData: this.data
}
}
}
</script>
This prevented my table from updating whenever data gets changed in the prop, note that I am passing in my prop to data() then passing THAT into my table. So the prop would change but the actual data that the table was showing would not.
Proper Way to pass in from props to table to prevent this from happening:
<template>
<b-table :items="data"><b-table/>
</template>
<script>
export default{
props: ["data"]
}
</script>
Related
I'm trying to create a custom button component with a spinner, it works fine - disables the button and shows a spinner when clicked but I'm having trouble resetting the button's state when whatever is "loading" is finished.
My current code is as follows:
Parent.vue
<template>
<custom-button #button-click="requestAPI" :disabled="canRequestAPI">
Request API
</custom-button>
</template>
<script>
methods: {
async requestAPI(){
// Request API here
}
</script>
CustomButton.vue
<template>
<button :disabled="disabled || loading" #click="processClick" :loading="loading">
<img src="/loader.svg" />
<span class="buttonContent">
<slot />
</span>
</button>
</template>
<script>
props: ["disabled"],
data() {
return: {
loading: false,
}
},
methods: {
processClick(){
this.$emit('button-click');
this.loading = true;
}
}
</script>
The spinner shows and the API is requested but I can't work out how to stop the spinner in a good way. I could create a prop to watch and set loading to false but I'd like to be able to use this custom button several without having to add lots of different variables.
Ideally i'd be able to do something like:
Parent.vue
<script>
methods: {
async requestAPI(e){
// Request API here
e.stopLoading();
}
</script>
CustomButton.vue
<script>
methods: {
stopLoading(){
this.loading = false;
}
}
</script>
Worked it out, Just emitted the stopLoading function to the parent and called that.
Parent.vue
<script>
methods: {
async requestAPI(stopLoading){
// Request API here
stopLoading();
}
}
</script>
Child.vue
methods: {
processClick(){
this.$emit('button-click', () => this.stopLoading());
this.loading = true;
},
stopLoading(){
this.loading = false;
}
}
//Parent component
<template>
<childComp #onchangeData='changeData' />
</template>
<script>
setup() {
const state = reactive({
data: 'anything
});
function changeData(v){
state.data = v
}
return { changeData}
},
</script>
//Child
<template>
<button #click='change('hello')' />
</template>
<script>
setup() {
function change(v){
this.$emit('onchangeData', v)
}
return{change}
},
</script>
I am struggling to change the parents' reactive state from the child's button click. It's saying this.$emit is not a function. I tried many ways like using #onchangeData='changeData()' instead of #onchangeData='changeData', using arrow functions etc. But nothing works. Here, I wrote an example and minimal code to keep it simple. But I hope my problem is clear.
Look at following snippet, this is not the same in composition as in options API, so you need to use emit passed to setup function:
const { reactive } = Vue
const app = Vue.createApp({
setup() {
const state = reactive({
data: 'anything'
});
function changeData(v){
state.data = v
}
return { changeData, state }
},
})
app.component("ChildComp", {
template: `
<div>
<button #click="change('hello')">click</button>
</div>
`,
setup(props, {emit}) {
function change(v){
emit('onchangeData', v)
}
return { change }
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<child-comp #onchange-data='changeData'></child-comp>
<p>{{ state.data }}</p>
</div>
I have the following code
<body>
<div class="content" id="app">
<file-management></file-management>
<attachment-list></attachment-list>
</div>
<script src="{{ asset('js/app.js') }}"></script>
</body>
FileManagement component code:
<template>
<div>
<button type="button" #click="storeList()">
Save
</button>
</div>
</template>
<script>
export default {
methods: {
storeList: function () {
axios.post('/list', this.data, config)
.then(() => {
// on save I want to be able to load the table again that is found in AttachmentList component
});
},
}
}
</script>
AttachmentList component code:
<template>
<div>
<tr v-for="attachment in attachments" :key="attachment.id">
<td>{{ attachment.name }}</td>
</tr>
</div>
</template>
<script>
export default {
data() {
return {
attachments: []
}
},
methods: {
getList() {
axios.get(`/list`)
.then((data) => {
this.attachments = data;
});
}
}
}
</script>
What I want to do is that I want to be able to load the table of the list when I click save in the other component (after the post request has completed). How will I be able to achieve this?
Easiest way would be to have your FileManagement component emit an event which the parent can listen to, then trigger the AttachmentList#getList() method.
For example
// in AttachmentList
methods: {
async storeList () {
await axios.post('/list', this.data, config)
this.$emit('list-updated')
}
}
and in the parent template
<file-management #list-updated="$refs.list.getList()"></file-management>
<attachment-list ref="list"></attachment-list>
This is how I would proceed.
create a parent component for the siblings.
add a boolean data member (flag) to it with the status of the clicked button.
emit a signal from FileManagement when the button is clicked.
catch this signal in the parent component to set the flag.
pass this flag to the AttachmentList component as a prop.
use this flag inside a v-if to show / hide the the table.
I have two components, the first one is for uploading a file and the second one to Show a file. Inside my Upload Component I would like to call the Preview Component and add a Parameter so that a method inside the Preview Component uses a value which is created inside the Upload Component.
So far I have done this:
UploadComponent.vue
<template>
…
<button #click="upload"></button>
<preview-component :url="this.location"></preview-component>
</template >
<script>
import PreviewComponent from '#/js/components/PreviewComponent';
export default {
components: {
'preview-component': PreviewComponent
},
props: ['url'],
data () {
return {
// ...
location: ''
}
},
methods: {
upload() {
// ... upload stuff then update the global var location
this.location = response.data.location;
},
}
}
</script>
This is my Preview Component:
<template>
<div id="body">
///...
</div>
</template>
<script>
export default {
props: ['url'],
methods: {
loadPdf (url) {
//
},
}
}
</script>
So far I am getting the error that url is not defined, so it actually does not sent the url from the UploadCOmponent to the PreviewComponent, how do I manage to sent it?
You got a ninja this in your UploadComponent's template.
It should be <preview-component :url="location"></preview-component>
I'm building a small vue application where among other things it is possible to delete an entry of a music collection. So at this point I have a list of music albums and next to the entry I have a "delete" button. When I do the following:
<li v-for="cd in cds">
<span>{{cd.artist}} - {{cd.album}}</span> <button v-on:click="deleteAlbum(cd.ID)">Delete</button>
</li>
and then in my methods do:
deleteAlbum(id){
this.$http.delete('/api/cds/delete/'+id)
.then(function(response){
this.fetchAll()
// });
},
this works fine so far, but to make it more nice, I want the delete functionality to appear in a modal/popup, so I made the following changes:
<li v-for="cd in cds">
<div class="cd-wrap">
<span>{{cd.artist}} - {{cd.album}}</span>
<button #click="showDeleteModal({id: cd.ID, artist: cd.artist, album: cd.album})" class="btn">Delete</button>
</div>
<delete-modal v-if="showDelete" #close="showDelete = false" #showDeleteModal="cd.ID = $event"></delete-modal>
</li>
so, as seen above I created a <delete-modal>-component. When I click on the delete button I want to pass the data from the entry to <delete-modal> component with the help of an eventbus. For that, inside my methods I did this:
showDeleteModal(item) {
this.showDelete = true
eventBus.$emit('showDeleteModal', {item: item})
}
Then, in the <delete-modal>, inside the created()-lifecycle I did this:
created(){
eventBus.$on('showDeleteModal', (item) => {
console.log('bus data: ', item)
})
}
this gives me plenty of empty opened popups/modals!!??
Can someone tell me what I'm doing wrong here?
** EDIT **
After a good suggestion I dumped the eventBus method and pass the data as props to the <delete-modal> so now it looks like this:
<delete-modal :id="cd.ID" :artist="cd.artist" :album="cd.album"></delete-modal>
and the delete-modal component:
export default {
props: ['id', 'artist', 'album'],
data() {
return {
isOpen: false
}
},
created(){
this.isOpen = true
}
}
Only issue I have now, is that it tries to open a modal for each entry, how can I detect the correct ID/entry?
I am going to show you how to do it with props since it is a parent-child relation.I will show you a simple way of doing it.You need to modify or add some code of course in order to work in your app.
Parent component
<template>
<div>
<li v-for="cd in cds" :key="cd.ID">
<div class="cd-wrap">
<span>{{cd.artist}} - {{cd.album}}</span>
<button
#click="showDeleteModal({id: cd.ID, artist: cd.artist, album: cd.album})"
class="btn"
>
Delete
</button>
</div>
<delete-modal v-if="showDelete" :modal.sync="showDelte" :passedObject="objectToPass"></delete-modal>
</li>
</div>
</template>
<script>
import Child from 'Child'
export default {
components: {
'delete-modal': Child
},
data() {
return {
showDelete: false,
objectToPass: null,
//here put your other properties
}
},
methods: {
showDeleteModal(item) {
this.showDelete = true
this.objectToPass = item
}
}
}
</script>
Child Component
<template>
/* Here put your logic component */
</template>
<script>
export default {
props: {
modal:{
default:false
},
passedObject: {
type: Object
}
},
methods: {
closeModal() { //the method to close the modal
this.$emit('update:modal')
}
}
//here put your other vue.js code
}
</script>
When you use the .sync modifier to pass a prop in child component then,there (in child cmp) you have to emit an event like:
this.$emit('update:modal')
And with that the modal will close and open.Also using props we have passed to child component the object that contains the id and other stuff.
If you want to learn more about props, click here