import { BehaviorSubject , combineLatest , Observable } from 'rxjs' ; import { distinctUntilChanged , map } from 'rxjs/operators' ; import { AlainSFConfig } from '@delon/util/config' ; import type { NzSafeAny } from 'ng-zorro-antd/core/types' ; import { SF_SEQ } from '../const' ; import { ErrorData } from '../errors' ; import { SFFormValueChange , SFUpdateValueAndValidity , SFValue } from '../interface' ; import { SFSchema , SFSchemaType } from '../schema' ; import { SFUISchema , SFUISchemaItem , SFUISchemaItemRun } from '../schema/ui' ; import { isBlank } from '../utils' ; import { SchemaValidatorFactory } from '../validator.factory' ; import type { Widget } from '../widget' ; export abstract class FormProperty { private _errors : ErrorData [ ] | null = null ; private _valueChanges = new BehaviorSubject < SFFormValueChange > ( { path : null , pathValue : null , value : null } ) ; private _errorsChanges = new BehaviorSubject < ErrorData [ ] | null > ( null ) ; private _visible = true ; private _visibilityChanges = new BehaviorSubject < boolean > ( true ) ; private _root : PropertyGroup ; private _parent : PropertyGroup | null ; _objErrors : { [ key ] : ErrorData [ ] ; } = { } ; schemaValidator : ( value : SFValue ) => ErrorData [ ] ; schema : SFSchema ; ui : SFUISchema | SFUISchemaItemRun ; formData : Record < string , unknown > ; _value : SFValue = null ; widget ! : Widget < FormProperty , SFUISchemaItem > ; path : string ; constructor ( schemaValidatorFactory , schema , ui , formData , parent , path , private _options ) { this . schema = schema ; this . ui = ui ; this . schemaValidator = schemaValidatorFactory . createValidatorFn ( schema , { ingoreKeywords : this . ui . ingoreKeywords as string [ ] , debug : ( ui as SFUISchemaItem ) ! . debug ! } ) ; this . formData = formData || schema . default ; this . _parent = parent ; if ( parent ) { this . _root = parent . root ; } else { this . _root = this as NzSafeAny ; } this . path = path ; } get valueChanges ( ) : BehaviorSubject < SFFormValueChange > { return this . _valueChanges ; } get errorsChanges ( ) : BehaviorSubject < ErrorData [ ] | null > { return this . _errorsChanges ; } get type ( ) : SFSchemaType { return this . schema . type ! ; } get parent ( ) : PropertyGroup | null { return this . _parent ; } get root ( ) : PropertyGroup { return this . _root ; } get value ( ) : SFValue { return this . _value ; } get errors ( ) : ErrorData [ ] | null { return this . _errors ; } get visible ( ) : boolean { return this . _visible ; } get valid ( ) : boolean { return this . _errors === null || this . _errors . length === 0 ; } get options ( ) : AlainSFConfig { return this . _options ; } param onlySelf abstract setValue ( value , onlySelf ) ; param onlySelf abstract resetValue ( value , onlySelf ) ; internal abstract _hasValue ( ) ; internal abstract _updateValue ( ) ; /**
     * 更新值且校验数据
     */ updateValueAndValidity ( options ? ) { options = { onlySelf : false , emitValidator : true , emitValueEvent : true , updatePath : '' , updateValue : null , ... options } ; this . _updateValue ( ) ; if ( options . emitValueEvent ) { options . updatePath = options . updatePath || this . path ; options . updateValue = options . updateValue == null ? this . value : options . updateValue ; this . valueChanges . next ( { value : this . value , path : options . updatePath , pathValue : options . updateValue } ) ; } if ( options . emitValidator && this . ui . liveValidate === true ) { this . _runValidation ( ) ; } if ( this . parent && ! options . onlySelf ) { this . parent . updateValueAndValidity ( { ... options , emitValidator : false } ) ; } } /** 根据路径搜索表单属性 */ searchProperty ( path ) { let prop = this ; let base = null ; let result = null ; if ( path [ 0 ] === SF_SEQ ) { base = this . findRoot ( ) ; result = base . getProperty ( path . substring ( 1 ) ) ; } else { while ( result === null && prop . parent !== null ) { prop = base = prop . parent ; result = base . getProperty ( path ) ; } } return result ! ; } /** 查找根表单属性 */ findRoot ( ) { let property = this ; while ( property . parent !== null ) { property = property . parent ; } return property as PropertyGroup ; } private isEmptyData ( value ) { if ( isBlank ( value ) ) return true ; switch ( this . type ) { case 'string' : return `${ value }` . length === 0 ; } return false ; } internal _runValidation ( ) { let errors ; const isEmpty = this . isEmptyData ( this . _value ) ; if ( isEmpty && this . ui . _required ) { errors = [ { keyword : 'required' } ] ; } else if ( isEmpty ) { errors = [ ] ; } else { errors = this . schemaValidator ( this . _value ) || [ ] ; } const customValidator = ( this . ui as SFUISchemaItemRun ) . validator ; if ( typeof customValidator === 'function' ) { const customErrors = customValidator ( this . value , this , this . findRoot ( ) ) ; if ( customErrors instanceof Observable ) { customErrors . subscribe ( res => { this . setCustomErrors ( errors , res ) ; this . widget . detectChanges ( ) ; } ) ; return ; } this . setCustomErrors ( errors , customErrors ) ; return ; } this . _errors = errors ; this . setErrors ( this . _errors ) ; } private setCustomErrors ( errors , list ) { const hasCustomError = list != null && list . length > 0 ; if ( hasCustomError ) { list . forEach ( err => { if ( ! err . message ) { throw new Error ( `The custom validator must contain a 'message' attribute to viewed error text` ) ; } err . _custom = true ; } ) ; } this . _errors = this . mergeErrors ( errors , list ) ; this . setErrors ( this . _errors ) ; } private mergeErrors ( errors , newErrors ) { if ( newErrors ) { if ( Array . isArray ( newErrors ) ) { errors = errors . concat ( ... newErrors ) ; } else { errors . push ( newErrors ) ; } } return errors ; } protected setErrors ( errors , emitFormat = true ) { if ( emitFormat && errors && ! this . ui . onlyVisual ) { const l = ( this . widget && this . widget . l . error ) || { } ; errors = errors . map ( ( err ) => { let message = err . _custom === true && err . message ? err . message : ( this . ui . errors || { } ) [ err . keyword ] || this . _options . errors ! [ err . keyword ] || l [ err . keyword ] || `` ; if ( message && typeof message === 'function' ) { message = message ( err ) as string ; } if ( message ) { if ( ~ ( message as string ) . indexOf ( '{' ) ) { message = ( message as string ) . replace ( /{([\.a-zA-Z0-9]+)}/g , ( _v , key ) => err . params ! [ key ] || '' ) ; } err . message = message as string ; } return err ; } ) ; } this . _errors = errors ; this . _errorsChanges . next ( errors ) ; if ( this . _parent ) { this . _parent . setParentAndPlatErrors ( errors , this . path ) ; } } setParentAndPlatErrors ( errors , path ) { this . _objErrors [ path ] = errors ; const platErrors = [ ] ; Object . keys ( this . _objErrors ) . forEach ( p => { const property = this . searchProperty ( p ) ; if ( property && ! property . visible ) return ; platErrors . push ( ... this . _objErrors [ p ] ) ; } ) ; this . setErrors ( platErrors , false ) ; } /**
     * Set the hide or display of widget
     * 设置小部件的隐藏或显示
     */ setVisible ( visible ) { this . _visible = visible ; this . _visibilityChanges . next ( visible ) ; if ( visible ) { this . resetValue ( this . value , true ) ; } } _bindVisibility ( ) { const visibleIf = ( this . ui as SFUISchemaItem ) . visibleIf ; if ( typeof visibleIf === 'object' && Object . keys ( visibleIf ) . length === 0 ) { this . setVisible ( false ) ; } else if ( visibleIf !== undefined ) { const propertiesBinding = [ ] ; for ( const dependencyPath in visibleIf ) { if ( visibleIf . hasOwnProperty ( dependencyPath ) ) { const property = this . searchProperty ( dependencyPath ) ; if ( property ) { const valueCheck = property . valueChanges . pipe ( map ( res => { const vi = visibleIf [ dependencyPath ] ; if ( typeof vi === 'function' ) { return vi ( res . value , property ) ; } if ( vi . indexOf ( '$ANY$' ) !== - 1 ) { return res . value . length > 0 ; } else { return vi . indexOf ( res . value ) !== - 1 ; } } ) ) ; const visibilityCheck = property . _visibilityChanges ; const and = combineLatest ( [ valueCheck , visibilityCheck ] ) . pipe ( map ( results => results [ 0 ] && results [ 1 ] ) ) ; propertiesBinding . push ( and ) ; } else { if ( typeof ngDevMode === 'undefined' || ngDevMode ) { console . warn ( `Can't find property ${ dependencyPath } for visibility check of ${ this . path }` ) ; } } } } combineLatest ( propertiesBinding ) . pipe ( map ( values => values . indexOf ( true ) !== - 1 ) , distinctUntilChanged ( ) ) . subscribe ( visible => this . setVisible ( visible ) ) ; } } } export abstract class PropertyGroup extends FormProperty { properties : { [ key ] : FormProperty ; } | FormProperty [ ] | null = null ; getProperty ( path ) { const subPathIdx = path . indexOf ( SF_SEQ ) ; const propertyId = subPathIdx !== - 1 ? path . substring ( 0 , subPathIdx ) : path ; let property = ( this . properties as { [ key ] : FormProperty ; } ) [ propertyId ] ; if ( property !== null && subPathIdx !== - 1 && property instanceof PropertyGroup ) { const subPath = path . substring ( subPathIdx + 1 ) ; property = ( property as PropertyGroup ) . getProperty ( subPath ) ! ; } return property ; } forEachChild ( fn ) { for ( const propertyId in this . properties ) { if ( this . properties . hasOwnProperty ( propertyId ) ) { const property = ( this . properties as { [ key ] : FormProperty ; } ) [ propertyId ] ; fn ( property , propertyId ) ; } } } forEachChildRecursive ( fn ) { this . forEachChild ( child => { fn ( child ) ; if ( child instanceof PropertyGroup ) { ( child as PropertyGroup ) . forEachChildRecursive ( fn ) ; } } ) ; } _bindVisibility ( ) { super . _bindVisibility ( ) ; this . _bindVisibilityRecursive ( ) ; } private _bindVisibilityRecursive ( ) { this . forEachChildRecursive ( property => { property . _bindVisibility ( ) ; } ) ; } isRoot ( ) { return this === this . root ; } }