Vuejs modal component cannot reference method - vue.js

I have a modal dialog which is trying to use a method on the Vue app instance but getting the error
app.js:32117 [Vue warn]: Property or method "calcFees" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
The app declaration
Vue.component('sale', require('./components/Sale.vue'));
const app = new Vue({
el: '#app',
data: {
showModal: false
},
methods: {
calcFees: function (event) {
alert('GOOD');
}
}
});
Sale.vue component minimised for now
<template name="sale">
<input type="text" placeholder="Sale Price" class="form-control" #blur="calcFees">
</template>
The sale component is simply included in the main page here
<sale v-if="showModal"></sale>
The modal dialog works fine, displays the above text input however the above error is shown in the console and the blur event doesn't call the method.
It seems it has something to do with the component template, because i tested the blur event successfully by putting a text input in the main blade page directly.
Any ideas why it doesn't work in this way? I saw a comment somewhere about something to do with it being a <template> but it didn't explain how to fix.

Components cannot access methods declared in other components or the root Vue directly.
The problem with this code is that the calcFees method is declared in the root Vue, but you are trying to call it from the Sale.vue component.
There are several ways to make this work. One is you can move calcFees to the component. Another is you can $emit an event to the parent with whatever it needs to use in calcFees.
Sale.vue
<template name="sale">
<input type="text" v-model="price" placeholder="Sale Price" class="form-control" #blur="onSale">
</template>
<script>
export default {
data(){
return {
price: null
}
},
methods: {
onSale(){
this.$emit('sale', this.price)
}
}
}
</script>
Vue
<sale v-if="showModal" #sale="calcFees"></sale>
const app = new Vue({
el: '#app',
data: {
showModal: false
},
methods: {
calcFees: function (price) {
alert(price);
}
}
});

Related

How to call a component method each time this component show in vue2?

Vue component hasn't onShow lifecycle method.
How to call a component method each time this component show in vue2?
I have used this library for that purpose before:
https://github.com/Akryum/vue-observe-visibility
I am assuming you are dynamically hiding and showing the component and you want to trigger few functionality every time component show. If Yes, You can simply achieve this requirement by using v-if directive instead of v-show. As v-if will rebuild the component every time condition got passed.
Live Demo :
Vue.component('child', {
props: ['childmsg'],
template: '<p>{{ childmsg }}</p>',
mounted() {
console.log('child component mounted');
}
});
var app = new Vue({
el: '#app',
data: {
buttonMsg: 'Show Child Component',
showChild: false
},
methods: {
toggleBtn() {
this.showChild = !this.showChild;
this.buttonMsg = this.showChild ? 'Hide Child Component' : 'Show Child Component';
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="toggleBtn">{{ buttonMsg }}</button>
<child v-if="showChild" childmsg="This is a child message"/>
</div>

Bind click from instance instead of html tag

In vue is possible to bind button click directly from vue instance?
I have this button:
<el-button #click="alert_me" class="gf-button" type="primary" style="margin-left: 16px;">Button</el-button>
I wan't to remove #click="alert_me" and do like i would normally do with jquery but with vue.
Is it possible?
My Vue Instance:
new Vue({
el: "#app",
data: {
},
methods: {
alert_me() {
alert('Hello from vue!');
}
},
});
Thanks
If you need to attach a click event listener programmatically, it is possible with the classic javascript api:
<template>
<el-button class="gf-button" type="primary">Button</el-button>
</template>
<script>
export default {
mounted () {
// jquery would also work if it's installed.
document.getElementByClassName('gf-button').addEventListener('click', this.alert_me)
},
methods: {
alert_me() {
console.log('alert')
}
}
}
</script>
You could avoid the manual element query from the document with the Vue $refs object.
<template>
<el-button ref="myButton" class="gf-button" type="primary">Button</el-button>
</template>
<script>
export default {
mounted () {
this.$refs.myButton.addEventListener('click', this.alert_me)
},
methods: {
alert_me() {
console.log('alert')
}
}
}
</script>
But if you need that event as soon as the Vue component is created, I wouldn't recommend doing this. It kinda oversee the shadow dom optimisation of Vue.
The #click="" syntax provided is the best way to attach a click listener to an html element.
You can make use of addEventListener and call it in mounted life cycle.
mounted() {
document.querySelector('#element').addEventListener('click', event =>
{
//handle click
}
)
}

How to display stub component when component is not found in vue

I am trying to catch situation, when component is not found, ie:
{
template: '<some-unknown-component></some-unknown-component>'
}
At that moment, Vue warns us with unknown custom element: <some-unknown-component>...
I would like to step in when some-unknown-component is not found and then use another component instead, like stub-component:
{
name: 'stub-component',
props: ['componentName'],
template: '<p>component ${componentName} does not exists, click here to create...</p>'
}
UPDATE: I am looking for solution without changing the template itself, so no v-if and component added.
Vue exposes a global error and warning handler. I managed to get a working solution by using the global warnHandler. I don't know if it is exactly what you are looking for, but it may be a good starting point. See the working snippet (I think it is quite self explanatory).
Vue.config.warnHandler = function (err, vm, info) {
if (err.includes("Unknown custom element:")) {
let componentName = err.match(/<.*>/g)[0].slice(1, -1)
vm.$options.components[componentName] = Vue.component('stub-component', {
props: ['componentName'],
template: `<p>component "${componentName}" does not exists, click here to create...</p>`,
});
vm.$forceUpdate()
} else {
console.warn(err)
}
};
new Vue({
el: '#app',
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<unknown-component></unknown-component>
</div>
Vue stores the details of all the registered components in the $options.component property of the Vue instance.
So, you can check for the component availability using this.$options.component and if the component is present then load the component otherwise load the other component.
In the below example, suppose you have two different components and you want to load them on the availability, then you can create a computed property on the basis of it, load the component as needed.
var CustomComponent = Vue.extend({ template: '<h2>A custom Component</h2>' });
var AnotherComponent = Vue.extend({ template: '<h2>Custom component does not exist.</h2>' });
new Vue({
el: "#app",
components: {
CustomComponent,
AnotherComponent
},
computed: {
componentAvailable () {
return this.$options.components.CustomComponent
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-if="componentAvailable">
<custom-component />
</div>
<div v-else>
<another-component />
</div>
</div>

How can I access the value of a nested vue.js component?

I have a component called lbcontainer and a second component called lbitem.
Now I want to nest any number of lbitem components into one lbcontainer component.
The lbcontainer component has a method that should access all lbitem components that I have nested in the lbcontainer component.
Problem is: with ref I can get to the item via this.$ref.lbitem, but this only works in the component declaration, but not when I use the component later in HTML.
Vue.component('lbcontainer', {
methods: {
"showChildren": function() {
console.log(this.$refs);
}
},
template: `
<div>
<slot></slot>
<a #click='showChildren'>Show children</a>
</div>
`
});
Vue.component('lbitem', {
data: function() {
return {
value: ""
}
},
template: `
<input v-model="value"></span>
`
});
new Vue({
el: "#app",
data: {
},
methods: {
}
});
<div id="app">
<lbcontainer>
<lbitem ref="item"></lbitem>
<lbitem ref="item"></lbitem>
</lbcontainer>
</div>
When I press the button the console.log shows an empty object. How can I access the nested children?
Here is jsfiddle
Avoiding using $ref in vue ...
( $ref is populated after the first render...)
child can only communicate with their parent by event ...
or parent have all data and send to their child by props.
if parent and chidrend have to edit the data you can use v-model or .sync;
https://codesandbox.io/s/p95x1mxykm

Show string in component

I am trying to learn Vue.js, and am playing around with the modal component example. I'm trying to change it up a bit so that the button clicked can provide data to the component (I'm very new to this so my terminology may be off).
I've updated the app to be:
// start app
var app = new Vue({
el: '#app',
data: {
showModal: false,
title: 'Default Title'
},
methods: {
modalInit: function(title) {
//this.title = title;
this.showModal = true;
}
}
})
The updates were mainly so that I can change the title within the modal based on the button clicked, here is the update to the button:
<button id="show-modal"#click="modalInit('A title')">Show Modal</button>
The relevant portion of the x-template:
<div class="modal-header">
<h2>{{ title }}</h2>
</div>
Not sure if it matters, but the component is:
Vue.component('modal', {
template: '#modal-template',
})
In this state, the modal will open fine but the title won't be there and I get the console error: [Vue warn]: Property or method "title" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
I can't figure out to to properly "declare reactive data properties in the data option".
Thanks!
You've specified a title property for the root component. But, the modal component does not have a title property. Add it like this:
Vue.component('modal', {
template: '#modal-template',
data() {
return {
title: "Default Title"
}
}
})
If you want to pass in a dynamic value for title, make it a property instead:
Vue.component('modal', {
template: '#modal-template',
props: ['title']
})
Then, you can pass the value for the title in the component tag:
<modal :title="dynamicTitle"></modal>