Passing v-model into a checkbox inside a Component in Vue 3? - vue.js

I want to embed a checkbox inside a Vue3 Component and have the v-model binding passed down to the checkbox.
Inside the Component:
<!-- Tile.vue -->
<template>
<div>
<input type=checkbox v-model="$attrs">
</div>
</template>
<script>
export default {inheritAttrs: false}
</script>
Then in an outside file:
<template>
<Tile value="carrot" v-model="foods" />
<Tile value="tomatoes" v-model="foods" />
</template>
<script setup>
var foods = ref([]);
</script>
How do I achieve this?
The documentation says that v-model is just a shorthand for :modelValue and #update:modelValue but this is not universal as Vue obviously behaves differently for form elements such as smartly listening to onchange instead of oninput and modifying the property checked instead of value depending on the node.
If I use v-model on the outer component, how do I forward it to the checkbox and get the same smart behavior that Vue has?

I have found tons of controversial information. Some recommend using #input event (Vue 3 custom checkbox component with v-model and array of items). Some recommend emitting modelValue:update instead of update:modelValue (https://github.com/vuejs/core/issues/2667#issuecomment-732886315). Etc.. Following worked for me after hour of trial and error on latest Vuejs3
Child
<template>
<div class="form-check noselect">
<input class="form-check-input" type="checkbox" :id="id" :checked="modelValue" #change="$emit('update:modelValue', $event.target.checked)" />
<label class="form-check-label" :for="id"><slot /></label>
</div>
</template>
<script>
import { v4 as uuidv4 } from "uuid";
export default {
inheritAttrs: false,
emits: ["update:modelValue"],
props: {
modelValue: {
type: Boolean,
required: true,
},
},
setup() {
return {
id: uuidv4(),
};
},
};
</script>
Parent:
<Checkbox v-model="someVariable">Is true?</Checkbox>
you can verify that it works but doing this in parent:
var someVariable= ref(false);
watch(someVariable, () => {
console.log(someVariable.value);
});
p.s. The other solution above does not work for me. Author recommends using value property. But in example he passes v-model attribute. So I don't know exactly how it's supposed to work.

You can achieve the behavior by using emits to keep data in sync and behave as default v-model behavior. Checkbox component:
<template>
<div>
<input
type="checkbox"
:checked="value"
#change="$emit('input', $event.target.checked)"
/>
{{ text }}
</div>
</template>
<script>
export default {
name: "inputcheckbox",
props: ["value", "text"],
};
</script>
And in the parent component you can have as many checkboxes you want.
<template>
<div id="app">
<maincontent :showContent="showContent" />
<inputcheckbox text="one" v-model="checkedOne" />
<inputcheckbox text="two" v-model="checkedTwo" />
</div>
</template>
Here is a vue 2 example but is applicable to vue 3 as well. Hope this was helpful. Sandbox with this behavior:
https://codesandbox.io/embed/confident-buck-kith5?fontsize=14&hidenavigation=1&theme=dark

Related

Vue.js Child Component not Updating Data in Parent Component

I am using vue components in a Laravel project.
I have taken sample code from https://vuejs.org/guide/components/events.html#usage-with-v-model
I have a child component with an input box:
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue']
}
</script>
<template>
<input
:value="modelValue"
#input="$emit('update:modelValue', $event.target.value)"
/>
</template>
This component is being used in a parent component.
<phone-input v-model="phone"/> {{phone}}
The parent component displays the input box with the initial value of the phone variable. However, the changed value is not reflected in the parent's phone variable ( {{phone}} does not update). Am I missing something? I have cleared the cache, but it did not help.
I tried another variation of the code (from vue.js documentation code) as given here. However, this also does not work.
Parent
<MyComponent v-model:title="bookTitle" />. {{bookTitle}}
<!-- Child Component MyComponent.vue -->
<script>
export default {
props: ['title'],
emits: ['update:title']
}
</script>
<template>
<input
type="text"
:value="title"
#input="$emit('update:title', $event.target.value)"
/>
</template>
Thanks for your help.
Take a look at following snippet, looks ok:
const app = Vue.createApp({
data() {
return {
phone: "0123456"
}
},
})
app.component('phoneInput', {
template: `
<input
:value="modelValue"
#input="$emit('update:modelValue', $event.target.value)"
/>
`,
props: ['modelValue'],
emits: ['update:modelValue'],
})
app.mount("#demo")
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<phone-input v-model="phone"></phone-input>
{{ phone }}
</div>
<script>
export default {
props: ['modelValue'],
}
</script>
<template>
<input
:value="modelValue"
#input="$emit('input', $event.target.value)"
/>
</template>
You need specially emit input to make it work

