How to emit data to DIRECT PARENT only in Vue 2.0? - vuejs2

I have a reusable child component SizeSelector and it has been used under 2 components served for different purposes.
In SizeSelector
onSizeSelectionChanged() {
if (this.isMultiSelectable === false) {
this.selectedVariants = [this.selectedVariant];
}
this.$emit('update', this.selectedVariants);
}
Under component A:
<product-size-form :product="product" #update="selectedVariant = $event[0]"></product-size-form>
Under component B:
<product-size-form
:product="product"
:is-multi-selectable="true"
#update="selectedVariants = $event; log($event, 'modal')">
</product-size-form>
My problem is when I click and change the selectedVariants in B it emited to A. How to fix it? I seems the event got emitted globally
Update
It sounds crazy and it is.
This one is not working
<div class="product-form">
<product-size-form
:product="product"
:is-multi-selectable="true"
#update="selectedVariants = $event; log($event, 'modal')">
</product-size-form>
</div>
And this one works completely fine
<div class="product-form-wow-this-is-working-wtf">
<product-size-form
:product="product"
:is-multi-selectable="true"
#update="selectedVariants = $event; log($event, 'modal')">
</product-size-form>
</div>

The way I see it you have 2 options:
1)Just change the names of your events, call one "update" (not particularly descriptive), and the other "update2" or anything except "update".
2)The other thing you can do is work react style i.e. instead of emitting an event pass in a prop with a callback function that does something in the parent.
Like it was pointed out this should not be happeing events are not supposed to be emitted globally.

Related

Pass props instantaneously to a child component in vue 3 by calling a function that creates a modal

I have a problem with a vue component that handles model changes.
In the parent component I have something like this:
<div v-for="role in roles">
Start date: {{ role.start_date) }}
<button #click="handleSelectRoleToEdit($event, role)" id="start_date"</button>
</div>
<edit-role
:role="selectedRole"
:editField="editField"
ref="ToEditRole"
>
</edit-role>
where handleSelectRoleToEdit is
handleSelectRoleToEdit(event, role) {
this.selectedRole = role;
this.$refs.ToEditRole.editRoleModal()
this.editField = event.currentTarget.id
}
This piece of code should take the current item and pass it to the child component <edit-role>. Inside the child component there is
props: [
"role",
"editField"
]
...
editRoleModal() {
this.modal_edit_role = new Modal(document.getElementById('edit_roleModal'), {})
this.modal_edit_role.show()
console.log(this.role)
}
The modal is successfully created and opened, but console.log(this.role) returns Proxy {}.
If I edit the fields (so I call another function), everything is fine, because this.role becomes available.
However I need this.role to be available as soon as the modal is created.
How can I solve this problem?
Any help is greatly appreciated
I don't know if this suits your needs, but instead of using $refs you could set a watcher for the prop role in the child component. This ensures that the function is executed when the data is available.

How can I update data object whenever changes happens inside v-for of child

How can I update data object of parent whenever changes happen inside v-for. I have a child component that I use inside parent component.
ParentComponent.vue
<template>
....
....
<child-component
v-for="i in count"
ref="childComponent"
:key="i"
currentPage="i" // currentPage doesn't update.
:page="i"
/>
<q-header>
{{currentPage}} // always displays default value:1
</q-header>
</template>
<script>
data () {
return {
pageCount: 10,
currentPage: 1,
}
},
How can I update currentPage of data object whenever i changes inside v-for. I have tried using watch without much luck. I don't have access to child component and can't modify it.
Much appreciated!
There is some slight confusion with how v-for is working on the child-component here. Writing currentPage="i" as a property (which should actually be v-bind:currentPage in order for the i to be interpreted as JS) will simply declare the attribute on each child-component
How can I update currentPage of data object whenever i changes inside v-for
i doesn't "change" in the traditional context of running a for loop inside of a normal JavaScript application. In Vue, your rendering logic and application logic are separate, and rightly so, because running logic as part of the rendering doesn't really make sense.
For example, let's look at how your app will render the child-component:
<!-- Vue output -->
<child-component ... currentPage="1" />
<child-component ... currentPage="2" />
<child-component ... currentPage="3" />
So let's look at separating the rendering logic from the application logic.
I realise you don't have access to child-component, but based on the context I will assume it is some kind of tabbing functionality (based on you trying to set a value for the "current page" - feel free to be more specific and I can update my answer).
We need to bridge that gap between the rendering logic and the application logic and we can do that by using events:
<child-component
v-for="i in count"
:ref="`childComponent-${i}`" // ref should be unique so add the i as part of it
:key="i"
:page="i"
v-on:click="currentPage = i" // when the user clicks this child component, the current page will be updated
/>
You may have to utilise a different event other than click but I hope this gets you closer to what you are trying to achieve. For the value of currentPage to update there has to be some kind of user input, so just find out which event makes the most sense. Maybe the child-component library you are using has custom events that are more appropriate.
you should look into Custom Events.
https://v2.vuejs.org/v2/guide/components-custom-events.html
Idea is, that whenever there is some update of your desire in child component, you can execute this.$emit(“change”), which will throw an event.
On parent side you can catch this event by #change=“myMethod” as one of the attributes.
methods: {
myMethod() {
console.log("Testing")
}
}
<child-component
v-for="i in count"
ref="childComponent"
:key="i"
currentPage="i"
:page="i"
#change=“myMethod”
/>
Let me know if that helped.

