Passing through events and props to/from a root element - vue.js

I'm trying to create a re-usable button. Is there any way to pass props and events down to the button itself without having to account for all the different possibilities?
For example, if I set a type on the <new-button> component, it passes the type to the <button> without me having to add a 'type' prop to the <new-button> component? Same goes for events, can a click event trigger from the button itself?
The component template itself is fairly simple and the button is the root component:
<template>
<button :disabled="submitting">
<i class="fa fa-spinner fa-spin" v-show="submitting"></i>
<i v-if="icon" :class="icon" v-show="!submitting"></i>
<slot></slot>
</button>
</template>
I do know that the class attribute passes through, but I don't see any way of passing through anything else.

How to pass all props down
If you want to pass all props down to an element without having to specify each of the props in particular, the way to achieve it is to pass the $props object (containing all the props passed to the component) using v-bind.
parent:
<custom-button :prop1="alice" :prop2="has" :prop3="a" :prop4="cat">
child:
<button v-bind="$props">
export default {
props: ['prop1', 'prop2', 'prop3', 'prop4']
}
How to prevent declaring all props
As you see, we still have to declare all the props in the props option of the component though. Luckily there's a workaround for that. Let's just pass an object of props:
parent:
<custom-button :props="{ prop1, prop2, prop3, prop4 }">
child:
<button v-bind="props">
export default {
props: ['props']
}
How to listen to native events
Finally for emitting events, you can register listeners for native Javascript events, such as click or input which bubble from HTML elements.
parent:
<custom-button #click.native="doStuff">
child:
<button>

Related

How to use v-if on a child component to be reactive to the parents attributes?

