Call great grand children component method - javascript

I got a set of nested components as follows in Vue 2.2.1:
<Root>
<VForm>
<Accordion>
<Panel>
<Stripe ref="stripe">
And I need to call a method getToken on the Stripe component when the form is submitted. On my <VForm> component I have the following template.
<template>
<form :method="method" :action="action" :class="classes" :autocomplete="autocomplete" #submit.prevent="submit">
<slot></slot>
</form>
</template>
<script>
export default {
props: {
method: {
type: String,
default: 'POST'
},
action: {
required: true,
type: String
},
classes: {
type: String
},
autocomplete: {
type: String,
default: 'on'
}
},
methods: {
submit(){
this.$refs.stripe.getToken
}
}
}
</script>
But I get Uncaught TypeError: Cannot read property 'getToken' of undefined. I also tried it by emitting an event at the <v-form> level but, if i'm not mistaken, it is my understanding that the events flow from child to parent, so that didn't work.
How can I trigger stripe.getToken on <v-form> submit?

You could use a bus.
const bus = new Vue();
Vue.component("parent", {
methods:{
triggerStripe(){
bus.$emit('get-token');
}
}
})
Vue.component("stripe",{
mounted(){
bus.$on('get-token', () => this.getToken());
}
})

Related

Pass a props in the ":to" attribute of a router link in Vue.js

I'm having an issue to create a router-link in vue and passing it a route name as a props...
What I want to do basically is this :
<template>
<div>
<router-link :to="myProps">Login</router-link>
</div>
</template>
<script>
export default {
name: componentName,
props: {
myProps: {
type: String,
default: '{ name: login }'
}
}
}
</script>
Login is of course difined in my router. But as I give the route as a props, I get redirected to /%7B%20name:%20'login'%20%7D. Do you know of any trick to get around this ?
What you get from the default value is a string, but you need an object. Try in this way:
props: {
myProps: {
type: Object,
default: () => ({name: 'login'})
}
}
Here's more about props default value: https://v2.vuejs.org/v2/guide/components-props.html#Prop-Validation

How to auto close a customized vue toast component

I have a customized toast component:
<template>
<div v-show="isToastVisible" class="toast-wrapper">
<div class="toast-conatiner">
{{ message }}
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
props: {
message: {
type: String,
default: '',
},
isToastVisible: {
type: Boolean,
default: false,
},
},
};
</script>
I pass state to the property:
<toast :is-toast-visible="isToastVisible" message="please say the word again"/>
I have an action to control the value of isToastVisbible. When I want to toast some message , I set the state to true.
What I want is to make the toast auto closed in 3 seconds, it means that I have to call a setTimeout function when I set isToastVisbible to true.
commit('SET_TOAST_VISIBLILITY', true);
setTimeout(() => {
commit('SET_TOAST_VISIBLILITY', false);
}, 3000);
I try to add watch on isToastVisbible, but it only can works at first time.
<template>
<div v-show="localVisible" class="toast-wrapper">
<div class="toast-conatiner">
{{ message }}
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
props: {
message: {
type: String,
default: '',
},
isToastVisible: {
type: Boolean,
default: false,
},
},
data: () => ({
localVisible: false,
}),
watch: {
isToastVisible(v) {
if (v) {
this.localVisible = true;
setTimeout(() => {
this.localVisible = false;
}, 3000);
}
},
},
};
I am trying to do a better design on the component, but I don't know what is the best practice of this. Hope somebody could give me a hand, thanks a lot.
Try to use toast component with v-model directive. In toast you may use timer to emit hide event:
setTimeout(() => this.$emit('input'), 3000);
On the parent component you may use this one:
<toast-component v-model="isVisible"></toast-component>
Or without v-model sugar:
<toast-component :value="isVisible" #input="isVisible = false"></toast-component>

How can i access the errors computed property on custom input component validated using vee-validate?

