I have a child and a parent component.
The flow is: In parent I have "Save changes"-> I have one method that update it -> also in child I have an method to update a part of the component , but if I have duplicate value first emits the Update from parent and after that I got error from Child, and I want to await for a answer from child until I update parent component.
Parent:
<b-btn #click="updateCustomer">Save Changes</b-btn>
<display-pr #duplicateValue="valueDuplicate"></display-pr>
updateCustomer() {
if(this.duplicateValue){
//error message
}else{
//do something
}
}
valueDuplicate(duplicateValue) {
this.duplicateValue = duplicateValue;
},
Child
updateCustomerName(){
//find if I have duplicate and if I have let duplicate= true;
if(duplicate){
this.$emit('duplicateValue',duplicateValue);
}
else {
//post
}
}
You can disable "Save Changes" button for click event at parent component until finishing up checking duplication at child component. if you check duplication from network call, you should use asynchronous javascript paradigm.
parent
<template>
<b-btn #click="updateCustomer">Save Changes :disabled="isSaveBtnDisabled"</b-btn>
<display-pr #duplicateValue="valueDuplicate"></display-pr>
<child-component #checkDuplicates="changeSaveBtnStatus"></child-component>
</template>
<script>
data: () => {
isSaveBtnDisabled: false
},
methods: {
changeSaveBtnStatus: (event) => {
this.isSaveBtnDisabled = event;
}
}
</script>
Child
updateCustomerName: () => {
this.$emit('checkDuplicates', true);
/*
* Here is code statements the checking duplicates and enable save button by calling
* this.$emit('checkDuplicates', false);
*
*/
this.$emit('checkDuplicates', false);
if(duplicate){
this.$emit('duplicateValue',duplicateValue);
}
else {/***post ***/}
}
Related
I'm quite new to vue and right now I'm trying to figure out how to make changes to a computed array and make an element react to this change. When I click the div element (code section 4), I want the div's background color to change. Below is my failed code.
Code section 1: This is my computed array.
computed: {
arrayMake() {
let used = [];
for (let i = 0; i < 5; i++) {
used.push({index: i, check: true});
}
return used;
Code section 2: This is where I send it as a prop to another component.
<test-block v-for="(obj, index) in arrayMake" v-bind:obj="obj" v-on:act="act(obj)"></card-block>
Code section 3: This is a method in the same component as code section 1 and 2.
methods: {
act(obj){
obj.check = true;
}
Code section 4: Another component that uses the three sections above.
props: ["obj"],
template: /*html*/`
<div v-on:click="$emit('act')">
<div v-bind:style="{ backgroundColor: obj.check? 'red': 'blue' }">
</div>
</div>
Easiest way to achieve this, store the object into another data prop in the child component.
child component
data() => {
newObjectContainer: null
},
onMounted(){
this.newObjectContainer = this.obj
},
methods: {
act(){
// you don't need to take any param. because you are not using it.
newObjectContainer.check = !newObjectContainer.check
}
}
watch: {
obj(val){
// updated if there is any changes
newObjectContainer = val
}
}
And if you really want to update the parent component's computed data. then don't use the computed, use the reactive data prop.
child component:
this time you don't need watcher in the child. you directly emit the object from the method
methods: {
act(){
newObjectContainer.check = !newObjectContainer.check
this.emits("update:modelValue", nextObjectContainer)
}
}
parent component:
data() => {
yourDynamicData: [],
},
onMounted(){
this.yourDynamicData = setAnArray()
},
methods(){
setAnArray(){
let used = [];
for (let i = 0; i < 5; i++) {
used.push({index: i, check: true});
}
return used;
}
}
okay above you created a reactive data property. Now you need the update it if there is a change in the child component,
in the parent first you need object prop so, you can update that.
<test-block v-for="(obj, index) in arrayMake" v-model="updatedObject" :obj="obj"></card-block>
data() => {
yourDynamicData: [],
updatedObject: {}
},
watch:{
updatedObject(val){
const idx = val.index
yourDynamicData[idx] = val
}
}
I have created a table component which accept dynamic data (th, tr, td,...).
Table data (td) could be a dynamic component as below:
<td>
<component
:is="data.content"
:colspan="data.colspan"
v-bind="data.props"
v-on="data.events"/>
</td>
...
export default {
name: "DynamicTable",
props: {
...
isLoading : { // loading slot will be used if true
type: Boolean,
default: false
}
}
}
I feed required data in another component like this:
<other-html-elements/>
<dynamic-table
:table-heads="tableHeads"
:table-rows="tableRows"
:is-loading="isLoading">
...
computed: { ...
tableRows () {...
new TableData(CancelOrderButton, 'component', {
props: {
order
},
events: {
'updateLoadingStatus': this.updateLoadingStatus
}
})
...
methods: { ...
updateLoadingStatus (status) {
this.isLoading = status
}
and here is my CancelOrderButton:
methods: {
cancelOrder () {
this.$emit('updateLoadingStatus', true)
somePromise().finally(() => {
this.$emit('updateLoadingStatus', false)
})
once I click on a button and invoke the cancelOrder method, the updateLoadingStatus will be emitted without any problem. and after the promise settled, it will be emitted again. but the handler will not triggered.
I have checked everything. I'm sure that events are emitted. this problem will be fixed when I move the second emit statement out of the finally block or I if do not pass isLoading as a props for the dynamicTable.
Try setting the prop for that emit like this:
<dynamic-table
:table-heads="tableHeads"
:table-rows="tableRows"
#update-loading-status="updateLoadingStatus"
:is-loading="isLoading">
And calling that emit like this (although it should work as you have it):
this.$emit('update-loading-status', true)
Also you can define them in a general way and use them in the component you want:
https://v2.vuejs.org/v2/guide/custom-directive.html
Working with VUE.JS...
Having a 'theParent' component with two childs: 'theForm' and 'theButtons'.
(This is a simplification of more complex scenario).
In 'theButtons' exists a 'Clear' button to clean fields of 'theForm' form.
I am able to pass the event from 'theButtons' to 'theParent', but not from 'theParent' to 'theForm' ¿?
Within 'theButtons':
<b-button v-on:click="clean()">Clear</b-button>
methods: {
clean() {
this.$emit('clean');
}
},
Within 'theParent':
<theForm />
<theButtons v-on:clean="clean"/>
methods: {
clean() {
this.$emit('theForm.clean'); //Here is the point I dont know what to put
}
},
Within 'theForm':
methods: {
clean() {
alert("This is what I want to get executed!!");
}
},
Add a ref to the TheForm component then run its method from parent :
<theForm ref="form" />
<theButtons v-on:clean="clean"/>
methods: {
clean() {
this.$refs.form.clean(); //clean should be a method in theForm component.
}
},
Or you could add a prop to theForm component which could be updated in parent and watch in child component to clean the form :
<theForm :clean="clean" />
<theButtons v-on:clean="clean"/>
data(){
return {
clean:false
}
},
methods: {
clean() {
this.clean=clean;
}
},
inside theForm :
props:['clean'],
watch:{
clean(val){
if(val){
this.clean()
}
}
}
One thing to keep in mind is the flow of passing data from child to parent and vice versa.
In a nutshell:
Parent => Child: if you want to pass data from parent to child, use props.
Child => Parent: if you want to pass data from child to parent, use events.
One solution to your problem is to have <b-button> as a child to the <theForm> as follows:
<theForm v-on:clean="cleanForm">
<!-- form children -->
....
<theButton />
....
</theForm>
Another solution is to have an event bus that passes events between components which is totally unnecessary in your case.
Reference: https://v3.vuejs.org/guide/component-basics.html#passing-data-to-child-components-with-props
https://v3.vuejs.org/guide/component-basics.html#listening-to-child-components-events
I'm working on a wizard and here's the 3rd step of the wizard,
if(this.currentstep == 3){
var data = this.$parent.permit_info
axios.post('/customer/project-info/'+this.currentstep,data)
.then((response) => {
this.$parent.permit_info.id = response.data.id
})
The permit_info data object is in the 'create.vue' component like this,
data(){
return{
permit_info:{
id:null,
projectType:null,
p_type:'',
},
}
}
Also form 'create.vue' I have created another child component . And in there I have several data objects like,
data(){
return{
mep:{
selectedOAAMep: [],
mepTIBMech: '',
mepTIElec: '',
mepTIPlumb: '',
},
poolspa:{
selected: '',
selectedSpa: [],
selectedOAAPoolspa: [],
}
}
I want to pass child data objects to the permit_info in order to save them from controller. It's bit confusing for me. I've done props. But in a wizrd I would like to get some help on $emit. Thanks
You can consider the $emit as an event that contains some data. Here is a fast example of how to get data back to parent using $emit:
Parent Component:
<ParentComponent>
...
<ChildComponent #onEmitEvent="handleEmitEvent($event)">
</ChildComponent>
...
methods: {
handleEmitEvent(dataFromChild){
// HANDLE THE DATA HERE
}
}
</ParentComponent>
Child Component:
<ChildComponent>
...
...
<!-- element that will send the data back to ParentComponent -->
<!-- I am using VueMaterial so for me it will be something like: -->
<!-- <md-button #click="sendDataToParentComponent(data)"> -->
...
...
methods: {
sendDataToParentComponent(data){
// FOLLOWING LINE WILL SEND THE "data" OBJECT TO THE PARENT
this.$emit("onEmitEvent", data);
}
}
</ParentComponent>
Accessing the data on the parent level is the same as you handle the Object itself. You can also send "complicated objects" as:
sendDataToParentComponent(data){
// FOLLOWING LINE WILL SEND THE "data" OBJECT TO THE PARENT
this.$emit("onEmitEvent", {data: dataObject, addObject: true);
}
And on the the parent level you will have:
handleEmitEvent(dataFromChild){
// dataObject
dataFromChild.data
// addObject
dataFromChild.addObject
}
I have the following component:
Vue.component('visible-filter', {
template: `
<span class="text-muted"
#mouseenter="changeClassMouseenter($event)"
#mouseout="changeClassMouseout($event)"
#click="countryTest(filter)"
#clicked="clicked = true"
v-model="clicked"
><slot></slot></span>
`,
props: ['filter', 'clicked'],
methods: {
changeClassMouseenter(event) {
console.log(this.clicked);
event.target.classList.remove('text-muted')
},
changeClassMouseout(event) {
event.target.classList.add('text-muted')
},
countryTest(filter) {
Event.$emit('clicked');
Event.$emit('country-filter', filter);
}
}
});
The Event in the main instance:
Event.$on('clicked', () => {
this.clicked = true;
})
The data in the main instance:
data: {
clicked: false
},
The thing I want to do:
When I click on the element, I want to set the clicked property to true (for that element), and for the rest of the elements I want to set it to false; Also I want to check if the clicked is true/false when the mouseenter/mouseout event is fired.
How can I achive this?
You should not change the parent from child.
To make communication between parent and child (and child->parent as well) you can/should set up the events.
Emit the event in child component. Nice examples here: https://v2.vuejs.org/v2/guide/components.html#Using-v-on-with-Custom-Events
Listen to event in your parent using <child-component #yourEventName='eventHandler(data)'>
Handle data in your parent, so add eventHandler(data) into your methods and do whatever you want with your data.