Here are my components. I've successfully emitted parameters from the child component to the parent, but not from the parent to the child. The console.log('In initDetail()'); line fires, which means the $on event is triggered, but param is undefined. I'm wondering why.
Parent component:
components: {
"child-component": ChildComponent
}
methods: {
loadDetail() {
this.$emit('loadDetailEvent', 'test');
}
}
Child Component:
mounted() {
this.$on('loadDetailEvent', this.initDetail(param));
}
methods: {
initDetail(param) {
console.log('In initDetail()');
console.log(param);
}
}
I've also tried calling a function right away. Neither the console.log('$on event'); nor the console.log(param); lines print.
this.$on('loadDetailEvent', function(param){
console.log('$on event');
console.log(param);
});
As pointed out in the comments, there are a couple misconceptions in your code:
Your parent component is emitting a loadDetailEvent event when the loadDetail method is called. Your child component is also listening for a loadDetailEvent event, but it will only handle this event if it is emitted by its own Vue instance. If the Vue instance of the parent emits an event, the child will have no knowledge of it.
You are attempting to handle the loadDetailEvent by firing initDetail and passing the parameters of the event to that method. But, in your code you are simply setting the loadDetailEvent handler to be the result of calling this.initDetail(param) within the scope of the mounted hook. What you would want to do in this case would be to specify an anonymous function as the handler, which receives the param value and passes it to this.initDetail:
this.$on('loadDetailEvent', (param) => this.initDetail(param))
But, to get at the root of your issue: it seems like you want to call a child method when a parent method is called. You could do this a few different ways:
As #Ohgodwhy suggested, you could create a separate, global Vue instance to use as an event bus.
You could set a ref (say ref="child") on the child component tag in your parent's template and then call the child component's method directly via this.$refs.child.initDetail('test').
As #B.Fleming suggested, you could set a watcher on the child component to react to a changing prop value passed by the parent component. This gets a little tricky since you would need to maintain the value of the variable being passed as the prop (I would use a .sync modifier, as you can see below).
Here are example snippets of the above solutions:
Using an event bus:
let EventBus = new Vue();
Vue.component('child', {
template: `<div>Child</div>`,
mounted() {
EventBus.$on('loadDetailEvent', (param) => this.initDetail(param));
},
methods: {
initDetail(param) {
console.log('In initDetail()');
console.log(param);
}
}
});
new Vue({
el: '#app',
methods: {
loadDetail() {
EventBus.$emit('loadDetailEvent', 'test');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<child></child>
<button #click="loadDetail">load</button>
</div>
Using a ref:
Vue.component('child', {
template: `<div>Child</div>`,
methods: {
initDetail(param) {
console.log('In initDetail()');
console.log(param);
}
}
});
new Vue({
el: '#app',
methods: {
loadDetail() {
this.$refs.child.initDetail('test');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<child ref="child"></child>
<button #click="loadDetail">load</button>
</div>
Using a prop value and a watcher:
Vue.component('child', {
template: `<div>Child</div>`,
props: { loading: String },
methods: {
initDetail(param) {
console.log('In initDetail()');
console.log(param);
}
},
watch: {
loading(val) {
if (val) {
this.initDetail(val);
this.$emit('update:loading', '');
}
}
}
});
new Vue({
el: '#app',
data() {
return { payload: '' }
},
methods: {
loadDetail() {
this.payload = 'test';
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<child :loading.sync="payload"></child>
<button #click="loadDetail">load</button>
</div>
Related
I've a hard time in understanding the methods of vue. In my put-request users can edit, delete images. In parent component the get-request loads the images and the are pushed to an image-gallery (the child-component) via properties. In my set up the console.log is always empty.
//PARENT COMPONENT
<template>
<div class="form-group">
<image-gallery :serverData="serverMap"/>
</div>
</template>
<script>
import ImageGallery from './ImageGallery.vue';
export default {
components:{ImageGallery},
data: () => ({
serverMap: {
title: '',
file: ''
}
}),
mounted () {
//AJAX ETC get servermap
.then((response) => {
this.serverMap = response.data
})
}
Just a normal straight parent-child situation. Here under the child-component
<template>
</template>
<script>
export default {
name: 'ImageGallery',
//incoming data
props: {
serverData: {
type: Object,
default () {
return {
hasLabels: true,
isHorizontal: false
}
}
}
},
created: function () {
this.loadImages()
},
methods: {
loadImages () {
console.log(this.serverData.file)
//do something with the serverData
//prepare for fileReader function
//together with new image validation
}
}
The method 'loadImages' should be automatically delevering the serverData via computed.But is doesn t. Who can help?
There is race condition.
Either not render a child until data is available; serverMap needs to be null instead of empty object in order to be distinguished from populated object:
<image-gallery v-if="serverMap" :serverData="serverMap"/>
Or delay data access in a child until it's available instead of doing this immediately in created:
watch: {
serverData(data) {
if (data)
this.loadImages()
}
}
I am using Laravel 7 and Vue.js 2.
I want to pass an object of validation errors from the catch block of an axios call to a parent component but for some reasons it doesn't work.
This is the code of the axios call:
runReport: function() {
let self = this;
const url = "api/get_report?room="+this.formReport['room']+"&participant="+this.formReport['participant']+"&start="+this.formReport['start']+"&end="+this.formReport['end'];
axios.get(url)
.then((response) => {
console.log(response.data.data);
this.meetingsReport = response.data.data;
this.$emit('passMeetings', this.meetingsReport);
this.$emit('success');
this.errors = {};
})
.catch(function(error) {
console.log(error.response.data);
self.errors = error.response.data;
alert(self.errors);
self.$emit('failure');
self.$emit('passErrors', self.errors); //problem
console.log('call ended');
});
}
This is the code in the parent component:
<template>
<div>
<report-meeting #passMeetings="onPassMeetings" #failure="displayTable=false" #success="displayTable=true"></report-meeting>
<hr>
<validated-errors :errorsMeeting="errorsMeeting" #passErrors="onPassErrors" v-if="displayTable===false"></validated-errors>
<table-report :meetingsSelected="meetingsSelected" v-if="displayTable===true"></table-report>
</div>
</template>
<script>
import TableReport from "./TableReport.vue"
import ReportMeeting from "./ReportMeeting.vue"
import ValidatedErrors from "./ValidatedErrors.vue"
export default {
components: {
'table-report': TableReport,
'report-meeting': ReportMeeting,
'validated-errors': ValidatedErrors
},
mounted() {
console.log('Component mounted.');
},
data: function() {
return {
displayTable: false,
meetingsSelected: {},
errorsMeeting: {}
}
},
methods: {
onPassMeetings(value) {
console.log(value);
this.meetingsSelected = value;
},
onPassErrors(value) {
console.log('errors passed'); //never used
this.errorsMeeting = value;
}
}
}
</script>
In the console I visualize no errors (except an 422 Unprocessable Entity). The strange thing is that the first emit works (failure), but the second one doesn't work (passErrors).
In the parent function onPassErrors I put a console.log that is never used so I suppose that the function is never called.
Can help?
This is likely caused by an event name mismatch, which can occur when using in-DOM templates because HTML attributes are automatically lower-cased (#passErrors becomes #passerrors in the DOM).
When using the development build of Vue, you'd see a warning in the browser's console:
[Vue tip]: Event "passerrors" is emitted in component but the handler is registered for "passErrors". Note that HTML attributes are case-insensitive and you cannot use v-on to listen to camelCase events when using in-DOM templates. You should probably use "pass-errors" instead of "passErrors".
This is not a problem in single file components (demo 1) or string templates (demo 2), but if you must stick with in-DOM templates, custom event names are recommended to be kebab-case:
<!-- Parent.vue -->
<MyComponent #pass-errors="onPassEvent" />
// MyComponent.vue
runReport() {
this.$emit('pass-errors', /*...*/)
}
demo 3
Would like some assistance on this error I am getting in Vue: Property or method "type" is not defined on the instance but referenced during render.
Vue code:
Vue.component('content-text-area', {
data() {
return {
type: document.getElementById('type').value
}
},
mounted: function () {
console.log(this.type)
},
template: '<p>Testing</p>'
})
new Vue({ el: '#content-text-area-div' });
Blade code:
<div id="content-text-area-div">
<content-text-area v-if="type === 'article'"></content-text-area>
</div>
There is 2 issues to fix here:
type should be defined in the parent component:
new Vue({
el: '#content-text-area-div',
data() {
type: '' // Initialize it here
}
});
You can't refrence the DOM in data, as it is instantiated before the DOM is built. So you should read and assign the value in the mounted hook.
See the Vue lifecycle.
mounted() {
this.type = document.getElementById('type').value
}
Or even better (the "Vue way"):
<input ref="type"/>
mounted() {
this.type = this.$refs.type.value;
}
See also: Refs
I'm working with 3 VUE nested components (main, parent and child) and I'm getting trouble passing data.
The main component useget a simple API data based on input request: the result is used to get other info in other component.
For example first API return the regione "DE", the first component is populated then try to get the "recipes" from region "DE" but something goes wrong: The debug comments in console are in bad order and the variable used results empty in the second request (step3):
app.js:2878 Step_1: DE
app.js:3114 Step_3: 0
app.js:2890 Step_2: DE
This is the parent (included in main component) code:
parent template:
<template>
<div>
<recipes :region="region"/>
</div>
</template>
parent code:
data: function () {
return {
region: null,
}
},
beforeRouteEnter(to, from, next) {
getData(to.params.e_title, (err, data) => {
console.log("Step_1: "+data.region); // return Step_1: DE
// here I ned to update the region value to "DE"
next(vm => vm.setRegionData(err, data));
});
},
methods: {
setRegionData(err, data) {
if (err) {
this.error = err.toString();
} else {
console.log("Step_2: " + data.region); // return DE
this.region = data.region;
}
}
},
child template:
<template>
<div v-if="recipes" class="content">
<div class="row">
<recipe-comp v-for="(recipe, index) in recipes" :key="index" :title="recipe.title" :vote="recipe.votes">
</recipe-comp>
</div>
</div>
</template>
child code:
props: ['region'],
....
beforeMount () {
console.log("Step_3 "+this.region); // Return null!!
this.fetchData()
},
The issue should be into parent beforeRouteEnter hook I think.
Important debug notes:
1) It looks like the child code works properly because if I replace the default value in parent data to 'IT' instead of null the child component returns the correct recipes from second API request. This confirms the default data is updated too late and not when it got results from first API request.
data: function () {
return {
region: 'IT',
}
},
2) If I use {{region}} in child template it shows the correct (and updated) data: 'DE'!
I need fresh eyes to fix it. Can you help me?
Instead of using the beforeMount hook inside of the child component, you should be able to accomplish this using the watch property. I believe this is happening because the beforeMount hook is fired before the parent is able to set that property.
More on the Vue lifecycle can be found here
More on the beforeMount lifecycle hook can be found here
In short, you can try changing this:
props: ['region'],
....
beforeMount () {
console.log("Step_3 "+this.region); // Return null!!
this.fetchData()
},
To something like this:
props: ['region'],
....
watch: {
region() {
console.log("Step_3 "+this.region); // Return null!!
this.fetchData()
}
},
Cheers!!
My code is to realize a paginate page like this example, https://github.com/bitovi/canjs/blob/master/component/examples/paginate.html .
I found the {#messages}...{/messages} in message.mustache template was not been inserted into page , while messagelist component inserted event has been triggered, so i can not do any binding to {#messages} dom in the event, because it ‘not exists in the page.
Are there other ways to fix this problem?
The Templates:
message_list.mustache:
<app>
<messagelist deferredData='messagesDeferred'></messagelist>
<next-prev paginate='paginate'></next-prev>
<page-count page='paginate.page' count='paginate.pageCount'></page-count>
</app>
message.mustache:
{#messages}}
<dl>
<dt>.....</dt>
<dd>....</dd>
</dl>
{/messages}
The Component:
can.Component.extend({
tag: "messagelist",
template: can.view('/static/web/tpl/mobile/message.mustache'), // to load message template
scope: {
messages: [],
waiting: true,
},
events: {
init: function () {
this.update();
},
"{scope} deferreddata": "update",
update: function () {
var deferred = this.scope.attr('deferreddata'),
scope = this.scope,
el = this.element;
if (can.isDeferred(deferred)) {
this.scope.attr("waiting", true);
deferred.then(function (messages) {
scope.attr('messages').replace(messages);
});
} else {
scope.attr('messages').attr(deferred, true);
}
},
"{messages} change": function () {
this.scope.attr("waiting", false);
},
inserted: function(){
// can't operate the dom in message.mustache template
}
}
});
//to load component template
can.view("/static/web/tpl/mobile/message_list.mustache",{}, function(content){
$("#message-list").html(content)
});
I have solved the problem, but not the best, Maybe someone have a better way.
I changed my template, add a new component called <messageitem>
<messageitem> will load another template: message.mustache
Every <messageitem> will trigger inserted event when inserted into <messagelist>
The new component:
can.Component.extend({
tag: "messageitem",
template:can.view('/static/web/tpl/mobile/message.mustache'),
events: {
inserted: function(el, ev){
// Can-click can not satisfy my needs,
// because i call the third-party module to bind click event
// this module will be called repeatedly, not the best way
reloadModules(['accordion']);
}
}
});
// To load message_list.mustache
can.view("/static/web/tpl/mobile/message_list.mustache",{}, function(content){
$("#message-list").html(content)});
Static html:
<body>
<div id="message-list">
....
</div>
</body>
message_list.mustache:
<app>
<messagelist deferredData='messagesDeferred'>
{{#messages}}
<messageitem></messageitem>
{{/messages}}
</messagelist>
<next-prev paginate='paginate'></next-prev>
<page-count page='paginate.page' count='paginate.pageCount'></page-count>
</app>
message.mustache:
<dl class="am-accordion-item" >
...
</dl>