Commit 57ea4efd authored by shraddha's avatar shraddha
Browse files

Changes for validations

Showing with 406 additions and 14 deletions
+406 -14
{
"name": "common-form-elements-v9",
"version": "4.1.2",
"version": "4.3.1",
"peerDependencies": {
"@angular/common": "^9.1.13",
"@angular/core": "^9.1.13",
......
......@@ -123,7 +123,7 @@ export interface Validator {
export type DynamicFieldConfigOptionsBuilder<T> =
(control: FormControl, depends?: FormControl[], formGroup?: FormGroup, notifyLoading?: () => void,
(control: CustomFormControl, depends?: FormControl[], formGroup?: FormGroup, notifyLoading?: () => void,
notifyLoaded?: () => void) => Observable<FieldConfigOption<T>[]> | Promise<FieldConfigOption<T>[]>;
......@@ -145,4 +145,5 @@ export interface CustomFormControl extends FormControl {
output?: any;
customEventHandler$?: Subject<any>;
shouldListenToCustomEvent?: Boolean;
isVisible?: any;
}
......@@ -36,6 +36,7 @@ import { DynamicFrameworkComponent } from './dynamic-framework/dynamic-framework
import { DynamicRadioComponent } from './dynamic-radio/dynamic-radio.component';
import { DynamicDialcodeComponent } from './dynamic-dialcode/dynamic-dialcode.component';
import { DynamicFrameworkCategoryNestedSelectComponent } from './dynamic-framework-category-nested-select/dynamic-framework-category-nested-select.component';
import { DynamicDateComponent } from './dynamic-date/dynamic-date.component';
@NgModule({
declarations: [
......@@ -70,7 +71,8 @@ import { DynamicFrameworkCategoryNestedSelectComponent } from './dynamic-framewo
DynamicFrameworkComponent,
DynamicRadioComponent,
DynamicDialcodeComponent,
DynamicFrameworkComponent
DynamicFrameworkComponent,
DynamicDateComponent
],
imports: [
CommonModule,
......@@ -110,7 +112,8 @@ import { DynamicFrameworkCategoryNestedSelectComponent } from './dynamic-framewo
DynamicFrameworkComponent,
DynamicRadioComponent,
DynamicDialcodeComponent,
DynamicFrameworkComponent
DynamicFrameworkComponent,
DynamicDateComponent
],
entryComponents: [
DynamicFormComponent,
......@@ -129,7 +132,8 @@ import { DynamicFrameworkCategoryNestedSelectComponent } from './dynamic-framewo
DynamicFrameworkComponent,
DynamicRadioComponent,
DynamicDialcodeComponent,
DynamicFrameworkComponent
DynamicFrameworkComponent,
DynamicDateComponent
]
})
export class CommonFormElementsModule { }
label {
display: block;
font-size: 1rem;
margin: 0;
}
.sb-textbox {
width: 100%;
padding: 8px 16px;
border: 0.5px solid #333333;
box-sizing: border-box;
}
::placeholder {
padding: 0.25rem;
opacity: 0.99;
color: #999999;
font-family: "Noto Sans";
font-size: 12px;
font-weight: bold;
}
.sb-input {
margin: 1rem 0;
}
.cf-error{
color: red;
font-family: "Noto Sans";
font-size: 12px;
}
.async-container{
text-align: center;
}
.async-btn{
padding: 12px 16px;
background-color: #008840;
color: white;
border-radius: 20px !important;
}
.async-text{
display: flex;
align-items: center;
border: 0.5px solid #333333;
}
.async-text > input{
border: none
}
.normal-text > .async-icons > sb-red-exclamation, .normal-text > .async-icons > sb-green-tick, .normal-text > .async-icons > sb-empty-circle{
display: none;
}
.prefix{
white-space: nowrap;
padding: 0 4px;
}
.async-icons{
margin: auto;
padding: 0 4px;
}
\ No newline at end of file
<div class="sb-input">
<label *ngIf="label" [attr.data-title]="field.description ? field.description : null">{{label}}</label>
<!-- <div *ngIf="labelHtml" [innerHTML]="labelHtml | transposeHtml"></div> -->
<div [ngClass]="{'async-text': (asyncValidation && asyncValidation?.trigger), '': (!asyncValidation || !asyncValidation?.trigger)}">
<div class="prefix" *ngIf="prefix">
<span>{{prefix}}</span>
</div>
<input [formControl]="formControlRef" [class.valid]="formControlRef.valid &&
(formControlRef.dirty || formControlRef.touched)"
[class.invalid]="formControlRef.invalid &&
(formControlRef.dirty || formControlRef.touched)" class="form-control sb-textbox {{disabled}}" placeholder={{placeholder}} [type]="field?.dataType"
[attr.disabled]="disabled ? true : null">
<div class="async-icons" *ngIf="asyncValidation && asyncValidation?.trigger">
<!-- <sb-green-tick *ngIf="formControlRef.value && formControlRef.status === 'VALID'"></sb-green-tick> -->
<!-- <sb-red-exclamation *ngIf="formControlRef.value && formControlRef.status !== 'VALID'"></sb-red-exclamation> -->
<!-- <sb-empty-circle *ngIf="!formControlRef.value"></sb-empty-circle> -->
</div>
</div>
<ng-container *ngFor="let validation of validations">
<div class="cf-error"
*ngIf="(validation.type && (validation.type).toLowerCase() && validation.message && formControlRef.errors && formControlRef.errors[(validation.type).toLowerCase()] &&
(formControlRef.dirty || formControlRef.touched)) ">
{{ validation.message }}
</div>
</ng-container>
<ng-container *ngIf="asyncValidation?.trigger">
<div class="async-validator"
[hidden]="formControlRef.status === 'VALID' || formControlRef.status !== 'PENDING' || !formControlRef.value">
<div class="cf-error" *ngIf="asyncValidation?.message">
{{ asyncValidation.message }}
</div>
<div class="async-container">
<button class="async-btn" #validationTrigger [attr.data-marker]="asyncValidation.marker">
{{asyncValidation.trigger}}
</button>
</div>
</div>
</ng-container>
</div>
\ No newline at end of file
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DynamicDateComponent } from './dynamic-date.component';
describe('DynamicDateComponent', () => {
let component: DynamicDateComponent;
let fixture: ComponentFixture<DynamicDateComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DynamicDateComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DynamicDateComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, Input, OnInit, AfterViewInit, OnChanges, ViewChild, ElementRef} from '@angular/core';
import {FormControl} from '@angular/forms';
import { FieldConfigAsyncValidation } from '../common-form-config';
import { DatePipe } from '@angular/common';
import { tap } from 'rxjs/operators';
import { Subscription } from 'rxjs/internal/Subscription';
import * as moment_ from 'moment';
@Component({
selector: 'sb-dynamic-date',
templateUrl: './dynamic-date.component.html',
styleUrls: ['./dynamic-date.component.css']
})
export class DynamicDateComponent implements OnInit {
@Input() asyncValidation?: FieldConfigAsyncValidation;
@Input() label: String;
@Input() labelHtml: any;
@Input() placeholder: String;
@Input() validations?: any;
@Input() formControlRef?: FormControl;
@Input() prefix?: String;
@Input() default: String;
@Input() field?: any;
@Input() disabled: Boolean;
@ViewChild('validationTrigger') validationTrigger: ElementRef;
valueChangesSubscription: Subscription;
constructor() {
}
ngOnInit() {
let result = this.validations.find(data => data.type==='dateFormat');
let date = moment_(this.field.default,result.value).format("YYYY-MM-DD")
this.formControlRef.setValue(date);
}
ngAfterViewInit() {
if (this.asyncValidation && this.asyncValidation.asyncValidatorFactory && this.formControlRef) {
if (this.formControlRef.asyncValidator) {
return;
}
this.formControlRef.setAsyncValidators(this.asyncValidation.asyncValidatorFactory(
this.asyncValidation.marker,
this.validationTrigger.nativeElement
));
console.log(this.formControlRef);
}
}
}
......@@ -23,6 +23,7 @@ import { DynamicFrameworkCategorySelectComponent } from '../dynamic-framework-ca
import { DynamicRadioComponent } from '../dynamic-radio/dynamic-radio.component';
import { DynamicDialcodeComponent } from '../dynamic-dialcode/dynamic-dialcode.component';
import { DynamicFrameworkCategoryNestedSelectComponent } from '../dynamic-framework-category-nested-select/dynamic-framework-category-nested-select.component';
import { DynamicDateComponent } from '../dynamic-date/dynamic-date.component';
const componentMapper = {
textarea: DynamicTextareaComponent,
......@@ -41,7 +42,8 @@ const componentMapper = {
frameworkCategoryNestedSelect: DynamicFrameworkCategoryNestedSelectComponent,
frameworkCategorySelect: DynamicFrameworkCategorySelectComponent,
radio: DynamicRadioComponent,
dialcode: DynamicDialcodeComponent
dialcode: DynamicDialcodeComponent,
date:DynamicDateComponent
};
@Directive({
......
......@@ -169,6 +169,9 @@ export class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
case 'text':
defaultVal = element.default || null;
break;
case 'date':
defaultVal = element.default || null;
break;
case 'dialcode':
defaultVal = element.default || null;
break;
......@@ -282,12 +285,16 @@ export class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
case 'compare':
validationList.push(this.compareFields.bind(this, element.validations[i].criteria));
break;
case 'minDate':
validationList.push(this.compareDate.bind(this, element.validations,element.validations[i]));
break;
case 'maxDate':
validationList.push(this.compareDate.bind(this, element.validations,element.validations[i]));
break;
}
});
}
formValueList.push(Validators.compose(validationList));
return formValueList;
}
......@@ -369,4 +376,17 @@ export class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {
return false;
}
}
compareDate(validations,types, control: AbstractControl): ValidationErrors | null {
let result = validations.find(data => data.type === 'dateFormat');
let minDate = moment_(types.value, result.value).format('YYYY-MM-DD');
let maxDate = moment_(types.value, result.value).format('YYYY-MM-DD');
if ((types.type==='minDate' && control.value < minDate)){
return {mindate:types.message};
}
else if((types.type==='maxDate' && control.value > maxDate)){
return {maxdate:types.message};
}
return null;
}
}
<div class="sb-dropdown" *ngIf="!type">
<div class="sb-dropdown" *ngIf="!type && formControlRef?.isVisible === 'yes'">
<label [attr.data-title]="field.description ? field.description : null">{{label}} {{context && 'has context'}} {{context ? field.context : '' }}</label>
<ng-container *ngIf="options">
<div class="dropdown-container">
......
......@@ -57,6 +57,7 @@ export class DynamicMultiSelectComponent implements OnInit, OnChanges, OnDestroy
}
ngOnInit() {
this.formControlRef.isVisible = 'yes';
if (!_.isEmpty(this.field.sourceCategory)) {
this.formControlRef.sourceCategory = this.field.sourceCategory;
}
......@@ -65,6 +66,10 @@ export class DynamicMultiSelectComponent implements OnInit, OnChanges, OnDestroy
this.formControlRef.output = this.field.output;
}
if (!this.options) {
this.options = _.isEmpty(this.field.options) ? this.isOptionsClosure(this.field.options) && this.field.options : [];
}
// if (this.context) {
// this.contextValueChangesSubscription = this.context.valueChanges.pipe(
// tap(() => {
......@@ -107,6 +112,15 @@ export class DynamicMultiSelectComponent implements OnInit, OnChanges, OnDestroy
if (this.isOptionsClosure(this.options)) {
// tslint:disable-next-line:max-line-length
this.options$ = (this.options as DynamicFieldConfigOptionsBuilder<any>)(this.formControlRef, this.depends, this.formGroup, () => this.dataLoadStatusDelegate.next('LOADING'), () => this.dataLoadStatusDelegate.next('LOADED')) as any;
this.options$.subscribe(
(response: any) => {
if (response && response.range) {
this.field.range = response.range;
} else {
this.field.range = null;
}
}
);
}
......
<div class="sb-input">
<div class="sb-input" *ngIf="isTextBoxRequired=='yes'">
<label *ngIf="label" [attr.data-title]="field.description ? field.description : null">{{label}}</label>
<!-- <div *ngIf="labelHtml" [innerHTML]="labelHtml | transposeHtml"></div> -->
......
import {Component, Input, OnInit, AfterViewInit, OnChanges, ViewChild, ElementRef} from '@angular/core';
import {Component, Input, OnInit, AfterViewInit, OnChanges, ViewChild, ElementRef, OnDestroy} from '@angular/core';
import {FormControl} from '@angular/forms';
import { FieldConfigAsyncValidation } from '../common-form-config';
import * as _ from 'lodash-es';
import { merge, Subscription } from 'rxjs';
import { tap } from 'rxjs/operators'
@Component({
selector: 'sb-dynamic-textbox',
templateUrl: './dynamic-textbox.component.html',
styleUrls: ['./dynamic-textbox.component.css']
})
export class DynamicTextboxComponent implements OnInit, AfterViewInit, OnChanges {
export class DynamicTextboxComponent implements OnInit, AfterViewInit, OnChanges,OnDestroy {
@Input() asyncValidation?: FieldConfigAsyncValidation;
@Input() label: String;
......@@ -20,11 +23,17 @@ export class DynamicTextboxComponent implements OnInit, AfterViewInit, OnChange
@Input() field?: any;
@Input() disabled: Boolean;
@ViewChild('validationTrigger') validationTrigger: ElementRef;
@Input() depends?: FormControl[];
public isTextBoxRequired: String='yes';
contextValueChangesSubscription?: Subscription;
constructor() {
}
ngOnInit() {
if (!_.isEmpty(this.depends)) {
this.handleDependantFieldChanges();
}
}
ngOnChanges() {
......@@ -43,4 +52,18 @@ export class DynamicTextboxComponent implements OnInit, AfterViewInit, OnChange
}
}
handleDependantFieldChanges() {
this.contextValueChangesSubscription = merge(..._.map(this.depends, depend => depend.valueChanges)).pipe(
tap((value: any) => {
this.isTextBoxRequired = _.toLower(value);
})
).subscribe();
this.isTextBoxRequired = _.toLower(_.first(_.map(this.depends, depend => depend.value)));
}
ngOnDestroy(): void {
if (this.contextValueChangesSubscription) {
this.contextValueChangesSubscription.unsubscribe();
}
}
}
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { of } from 'rxjs';
import { merge, of } from 'rxjs';
import { delay, switchMap } from 'rxjs/operators';
import { timer } from './formConfig';
import * as _ from 'lodash-es';
import * as moment_ from 'moment';
let evidenceMimeType;
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
......@@ -12,6 +14,7 @@ import * as _ from 'lodash-es';
export class AppComponent implements OnInit {
title = 'sunbird-forms';
config: any = timer;
evidenceMimeType: any;
ngOnInit() {
......@@ -20,6 +23,11 @@ export class AppComponent implements OnInit {
if (field.code === 'framework') {
field.options = this.getFramework;
}
if (field.code === 'evidenceMimeType') {
evidenceMimeType = field.range;
field.options = this.setEvidence;
field.range = null;
}
});
});
......@@ -30,6 +38,10 @@ export class AppComponent implements OnInit {
}
valueChanges(event) {
if(event.startDate){
let date = moment_(event.startDate).format("YYYY-MM-DD[T]HH:mm:ss.SSS[Z]");
event.startDate=date;
}
console.log(event);
}
......@@ -347,4 +359,19 @@ export class AppComponent implements OnInit {
return response;
}
setEvidence(control, depends: FormControl[], formGroup: FormGroup, loading, loaded) {
control.isVisible = 'no';
const response = merge(..._.map(depends, depend => depend.valueChanges)).pipe(
switchMap((value: any) => {
if (!_.isEmpty(value) && _.toLower(value) === 'yes') {
control.isVisible = 'yes';
return of({range: evidenceMimeType});
} else {
control.isVisible = 'no';
return of(null);
}
})
);
return response;
}
}
......@@ -2,6 +2,59 @@ export const timer = [
{
'name': 'First Section',
'fields': [
{
'code': 'showEvidence',
'dataType': 'text',
'description': 'Allow Evidence',
'editable': true,
'index': 5,
'inputType': 'radio',
'label': 'Allow Evidence',
'name': 'showEvidence',
'placeholder': 'showEvidence',
'renderingHints': {
'class': 'sb-g-col-lg-1'
},
'range': [
'Yes',
'No'
],
'required': true,
'visible': true
},
{
'code': 'evidenceMimeType',
'dataType': 'list',
'depends': [
'showEvidence'
],
'description': 'Evidence',
'editable': true,
'inputType': 'multiselect',
'label': 'evidence',
'name': 'evidenceMimeType',
'placeholder': 'evidence',
'renderingHints': {
'class': 'sb-g-col-lg-1'
},
'required': false,
'visible': true,
'range': [
{
'value': 'image/png',
'label': 'image/png'
},
{'value': 'audio/mp3',
'label': 'audio/mp3'
},
{'value': 'video/mp4',
'label': 'video/mp4'
},
{'value': 'video/webm',
'label': 'video/webm'
}
]
},
{
'code': 'appIcon',
'dataType': 'text',
......@@ -44,6 +97,39 @@ export const timer = [
],
'default': 'Untitled Course'
},
{
'code': 'startDate',
'dataType': 'date',
'description': 'start Date',
'editable': true,
'inputType': 'date',
'label': 'Start Date',
'name': 'Start Date',
'placeholder': 'start Date',
'renderingHints': {
'class': 'sb-g-col-lg-1 required'
},
'required': true,
'visible': true,
'default': '2021-07-20T00:00:00.000Z',
'validations': [
{
'type': 'minDate',
'value': '2021-07-01T00:00:00.000Z',
'message': 'Date should not be less than 01-07-2021'
},
{
'type': 'maxDate',
'value':'2022-01-01T00:00:00.000Z',
'message': 'Date should be greater than 01-07-2022'
},
{
'type': 'dateFormat',
'value':'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]',
'message': 'Date format not matched'
}
]
},
{
'code': 'description',
'dataType': 'text',
......@@ -136,7 +222,36 @@ export const timer = [
'value': '20'
}
]
}
},
{
'code': 'instanceLabel',
'depends': [
'dialcodeRequired'
],
'dataType': 'text',
'description': 'Add label',
'editable': true,
'inputType': 'text',
'label': 'Add label',
'name': 'instanceLabel',
'placeholder': '',
'renderingHints': {
'class': 'sb-g-col-lg-1 required'
},
'required': true,
'visible': true,
'validations': [
{
'type': 'maxLength',
'value': '120',
'message': 'Input is Exceeded'
},
{
'type': 'required',
'message': 'Label is required'
}
],
},
]
},
{
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment