Form collection validation with dates and string - Vuelidate - vue.js

I am trying to validate series of dates with something like this.
const data = [
{begin: new Date('2019-12-01'), place: '2'},
{begin: new Date('2019-12-03'), place: '3'}
... more values
];
// Elements inside data can be added or removed but will have at least one.
Here data[1][begin] should be more than or equal to data[0][begin] and data[1][place] should not equal to data[0][place]. Is there anyway to achieve this. Documentation talks about dynamic validation but I am not sure how I can achieve this with collection.

You can consider implementing a custom validation in the form submit event listener.
This can be achieved by looping through your array of objects and compare items in pairs.
HTML
<form
id="app"
#submit="checkForm"
action="/someurl"
method="post"
>
<table border="1">
<tr v-for="(item,index) in dates" :key="index">
<td>
{{index}}
</td>
<td>
{{formatDate(item.begin)}}
</td>
<td>
{{item.place}}
</td>
</tr>
</table>
<input type="date" v-model="dateEntry"/>
<input type="text" v-model="placeEntry"/>
<button type="button" #click="addEntry">Add</button>
<p>
<br>
<input
type="submit"
value="Submit"
>
</p>
<p v-for="error in errorList">
{{error}}
</p>
</form>
JS
new Vue({
el: "#app",
data: {
errorList: [],
dateEntry: null,
placeEntry: null,
dates: [
{begin: new Date('2019-12-01'), place: '2'},
{begin: new Date('2019-12-03'), place: '3'}
]
},
methods: {
addEntry: function(){
if(this.dateEntry == null || this.dateEntry == "")
return false;
if(this.placeEntry == "")
return false;
this.dates.push({
begin: new Date(this.dateEntry),
place: this.placeEntry
});
this.dateEntry = null;
this.placeEntry= "";
},
checkForm: function(e){
var isValid = true;
var index = 0;
var nextIndex = 1;
this.errorList = [];
while(nextIndex < this.dates.length){
if(nextIndex < this.dates.length){
var isValidDate = this.validDate(this.dates[nextIndex].begin,this.dates[index].begin);
var isValidPlace = this.validPlace(this.dates[nextIndex].place,this.dates[index].place);
if(!isValidDate){
this.errorList.push("Invalid date on index " + nextIndex);
}
if(!isValidPlace){
this.errorList.push("Invalid place on index " + nextIndex);
}
}
index++;
nextIndex++;
}
if(!this.errorList.length){
this.errorList.push("All dates are valid");
return true;
}
e.preventDefault();
},
formatDate: function(date){
return date.toDateString();
},
validPlace: function(curPlace, prevPlace){
return curPlace != prevPlace;
},
validDate: function(curDate,prevDate){
try{
return curDate.getTime() >= prevDate.getTime();
}catch(e){
return false;
}
}
}
})
Check out this JS Fiddle that I created to illustrate my suggestion.
On the other hand, if you are building the array during runtime, then you can apply the validation before it gets added into the array.

Related

validating multiple fields with same name not working in vue.js

I would like to validate multiple fields with same name but for some reason its not working.If i make first input blank,nothing happens.If i make second input blank ,i get errors message on first input.Not sure what i am doing wrong.
<tr v-for="(name, index) in form"
:key="index">
<td>
<div>
<input
class="input"
:class="{
'is-danger': hasErrorName[index] === true
}"
#keyup.prevent="validateField('name',index)"
type="text"
v-model="name.description"
>
</div>
<span v-if="hasErrorName[index] === true"
class="help is-danger">
{{ msgName[index] }}
</span>
</td>
</tr>
data() {
return {
form: {},
hasErrorName: {},
msgName: {},
};
},
method() {
validateField(field,index) {
if (field === 'name') {
if (!this.form[index].description) {
this.hasErrorName[index] = true;
this.msgName[index] = 'Name is required.';
} else {
this.hasErrorName[index] = false;
this.msgName[index] = null;
}
}
}

Getting part of the page to display updated data in vue

I'm using vue to create a page where I list all users and if I click on the edit button the details of that user then gets shown
next to the list.
What I'm trying to do is, if I update a user and click save then the user details in the list needs to change.
The problem I'm having is that I'm not able to get the details to change in the list after I've saved.
My vue
<template>
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-7">
<table class="table table-striped table-sm mt-2">
<thead>
<tr>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="user in displayAllUsers">
<td>{{ user.name }}</td>
<td>
<button class="btn btn-sm btn-success" #click="manageUser(user)">Edit</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-5" v-if="user != null">
<div class="card">
<div class="card-header">
<h4 class="card-title mb-0">Manage {{ user.name }}</h4>
</div>
<div class="card-body">
<table class="table">
<tr>
<th>Name</th>
<td>
<input type="text" v-model="user.name">
</td>
</tr>
</table>
</div>
<div class="card-footer">
<button #click="updateUser()"class="btn btn-success"><i class="fa fa-save"></i> Save</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
components: {
},
data: function () {
return {
users: [],
user: null
}
},
computed: {
displayAllUsers(){
return this.users;
}
},
methods: {
manageUser(user){
axios.get('/admin/user/'+user.id).then((response) => {
this.user = response.data.user;
});
},
updateUser(){
axios.put('/admin/user/'+this.user.id, {
name: this.user.name
}).then((response) => {
this.users = response.data.user;
});
}
},
mounted() {
axios.get('/admin/users').then((response) => {
this.users = response.data.users;
});
}
}
</script>
There are two possible solutions.
The first is to run this code at the end of the updateUser method:
axios.get('/admin/users').then((response) => {
this.users = response.data.users;
});
The second is to use a state manager like Vuex.
The first scenario will fetch again your users data from the remote API and will update your view with all your users.
With the second scenario, you will handle your application state way much better than just using the data attribute of your page module, but in the background, it is more or less the same as the first solution I suggest.
To update the current user only in the table you could do something like that at the end of the updateUser method:
let userIdx = -1;
for(let idx = 0, l = this.users.length; idx < l; idx++) {
if ( this.user.id === this.users[idx].id ) {
userIdx = idx;
break;
}
}
if ( -1 !== userIdx ) {
this.users[userIdx] = this.user;
this.user = {};
}
Other than your problem, it seems like you don't need this code:
computed: {
displayAllUsers(){
return this.users;
}
},
You could remove this code, and instead use this code in the HTML part:
<tr v-for="user in users">
For your updateUser function you could just return the modified user in the same format that you have for all the users in you user list and update the user by index. This is presuming that the user you want to update is in the users array to start with.
updateUser() {
axios.put('/admin/user/'+this.user.id, {
name: this.user.name
}).then((response) => {
const updatedUser = response.data.user;
// Find the index of the updated user in the users list
const index = this.users.findIndex(user => user.id === updatedUser.id);
// If the user was found in the users list update it
if (index >= 0) {
// Use vue set to update the array by index and force an update on the page
this.$set(this.users, index, updatedUser);
}
});
}
This could be a good starting point.
Unrelated Note:
You can add your mounted function code to its own method, for example
getUsers() {
axios.get('/admin/users').then((response) => {
this.users = response.data.users;
});
}
then
mounted() {
this.getUsers()
}
this makes it a little cleaner and easier if you ever need to get the users again (example: if you start having filters the user can change)
As it could get more complex vuex would be a great addition.

Date range inside loop of multiple datatable in the same page

I came from the issue [https://datatables.net/forums/discussion/51949/looping-multiple-datatables-on-the-same-page#latest] and found an issue that comes from filtering of dates: if I filter and on change of this date range, it triggers table.draw() for the first one if it is inside of the loop of multiple datatable in the same page
My intention is to have the data range to work on each datatable individually
Here is a sample of what I already done
http://live.datatables.net/magokusa/4/edit
HTML
<div class="container">
<h3>Table 1</h3>
<div class="input-group input-group-sm">
<input type="date" id="dateFromupper" placeholder="Date from" value="2017-04-10">
<div>
<div>to</div>
</div>
<input type="date" id="dateToupper" placeholder="Date to" value="2018-09-10">
</div>
<table id="upper" data-action="https://demo.wp-api.org/wp-json/wp/v2/posts?per_page=5" class="display nowrap datatable" width="100%">
<thead>
<tr>
<th>col1</th>
<th>col2</th>
<th>col3</th>
</tr>
</thead>
</table>
<hr>
<h3>Table 2</h3>
<div class="input-group input-group-sm">
<input type="date" id="dateFromlower" placeholder="Date from" value="2016-04-10">
<div>
<div>to</div>
</div>
<input type="date" id="dateTolower" placeholder="Date to" value="2018-09-12">
</div>
<table id="lower" data-action="https://css-tricks.com/wp-json/wp/v2/posts?per_page=5" class="display nowrap datatable" width="100%">
<thead>
<tr>
<th>col1</th>
<th>col2</th>
<th>col3</th>
</tr>
</thead>
</table>
</div>
JS
$(document).ready( function () {
$.each($('.datatable'), function () {
var dt_id = $(this).attr('id');
var dt_action = $(this).data('action');
$.fn.dataTable.ext.search.push(
function (settings, data, dataIndex) {
var min = $("#dateFrom" + dt_id).val();
var max = $("#dateTo" + dt_id).val();
if (min != null && max != null) {
min = new Date(min);
max = new Date(max);
var startDate = new Date(data[1]);
if (min == null && max == null) {
return true;
}
if (min == null && startDate <= max) {
return true;
}
if (max == null && startDate >= min) {
return true;
}
if (startDate <= max && startDate >= min) {
return true;
}
} else {
return true;
}
}
);
$("#dateFrom" + dt_id + ", #dateTo" + dt_id).change(function () {
table.draw();
});
if (dt_action != null) {
var dt_ajax = dt_action;
var table = $('#' + dt_id).DataTable({
ajax: {
"url": dt_ajax,
"dataSrc": ""
},
columns: [
{ "data": "status" },
{ "data": "date" },
{ "data": "date_gmt" },
]
});
} else {
var table = $('.datatable').DataTable({
retrieve: true,
responsive: true,
});
}
});
});
Since you already are declaring two different filters, you can just check if the current draw process is related to the filter itself :
$.fn.dataTable.ext.search.push(
function (settings, data, dataIndex) {
if (settings.sInstance != dt_id) return true
...
}
)

v-model in a nested v-for of a multidimensional array

Hi I want to create a table with the days of the selected month where you can a add an employee and mark meals you want to assign to the employee.
I almost there, I can add a row to the table and mark the meals by day but when a second row is added the same meals are marked, all the meals are binded by day if i mark a meal in a row it marks for all the rows.
Here is the code and a jsfiddle
Html
<div id="app">
<span class="demonstration">Pick a month</span>
<input type="month" v-model="month">{{month}}<br><br>
<button #click="addEmployee()">Add a employee</button><br>
Mark meals for the employee<br>
<table border="1">
<thead>
<tr>
<th rowspan="3">Name</th>
<th :colspan="calendar.length*3">days of the month</th>
</tr>
<tr>
<th colspan="3" v-for="day in calendar">{{day.date}}</th>
</tr>
<tr>
<template v-for="c in calendar">
<th>b</th>
<th>l</th>
<th>d</th>
</template>
</tr>
</thead>
<tbody>
<tr v-for="(item, indexItem) in list" :key="indexItem">
<td>
<input type="text" v-model="item.name">
</td>
<template v-for="(day, indexDay) in item.days">
<td>
<input type="checkbox" v-model="item.days[indexDay].breakfast">
</td>
<td>
<input type="checkbox" v-model="item.days[indexDay].lunch">
</td>
<td>
<input type="checkbox" v-model="item.days[indexDay].dinner">
</td>
</template>
</tr>
</tbody>
</table>
</div>
Vue
new Vue({
el: "#app",
data: {
month: '',
list: [
]
},
computed: {
calendar () {
let selected = new Date(this.month)
let daysOfMonth = new Date(selected.getFullYear(), selected.getMonth() + 1, 0)
let days = [{}]
for (var i = 0; i < daysOfMonth.getDate(); i++) {
days[i] = {
date: selected.getFullYear().toString() + '-' + (selected.getMonth() + 1).toString() + '-' + (i + 1).toString(),
breakfast: false,
lunch: false,
dinner: false
}
}
return days
}
},
methods: {
addEmployee () {
let cal = []
cal = this.calendar
this.list.push(
{
name: '',
days: cal
}
)
}
}
})
https://jsfiddle.net/patogalarzar/v8h0knt7/
You are sharing the same object on every row, which means when one row is update, all the rest get updated as well.
Computed method is not the right tool here. I suggest you create a method to generate the calendar object.
methods: {
createCalander (month) {
let selected = new Date(month)
let daysOfMonth = new Date(selected.getFullYear(), selected.getMonth() + 1, 0)
let days = [{}]
for (var i = 0; i < daysOfMonth.getDate(); i++) {
days[i] = {
date: selected.getFullYear().toString() + '-' + (selected.getMonth() + 1).toString() + '-' + (i + 1).toString(),
breakfast: false,
lunch: false,
dinner: false
}
}
return days
}
}
}
Now you can create the computed property using this method, passing this.month.
On the add employees you would be using the new method to generate the list.
addEmployee () {
let cal = []
cal = this.getCalander(this.month)
this.list.push(
{
name: '',
days: cal
}
)
}
Now that you are not using the same object, the rows will not update together.
Your mistake was to use the same object on every row.
I've updated the jsfiddle
Change your addEmployee method to avoid point to same object:
addEmployee () {
let cal = []
cal = JSON.parse(JSON.stringify(this.calendar))
this.list.push(
{
name: '',
days: cal
}
)
}
More proper way to create a method call getCalendar and let cal = this.getCalendar()
this is because all employees reference the same object calendar, you can deep copy the object, or try this way

