2.0.0-rc.1
As the error suggested, v-model does not support dynamic input types, so don't use :type="type"
i am stuck in this error, someone can help me out ... thx
ok ... thank you ... i should change the implement ...
@yyx990803 Is this limitation going to be fixed? Or is there a workaround other than painful/unmaintenable v-if branches?
@Akryum This is a feature introduced in this commit: https://github.com/vuejs/vue/commit/f35f7e35cd48efc21e759752058c07a1ff32cfb6. It seems that this check is skipped in production.
Perhaps you can bypass this check by defining :value
and @input
separately instead of v-model
, though it may be discouraged: https://vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events
A better implementation would be to create your dynamic input type as a component. In Vue 2.x, you can attach v-model
to custom components to get two-way data binding.
@sirlancelot But in that component you would still need to dynamically bind the type, no? Or could the limitation be circumvented by using a custom model?
If not, it would mean you have duplicated code for every type you want to cover. That can get quite messy, especially as those generic components change quite often until you get a more or less stable version.
@gwildu Yes, I encountered the same issue while trying to use an input component in (the development version of) Keen UI: https://josephuspaye.github.io/Keen-UI/#/ui-textbox-docs. After the recent update of Vue.js, now I cannot make it work. A workaround would be to always set the input type to text
and validate its value, but it will require work on every similar component.
@sirlancelot I'm not sure if this is possible but couldn't the old behavior be hidden by an attribute flag like 'generic-type' or 'dynamic-type'? My guess is, that this new behavior has a positive performance impact but that might not be an issue for all projects.
How about implementing a render function in JavaScript? It does not look elegant but probably powerful enough to do almost anything. Actually, it is the way some components are implemented. https://vuejs.org/v2/guide/render-function.html
Thanks for sharing that @akirak, I hadn't personally read that far in the docs yet but it outlines exactly what I had in mind for OP's dynamic input component which I suggested earlier: Create a component which has as many hard-coded inputs as desired and wrap them in a v-if
. Abstracting this functionality away in to a component allows re-usability and you don't have to think about its implementation after it's written.
Render functions don't support v-model, so you'd have to re-implement v-model essentially, and that doesn't look that simple: https://github.com/vuejs/vue/blob/dev/src/platforms/web/compiler/directives/model.js
The error doesn't seem to appear if you use <component is="input" v-model="m" :type="t" />
and I believe you still get all the functionality.
Is this intentional?
You probably don't get all the functionality of v-model.
Unless I'm mistaken, what's going to happen is that you'll get genDefaultModel here: https://github.com/vuejs/vue/blob/dev/src/platforms/web/compiler/directives/model.js#L27
So it won't work properly with select, checkbox and radio.
@nkovacs The point for my use case is creating a generic text input component, part of a UI toolkit. It won't be used as a select/checkbox/radio (there will be specific components for that), but, it could be used as a text/email/password/phone/etc. text input.
maybe I'm missing something but this works for me
dynamic-input.vue
export default {
props: {
tag: {
type: String,
required: true
}
},
render: function(createElement) {
var component = this
return createElement('input',
{
domProps: {
type: this.tag,
value: component.value
},
on: {
input: function(event) {
component.value = event.target.value
component.$emit('input', event.target.value)
}
}
})
}
}
and then
let dynamicTag = 'email'
<dynamic-input :tag="dynamicTag" v-model="value" />
or even
export default {
props: {
type: {
type: String,
required: true
}
},
render: function(createElement) {
let component = this
let tag = 'textarea'
let domProps = {
value: component.value
}
if (this.type !== 'textarea') {
tag = 'input'
domProps.type = this.type
}
return createElement(tag,
{
domProps,
on: {
input: function(event) {
component.value = event.target.value
component.$emit('input', event.target.value)
}
}
})
}
}
It does work in some cases, but not in others, e.g. if type is radio. v-model, when used on native tags (input, select) in vue template files, is compiled into different render functions depending on the tag and the type attribute. You can see that code here: https://github.com/vuejs/vue/blob/dev/src/platforms/web/compiler/directives/model.js
That's why type can't be dynamic.
Your code doesn't work on radio or checkboxes, for example, because it only listens to input events. v-model checks the type and listens to the correct event.
ah okay, got it thanks :-)
for me, alt least for now, it's exactly what i needed :D
I worked around this by generating all the different input types I needed and then using v-if to activate only the one that matches the dynamic type (as suggested by the error message). I used pug to avoid a bunch of duplication:
<template lang="pug">
.form-group
label.label(:for="id", :class="offsetClass", v-if="label") {{ label }}
div(:class="widthClass")
each type in ["text", "password", "search", "email", "date"]
input.form-control(
:id="id",
v-if="type === '" + type + "'",
v-model="val",
type=type,
:placeholder="placeholder"
)
slot(name="help")
</template>
<script>
import Moment from 'moment';
const dateFormat = 'YYYY-MM-DD';
export default {
props: {
id: {
type: String,
required: true,
},
value: null,
label: {
type: String,
},
type: {
type: String,
default: 'text',
},
placeholder: {
type: String,
},
},
data() {
return {
internalVal: null,
};
},
watch: {
value: {
handler(value) {
this.internalVal = value;
},
immediate: true,
},
},
computed: {
offsetClass() {
return false;
},
widthClass() {
return false;
},
val: {
get() {
if (this.type === 'date') {
const m = Moment.unix(this.internalVal);
return m.format(dateFormat);
}
return this.internalVal;
},
set(value) {
if (this.type === 'date') {
const m = Moment(value, dateFormat);
this.internalVal = m.unix();
} else {
this.internalVal = value;
}
this.$emit('input', this.internalVal);
},
},
},
};
</script>
<style lang="scss">
</style>
This way I can use the built-in v-model directive. This can be extended to radio and checkboxes as well.
interesting, thanks for sharing =)
Here is my working solution in a component bootstrap based and also using vee-validate for this... Enjoy!
//Vue component definition
Vue.component("boost-input", {
props: ["gridLayout", "txtIdentifier", "txtType", "txtLabel", "txtVal", "txtValidation", "txtPlaceholder", "helpBlockText", "maxLength", "parentModel"],
model: {
prop: 'parent-model',
event: 'update:parentModel'
},
data: function() {
return {
templateRender: null,
modelValue: (typeof this.txtVal === "undefined") ? "" : this.txtVal
}
},
render(h) {
return h('div', [
(this.templateRender ?
this.templateRender() :
'')
]);
},
mounted: function ()
{
var compiledTemplate = Vue.compile('\
<div :class="\'form-group \' + gridLayout">\
<label :for="txtIdentifier" class="hidden-xs" v-text="txtLabel"></label>\
<input v-model="modelValue" type="' + this.txtType + '" :maxlength="maxLength" v-on:change="change($event.target.value)" v-validate.initial="' + this.txtValidation + '" data-vv-as="' + this.txtPlaceholder + '" :id="txtIdentifier" :name="txtIdentifier" :placeholder="txtPlaceholder" class="form-control">\
<span class="text-danger" v-show="errors.has(txtIdentifier)">{{ errors.first(txtIdentifier) }}</span>\
<span class="help-block" v-text="helpBlockText"></span>\
</div>');
this.templateRender = compiledTemplate.render;
},
methods: {
change: function (newValue) {
this.$emit('update:parentModel', newValue)
}
}
});
//Using the component in html
<boost-input v-model="Username"
grid-layout="col-sm-6 col-md-4 col-lg-3"
txt-identifier="Username"
:txt-val="Username" //The parent model
txt-type="text"
txt-label="Username"
txt-validation="'required'"
txt-placeholder="Username"
max-length="60"
help-block-text="Help text block">
Another alternative and the way this framework push you to do it could be something like this...
<input v-if="type == 'text'" type="text" :name="name" :id="name" v-model="inputModel">
<input v-if="type == 'number'" type="number" :name="name" :id="name" v-model="inputModel">
<input ref="input" v-model="modelValue">
props: {
type: {
type: String,
default: 'text'
}
},
mounted () {
this.$refs.input.type = this.type
}
interesting :D 👍
@meteorlxy Your solution will break when you use radio or checkbox.
Vue 2.5.0 supports dynamic types by converting it to a v-if/v-else: https://github.com/vuejs/vue/commit/f3fe012d5499f607656b152ce5fcb506c641f9f4#diff-6eaa1698ef4f51c9112e2e5dd84fcde8R4, which looks similar to my workaround.
It's buggy in 2.5.0, you have to call the property type
: https://github.com/vuejs/vue/issues/6800
@nkovacs Yeah, I only use it for those "text-like" types in some UI components, like @Akryum commented. I think using render function could be a better solution, but it's a little more complicate.
Happy to see that dynamic types supported in new release.
Why is this issue closed? Even if it isn't a bug, it seems reasonable that the API would support this? It works fine with
<component :is="input" :type="type">
the only limitation is that it's weird and unreadable.
Because it's implemented in 2.5.0: https://github.com/vuejs/vue/commit/f3fe012d5499f607656b152ce5fcb506c641f9f4#diff-6eaa1698ef4f51c9112e2e5dd84fcde8R4
V-model doesn't work with the <component />
workaround. I ended up upgrading Vue to v2.5.x and it solved my issue.
Most helpful comment
@nkovacs The point for my use case is creating a generic text input component, part of a UI toolkit. It won't be used as a select/checkbox/radio (there will be specific components for that), but, it could be used as a text/email/password/phone/etc. text input.