How to pass #blur into child component Vuejs

I created a custom Input component.
I needed pass #blur from vee-validate form to my custom input component.
It works great in normal html input tag. I no idea how could we pass the #blur into custom Input component.
Example 1 work correctly, it triggered the validation after blur the input.
<template>
<form #submit="onSubmit">
<input #blur="emailBlur" v-model="email" type="text" autocomplete="off" name="email" placeholder="email">
<button type="submit" :disabled="isSubmitting">Submit</button>
</form>
</template>
Example 2 with My custom Input Component:
// src/components/Input.vue
<template>
<div class="mt-2">
<label :for="name" class="h5">Name</label>
<input
:type="type"
:value="modelValue"
#change="$emit('update:modelValue', $event.target.value)"
:id="name"
:placeholder="placeholder"
/>
</div>
</template>
<script>
export default {
name: 'Input',
props: ["modelValue", 'name', 'type', 'placeholder'],
setup(props) {
console.log('props :>> ', props); // not receive the #blur
}
}
</script>
Parent Component (App.vue):
<template>
<form>
<Input #blur="emailBlur" v-model="email" type="text" name="email" placeholder="Custom input email" />
<button type="submit" :disabled="isSubmitting">Submit</button>
</form>
</template>
export default {
name: 'App',
components: {
Input
},
setup() {
// the vee validation values v-model to template
}
}
Sending the handler as props from the parent is not a good practice instead need to trigger the handler(present in the parent) from the child component. By doing so you will have an advantage where you can bind different blur handlers based on your requirement inside different parent components
To do so you can follow the below approach
Custom Input Component
// src/components/Input.vue
<template>
<div class="mt-2">
<label :for="name" class="h5">Name</label>
<input
:type="type"
:value="modelValue"
#change="$emit('update:modelValue', $event.target.value)"
:id="name"
:placeholder="placeholder"
#blur="$emit('blur')" //Change added
/>
</div>
</template>
<script>
export default {
name: 'Input',
props: ["modelValue", 'name', 'type', 'placeholder'],
emits: ['blur', 'update:modelValue'], // change added
}
</script>
Note:
for all v-models without arguments, make sure to change props and events name to modelValue and update:modelValue respectively
For Example:
Parent.vue
<ChildComponent v-model="pageTitle" />
and in Child.vue it should be like
export default {
props: {
modelValue: String // previously was `value: String`
},
emits: ['update:modelValue'],
methods: {
changePageTitle(title) {
this.$emit('update:modelValue', title) // previously was `this.$emit('input', title)`
}
}
}
What you are creating is usually called "transparent wrapper" component. What you want from this wrapper is to behave in almost every way as normal input component so the users of the component can work with it as it was normal input (but it is not)
In your case, you want to attach #blur event listener to your wrapper. But the problem is that blur is native browser event i.e. not a Vue event.
When you place event listener on a component for an event not specified in emits option (or v-bind an attribute that is not specified in component's props), Vue will treat it as Non-Prop Attribute. This means it take all such event listeners and non-prop attributes and place it on the root node of the component (div in your case)
But luckily there is a way to tell Vue "Hey, don't place those automatically on root, I know where to put it"
Use inheritAttrs: false option on your component
Put all non-prop attributes (including the event listeners) on the element you want - input in this case - using v-bind="$attrs"
Now you can even remove some props - for example placeholder (if you want), because if you use it directly on your component, Vue place it on input, which is what you want...
Also handling #change event is not optimal - you component allows to specify a type and different input types has different events. Nice trick around it is not to pass value and bind event explicitly, but instead use v-model with computed (see example below)
const app = Vue.createApp({
data() {
return {
text: ""
}
},
methods: {
onBlur() {
console.log("Blur!")
}
}
})
app.component('custom-input', {
inheritAttrs: false,
props: ["modelValue", 'name', 'type'],
emits: ['update:modelValue'],
computed: {
model: {
get() { return this.modelValue },
set(newValue) { this.$emit('update:modelValue', newValue) }
}
},
template: `
<div class="mt-2">
<label :for="name" class="h5">{{ name }}:</label>
<input
:type="type"
v-model="model"
:id="name"
v-bind="$attrs"
/>
</div>
`
})
app.mount("#app")
<script src="https://unpkg.com/vue#3.2.19/dist/vue.global.js"></script>
<div id='app'>
<custom-input type="text" name="email" v-model="text" placeholder="Type something..." #blur="onBlur"></custom-input>
<pre>{{ text }}</pre>
</div>

Deep in slots vue 2

Does anybody know is there are some limitations on how deep my components with slots can be ?
Now I have 3 components, like
list.vue
<div class="list">
<slot name="list" />
</div
wrapper.vue
<list>
<template #list>
<div>hello</div>
<slot name="wrapper" />
</template>
</list>
last.vue
<wrapper>
<template #wrapper>
<search :value=value />
</template>
</wrapper>
So, i want to transfer some value from last component to search component. On init something is good, but if value is changing in my last component my search component it doesn't see.
Maybe someone know information about max deep for components + slots ?
You forgot to share your Search component and the rest of the other components (namely, the script section). There is no limit in the slot depth (whatever that means). I'm assuming you have some other errors in parts you haven't shared. My first guess would be the way you have set the props in your Search component.
Here is an example of a working version on how you should set a nullable prop on a component:
Last.vue
<template>
<Wrapper>
<template #wrapper>
<label>
<input type="text" v-model="value">
</label>
<Search :value=value />
</template>
</Wrapper>
</template>
<script>
import Wrapper from "#/components/Wrapper";
import Search from "#/components/Search";
export default {
components: {Search, Wrapper},
data() {
return {
value: null
}
}
}
</script>
Search.vue
<template>
<p>{{ value }}</p>
</template>
<script>
export default {
props: {
value: {
type: String,
default: null,
}
},
}
</script>
If this doesn't address your problem, please share the rest of your code and I'd be glad to help!

Vue pass props to an element in a component

I'm trying to figure out how to pass a Vue prop to the a tag located in the second component. It can be any prop so I can't specifically import that one to the component.
First component
<script>
export default {
name: 'first',
}
</script>
<template>
<div class="first">
<slot />
</div>
</template>
Second component
<script>
export default {
name: 'second',
text: {
type: String,
required: true,
},
};
</script>
<template>
<li>
</li>
</template>
When I use the component I want to add a prop that gets attached to the a tag.
<first>
<second text="test" />
</first>
Look at v-bind="$attrs" in https://v2.vuejs.org/v2/guide/components-props.html#Disabling-Attribute-Inheritance.
V-bind actually can bind an entire key-value object.

How to point to wrapping custom element with v-model?

I currently have a custom vue-form component:
In my common HTML it kind of looks something like this:
<vue-form>
<input type="text" v-model="test">
<input type="password" v-model="test2">
</vue-form>
Note that this is not a Vue template.
My v-model keeps pointing to my root component when in this case I would like my v-model to point the the actual component that is wrapping my content.
For the vue-form component I am simply using a slot like so:
<template>
<div>
<slot></slot>
</div>
</template>
Is there a way to get the v-model binding to point towards the wrapping component instead of the root element?
You can use Scoped Slots documentation.
Edit:
Here is working example:
<div id="app">
<vue-form v-bind:model="x">
<template scope="props">
<input type="text" v-model="props.model.test">
<input type="password" v-model="props.model.test2">
</template>
</vue-form>
</div>
new Vue({
el: '#app',
data: function() {
return {
x: {
test: 'John',
test2: 'Smith'
}
}
},
components: {
'vue-form': {
template: `<div><slot :model="model"></slot></div>`,
props: ['model']
}
}});
jsfiddle
Just notice that this solution requires from you to propagate property from component to slot by :model="model" part in slot declaration.