Vue2: v-model does not support dynamic input types - vue.js

When trying to create dynamic input elements i get an template compiling error like this:
v-model does not support dynamic input types. Use v-if branches
instead.
https://jsfiddle.net/u8ncfdvn/
HTML
<div id="app">
<sr-el :persons="personsFoo" name="foo" type="number"></sr-el>
<br>
<sr-el :persons="personsBar" name="bar" type="text"></sr-el>
</div>
JS
Vue.component('sr-el', {
template: `
<span>
<input :type="type" :name="name" :id="name" v-model="inputVal" :class="{invalid: !persons}">
Here's the bound {{ name }} input value: {{ inputVal }}
</span>
`,
props: ['type', 'name', 'persons'],
data() {
return {
inputVal: this.persons,
}
}
})
new Vue({
el: '#app',
data() {
return {
personsFoo: 1,
personsBar: 2,
}
}
})

As of version 2.5.0, Vue supports dynamic input types, so you're now able to bind type to a data property like you want:
<input :type="type" :name="name" :id="name" v-model="inputVal">
Here's a working fiddle.
For anyone who still needs to use a version earlier than 2.5:
What this error is saying is that, if you dynamically change the input type being sent to the component, Vue will not update the input element to change its type.
You should use v-if statements instead:
<input v-if="type == 'text'" type="text" :name="name" :id="name" v-model="inputVal">
<input v-if="type == 'number'" type="number" :name="name" :id="name" v-model="inputVal">

Related

Is it possible to use a prop as a v-model value?

Is it possible to use the value of a prop as the input's v-model?
I normally do the following when creating an input:
<template>
<form>
<input v-model="form.email" type="email"/>
</form>
</template>
<script>
export default {
data() {
return {
form: {
email: '',
}
}
}
}
</script>
But now I'm trying to achieve the following where this.myProp is used within the v-model without being displayed as a string on the input:
<template>
<form>
<input v-model="this.myProp" type="email"/>
</form>
</template>
<script>
export default {
props: ['myProp'] // myProp = form.email for example (to be handled in a parent component)
}
</script>
Yes, but while using it in parent component. In child component you need to extract value and #input instead of using v-model (v-model is shortcut for value="" and #input) Here is an example of input with label, error and hint in Vue 3 composition API.
BaseInput.vue
<template>
<div class="flex flex-col">
<label>{{ label }}</label>
<input v-bind="$attrs" :placeholder="label" :value="modelValue" #input="$emit('update:modelValue', $event.target.value)">
<span v-for="item of errors" class="text-red-400">{{ item.value }}</span>
<span v-if="hint" class="text-sm">{{ hint }}</span>
</div>
</template>
<script setup>
defineProps({ label: String, modelValue: String | Number, errors: Array, hint: String })
defineEmits(['update:modelValue'])
</script>
Using v-bind="$attrs" you target where attributes like type="email" need to be applied in child component. If you don't do it, it will be added to the top level DOM element. In above scenario <div>.
ParentComponent.vue
<BaseInput type="email" v-model="formData.email" :label="Email" :errors="formErrors.email"/>

How do I have a vue component with a ref apply it to a child?

I have a component that contains an input. When I put a ref on the component I want it to point to the input. Is this possible?
Vue.component('input-icon', {
props: ['value', 'icon'],
template: `
<span class="input-icon flex-container">
<span class="icon-container vertical-center" #click="icon_click" :class="{'icon-click': is_icon_click}">
<i class="material-icons">{{icon}}</i>
</span>
<input ref="input" type="text" :value="value" #input="$emit('input', $event.target.value)" class="full-flex text-left" v-bind="$attrs">
<span class="clear" #click="clear"><i class="material-icons">clear</i></span>
</span>
`,
methods: {
icon_click(e) {if (this.is_icon_click) this.$emit('button-click', e)},
clear() {
this.value = '';
this.$refs.input.focus();
}
},
computed: {
is_icon_click() {return !!this._events['button-click']}
}
});
<input-icon ref="search_bar" icon="search" v-model="search" placeholder="Search"></input-icon>
EDIT: I am aware that I can use this.$refs.search_bar.$refs.input but it's ugly. Just hoping for a better way.
If you want to access the input within the input-icon component. Create a method within the input-icon component that returns the input ref.
That is a cleaner way in my opinion

