diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e353730..e7ed7ff0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.6.6-forked (2022-08-26) + +- Customize creating of form property for FHIR Extension schema. + # 2.6.6 (2021-08-12) - Fix HTML IDs in elements nested in arrays diff --git a/package-lock.json b/package-lock.json index 8efbd701..267148c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12556,9 +12556,9 @@ "dev": true }, "tar": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.4.tgz", - "integrity": "sha512-kcPWrO8S5ABjuZ/v1xQHP8xCEvj1dQ1d9iAb6Qs4jLYzaAIYWwST2IQpz7Ud8VNYRI+fGhFjrnzRKmRggKWg3g==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dev": true, "requires": { "chownr": "^2.0.0", @@ -13156,9 +13156,9 @@ } }, "url-parse": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", - "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", + "integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", "dev": true, "requires": { "querystringify": "^2.1.1", diff --git a/projects/schema-form/package.json b/projects/schema-form/package.json index 68aeefa3..481ef7ac 100644 --- a/projects/schema-form/package.json +++ b/projects/schema-form/package.json @@ -1,9 +1,9 @@ { - "name": "ngx-schema-form", - "version": "2.6.6", + "name": "@lhncbc/ngx-schema-form", + "version": "2.6.6-forked", "repository": { "type": "git", - "url": "git+https://github.com/guillotinaweb/ngx-schema-form" + "url": "git+https://github.com/lhncbc/ngx-schema-form" }, "keywords": [ "angular", @@ -13,7 +13,7 @@ ], "license": "MIT", "bugs": { - "url": "https://github.com/guillotinaweb/ngx-schema-form/issues" + "url": "https://github.com/lhncbc/ngx-schema-form/issues" }, "contributors": [ "Frank Bessou ", @@ -21,7 +21,8 @@ "Simon Bats ", "Gabor Pankotay ", "Juan Manuel Verges", - "Daniele Pecora " + "Daniele Pecora ", + "Ajay Kanduru " ], "dependencies": { "tslib": "^2.0.0" diff --git a/projects/schema-form/src/lib/form.component.ts b/projects/schema-form/src/lib/form.component.ts index 7039466d..9e4368d8 100644 --- a/projects/schema-form/src/lib/form.component.ts +++ b/projects/schema-form/src/lib/form.component.ts @@ -69,16 +69,21 @@ export class FormComponent implements OnChanges, ControlValueAccessor { @Input() bindings: { [path: string]: Binding } = {}; + // tslint:disable-next-line:no-output-on-prefix @Output() onChange = new EventEmitter<{ value: any }>(); @Output() modelChange = new EventEmitter(); @Output() isValid = new EventEmitter(); + // tslint:disable-next-line:no-output-on-prefix @Output() onErrorChange = new EventEmitter<{ value: any[] }>(); + // tslint:disable-next-line:no-output-on-prefix @Output() onErrorsChange = new EventEmitter<{value: any}>(); + @Output() modelReset = new EventEmitter<{value: any}>(); + rootProperty: FormProperty = null; private onChangeCallback: any; @@ -156,6 +161,7 @@ export class FormComponent implements OnChanges, ControlValueAccessor { if (this.schema && (changes.model || changes.schema )) { this.rootProperty.reset(this.model, false); this.cdr.detectChanges(); + this.modelReset.next({value: this.rootProperty.value}); } } diff --git a/projects/schema-form/src/lib/model/arrayproperty.ts b/projects/schema-form/src/lib/model/arrayproperty.ts index 77ba1b38..c25d5188 100644 --- a/projects/schema-form/src/lib/model/arrayproperty.ts +++ b/projects/schema-form/src/lib/model/arrayproperty.ts @@ -21,26 +21,25 @@ export class ArrayProperty extends PropertyGroup { } addItem(value: any = null): FormProperty { - let newProperty = this.addProperty(); - newProperty.reset(value, false); - return newProperty; + return this.addProperty(value); } - private addProperty() { - let itemSchema = this.schema.items - if (Array.isArray(this.schema.items)) { - const itemSchemas = this.schema.items as object[] + private addProperty(value) { + let itemSchema = this.schema.items; + if (Array.isArray(itemSchema)) { + const itemSchemas = itemSchema as object[]; if (itemSchemas.length > (this.properties).length) { - itemSchema = itemSchema[(this.properties).length] + itemSchema = itemSchema[(this.properties).length]; } else if (this.schema.additionalItems) { - itemSchema = this.schema.additionalItems + itemSchema = this.schema.additionalItems; } else { - // souldn't add new items since schema is undefined for the item at its position - return null + // shouldn't add new items since schema is undefined for the item at its position + return null; } } - let newProperty = this.formPropertyFactory.createProperty(itemSchema, this); + const newProperty = this.formPropertyFactory.createProperty(itemSchema, this, null, value); (this.properties).push(newProperty); + newProperty.reset(value, false); return newProperty; } @@ -86,9 +85,9 @@ export class ArrayProperty extends PropertyGroup { private resetProperties(value: any) { - for (let idx in value) { + for (const idx in value) { if (value.hasOwnProperty(idx)) { - let property = this.addProperty(); + const property = this.addProperty(value[idx]); property.reset(value[idx], true); } } diff --git a/projects/schema-form/src/lib/model/formpropertyfactory.ts b/projects/schema-form/src/lib/model/formpropertyfactory.ts index 23e2ac56..90185dd2 100644 --- a/projects/schema-form/src/lib/model/formpropertyfactory.ts +++ b/projects/schema-form/src/lib/model/formpropertyfactory.ts @@ -16,7 +16,7 @@ export class FormPropertyFactory { private logger: LogService) { } - createProperty(schema: ISchema, parent: PropertyGroup = null, propertyId?: string): FormProperty { + createProperty(schema: ISchema, parent: PropertyGroup = null, propertyId: string = null, value?: any): FormProperty { let newProperty = null; let path = ''; let _canonicalPath = ''; @@ -33,7 +33,7 @@ export class FormPropertyFactory { path += '*'; _canonicalPath += '*'; } else { - throw 'Instanciation of a FormProperty with an unknown parent type: ' + parent.type; + throw new Error('Instanciation of a FormProperty with an unknown parent type: ' + parent.type); } _canonicalPath = (parent._canonicalPath || parent.path) + _canonicalPath; } else { @@ -43,7 +43,7 @@ export class FormPropertyFactory { if (schema.$ref) { const refSchema = this.schemaValidatorFactory.getSchema(parent.root.schema, schema.$ref); - newProperty = this.createProperty(refSchema, parent, path); + newProperty = this.createProperty(refSchema, parent, path, value); } else { const type: FieldType = this.isUnionType(schema.type) && this.isValidNullableUnionType(schema.type as TNullableFieldType) @@ -54,7 +54,8 @@ export class FormPropertyFactory { if (PROPERTY_TYPE_MAPPING[type]) { if (type === 'object' || type === 'array') { newProperty = PROPERTY_TYPE_MAPPING[type]( - this.schemaValidatorFactory, this.validatorRegistry, this.expressionCompilerFactory, schema, parent, path, this, this.logger); + this.schemaValidatorFactory, this.validatorRegistry, this.expressionCompilerFactory, schema, parent, path, this, this.logger, + value); } else { newProperty = PROPERTY_TYPE_MAPPING[type]( this.schemaValidatorFactory, this.validatorRegistry, this.expressionCompilerFactory, schema, parent, path, this.logger); diff --git a/projects/schema-form/src/lib/model/objectproperty.spec.ts b/projects/schema-form/src/lib/model/objectproperty.spec.ts index e698d40a..a26d8d7a 100644 --- a/projects/schema-form/src/lib/model/objectproperty.spec.ts +++ b/projects/schema-form/src/lib/model/objectproperty.spec.ts @@ -13,13 +13,14 @@ import { DefaultLogService, LogLevel } from '../log.service'; describe('ObjectProperty', () => { - let A_VALIDATOR_REGISTRY = new ValidatorRegistry(); - let A_SCHEMA_VALIDATOR_FACTORY = new ZSchemaValidatorFactory(); - let A_PROPERTY_BINDING_REGISTRY=new PropertyBindingRegistry(); - let A_EXPRESSION_COMPILER_FACTORY = new JEXLExpressionCompilerFactory(); - let A_LOGGER = new DefaultLogService(LogLevel.off); - let A_FORM_PROPERTY_FACTORY = new FormPropertyFactory(A_SCHEMA_VALIDATOR_FACTORY, A_VALIDATOR_REGISTRY, A_PROPERTY_BINDING_REGISTRY, A_EXPRESSION_COMPILER_FACTORY, A_LOGGER); - + const A_VALIDATOR_REGISTRY = new ValidatorRegistry(); + const A_SCHEMA_VALIDATOR_FACTORY = new ZSchemaValidatorFactory(); + const A_PROPERTY_BINDING_REGISTRY = new PropertyBindingRegistry(); + const A_EXPRESSION_COMPILER_FACTORY = new JEXLExpressionCompilerFactory(); + const A_LOGGER = new DefaultLogService(LogLevel.off); + const A_FORM_PROPERTY_FACTORY = new FormPropertyFactory(A_SCHEMA_VALIDATOR_FACTORY, A_VALIDATOR_REGISTRY, A_PROPERTY_BINDING_REGISTRY, + A_EXPRESSION_COMPILER_FACTORY, A_LOGGER); + const THE_OBJECT_SCHEMA: ISchema = { type: 'object', @@ -31,6 +32,7 @@ describe('ObjectProperty', () => { }; let objProperty: ObjectProperty; + let value: any = null; beforeEach(() => { @@ -42,18 +44,67 @@ describe('ObjectProperty', () => { THE_OBJECT_SCHEMA, null, '', - A_LOGGER + A_LOGGER, + value ); }); it('should create same properties as in the schema', () => { - for (let propertyId in THE_OBJECT_SCHEMA.properties) { + for (const propertyId in THE_OBJECT_SCHEMA.properties) { + if (THE_OBJECT_SCHEMA.properties.hasOwnProperty(propertyId)) { + const property = objProperty.getProperty(propertyId); + expect(property).toBeDefined(); + } + } + }); + + it('should create same properties as in schema with arbitrary parent path', () => { + value = {FOO: 1}; + objProperty = new ObjectProperty( + A_FORM_PROPERTY_FACTORY, + A_SCHEMA_VALIDATOR_FACTORY, + A_VALIDATOR_REGISTRY, + A_EXPRESSION_COMPILER_FACTORY, + THE_OBJECT_SCHEMA, + null, + '/abc/def/*', + A_LOGGER, + value + ); + for (const propertyId in THE_OBJECT_SCHEMA.properties) { if (THE_OBJECT_SCHEMA.properties.hasOwnProperty(propertyId)) { - let property = objProperty.getProperty(propertyId); + const property = objProperty.getProperty(propertyId); expect(property).toBeDefined(); } } }); + ['extension', 'modifierExtension'].forEach((field) => { + it('Special handling for ' + field + ': should create same properties as in value', () => { + value = {FOO: 1}; + objProperty = new ObjectProperty( + A_FORM_PROPERTY_FACTORY, + A_SCHEMA_VALIDATOR_FACTORY, + A_VALIDATOR_REGISTRY, + A_EXPRESSION_COMPILER_FACTORY, + THE_OBJECT_SCHEMA, + null, + '/abc/' + field + '/*', + A_LOGGER, + value + ); + for (const propertyId in THE_OBJECT_SCHEMA.properties) { + if (THE_OBJECT_SCHEMA.properties.hasOwnProperty(propertyId)) { + const property = objProperty.getProperty(propertyId); + if (value.hasOwnProperty(propertyId)) { + expect(property).toBeDefined(); + } else { + expect(property).toBeUndefined(); + } + } + } + }); + }); + }); diff --git a/projects/schema-form/src/lib/model/objectproperty.ts b/projects/schema-form/src/lib/model/objectproperty.ts index 7bdb3f3e..200dc23c 100644 --- a/projects/schema-form/src/lib/model/objectproperty.ts +++ b/projects/schema-form/src/lib/model/objectproperty.ts @@ -18,14 +18,25 @@ export class ObjectProperty extends PropertyGroup { schema: ISchema, parent: PropertyGroup, path: string, - logger: LogService) { + logger: LogService, + value?: any) { super(schemaValidatorFactory, validatorRegistry, expressionCompilerFactory, schema, parent, path, logger); - this.createProperties(); + if (path.match(/\/(extension|modifierExtension)\/\*$/)) { + // Special handling for extension schema. + this.createPropertiesExtension(value); + } else { + this.createProperties(); + } } setValue(value: any, onlySelf: boolean) { for (const propertyId in value) { if (value.hasOwnProperty(propertyId)) { + if (!this.properties[propertyId]) { + const propertySchema = this.schema.properties[propertyId]; + this.properties[propertyId] = this.formPropertyFactory.createProperty(propertySchema, this, propertyId, value[propertyId]); + this.propertiesId.push(propertyId); + } this.properties[propertyId].setValue(value[propertyId], true); } } @@ -40,7 +51,7 @@ export class ObjectProperty extends PropertyGroup { resetProperties(value: any) { for (const propertyId in this.schema.properties) { - if (this.schema.properties.hasOwnProperty(propertyId)) { + if (this.properties[propertyId] && this.schema.properties.hasOwnProperty(propertyId)) { this.properties[propertyId].reset(value[propertyId], true); } } @@ -58,6 +69,29 @@ export class ObjectProperty extends PropertyGroup { } } + /** + * For some FHIR schemas, such as Extension, we need information from the url to limit creating associated value[x] fields. + * Enumerating all possible extension urls is kind of impossible task. The next best thing is to look for them in the value parameter. + * If value is null, create all possible value[x] as a fallback. + * + * @param value - Value of extension + */ + createPropertiesExtension(value: any) { + this.properties = {}; + this.propertiesId = []; + const propList: string[] = value ? Object.keys(value) : this.schema.properties ? Object.keys(this.schema.properties) : []; + for (const propertyId of propList) { + if (this.schema.properties.hasOwnProperty(propertyId)) { + const propertySchema = this.schema.properties[propertyId]; + if (propertySchema) { + this.properties[propertyId] = this.formPropertyFactory.createProperty(propertySchema, this, propertyId, + value ? value[propertyId] : null); + this.propertiesId.push(propertyId); + } + } + } + } + public _hasValue(): boolean { return !!Object.keys(this.value).length; } @@ -98,8 +132,9 @@ PROPERTY_TYPE_MAPPING.object = ( parent: PropertyGroup, path: string, formPropertyFactory: FormPropertyFactory, - logger: LogService + logger: LogService, + value?: any ) => { return new ObjectProperty( - formPropertyFactory, schemaValidatorFactory, validatorRegistry, expressionCompilerFactory, schema, parent, path, logger); + formPropertyFactory, schemaValidatorFactory, validatorRegistry, expressionCompilerFactory, schema, parent, path, logger, value); };