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
}
Related
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 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.
How do I access $refs inside computed? It's always undefined the first time the computed property is run.
Going to answer my own question here, I couldn't find a satisfactory answer anywhere else. Sometimes you just need access to a dom element to make some calculations. Hopefully this is helpful to others.
I had to trick Vue to update the computed property once the component was mounted.
Vue.component('my-component', {
data(){
return {
isMounted: false
}
},
computed:{
property(){
if(!this.isMounted)
return;
// this.$refs is available
}
},
mounted(){
this.isMounted = true;
}
})
I think it is important to quote the Vue js guide:
$refs are only populated after the component has been rendered, and they are not reactive. It is only meant as an escape hatch for direct child manipulation - you should avoid accessing $refs from within templates or computed properties.
It is therefore not something you're supposed to do, although you can always hack your way around it.
If you need the $refs after an v-if you could use the updated() hook.
<div v-if="myProp"></div>
updated() {
if (!this.myProp) return;
/// this.$refs is available
},
I just came with this same problem and realized that this is the type of situation that computed properties will not work.
According to the current documentation (https://v2.vuejs.org/v2/guide/computed.html):
"[...]Instead of a computed property, we can define the same function as a method. For the end result, the two approaches are indeed exactly the same. However, the difference is that computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed"
So, what (probably) happen in these situations is that finishing the mounted lifecycle of the component and setting the refs doesn't count as a reactive change on the dependencies of the computed property.
For example, in my case I have a button that need to be disabled when there is no selected row in my ref table.
So, this code will not work:
<button :disabled="!anySelected">Test</button>
computed: {
anySelected () {
if (!this.$refs.table) return false
return this.$refs.table.selected.length > 0
}
}
What you can do is replace the computed property to a method, and that should work properly:
<button :disabled="!anySelected()">Test</button>
methods: {
anySelected () {
if (!this.$refs.table) return false
return this.$refs.table.selected.length > 0
}
}
For others users like me that need just pass some data to prop, I used data instead of computed
Vue.component('my-component', {
data(){
return {
myProp: null
}
},
mounted(){
this.myProp= 'hello'
//$refs is available
// this.myProp is reactive, bind will work to property
}
})
Use property binding if you want. :disabled prop is reactive in this case
<button :disabled="$refs.email ? $refs.email.$v.$invalid : true">Login</button>
But to check two fields i found no other way as dummy method:
<button :disabled="$refs.password ? checkIsValid($refs.email.$v.$invalid, $refs.password.$v.$invalid) : true">
{{data.submitButton.value}}
</button>
methods: {
checkIsValid(email, password) {
return email || password;
}
}
I was in a similar situation and I fixed it with:
data: () => {
return {
foo: null,
}, // data
And then you watch the variable:
watch: {
foo: function() {
if(this.$refs)
this.myVideo = this.$refs.webcam.$el;
return null;
},
} // watch
Notice the if that evaluates the existence of this.$refs and when it changes you get your data.
What I did is to store the references into a data property. Then, I populate this data attribute in mounted event.
data() {
return {
childComps: [] // reference to child comps
}
},
methods: {
// method to populate the data array
getChildComponent() {
var listComps = [];
if (this.$refs && this.$refs.childComps) {
this.$refs.childComps.forEach(comp => {
listComps.push(comp);
});
}
return this.childComps = listComps;
}
},
mounted() {
// Populates only when it is mounted
this.getChildComponent();
},
computed: {
propBasedOnComps() {
var total = 0;
// reference not to $refs but to data childComps array
this.childComps.forEach(comp => {
total += comp.compPropOrMethod;
});
return total;
}
}
Another approach is to avoid $refs completely and just subscribe to events from the child component.
It requires an explicit setter in the child component, but it is reactive and not dependent on mount timing.
Parent component:
<script>
{
data() {
return {
childFoo: null,
}
}
}
</script>
<template>
<div>
<Child #foo="childFoo = $event" />
<!-- reacts to the child foo property -->
{{ childFoo }}
</div>
</template>
Child component:
{
data() {
const data = {
foo: null,
}
this.$emit('foo', data)
return data
},
emits: ['foo'],
methods: {
setFoo(foo) {
this.foo = foo
this.$emit('foo', foo)
}
}
}
<!-- template that calls setFoo e.g. on click -->
In VueJS 2 I am trying to create a component that gets and passes data back to the parent which then passes it to another component to display.
The component that gets the data has a user input field it uses to search. When I have it pass data back to the parent using $emit the value in the input keeps being wiped.
I am receiving the below mutation error but I haven't directly tried to change the userSearch field in the component so I am not sure why.
"Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "userSearch" (found in PersonField)"
Relevant html
<person-field v-on:event_child="eventChild"></person-field>
<person-search :prop="personListArray" ></person-search>
Parent app
var app = new Vue({
el: '#app',
data: {
personListArray : [],
tempArray: []
},
methods: {
eventChild: function (arr) {
this.personListArray = arr
}
}
})
Component 1, displays a user input. Uses the input to search and bring back data. Starts search when the length of the input is more then 2. As soon as you hit the 3rd character something is causing the input to clear which I don't want.
Vue.component('person-field', {
props: ['userSearch'],
template: '<input class="form-control" v-model="userSearch" >',
watch: {
userSearch: function () {
var arr = []
if (typeof this.userSearch !== 'undefined') { //added this because once i passed 3 characters in the field the userSearch variable becomes undefined
if (this.userSearch.length > 2) {
$.each(this.getUsers(this.userSearch), function (index, value) {
var obj = {
Title: value.Title,
ID: value.ID
}
arr.push(obj)
});
this.$emit('event_child', arr) //emits the array back to parent "eventChild" method
} else {
console.log('no length')
}
} else {
console.log('cant find field')
}
},
},
methods: {
getUsers: function (filter) {
//gets and returns an array using the filter as a search
return arr
},
}
});
Component 2 - based on the personListArray which is passed as a prop, displays the results as a list (this works)
Vue.component('person-search', {
props: ['prop'],
template: '<ul id="personList">' +
'<personli :ID="person.ID" v-for="person in persons">' +
'<a class="" href="#" v-on:click="fieldManagerTest(person.Title, person.ID)">{{person.Title}}</a>' +
'</personli></ul>',
computed: {
persons: function () {
return this.prop
}
},
methods: {
fieldManagerTest: function (title, ID) { //Remove item from users cart triggered via click of remove item button
//var user = ID + ';#' + title
//this.internalValue = true
//this.$emit('fieldManagerTest');
//this.$parent.$options.methods.selectManager(user)
},
},
});
Component 3, part of component 2
Vue.component('personli', {
props: ['ID'],
template: '<transition name="fade"><li class="moving-item" id="ID"><slot></slot></li></transition>'
})
;
The reason you get the warning,
Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders. Instead, use a data or
computed property based on the prop's value. Prop being mutated:
"userSearch" (found in PersonField)
Is because of this line
<input class="form-control" v-model="userSearch" >
v-model will attempt to change the value of the expression you've told it to, which in this case is userSearch, which is a property.
Instead, you might copy userSearch into a local variable.
Vue.component('person-field', {
props: ['userSearch'],
data(){
return {
searchValue: this.userSearch
}
},
template: '<input class="form-control" v-model="searchValue" >',
...
})
And modify your watch to use searchValue.
Here is an example.