How to append input elements dynamically in vue - javascript

I'm trying to append new input fields based on a condition, I will describe the workflow to help you understand
First stage is to press this button to implement 2 functions(1 is to move to other fieldset in the staged form, second function is to append the inputs:
<input type="button" name="secondBtn" class="next action-button" value="Next" id="secondBtn" #click="nextPlusappend"/>
nextPlusappend:
nextPlusappend() {
this.isNextClicked();
this.appendFields();
}
appendFields:
//this.platform initllized as 'one' so the condition is true.
if(this.platform === 'one'){
this.inputFields.push({
Username: '',
Password: '',
AffiliateID: '',
CI: '',
GI: '',
})
}
And I want to append all the fields from the function to this fieldset:
<div v-if="currentPage === 2">
<fieldset id="fieldset3" v-for="(param, index) in inputFields" :key="index">
<h2 class="fs-title">API Credentials</h2>
<h3 class="fs-subtitle">Step 3- Add any parameter for the integration</h3>
<input v-model="param.Username" type="text" name="`inputFields[${index}[Username]]`" placeholder="userName">
<input type="button" name="previous" class="previous action-button" value="Previous" #click="isPreviousClicked"/>
<input type="submit" name="submit" class="submit action-button" value="Create a Ticket" id="excel"/>
</fieldset>
</div>
How can I append this without hard code all the input fields as I did here:?
<input v-model="param.Username" type="text" name="`inputFields[${index}[Username]]`" placeholder="userName">
This is designated to be dynamic, what do i mean?
I mean that if the this.platform is equal to "one" there will be a unique fields, and if this.platform equal to "two" for example there will be other unique fields.

Don't think like "pushing a form field", rather think like "adding a new item to the dataset" (and of course, its displayed UI is a form field).
Let me give an example:
Vue.component("FormField", {
props: ["label", "value"],
computed: {
val: {
get() {
return this.value
},
set(val) {
this.$emit("update:value", val)
}
},
},
methods: {
handleClickAdd() {
this.$emit("click-add-field")
}
},
template: `
<div>
<label>
{{ label }}: <input type="text" v-model="val" />
</label>
<button
#click="handleClickAdd"
>
+ ADD
</button>
</div>
`,
})
new Vue({
el: "#app",
data() {
return {
formFields: [{
label: "Field 1",
value: null,
}],
}
},
methods: {
handleClickAddField() {
const item = {
label: `Field ${ this.formFields.length + 1 }`,
value: null,
}
this.formFields = [...this.formFields, item]
},
},
template: `
<div
class="container"
>
<div
class="col"
>
<h4>FIELDS:</h4>
<hr>
<form-field
v-for="(field, i) in formFields"
:key="i"
:label="field.label"
:value.sync="field.value"
#click-add-field="handleClickAddField"
/>
</div>
<div
class="col"
>
<h4>FIELD VALUES:</h4>
<hr>
<div
v-for="(field, i) in formFields"
:key="i"
>{{ field.label }}: {{ field.value }}</div>
</div>
</div>
`,
})
.container {
display: flex;
}
.col {
padding: 0 8px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
You can see, that on ADD I just added a new item in the formFields - the values are bound in the template to a child-component, that handles the actual representation of the fields.
On the right side of the snippet, you can see another benefit of decoupling data from UI: I created another representation of the same data source - that immediately reacts to any changes!

Related

Dynamic generated form with checkbox in Vue

I have dynamic generated form in Vue. Every loop have v-model. Everything work fine. But when I use checkboxes v-model work for all loops not on one like in input type text. Can You help me solved this problem? Below Vue code:
<fieldset>
<div class="form-row mb-2" v-for="input, index in journal" :key="index">
<div class="col-auto">
<label for="date">Data</label>
<Datepicker v-model="input.date" input-class="form-control" :input-attr="{id: 'date', name: 'date'}" style="width: 100%;" />
</div>
<div class="col-md-2">
<label for="timeStart">Od</label>
<Datepicker type="time" v-model="input.timeStart" format="HH:mm" input-class="form-control" :input-attr="{id: 'timeStart', name: 'timeStart'}" style="width: 100%;" />
</div>
<div class="col-md-2">
<label for="timeEnd">Do</label>
<Datepicker type="time" v-model="input.timeEnd" format="HH:mm" input-class="form-control" :input-attr="{id: 'timeEnd', name: 'timeEnd'}" style="width: 100%;" />
</div>
<div class="col-md-2">
<label for="players">Lista obecności</label>
<div class="form-check" v-for="item in input.players">
<input v-model="item.checked" type="checkbox" class="form-check-input" :id="'id-'+item.id+'set'+index">
<label class="form-check-label" :for="'id-'+item.id+'set'+index">{{ item.fullName }}</label>
</div>
</div>
<div class="col-auto">
<label for="description">Opis</label>
<textarea v-model="input.description" class="form-control" rows="7" id="description" placeholder="Opis"></textarea>
</div>
<div class="col-auto" #click="addInput" v-show="index == journal.length-1 && journal.length < 16">
<ButtonVue style="margin-top: 30px;" title="Dodaj" type="button" cancelWidth="true" color="btn-success"><i class="fas fa-plus"></i></ButtonVue>
</div>
<div class="col-auto align-self-start" #click="removeInput(index)" v-show="index || ( !index && journal.length > 1)">
<ButtonVue style="margin-top: 30px;" title="Usuń" type="button" cancelWidth="true" color="btn-danger"><i class="fas fa-minus"></i></ButtonVue>
</div>
</div>
</fieldset>
 
data() {
return {
contact: [],
journal: [{
date: "",
timeStart: "",
timeEnd: "",
players: "",
description: ""
}],
contacts: [],
}
},
Methods:
Method for creating dynamic form
addInput() {
this.journal.push({
date: "",
timeStart: "",
timeEnd: "",
players: this.contact,
description: ""
});
},
And here is the method which gets players from contacts
getContacts() {
this.pageLoader = true;
this.$http.get('/pkpar/get-contacts')
.then(({
data
}) => {
this.contacts = data.contacts;
for(let i=0; i<this.contacts.length; i++)
{
this.contact.push({'id': this.contacts[i]['id'], 'fullName' :
this.contacts[i]['fullName'], 'checked': true});
}
this.journal[0].players = this.contact;
this.pageLoader = false;
})
.catch(error => {
console.log(error);
});
},
Your addInput method creates and pushes new object into journal array, but each object created this way has a players property which references same array (this.contact)
The Difference Between Values and References in JavaScript
Easiest (but not most optimal) way to handle this is to create a copy of the array and objects inside for each new journal:
addInput() {
this.journal.push({
date: "",
timeStart: "",
timeEnd: "",
players: this.contact.map((player) => ({ ...player })),
description: ""
});
},

Vuejs generating click event in next button after pressing enter input

I'm encountering a very strange case.
I've build a very simple example in order to present my problem.
I've 3 files : App.vue, todo2.vue, todoI.vue.
App.vue has 1 component (todo2.vue). todo2.vue has 1 component (todoI.vue).
You'll find under the code of todo2 and todoI.
The problem I'm facing is that when i press enter in the input text id #checkboxAdd, it triggers an event on the next button.
In the code below when pressing enter in the input text #checkboxAdd, it triggers the click event on the first iteration of my todoI button, which in my example calls the function del (#click="del()"), which console.log(this), logging the first iteration of the component todoI.
What is even stranger is that when I add a button just after the input text, add a #click to console.log the event, it is indeed called (instead of calling the button of the first iteration of todoI).
Does anyone understand why this happens ? Is this something I'm doing wrong ?
todo2.vue:
<template>
<div class="d-flex flex-column">
<div>
<form #submit.prevent="">
<div class="mb-3 input-group">
<div class="input-group-text">
<input type="checkbox" class="form-check-input" id="checkboxAdd" aria-describedby="checkboxAdd">
</div>
<input type="text" class="form-control" id="inputAdd" aria-describedby="inputAdd" v-model="tempI">
</div>
<ul class="list-group">
<todoI v-for="(it, k) in todos" v-model="it.v" :key="k" #delItem="del(it)"></todoI>
</ul>
<br>
</form>
</div>
</div>
</template>
<script>
import todoI from './todoI.vue'
export default {
name:"todo2",
components: {todoI},
data: function(){
return {
todos: [
{v:{
val: "Bread",
checked: false
}},
{v:{
val: "Fruits",
checked: false
}},
{v:{
val: "Ironing",
checked: false
}}
],
tempI: ''
}
},
methods:{
del (it){
this.todos = this.todos.filter(i => i!==it)
}
}
}
</script>
todoI.vue:
<template>
<li class="list-group-item d-flex align-items-center" #mouseover="btnShow=true" #mouseleave="btnShow=false">
<input type="checkbox" class="me-4" v-model="value.checked">
<div class="w-100" :class="value.checked ? checkedClass : '' ">{{ value.val }}</div>
<div class="flex-shrink-1">
<button class="btn btn-sm btn-close" v-show="btnShow" #click="del()"></button>
</div>
</li>
</template>
<script>
export default {
name:"todoI",
props:{
value: Object
},
data: function(){
return {
checkedClass:['text-decoration-line-through', 'text-muted'],
btnShow: false,
}
},
methods:{
del(){
console.log(this)
}
}
}
</script>
you can simple use #keypress or #keydown
<input type="text" class="form-control" id="inputAdd" v-model="tempI" #keypress.enter.prevent />
or
<input type="text" class="form-control" id="inputAdd" v-model="tempI" #keydown.enter.prevent = "">

Vue displays validation text on multiple fields

There are two buttons called ADD and REMOVE. If the user clicks on ADD it will add one more input field for FULL NAME. I am using validationText to display text as PLEASE ENTER MORE THAN 5 CHARACTERS for full name. If I ADD two fields and insert only two characters in second one then it displays validationText on both input fields as
Is there a way to display validationText message to the particular field which consists of less than 5 characters?
View
<div id="app">
<div class="work-experiences">
<div class="form-row" v-for="(minordatabase, index) in minorsDetail" :key="index">
<div class="col">
<br>
<label id="minorHeading">FULL NAME</label>
<input v-model="minordatabase.full_name" type="text" class="form-control" placeholder="FULL NAME" size="lg" #input="checkValidation"/>
<p v-show="!validationText" style="color:red;">
Please enter than 5 characters
</p>
</div>
</div>
</div>
<br>
<div class="form-group">
<button #click="addExperience" type="button" class="btn btn-info" style="margin-right:1.5%;">Add</button>
<button #click="removeExperience" type="button" class="btn btn-outline-info">Remove Last Field</button>
</div>
</div>
Script
new Vue({
el: "#app",
data: {
minorsDetail: [
{
full_name: "",
date_of_birth: "",
}
],
validationText: true
},
methods: {
checkValidation(){
console.log("SAN");
var minorsDetailLastElement = this.minorsDetail[this.minorsDetail.length-1].full_name.length;
console.log(minorsDetailLastElement);
if(minorsDetailLastElement > 2){
this.validationText = false;
}
if(minorsDetailLastElement > 5){
this.validationText = true;
}
},
addExperience(){
this.minorsDetail.push({
full_name: ''
})
},
removeExperience: function(todo){
var index = this.minorsDetail.indexOf(todo)
this.minorsDetail.splice(index, 1)
this.removeMinorFieldFunction();
},
}
})
Below is the code on JSFIDDLE
https://jsfiddle.net/ujjumaki/5mqp1bag/28/
You only have one validationText for all fields. So, if you set it for one field, it's going to show up in the other field too.
I recommend doing something like this instead to show the validation:
<p v-if="minordatabase.full_name.length > 2 && minordatabase.full_name.length < 5" style="color: red;">
Please enter more than 5 characters
</p>

Make diffrent V-MODEL on every index that i generate

How can I have different V-MODEL on every object that i generate
I am trying to make an sample cupcake website that can generate multiple forms in one submit.
But when I generate 2 field, the inputs of the 2 generated field bound by each other inputs.
This is the code I am trying to generate:
<template>
<div>
<button #click="cupcakes.push(def)">Add Cup Cake</button>
<div v-for="(cupcake, index) in cupcakes" :key="index">
<input type="text" v-model="cupcakes[index].name">
<input type="text" v-model="cupcakes[index].description">
<input type="text" v-model="cupcakes[index].type">
<input type="text" v-model="cupcakes[index].prize">
<input type="text" v-model="cupcakes[index].color">
</div>
<button #click="onSubmit">Create Cupcate</button>
</div>
</template>
<script>
export default {
data() {
return {
cupcakes: [],
def: {
name: '',
description: 'Originals',
type: 'small',
prize: 500,
color: 'color'
}
}
},
methods: {
onSubmit() {
console.log(this.cupcakes);
}
}
}
</script>
I tried to do other things but it doesn't work.
How can I dis bind the 2 field and when I submit it will take the inputs that I type or input.
You are pushing n times the same objet (def) into your cupcakes Array. def is a reference to an object. So when you update cupcakes[n], you are just updating the def values.
What you need to do is send a copy of that object into the cupcakes object:
<button #click="cupcakes.push(JSON.parse(JSON.stringify(def)))">Add Cup Cake</button>
I think a better pattern would be to make a method that returns you a new cupcake:
<template>
<div>
<button #click="cupcakes.push(getNewCupcake())">Add Cup Cake</button>
<div v-for="(cupcake, index) in cupcakes" :key="index">
<input type="text" v-model="cupcakes[index].name">
<input type="text" v-model="cupcakes[index].description">
<input type="text" v-model="cupcakes[index].type">
<input type="text" v-model="cupcakes[index].prize">
<input type="text" v-model="cupcakes[index].color">
</div>
<button #click="onSubmit">Create Cupcate</button>
</div>
</template>
<script>
export default {
data() {
return {
cupcakes: []
};
},
methods: {
onSubmit() {
console.log(this.cupcakes);
},
getNewCupcake() {
return {
name: "",
description: "Originals",
type: "small",
prize: 500,
color: "color"
}
}
}
};
</script>

vue.js: HTML element show-if not toggling changes

I'm initially rendering some objects from an API call to my database, they are serialized and look like this initially:
<h3>Messages on Database</h3>
<p v-if="messages.length ===0">No Messages</p>
<div class="msg" v-for="(msg, index) in messages" :key="index">
<p class="msg-index">[{{index}}]</p>
<p class="msg-subject" v-html="msg.subject" v-if="!msg.editing"></p>
<p><input type="text" v-model="msg.subject" v-if="msg.editing" ></p>
<p>{{msg.editing}}</p>
<p class="msg-body" v-html="msg.body" v-show="!messages[index].editing"></p>
<p><input type="text" v-model="msg.body" v-show="messages[index].editing" ></p>
<input type="submit" #click="deleteMsg(msg.pk)" value="Delete" />
<input type="submit" #click="EditMsg(index)" value="Edit" />
<input type="submit" #click="updateMsg(msg.pk)" value="Update" />
</div>
</div>
</template>
<script>
export default {
name: "Messages",
data() {
return {
subject: "",
msgBody: "",
messages: [],
};
},
each message looks like this:
notice that body, pk and subject are the Django model fields. Each item in the array represents a database object.
What I want to do using vue.js, is allow users to edit each item. If the user clicks the edit button for an item, I want to transform its element from p to input, and submit that to the database.
In order to allow editing of individual items, I need an editing field in each item in the array, so I'm doing this in my mounted() property:
mounted() {
this.fetchMessages();
},
methods: {
fetchMessages() {
this.$backend.$fetchMessages().then(responseData => {
this.messages = responseData;
this.messages.forEach(function (value) {
value['editing'] = false;
});
console.log(this.messages);
});
},
Now, when I load up the array in my console, I see this:
So I assumed that now, when the user clicks the Edit button, EditMsg is called, and the fields will transform according to the v-if/v-show directives:
EditMsg(msgIdx) {
this.messages[msgIdx].editing = true;
console.log(this.messages);
},
But that's not happening. What is actually happening is this: the editing flag for the item is changed to true in the console/vue-developer-tools window, but nothing changes in the HTML.
What am I missing?
Full code:
<template>
<div class="hello">
<img src='#/assets/logo-django.png' style="width: 250px" />
<p>The data below is added/removed from the Postgres Database using Django's ORM and Restframork.</p>
<br/>
<p>Subject</p>
<input type="text" placeholder="Hello" v-model="subject">
<p>Message</p>
<input type="text" placeholder="From the other side" v-model="msgBody">
<br><br>
<input type="submit" value="Add" #click="postMessage" :disabled="!subject || !msgBody">
<hr/>
<h3>Messages on Database</h3>
<p v-if="messages.length ===0">No Messages</p>
<div class="msg" v-for="(msg, index) in messages" :key="index">
<p class="msg-index">[{{index}}]</p>
<p class="msg-subject" v-html="msg.subject" v-if="!msg.editing"></p>
<p><input type="text" v-model="msg.subject" v-if="msg.editing" ></p>
<p>{{msg.editing}}</p>
<p class="msg-body" v-html="msg.body" v-show="!messages[index].editing"></p>
<p><input type="text" v-model="msg.body" v-show="messages[index].editing" ></p>
<input type="submit" #click="deleteMsg(msg.pk)" value="Delete" />
<input type="submit" #click="EditMsg(index)" value="Edit" />
<input type="submit" #click="updateMsg(msg.pk)" value="Update" />
</div>
</div>
</template>
<script>
export default {
name: "Messages",
data() {
return {
subject: "",
msgBody: "",
messages: [],
};
},
mounted() {
this.fetchMessages();
},
methods: {
fetchMessages() {
this.$backend.$fetchMessages().then(responseData => {
this.messages = responseData;
this.messages.forEach(function (value) {
value['editing'] = false;
});
console.log(this.messages);
});
},
postMessage() {
const payload = { subject: this.subject, body: this.msgBody };
this.$backend.$postMessage(payload).then(() => {
this.msgBody = "";
this.subject = "";
this.fetchMessages();
});
},
deleteMsg(msgId) {
this.$backend.$deleteMessage(msgId).then(() => {
this.messages = this.messages.filter(m => m.pk !== msgId);
this.fetchMessages();
});
},
EditMsg(msgIdx) {
this.messages[msgIdx].editing = true;
console.log(this.messages);
},
updateMsg(msgId) {
console.log(this.subject, this.msgBody);
const payload = { subject: this.subject, body: this.msgBody };
this.$backend.$putMessage(msgId, payload).then(() => {
this.fetchMessages();
}
)
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
hr {
max-width: 65%;
}
.msg {
margin: 0 auto;
max-width: 30%;
text-align: left;
border-bottom: 1px solid #ccc;
padding: 1rem;
}
.msg-index {
color: #ccc;
font-size: 0.8rem;
/* margin-bottom: 0; */
}
img {
width: 250px;
padding-top: 50px;
padding-bottom: 50px;
}
</style>
According to Vue internals:
Vue observes data by converting properties with Object.defineProperty. However, in ECMAScript 5 there is no way to detect when a new property is added to an Object, or when a property is deleted from an Object.
So, when you bind your response data to this.messages, any mutation to array properties is not considered reactive anymore by Vue.
Instead, if you enrich responseData properties before binding it to the Vue data properties, all the array stays reactive. I mean like this:
fetchMessages() {
this.$backend.$fetchMessages().then(responseData => {
let editableMessages = responseData;
editableMessages.forEach(function (value) {
value['editing'] = false;
});
this.messages = editableMessages;
});
}
Here there is a small example based on your domain.

Categories