Form Validation (Angular) - javascript

I am using angular reactive form and making distance input fields which has two input boxes called From and To.
HTML:
<form [formGroup]="form">
<button (click)="addRow()">Add</button>
<div formArrayName="distance">
<div
*ngFor="let item of form.get('distance').controls; let i = index"
[formGroupName]="i"
style="display: flex"
>
<input type="number" placeholder="From" formControlName="from" />
<div><input type="number" placeholder="To" formControlName="to" /></div>
</div>
</div>
<br /><br />
<button type="submit" [disabled]="!form.valid">Submit</button>
</form>
TypeScript:
ngOnInit() {
this.form = this.fb.group({
distance: this.fb.array([]),
});
this.addRow()
}
addRow() {
const control = this.form.controls.distance as FormArray;
control.push(this.fb.group({
from: ['',Validators.required],
to: ['',Validators.required]
}));
}
Here you could able to see the two input boxes in default as from and to.
There is an add button at top and upon clicking add button the rows with same input fields gets added and forms as array.
Here i am in the need of restriction that user should not allowed to enter the previous row to value and also not the value lesser than that.
For eg.,
In the first row, if user enters the below values like 0 and 5 for from and to respectively,
"distance": [
{
"from": 0,
"to": 5
}
]
After clicking add and in second row in From input box user needs to be restricted on adding the values of 5 and lesser than that (which means those values were already entered).
So like this is invalid,
{
"distance": [
{
"from": 0,
"to": 5
},
{
"from": 5,
"to": 10
}
]
}
Here "from": 5, or "from": 4(or)3(or)2(or)1, anything is invalid in second row..
Only 6 and greater than 6 is valid.
Likewise for each row it needs to check for previous row to value and validation needs to be done.
Kindly help me to achieve this type of validation of restricting the user not to enter previous row to value (or) lesser than that in current row's from value.
Working Example: https://stackblitz.com/edit/disable-group-control-value-on-another-control-value-for-j58atx
Edit:
Tried with input change like,
<input type="number" (input)="onInputChange($event.target.value)" placeholder="From" formControlName="from">
in the link https://stackblitz.com/edit/disable-group-control-value-on-another-control-value-for-ymfpkj but not sure whether i am going correct..
Kindly change if this procedure is wrong.