I am trying to use VeeValidate in a custom input component.
I tried using $emit on #input and #blur but the validation occurs on next tick and i end up failing to catch the validation on event.
onEvent (event) {
console.log('error length', this.errors.items.length)
if (this.errors.items.length) {
this.hasError = true
this.$emit('has-error',this.errors.items)
} else {
this.hasError = false
this.$emit('on-input', event)
}
}
I also tried injecting the validator from the parent so as to be able to access the errors computed property directly but there might be 1-2-3 levels of nesting between the parent page and the custom input component itself. I would have to inject the validator through all of them and these component are meant to be reusable.
export default {
//in custom input component
inject: ['$validator'],
}
The best idea i got is to watch the errors computed property and emit an event when a change occurs whit the errors in that instance of the component.
watch: {
errors: function (errorsNew) {
console.log('errors watch',errorsNew)
}
},
The problem is that i can't seem to watch the errors computed property introduced by vee-validate.
Some simplified version of code:
parent
<template>
<div id="app">
<CustomInput
:label="'Lable1'"
:value="'value from store'"
:validations="'required|max:10|min:5'"
:name="'lable1'"
/>
<button>Save</button>
</div>
</template>
<script>
import CustomInput from "./components/CustomInput";
export default {
name: "App",
components: {
CustomInput
}
};
</script>
CustomInput
<template>
<div>
<label >{{ label }}</label>
<input :value="value" :name="name" v-validate="validations">
<span v-if="this.errors.items.length">{{this.errors.items[0].msg}}</span>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
label: {
type: String,
required: true
},
value: {
type: String,
default: ""
},
validations: {
type: String,
default: ""
},
name: {
type: String,
required: true
}
},
watch: {
errors: function(errorsNew) {
console.log("errors watch", errorsNew);
this.$emit('has-error')
}
}
};
</script>
Can someone please help me access the validation errors from the custom input?
Update
I created a simple fiddle if anyone finds it easier to test it https://codesandbox.io/s/mqj9y72xx
I think the best way to handle this is to inject $validator into the child component. That seems to be how the plugin recommends it be done: https://baianat.github.io/vee-validate/advanced/#injecting-parent-validator.
So, in your CustomInput, you'd add inject: ['$validator'],.
Then, in the App.vue, you can have this in the template:
<div>
These are the errors for "lable1" in App.vue:
<span v-if="errors.has('lable1')">{{errors.first('lable1')}}</span>
</div>
I think that's really it.
Working example based off your example: https://codesandbox.io/s/pw2334xl17
I realize that you've already considered this, but the inject method searches up the component tree until it finds a $validator instance, so perhaps you should disable automatic injection in your app at the root level, thus every component searching for a validator to inject will all get that one. You could do that using:
Vue.use(VeeValidate, { inject: false });

Passing default data to Vue form component

Here is a simple Vue 2.0 form component. It consists of a number input and a button, e.g.:
Note that the value of the input is tied to the component's data using v-model. buttonText is passed in as a prop.
What's the best way to pass a default value into the form, so that it initially renders with a value other than 10?
Using props doesn't seem to be the right way to do it because then v-model no longer works properly.
However, data can't be passed in the way props can, as far as I can tell from Vue documentation.
.
<template>
<form v-on:submit.prevent="onSubmit">
<input v-model="amount" type="number" min="1" max="20"></input>
<button type="submit">{{ buttonText }}</button>
</form>
</template>
<script>
export default {
props: [ 'buttonText' ],
data: function() {
return {
amount: 10
}
},
methods: {
onSubmit: function() {
this.$emit("submit", parseInt(this.amount) );
}
}
}
</script>
You can pass in a prop (say initialAmount) and reference that when initializing the amount value in the data function:
export default {
props: {
buttonText: { type: String },
initialAmount: { type: Number, default: 10 },
},
data: function() {
return {
amount: this.initialAmount
}
},
methods: {
onSubmit: function() {
this.$emit("submit", parseInt(this.amount) );
}
}
}

Vue.js Changing props

I'm a bit confused about how to change properties inside components, let's say I have the following component:
{
props: {
visible: {
type: Boolean,
default: true
}
},
methods: {
hide() {
this.visible = false;
}
}
}
Although it works, it would give the following 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: "visible"
(found in component )
Now I'm wondering what the best way to handle this is, obviously the visible property is passed in when created the component in the DOM: <Foo :visible="false"></Foo>
Referencing the code in your fiddle
Somehow, you should decide on one place for the state to live, not two. I don't know whether it's more appropriate to have it just in the Alert or just in it's parent for your use case, but you should pick one.
How to decide where state lives
Does the parent or any sibling component depend on the state?
Yes: Then it should be in the parent (or in some external state management)
No: Then it's easier to have it in the state of the component itself
Kinda both: See below
In some rare cases, you may want a combination. Perhaps you want to give both parent and child the ability to hide the child. Then you should have state in both parent and child (so you don't have to edit the child's props inside child).
For example, child can be visible if: visible && state_visible, where visible comes from props and reflects a value in the parent's state, and state_visible is from the child's state.
I'm not sure if this is the behavour that you want, but here is a snippet. I would kinda assume you actually want to just call the toggleAlert of the parent component when you click on the child.
var Alert = Vue.component('alert', {
template: `
<div class="alert" v-if="visible && state_visible">
Alert<br>
<span v-on:click="close">Close me</span>
</div>`,
props: {
visible: {
required: true,
type: Boolean,
default: false
}
},
data: function() {
return {
state_visible: true
};
},
methods: {
close() {
console.log('Clock this');
this.state_visible = false;
}
}
});
var demo = new Vue({
el: '#demo',
components: {
'alert': Alert
},
data: {
hasAlerts: false
},
methods: {
toggleAlert() {
this.hasAlerts = !this.hasAlerts
}
}
})
.alert {
background-color: #ff0000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo" v-cloak>
<alert :visible="hasAlerts"></alert>
<span v-on:click="toggleAlert">Toggle alerts</span>
</div>
According to the Vue.js component doc:
When the parent property updates, it will flow down to the child, but not the other way around. So, how do we communicate back to the parent when something happens? This is where Vue’s custom event system comes in.
Use $emit('my-event) from the child to send an event to the parent. Receive the event on the child declaration inside the parent with v-on:my-event (or #my-event).
Working example:
// child
Vue.component('child', {
template: '<div><p>Child</p> <button #click="hide">Hide</button></div>',
methods: {
hide () {
this.$emit('child-hide-event')
}
},
})
// parent
new Vue({
el: '#app',
data: {
childVisible: true
},
methods: {
childHide () {
this.childVisible = false
},
childShow () {
this.childVisible = true
}
}
})
.box {
border: solid 1px grey;
padding: 16px;
}
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<div id="app" class="box">
<p>Parent | childVisible: {{ childVisible }}</p>
<button #click="childHide">Hide</button>
<button #click="childShow">Show</button>
<p> </p>
<child #child-hide-event="childHide" v-if="childVisible" class="box"></child>
</div>
If the prop is only useful for this child component, give the child a prop like initialVisible, and a data like mutableVisible, and in the created hook (which is called when the component's data structure is assembled), simply this.mutableVisible = this.initialVisible.
If the prop is shared by other children of the parent component, you'll need to make it the parent's data to make it available for all children. Then in the child, this.$emit('visibleChanged', currentVisible) to notify the parent to change visible. In parent's template, use <ThatChild ... :visibleChanged="setVisible" ...>. Take a look at the guide: https://v2.vuejs.org/v2/guide/components.html
After a read of your latest comments it seems that you are concerned about having the logic to show/hide the alerts on the parent. Therefore I would suggest the following:
parent
# template
<alert :alert-visible="alertVisible"></alert>
# script
data () {
alertVisible: false,
...
},
...
Then on the child alert you would $watch the value of the prop and move all logic into the alert:
child (alert)
# script
data: {
visible: false,
...
},
methods: {
hide () {
this.visible = false
},
show () {
this.visible = true
},
...
},
props: [
'alertVisible',
],
watch: {
alertVisible () {
if (this.alertVisible && !this.visible) this.show()
else if (!this.alertVisible && this.visible) this.hide()
},
...
},
...
To help anybody, I was facing the same issue. I just changed my var that was inside v-model="" from props array to data. Remember the difference between props and data, im my case that was not a problem changing it, you should weight your decision.
E.g.:
<v-dialog v-model="dialog" fullscreen hide-overlay transition="dialog-bottom-transition">
Before:
export default {
data: function () {
return {
any-vars: false
}
},
props: {
dialog: false,
notifications: false,
sound: false,
widgets: false
},
methods: {
open: function () {
var vm = this;
vm.dialog = true;
}
}
}
After:
export default {
data: function () {
return {
dialog: false
}
},
props: {
notifications: false,
sound: false,
widgets: false
},
methods: {
open: function () {
var vm = this;
vm.dialog = true;
}
}
}
Maybe it looks like on hack and violates the concept of a single data source, but its work)
This solution is creating local proxy variable and inherit data from props. Next work with proxy variable.
Vue.component("vote", {
data: function() {
return {
like_: this.like,
dislike_: this.dislike,
}
},
props: {
like: {
type: [String, Number],
default: 0
},
dislike: {
type: [String, Number],
default: 0
},
item: {
type: Object
}
},
template: '<div class="tm-voteing"><span class="tm-vote tm-vote-like" #click="onVote(item, \'like\')"><span class="fa tm-icon"></span><span class="tm-vote-count">{{like_}}</span></span><span class="tm-vote tm-vote-dislike" #click="onVote(item, \'dislike\')"><span class="fa tm-icon"></span><span class="tm-vote-count">{{dislike_}}</span></span></div>',
methods: {
onVote: function(data, action) {
var $this = this;
// instead of jquery ajax can be axios or vue-resource
$.ajax({
method: "POST",
url: "/api/vote/vote",
data: {id: data.id, action: action},
success: function(response) {
if(response.status === "insert") {
$this[action + "_"] = Number($this[action + "_"]) + 1;
} else {
$this[action + "_"] = Number($this[action + "_"]) - 1;
}
},
error: function(response) {
console.error(response);
}
});
}
}
});
use component and pass props
<vote :like="item.vote_like" :dislike="item.vote_dislike" :item="item"></vote>
I wonder why it is missed by others when the warning has a hint
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: "visible" (found in component )
Try creating a computed property out of the prop received in the child component as
computed: {
isVisible => this.visible
}
And use this computed in your child component as well as to emit the changes to your parent.

Categories