i have the following vue component
<template>
<div id="input-div">
<label v-if="label">{{ label }}</label>
<select :multiple="multiple" :value="value" #change="change($event.target.value)">
<slot></slot>
</select>
<div class="error"><slot name="error"></slot></div>
</div>
</template>
<script>
export default {
name: 'ha-select',
props: ['label', 'value', 'multiple'],
methods: {
change(value) {
this.$emit('input', value);
}
}
}
</script>
and since it is a select component i want it to be usable with v-model which works when the multiple attribute is not set but when i set multiple pass v-model an Array instead of appending the selected value to the Array, the array just gets replaced by the value of the current selected option. maybe it has something to do with the way i am emitting the input event. So how do i create a custom select component with multiple support?
I've modified the code slightly to use a local variable bound to the select with v-model and emit the bound value. This results in emitting the array of selected values when multiple is true.
console.clear()
const CustomSelect = {
template: `
<div id="input-div">
<label v-if="label">{{ label }}</label>
<select :multiple="multiple" v-model="selected" #change="$emit('input', selected)">
<slot></slot>
</select>
<div class="error"><slot name="error"></slot></div>
</div>
`,
name: 'ha-select',
props: ['label', 'value', 'multiple'],
data(){
return {
selected:this.value
}
},
}
new Vue({
el:"#app",
data:{
albums: []
},
components: {
CustomSelect
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<custom-select label="Pink Floyd Albums" v-model="albums" :multiple="true">
<option value="1">Dark Side of the Moon</option>
<option value="2">Animals</option>
<option value="3">The Wall</option>
</custom-select>
<div>
Selected Albums {{albums}}
</div>
</div>
Related
I have a list of properties in my database. I want the user to select the type of houses in the property.
Property
Selected property types
Property 1
['studio_apartment', 'one_bedroom', 'two_bedroom']
Property 2
['studio_apartment', 'one_bedroom']
To achieve this I am using the Vue v-for loop as follows:
<template>
<div v-for="(name, index) in propertyNames" :key="index">
NAME: {{ name }}<br />
<input
type="checkbox"
value="studio_apartment"
v-model="form.property_type"
/>Studio apartment<br />
<input
type="checkbox"
value="one_bedroom"
v-model="form.property_type"
/>One bedroom<br />
<input
type="checkbox"
value="two_bedroom"
v-model="form.property_type"
/>Two bedroom<br />
SELECTED: {{}}
</div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
data() {
return {
form: {
property_type: [],
},
};
},
</script>
The problem is when I select for example studio_apartment in property 1, then all the properties studio_apartment is checked. How do I only get the selected checkboxes for each property individually. Thanks.
This happens because there's only one form instance being shared in each loop iteration.
You could change form into an object array based on propertyNames, so that they have the same array length; then index into form with the iteration index of v-for:
<template>
<div>
<div v-for="(name, index) in propertyNames" :key="index">
NAME: {{ name }}<br>
<input type="checkbox" value="studio_apartment" v-model="form[index].property_type">Studio apartment<br>
<input type="checkbox" value="one_bedroom" v-model="form[index].property_type">One bedroom<br>
<input type="checkbox" value="two_bedroom" v-model="form[index].property_type">Two bedroom<br>
</div>
</div>
</template>
<script>
export default {
data() {
const propertyNames = ['Property 1', 'Property 2']
return {
propertyNames,
form: propertyNames.map(name => ({ property_type: [] }))
}
}
}
</script>
demo
capture the content of a component child, this component and this in turn calls another, in this second component there is an html, in this second component in the code javascripot capture this component to use it
first component, has as component child -- field --
<template>
<div id="login">
<field type="select" name="lang">
<option>one</option>
<option>zero</option>
</field>
</div>
</template>
second component, is field
<template>
<div class="field input">
<label>{{label}}</label>
<div v-html="htmlfield"> </div>
</div>
</template>
<script>
export default {
props: ['type'],
computed : {
htmlfield : function(){
switch (this.type) {
case 'select':
return `<select ${attr}>**__here__**</select>`; break;
}
},
},
}
</script>
__here__ I want to put the options indicated in the first component
I would like to be able to capture with javascript the html of a component
It looks like you're trying to create a universal <field> that you can pass the type of input you want.
Can I suggest a different approach?
First component
<template>
<div id="login">
<field type="select" :options="options" name="lang"></field>
</div>
</template>
<script>
export default {
data () {
return {
options: [
{ label: 'one', value: 'value1' },
{ label: 'zero', value: 'value2' }
]
}
}
}
</script>
Second component:
<template>
<div class="field input">
<label>{{ label }}</label>
<select v-if="type === 'select'">
<option v-for="option in options" :value="option.value" :key="option.value">{{ option.label }}</option>
</select>
<input v-if="type === 'text'" type="text">
<input v-if="type === 'number'" type="number">
</div>
</template>
<script>
export default {
props: ['label', 'type', 'options']
}
</script>
Here, instead of writing the <option> elements in the parent template, we create option objects in an array called options and pass it to <field> as a prop, then we can render the <option> elements in the child IF the 'type' === 'select'.
I have a simple text field in Vue.js:
new Vue({
el: '#app',
data: {
value: 'Test'
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-model="value" ref="input" />
<p>{{ value }}</p>
</div>
Is it possible to decrease the frequency of updating the value in the data model from 'onChange' to 'onBlur'?
v-model is just syntax sugar for =>
:value="modelValue" #input="modelValue = $event.target.value"
If you want something else, it's very easy to do. Just change the update side to onBlur, so =>
<input class="form-control
:value="value"
#blur="value = $event.target.value"
#input="value = $event.target.value"
>
The improved example code:
new Vue({
el: '#app',
data: {
value: 'Test'
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input
type="text"
:value="value"
#blur="value = $event.target.value"
ref="input"
/>
<p>{{ value }}</p>
</div>
Yes, you should Just add #blur event, and pass through it the value of the event
Then when this event gets triggered in methods, it will change the value of result to the input value...so the updating became only conditioned to blur of the input
new Vue({
el: '#app',
data: {
result: '',
value:''
},
methods:{
blo(e){
this.result = e
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div id="app">
<input type="text" #blur='blo(value)' v-model="value" />
<p>{{ result }}</p>
</div>
</div>
I forked a fiddle with a simple radio button functionality and changed it a bit. It works perfectly, as you can see in this fiddle:
Vue.component('radio-button', {
props: ['id', 'value'],
template: `
<label class="radio">
<input type="radio" :value="id" v-model="radioButtonValue">
<span>{{ id }}</span>
</label>
`,
computed: {
radioButtonValue: {
get: function() {
return this.value;
},
set: function() {
this.$emit("input", this.id);
}
}
}
});
Vue.component('example-form', {
template: `
<div>
<radio-button id="1" v-model="selectedValue"/>
<radio-button id="2" v-model="selectedValue"/>
<radio-button id="3" v-model="selectedValue"/>
<div class="result">
Radio button selection: {{selectedValue}}
</div>
</div>
`,
data: function() {
return {
selectedValue: null
};
}
});
new Vue({
el: '#my-app',
template: `<example-form></example-form>`
});
<script src="https://unpkg.com/vue#2.0.4/dist/vue.js"></script>
<div id="my-app"></div>
However, it only seems to work when the emitted event in the radio-button component is input.
If, for example, I want to use a different value name ("myvar") and a custom event name ("foobar") by utilizing the model property to do so, it doesn't work at all:
Vue.component('radio-button', {
props: ['id', 'myval'],
model: {
prop: 'myval',
event: 'foobar'
},
template: `
<label class="radio">
<input type="radio" :value="id" v-model="radioButtonValue">
<span>{{ id }}</span>
</label>
`,
computed: {
radioButtonValue: {
get: function() {
return this.myval;
},
set: function() {
this.$emit("foobar", this.id);
}
}
}
});
Vue.component('example-form', {
template: `
<div>
<radio-button id="1" v-model="selectedValue"/>
<radio-button id="2" v-model="selectedValue"/>
<radio-button id="3" v-model="selectedValue"/>
<div class="result">
Radio button selection: {{selectedValue}}
</div>
</div>
`,
data: function() {
return {
selectedValue: null
};
}
});
new Vue({
el: '#my-app',
template: `<example-form></example-form>`
});
<script src="https://unpkg.com/vue#2.0.4/dist/vue.js"></script>
<div id="my-app"></div>
What the model property does is it specifies that:
<radio-button id="1" v-model="selectedValue"/>
is "translated" to:
<radio-button id="1" :myvar="selectedValue" #foobar="selectedValue = $event"/>
instead of the default:
<radio-button id="1" :value="selectedValue" #input="selectedValue = $event"/>
is that right?
Am I missing something or is this a bug in Vue?
What you are describing is how v-model works
v-model="anything" is shorthand for :value="anything" #input="anything = $event". So unless you follow that convention of accepting a value prop and emiting an input event, your parent component will not work.
The reason that your first example works is because your radio button component accepts a value prop and you emit an input
So in this case you have two options, follow the convention of v-model in your radio-button component or do something custom and pass down any prop name and emit any event up.
I want to create a simple form builder with Vue where users click on buttons from a menu to add different form fields to a form. I know that if there was just one type of form field to add, I could do it with something like this (https://jsfiddle.net/u6j1uc3u/32/):
<div id="app">
<form-input v-for="field in fields"></form-input>
<button type="button" v-on:click="addFormElement()">Add Form Element</button>
</div>
<script type="x-template" id="form-input">
<div>
<label>Text</label>
<input type="text" />
</div>
</script>
And:
Vue.component('form-input', {
template: '#form-input'
});
new Vue({
el: '#app',
data: {
fields: [],
count: 0
},
methods: {
addFormElement: function() {
this.fields.push({type: 'text', placeholder: 'Textbox ' + (++this.count)});
}
}
})
But what if there's more than one type of form field (input, file, select, etc...)? I was thinking maybe build a different component for each type, but then how would I show multiple types of components in a single list of form elements?
Could I maybe create a component with children components of different types based on the data in the fields array?
Or is there a better way to go about this situation that I'm missing? I've just started learning Vue, so any help is appreciated!
Ok, so I looked into dynamic elements and managed to pull this together:
Vue.component('form-input', {
template: '#form-input'
});
Vue.component('form-select', {
template: '#form-select'
});
Vue.component('form-textarea', {
template: '#form-textarea'
});
new Vue({
el: '#app',
data: {
fields: [],
count: 0
},
methods: {
addFormElement: function(type) {
this.fields.push({
'type': type,
id: this.count++
});
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.min.js"></script>
<div id="app">
<component v-for="field in fields" v-bind:is="field.type" :key="field.id"></component>
<button type="button" v-on:click="addFormElement('form-input')">Add Textbox</button>
<button type="button" v-on:click="addFormElement('form-select')">Add Select</button>
<button type="button" v-on:click="addFormElement('form-textarea')">Add Textarea</button>
</div>
<script type="x-template" id="form-input">
<div>
<label>Text</label>
<input type="text" />
</div>
</script>
<script type="x-template" id="form-select">
<div>
<label>Select</label>
<select>
<option>Option 1</option>
<option>Option 2</option>
</select>
</div>
</script>
<script type="x-template" id="form-textarea">
<div>
<label>Textarea</label>
<textarea></textarea>
</div>
</script>
So instead of creating a new form-input component for each item in the fields array, I'm creating a new component that is associated with the correct component via the type property of the fields
You can pass the field object as props of your form-input component and make the type dynamic:
Vue.component('form-input', {
template: '#form-input',
props: ['field']
})
new Vue({
el: '#app',
data: {
fields: [],
inputType: '',
count: 0
},
methods: {
addFormElement(val) {
this.fields.push({type: val, placeholder: 'Textbox ' + (++this.count)});
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<h3>Add form element</h3>
<select size="3" v-model='inputType' #click="addFormElement(inputType)">
<option value="text">Text</option>
<option value="checkbox">Checkbox</option>
<option value="radio">Radio</option>
</select>
<p>
<form-input v-for="field in fields" :field="field"></form-input>
</p>
</div>
<template id="form-input">
<div>
<label>{{ field.type }}</label>
<input :type="field.type" />
</div>
</template>
Based on the code from the answer, one could add dynamic content for each one of those form controls as well ( the full concept could be seen from the following site):
Vue.component('form-input', {
template: '#form-input'
, props: ['label','cnt']
});
Vue.component('form-select', {
template: '#form-select'
, props: ['label','cnt']
});
Vue.component('form-textarea', {
template: '#form-textarea'
, props: ['label','cnt']
});
new Vue({
el: '#app',
data: {
fields: [],
count: 0
}
, mounted() {
// fetch those from back-end
this.addFormElement('form-input','lbl', "form-input-content")
this.addFormElement('form-textarea','lbl', "form-textarea-content")
var select_cnt = [
{'value': 1, 'text': 'item-01'},
{'value': 2, 'text': 'item-02'}
]
this.addFormElement('form-select','some-label',select_cnt)
}
, methods: {
addFormElement: function(type,label,cnt) {
this.fields.push({
'type': type
, id: this.count++
, 'label':label
, 'cnt':cnt
});
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.min.js"></script>
<div id="app">
<component v-for="field in fields" v-bind:is="field.type" :key="field.id" :cnt="field.cnt" :label="field.label"></component>
</div>
<script type="x-template" id="form-input">
<div v-on:keyup.tab="this.document.execCommand('selectAll',false,null);">
<label>{{label}}</label>
<input type="text" :value="cnt"/>
</div>
</script>
<script type="x-template" id="form-textarea">
<div v-on:keyup.tab="this.document.execCommand('selectAll',false,null);">
<label>{{label}}</label>
<textarea :value="cnt"></textarea>
</div>
</script>
<script type="x-template" id="form-select">
<div>
<label>Select</label>
<select>
<option v-for="oitem in cnt" :value="oitem.value">{{oitem.text}}</option>
</select>
</div>
<div v-html="cnt"></div>
</script>