Finally I decided divide the two conditions. see new stackblitz
ngOnInit() {
this.form = this.fb.group({
distance: this.fb.array([], this.distanceValidator()),
});
this.addRow()
}
addRow() {
const control = this.form.controls.distance as FormArray;
control.push(this.fb.group({
from: ['', Validators.required],
to: ['', Validators.required]
}, { validator: this.greaterValidator() }));
}
setDefault() {
const control = this.form.controls.distance as FormArray;
this.default.forEach(data => {
control.push(this.fb.group({
from: [data.from, Validators.required],
to: [data.to, Validators.required]
}, { validator: this.greaterValidator() }));
});
}
greaterValidator() {
return (fa: FormGroup) => {
return fa.value.to && fa.value.to < fa.value.from ? { error: "from greater than to" } : null;
}
}
distanceValidator() {
return (fa: FormArray) => {
let ok = true;
for (let i = 1; i < fa.value.length; i++) {
ok = (!fa.value[i].from || fa.value[i].from > fa.value[i - 1].to) && (!fa.value[i].to || fa.value[i].to > fa.value[i - 1].from);
if (!ok)
return { error: "from/to yet included", index: i }
}
return null
}
}
And the .html
<form [formGroup]="form">
<button (click)="addRow()">Add</button>
<div formArrayName="distance" >
<div
*ngFor="let item of form.get('distance').controls; let i = index"
[formGroupName]="i"
style="display: flex">
<input type="number"
placeholder="From"
formControlName="from">
<div>
<input type="number"
placeholder="To"
formControlName="to">
</div>
<span *ngIf="item.errors">*</span>
<span *ngIf="form.get('distance')?.errors && form.get('distance')?.errors.index==i">**</span>
</div>
</div>
<div *ngIf="form.get('distance')?.errors">{{form.get('distance')?.errors.error}}</div>
<br><br>
<button type="submit" [disabled]="!form.valid"> Submit </button>
</form>
<button (click)="setDefault()"> Set Default Values </button>
Update:Actually only when find an error not control more.
Moreover, if the from and to before is empty, don't give an error. For avoid this we can "convert" to number, writing
let ok = (!fa.value[i].from || fa.value[i].from > +fa.value[i - 1].to)
&& (!fa.value[i].to || fa.value[i].to > +fa.value[i - 1].from);
(see the "+" in +fa.value[i-1].to and +fa.value[i-1].from
Well, As we decided the error we send, imagine you has 6 rows and the line in position 0, in position 3 and in position 4 (0 is the first row) send a error like
{error:"there are errors",indexError:",0,3,4,"}
This allow inside the *ngFor write some like
<span *ngIf="form.get('distance')?.errors &&
form.get('distance')?.errors.indexError.indexOf(','+i+',')>=0">
**
</span>
Well, our distanceValidator becomes like
distanceValidator() {
return (fa: FormArray) => {
let indexError:string="";
for (let i = 1; i < fa.value.length; i++) {
let ok = (!fa.value[i].from || fa.value[i].from > +fa.value[i - 1].to) && (!fa.value[i].to || fa.value[i].to > +fa.value[i - 1].from);
if (!ok)
indexError+=','+i;
}
return indexError?{error:"there are errors",indexError:indexError+','}:null
}
Someone can think that it's better return an array of errors, but this not allowed as to know in a easy way the row with errors. some like errors.find(x=>x.id==i) not work because we can not use find in a interpolation.
It's true that only compare one row with the inmediaty before. It's possible to check over all before -using a for (let j=i-1;j>0;j++){ok=ok && ...}-, but I think it's not necesary and we must be stingy in code. Remember that the function distanceValidator are executed several times
See another stackblitz

Just use a customValidation (I choose the validation in the same component
ngOnInit() {
this.form = this.fb.group({
distance: this.fb.array([], this.distanceValidator()),
});
this.addRow()
}
distanceValidator() {
return (fa: FormArray) => {
let ok = true;
let ok2 = fa.value.length ? (!fa.value[0].to || !fa.value[0].from) || fa.value[0].to > fa.value[0].from : true;
if (!ok2)
return { error: "from greater than to" }
for (let i = 1; i < fa.value.length; i++) {
if (fa.value[i].from && fa.value[i].to )
{
ok = (fa.value[i].from > fa.value[i - 1].to || fa.value[i].to < fa.value[i - 1].from);
ok2 = (fa.value[i].to > fa.value[i].from);
if (!ok)
return { error: "from/to yet included" }
if (!ok2)
return { error: "from greater than to" }
}
}
return ok && ok2 ? null : !ok?{ error: "from yet included" }:{ error: "from greater than to" }
}
}
You can see the error like another
<div *ngIf="form.get('distance')?.errors">
{{form.get('distance')?.errors.error}}
</div>
see [stackblitz forked][1]

Related

Vue Js range checkbox selection with shift

I have this html:
<div
class="data__file"
v-for="(data, index) in paginatedData"
:key="index"
>
<label class="data__info" :for="data.idfile" #click="onClickWithShift($event, index)">
<img
:src="data.link"
alt=""
:class= "{ 'data__image' : 1 ,'data__image-active' : (data.checked === 1) }"
/>
<input
v-if="isManager === true"
type="checkbox"
class="data__access"
:value="data.idaccess"
:checked="(data.checked === 1) ? 1 : null"
v-model="checkedFilesPermission"
/>
<input
v-if="isManager === false"
type="checkbox"
class="data__access"
:value="data.idfile"
:checked="(data.checked === 1) ? 1 : null"
v-model="checkedFilesDownload"
/>
</label>
</div>
This code generate list of checkbox inputs, then I need when user click on label with shift (because input`s is display:none), all checkboxes between clicked inputs will checked or unchecked like it make with jquery here
How can I shift-select multiple checkboxes like GMail?
But I cant realise how I can get it.
Big thanks to user Spiky Chathu, I did how He said, and its work without v-model , but when I try use v-model, it doesn`t work.
also this is my data:
data() {
return {
isManager: this.$store.getters.isManager,
checkedFilesPermission: [],
checkedFilesDownload: [],
lastCheckedIdx: -1,
checkedCount: 0,
paginatedData: [
{"link":"images/2020/08/20200803.jpg","idfile":296,"idaccess":2},
{"link":"images/2020/08/20200807.jpg","idfile":6,"idaccess":99},
{"link":"images/2020/08/20200812.jpg","idfile":26,"idaccess":29},
{"link":"images/2020/08/202123.jpg","idfile":960,"idaccess":2919},
{"link":"images/2020/08/2020032.jpg","idfile":16,"idaccess":9339},
{"link":"images/2020/08/20200000.jpg","idfile":2,"idaccess":9},
]
};
I think main problem that VUE somehow block input with v-model
I have come up with a solution to your problem. I have added a mock object to recreate the same scenario hoping that you have a array of objects.
Editted : Solution has been modified to cater multiple deselect scenario
new Vue({
el: '#app',
data: {
paginatedData: [
{"link":"https://img.icons8.com/list","idfile":296,"idaccess":2},
{"link":"https://img.icons8.com/list","idfile":6,"idaccess":99},
{"link":"https://img.icons8.com/list","idfile":26,"idaccess":29},
{"link":"https://img.icons8.com/list","idfile":960,"idaccess":2919},
{"link":"https://img.icons8.com/list","idfile":16,"idaccess":9339},
{"link":"https://img.icons8.com/list","idfile":2,"idaccess":9},
],
lastCheckedIdx: -1,
checkedFilesPermission : []
},
methods: {
onClickWithShift(event, idx, idFile) {
var action = (this.checkedFilesPermission.indexOf(idFile) === -1) ? 'select' : 'deselect';
if (event.shiftKey && this.lastCheckedIdx !== -1) {
var start = this.lastCheckedIdx;
var end = idx-1;
// can select top to bottom or bottom to top
// always start with lesser value
if (start > end) {
start = idx+1;
end = this.lastCheckedIdx;
}
for (var c = start; c <= end; c++) {
var currentIdFile = this.paginatedData[c]['idfile'];
this.markSelectDeselect(c, action, currentIdFile);
}
}
this.markSelectDeselect(idx, action, idFile);
this.lastCheckedIdx = idx;
if (this.checkedFilesPermission.length === 0) {
//reset last checked if nothing selected
this.lastCheckedIdx = -1;
}
},
markSelectDeselect(idx, action, idFile) {
var currentPos = this.checkedFilesPermission.indexOf(idFile);
if (action === 'select' && currentPos === -1) {
this.checkedFilesPermission.push(idFile);
} else if (action === 'deselect' && currentPos !== -1){
this.checkedFilesPermission.splice(currentPos, 1);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div
class="data__file"
v-for="(data, index) in paginatedData"
:key="index"
>
<input
:id="data.idfile"
type="checkbox"
class="data__access"
:value="data.idfile"
v-model="checkedFilesPermission"
/>
<label class="data__info" :for="data.idfile" #click="onClickWithShift($event, index, data.idfile)">
<img
:src="data.link"
alt=""
:class= "{ 'data__image' : 1 ,'data__image-active' : (checkedFilesPermission.indexOf(data.idfile) !== -1) }"
/>
</label>
</div>
</div>
You need to click on the image icon to see the result, since you have mentioned the input is hidden. I kept it visible here so that you can see it is actually getting changed
Here's something I just tried that seems to do the work
<template>
<div>
<div v-for="(item, index) in items" :key="index">
<label>
<input type="checkbox" v-model="item.checked" #click="checked($event, index)">
{{item.file}}
</label>
</div>
<pre>{{items}}</pre>
</div>
</template>
<script>
export default {
data() {
return {
lastCheckedIndex: null,
lastChange: null,
items: [
{ file: "foo1", idx: 10 },
{ file: "foo2", idx: 20 },
{ file: "foo3", idx: 40 },
{ file: "foo4", idx: 30 },
{ file: "foo5", idx: 10 },
{ file: "foo6", idx: 90 },
{ file: "foo8", idx: 50 },
]
}
},
methods: {
checked(event, index) {
// wheter or not to the multiple selection
if (event.shiftKey && (null != this.lastCheckedIndex) && (this.lastCheckedIndex != index)) {
const dir = index > this.lastCheckedIndex ? 1 : -1; // going up or down ?
const check = this.lastChange; // are we checking all or unchecking all ?
for (let i = this.lastCheckedIndex; i != index; i += dir) {
this.items[i].checked = check;
}
}
// save action
this.lastCheckedIndex = index;
this.lastChange = !this.items[index].checked; // onclick is triggered before the default change hence the !
}
},
};
</script>

How to solve blur and submit event simultaneous in angular

I have text box in my page, which i can enter 9 digit number. Onblur I am validating like the entered number is valid or not using API call, If service returns failure it will clear the text box with red border and form will be invalid. The event conflict happening between OnBlur and Submit. Submit service will call only the form is valid otherwise it will show toaster like enter mandatory filed.
If the text field focused and directly if I click on submit button, both event calling simultaneously and it is clearing the number field OnBlur as well as the service is calling.
Please can you help me to resolve this conflicts.
file.html
<form class="contact-form" #create="ngForm">
<div class="controls">
<input NumberOnly="true" type="text" id="num" [ngClass]="{'red-border-class': ((showErrorFlag == true && numberField.errors) || (showErrorFlag == true && numberField.errors && (numberField.dirty || numberField.touched)))}"
[disabled]="disableRotaDetailFields" [(ngModel)]="number"
class="floatLabel" name="ownership" required #numberField="ngModel" (blur)="validatenumber(number)" [maxLength]="einLength">
<label for="ein">number<sup>*</sup></label>
</div>
<button (click)="SaveData(create)">Save</button>
</form>
file.ts
public validatenumber(number) {
let reqObj = {
"ownership": number
}
this.calloutService.validateOwnerEin(reqObj)
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => {
}, (err) => {
if (err.status == 404) {
this.number = "";
}
this.toastr.error(err.overriddenMessage);
})
}
SaveData(){
if (!formFlag.valid ) {
this.showErrorFlag = true;
this.toastr.error('Please fill all the mandatory fields');
}else {
this.calloutService.createData(this.data)
.pipe(takeUntil(this.unsubscribe))
.subscribe(data => {
this.showSpinnerFlag = false;
let response = data;
if (data) {
this.toastr.success("Rota created successfully.");
} else {
this.toastr.error("Could not save.");
}
}, err => {
this.showSpinnerFlag = false;
this.toastr.error(err.overriddenMessage);
})
}
}

Create a Dynamic Checkbox Validation in Angular With Data from API

So I have a function that displays a list from an API:
displayEventTicketDetails() {
this.Service
.getEventTicketDetails().subscribe((data: any) => {
this.eventTicketDetails = data.map(ticket => ticket.ticket_name);
console.log(this.eventTicketDetails);
});
}
This is the result from the function above:
["Regular", "VIP", "Table", "Testing", "Cabana"]
Here is how the form array is declared:
ngOnInit() {
this.composeMessage = this.fb.group({
ticket: new FormArray([])
});
Then I use this function below to track if the check boxes are checked
onChange(event: any, isChecked: boolean){
const control = <FormArray>this.composeMessage.controls.ticket;
if(isChecked){
control.push(new FormControl(event))
} else{
const index = control.controls.findIndex(x => x.value === event);
control.removeAt(index)
}
}
Then finally in my ts file, here is my onsubmit function that submits the data on the form:
submitComposeMessage() {
this.submitted = true;
if (this.composeMessage.invalid) {
return;
}
const ticket = this.f.ticket.value;
this.Service
.createMessage(
ticket)
.subscribe(
(res: any) => {
if (res.err) {
this.toaster.errorToastr(res.message, null, { toastTimeout: 5000 });
return false;
}
this.toaster.successToastr(res.message, null, { toastTimeout: 5000 });
console.log("Message successfully created");
},
err => {
console.log(err);
}
);
}
So in my Html file here is my input field:
<ng-container *ngFor="let event of eventTicketDetails; let i = index" >
<label class="w-checkbox checkbox-field" >
<input
type="checkbox"
id="{{i}}"
name="checkbox-9"
class="w-checkbox-input checkbox"
(change)="onChange(event, $event.target.checked)"
[checked]="composeMessage.controls.ticket.value.indexOf(event)>=0">
<span class="no-margin w-form-label">{{event}}</span>
</label>
</ng-container>
With that loop, I'm able to get this result
So, I need help with two things:
1). I want all the checkbox to be checked by default when the page loads at first instance.
2). I want to validate the checkbox to ensure at least one checkbox is checked on submission.
I'll appreciate any help I can get.
If you want to only show validation message after submit, I would suggest the following, where we instead iterate the formarray in template, initially set all checked (as that is what you wish). We would listen to valueChanges of the formarray, but filter out as long as form is not submitted. We would introduce a new variable, for example isEmpty, which based on we would show/hide validation message. So all in all....
TS:
isEmpty = false;
submitted = false;
constructor(private fb: FormBuilder) {
const ctrls = this.eventTicketDetails.map(control => this.fb.control(true));
this.composeMessage = this.fb.group({
ticket: this.fb.array(ctrls)
});
this.tickets.valueChanges.pipe(
filter(() => !!this.submitted)
).subscribe((value) => {
value.some(x => x === true) ? this.isEmpty = false : this.isEmpty = true;
})
}
get tickets() {
return this.composeMessage.get("ticket") as FormArray;
}
onSubmit() {
this.submitted = true;
const selectedTickets = this.tickets.value
.map((checked, i) => (checked ? this.eventTicketDetails[i] : null))
.filter(value => !!value);
selectedTickets.length ? this.isEmpty = false : this.isEmpty = true
}
HTML:
<label formArrayName="ticket" *ngFor="let t of tickets.controls; index as i">
<input type="checkbox" [formControlName]="i">
{{eventTicketDetails[i]}}
</label>
<small *ngIf="isEmpty">Choose at least one checkbox</small>
STACKBLITZ
change Id to something like this
id="ticket{{i}}"
In this method write like this and call displayEventTicketDetails on ngOnit. This will check all the values:
displayEventTicketDetails() {
this.Service
.getEventTicketDetails().subscribe((data: any) => {
this.eventTicketDetails = data.map(ticket =>ticket.ticket_name);
setTimeout(() => {
for(var i= 0;i < this.evenTicketDetails.length ; i++){
var id = "ticket" + i;
(<HTMLInputElement>document.getElementById(id)).checked = true;
console.log(this.eventTicketDetails);
}, 500);
});
}
2. In submit method write something like this
submitComposeMessage() {
for(var i= 0;i < this.evenTicketDetails.length ; i++){
var id = "ticket" + i;
var resval = (<HTMLInputElement>document.getElementById(id)).value;
if(resval){
// this will check atleast one value is checked and if it true we are coming
out of the loop and performing other operations..
i = this.evenTicketDetails.length;
}
else{
// show error message or block from going forward..
}
});
}
This will solve your issues.

Looping through 2 elements at a time

I'm trying to create error messages for labels on a form. Problem is that it's not working. The submitted input must be a number. Whenever it is not, clicking on the button should return a error message on the specific label.
Problem is - it only works OK if the first thing you submit is a correct set of numbers. I can't seem to get the combinations right. Do you know how I can solve this?
let coordValues = document.getElementsByClassName("input-card__input");
let submitBtn = document.getElementsByClassName("input-card__button");
let inputLabel = document.getElementsByClassName("input-card__label");
let weatherArray = [];
let labelArray = [];
for(let j=0;j<inputLabel.length;j++) {
labelArray.push(inputLabel[j].innerHTML);
}
submitBtn[0].addEventListener("click", function checkInputs() {
for(let i = 0; i<coordValues.length;i++) {
for(let k = 0; k<inputLabel.length;k++) {
if(coordValues[i].value === "" || isNaN(Number(coordValues[i].value))) {
inputLabel[k].classList.add("input-card__label--error");
inputLabel[k].innerHTML = "Oops! Write a number here."
console.log("nop");
break;
} else {
inputLabel[k].classList.remove("input-card__label--error");
inputLabel[k].innerHTML = labelArray[k];
console.log("yep");
break;
}
}
}
});
.input-card__label--error {
color: red;
}
<head>
</head>
<body>
<div class="input-card">
<h1 class="input-card__title">Where are you?</h1>
<h3 class="input-card__label">LONGITUDE</h3>
<input type="text" placeholder="Longitude" class="input-card__input">
<h3 class="input-card__label">ALTITUDE</h3>
<input type="text" placeholder="Altitude" class="input-card__input">
<button class="input-card__button">Show me weather ⛅</button>
</div>
</body>
There's a few errors in your code, here's a version I modified:
submitBtn[0].addEventListener("click", function checkInputs() {
for(let i = 0; i<coordValues.length;i++) {
if(coordValues[i].value === "" || isNaN(Number(coordValues[i].value))) {
inputLabel[i].classList.add("input-card__label--error");
inputLabel[i].innerHTML = "Oops! Write a number here."
console.log("nop");
return;
}
inputLabel[i].classList.remove("input-card__label--error");
inputLabel[i].innerHTML = labelArray[i];
}
console.log("yep");
});
One issue is the double for loop, it over complicates what you're trying to do.
Then once removed your code is left with a for loop then a test which all end up with a break so you never do more than one iteration.
The code above basically says log yes unless you find a reason to log nop.
In this case we need a flag to remember the error state:
submitBtn[0].addEventListener("click", function checkInputs() {
let allInputValid = true
for(let i = 0; i<coordValues.length;i++) {
if(coordValues[i].value === "" || isNaN(Number(coordValues[i].value))) {
inputLabel[i].classList.add("input-card__label--error");
inputLabel[i].innerHTML = "Oops! Write a number here."
console.log("nop");
allInputValid = false
}
else {
inputLabel[i].classList.remove("input-card__label--error");
inputLabel[i].innerHTML = labelArray[i];
}
}
if ( allInputValid )
console.log("yep");
});
Whenever an error is spotted, allInputValid is set to false. If there's two errors you set allInputValid to false twice.

Input field not updating with ng-keydown event

In this piece of code I want to add validation to input field, if the value is 0 but I don't know why I am not able to update and enter new value in the input field. Simply I am not able to change the input field value.
Value remain same if I delete existing value and add something in existing value.
Here is HTML code:
<input ng-model="lineitemDetailsCtrlAs.lineItems.orderedQuantity" type="text" class="col-md-6 text-right panel-row-spacing"
ng-keydown="valueChanged($event)" required
/>
and angular code is:
$scope.valueChanged = function (event) {
var quantityRequire={};
if (event.keyCode === 48 || lineitemDetailsCtrlAs.lineItems.orderedQuantity == 0) {
quantityRequire = {
"messageId": "ORDERED_QUANTITY",
"params": [],
"severity": "ERROR"
};
lineitemDetailsCtrlAs.errorMessages.push(quantityRequire);
}
else{
event.preventDefault();
}
};
you are stopping event by "event.preventDefault();", because only keycode 48 (only number 0) is acceptable others keyevents are falling down on else condition and stop updating input value.
I think Rameshkumar Arumugam is right about what's wrong with your code, below is a working example
angular.module("myApp", [])
.controller("MainCtrl", function($scope) {
$scope.lineItems = {
orderedQuantity: 12
};
$scope.errorMessages = [];
$scope.valueChanged = function(event) {
var quantityRequire = {};
if (event.keyCode === 48 || $scope.lineItems.orderedQuantity == 0) {
quantityRequire = {
"messageId": "ORDERED_QUANTITY",
"params": [],
"severity": "ERROR"
};
alert(quantityRequire["messageId"]);
$scope.errorMessages.push(quantityRequire);
}
};
})
<div ng-app="myApp">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<div ng-controller="MainCtrl">
<input ng-model="lineitemDetailsCtrlAs.lineItems.orderedQuantity" type="text" class="col-md-6 text-right panel-row-spacing" ng-keydown="valueChanged($event)" required />
</div>
</div>

Categories