Client: Add form validation and request handling
This commit is contained in:
parent
ea180246c7
commit
bc382df68f
@ -1,4 +1,4 @@
|
|||||||
import { FormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';
|
import { AbstractControl, FormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constant to validate a `duration` field.
|
* Constant to validate a `duration` field.
|
||||||
@ -17,3 +17,13 @@ export const durationValidator: ValidatorFn = (control: FormGroup): ValidationEr
|
|||||||
const regExp = /^\s*([0-9]+)(:)?([0-5][0-9]?)?\s*[h|m]?$/g;
|
const regExp = /^\s*([0-9]+)(:)?([0-5][0-9]?)?\s*[h|m]?$/g;
|
||||||
return regExp.test(control.value) || control.value === '' ? null : { valid: false };
|
return regExp.test(control.value) || control.value === '' ? null : { valid: false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isNumberRange(minCtrlName: string, maxCtrlName: string): ValidatorFn {
|
||||||
|
return (formControl: AbstractControl): { [key: string]: any } => {
|
||||||
|
const min = formControl.get(minCtrlName).value;
|
||||||
|
const max = formControl.get(maxCtrlName).value;
|
||||||
|
if (min > max) {
|
||||||
|
return { rangeError: true };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -40,10 +40,10 @@
|
|||||||
<!-- Amount of Votes -->
|
<!-- Amount of Votes -->
|
||||||
<small *ngIf="poll.max_votes_amount > 1">
|
<small *ngIf="poll.max_votes_amount > 1">
|
||||||
<ng-container *ngIf="poll.max_votes_amount !== poll.min_votes_amount">
|
<ng-container *ngIf="poll.max_votes_amount !== poll.min_votes_amount">
|
||||||
<span> {{ pollPropertyVerbose.max_votes_amount | translate }}: {{ poll.max_votes_amount }}</span>
|
|
||||||
<br />
|
|
||||||
<span> {{ pollPropertyVerbose.min_votes_amount | translate }}: {{ poll.min_votes_amount }}</span>
|
<span> {{ pollPropertyVerbose.min_votes_amount | translate }}: {{ poll.min_votes_amount }}</span>
|
||||||
<br />
|
<br />
|
||||||
|
<span> {{ pollPropertyVerbose.max_votes_amount | translate }}: {{ poll.max_votes_amount }}</span>
|
||||||
|
<br />
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="poll.max_votes_amount === poll.min_votes_amount">
|
<ng-container *ngIf="poll.max_votes_amount === poll.min_votes_amount">
|
||||||
|
@ -27,8 +27,7 @@ export class AssignmentPollMetaInfoComponent {
|
|||||||
public constructor() {}
|
public constructor() {}
|
||||||
|
|
||||||
public userCanVoe(): boolean {
|
public userCanVoe(): boolean {
|
||||||
// this.poll.canBeVotedFor
|
return this.poll.canBeVotedFor();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getOptionTitle(option: ViewAssignmentOption): string {
|
public getOptionTitle(option: ViewAssignmentOption): string {
|
||||||
|
@ -31,6 +31,11 @@
|
|||||||
<h4 *ngIf="(poll.isMethodY || poll.isMethodN) && poll.max_votes_amount > 1">
|
<h4 *ngIf="(poll.isMethodY || poll.isMethodN) && poll.max_votes_amount > 1">
|
||||||
{{ 'Available votes' | translate }}:
|
{{ 'Available votes' | translate }}:
|
||||||
<b> {{ getVotesAvailable(delegation) }}/{{ poll.max_votes_amount }} </b>
|
<b> {{ getVotesAvailable(delegation) }}/{{ poll.max_votes_amount }} </b>
|
||||||
|
|
||||||
|
<span *ngIf="poll.min_votes_amount > 1">
|
||||||
|
({{ 'At least' | translate }} <b>{{ poll.min_votes_amount }}</b
|
||||||
|
>)
|
||||||
|
</span>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<!-- Options and Actions -->
|
<!-- Options and Actions -->
|
||||||
@ -169,7 +174,7 @@
|
|||||||
mat-flat-button
|
mat-flat-button
|
||||||
color="accent"
|
color="accent"
|
||||||
(click)="submitVote(delegation)"
|
(click)="submitVote(delegation)"
|
||||||
[disabled]="getVotesCount(delegation) == 0"
|
[disabled]="getVotesCount(delegation) < minVotes"
|
||||||
>
|
>
|
||||||
<mat-icon> how_to_vote </mat-icon>
|
<mat-icon> how_to_vote </mat-icon>
|
||||||
<span>
|
<span>
|
||||||
|
@ -57,6 +57,10 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
return this.poll.assignment.default_poll_description;
|
return this.poll.assignment.default_poll_description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get minVotes(): number {
|
||||||
|
return this.poll.min_votes_amount;
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
title: Title,
|
title: Title,
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
|
@ -58,6 +58,8 @@ export abstract class BasePollDialogComponent<
|
|||||||
votes: this.getVoteData(),
|
votes: this.getVoteData(),
|
||||||
publish_immediately: this.publishImmediately
|
publish_immediately: this.publishImmediately
|
||||||
};
|
};
|
||||||
|
console.log('answer: ', answer);
|
||||||
|
|
||||||
this.dialogRef.close(answer);
|
this.dialogRef.close(answer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<!-- Groups entitled to Vote -->
|
<!-- Groups entitled to Vote -->
|
||||||
<mat-form-field *ngIf="contentForm.get('type').value && contentForm.get('type').value !== 'analog'">
|
<mat-form-field *ngIf="isEVotingSelected">
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
formControlName="groups_id"
|
formControlName="groups_id"
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
@ -57,6 +57,23 @@
|
|||||||
<mat-error>{{ 'This field is required.' | translate }}</mat-error>
|
<mat-error>{{ 'This field is required.' | translate }}</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
|
<ng-container formGroupName="votes_amount" *ngIf="isEVotingSelected">
|
||||||
|
<!-- Min Amount of Votes -->
|
||||||
|
<mat-form-field *ngIf="showAmountAndGlobal(data)">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
matInput
|
||||||
|
placeholder="{{ PollPropertyVerbose.min_votes_amount | translate }}"
|
||||||
|
formControlName="min_votes_amount"
|
||||||
|
min="1"
|
||||||
|
required
|
||||||
|
[errorStateMatcher]="parentErrorStateMatcher"
|
||||||
|
/>
|
||||||
|
<mat-error *ngIf="contentForm.controls['votes_amount'].hasError('rangeError')">
|
||||||
|
{{ 'Min votes must be smaller or equal to max votes' | translate }}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<!-- Max Amount of Votes -->
|
<!-- Max Amount of Votes -->
|
||||||
<mat-form-field *ngIf="showAmountAndGlobal(data)">
|
<mat-form-field *ngIf="showAmountAndGlobal(data)">
|
||||||
<input
|
<input
|
||||||
@ -68,22 +85,11 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
</ng-container>
|
||||||
<!-- Min Amount of Votes -->
|
|
||||||
<mat-form-field>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
matInput
|
|
||||||
placeholder="{{ PollPropertyVerbose.min_votes_amount | translate }}"
|
|
||||||
formControlName="min_votes_amount"
|
|
||||||
min="1"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Amount of Votes and global options -->
|
<!-- Amount of Votes and global options -->
|
||||||
<div class="global-options" *ngIf="showAmountAndGlobal(data)">
|
<div class="global-options" *ngIf="isEVotingSelected && showAmountAndGlobal(data)">
|
||||||
<mat-checkbox formControlName="global_yes">
|
<mat-checkbox formControlName="global_yes">
|
||||||
{{ PollPropertyVerbose.global_yes | translate }}
|
{{ PollPropertyVerbose.global_yes | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { AbstractControl, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
@ -13,7 +13,9 @@ import { VotingPrivacyWarningComponent } from 'app/shared/components/voting-priv
|
|||||||
import { AssignmentPollMethod, AssignmentPollPercentBase } from 'app/shared/models/assignments/assignment-poll';
|
import { AssignmentPollMethod, AssignmentPollPercentBase } from 'app/shared/models/assignments/assignment-poll';
|
||||||
import { PercentBase } from 'app/shared/models/poll/base-poll';
|
import { PercentBase } from 'app/shared/models/poll/base-poll';
|
||||||
import { PollType } from 'app/shared/models/poll/base-poll';
|
import { PollType } from 'app/shared/models/poll/base-poll';
|
||||||
|
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
|
||||||
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||||
|
import { isNumberRange } from 'app/shared/validators/custom-validators';
|
||||||
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
||||||
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
||||||
import {
|
import {
|
||||||
@ -39,6 +41,7 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
|
|||||||
* The form-group for the meta-info.
|
* The form-group for the meta-info.
|
||||||
*/
|
*/
|
||||||
public contentForm: FormGroup;
|
public contentForm: FormGroup;
|
||||||
|
public parentErrorStateMatcher = new ParentErrorStateMatcher();
|
||||||
|
|
||||||
public PollType = PollType;
|
public PollType = PollType;
|
||||||
public PollPropertyVerbose = PollPropertyVerbose;
|
public PollPropertyVerbose = PollPropertyVerbose;
|
||||||
@ -102,6 +105,10 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get isEVotingSelected(): boolean {
|
||||||
|
return this.contentForm.get('type').value && this.contentForm.get('type').value !== 'analog';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Retrieves necessary metadata from the pollService,
|
* Constructor. Retrieves necessary metadata from the pollService,
|
||||||
* injects the poll itself
|
* injects the poll itself
|
||||||
@ -141,11 +148,7 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(this.contentForm.controls).forEach(key => {
|
this.patchForm(this.contentForm);
|
||||||
if (this.data[key]) {
|
|
||||||
this.contentForm.get(key).patchValue(this.data[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
this.updatePollValues(this.contentForm.value);
|
this.updatePollValues(this.contentForm.value);
|
||||||
this.updatePercentBases(this.contentForm.get('pollmethod').value);
|
this.updatePercentBases(this.contentForm.get('pollmethod').value);
|
||||||
@ -173,6 +176,25 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
|
|||||||
this.setWarning();
|
this.setWarning();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic recursive helper function to patch the form
|
||||||
|
* will transitive move poll.min_votes_amount and poll.max_votes_amount into
|
||||||
|
* form.votes_amount.min_votes_amount/max_votes_amount
|
||||||
|
* @param formGroup
|
||||||
|
*/
|
||||||
|
private patchForm(formGroup: FormGroup): void {
|
||||||
|
for (const key of Object.keys(formGroup.controls)) {
|
||||||
|
const currentControl = formGroup.controls[key];
|
||||||
|
if (currentControl instanceof FormControl) {
|
||||||
|
if (this.data[key]) {
|
||||||
|
currentControl.patchValue(this.data[key]);
|
||||||
|
}
|
||||||
|
} else if (currentControl instanceof FormGroup) {
|
||||||
|
this.patchForm(currentControl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private disablePollType(): void {
|
private disablePollType(): void {
|
||||||
this.contentForm.get('type').disable();
|
this.contentForm.get('type').disable();
|
||||||
}
|
}
|
||||||
@ -243,8 +265,14 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getValues<V extends ViewBasePoll>(): Partial<V> {
|
public getValues(): Partial<T> {
|
||||||
return { ...this.data, ...this.contentForm.value };
|
return { ...this.data, ...this.serializeForm(this.contentForm) };
|
||||||
|
}
|
||||||
|
|
||||||
|
private serializeForm(formGroup: FormGroup): Partial<T> {
|
||||||
|
const formData = { ...formGroup.value, ...formGroup.value.votes_amount };
|
||||||
|
delete formData.votes_amount;
|
||||||
|
return formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -300,8 +328,13 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
|
|||||||
pollmethod: ['', Validators.required],
|
pollmethod: ['', Validators.required],
|
||||||
onehundred_percent_base: ['', Validators.required],
|
onehundred_percent_base: ['', Validators.required],
|
||||||
majority_method: ['', Validators.required],
|
majority_method: ['', Validators.required],
|
||||||
|
votes_amount: this.fb.group(
|
||||||
|
{
|
||||||
max_votes_amount: [1, [Validators.required, Validators.min(1)]],
|
max_votes_amount: [1, [Validators.required, Validators.min(1)]],
|
||||||
min_votes_amount: [1, [Validators.required, Validators.min(1)]],
|
min_votes_amount: [1, [Validators.required, Validators.min(1)]]
|
||||||
|
},
|
||||||
|
{ validator: isNumberRange('min_votes_amount', 'max_votes_amount') }
|
||||||
|
),
|
||||||
groups_id: [],
|
groups_id: [],
|
||||||
global_yes: [false],
|
global_yes: [false],
|
||||||
global_no: [false],
|
global_no: [false],
|
||||||
|
Loading…
Reference in New Issue
Block a user