cant get multiple methods in vuejs instance to work

I have two v-on:click events attached to html elements. the one calling method1 works but the other one doesnt work. i cant imagine what the issue is. i have no errors in the console
heres the the entire html page.
<div class="col-md-10" id="deckBuilder">
<button class="ClassTabs" id="classCardsTab">"#ViewData["ClassChoice"]"</button>
<button class="ClassTabs" id="neutralCardsTab">Neutral</button>
<div class="well col-md-9" id="classCards">
#foreach (var card in Model.ClassCards)
{
<img v-on:click="addCard" class="card" id="#card.CardID;#card.Name" style="width:200px;height:260px;" src="#Url.Content(card.Image)" alt="#card.Name" />
}
</div>
<div class="well col-md-3" id="tableWrapper">
<table id="deckTable">
<tr>
<th colspan="3" style="font-size:24px;"><input style="text-align:center;" placeholder="My #ViewData["ClassChoice"] Deck" v-model="deckName" /></th>
</tr>
<tr>
<th style="text-align:center;font-size:20px;">Name</th>
<th style="text-align:center;font-size:20px;">Count</th>
<th></th>
</tr>
</table>
</div>
<div class="well col-md-9" id="neutralCards">
#foreach (var item in Model.NeutralCards)
{
<img v-on:click="addCard" class="card" id="#item.CardID;#item.Name" style="width:200px;height:260px;" src="#Url.Content(item.Image)" alt="#item.Name" />
}
</div>
</div>
#section Scripts {
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script>
var deckBuilder = new Vue({
el: '#deckBuilder',
data: {
deckList: [],
deckCards: 0,
deckName: ''
},
methods: {
addCard: function(event) {
var count = 0;
var foundCard = false;
var cardInfo = event.path[0].id.split(';');
var cardId = cardInfo[0];
var cardName = cardInfo[1];
var deckTable = document.getElementById('deckTable');
var row;
for (var i = 0; i < this.deckList.length; i++) {
if (this.deckList[i].id === cardId && this.deckList[i].count < 3 && this.deckCards < 30) {
this.deckList[i].count++;
foundCard = true;
this.deckCards++;
for (var x = 0; x < deckTable.rows.length; x++) {
if (deckTable.rows[x].id === cardId) {
deckTable.rows[x].cells[1].innerHTML = this.deckList[i].count;
break;
}
}
break;
} else if (this.deckList[i].id === cardId && this.deckList[i].count === 3 && this.deckCards < 30) {
alert('Deck limit reached for this card.');
foundCard = true;
break;
}
}
if ((this.deckList.length === 0 || !foundCard) && this.deckCards < 30) {
this.deckList.push({ id: cardId, count: 1 });
this.deckCards++;
row = deckTable.insertRow(-1);
row.insertCell(0).innerHTML = '<a class="cardLink" href="#Url.Action("Details", "Cards")/' + cardId + '" >' + cardName + '</a>';
row.insertCell(1).innerHTML = 1;
row.insertCell(2).innerHTML = '<button v-on:click="removeCard">X</button>';
row.id = cardId;
}
console.log(this.deckCards);
},
removeCard: function (event) {
console.log("remove card");
}
}
})
</script>
}
You could try writing it like this:
var vueInstanct = new Vue({
el: "#myVueInstance",
methods: {
method1() {
console.log('method1 hit');
},
method2() {
console.log('method2 hit');
}
}
})
But there doesn't seem to be anything wrong with your code... maybe post the html elements these methods are attached to? Could be something there.