I made multiple v-model binding in vue 3. but got some probel in code quality when i must code emit value function repeating with 90% the function code is same. Is this possible to refactoring this code
<script lang="ts" setup>
const props = defineProps({
firstName: String,
lastName: String,
firstNameModifiers: {
default: {
default: () => ({}),
capitalize: () => ({}),
},
},
lastNameModifiers: {
default: {
default: () => ({}),
capitalize: () => ({}),
},
},
});
const emit = defineEmits(["update:firstName", "update:lastName"]);
// ! TODO: Make Function to handle modifiers - its repeating below
function firstNameEmitValue(e: Event) {
const target = e.target as HTMLInputElement;
let value = target.value;
if (props.firstNameModifiers["capitalize"]) {
value = value.charAt(0).toUpperCase() + value.slice(1);
}
emit(`update:firstName`, value);
}
function lastNameEmitValue(e: Event) {
const target = e.target as HTMLInputElement;
let value = target.value;
if (props.firstNameModifiers["capitalize"]) {
value = value.charAt(0).toUpperCase() + value.slice(1);
}
emit(`update:lastName`, value);
}
</script>
<template>
<input type="text" :value="firstName" @input="firstNameEmitValue" />
<input type="text" :value="lastName" @input="lastNameEmitValue" />
</template>
Its ok when just using 1 modifiers. But its will become annoying if i want to add other modifiers. for example toUppercase, toLowerCase etch. Maybe the solution is just separate the component for firstname input and lastname input and make it as single v-model and emit.
But i just want to try this approach bcause vue put it in, in their documentation.
This should do it:
helpers.ts
export const names = ["first", "last"]
your component
<script lang="ts" setup>
import { reactive, computed } from "vue"
import { names } from '../path/to/helpers'
const modifiers = {
default: {
default: () => ({}),
capitalize: () => ({})
}
}
const props = defineProps(
Object.assign(
{},
...names.map((name) => ({
[name + "Name"]: String,
[name + "NameModifiers"]: modifiers
}))
)
)
const emit = defineEmits(names.map((name) => `update:${name}Name`))
const emitValue = ({ target }: Event, name: string) => {
if (target instanceof HTMLInputElement) {
let { value } = target
if (props[`${name}NameModifiers`]["capitalize"]) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit(`update:${name}Name`, value)
}
}
const state = reactive(
Object.assign(
{},
...names.map((name) => ({ [name]: computed(() => props[name]) }))
)
)
</script>
<template>
<input
type="text"
v-for="name in names"
:key="name"
:value="state[name]"
@input="emitValue($event, name)"
/>
</template>
If you add 'middle'
to names
, it should work out of the box.
Note: haven't tested it, since you haven't provided a sandbox. If it doesn't work, I'll make one and test it.
I tend to avoid @input
+ :value
(although it works), in favor of v-model
with computed setter. Has the advantage of not having to cast the input type and also allows assigning to model programmatically, should you ever need it. I also find the resulting syntax cleaner, hence more readable:
<script lang="ts" setup>
import { reactive, computed } from "vue"
import { names } from '../path/to/helpers'
const modifiers = {
default: {
default: () => ({}),
capitalize: () => ({})
}
}
const props = defineProps(
Object.assign(
{},
...names.map((name) => ({
[name + "Name"]: String,
[name + "NameModifiers"]: modifiers
}))
)
)
const emit = defineEmits(names.map((name) => `update:${name}Name`))
const state = reactive(
Object.assign(
{},
...names.map((name) => ({
[name]: computed({
get: () => props[name],
set: (val) =>
emit(
`update:${name}Name`,
props[`${name}NameModifiers`]["capitalize"]
? val.charAt(0).toUpperCase() + val.slice(1)
: val
)
})
}))
)
)
</script>
<template>
<input type="text" v-for="name in names" :key="name" v-model="state[name]" />
</template>