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;
}
}
Related
What I want to come true
I want to display an alert message considering the result of the data sent to the server.
However, since alert messages are managed by another component, it is necessary to call the component asynchronously.
The official Vue.js documentation used Vue.component, but what's the right way to do it with Nuxt.js?
Code
I want to use search.vue in success.vue
search.vue
<template>
<v-app>
<div
class="teal lighten-1 background pa-10"
>
<!-- <div
v-if="responseBook === 200"
> -->
<alert-success />
<v-sheet
width="1100px"
class="mx-auto pa-5 rounded-xl"
color="grey lighten-5"
min-height="500px"
>
<!-- 書籍検索、表示 -->
<BookPostDialog />
<!-- 選択されたデータの表示 -->
<BookPostSelected />
</v-sheet>
</div>
</v-app>
</template>
<script>
export default {
computed: {
responseBook () {
return this.$store.state.book.responseBook.status
}
}
}
</script>
<style lang="scss" scoped>
.background {
background-image: url('~/assets/images/tree.png');
background-repeat: space repeat;
}
</style>
Alert/success.vue
<template>
<v-alert type="success">
Succeeded
</v-alert>
</template>
If you want to use that kind of feature, you'll be better suited looking for something like this component: https://buefy.org/documentation/toast
Or anything like this in the jungle of CSS frameworks, pretty sure each of them have one.
Or implement it yourself, for this, you need to rely on portals.
For Vue2, this is how to do achieve it: https://portal-vue.linusb.org/guide/getting-started.html#enabling-disabling-the-portal
<portal to="destination" :disabled="true">
<p>
Your content
</p>
</portal>
If you want to show success.vue component after the connection to server (getting or posting data), you can use v-if as follows:
search.vue
<template>
<div>
<p>search compo</p>
<div v-if="this.$store.state.book.responseBook == 'ok'">
data was received.
<success />
</div>
</div>
</template>
<script>
export default {
mounted() {
this.$store.dispatch('getData')
}
}
</script>
success.vue
<template>
<div>
succeess compo
</div>
</template>
And then in your store/index.js file:
import Vuex from "vuex";
const createStore = () => {
return new Vuex.Store({
state: {
book: {
responseBook: ""
}
},
mutations: {
bookMutate(state, data) {
state.book.responseBook = data;
}
},
actions: {
getData(vuexContext) {
let vue = this;
// your request is here
setTimeout(function() {
vue.$axios.$get("https://pokeapi.co/api/v2/pokemon/ditto").then((result) => {
console.log(result);
vuexContext.commit("bookMutate", "ok");
}).catch(err => {
console.log(err);
})
}, 10000)
},
}
});
};
export default createStore;
I intentionally used setTimeout() in my action to see that the success component is loaded after the data was received. in actual situation it is better to use this action:
getData(vuexContext) {
this.$axios.$get("https://pokeapi.co/api/v2/pokemon/ditto").then((result) => {
console.log(result);
vuexContext.commit("bookMutate", "ok");
}).catch(err => {
console.log(err);
})
},
I used axios for calling the api but you can use your own method of getting data. but after that you must commit the mutation to change the state.
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 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>
I have a Vue app configured by NuxtJS.
I have the following template and worker methods that should be called upon clicking the button. But they are not being called.
<template>
<button class="btn google-login" id="google-login" #click.native="googleLogin">
<img src="~assets/google-icon.svg" alt />
Login with Google
</button>
</template>
<script>
import firebase from "firebase";
export default {
data() {
return {
showPassword: false,
checkbox1: false
};
},
mounted: function() {
console.log(firebase.SDK_VERSION);
},
methods: {
googleLogin: function(event) {
console.log('Reached inside the function');
let googleProvider = new firebase.auth.GoogleAuthProvider();
googleProvider.addScope(
"https://www.googleapis.com/auth/contacts.readonly"
);
firebase.auth().useDeviceLanguage();
console.log(googleProvider);
}
}
};
</script>
I have the method inside the methods object. I have tried multiple solutions v-on:click, #click, #click.prevent but none seem to be working
.native event modifier is used with elements when you are trying listen any event happening in the child Component from the root Component.
For example you have a component button-counter, and your parent component need to listen to the click event happening in the button-counter component.
In your case you just neeed to trigger click event using #click="googleLogin"
Official Documentation
Read More
Sample implementation
new Vue({
el: "#app",
name: "MyApp",
components: {
'button-counter': {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
},
},
methods: {
googleLogin: function (event) {
console.log('Reached inside the function');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.4/vue.js"></script>
<div id="app">
<button class="btn google-login" id="google-login" #click="googleLogin">
Login with Google
</button>
<button-counter #click.native="googleLogin" />
</div>
You need to trigger the click event this way.
#click="googleLogin"
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>