UNPKG

@chatterton/angular2-schema-form

Version:

Angular2 Schema Form (DISCLAIMER: it is not related to angular-schema-form)

253 lines (208 loc) 6.67 kB
import { Observable } from 'rxjs/Observable'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import 'rxjs/add/observable/combineLatest'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/distinctUntilChanged'; import { SchemaValidatorFactory } from '../schemavalidatorfactory'; import { ValidatorRegistry } from './validatorregistry'; export abstract class FormProperty { public schemaValidator: Function; public required: boolean; protected _value: any = null; protected _errors: any = null ; private _valueChanges = new BehaviorSubject<any>(null); private _errorsChanges = new BehaviorSubject<any>(null); private _visible = true; private _visibilityChanges = new BehaviorSubject<boolean>(true); private _root: PropertyGroup; private _parent: PropertyGroup; private _path: string; constructor( schemaValidatorFactory: SchemaValidatorFactory, private validatorRegistry: ValidatorRegistry, public schema: any, parent: PropertyGroup, path: string ) { this.schemaValidator = schemaValidatorFactory.createValidatorFn(this.schema); this._parent = parent; if (parent) { this._root = parent.root; } else if (this instanceof PropertyGroup) { this._root = <PropertyGroup><any>this; } this._path = path; } public get valueChanges() { return this._valueChanges; } public get errorsChanges() { return this._errorsChanges; } public get type(): string { return this.schema.type; } public get parent(): PropertyGroup { return this._parent; } public get root(): PropertyGroup { return this._root || <PropertyGroup><any>this; } public get path(): string { return this._path; } public get value() { return this._value; } public get visible() { return this._visible; } public get valid() { return this._errors === null; } public abstract setValue(value: any, onlySelf: boolean); public abstract reset(value: any, onlySelf: boolean) public updateValueAndValidity(onlySelf = false, emitEvent = true) { this._updateValue(); if (emitEvent) { this.valueChanges.next(this.value); } this._runValidation(); if (this.parent && !onlySelf) { this.parent.updateValueAndValidity(onlySelf, emitEvent); } } /** * @internal */ public abstract _updateValue(); /** * @internal */ public _runValidation(): any { let errors = this.schemaValidator(this._value) || []; let customValidator = this.validatorRegistry.get(this.path); if (customValidator) { let customErrors = customValidator(this.value, this, this.findRoot()) ; errors = this.mergeErrors(errors, customErrors); } if (errors.length === 0) { errors = null; } this._errors = errors; this.setErrors(this._errors); } private mergeErrors(errors, newErrors) { if (newErrors) { if (Array.isArray(newErrors)) { errors = errors.concat(...newErrors); } else { errors.push(newErrors); } } return errors; } private setErrors(errors) { this._errors = errors; this._errorsChanges.next(errors); } protected searchProperty(path: string): FormProperty { let prop: FormProperty = this; let base: PropertyGroup = null; let result = null; if (path[0] === '/') { base = this.findRoot(); result = base.getProperty(path.substr(1)); } else { while (result === null && prop.parent !== null) { prop = base = prop.parent; result = base.getProperty(path); } } return result; } public findRoot(): PropertyGroup { let property: FormProperty = this; while (property.parent !== null ) { property = property.parent; } return <PropertyGroup>property; } private setVisible(visible: boolean) { this._visible = visible; this._visibilityChanges.next(visible); this.updateValueAndValidity(); if (this.parent) { this.parent.updateValueAndValidity(false, true); } } // A fiel is visible if AT LEAST ONE of the properties it depends on is visible AND has a value in the list public _bindVisibility() { let visibleIf = this.schema.visibleIf; if (visibleIf !== undefined) { let propertiesBinding = []; for (let dependencyPath in visibleIf) { if (visibleIf.hasOwnProperty(dependencyPath)) { let property = this.searchProperty(dependencyPath); if (property) { let valueCheck = property.valueChanges.map( value => visibleIf[dependencyPath].indexOf(value) !== -1 ); let visibilityCheck = property._visibilityChanges; let and = Observable.combineLatest([valueCheck, visibilityCheck], (v1, v2) => v1 && v2); propertiesBinding.push(and); } else { console.warn('Can\'t find property ' + dependencyPath + ' for visibility check of ' + this.path); } } } Observable.combineLatest(propertiesBinding, (...values: boolean[]) => { return values.indexOf(true) !== -1; }).distinctUntilChanged().subscribe((visible) => { this.setVisible(visible); }); } } } export abstract class PropertyGroup extends FormProperty { protected properties: FormProperty[] | {[key: string]: FormProperty} = null; getProperty(path: string) { let subPathIdx = path.indexOf('/'); let propertyId = subPathIdx !== -1 ? path.substr(0, subPathIdx) : path ; let property = this.properties[propertyId]; if (property !== null && subPathIdx !== -1 && property instanceof PropertyGroup) { let subPath = path.substr(subPathIdx + 1); property = (<PropertyGroup>property).getProperty(subPath); } return property; } public forEachChild(fn: (formProperty: FormProperty, str: String) => void) { for (let propertyId in this.properties) { if (this.properties.hasOwnProperty(propertyId)) { let property = this.properties[propertyId]; fn(property, propertyId); } } } public forEachChildRecursive(fn: (formProperty: FormProperty) => void) { this.forEachChild((child) => { fn(child); if (child instanceof PropertyGroup) { (<PropertyGroup>child).forEachChildRecursive(fn); } }); } public _bindVisibility() { super._bindVisibility(); this._bindVisibilityRecursive(); } private _bindVisibilityRecursive () { this.forEachChildRecursive((property) => { property._bindVisibility(); }); } public isRoot() { return this === this.root; } }