Being quite new to Vue, I ran into the issue when I cannot establish binding between input/select tags inside components and the data inside Vue instance.
Example #1:
html
<sidebar-select
:title="UI.sidebar.localeSelect.title"
:name="UI.sidebar.localeSelect.name"
:options="UI.sidebar.localeSelect.options"
:vmodel="selectedLocale">
</sidebar-select>
<sidebar-select
:title="UI.sidebar.currencySelect.title"
:name="UI.sidebar.currencySelect.name"
:options="UI.sidebar.currencySelect.options"
:vmodel="state.currency">
</sidebar-select>
js - component
Vue.component('sidebar-select', {
props: ['title', 'name', 'options', 'vmodel'],
data() {
return {
vmodel: this.value
}
},
template: `<div class="col-xs-12 col-md-6" style="padding-left:0;padding-right:30px">
<div class="cg">
<label :for="name"><h4>{{ title }}</h4></label>
<select class="form-control form-horizontal" style="max-width: 300px"
:name="name"
v-model="vmodel">
<option v-for="option in options" :value="option.value">{{ option.text }}</option>
</select>
</div>
</div>`
});
js - data part
var app = new Vue({
el: '#app',
data: {
selectedLocale: 'ru',
user: {
'ru': {
name: 'Саша',
surname: 'Найдович',
position: 'программист',
phone: '1234567',
email: 'jdlfh#jdlbf.com'
},
'en': {
name: 'Alex',
surname: 'Naidovich',
position: 'frontend',
phone: '1234567',
email: '1234567#email.eu'
}
},
state: {
locale: 'ru',
currency: '€',
/* *** */
},
UI: {
sidebar: {
localeSelect: {
title: 'Язык КП',
name: 'offer-lang',
options: [
{value: 'en', text: 'International - English'},
/* *** */
{value: 'ru', text: 'Русский'}
],
preSelected: 'ru',
stateprop: 'locale'
},
currencySelect: {
title: 'Валюта КП',
name: 'offer-curr',
options: [
{value: '$', text: '$ (Dollar)'},
{value: '€', text: '€ (Euro)'},
{value: '₤', text: '₤ (UK Фунт)'},
{value: '₽', text: '₽ (RUS Рубль)'},
{value: '', text: 'Ввести вручную'}
],
preSelected: '€',
stateprop: 'currency'
},
} /* etc */
}
}
});
There are 2 errors I run into: [Vue warn]: The data property "vmodel" is already declared as a prop. Use prop default value instead. at init and [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "vmodel" vhen I try to change selects. I wish to know what am I doing wrong and what would be the best-practice way for this case.
UPDATE: the main question is about how to properly use v-model two-way-data-binding when passing v-model as an argument into the props of component.
To be updated: example #2 with text inputs will be added tomorrow (the code is left at work).
Related
I'm trying to build an application in VueJS where I'm having a component something like this:
<template>
<div>
<base-subheader title="Members" icon="la-home" heading="Member Details" description="Home"></base-subheader>
<div class="m-content">
<div class="row">
<div class="col-xl-6">
<nits-form-portlet
title="Add Member/User"
info="Fill the details below to add user, fields which are mandatory are label with * (star) mark."
headIcon="flaticon-multimedia"
headerLine
apiUrl="api/user"
backUrl="members__home"
action="create"
:formElements="form_elements"
>
</nits-form-portlet>
</div>
<div class="col-xl-6">
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "member-add",
data() {
return {
form_elements: [
{
field_name: 'first_name',
config_elements: {
label: 'First Name *',
type: 'text',
inputStyle: 'pill',
placeholder: 'Enter first name of user',
formControl: true,
addonType: 'left-icon',
addon: {leftAddon: 'la-exclamation'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'last_name',
config_elements: {
label: 'Last Name',
type: 'text',
inputStyle: 'pill',
placeholder: 'Enter last name of user',
formControl: true,
addonType: 'left-icon',
addon: {leftAddon: 'la-exclamation'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'email',
config_elements: {
label: 'Email *',
type: 'email',
inputStyle: 'pill',
placeholder: 'Enter email of user',
formControl: true,
addonType: 'left',
addon: {leftAddon: '#'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'password',
config_elements: {
label: 'Password *',
type: 'password',
inputStyle: 'pill',
placeholder: 'Enter password',
formControl: true,
addonType: 'left-icon',
addon: {leftAddon: 'la-expeditedssl'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'confirm_password',
config_elements: {
label: 'Confirm Password *',
type: 'password',
inputStyle: 'pill',
placeholder: 'Confirm password should match',
formControl: true,
addonType: 'left-icon',
addon: {leftAddon: 'la-expeditedssl'},
},
value: '',
nitsFormType: 'nits-input'
},
{
field_name: 'role',
config_elements: {
label: 'Select Role *',
inputStyle: 'pill',
options: [
{option: 'Select one'},
{value: '1', option: 'Admin'},
{value: '2', option: 'Subscriber'},
{value: '3', option: 'Analyst'},
{value: '4', option: 'Guest'}
],
addonType: 'left-icon',
addon: {leftAddon: 'la-user-secret'},
},
value: '',
nitsFormType: 'nits-select'
},
{
field_name: 'profile_pic',
config_elements: {
label: 'Profile pic',
},
value: '',
nitsFormType: 'nits-file-input'
}
],
}
}
}
</script>
<style scoped>
</style>
Whenever I try to pass data to my component config_elements which holds an object of all attributes being passed to child component gets lost, if I move from other component to nits-form-portlet> it renders the child components appropriately, but in my Vue-debug tool it shows empty:
But the components are rendered properly:
And same happens to the props inside the component:
But in case of any event or refreshing the page it shows:
Since all the attributes are gone, if I try to configure my config_elements object data, I get the attributes but within a fraction of seconds it again goes to empty. But attributes gets passed and it displays the fields:
I am using render function to render these components so for nits-form-portlet component I have:
return createElement('div', { class: this.getClasses() }, [
createElement('div', { class: 'm-portlet__head' }, [
createElement('div', { class: 'm-portlet__head-caption' }, [element])
]),
createElement('form', { class: 'm-form m-form--fit m-form--label-align-right' }, [
createElement('div', { class: 'm-portlet__body' }, [
createElement('div', { class: 'form-group m-form__group m--margin-top-10' }, [infoElement]),
this.computedFormElements.map(a => {
if(this.error[a.field_name]) {
a.config_elements.error = this.error[a.field_name][0];
return createElement(a.nitsFormType, { attrs: a.config_elements, on: {
input: (event) => {
this.form[a.field_name] = event
}
}})
}
else
return createElement(a.nitsFormType, { attrs: a.config_elements, on: {
input: (event) => {
this.form[a.field_name] = event
}
}})
})
]),
footerElement
])
])
And I think factors affecting the template is this map statement:
this.computedFormElements.map(a => {
if(this.error[a.field_name]) {
a.config_elements.error = this.error[a.field_name][0];
return createElement(a.nitsFormType, { attrs: a.config_elements, on: {
input: (event) => {
this.form[a.field_name] = event
}
}})
}
else
return createElement(a.nitsFormType, { attrs: a.config_elements, on: {
input: (event) => {
this.form[a.field_name] = event
}
}})
})
but still I'm sharing my whole code of render function, Link to code # github hoping someone can help me with this strange situation.
It's a bit hard to tell for me without running the code, but I've noticed something that may be related.
I see that you're overwriting the formElements prop here:
a.config_elements.error = this.error[a.field_name][0];
Vue usually gives you a warning that you can't do that, but maybe because it's abstracted through the computed it's not doing so.
If you want to extend the data for use in the child component, then it's best to do copy of the object (preferably a deep copy using computed, which will allow you to have it dynamically updated)
because config_elements is an object, when you add error variable you are likely triggering an observer, that may be clearing the data in the parent.
Anyway, it's just a guess, but something you may want to resolve anyway.
I'm just new to Vue and I'm struggling with accessing my component data within a v-for scope. I get this error using the following code.
TypeError: Cannot read property 'whatever' of undefined
at eval
<template>
<b-row class="my-1" v-for="field in inputFields" :key="field.key">
<b-col>
<b-form-input :type="field.type" :placeholder="this.whatever" required>
</b-form-input> <!-- placeholder ERROR! -->
</b-col>
</b-row>
<b-form-input :placeholder="this.whatever" required>
</b-form-input> <!-- placeholder OK! -->
</template>
<script>
export default {
data() {
return {
whatever: 'this is a string',
userShouldSignup: false,
parameters: {
email: {
label: 'Enter email',
type: 'email',
value: '',
},
password: {
label: 'Enter password',
type: 'password',
value: '',
},
confirmPassword: {
label: 'Confirm password',
type: 'password',
value: '',
},
},
}
},
computed: {
inputFields() {
return this.userShouldSignup ? [
this.parameters.email,
this.parameters.password,
this.parameters.confirmPassword,
] : [
this.parameters.email,
this.parameters.password,
];
},
}
};
</script>
How do I access my data variables within v-for?
Change :placeholder="this.whatever" to :placeholder="whatever". You don't need to use this there because Vue recognize that you want to access its data or computed values. This doesn't work because this in the loop is something else.
Look at this code snippet below (I had to change some things to reproduce your problem):
var app = new Vue({
el: '#app',
data() {
return {
whatever: 'this is a string',
userShouldSignup: false,
parameters: {
email: {
label: 'Enter email',
type: 'email',
value: '',
},
password: {
label: 'Enter password',
type: 'password',
value: '',
},
confirmPassword: {
label: 'Confirm password',
type: 'password',
value: '',
},
},
}
},
computed: {
inputFields() {
return this.userShouldSignup ? [
this.parameters.email,
this.parameters.password,
this.parameters.confirmPassword,
] : [
this.parameters.email,
this.parameters.password,
];
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
<div class="my-1" v-for="field in inputFields" :key="field.key">
<div>
<input type="text" :placeholder="whatever" required/>
</div>
</div>
<input :placeholder="whatever" required/>
</div>
It's not working because "this" is pointing to "field" (loop), and the "field" doesn't have a "whatever" variable.
You just need to remove "this" keyword. You don't need it at all when access the variables from "data"
In a VueJS 2.0 I want to update the rendering of items using a v-for directive when a selection changes.
html:
<main>
{{ testvalue }}
<select v-model="selected.name">
<option v-for="foo in foobar">{{foo.name}}</option>
</select>
<span>Selected: {{ selected.name }}</span>
<div v-for="(value, key) in selected.properties">
<p>{{ key }} {{ value }}</p>
</div>
</main>
app.js
new Vue({
el: 'main',
data: {
foobar: [
{ name: 'rex', type: 'dog', properties: [{color: 'red', sound: 'load'}] },
{ name: 'tike', type: 'cat', properties: [{color: 'brown', sound: 'quiet'}] },
{ name: 'tiny', type, 'mouse', properties: [{color: 'white', sound: 'quiet'}]}
],
selected: { name: 'tiny', type, 'mouse', properties: [{color: 'white', sound: 'quiet'}]},
testvalue: 'Test'
},
created: function () {
this.selected = this.foobar[0];
},
mounted: function () {
this.selected = this.foobar[1];
}
})
jsfiddle here: https://jsfiddle.net/doritonimo/u1n8x97e/
The v-for directive does not update when the selected object it is getting it's data from changes. Is there a way to tell the v-for loop to update when data changes in Vue?
You fiddle still contains some typo so it doesn't work.
Please check: https://jsfiddle.net/u1n8x97e/39/
You don't need to store the entire selected object. You can just keep the index.
<select v-model="selected">
<option v-for="(foo, i) in foobar" :value="i">{{foo.name}}</option>
</select>
Now selected is just a number:
selected: 0
And you access the selected object by foobar[selected]
I'm building a form with a long list of select items using vue.js. I'm using the dynamic select list documented here: http://012.vuejs.org/guide/forms.html#Dynamic_Select_Options
However, I want to allow users to quickly filter this list using the filterBy directive. I don't see how you can combine these two-- is it possible to filter a dynamic list? Or can filterBy only be used against a v-for?
In 0.12 you can use filters with the options param. The docs show a syntax that looks identical to filtering v-for.
Here is an example showing filterBy (uses version 0.12.16):
var app = new Vue({
el: '#app',
data: {
selected: '',
options: [
{ text: '1', value: 1, show: true },
{ text: '2', value: 2, show: false },
{ text: '3', value: 3, show: true },
{ text: '4', value: 4, show: true },
{ text: '5', value: 5, show: false },
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/0.12.16/vue.js"></script>
<div id="app">
Filter by 'show' <br>
<select v-model="selected" options="options | filterBy true in 'show'"></select>
</div>
Note: the options param for <select v-model> has been deprecated in 1.0. In 1.0, you can use v-for directly within <select>. v-for can be nested to use optgroups.
Check this fiddle https://jsfiddle.net/tronsfey/4zz6zrxv/5/.
<div id="app">
<input type="text" v-model="keyword">
<select name="test" id="test">
<optgroup v-for="group in list | myfilter keyword" label="{{group.label}}">
<option value="{{item.price}}" v-for="item in group.data">{{item.text}}</option>
</optgroup>
</select>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.js"></script>
new Vue({
el: '#app',
data: function () {
return {
keyword : '',
list: [
{
label:'A',
data:[
{price: 3, text: 'aa'},
{price: 2, text: 'bb'}
]
},
{
label:'B',
data:[
{price: 5, text: 'cc'},
{price: 6, text: 'dd'}
]
}
]
}
},
filters : {
myfilter : function(value,keyword){
return (!keyword || keyword === 'aaa') ? value : '';
}
}
})
You can use v-for to create optgroups and then use filterBy or a custom filter instead(as in the fiddle) to filter your options data.
Hope it would help.
I am trying to load some content into a component with Vue.js. I am trying to do this with the :is attribute. I keep getting "[Vue warn]: Failed to resolve component: interview"
<todo-tabs :list="items"></todo-tabs>
<template id="interview-slot">
//Interview Slot Content
</template>
<template id="membership-slot">
//Interview Slot Content
</template>
<script>
Vue.component('todo-tabs', {
template: '#todo-tabs',
props: ['list'],
data: function(){
return {
currentView: 'interview',
components:{
interview:{
template: '#interview-slot'
},
membership:{
template: '#membership-slot'
}
}
}
}
});
var vm = new Vue({
el: "#todo",
data: {
items : [
{id: 'interview', name: 'interview', complete: true, body: 'something1', step_content: 'SOME',current: true },
{id: 'membership', name: 'membership', complete: true, body: 'something2', step_content: 'SOME' },
{id: 'profile', name: 'profile', complete: false, body: 'something3', step_content: 'SOME' },
{id: 'handoff', name: 'handoff', complete: false, body: 'something4', step_content: 'SOME'}
]
}
});
</script>
I really don't want to put my html in the json if possible as it content lots of form elements.
Thank you in advance for any help
In this part of the code:
<template id="interview-slot">
//Interview Slot Content
</template
You are missing the closing > after </template.