Cannot validate dynamic input fields in vue.js using vee-validate - vue.js

I am generating some inputs fields on my vue component page dynamically like given in below code:
Script part:
data(){
return {
forminputs: [
{
fairPaid: '',
}
],
}
Component:
<tr v-for="(input,k) in forminputs" :key="k">
<td>
<input v-validate="'required'" name="fairPaid" type="text" :class="['form-control', {'is-invalid': errors.has('fairPaid')}]" v-model="input.fairPaid">
<div v-show="errors.has('fairPaid')" class="invalid-feedback">
{{ errors.first('fairPaid') }}
</div>
</td>
</tr>
The fields are being validated but there is one issue if the error is on one input field but the error message gets displayed in all input fields. The error message should be there on the field where the error occurs and i don't want to change the field name. Any suggestions will be highly appreciated

The errors are associated by the input's name attribute, so those should be unique. Instead of just 'fairPaid', bind 'fairPaid' + k (with the v-for's k index as a suffix) to name, and use that as a key in the errors bag:
<tr v-for="(input,k) in forminputs" :key="k">
<td>
<input v-validate="'required'" :name="`fairPaid${k}`" type="text" :class="['form-control', {'is-invalid': errors.has(`fairPaid${k}`)}]" v-model="input.fairPaid">
<div v-show="errors.has(`fairPaid${k}`)" class="invalid-feedback">
{{ errors.first(`fairPaid${k}`) }}
</div>
</td>
</tr>

Related

Generating Dynamic Table in vuejs

I am trying to generate a dynamic table in vue where i can manipulate table rows and column value through <input> tag.The problem i am facing is my "v-for" loop is not updating according to the assigned variable.
Index.vue
<template>
<section>
<table>
<tr v-for="row in tableRows">
<td v-for="col in tableCols">Test</td>
</tr>
</table>
<div class="form-row">
<label>Row</label>
<input type="number" v-model.lazy="tableRows">
</div>
<div class="form-row">
<label>Column</label>
<input type="number" v-model.lazy="tableCols">
</div>
</section>
</template>
<script>
export default {
name: "index",
data() {
return {
tableRows : 1,
tableCols : 1,
};
}
};
</script>
The value for the 'tableRows' and 'tableCols' is getting updated according to vue dev tools but the table loop is not generating rows and column accordingly.Can someone help and tell me what am i doing wrong.
Thanks

Vue - change/set a variable value in template

Working in Vue, I am trying to set a variable based on another variable within the template. This is within a loop, and I need to set a value that can be used in 'next' iteration of the loop (to change the way a table is rendered, based on the variable).
I have the following:
<template>
...
<tbody v-for="(lo,index) in learn" :key="lo.id">
<tr>
<td colspan="2">LO{{index+1}} {{lo.attributes.field_lo}}</td>
<td v-if="!nextDist"> </td>
</tr>
<tr>
<td>
<div
v-for="(pass,index) in lo.attributes.field_pass"
:key="index"
>P{{pStep()}} {{pass}}</div>
</td>
<td>
<div
v-for="(merit,index) in lo.attributes.field_merit"
:key="index"
>M{{mStep()}} {{merit}}</div>
</td>
<td v-if="lo.attributes.field_dshared && next" ***SET VALUE OF this.next*** rowspan="3">
<span class="has-text-weight-bold">D{{dStep()}} </span>{{lo.attributes.field_dist}}
</td>
<td v-else-if="!lo.attributes.field_dshared" ***SET VALUE of this.next*** ><span class="has-text-weight-bold">D{{dStep()}} </span>{{lo.attributes.field_dist}}
</td>
***else render nothing***
</tr>
</tbody>
</template>
export default {
name: "SpecUnit",
components: {
EssentialContent
},
data() {
return {
unit: "",
learn: "",
nextDist: "",
next: ""
};
},
...
}
What I'd like to be able to do is set the value of 'next' (this.next) so that when the loop iterates, I can check to see if I should which of the I should render or render nothing (because we are 'rowspanning').
I've tried computed and methods, but can't seem to get this working. I've looked to use Vue.set, but I'm struggling with that.
I'm still new to Vue, so any help would be greatly appreciated.
Thanks
It looks like Florian Reuschel had a similar problem and already solved it (although with some caveats)
Let's say we have something like that:
<!-- List.vue -->
<ul>
<li v-for="id in users" :key="id">
<img :src="getUserData(id).avatar"><br>
🏷️ {{ getUserData(id).name }}<br>
🔗 {{ getUserData(id).homepage }}
</li>
</ul>
His approach is to use a helper renderless component with a scoped slot
const Pass = {
render() {
return this.$scopedSlots.default(this.$attrs)
}
}
and then
<!-- List.vue -->
<ul>
<Pass v-for="id in users" :key="id" :metadata="getUserData(id)">
<li slot-scope="{ metadata }">
<img :src="metadata.avatar"><br>
🏷️ {{ metadata.name }}<br>
🔗 {{ metadata.homepage }}
</li>
</Pass>
</ul>
If you take a look at the comments section on his blog article, you will see other approaches, too. For example, you can use an expression inside v-bind
<li v-for="id in users" :key="id" :demo="item = getUserData(id)">
<img :src="item.avatar" /><br />
🏷️ {{ item.name }}<br />
🔗 {{ item.homepage }}
</li>

Getting data back into the root with nested components in vue

I am building a multiple page app with latest Laravel and latest Vue.js. At the end of this post you will see what I am trying to achieve - which I have done visually. However the user needs to be able to edit the text, assigned user and the date of each item. I have started with the date and as you can see I have the date picker working as well.
Where I am struggling is updating the main model of data in the root so that I can save the changes that the user has made via a HTTP request. Initially the tree's data is loaded in via HTTP as well (example below).
I have built the below using nested components and I have read that two binding has been depreciated for props on components. I know that I need to emit and user events but I'm sure how this would work if the components are nested?
Here is an example of the data that get's loaded via HTTP. Below is a very small example, however this could be much larger
{
"objective":"Test",
"user_id":null,
"by":"08\/09\/2018",
"colour":"#1997c6",
"children":[
{
"objective":"Test",
"user_id":11,
"by":"08\/09\/2018",
"colour":"#d7e3bc",
"children":[]
}, {
"objective":"Test",
"user_id":11,
"by":null,
"colour":"#1997c6",
"children":[]
}
]
}
Here are the components that I have put together so far.
Vue.component('tree-date', {
props: ['date'],
data () {
return {
id: 0
}
},
mounted() {
this.id = uniqueId();
$('#picker-' + this.id).datetimepicker({
format: 'DD/MM/YYYY',
ignoreReadonly: true
});
},
template: `
<div class="date-container" :id="'picker-' + id" data-target-input="nearest" data-toggle="datetimepicker" :data-target="'#picker-' + id">
<div class="row">
<div class="col-2">
<div class="icon">
<i class="fa fa-calendar-alt"></i>
</div>
</div>
<div class="col-10">
<input type="text" class="form-control datetimepicker-input" readonly="readonly" :data-target="'#picker-' + id" v-model="date">
</div>
</div>
</div>`
});
Vue.component('tree-section', {
props: ['data', 'teamUsers', 'first'],
methods: {
test () {
this.$emit('test');
}
},
template: `
<table v-if="data.length != 0">
<tr>
<td :colspan="data.children !== undefined && (data.children.length * 2) > 0 ? data.children.length * 2 : 2">
<div class="node" :class="{'first': first == true}">
<div class="inner">
<tree-date :date="data.by"></tree-date>
<div class="objective">
{{ data.objective }}
</div>
<div class="author" v-if="data.user_id !== null">
{{ teamUsers[data.user_id].first_name }} {{ teamUsers[data.user_id].last_name }}
</div>
<div class="author" v-if="data.user_id === null">
Unassigned
</div>
</div>
</div>
</td>
</tr>
<tr class="lines" v-if="data.children.length > 0">
<td :colspan="data.children.length * 2"><div class="downLine"></div></td>
</tr>
<tr class="lines" v-if="data.children.length > 0">
<td class="rightLine"></td>
<td class="topLine" v-for="index in ((data.children.length * 2) - 2)" :key="index" :class="{'rightLine': index % 2 == 0, 'leftLine': Math.abs(index % 2) == 1}"></td>
<td class="leftLine"></td>
</tr>
<tr v-if="data.children.length > 0">
<td colspan="2" v-for="child in data.children">
<tree-section :data="child" :team-users="teamUsers" :first="false"></tree-section>
</td>
</tr>
</table>
`
});
This all get's called in the view by:
<tree-section :data="data" :team-users="teamUsers" :first="true"></tree-section>
Any help getting data update in the components back into the root will be most helpful.
by default, vue props (if objects or arrays) are being passed by reference- that means that if you change your object on the child component, the original object on the parent component will get changed too.
from vue api:
Note that objects and arrays in JavaScript are passed by reference, so
if the prop is an array or object, mutating the object or array itself
inside the child component will affect parent state.
https://v2.vuejs.org/v2/guide/components-props.html

How to handle variable number of process variables in Camunda

