I'm making an InputWrapper component used for decorating some BootstrapVue input components. The point is to automatically handle validation states, messages, styling, etc. (not shown in the example bellow) around a given input.
I would like to dynamically "forward" v-model. The problem comes when the wrapped component uses custom model attribute and update event for two way binding.
The main idea goes as follow.
InputWrapper.Vue
<template>
<div>
<slot v-bind="wrappedBindings"></slot>
</div>
</template>
<script>
export default {
props: {
value: {required: true}
},
methods: {
onInput($event){
this.$emit('input', $event);
}
},
computed: {
wrappedBindings(){
return {
attrs: {
value: this.value
},
on: {
input: $event => this.onInput($event),
'update:value': $event => this.onInput($event)
}
}
}
}
}
</script>
Usage
<div>
<input-wrapper v-model="selectModel" v-slot="{attrs, on}">
<!-- v-model of b-form-select is :value and @input so this works -->
<b-form-select v-bind="attrs" v-on="on" ...other stuff...></b-form-select>
</input-wrapper>
<input-wrapper v-model="inputModel" v-slot="{attrs, on}">
<!-- v-model of b-form-input is :value and @update (not @update:value or @input) so this does not work -->
<b-form-input v-bind="attrs" v-on="on" ...other stuff...></b-form-input>
</input-wrapper>
<input-wrapper v-model="checkModel" v-slot="{attrs, on}">
<!-- v-model of b-form-checkbox is :checked (not :value) and @input so this does not work -->
<b-form-checkbox v-bind="attrs" v-on="on" ...other stuff...></b-form-checkbox>
</input-wrapper>
</div>
My current and unsatisfactory solution
<div>
<input-wrapper v-model="inputModel" v-slot="{attrs, on}">
<b-form-input v-bind="attrs" v-on="on" @update="on.input"
...other stuff...></b-form-input>
</input-wrapper>
<input-wrapper v-model="checkModel" v-slot="{attrs, on}">
<b-form-checkbox v-bind="attrs" v-on="on" :checked="attrs.value"
...other stuff...></b-form-checkbox>
</input-wrapper>
</div>
This solution allows me to do what I want but it's longer to implement and you always need the BootstrapVue documentation close by.
Another solution would be to make a custom component for every BsVue input, but I would also need to forward every attribute and event to the custom component. There are many reasons for not doing this but mainly it would be harder to maintain.
My question is the following: How can I use v-bind="attrs" and v-on="on" to dynamically bind any custom v-model attribute and event without knowing them beforehand?
This one is not easy...
Well you can't.
My first idea was to somehow reach to a model option used in Vue 2 to customize the prop name and event name of the
v-modelon this component. But unfortunately this is not accessible on$scopedSlots.default()(and using it in that way would be very ineffective anyway)Imho the best option is to use the
v-modelon the slotted component and let Vue do the heavy lifting for you...BUT..normally when you are creating an
input(or some custom input) wrapper, easiest thing to do is to usecomputedto "connect" (or forward) inner component'sv-modelto av-modelof the wrapper:Why ? For exactly the same reason you are asking your question. You do not need to choose which prop and event use (because different
inputtypes use a different prop name for avalueand use different event) - you can just leave it to av-modelBut to do same thing on slotted component is a bit tricky. You can't place a
v-modelon<slot>directly. It must be placed on the slotted component (in parent's template). So only way is to pass "something" like the computed above into a slot props. That is problem too.v-bindcomputed as a whole to a slot. If you try that, the slotted component only receives the current value read from the getter and setter is left behind...To overcome the first problem, same technique as with normal props can be used - instead of passing just a value, pass an object and make the value its property. Now you can mutate the value of that property as you wish (this is considered by many as dirty but I think it is very powerful and can save you a lot of boilerplate code if used wisely)
Second problem can be solved by using Object.defineProperty API which allows you to do something similar as Vue
computedproperties - define a property of an object and declare your own functions to be used when the property is read or written..Here is a final solution:
And the usage:
This is of course not ideal (especially the fact that you need to use
model.proxyin thev-model) but right now I do not see any other way how to implement such "universal" wrapper (except of course to choose a component library that sticks to a "value"/"input" for av-modelon custom components)Demo