Reputation: 19158
Using Vue3
I'm creating a custom component and would like to bind the events to the input inside it. So in this case I would like to do something similar as I do with the v-bind="inputProps"
. How is it done now $listeners
are being removed? As you probably can see I'm not in a position to use v-bind="$attrs"
as the properties are divided to where they "go".
I'm here to solve a problem but any hints on improvement is always a welcome bonus.
// c-text.vue
<template>
<input
v-if="useAsSingleElement"
v-bind="inputProps"
:class="['c-text', 'c-text--is-single-element']"
@input="$emit('input', $event)"
@blur="$emit('blur')"
@focus="$emit('focus')">
<c-form-item-shell
v-else
:id="id"
:is-required="isRequired"
:label-text="labelText"
:icon="icon"
:icon-class="iconClass"
:errors="errors"
:information="information">
<template #element>
<input
v-bind="inputProps"
class="c-text"
@input="$emit('input', $event.target.value)"
@blur="$emit('blur')"
@focus="$emit('focus')">
</template>
</c-form-item-shell>
</template>
<script>
import { computed } from 'vue'
import CFormItemShell from '@/components/c-form-item-shell.vue'
export default {
components: {
CFormItemShell
},
props: {
useAsSingleElement: { type: Boolean, required: false, default: false },
name: { type: String, required: true },
type: { type: String, required: false, default: 'text' },
placeholder: { type: String, required: false, default: null },
maxlength: { type: [String, Number], required: false, default: null },
minxlength: { type: [String, Number], required: false, default: null },
pattern: { type: String, required: false, default: null },
readonly: { type: Boolean, required: false, default: false },
spellcheck: { type: Boolean, required: false, default: false },
autocomplete: { type: Boolean, required: false, default: true },
id: { type: String, required: true },
isRequired: { type: Boolean, required: false, default: false },
labelText: { type: String, required: false, default: null },
icon: { type: String, required: false, default: null },
iconClass: { type: String, required: false, default: null },
errors: { type: [String, Array], required: false, default: null },
information: { type: [String, Array], required: false, default: null }
},
emits: ['input', 'blur', 'focus'],
setup (props) {
const inputProps = computed(() => {
return {
id: props.id,
name: props.name ? props.name : props.id,
type: props.type,
placeholder: props.placeholder,
maxlength: props.maxlength,
minlength: props.minlength,
pattern: props.pattern,
readonly: props.readonly,
spellcheck: props.spellcheck,
autocomplete: props.autocomplete ? 'on' : 'off'
}
})
return {
inputProps
}
}
}
</script>
Upvotes: 1
Views: 1352
Reputation: 19158
To answer my own question and more of a detailed answer in the hopes that it can help anybody in the future. Thanks to Boussadjra Brahim who's answer got me thinking. I did the following
inhertsAttrs: false
This makes it so the events (meaning your @input
, @click
ect) on the component don't get applied to the parent node
emits: ['update:modelValue', 'blur', 'focus']
This should be all of the emits that you want to capture and emit back
propsAndListeners
object like this. Then bind it to the input like <input v-bind="propsAndListeners" />
Keep in mind that you have to prefix your events with on
so if you have a @click
event that will have the name onClick
in the $attrs
(@ short is v-on)
const propsAndListeners = computed(() => {
return {
onInput: (event) => emit('update:modelValue', event.target.value)
}
})
In most cases you probably would bind the $attrs
to a element but the reason for the propsAndListeners
is that I have if
condition and binding the same things for an input in two places.
Here is the full script part
export default {
components: {
CFormItemShell
},
props: {
useAsSingleElement: { type: Boolean, required: false, default: false },
name: { type: String, required: true },
type: { type: String, required: false, default: 'text' },
modelValue: { type: [String, Number, Date], required: false, default: null },
placeholder: { type: String, required: false, default: null },
maxlength: { type: [String, Number], required: false, default: null },
minxlength: { type: [String, Number], required: false, default: null },
pattern: { type: String, required: false, default: null },
readonly: { type: Boolean, required: false, default: false },
spellcheck: { type: Boolean, required: false, default: false },
autocomplete: { type: Boolean, required: false, default: true },
id: { type: String, required: true },
isRequired: { type: Boolean, required: false, default: false },
labelText: { type: String, required: false, default: null },
icon: { type: String, required: false, default: null },
iconClass: { type: String, required: false, default: null },
errors: { type: [String, Array], required: false, default: null },
information: { type: [String, Array], required: false, default: null }
},
emits: ['update:modelValue', 'blur', 'focus'],
setup (props, { emit }) {
const propsAndListeners = computed(() => {
return {
id: props.id,
name: props.name ? props.name : props.id,
type: props.type,
placeholder: props.placeholder,
value: props.modelValue,
maxlength: props.maxlength,
minlength: props.minlength,
pattern: props.pattern,
readonly: props.readonly,
spellcheck: props.spellcheck,
autocomplete: props.autocomplete ? 'on' : 'off',
onInput: (event) => emit('update:modelValue', event.target.value),
onBlur: (event) => emit('blur', event),
onFocus: (event) => emit('focus', event)
}
})
return {
propsAndListeners
}
}
}
Upvotes: 0
Reputation: 1
Based on the migration guide :
In Vue 3's virtual DOM, event listeners are now just attributes, prefixed with
on
, and as such are part of the$attrs
object, so$listeners
has been removed.
so just pass your events like onClick
, onBlur
... and
export default {
inheritAttrs: false
}
Upvotes: 1