Given the following component
<template>
<div class="text-center">
<v-dialog
v-model="dialog"
width="500"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
color="red lighten-2"
dark
v-bind="attrs"
v-on="on"
>
Click Me
</v-btn>
</template>
<v-card>
<v-card-title class="headline grey lighten-2">
Privacy Policy
</v-card-title>
<v-card-text>
Some text
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
text
#click="handleAsyncAction"
>
I accept
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";
#Component()
export class MyDialog extends Vue {
dialog: booolean = false;
async handleAsyncAction() {
const value = await this.$emit('click'); // you can't really do that
if(value === 'x') {
dialog = false; //<--- then close
} else {
alert('error!')
}
}
}
</script>
Used by some parent like this:
<template>
<div>
<MyDialog #click="makeSomeAjaxRequest($event)"
</div>
</template>
<script type="ts">
import Vue from "vue";
import Component from "vue-class-component";
import MyDialog from "./MyDialog"
#Component({
components: { MyDialog }
})
export default class ParentComponent extends Vue {
async makeSomeAjaxRequest() {
const data = await fetch('http://example.com');
return data.toString();
}
}
</script>
Using React you'll be just able to pass some function from the parent and wrap the whole thing up, but from my understanding, this is not the Vue way of doing things.
How should I approach making the child component 'await' for the call to be back?
Vue stores all of the relevant event listeners inside the component under this.$listeners
By leveraging that, you'll be able to invoke the function without emitting an actual event:
// MyDialog
async handleAsyncAction() {
//#ts-ignore since Vue 2s' TS definitions condone such behavior
const value = await this.$listeners.click();
if(value === 'x') {
dialog = false; //<--- then close
} else {
alert('error!')
}
}
}
You can see a complete example in an article named Reacting to Promises from event listeners in Vue.js
Related
This is my component.vue:
<template>
<v-text-field
name="Foo"
:label="$t('foo')"
type="text"
hint="This is a hint"
persistent-hint
></v-text-field>
<v-btn color="primary" #click="onButtonClick()">Press</v-btn>
</template>
And this is component.ts
import { defineComponent, reactive, ref, Ref} from 'vue';
export default defineComponent({
setup() {
function onButtonClick() {
}
return { onButtonClick }
}
});
I want to change hint on button click, for example to This is a new hint. Could anyone say how to do in Vuetify 3 using Composition API?
Just create a ref property called hint inside the setup hook then bind it to the hint prop and update it when you click on the button:
import { defineComponent, reactive, ref, Ref} from 'vue';
export default defineComponent({
setup() {
const hint=ref('This is a hint')
function onButtonClick() {
hint.value="new hint"
}
return { onButtonClick, hint }
}
});
in template :
<template>
<v-text-field
name="Foo"
:label="$t('foo')"
type="text"
:hint="hint"
persistent-hint
></v-text-field>
<v-btn color="primary" #click="onButtonClick()">Press</v-btn>
</template>
I'm currently learning Vue, Vuetify and other parts of the ecosystem
Now I'm trying to call a function defined in a different .js file when clicking a button. It's probably something stupid again, but errors keep being thrown.
Ladder.vue shows the buttons and binds the methods to them.
test.js contains an exported function that is called in previewSell
// Ladder.vue
<template>
<div class="ladder">
<v-container fluid>
<v-row align="start" justify="center">
<v-col align="right" sm="4">
<v-btn #click="previewBuy" color="#78b63f" width="125"
>Preview Buy<BR /> Entries</v-btn
>
</v-col>
<v-col align="center" sm="4">
<v-btn #click="resetForm" color="grey darken-2">reset</v-btn>
</v-col>
<v-col align="left" sm="4">
<v-btn #click="previewSell" color="#ba4967" width="125"
>Preview Sell<BR /> Entries</v-btn
>
</v-col>
</v-row>
</v-container>
</div>
</template>
<script>
import Test from "../components/test";
export default {
name: "Ladder",
components: {
},
methods: {
resetForm() {
this.$refs.form.reset();
},
previewBuy() {
console.log('This works')
},
previewSell() {
console.log(Test())
},
},
};
</script>
// test.js
const Test = () => {
return 'Test'
}
export {Test};
Below the error messages:
If you are exporting some variable without default param, you need to import it with {} for found it in file exports. If you`re using export default someFunc, u can use import without bracers
// test.js
export const Test = () => {
return 'Test'
}
import {Test} from "../components/test";
or
const Test = () => {
return 'Test'
}
export default Test
import Test from "../components/test";
// You can change the name of Test to anything else, it will work
Components { test },
is missing.
and
console.log(this.test())
I've got two components. One parent, and one child component. In the child component I'm using a Vuetify Button component. When a user clicks the button, it should emit from the child to the parent component.
I've tried using different Vue methods to emit and capture the emit.
These are the components I'm using. I'm working with "Single file components".
// customer.vue | Parent component
<template>
<v-container grid-list-xl fluid>
<v-layout row wrap>
<v-dialog
persistent max-width="600px">
<customer-messages #cancelmessage="handleCancelMessage"></customer-messages>
</v-dialog>
</v-layout>
</v-container>
</template>
<script>
import CustomerMessages from "#/views/customer-care/customer-care_customer-messages.vue"
export default {
components: {
CustomerMessages,
},
data() {
return {
methods: {
handleCancelMessage() {
console.log('emit worked!');
}
}
}
}
};
// customer-care_customer-messages.vue | Child component
<template>
<v-btn
color="blue darken-1"
flat
#click="cancelMessage">
Cancel
</v-btn>
</template>
<script>
export default {
data() {
return {
};
},
methods: {
cancelMessage() {
this.$emit('cancelmessage');
}
}
};
</script>
I expect the parent component to log "emit worked!". Instead, I keep getting the following error message:
[Vue warn]: Invalid handler for event "cancelmessage": got undefined
found in
---> <CustomerMessages> at src/views/customer-care/customer-care_customer-messages.vue
<ThemeProvider>
<VDialog>
<VCard>
<Customer> at src/views/customer-care/customer.vue
<VContent>
<VApp>
<App> at src/app.vue
<Root>
So I am trying to use multiple instances of a component inside a parent component. I am currently using a Vuetify.js tab-control to display each component.
For each child component I want to load different dynamic data from a external Rest API.
My preferd way to approach this is to pass a prop down to child's so that they can figure out what data to load. I have a vuex store for each type of child, but would like to reuse template for both, since the data is identical besides values.
Parent/Wrapper
<template>
<div>
<v-tabs fixed-tabs>
<v-tab>
PackagesType1
</v-tab>
<v-tab>
PackagesType2
</v-tab>
<v-tab-item>
<packages packageType="packageType1"/>
</v-tab-item>
<v-tab-item>
<packages packageType="packageType2"/>
</v-tab-item>
</v-tabs>
</div>
</template>
<script>
import Packages from './Packages'
export default {
components: {
Packages
}
}
</script>
Named child component
<template>
<v-container fluid>
<v-data-table v-if="packageType1"
:headers="headers"
:items="packageType1"
>
<div>
//ITEM TEMPLATE
</div
</v-data-table>
<v-data-table v-else-if="packagesType2"
:items="packagesType2"
>
<div>
//ITEM TEMPLATE
</div
</v-data-table>
</v-container>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data () {
return {
name: 'packages'
}
},
props: ['packageType'],
computed: {
...mapGetters('packageModule1', ['packageType1']),
...mapGetters('packageModule2', ['packagesType2'])
},
methods: {
getPackageByType () {
if (this.packageType === 'packageType1') {
this.getPackageType1()
return
}
this.getPackageType2()
},
getPackageType1 () {
this.$store.dispatch('chocoPackagesModule/getPackageType1')
.then(_ => {
this.isLoading = false
})
},
getPackageType2 () {
this.$store.dispatch('packagesType2Module/getPackageType2')
.then(_ => {
this.isLoading = false
})
}
},
created () {
this.getPackageByType()
}
}
</script>
Right now it renders only packageType1 when loading.
How can I easily differ those types without duplicating everything?
Should I just hard differ those types? Make separate child-components and role with it that way? I am new to this, but would love as much reuse as possible.
Parent component:
<template>
<v-btn v-on:click="dialog = !dialog">
</v-btn>
</template
<script>
export default {
data: () => ({
dialog: false
}
</script
Child component:
<template>
<v-layout>
<v-dialog v-model="toggledialog">
<v-btn color="green darken-1" flat="flat"
#click.native="toggledialog = false">Close</v-btn>
</v-dialog>
</v-layout>
</template>
<script>
export default {
data: () => ({
toggledialog: false,
}),
watch: {
dialog: function(){
this.toggledialog = true
},
},
props:['dialog']
}
</script>
This code works but I really don't like this workaround with watch.
Questions:
How to open dialog in child component when v-model="toggledialog" if it doesn't watch for props:['dialog']?
How to change dialog to false in parent component when I i change it in child component v-model="dialog" if in Vue JS it is not allowed to change props?
Pass value prop as value to v-dialog, and re-emit input v-dialog whenever you want to close it:
//CustomDialog.vue
<v-dialog :value="value" #input="$emit('input')">
<v-btn color="green darken-1" flat="flat"
#click.native="$emit('input')"
>Close</v-btn>
</v-dialog>
...
props:['value']
and add v-model to your parent
//Parent.vue
<custom-dialog v-model="dialog">
So no data and no watch
example