I'm new to Camunda and didn't find any tutorial or reference explaining how to achieve the following:
When starting a process I want the user to add as many items as he likes to an invoice. On the next user task all those items and their quantity should be printed to somebody approving the data.
I don't understand yet how to get this 1:n relationship between a process and its variables working. Do I need to start subprocesses for each item? Or do I have to use a custom Java object? If so, how can I map form elements to such an object from within the Tasklist?
I got it working with the help of the links provided by Thorben.
The trick is to use JSON process variables to store more complex data structures. I initialize such lists in my "Start Event". This can be done either in a form or in my case in a Listener:
execution.setVariable("items", Variables.objectValue(Arrays.asList(dummyItem)).serializationDataFormat("application/json").create());
Note that I added a dummyItem, as an empty list would lose its type information during serialization.
Next in my custom form I load this list and can add/remove items. Using the camForm callbacks one can persist the list.
<form role="form" name="form">
<script cam-script type="text/form-script">
/*<![CDATA[*/
$scope.items = [];
$scope.addItem = function() {
$scope.items.push({name: '', count: 0, price: 0.0});
};
$scope.removeItem = function(index) {
$scope.items.splice(index, 1);
};
camForm.on('form-loaded', function() {
camForm.variableManager.fetchVariable('items');
});
// variables-fetched is not working with "saved" forms, so we need to use variables-restored, which gets called after variables-fetched
camForm.on('variables-restored', function() {
$scope.items = camForm.variableManager.variableValue('items');
});
camForm.on('store', function() {
camForm.variableManager.variableValue('items', $scope.items);
});
/*]]>*/
</script>
<table class="table">
<thead>
<tr><th>Name</th><th>Count</th><th>Price</th><th></th></tr>
</thead>
<tbody>
<tr ng-repeat="i in items">
<td><input type="text" ng-model="i.name"/></td>
<td><input type="number" ng-model="i.count"/></td>
<td><input type="number" ng-model="i.price"/></td>
<td>
<button class="btn btn-default" ng-click="removeItem($index)">
<span class="glyphicon glyphicon-minus"></span>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="4">
<button class="btn btn-default" ng-click="addItem()">
<span class="glyphicon glyphicon-plus"></span>
</button>
</td>
</tr>
</tfoot>
</table>
</form>
Two things aren't working yet:
Field validation, e.g. for number fields
The dirty flag used on the "save" button doesn't get updated, when adding/removing rows

How do I implement something like this?

Well I have to implement something as follows:
I need to display a list of Contact IDs of all the contacts I have in my model.
<ul>
#for (int i = 0; i < Model.Contacts.ToList().Count; i++)
{
<li><a onclick="showContactInfoDialog(#Model.Contacts.ToList()[i].ContactId)">Model.Contacts.ToList()[i].ContactId</a></li>
}
</ul>
Each list element will be clickable, upon clicking which, a dialog will popup.
function showContactInfoDialog(id) {
document.getElementById('contact-dialog').style.display = 'block';
}
The dialog should show that particular contact's First Name, Last Name, Title, Email.
<div id="contact-dialog">
<form action="Contact/SaveContactEdits" method="post">
<table>
<tr>
<td>First Name</td>
<td>
<input type="text" name="FName"value="#Model.Contacts.ToList()[id].FirstName" /></td>
</tr>
<tr>
<td>Last Name</td>
<td>
<input type="text" name="LName" value="#Model.Contacts.ToList()[id].LastName" /></td>
</tr>
<tr>
<td>Title</td>
<td>
<input type="text" name="Title" value="#Model.Contacts.ToList()[id].Title" /></td>
</tr>
<tr>
<td>Email Address</td>
<td>
<input type="text" name="Email" value="#Model.Contacts.ToList()[id].Email" /></td>
</tr>
</table>
<input type="submit" value="Save"/>
</form>
</div>
The dialog should let user make edits to the contact's details.
How do I do this? I'm having problem in passing the 'id' parameter to the dialog box element.
<div id="contact-dialog">
I'm having problem in passing the 'id' parameter to the dialog box
element.
The way you are using the id is not correct as you use it like the following. In the given code below (taken from your code) you are using the id as the index and that won't work most of the time especially if the ids does not start with 0.
Model.Contacts.ToList()[id]
That also won't work because the onclick event happens on the client side where the model is no longer available. So what you can do, since calling another controller method is not an option is to write all the details in a hidden field. Put them on a single container, for example one div per contact, the assign the id of the contact to the div. When the a tag is clicked, you read the values from the div and assign them to the form. All of this can be handled easier with tools like knockout, but if using it is not an option then here's the code that will do the trick.
// in your loop do this
// btw, it would be better if you Contacts object is an IList so you can do indexing easier
<li><a onclick="showContactInfoDialog(#Model.Contacts.ToList()[i].ContactId)">Model.Contacts.ToList()[i].ContactId</a>
<div id="#("contactrow"+Model.Contacts.ToList()[i].ContactId)">
#Html.HiddenFor(m=>Model.Contacts.ToList()[i].FirstName)
// do the same for the rest of the fields you want to show on the dialog
</div>
</li>
before you show the dialog, copy the contents to the form:
function showContactInfoDialog(id) {
// we are targetting this -> <input type="text" name="FName"
// assign an id (fname) for optimal performance
var container = $("#contactrow"+id);
        $("#fname").val(container.find('#FirstName').val());
// do the same for the rest of the fields
document.getElementById('contact-dialog')
.style.display = 'block';
}