I have a vuejs component("Main") with a dialog and I use a subcomponent("SpeechToText") into it.
When I´m going to open the dialog, I need to check if "speechInititalized" of the "SpeechToText " is true.
if yes, I´d like to call a "reset method" to stop the microphone and the user can restart as if were the first time.
How could I do that?
Here is a snippet of the code.
//Main component
<template>
<div>
<v-dialog v-model="dialogSpeech" hide-overlay persistent width="700">
<SpeechToText></SpeechToText>
<v-btn color="info" #click="closeDialogSpeech">Fechar</v-btn>
</v-dialog>
</div>
</template>
data:()=>({
speechInititalized:false,
}),
methods:{
closeDialogSpeech(){
this.dialogSpeech = false;
}
openDialogSpeech(){
//I´d like to call reset method of the SpeechToText component if it was initialized
if (speechInititalized){ //from SpeechToText data
reset(); //from SpeechToText data
}
}
}
//SpeechToText
data:()=>({
speechInititalized:false,
})
mounted(){
this.initialize();
}
methods{
initialize(){
//speech api is initialized
speechInititalized=true;
};
reset(){
//speech api is stopped
//stop microphone
//clear data
};
}
Don't check if speech was initialized in parent component. That's child component's responsibility. All you do in parent component is emit/announce the dialogue has opened.
Child component reacts to this announcement.
Proof of concept:
// parent
<template>
...
<speech-to-text :is-dialogue-open="isDialogueOpen" />
...
</template>
<script>
export default {
...
data: () => ({
isDialogueOpen: false // set this to true/false when dialogue is opened/closed
}),
...
}
// child:
export default {
props: {
isDialogueOpen: {
type: boolean,
default: false
}
},
...,
watch: {
isDialgueOpen(value) {
// reset if dialogue has just opened and speech has previously been initialized
if (value && this.speechInitialized) {
this.reset();
}
}
},
...
}
Another, more flexible and cleaner approach, preferable when the relation between parent and child is not as direct, or even dynamic and, generally, preferable in larger scale applications, is to use an eventBus (basically a singleton shared across components for emitting/listening to events).
Emit an event on the bus in any corner of the application and have as many listeners reacting to that event in as many other components in the app, regardless of their relation to the original emitter component.
Here's a neat example explaining the concept in more detail.
If you're using typescript, you might want to give vue-bus-ts a try.
This approach is similar to the previous one (emit an event when dialogue is opened and react to it in SpeechToText component), except both parent and child are now cleaner (none of them needs the isDialogueOpen prop, and you also get rid of the watch - whenever possible, avoid watch in Vue as it's more expensive than most alternatives).
Since the event listener is inside SpeechToText, you can check if speech has already been initialized.
Another benefit of this approach is that you don't have to emit/change anything when dialogue closes (unless you want to react to that event as well).
Related
So I’m building a Nuxt app for working with docs (in a broad sense), and it will have a menu, which I will obviously make a component. The menu will be home to lots of actions on the doc itself, such as opening/saving files, editing, etc. etc.
I know the standard way to pass info from a component to its parent (the doc vm in this case) is via messages, but it feels like a bit of an overkill, what with the syntax (emit handlers just don’t feel natural to me in this case) and whatnot.
For this reason I was wondering why can’t I just pass the parent vm as a prop to the menu component? It will contain all kinds of methods, and I will be able to easily invoke them via the menu. Something like:
Parent (Document.vue):
<template>
<main-menu :document='vm'/>
</template>
<script>
import MainMenu from '~/components/MainMenu.vue'
export default {
data(): {
return {
vm: this,
//...
}
},
methods: {
save() {
//...
}
}
//...
</script>
Menu component (MainMenu.vue):
<template>
<button #click='document.save()'>Save document</button>
</template>
<script>
export default {
props = ['document']
}
</script>
The question: Is there something intrinsically bad in this approach?
(I imagine this could be problematic if the app architecture could change, but it’s hard to imagine that I would for some reason need a menu without an underlying document.)
IF your Menu is always the child of the component, then you don't have to pass your parent. It is already held in a Vue variable called this.$parent.
I made a little sandbox to give you an example.
The parent has a function, for example:
/// PARENT
export default {
name: "App",
components: {
HelloWorld,
},
methods: {
iExist(add) {
console.log("I am in parent" + add);
},
},
};
Then you can call it from child with this.$parent.iExist('something').
Since this.$parent is not defined when the template is being evaluated, we have to make a method in the child as well, to call(super) the corresponding function on it's parent.
/// CHILD
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<button #click="iExist(', but was called from child')">Click Me</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String,
},
methods: {
iExist(add) {
this.$parent.iExist(add);
},
},
};
</script>
The question: Is there something intrinsically bad in this approach?
(I imagine this could be problematic if the app architecture could change, but it’s hard to imagine that I would for some reason need a menu without an underlying document.)
Yes, this is bad design. Parents can be aware of children, children shouldn't be aware of parents. A child could be tested in isolation, or be nested inside wrapper component that doesn't have this method.
As another answer suggests, a way to access a parent is to use $parent property. This part was borrowed in Vue from AngularJS 1.x, accessing it was considered a bad practice even then.
This is generally achieved by providing a callback from a parent that does exactly a desired thing, without allowing to access the whole instance and break the encapsulation. It's unnecessary to explicitly define callback function in Vue because this is naturally provided by Vue template syntax:
In a parent:
<child #save="save()">
In a child:
<button #click="$emit('save')">
In case of deeply nested components the event can be passed through them to a parent.
I made a component called dialog, and I want to make it plugin and register it as a global function.
However, I don't know how to access the component from plugin and call the component's method.
import Vue from 'vue'
import AlertDialog from '#/components/AlertDialog'
const methods = {
openDialog: (
maxWidth,
title,
message
) =>
AlertDialog.openDialog(
maxWidth,
title,
message
),
closeDialog: () => AlertDialog.closeDialog()
}
Vue.prototype.openDialog = methods.openDialog
Vue.prototype.closeDialog = methods.closeDialog
This is a dialog_plugin.js.
But it doesn't work.
edit
::
<template>
<v-dialog
v-if="isShow"
v-model="isShow"
:max-width="maxWidth ? maxWidth : 290"
>
<v-card>
...
</v-card>
</v-dialog>
</template>
<script>
export default {
data() {
return {
isShow: false,
maxWidth: null,
title: null
}
},
methods: {
openDialog(
maxWidth,
title
) {
this.isShow = true
...
},
closeDialog() {
this.isShow = false
}
}
}
</script>
This is AlertDialog.vue
A component's methods are only intended to be called from within the component itself. Trying to expose them to be called by outside code is really going against the way Vue is designed to work.
If you want to affect the state of a component from outside of itself, there are a couple of ways:
Props - Rather than isShow being a piece of the AlertDialog's state, you would pass this value in as a prop. That way the parent component can change the value to show/hide the alert as needed.
Vuex - If you needed to have a single instance of a component in your app (e.g. for a toast display), having it receive its state from a Vuex store would make it easy to display messages from any part of your app.
An Event Bus - For a simpler app where you don't want to bring in Vuex, you can always use an instance of Vue as an event bus, to control your component by emitting events from anywhere else in your app. Your component can then listen for these events and show/hide as needed.
i make modal popup components myPopup.vue for global.
and import that in App.vue and main.js
i use this for global, define some object Vue.prototype
make about popup method in Vue.prototype
like, "show" or "hide", any other.
but i think this is maybe anti pattern..
i want to find more best practice.
in App.vue
<div id="app>
<my-popup-component></my-popup-conponent>
<content></content>
</div>
main.js
...
Vue.prototype.$bus = new Vue(); // global event bus
Vue.prototype.$popup = {
show(params) {
Vue.prototype.$bus.$emit('showPopup', params);
},
hide() {
Vue.prototype.$bus.$emit('hidePopup');
}
}
Vue.component('my-popup-component', { ... });
...
myPopup.vue
....
export default {
...
created() {
this.$bus.$on('showPopup', this.myShow);
this.$bus.$on('hidePopup', this.myHide);
}
...
need-popup-component.vue
methods: {
showPopup() {
this.$popup.show({
title: 'title',
content: 'content',
callback: this.okcallback
});
}
}
It seems to be works well, but i don't know is this correct.
Is there any other way?
I was very surprised while reading your solution, but if you feel it simple and working, why not?
I would do this:
Add a boolean property in the state (or any data needed for showing popup), reflecting the display of the popup
use mapState in App.vue to bring the reactive boolean in the component
use v-if or show in App.vue template, on the popup declaration
create a 'showPopup' mutation that take a boolean and update the state accordingly
call the mutation from anywhere, anytime I needed to show/hide the popup
That will follow the vue pattern. Anything in state, ui components reflect the state, mutations mutates the state.
Your solution works, ok, but it doesn't follow vue framework, for exemple vue debug tools will be useless in your case. I consider better to have the minimum of number of patterns in one app, for maintenance, giving it to other people and so on.
You somehow try to create global component, which you might want to consume in your different projects.
Here is how I think I would do this -
How do I reuse the modal dialog, instead of creating 3 separate dialogs
Make a separate modal component, let say - commonModal.vue.
Now in your commonModal.vue, accept single prop, let say data: {}.
Now in the html section of commonModal
<div class="modal">
<!-- Use your received data here which get received from parent -->
<your modal code />
</div>
Now import the commonModal to the consuming/parent component. Create data property in the parent component, let say - isVisible: false and a computed property for the data you want to show in modal let say modalContent.
Now use it like this
<main class="foo">
<commonModal v-show="isVisible" :data="data" />
<!-- Your further code -->
</main>
The above will help you re-use modal and you just need to send the data from parent component.
How do I know which modal dialog has been triggered?
Just verify isVisible property to check if modal is open or not. If isVisible = false then your modal is not visible and vice-versa
How my global dialog component will inform it's parent component about its current state
Now, You might think how will you close your modal and let the parent component know about it.
On click of button trigger closeModal for that
Create a method - closeModal and inside commonModal component and emit an event.
closeModal() {
this.$emit('close-modal')
}
Now this will emit a custom event which can be listen by the consuming component.
So in you parent component just use this custom event like following and close your modal
<main class="foo">
<commonModal v-show="isVisible" :data="data" #close- modal="isVisible = false"/>
<!-- Your further code -->
</main>
ok so I've learned that I'm not supposed to call a child's method but pass it props instead.
I've got (parent) :
<template>
<div id="main">
<Header :title ="title"/>
<router-view/>
<LateralMenu/>
</div>
</template>
<script>
export default {
name: 'app'
data: function () {
return {
title: true
}
},
methods: {
hideTitle: function () {
this.title = false
console.log(this.title)
},
showTitle: function () {
this.title = true
console.log(this.title)
}
}
}
</script>
and (child) :
<script>
export default {
name: 'Header',
props: ['title'],
created () {
console.log(this.title)
},
methods: {
}
}
</script>
the first console logs (inside the parent) print correctly on each method but the second console log within the child stays true all the time. I got this from : Pass data from parent to child component in vue.js
inside what method does the console.log need to be to be printed everytime the methods in the parent are triggered?
(this is why I wanted to go for method-calling, originally, by going with variables instead, we're potentially omitting valuable parts of the process such as optimization and a "when" for the execution(s!!) of our code. pontetally being the key word here, don't blow up on me, keep in mind that I'm learning.)
OLD:
I've browsed the web and I know there a a million different answers
and my point is with the latest version of vue none of those millions
of answers work.
either everything is deprecated or it just doesn't apply but I need a
solution.
How do you call a child method?
I have a 1 component = 1 file setup.
DOM is declared inside a <template> tag javascript is written inside
a <script> tag. I'm going off of vue-cli scaffolding.
latest method I've tried is #emit (sometimes paired with an #on
sometimes not) doesn't work :
child :
<script>
export default {
name: 'Header',
created () {
this.$on('hideTitlefinal', this.hideTitlefinal)
},
methods: {
hideTitlefinal: function () {
console.log('hideeeee')
},
showTitlefinal: function () {
console.log('shwowwww')
}
}
}
</script>
parent :
<template>
<div id="main">
<Header v-on:hideTitle="hideTitlefinal" v-on:showTitle="showTitlefinal"/>
<router-view/>
<LateralMenu/>
</div>
</template>
<script>
export default {
methods: {
hideTitle: function () {
this.$emit('hideTitle')
},
showTitle: function () {
this.$emit('showTitle')
}
}
}
</script>
console :
Uncaught TypeError: this.$emit is not a function
at Object.showTitle (Main.vue?1785:74)
at VueComponent.showTitle (LateralMenu.vue?c2ae:113)
at boundFn (vue.esm.js?efeb:186)
at invoker (vue.esm.js?efeb:1943)
at HTMLDivElement.fn._withTask.fn._withTask (vue.esm.js?efeb:1778)
Please don't do this. You're thinking in terms of events. When x happens, do y. That's sooo jquery 2005 man. Vue still has all that stuff, but we're being invited to think in terms of a view model...
You want your state in a variable, in window scope, and you want reactive pipes linking your vue stuff to your state object. To toggle visibility, use a dynamic class binding, or v-if. Then think about how to represent your state. It could be as simple as having a property like store.titleVisible. But, you want to 'normalize' your store, and avoid relationships between items of state. So if title visibility really depends on something higher up, like an editMode or something, then just put the higher-up thing in the store, then create computed properties if you need them.
The goal is that you don't care when things happen. You just define the relationships between the markup and the store, then let Vue take care of it. The docs will tell you to use props for parent=>child and $emit for child=>parent communication. Truth is you don't need this until you have multiple instances of a component, or reusable components. Vue stuff talks to a store, not to other vue stuff. For single-use components, as for your root Vue, just use the data:.
Whenever you find yourself writing show/hide methods, you're doing it wrong. It's intuitive (because it's procedural), but you'll quickly appreciate how much better the MVVM approach is.
I'm using the vue-cli scaffold for webpack
My Vue component structure/heirarchy currently looks like the following:
App
PDF Template
Background
Dynamic Template Image
Static Template Image
Markdown
At the app level, I want a vuejs component method that can aggregate all of the child component's data into a single JSON object that can be sent off to the server.
Is there a way to access child component's data? Specifically, multiple layers deep?
If not, what is the best practice for passing down oberservable data/parameters, so that when it's modified by child components I have access to the new values? I'm trying to avoid hard dependencies between components, so as of right now, the only thing passed using component attributes are initialization values.
UPDATE:
Solid answers. Resources I found helpful after reviewing both answers:
Vuex and when to use it
Vuex alternative solution for smaller apps
In my child component, there are no buttons to emit changed data. It's a form with somewhat 5~10 inputs. the data will be submitted once you click the process button in another component. so, I can't emit every property when it's changing.
So, what I did,
In my parent component, I can access child's data from "ref"
e.g
<markdown ref="markdowndetails"></markdown>
<app-button #submit="process"></app-button>
// js
methods:{
process: function(){
// items is defined object inside data()
var markdowns = this.$refs.markdowndetails.items
}
}
Note: If you do this all over the application I suggest move to vuex instead.
For this kind of structure It's good to have some kind of Store.
VueJS provide solution for that, and It's called Vuex.If you are not ready to go with Vuex, you can create your own simple store.
Let's try with this
MarkdownStore.js
export default {
data: {
items: []
},
// Methods that you need, for e.g fetching data from server etc.
fetchData() {
// fetch logic
}
}
And now you can use those data everywhere, with importing this Store file
HomeView.vue
import MarkdownStore from '../stores/MarkdownStore'
export default {
data() {
sharedItems: MarkdownStore.data
},
created() {
MarkdownStore.fetchData()
}
}
So that's the basic flow that you could use, If you dont' want to go with Vuex.
what is the best practice for passing down oberservable data/parameters, so that when it's modified by child components I have access to the new values?
The flow of props is one way down, a child should never modify its props directly.
For a complex application, vuex is the solution, but for a simple case vuex is an overkill. Just like what #Belmin said, you can even use a plain JavaScript object for that, thanks to the reactivity system.
Another solution is using events. Vue has already implemented the EventEmitter interface, a child can use this.$emit('eventName', data) to communicate with its parent.
The parent will listen on the event like this: (#update is the shorthand of v-on:update)
<child :value="value" #update="onChildUpdate" />
and update the data in the event handler:
methods: {
onChildUpdate (newValue) {
this.value = newValue
}
}
Here is a simple example of custom events in Vue:
http://codepen.io/CodinCat/pen/ZBELjm?editors=1010
This is just parent-child communication, if a component needs to talk to its siblings, then you will need a global event bus, in Vue.js, you can just use an empty Vue instance:
const bus = new Vue()
// In component A
bus.$on('somethingUpdated', data => { ... })
// In component B
bus.$emit('somethingUpdated', newData)
you can meke ref to child component and use it as this
this.$refs.refComponentName.$data
parent-component
<template>
<section>
<childComponent ref="nameOfRef" />
</section>
</template>
methods: {
save() {
let Data = this.$refs.nameOfRef.$data;
}
},
In my case I have a registration form that I've broken down into components.
As suggested above I used $refs, In my parent I have for example:
In Template:
<Personal ref="personal" />
Script - Parent Component
export default {
components: {
Personal,
Employment
},
data() {
return {
personal: null,
education: null
}
},
mounted: function(){
this.personal = this.$refs.personal.model
this.education = this.$refs.education.model
}
}
This works well as the data is reactive.