Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

template syntax error <input :type="type" v-model="xxx">: v-model does not support dynamic input types. Use v-if branches instead. #3915

Closed
wowdeanwu opened this issue Oct 12, 2016 · 28 comments

Comments

@wowdeanwu
Copy link

Vue.js version

2.0.0-rc.1

Reproduction Link

Steps to reproduce

What is Expected?

What is actually happening?

@fnlctrl
Copy link
Member

fnlctrl commented Oct 12, 2016

As the error suggested, v-model does not support dynamic input types, so don't use :type="type"

@fnlctrl fnlctrl closed this as completed Oct 12, 2016
@wowdeanwu
Copy link
Author

i am stuck in this error, someone can help me out ... thx

@wowdeanwu
Copy link
Author

ok ... thank you ... i should change the implement ...

@Akryum
Copy link
Member

Akryum commented Nov 6, 2016

@yyx990803 Is this limitation going to be fixed? Or is there a workaround other than painful/unmaintenable v-if branches?

@akirak
Copy link

akirak commented Nov 7, 2016

@Akryum This is a feature introduced in this commit: f35f7e3. 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

@sirlancelot
Copy link

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.

@gwildu
Copy link

gwildu commented Nov 8, 2016

@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.

@akirak
Copy link

akirak commented Nov 8, 2016

@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.

@gwildu
Copy link

gwildu commented Nov 10, 2016

@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.

@akirak
Copy link

akirak commented Nov 11, 2016

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

@sirlancelot
Copy link

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.

@nkovacs
Copy link

nkovacs commented Nov 14, 2016

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

@jsfeldman
Copy link

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?

@nkovacs
Copy link

nkovacs commented Jan 19, 2017

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.

@Akryum
Copy link
Member

Akryum commented Jan 19, 2017

@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.

@CanRau
Copy link

CanRau commented Apr 18, 2017

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)
        }
      }
    })
  }
}

@nkovacs
Copy link

nkovacs commented Apr 18, 2017

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.

@CanRau
Copy link

CanRau commented Apr 18, 2017

ah okay, got it thanks :-)
for me, alt least for now, it's exactly what i needed :D

@nkovacs
Copy link

nkovacs commented Apr 18, 2017

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.

@CanRau
Copy link

CanRau commented Apr 18, 2017

interesting, thanks for sharing =)

@arivera12
Copy link

arivera12 commented Jul 15, 2017

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">

@meteorlxy
Copy link
Member

<input ref="input" v-model="modelValue">
props: {
  type: {
    type: String,
    default: 'text'
  }
},
mounted () {
  this.$refs.input.type = this.type
}

@CanRau
Copy link

CanRau commented Oct 7, 2017

interesting :D 👍

@nkovacs
Copy link

nkovacs commented Oct 13, 2017

@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: f3fe012#diff-6eaa1698ef4f51c9112e2e5dd84fcde8R4, which looks similar to my workaround.

It's buggy in 2.5.0, you have to call the property type: #6800

@meteorlxy
Copy link
Member

meteorlxy commented Oct 13, 2017

@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.

@CAYdenberg
Copy link

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.

@nkovacs
Copy link

nkovacs commented Jan 11, 2018

Because it's implemented in 2.5.0: f3fe012#diff-6eaa1698ef4f51c9112e2e5dd84fcde8R4

@adi518
Copy link

adi518 commented Apr 4, 2018

V-model doesn't work with the <component /> workaround. I ended up upgrading Vue to v2.5.x and it solved my issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests