import { Input , EventEmitter , Output , OnInit , OnChanges , SimpleChanges , ChangeDetectorRef , Directive } from '@angular/core' ; import { createValueForSchema , jsonSchemaDataTypes , jsonSchemaDataFormats , inferType , dataTypeMap , getIcon , getCurrentType , JsonSchemaDataType , JSONEditorSchema , PropertyIndex } from '../json-editor.helper' ; import { JSONSchema7TypeName } from 'json-schema' ; @ Directive ( ) export class ObjectNode implements OnInit , OnChanges { @ Input ( ) schema : JSONEditorSchema ; @ Input ( ) model : any ; @ Input ( ) required = false ; @ Input ( ) expanded : boolean ; @ Input ( ) path : string ; @ Input ( ) errors : any [ ] ; @ Input ( ) typeCheckOverrides ? : any ; @ Input ( ) schemaBuilderMode : boolean ; @ Input ( ) schemaRef : JSONEditorSchema ; @ Input ( ) showKnownProperties = false ; @ Output ( ) modelChange : EventEmitter < any > = new EventEmitter ( ) ; @ Output ( ) schemaUpdate : EventEmitter < JSONEditorSchema > = new EventEmitter ( ) ; requiredCache : { [ key ] : boolean ; } = { } ; initialized = false ; dataTypes : JsonSchemaDataType [ ] = [ ... jsonSchemaDataTypes , ... jsonSchemaDataFormats ] ; propertyCounter = 1 ; propertyId = 1 ; propertyIndex : PropertyIndex = { } ; duplicatedFields = new Map < string , string > ( ) ; dataTypeMap = dataTypeMap ; constructor ( protected cdr ) { } ngOnInit ( ) { this . update ( ) ; } ngOnChanges ( changes ) { if ( changes . model !== undefined || changes . schema !== undefined ) { this . update ( ) ; } } update ( ) { setTimeout ( ( ) => { for ( const prop in this . schema . properties ) { if ( Array . isArray ( this . schema . properties [ prop ] . type ) && this . schema . properties [ prop ] . type . length > 0 ) { if ( ! this . schema . properties [ prop ] . $meta ) { this . schema . properties [ prop ] . $meta = { } ; } this . schema . properties [ prop ] . $meta . type = [ ... this . schema . properties [ prop ] . type ] ; if ( this . model [ prop ] !== undefined ) { this . schema . properties [ prop ] = { ... this . schema . properties [ prop ] , ... inferType ( this . model [ prop ] , this . typeCheckOverrides , this . schema . properties [ prop ] . $meta . type ) } ; } else { this . schema . properties [ prop ] . type = this . schema . properties [ prop ] . type [ 0 ] as JSONSchema7TypeName ; this . schema . properties [ prop ] . $meta . currentType = getCurrentType ( this . schema . properties [ prop ] ) ; } } } this . updateRequiredCache ( ) ; this . indexProperties ( ) ; this . addRequiredProperties ( ) ; this . updateIcons ( ) ; this . initialized = true ; } ) ; } param propName param value updateProp ( id , value ) { const propName = this . propertyIndex [ id ] . propertyName ; this . model [ propName ] = value ; this . modelChange . emit ( this . model ) ; } param id param name updatePropertyName ( id , name ) { const existingPropertyValue = this . model [ name ] ; const oldName = this . propertyIndex [ id ] . propertyName ; this . duplicatedFields . delete ( id as string ) ; if ( existingPropertyValue === undefined ) { this . model [ name ] = this . model [ oldName ] ; this . propertyIndex [ id ] . propertyName = name ; delete this . model [ oldName ] ; this . propertyIndex = { ... this . propertyIndex } ; this . modelChange . emit ( this . model ) ; } else if ( oldName !== name ) { this . duplicatedFields . set ( id as string , name ) ; } } /**
     * Adds a new property to the model
     */ addProperty ( dataType ) { const propName = `${ dataType . name } ${ this . propertyCounter }` ; this . propertyCounter ++ ; const schema = JSON . parse ( JSON . stringify ( dataType . schema ) ) ; this . model [ propName ] = createValueForSchema ( dataType . schema as JSONEditorSchema ) ; schema . nameEditable = ! this . schemaBuilderMode ; schema . propertyName = propName ; schema . id = this . propertyId ++ ; this . propertyIndex [ schema . id ] = schema ; this . propertyIndex = { ... this . propertyIndex } ; this . modelChange . emit ( this . model ) ; this . updateIcons ( ) ; } /**
     * Adds a new property as defined in the schema
     */ addSchemaProperty ( propName ) { if ( this . model [ propName ] !== undefined ) { return ; } const schema = JSON . parse ( JSON . stringify ( this . schema . properties [ propName ] ) ) ; if ( ! schema . type ) { schema . type = 'object' ; } const value = createValueForSchema ( schema ) ; this . model [ propName ] = value ; schema . nameEditable = false ; schema . propertyName = propName ; schema . id = this . propertyId ++ ; this . propertyIndex [ schema . id ] = schema ; this . propertyIndex = { ... this . propertyIndex } ; if ( this . initialized ) { this . modelChange . emit ( this . model ) ; } this . updateIcons ( ) ; } /**
     * Adds a new patternProperty as defined in the schema
     */ addSchemaPatternProperty ( propName ) { const newPropName = `new ${ this . schema . patternProperties [ propName ] . title } ${ this . propertyCounter }` ; this . propertyCounter ++ ; const schema = JSON . parse ( JSON . stringify ( this . schema . patternProperties [ propName ] ) ) ; schema . isPatternProperty = true ; if ( ! schema . type ) { schema . type = 'object' ; } const value = createValueForSchema ( schema ) ; this . model [ newPropName ] = value ; schema . nameEditable = true ; schema . propertyName = newPropName ; schema . id = this . propertyId ++ ; this . propertyIndex [ schema . id ] = schema ; this . propertyIndex = { ... this . propertyIndex } ; this . modelChange . emit ( this . model ) ; this . updateIcons ( ) ; } /**
     * Deletes a property
     */ deleteProperty ( propName ) { delete this . model [ propName ] ; for ( const id in this . propertyIndex ) { if ( this . propertyIndex [ id ] . propertyName === propName ) { delete this . propertyIndex [ id ] ; break ; } } this . model = { ... this . model } ; this . propertyIndex = { ... this . propertyIndex } ; this . modelChange . emit ( this . model ) ; } /**
     * Returns the absolute tree path of the property
     */ getPath ( propName ) { let propSchema ; for ( const id in this . propertyIndex ) { if ( this . propertyIndex [ id ] . propertyName === propName ) { propSchema = this . propertyIndex [ id ] ; break ; } } if ( propSchema . isPatternProperty ) { return `['${ propName }']` ; } return `.${ propName }` ; } /**
     * Updates the required cache
     */ updateRequiredCache ( ) { this . requiredCache = { } ; if ( this . schema && this . schema . required ) { for ( const prop of this . schema . required ) { this . requiredCache [ prop ] = true ; } } } /**
     * Creates an index out of all the properties in the model
     */ indexProperties ( ) { const props = this . schemaBuilderMode ? this . schemaRef . properties : this . model ; for ( const prop in props ) { if ( this . isIndexed ( prop ) ) { continue ; } let schema ; if ( this . schema . properties && this . schema . properties [ prop ] ) { schema = JSON . parse ( JSON . stringify ( this . schema . properties [ prop ] ) ) ; } else { let matchesPattern = false ; if ( this . schema . patternProperties ) { for ( const pattern in this . schema . patternProperties ) { const patternRegex = new RegExp ( pattern ) ; if ( patternRegex . test ( prop ) ) { schema = JSON . parse ( JSON . stringify ( this . schema . patternProperties [ pattern ] ) ) ; matchesPattern = true ; } } } if ( ! matchesPattern ) { schema = { ... inferType ( this . model [ prop ] , this . typeCheckOverrides ) } ; } } schema . id = this . propertyId ++ ; schema . propertyName = prop ; this . propertyIndex [ schema . id ] = schema ; this . propertyIndex = { ... this . propertyIndex } ; } for ( const id in this . propertyIndex ) { const schema = this . propertyIndex [ id ] ; if ( this . model [ schema . propertyName ] === undefined ) { delete this . propertyIndex [ id ] ; } else { const model = this . model [ schema . propertyName ] ; const { type } = inferType ( model ) ; if ( schema . type !== type ) { this . propertyIndex [ schema . id ] . type = type ; } } } this . propertyIndex = JSON . parse ( JSON . stringify ( this . propertyIndex ) ) ; this . cdr . markForCheck ( ) ; } isIndexed ( propertyName ) { return Object . values ( this . propertyIndex ) . findIndex ( ( s ) => s . propertyName === propertyName ) !== - 1 ; } /**
     * Inits the required properties on the model
     */ addRequiredProperties ( ) { if ( this . schema && this . schema . properties ) { for ( const propName in this . schema . properties ) { if ( this . model [ propName ] !== undefined ) { continue ; } if ( this . requiredCache [ propName ] || this . schemaBuilderMode ) { this . addSchemaProperty ( propName ) ; } } } } param property param type changePropertyType ( property , type ) { const dataType = this . dataTypeMap [ type ] ; if ( dataType ) { delete property . format ; property . type = dataType . schema . type ; if ( dataType . schema . format ) { property . format = dataType . schema . format ; } property . $meta . currentType = getCurrentType ( property ) ; this . schema . properties [ property . propertyName ] = { ... property } ; } const value = createValueForSchema ( property ) ; this . model [ property . propertyName ] = value ; this . modelChange . emit ( this . model ) ; this . updateIcons ( ) ; } param index param value trackBy ( _ , value ) { return value . id ; } /**
     * Updates the icons in the schemas
     */ protected updateIcons ( ) { for ( const id in this . propertyIndex ) { const schema = this . propertyIndex [ id ] ; if ( ! schema . $meta ) { schema . $meta = { } ; } schema . $meta . icon = getIcon ( schema ) ; } } }