v-model and scoped slots not working?

I have a component:
<slot name="test" :name="name">
<input type="text" v-model="name">
</slot>
The input is bound to name in data.
When I use the slot in the parent:
<div slot="test" slot-scope="props">
<input type="text" v-model="props.name">
</div>
Data does not update on the child. It's not linked - why?
What you are seeing is actually the parent's default <input>. So you understand what I mean, add some text to both, like:
<slot name="test" :name="name">
Default: <input type="text" v-model="name">
</slot>
<div slot="test" slot-scope="props">
Actual: <input type="text" v-model="props.name">
</div>
You'll see that what appears is the default.
Now, that happens because, it seems like a bug, when the slot prop has the same name as the parent's, the slot does not work.
Workaround: rename the slot prop.
In the example below, I renamed it from name to namex. Notice the v-model in the default remains the same name because anything in the template refers to the props of that template (in other words, slot props, e.g. namex, will never be available in the parent default slot).
<slot name="test" :namex="name">
Default: <input type="text" v-model="name">
</slot>
<div slot="test" slot-scope="props">
Actual: <input type="text" v-model="props.namex">
</div>
to use v-model in scoped slots, the value of v-model must be one level deeper:
Vue.component('render-props', {
data: () => ({message: 'hello', obj: {msg: 'obj_msg'}}),
template: `<div>
<slot name="a" :message="message">
default: {{message}}
<input v-model="message"/>
</slot>
<slot name="b" :obj="obj">
default: {{obj.msg}}
<input v-model="obj.msg"/>
</slot>
</div>`
});
new Vue({
el: "#root",
template: `<div>
<render-props>
<template v-slot:a="props">
actual: {{props.message}}
<input v-model="props.message"/>
</template>
<template v-slot:b="props">
actual: {{props.obj.msg}}
<input v-model="props.obj.msg"/>
</template>
</render-props>
<cus_2 />
</div>`
});
you are not supposed to modify the data you pass to a slot, pretty
much like a prop. You should pass a method instead to change tha
value. You can always pass an object and modify a property (like a
prop) but it's also not recommended
-from https://github.com/vuejs/vue/issues/9726
So I imagine something like
<template>
<slot v-bind:value="value"></slot>
</template>
<script>
export default {
name: 'FooBar',
data() {
value: '',
},
methods: {
updateValue(e) {
this.value = e.target.value;
}
},
};
</script>
Then when using the component <FooBar> instead of using v-model, you can use the passed scoped slot props, the method (updateValue), and actual prop that will be updated, value.
<FooBar v-slot="slotProps">
<input type="text" :value="slotProps.value" #input="slotProps.updateValue" />
</Foobar>

How to point to wrapping custom element with v-model?

I currently have a custom vue-form component:
In my common HTML it kind of looks something like this:
<vue-form>
<input type="text" v-model="test">
<input type="password" v-model="test2">
</vue-form>
Note that this is not a Vue template.
My v-model keeps pointing to my root component when in this case I would like my v-model to point the the actual component that is wrapping my content.
For the vue-form component I am simply using a slot like so:
<template>
<div>
<slot></slot>
</div>
</template>
Is there a way to get the v-model binding to point towards the wrapping component instead of the root element?
You can use Scoped Slots documentation.
Edit:
Here is working example:
<div id="app">
<vue-form v-bind:model="x">
<template scope="props">
<input type="text" v-model="props.model.test">
<input type="password" v-model="props.model.test2">
</template>
</vue-form>
</div>
new Vue({
el: '#app',
data: function() {
return {
x: {
test: 'John',
test2: 'Smith'
}
}
},
components: {
'vue-form': {
template: `<div><slot :model="model"></slot></div>`,
props: ['model']
}
}});
jsfiddle
Just notice that this solution requires from you to propagate property from component to slot by :model="model" part in slot declaration.

How can I get value in datetimepicker bootstrap on vue component?

My view blade, you can see this below :
...
<div class="panel-body">
<order-view v-cloak>
<input slot="from-date" data-date-format="DD-MM-YYYY" title="DD-MM-YYYY" type="text" class="form-control" placeholder="Date" name="from_date" id="datetimepicker" required>
<input slot="to-date" data-date-format="DD-MM-YYYY" title="DD-MM-YYYY" type="text" class="form-control" placeholder="Date" name="to_date" id="datetimepicker" required>
</order-view>
</div>
...
My order-view component, you can see this below :
<template>
<div>
<div class="col-sm-2">
<div class="form-group">
<slot name="from-date" required v-model="fromDate"></slot>
</div>
</div>
<div class="col-sm-1">
<div class="form-group" style="text-align: center">
-
</div>
</div>
<div class="col-sm-2">
<div class="form-group">
<slot name="to-date" required v-model="toDate"></slot>
</div>
</div>
<div class="col-sm-4">
<button v-on:click="filter()" class="btn btn-default" type="button">
<span class="glyphicon glyphicon-search"></span>
</button>
</div>
</div>
</template>
<script>
export default {
data() {
return{
fromDate: '',
toDate: ''
}
},
methods: {
filter: function() {
console.log(this.fromDate)
console.log(this.toDate)
}
}
}
</script>
I using v-model like above code
But, when I click the button, the result of
console.log(this.fromDate)
console.log(this.toDate)
is empty
It display empty
Why it does not work?
How can I solve it?
You cannot bind a slot using v-model and expect that Vue will attach that automatically to your slot input, but I can't see any reason why you need to use a slot here anyway. It looks like you just want an input that you can attach custom attributes to and you can do that by passing the attributes as a prop and use v-bind to bind them:
<template>
<div>
<input v-bind="attrs" v-model="fromDate" />
<button #click="filter">filter</button>
</div>
</template>
export default{
props: ['attrs'],
methods: {
filter() {
console.log(this.fromDate)
}
},
data() {
return {
fromDate: ""
}
}
}
new Vue({
el: "#app",
data: {
fromDateAttrs: {
'data-date-format': "DD-MM-YYYY",
title: "DD-MM-YYYY",
type: "text",
class: "form-control",
placeholder: "Date",
name: "from_date",
id: "datetimepicker",
}
}
});
Now you can just pass your attrs as a prop in the parent:
<my-comp :attrs="fromDateAttrs"></my-comp>
Here's the JSFiddle: https://jsfiddle.net/rvederzc/
EDIT
In reference as to how to create a date picker component, here's how I would implement a jQuery datepicker using Vue.js:
<template id="date-picker">
<div>
<input v-bind="attrs" v-model="date" #input="$emit('input', $event.target.value)" v-date-picker/>
</div>
</template>
<script type="text/javascript">
export default {
props: ['attrs'],
directives: {
datePicker: {
bind(el, binding, vnode) {
$(el).datepicker({
onSelect: function(val) {
// directive talk for 'this.$emit'
vnode.context.$emit('input', val);
}
});
}
}
}
}
</script>
You can then bind that with v-model in the parent:
<date-picker v-model="myDate"></date-picker>
Here's the JSFiddle: https://jsfiddle.net/g64drpg6/
Cant expect any javascript technology to be complete before it becomes famous. Going by that, I tried all the recommendations from using moment to vue-datapicker. All recommendations heavily broke design and needed hardcode of the div id's in the vue initialisation under mounted. Cant introduce hacks into my project this way. Messes up design and implementation neatness.
I fixed it using plain old jsp. On Save, I just did this
vuedata.dateOfBirthMilliSecs = $("#dateOfBirth").val() ;
I'll figure out conversion of date format to milliseconds in my java controller.