I have a Child component, which displays a warning message if the wrong dates from an Input are selected. This warning should only be possible in the Parent Component, because the Child is re-used throughout my application.
I want to add a v-if to the warning notification, that it can only appear if it's rendered in that exact Parent Component, and not anywhere else the component is re-used.
I have tried with V-If, tied to the component name , but that has not solved the issue.
What is the best way to make the child component register that it has been rendered in ParentComponent?
<ChildComponent>
<div>
<b-datepicker
class="datepicker-input"
/>
<b-notification
v-if="this.componentName === 'parentComponent" >
'WARNING, WRONG DATES SELECTED'
</b-notification>
</div>
<ParentComponent>
<div>
<ChildComponent>
</ChildComponent>
</div>
data() {
return {
componentName: this.$options.name,
</ParentComponent>

What is the best way to pass data from a dropdown list to a prop?

I'm attempting to pass the selected value from my dropdown list, to my props, but being new at Vue I'm lost as to how to get this my selected value to pass to my prop, I've attempted many different methods but it remains an empty string. What am I missing here?
<template>
<FormLayout>
<div class="columns">
<div class="column is-4">
<FormField :for="ff.slug" />
<FormField :for="ff.name" />
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
export default {
fullScreen: true,
name: 'CcRequestForm',
mixins: [BaseForm],
props: {
selected: '',
modelName: {
default: 'CcRequest',
}
},
computed: {
...mapGetters(['ccTypesForRequests', 'currentRequesterSlug', 'currentCcRequest']), ccTypesCollection() {
return this.ccTypesForRequests.map((x)=>[x.slug, this.t(`cc_types.${x.slug}`)]);
}
},
There are a few points that you should consider and implement in your code in order to fix your component.
When you're defining props in a child component, you should define its type, using javascript types (String, Number, Boolean, Array, Object). So writing selected: '' is not the correct way. Use selected: String instead. Read the Vue documentation for more detailed information:
Prop Types
Prop definitions
You should know and consider that mutating a prop in the child component is an anti-pattern, as their documentation says:
All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around. This prevents child components from accidentally mutating the parent’s state, which can make your app’s data flow harder to understand.
So in your child component: use a computed property as your select box value, and emit an event whenever it changes. And in your parent component: set a function on selectComponent event emitting, and change the value of selected state there. Check the below links for further reading:
One-Way Data Flow
Implicit parent-child communication
Vue 2 - Mutating props vue-warn
I've also implemented the correct code for your better understanding. Open and check the console when you're changing the dropdown list: https://codesandbox.io/s/vue2-v-model-og0fd?file=/src/App.vue

I am using tiptap . How to make a preview from child component to parent component

Below is my code.
parent.vue
<client-only>
<EditorTiptap v-model="content" />
</client-only>
tiptap.vue
<div class="editor-panel">
<editor-content :editor="editor" />
</div>
and
computed: {
html () {
if (!this.editor) return ''
return this.editor.getHTML()
},
}
In such a structure, how can I send the content input value of the child component to the parent component?
In order to implement the preview function in the parent component, the content of the child component must be fetched in real time.
I couldn't find a way to do anything with the html of computed .
Please tell me how to implement editor input of child component (tiptap.vue) as preview in parent component.
You could add a watcher to your html computed property to $emit its content to parent.
watch: {
html: function (val) {
this.$emit('onHtmlChanged', html)
},
}
Then your parent can listen to #onHtmlChanged :
<client-only>
<EditorTiptap v-model="content" #onHtmlChanged="doSomething" />
</client-only>
NOTE : It might be recommended that you debounce your event to prevent emitting a change at each keystroke

Having a button on a child component run a function on the parent

I am currently stuck with a simple-looking issue but cannot find the wording.technicality behind solving it.
Essentially, I have a child component "project card" this card is rendered on my home page using
<div v-for="(project, index) in projects" :key="index">
(pulled from an array called "projects" which contain objects).
Anyway, my issue is, I have a button on the "project card" component, and clicking on that I would like to run a function on the "home" page. (as it seems to make sense to control the array from there).
But, how do I do that? How do i make a button pressed on a child component fire a function on the parent?
You need this in your child component:
this.$emit('myEvent')
Then you can do on your parent component:
<my-parent-component v-on:myEvent="doSomething"></my-parent-component>
You can read about firing events at https://v2.vuejs.org/v2/guide/components-custom-events.html . If this is the simple case then using event emiting is fine. But in another cases the better decision is using vuex.
emit the event and register it in the child component using the "emits" property. Listen to the emitted event at the parent element where you have created the child instance and point the event listener to the function you want to execute.
For Eg.
Parent
<template>
<child #close="closeIt"></child>
</template>
<script>
export default {
methods: {
closeIt() {
//DO SOMETHING...
}
}
}
</script>
Child
<template>
<button #click="closeInParent">Close</button>
</template>
<script>
export default {
emits: ["close"],
methods: {
closeInParent() {
this.$emit('close')
}
}
}
</script>

Why v-on doen't work on Vue component while using it in outer template?

The situation
Let's say I have a component BaseButton which is just a simple wrapper arround a <button> element:
Vue.component('base-button', {
template: `
<button>
<slot />
</button>
`
});
When using the button, I'll want to bind handler to a click event on this button:
<base-button #click="handler">
Click me
</base-button>
Pen with above code
The problem
The above solution doesn't work. The handler is not fired at all.
Can someone explain me why exactly? I'm guessing the handler is bound to the element, before it gets replaced with the component template, but it's just a guess - I can't find anything about it in vue.js docs. In addition to that docs
state:
A non-prop attribute is an attribute that is passed to a component,
but does not have a corresponding prop defined.
While explicitly defined props are preferred for passing information
to a child component, authors of component libraries can’t always
foresee the contexts in which their components might be used. That’s
why components can accept arbitrary attributes, which are added to the
component’s root element.
#click (v-on:click) seems to me to be a non-prop attribute and according to the above text should get inherited. But it's not.
Prop solution
I know I can declare a prop and pass the handler inside the component (code below). Then it works as expected.
The problem with this solution for me is that I don't have a fine grain control over how the handler is declared. What if in one usage of BaseButton I'd like to use on #click
some of the event modifiers Vue.js exposes (e.g. .stop, .prevent, .capture)? I'd have to use another prop (like capture) and use v-if, but it'd get the component template very messy. If I leave the handler in the template, where I use it, I can modify the event declaration as I want in a clean and flexible way.
Vue.component('base-button', {
prop: {
clickHandler: {
type: Function,
required: true
}
},
template: `
<button>
<slot />
</button>
`
});
<base-button :click-handler="handler">
Click me
</base-button>
The v-on directive behaves differently when used on a normal / native DOM element or on a Vue custom element component, as stated in the API docs:
When used on a normal element, it listens to native DOM events only. When used on a custom element component, it listens to custom events emitted on that child component.
In your case you apply it on your custom <base-button> element component, therefore it will listen only to custom event, i.e. ones that you explicitly $emit on this component instance.
Native "click" events bubbling phase from your underlying <button> will not trigger your #click listener…
…unless you use the .native modifier:
.native - listen for a native event on the root element of component.
Vue.component('base-button', {
template: `
<button>
<slot />
</button>
`
});
new Vue({
el: '#app',
data: {
handler(event) {
console.log('submit from where the component was used');
//console.log(event);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<base-button #click.native="handler">
Click
</base-button>
</div>