Dynamic number of input fields - javascript

I'm working in a installment payment Component, and I need a dynamic list of Form Inputs, according the number of installment payments the User selects (from 1 to 12).
I don't know how I could get the values of the future Form Inputs.
When I have just one Form Input, I use 'v-model' to capture the value of the input field that already exists.
But with multiple instances of a component inside a loop, I can't figure it out.
// The User choose 6
// The value 6 (Number) goes to a property inside data() with v-model
data () {
return {
numberOfFields: 6
}
}
Then to a v-for
<template v-for="n in numberOfFields">
<input type="text" v-model="????">
</template>
I don't want to create every possibility like:
data(){
return {
inputField1: '',
inputField2: '',
inputField3: '',
// up to inputField12
}
}
I want to capture the value of the input field only if the input exists, but without creating every possible option beforehand.

You can use:
<template v-for="n in numberOfFields">
<input type="text" v-model="$data['inputField' + n]">
</template>
Demo:
new Vue({
el: '#app',
data() {
return {
numberOfFields: 6,
inputField1: '11',
inputField2: '22',
inputField3: '33',
inputField4: '44',
inputField5: '55',
inputField6: '66',
// up to inputField12
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<template v-for="n in numberOfFields">
<input type="text" v-model="$data['inputField' + n]"> {{ $data['inputField' + n] }}<br>
</template>
</div>
But this is unusual. Generally, we create another object and use it (instead of the data root).
Example using another object, called fields:
new Vue({
el: '#app',
data() {
return {
numberOfFields: 6,
fields: {
inputField1: '11',
inputField2: '22',
inputField3: '33',
inputField4: '44',
inputField5: '55',
inputField6: '66',
// up to inputField12
}
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<b>Using numberOfFields:</b><br>
<template v-for="n in numberOfFields">
<input type="text" v-model="fields['inputField' + n]"> {{ fields['inputField' + n] }}
</template>
<br><hr><br>
<b>Using (val, key):</b><br>
<template v-for="(val, key) in fields">
<input type="text" v-model="fields[key]"> {{ fields[key] }}
</template>
</div>

Related

Dinamically modify data in Vue instance based on input element

I'm a beginner with Vue and JS and I'm struggling to update the Vue instance's data attribute based on user input. This is what I have as the template for the Vue component (located inside taskTemplate variable):
<div>
<input type="text" placeholder="Insert your task" v-model="desc"/>
<p>{{ desc }}</p>
</div>
The Vue component is defined as follows:
Vue.component("task-item", {
props: {
id: Number,
desc: String,
},
template: taskTemplate
});
And this is populated in the HTML as follows:
<div id="task-list">
<task-item
v-for="item in taskList"
v-bind="item"
></task-item>
<div id="add-more">
<button v-on:click="newTask" type="button">Add a new task</button>
</div>
</div>
Where the task list is created with the Vue instance:
var app = new Vue({
el: "#task-list",
data: {
taskList: [
{ id: 1, desc: "" },
{ id: 2, desc: "" },
]
},
methods: {
newTask() {
this.taskList.push({ id: this.taskList.length + 1, desc: "" })
}
}
});
My problem is that after updating the input element in the webpage, the component's property gets updated, but if I type in the console app.taskList[0].desc it still returns an empty string.
The end goal is to send the data the user has introduced in an API call, so if I can access Vue components instead of the taskList within the data property it is still ok. I would like to know the best practices here as well.
Props shouldn't be used in a two-way binding. Instead, bind their value to input :value, and emit any changes to the parent component.
<div>
<input
type="text"
placeholder="Insert your task"
:value="desc"
#input="$emit('update:desc', $event.target.value)"
/>
<p>{{ desc }}</p>
</div>
In the parent component then you have to listen to update event and update the source value:
<div id="task-list">
<task-item
v-for="item in taskList"
:key="item.id" // do not forget about key
v-bind="item"
#update:desc="item.desc = $event"
// or you can use the sync modifier to listen to update events for all item's props.
// v-bind.sync="item"
></task-item>
<div id="add-more">
<button v-on:click="newTask" type="button">Add a new task</button>
</div>
</div>
Try to use :value and #input instead v-model :
Vue.component('task-item', {
template: `
<div>
<input type="text"
placeholder="Insert your task"
:value="desc"
#input="$emit('update', $event.target.value)"/>
<p>{{ desc }}</p>
</div>
`,
props: {
id: Number,
desc: String,
},
//template: taskTemplate
})
new Vue({
el: '#demo',
data: {
taskList: [
{ id: 1, desc: "" },
{ id: 2, desc: "" },
]
},
methods: {
newTask() {
this.taskList.push({ id: this.taskList.length + 1, desc: "" })
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div id="task-list">
<task-item
v-for="(item, index) in taskList"
:key="index"
v-bind="item"
#update="item.desc = $event"
></task-item>
<div id="add-more">
<button v-on:click="newTask" type="button">Add a new task</button>
</div>
</div>
</div>

Vuejs set a variable maximum for a number input

I have the following vue js script:
<template>
<div>
<div v-if='cart.length > 0'>
<h1>Your Cart</h1>
<template>
<fieldset v-for='product in cart'>
<image :src='product.image'
<h4>{{product.name}}</h4>
<input type='number' :max='quantCheck'/>
<h5>{{product.price}}</h5>
</fieldset>
</template>
</div>
<div v-else><h1>Your Cart Is Empty</h1></div>
<br />
<h5>Subtotal: </h5>
<h5>Shipping: Free for a limited time!</h5>
<h2>Total: </h2>
</div>
</template>
<script>
const apiURL = 'http://localhost:3000';
import axios from 'axios';
export default {
data() {
return {
cart: [
{
id:"56461",
name:"lilly",
quantity: 2,
price: 30.10
}, {
id:"1253",
name:"wild",
quantity: 1,
price: 31.10
}
]
}
},
methods: {
let quantCheck = this.cart.product.quantity
}
}
</script>
I haven't been able to find a good way to make something like this work.
The quantity is variable, and I guess maybe I could make a function that checks the number after each input and pops an error when it goes above but that's not quite the goal.
Anyway sorry if this is a stupid question but thanks for your help in advance!
You can use HTML Form validation for input (type="number"):
<input type='number' :max='product.quantity'/>
If the input is greater than max value then it will show error on Submit the form
I believe that what you want to do is limit the number of items in <input type='number' :max='quantCheck'/> based on the quantity property of the item in your cart. If this is the case, there's a few things that can be improved in your component.
First, you are binding :max="quantityCheck" to your input. looking at your component, you have defined quantityCheck in the methods option.
methods: {
let quantCheck = this.cart.product.quantity
}
This is incorrect, there's no method declaration. You'll need to limit the number of characters using the quantity property directly:
new Vue({
el: '#app',
template: `
<div>
<fieldset v-for='product in cart'>
<h4>{{product.name}}</h4>
<input type='number' :max="product.quantity"/>
<h5>{{product.price}}</h5>
</fieldset>
</div>`,
data: () => ({
cart: [
{
id: "56461",
name: "lilly",
quantity: 2,
price: 30.10
},
{
id: "1253",
name: "wild",
quantity: 1,
price: 31.10
}
]
})
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
The above works, to test it, enter a value higher than the quantity and the input should highlight on blur
If you want better validation, I would suggest using Vee-Validate for a simple way to validate your inputs.
Using VeeValidate
Vue.use(VeeValidate);
new Vue({
el: '#app',
template: `
<div>
<fieldset v-for='product in cart'>
<h4>{{product.name}}</h4>
<input v-validate="'max_value:'+product.quantity" :name="product.name" type="number">
<span v-if="errors.first(product.name)">Quantity cannot be more than {{product.quantity}}</span>
<h5>{{product.price}}</h5>
</fieldset>
</div>`,
data: () => ({
cart: [{
id: "56461",
name: "lilly",
quantity: 2,
price: 30.10
},
{
id: "1253",
name: "wild",
quantity: 1,
price: 31.10
}
]
})
});
<script src="https://cdn.jsdelivr.net/npm/vee-validate#latest/dist/vee-validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

Binding property model value to array with the value from checkbox and inputbox

I am implementing the v-for as follows:
new Vue({
el: '#app',
data: {
PaymentType:[
{Id: 1, Text:"Cash"},
{Id: 2, Text:"Check"},
{Id: 3, Text:"Paypal"}
],
SelectedType:[]
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p> SelectedType {{ SelectedType }}</p>
<div v-for="(pay, index) in PaymentType">
<input type="checkbox" v-model="SelectedType" :value="{PayTypeId: pay.Id}" />
{{pay.Text}}
<input type="input" v-model="SelectedType" />
<!-- What goes here on :value attributes? -->
</div>
</div>
<div id="app">
<p> SelectedType {{ SelectedType }}</p>
<div v-for="(pay, index) in PaymentType">
<input type="checkbox" v-model="SelectedType" :value="{PayTypeId: pay.Id}"/> />
{{pay.Text}}
<input type="input" v-model="SelectedType" :value=""/>
<!-- What goes here on :value attributes? -->
</div>
</div>
Initially SelectedType is empty array object.
Is there any way in Vue to push the Id's to the PayTypeId and the value from the input to Remarks, on same array objects?
new Vue({
el: '#app',
data: {
PaymentType:[
{Id: 1, Text:"Cash"},
{Id: 2, Text:"Check"},
{Id: 3, Text:"Paypal"}
],
SelectedType:[]
}
})
SelectedType is an array of object, it signature looks like SelectedType:[{PayTypeId:0, Remarks:''}].
Is it possible to map the value from checkbox and input box to a Array of Object?
What you need is a bit complicated. The cleanest solution I see is to:
track the checked checkboxes and typed remarks separately, and
turning SelectedType into a computed property that merges those values.
In the example below the checked checkboxes are in selectePayTypeIds and the typed remarks are in typedRemarks.
Notice SelectedType is no longer in data but now in computed.
new Vue({
el: '#app',
data: {
PaymentType:[
{Id: 1, Text:"Cash"},
{Id: 2, Text:"Check"},
{Id: 3, Text:"Paypal"}
],
selectePayTypeIds: {},
typedRemarks: {}
},
computed: {
SelectedType: function () {
var vm = this;
return Object.keys(vm.selectePayTypeIds).filter(function (selectedPayTypeId) {
return vm.selectePayTypeIds[selectedPayTypeId];
}).map(function (selectedPayTypeId) {
return {PayTypeId: selectedPayTypeId, Remarks: vm.typedRemarks[selectedPayTypeId] || ''}
});
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p> SelectedType {{ SelectedType }}</p>
<div v-for="(pay, index) in PaymentType">
<input type="checkbox" v-model="selectePayTypeIds[pay.Id]" />
{{pay.Text}}
<input type="input" v-model="typedRemarks[pay.Id]">
<!-- What goes here on :value attributes? -->
</div>
</div>
Making the SelectedType computed property editable/assignable again
Since you actually want to also be able to assign values to the SelectedType property, we need to define a set function in the SelectedType computed.
To show an example of value being assigned to the SelectedType, have a look at the created function below.
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
PaymentType:[
{Id: 1, Text:"Cash"},
{Id: 2, Text:"Check"},
{Id: 3, Text:"Paypal"}
],
selectePayTypeIds: {},
typedRemarks: {}
},
created: function () {
this.SelectedType = [{PayTypeId: 2, Remarks: 'remarks about two'}];
},
computed: {
SelectedType: {
get: function () {
var vm = this;
return Object.keys(vm.selectePayTypeIds).filter(function (selectedPayTypeId) {
return vm.selectePayTypeIds[selectedPayTypeId];
}).map(function (selectedPayTypeId) {
return {PayTypeId: selectedPayTypeId, Remarks: vm.typedRemarks[selectedPayTypeId] || ''}
});
},
set: function (newSelectedType) {
var vm = this;
newSelectedType.forEach(function(sType) {
Vue.set(vm.selectePayTypeIds, sType.PayTypeId, true);
Vue.set(vm.typedRemarks, sType.PayTypeId, sType.Remarks);
});
}
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p> SelectedType {{ SelectedType }}</p>
<div v-for="(pay, index) in PaymentType">
<input type="checkbox" v-model="selectePayTypeIds[pay.Id]" />
{{pay.Text}}
<input type="input" v-model="typedRemarks[pay.Id]">
<!-- What goes here on :value attributes? -->
</div>
</div>

proper use of Vue $refs

Im attempting to recreate this exact inline editing functionality in on of my vue components. However, and I may be wrong, I see some of the syntax is outdated Vue, in particular the v-el directive being used. I've attempted to update the syntax like so:
new Vue({
el: '#app',
data: {
numbers: [{
val: 'one',
edit: false
},
{
val: 'two',
edit: false
},
{
val: 'three',
edit: false
}
]
},
methods: {
toggleEdit: function(ev, number) {
number.edit = !number.edit
// Focus input field
if (number.edit) {
Vue.nextTick(function() {
ev.$refs.input.focus(); // error occurs here
})
}
},
saveEdit: function(ev, number) {
//save your changes
this.toggleEdit(ev, number);
}
}
})
<div id="app">
<template v-for="number in numbers">
<span v-show="!number.edit"
v-on:click="toggleEdit(this, number)">{{number.val}}</span>
<input type="text"
ref="input"
v-model="number.val"
v-show="number.edit"
v-on:blur="saveEdit(ev, number)"> <br>
</template>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
However I get a range of errors... any suggestions on how to properly execute this?
Here is the Error:
[Vue warn]: Error in nextTick: "TypeError: undefined is not an object
(evaluating 'ev.$refs.input')"
Many things changed from Vue.js 1.x to 2.x. I will walk you through the changes necessary in that snippet of yours:
v-repeat should be v-for
Replace v-el="input" with ref="input"
Since you are using ref="input" inside a v-for, then this.$refs.input will be an array of elements, not a single element.
To access each single element, you will need an index (for the array), that's why you should include the index variable in the v-for: v-for="(number, index) in numbers"
Pass the index instead of the ev to the functions, so you can get the<input>s later using vm.$refs.input[index].focus();
And that's pretty much it. After changes you'll get:
new Vue({
el: '#app',
data: {
numbers: [
{
val: 'one',
edit: false
},
{ val: 'two',
edit: false
},
{
val: 'three',
edit: false
}
]
},
methods: {
toggleEdit: function(index, number){
number.edit = !number.edit;
// Focus input field
var vm = this;
if (number.edit){
Vue.nextTick(function() {
vm.$refs.input[index].focus();
})
}
},
saveEdit: function(index, number){
//save your changes
this.toggleEdit(index, number);
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<template v-for="(number, index) in numbers">
<span v-show="!number.edit"
v-on:click="toggleEdit(index, number)">{{number.val}}</span>
<input type="text"
ref="input"
v-model="number.val"
v-show="number.edit"
v-on:blur="saveEdit(index, number)"> <br>
</template>
</div>
If you want the functionality and not the code design, I'd recommend you redesign it. I think you want to edit data, and the data shouldn't have to know whether it's being edited. That is the role of a component.
So let's make a component that lets you v-model data. The component itself has a span and an input. If you're editing, it shows the input, otherwise, the span. Click starts editing, blur stops editing. When editing starts, set focus on the input.
It takes a value prop. Its input element emits an input event to signal changes (per component v-model spec.
new Vue({
el: '#app',
data: {
stuff: ['one', 'two', 'three']
},
components: {
inlineEditor: {
template: '#inline-editor-template',
props: ['value'],
data() {
return {
editing: false
}
},
methods: {
startEditing() {
this.editing = true;
this.$nextTick(() => this.$refs.input.focus());
},
stopEditing() {
this.editing = false;
}
}
}
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<inline-editor v-for="item, index in stuff" v-model="stuff[index]"></inline-editor>
</div>
<template id="inline-editor-template">
<div>
<span #click="startEditing" v-show="!editing">{{value}}</span>
<input ref="input" :value="value" #input="e => $emit('input', e.target.value)" #blur="stopEditing" v-show="editing">
</div>
</template>

Vue.js: How to concatenate v-model value

How to make this work:
<div v-for="index in 4">
<input v-model="'invoice_' + index" >
</div>
just like this:
<div v-for="index in 4">
<input v-model="invoice_1" >
</div>
It could be better if you put all the invoices_s variables in a single object and then refer to them by key, something like follows:
new Vue({
el: '#app',
data: {
invoices: {
invoice_1: '1',
invoice_2: '2',
invoice_3: '3',
invoice_4: '4'
}
}
})
<script src="https://unpkg.com/vue#2.5.3/dist/vue.js"></script>
<div id="app">
<div v-for="index in 4">
<input v-model="invoices['invoice_' + index]">
</div>
</div>
If you are not able to change how your data is structured, you can access the data object via $data. So you could bind to your invoice properties via $data['invoice_' + index]:
new Vue({
el: '#app',
data() {
return {
invoice_1: 'a',
invoice_2: 'b',
invoice_3: 'c',
invoice_4: 'd'
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
<div v-for="index in 4">
<input v-model="$data['invoice_' + index]" >
</div>
</div>
However, I agree with Psidom that it would make more sense to change your data structure if you are able to. Although, I would make invoices an array and just access each invoice by the index. This way you don't need to explicitly name each invoice with an index:
new Vue({
el: '#app',
data() {
return {
invoices: ['a', 'b', 'c', 'd' ]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
<div v-for="index in 4">
<input v-model="invoices[index-1]">
</div>
</div>
Even better would be to make invoices an array of objects, each with its own id and value. This way you can render the entire array of invoices (via v-for="invoice in invoices") without having to keep track of any indexes. This also allows you to provide a unique key attribute for each rendered element:
new Vue({
el: '#app',
data() {
return {
invoices: [
{ id: 1, value: 'a' },
{ id: 2, value: 'b' },
{ id: 3, value: 'c' },
{ id: 4, value: 'd' }
]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
<div v-for="invoice in invoices" :key="invoice.id">
<input v-model="invoice.value">
</div>
</div>
You could try the following code
Define 'invoices' as an empty object in the data state
new Vue({
el: '#app',
data() {
return {
invoices: {}
}
}
})
In the template, you can pass in the custom string with an index value
<script src="https://unpkg.com/vue#2.5.3/dist/vue.js"></script>
<div id="app">
<div v-for="index in 4">
<input v-model="invoices['invoice__' + index]">
</div>
</div>
I used the above code to implement custom checkbox value of items.
At the end, the 'invoices' object will output as 'invoice__0: true', 'invoice__1:false' and so on.

Categories

Resources