Reputation: 123
I'm trying to create a reusable styled input field in Vue. To make it styled (e.g. with an icon inside) I need to wrap it in another html-element.
Lets call the example below StyledInput
<div class="hasIcon">
<input />
<i class="someIcon"></i>
<div>
If I want to use StyledInput
it might look like so:
<styled-input @keyup.enter="doSomething">
</styled-input>
But this would not work, due to the event listener being attached to the <div>
instead of the <input>
.
A workaround to that could be to emit all key-events from the input field:
<div class="hasIcon">
<input @keyup="$emit('keyup', $event) />
<i class="someIcon"></i>
<div>
But this will not scale well since it would have to be rewritten every time a developer uses an unmapped prop or event.
Is there a way to only make the inner element exposed to whomever uses it?
Upvotes: 5
Views: 1309
Reputation: 17178
You can also use $attrs
to pass props and events onto children elements:
<template>
<div>
<input v-bind="$attrs">
</div>
</template>
In Vue 3, you can specify a second script tag:
<script setup>
</script>
<script>
export default {
inheritAttrs: false,
};
</script>
https://vuejs.org/guide/components/attrs.html#disabling-attribute-inheritance
Upvotes: 1
Reputation: 7186
You could use slots to achieve this. If your <styled-input>
template looks like this:
<div class="hasIcon">
<slot><input></slot>
<i class="someIcon"></i>
<div>
Then you can use it like this:
<styled-input>
<input @keyup.enter="doTheThing">
</styled-input>
Or, in cases where you don't care about the input events, like this:
<styled-input></styled-input>
and the default slot content (a bare <input>
) will be used. You can use CSS to style the <input>
inside the component, but you can't add custom properties or classes to it, so this approach may or may not fit your requirements.
Upvotes: 0
Reputation: 32724
I'm not sure there is a Vue
way to achieve this, because, as far as I'm aware there is no way to bind vue events dynamically, it is however possible to do this using vanilla javascript by passing all events as a prop then mapping them using addEventListener()
to add your custom events:
Vue.component('my-input', {
template: "#my-input",
props: ['events'],
mounted() {
// get the input element
let input = document.getElementById('styled-input');
// map events
this.events.forEach((event) => {
let key = Object.keys(event);
input.addEventListener(key, event[key]);
});
}
})
Then you can just pass through all events as a prop like so:
<my-input :events="events"></my-input>
View Model:
var app = new Vue({
el: "#app",
data: {
events: [{
focus: () => {
console.log('focus')
}
}, {
keyup: (e) => {
console.log(e.which)
}
}]
}
})
Heres the JSFiddle: https://jsfiddle.net/h1dnk40v/
Of course, this means any developer would have to do things like map key codes etc, so you will lose some of the convenience Vue provides.
One thing I will just mention is that Vue components
aren't necessarily intended to be infinitely reusable, they are supposed to provide specific functionality and encapsulate complex logic, so you would probably do better to implement the most likely use cases, and if the component doesn't fit you can extend it or write a new one for that particular event.
Upvotes: 4