VueJS emitted event not getting picked up by parent component

I am struggling to understand why an emitted event on a child component is not getting picked up by the parent.
The event is getting emitted from the child component - I can see it in the toolbar along with the correct values. The method that is tied to the event is not getting called on the parent component. ChildComponent.vue is a form that is imported into ParentComponent.vue.
Here is what I have (that's working but not).
ParentComponent.vue
<child-component/>
<div v-show="this.label === 'acme'" #label:name="handleLabelName">
<h3>Hello World</h3>
...
</div>
...
methods: {
handleLabelName(name) {
console.log('handleManualType called'); // never getting here
console.log('name: ', name); // never getting here
}
},
ChildComponent.vue
...
<button data-label="acme" #click="handleClick($event)">Click Me</button>
...
methods: {
handleClick(event) {
const label = event.target.dataset.label;
this.$emit('label:name', label); // acme
},
The event is getting broadcast, but handleLabelName is never getting called. I've read through this great article many times and I believe I'm following it correctly. Is there something I am doing wrong to make handleLabelName never get called? I've also tried wrapping everything in a div like so:
<div #label:name="handleLabelName">
<div v-show="this.label === 'acme'">
<h3>Hello World</h3>
...
</div>
</div>
And still the same result. handleLabelName never gets called. I've also tried changing the event name/method to simple things like, foo and still no difference.
Thank you for any suggestions!
You are listening for events on a div instead of listening to your child component. You must put your listener on the DOM element that sends the event. Try something like this :
<child-component #my-event-name="handleLabelName"/>
Another point is that you might prefer using kebab case to name your custom events. It might be a problem to name your event with this character ":" (see https://v2.vuejs.org/v2/guide/components-custom-events.html)

is it correct global component communication in vue?

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>

Is it posible to delete `div` from template?

I have a component myHello:
<temlate>
<div>
<h2>Hello</h1>
<p>world</p>
</div>
</template>
And main component:
<h1>my hello:</h1>
<my-hello><my-hello>
After rendering shows this:
<h1>my hello:</h1>
<div>
<h2>Hello</h1>
<p>world</p>
</div>
How to delete <div> ?
With VueJS, every component must have only one root element. The upgrade guide talks about this. If it makes you feel better, you are not alone. For what it's worth the components section is a good read.
With the myriad of solutions to your problem, here is one.
component myHello:
<temlate>
<h2>Hello</h1>
</template>
component myWorld:
<temlate>
<p>world</p>
</template>
component main
<h1>my hello:</h1>
<my-hello><my-hello>
<my-world><my-world>
Vue gives you the tools to do so by creating templates or you can do it by having a parent div with two parent divs as children. Reset the data from the data function. Stick with convention (create templates). It's hard to get used to use Vue when you have a jQuery background. Vue is better
Ex.
data () {
message: 'My message'
}
When you click a button to display a new message. Clear the message or just set the message variable with a new value.
ex. this.message = 'New Message'
If you like to show another div. you should used the if - else statement and add a reactive variable. Ex. showDivOne
data () {
message: 'My message'
showDivOne: true,
showDivTwo: false
}
Add this reactive variables to the parent divs that corresponds to the div.
When clicking the button, you should have a function like...
methods: {
buttonClick () {
this.showDivOne = false
this.showDivTwo = true
}
}
I think you can use v-if directive to controll. Add a flag to controll status to show or hide