From 3a4929219462c0e6a8027a035b7c687c5e2e930b Mon Sep 17 00:00:00 2001 From: Stephan Wienczny Date: Sun, 15 May 2022 20:33:21 +0200 Subject: [PATCH 01/14] Add additional interfaces for Scoped, Labelable and Labeled ui elements. Make Scopable scope optional and use Scoped with required scope where required. Add type predicates for Scopeable, Scoped, Labelable and Labeled. Duplicate old functions handling Scopeable to have Scopeable and Scoped where required. Update toDataPathSegments to allow nullable schemaPath and return an empty array if this happens. --- packages/core/src/models/uischema.ts | 67 ++++++++++++++++++---------- packages/core/src/util/path.ts | 10 +++-- packages/core/src/util/runtime.ts | 6 +-- 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/packages/core/src/models/uischema.ts b/packages/core/src/models/uischema.ts index b0c3de039..2e6730d60 100644 --- a/packages/core/src/models/uischema.ts +++ b/packages/core/src/models/uischema.ts @@ -25,17 +25,42 @@ import { JsonSchema } from './jsonSchema'; +/** + * Interface for describing an UI schema element that may reference + * a subschema. The value of the scope may be a JSON Pointer or null. + */ +export interface Scopable { + /** + * The scope that determines to which part this element may be bound to. + */ + scope?: string; +} + /** * Interface for describing an UI schema element that is referencing * a subschema. The value of the scope must be a JSON Pointer. */ -export interface Scopable { +export interface Scoped extends Scopable { /** * The scope that determines to which part this element should be bound to. */ scope: string; } +/** + * Interface for describing an UI schema element that may be labeled. + */ +export interface Lableable { + label?: string|T; +} + +/** + * Interface for describing an UI schema element that is labeled. + */ +export interface Labeled extends Lableable { + label: string|T; +} + /** * A rule that may be attached to any UI schema element. */ @@ -87,7 +112,7 @@ export interface Condition { /** * A leaf condition. */ -export interface LeafCondition extends Condition, Scopable { +export interface LeafCondition extends Condition, Scoped { type: 'LEAF'; /** @@ -96,7 +121,7 @@ export interface LeafCondition extends Condition, Scopable { expectedValue: any; } -export interface SchemaBasedCondition extends Condition, Scopable { +export interface SchemaBasedCondition extends Condition, Scoped { schema: JsonSchema; } @@ -170,12 +195,8 @@ export interface HorizontalLayout extends Layout { * A group resembles a vertical layout, but additionally might have a label. * This layout is useful when grouping different elements by a certain criteria. */ -export interface GroupLayout extends Layout { +export interface GroupLayout extends Layout, Lableable { type: 'Group'; - /** - * The label of this group layout. - */ - label?: string; } /** @@ -207,23 +228,15 @@ export interface LabelElement extends UISchemaElement { * A control element. The scope property of the control determines * to which part of the schema the control should be bound. */ -export interface ControlElement extends UISchemaElement, Scopable { +export interface ControlElement extends UISchemaElement, Scopable, Lableable { type: 'Control'; - /** - * An optional label that will be associated with the control - */ - label?: string | boolean | LabelDescription; } /** * The category layout. */ -export interface Category extends Layout { +export interface Category extends Layout, Labeled { type: 'Category'; - /** - * The label associated with this category layout. - */ - label: string; } /** @@ -231,12 +244,8 @@ export interface Category extends Layout { * A child element may either be itself a Categorization or a Category, hence * the categorization element can be used to represent recursive structures like trees. */ -export interface Categorization extends UISchemaElement { +export interface Categorization extends UISchemaElement, Labeled { type: 'Categorization'; - /** - * The label of this categorization. - */ - label: string; /** * The child elements of this categorization which are either of type * {@link Category} or {@link Categorization}. @@ -249,3 +258,15 @@ export const isGroup = (layout: Layout): layout is GroupLayout => export const isLayout = (uischema: UISchemaElement): uischema is Layout => (uischema as Layout).elements !== undefined; + +export const isScopeable = (obj: object): obj is Scopable => + obj !== undefined && obj.hasOwnProperty('scope'); + +export const isScoped = (obj: object): obj is Scoped => + isScopeable(obj) && obj.scope !== null; + +export const isLabelable = (obj: object): obj is Lableable => + obj !== undefined && obj.hasOwnProperty('label'); + +export const isLabeled = (obj: object): obj is Labeled => + isLabelable(obj) && obj.label !== null; diff --git a/packages/core/src/util/path.ts b/packages/core/src/util/path.ts index 401cfd685..eabafe22b 100644 --- a/packages/core/src/util/path.ts +++ b/packages/core/src/util/path.ts @@ -52,10 +52,14 @@ export { compose as composePaths }; * and de-referencing the single segments to obtain a new object. * * - * @param {string} schemaPath the schema path to be converted + * @param {string?} schemaPath the schema path to be converted * @returns {string[]} an array containing only non-schema-specific segments */ -export const toDataPathSegments = (schemaPath: string): string[] => { +export const toDataPathSegments = (schemaPath?: string): string[] => { + if (schemaPath === null) { + return []; + } + const s = schemaPath .replace(/anyOf\/[\d]\//g, '') .replace(/allOf\/[\d]\//g, '') @@ -100,4 +104,4 @@ export const encode = (segment: string) => segment?.replace(/~/g, '~0').replace( /** * Decodes a given JSON Pointer segment to its "normal" representation */ -export const decode = (pointerSegment: string) => pointerSegment?.replace(/~1/g, '/').replace(/~0/, '~'); \ No newline at end of file +export const decode = (pointerSegment: string) => pointerSegment?.replace(/~1/g, '/').replace(/~0/, '~'); diff --git a/packages/core/src/util/runtime.ts b/packages/core/src/util/runtime.ts index d636f4c70..1f381aec0 100644 --- a/packages/core/src/util/runtime.ts +++ b/packages/core/src/util/runtime.ts @@ -27,11 +27,12 @@ import has from 'lodash/has'; import { AndCondition, Condition, + JsonSchema, LeafCondition, OrCondition, RuleEffect, SchemaBasedCondition, - Scopable, + Scoped, UISchemaElement } from '../models'; import { resolveData } from './resolvers'; @@ -39,7 +40,6 @@ import { composeWithUi } from './path'; import Ajv from 'ajv'; import { getAjv } from '../reducers'; import { JsonFormsState } from '../store'; -import { JsonSchema } from '../models/jsonSchema'; const isOrCondition = (condition: Condition): condition is OrCondition => condition.type === 'OR'; @@ -54,7 +54,7 @@ const isSchemaCondition = ( condition: Condition ): condition is SchemaBasedCondition => has(condition, 'schema'); -const getConditionScope = (condition: Scopable, path: string): string => { +const getConditionScope = (condition: Scoped, path: string): string => { return composeWithUi(condition, path); }; From 4fcb30c5c15bc8436e7fd086994ce24d54b261c2 Mon Sep 17 00:00:00 2001 From: Stephan Wienczny Date: Mon, 16 May 2022 12:36:54 +0200 Subject: [PATCH 02/14] Revert changes to Scopable. toDataPathSegments: check for undefined, null and ''. null is not allowed by function definition, but it's included. Remove unnecessary type guards and improve the remaining ones. --- packages/core/src/models/uischema.ts | 27 +++++---------------------- packages/core/src/util/path.ts | 3 ++- packages/core/src/util/runtime.ts | 4 ++-- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/packages/core/src/models/uischema.ts b/packages/core/src/models/uischema.ts index 2e6730d60..fb0c23e62 100644 --- a/packages/core/src/models/uischema.ts +++ b/packages/core/src/models/uischema.ts @@ -25,22 +25,11 @@ import { JsonSchema } from './jsonSchema'; -/** - * Interface for describing an UI schema element that may reference - * a subschema. The value of the scope may be a JSON Pointer or null. - */ -export interface Scopable { - /** - * The scope that determines to which part this element may be bound to. - */ - scope?: string; -} - /** * Interface for describing an UI schema element that is referencing * a subschema. The value of the scope must be a JSON Pointer. */ -export interface Scoped extends Scopable { +export interface Scopable { /** * The scope that determines to which part this element should be bound to. */ @@ -112,7 +101,7 @@ export interface Condition { /** * A leaf condition. */ -export interface LeafCondition extends Condition, Scoped { +export interface LeafCondition extends Condition, Scopable { type: 'LEAF'; /** @@ -121,7 +110,7 @@ export interface LeafCondition extends Condition, Scoped { expectedValue: any; } -export interface SchemaBasedCondition extends Condition, Scoped { +export interface SchemaBasedCondition extends Condition, Scopable { schema: JsonSchema; } @@ -260,13 +249,7 @@ export const isLayout = (uischema: UISchemaElement): uischema is Layout => (uischema as Layout).elements !== undefined; export const isScopeable = (obj: object): obj is Scopable => - obj !== undefined && obj.hasOwnProperty('scope'); - -export const isScoped = (obj: object): obj is Scoped => - isScopeable(obj) && obj.scope !== null; - -export const isLabelable = (obj: object): obj is Lableable => - obj !== undefined && obj.hasOwnProperty('label'); + obj !== undefined && obj.hasOwnProperty('scope') && (obj as Scopable).scope !== undefined; export const isLabeled = (obj: object): obj is Labeled => - isLabelable(obj) && obj.label !== null; + obj !== undefined && obj.hasOwnProperty('label') && (obj as Lableable).label !== undefined; diff --git a/packages/core/src/util/path.ts b/packages/core/src/util/path.ts index eabafe22b..2436f21c0 100644 --- a/packages/core/src/util/path.ts +++ b/packages/core/src/util/path.ts @@ -56,7 +56,8 @@ export { compose as composePaths }; * @returns {string[]} an array containing only non-schema-specific segments */ export const toDataPathSegments = (schemaPath?: string): string[] => { - if (schemaPath === null) { + if (!schemaPath) { + // handle undefined, null and '' return []; } diff --git a/packages/core/src/util/runtime.ts b/packages/core/src/util/runtime.ts index 1f381aec0..d6cee0505 100644 --- a/packages/core/src/util/runtime.ts +++ b/packages/core/src/util/runtime.ts @@ -32,7 +32,7 @@ import { OrCondition, RuleEffect, SchemaBasedCondition, - Scoped, + Scopable, UISchemaElement } from '../models'; import { resolveData } from './resolvers'; @@ -54,7 +54,7 @@ const isSchemaCondition = ( condition: Condition ): condition is SchemaBasedCondition => has(condition, 'schema'); -const getConditionScope = (condition: Scoped, path: string): string => { +const getConditionScope = (condition: Scopable, path: string): string => { return composeWithUi(condition, path); }; From 6dfe94d6f8499a876100e69a54dde855f5f4b4d9 Mon Sep 17 00:00:00 2001 From: Stephan Wienczny Date: Tue, 31 May 2022 13:27:03 +0200 Subject: [PATCH 03/14] Add Scopeable and Scoped Replace Scopeable by Scoped where used Add type guards for Scoped and Labeled Make composeWithUI handle Scoped and Scopeable --- packages/core/src/models/uischema.ts | 29 ++++++++++++++++++++++------ packages/core/src/util/path.ts | 15 +++++++------- packages/core/src/util/util.ts | 16 +++++++-------- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/packages/core/src/models/uischema.ts b/packages/core/src/models/uischema.ts index fb0c23e62..d31d0f387 100644 --- a/packages/core/src/models/uischema.ts +++ b/packages/core/src/models/uischema.ts @@ -27,9 +27,20 @@ import { JsonSchema } from './jsonSchema'; /** * Interface for describing an UI schema element that is referencing - * a subschema. The value of the scope must be a JSON Pointer. + * a subschema. The value of the scope may be a JSON Pointer. */ export interface Scopable { + /** + * The scope that determines to which part this element should be bound to. + */ + scope?: string; +} + +/** + * Interface for describing an UI schema element that is referencing + * a subschema. The value of the scope must be a JSON Pointer. + */ +export interface Scoped extends Scopable { /** * The scope that determines to which part this element should be bound to. */ @@ -101,7 +112,7 @@ export interface Condition { /** * A leaf condition. */ -export interface LeafCondition extends Condition, Scopable { +export interface LeafCondition extends Condition, Scoped { type: 'LEAF'; /** @@ -110,7 +121,7 @@ export interface LeafCondition extends Condition, Scopable { expectedValue: any; } -export interface SchemaBasedCondition extends Condition, Scopable { +export interface SchemaBasedCondition extends Condition, Scoped { schema: JsonSchema; } @@ -217,7 +228,7 @@ export interface LabelElement extends UISchemaElement { * A control element. The scope property of the control determines * to which part of the schema the control should be bound. */ -export interface ControlElement extends UISchemaElement, Scopable, Lableable { +export interface ControlElement extends UISchemaElement, Scoped, Lableable { type: 'Control'; } @@ -249,7 +260,13 @@ export const isLayout = (uischema: UISchemaElement): uischema is Layout => (uischema as Layout).elements !== undefined; export const isScopeable = (obj: object): obj is Scopable => - obj !== undefined && obj.hasOwnProperty('scope') && (obj as Scopable).scope !== undefined; + obj !== undefined && obj.hasOwnProperty('scope'); + +export const isScoped = (obj: object): obj is Scoped => + isScopeable(obj) && obj.scope !== undefined; + +export const isLabelable = (obj: object): obj is Lableable => + obj !== undefined && obj.hasOwnProperty('label'); export const isLabeled = (obj: object): obj is Labeled => - obj !== undefined && obj.hasOwnProperty('label') && (obj as Lableable).label !== undefined; + isLabelable(obj) && obj.label !== undefined; diff --git a/packages/core/src/util/path.ts b/packages/core/src/util/path.ts index 2436f21c0..718f62e64 100644 --- a/packages/core/src/util/path.ts +++ b/packages/core/src/util/path.ts @@ -25,7 +25,7 @@ import isEmpty from 'lodash/isEmpty'; import range from 'lodash/range'; -import { Scopable } from '../models'; +import { isScoped, Scopable } from '../models'; export const compose = (path1: string, path2: string) => { let p1 = path1; @@ -52,15 +52,10 @@ export { compose as composePaths }; * and de-referencing the single segments to obtain a new object. * * - * @param {string?} schemaPath the schema path to be converted + * @param {string} schemaPath the schema path to be converted * @returns {string[]} an array containing only non-schema-specific segments */ -export const toDataPathSegments = (schemaPath?: string): string[] => { - if (!schemaPath) { - // handle undefined, null and '' - return []; - } - +export const toDataPathSegments = (schemaPath: string): string[] => { const s = schemaPath .replace(/anyOf\/[\d]\//g, '') .replace(/allOf\/[\d]\//g, '') @@ -87,6 +82,10 @@ export const toDataPath = (schemaPath: string): string => { }; export const composeWithUi = (scopableUi: Scopable, path: string): string => { + if (!isScoped(scopableUi)) { + return ''; + } + const segments = toDataPathSegments(scopableUi.scope); if (isEmpty(segments) && path === undefined) { diff --git a/packages/core/src/util/util.ts b/packages/core/src/util/util.ts index 1909d1f98..3b445380b 100644 --- a/packages/core/src/util/util.ts +++ b/packages/core/src/util/util.ts @@ -27,7 +27,7 @@ import isEmpty from 'lodash/isEmpty'; import isArray from 'lodash/isArray'; import includes from 'lodash/includes'; import find from 'lodash/find'; -import { JsonSchema, Scopable, UISchemaElement } from '..'; +import { JsonSchema, Scoped, UISchemaElement } from '..'; import { resolveData, resolveSchema } from './resolvers'; import { composePaths, toDataPathSegments } from './path'; import { isEnabled, isVisible } from './runtime'; @@ -56,8 +56,8 @@ export const hasType = (jsonSchema: JsonSchema, expected: string): boolean => { }; /** -* Derives the type of the jsonSchema element -*/ + * Derives the type of the jsonSchema element + */ export const deriveTypes = (jsonSchema: JsonSchema): string[] => { if (isEmpty(jsonSchema)) { return []; @@ -93,8 +93,8 @@ export const deriveTypes = (jsonSchema: JsonSchema): string[] => { }; /** -* Convenience wrapper around resolveData and resolveSchema. -*/ + * Convenience wrapper around resolveData and resolveSchema. + */ export const Resolve: { schema( schema: JsonSchema, @@ -108,18 +108,18 @@ export const Resolve: { }; // Paths -- -const fromScopable = (scopable: Scopable) => +const fromScoped = (scopable: Scoped): string => toDataPathSegments(scopable.scope).join('.'); export const Paths = { compose: composePaths, - fromScopable + fromScoped }; // Runtime -- export const Runtime = { isEnabled(uischema: UISchemaElement, data: any, ajv: Ajv): boolean { - return isEnabled(uischema, data,undefined, ajv); + return isEnabled(uischema, data, undefined, ajv); }, isVisible(uischema: UISchemaElement, data: any, ajv: Ajv): boolean { return isVisible(uischema, data, undefined, ajv); From 731ca7bc82df0cdf81a3c6f90f135b9c85755700 Mon Sep 17 00:00:00 2001 From: Stephan Wienczny Date: Thu, 2 Jun 2022 11:17:16 +0200 Subject: [PATCH 04/14] Update packages/core/src/models/uischema.ts use typeof obj.scope to check for Scoped Co-authored-by: Stefan Dirix --- packages/core/src/models/uischema.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/models/uischema.ts b/packages/core/src/models/uischema.ts index d31d0f387..0012dc544 100644 --- a/packages/core/src/models/uischema.ts +++ b/packages/core/src/models/uischema.ts @@ -262,8 +262,8 @@ export const isLayout = (uischema: UISchemaElement): uischema is Layout => export const isScopeable = (obj: object): obj is Scopable => obj !== undefined && obj.hasOwnProperty('scope'); -export const isScoped = (obj: object): obj is Scoped => - isScopeable(obj) && obj.scope !== undefined; +export const isScoped = (obj: unknown): obj is Scoped => + isScopeable(obj) && typeof obj.scope === 'string'; export const isLabelable = (obj: object): obj is Lableable => obj !== undefined && obj.hasOwnProperty('label'); From 0277312614bb9165671721a5f69e5d6a76b2c23b Mon Sep 17 00:00:00 2001 From: Stephan Wienczny Date: Sun, 15 May 2022 20:33:21 +0200 Subject: [PATCH 05/14] Add additional interfaces for Scoped, Labelable and Labeled ui elements. Make Scopable scope optional and use Scoped with required scope where required. Add type predicates for Scopeable, Scoped, Labelable and Labeled. Duplicate old functions handling Scopeable to have Scopeable and Scoped where required. Update toDataPathSegments to allow nullable schemaPath and return an empty array if this happens. --- packages/core/src/models/uischema.ts | 67 ++++++++++++++++++---------- packages/core/src/util/path.ts | 10 +++-- packages/core/src/util/runtime.ts | 6 +-- 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/packages/core/src/models/uischema.ts b/packages/core/src/models/uischema.ts index b0c3de039..2e6730d60 100644 --- a/packages/core/src/models/uischema.ts +++ b/packages/core/src/models/uischema.ts @@ -25,17 +25,42 @@ import { JsonSchema } from './jsonSchema'; +/** + * Interface for describing an UI schema element that may reference + * a subschema. The value of the scope may be a JSON Pointer or null. + */ +export interface Scopable { + /** + * The scope that determines to which part this element may be bound to. + */ + scope?: string; +} + /** * Interface for describing an UI schema element that is referencing * a subschema. The value of the scope must be a JSON Pointer. */ -export interface Scopable { +export interface Scoped extends Scopable { /** * The scope that determines to which part this element should be bound to. */ scope: string; } +/** + * Interface for describing an UI schema element that may be labeled. + */ +export interface Lableable { + label?: string|T; +} + +/** + * Interface for describing an UI schema element that is labeled. + */ +export interface Labeled extends Lableable { + label: string|T; +} + /** * A rule that may be attached to any UI schema element. */ @@ -87,7 +112,7 @@ export interface Condition { /** * A leaf condition. */ -export interface LeafCondition extends Condition, Scopable { +export interface LeafCondition extends Condition, Scoped { type: 'LEAF'; /** @@ -96,7 +121,7 @@ export interface LeafCondition extends Condition, Scopable { expectedValue: any; } -export interface SchemaBasedCondition extends Condition, Scopable { +export interface SchemaBasedCondition extends Condition, Scoped { schema: JsonSchema; } @@ -170,12 +195,8 @@ export interface HorizontalLayout extends Layout { * A group resembles a vertical layout, but additionally might have a label. * This layout is useful when grouping different elements by a certain criteria. */ -export interface GroupLayout extends Layout { +export interface GroupLayout extends Layout, Lableable { type: 'Group'; - /** - * The label of this group layout. - */ - label?: string; } /** @@ -207,23 +228,15 @@ export interface LabelElement extends UISchemaElement { * A control element. The scope property of the control determines * to which part of the schema the control should be bound. */ -export interface ControlElement extends UISchemaElement, Scopable { +export interface ControlElement extends UISchemaElement, Scopable, Lableable { type: 'Control'; - /** - * An optional label that will be associated with the control - */ - label?: string | boolean | LabelDescription; } /** * The category layout. */ -export interface Category extends Layout { +export interface Category extends Layout, Labeled { type: 'Category'; - /** - * The label associated with this category layout. - */ - label: string; } /** @@ -231,12 +244,8 @@ export interface Category extends Layout { * A child element may either be itself a Categorization or a Category, hence * the categorization element can be used to represent recursive structures like trees. */ -export interface Categorization extends UISchemaElement { +export interface Categorization extends UISchemaElement, Labeled { type: 'Categorization'; - /** - * The label of this categorization. - */ - label: string; /** * The child elements of this categorization which are either of type * {@link Category} or {@link Categorization}. @@ -249,3 +258,15 @@ export const isGroup = (layout: Layout): layout is GroupLayout => export const isLayout = (uischema: UISchemaElement): uischema is Layout => (uischema as Layout).elements !== undefined; + +export const isScopeable = (obj: object): obj is Scopable => + obj !== undefined && obj.hasOwnProperty('scope'); + +export const isScoped = (obj: object): obj is Scoped => + isScopeable(obj) && obj.scope !== null; + +export const isLabelable = (obj: object): obj is Lableable => + obj !== undefined && obj.hasOwnProperty('label'); + +export const isLabeled = (obj: object): obj is Labeled => + isLabelable(obj) && obj.label !== null; diff --git a/packages/core/src/util/path.ts b/packages/core/src/util/path.ts index b62ed6afc..ba5f85c56 100644 --- a/packages/core/src/util/path.ts +++ b/packages/core/src/util/path.ts @@ -52,10 +52,14 @@ export { compose as composePaths }; * and de-referencing the single segments to obtain a new object. * * - * @param {string} schemaPath the schema path to be converted + * @param {string?} schemaPath the schema path to be converted * @returns {string[]} an array containing only non-schema-specific segments */ -export const toDataPathSegments = (schemaPath: string): string[] => { +export const toDataPathSegments = (schemaPath?: string): string[] => { + if (schemaPath === null) { + return []; + } + const s = schemaPath .replace(/(anyOf|allOf|oneOf)\/[\d]\//g, '') .replace(/(then|else)\//g, ''); @@ -99,4 +103,4 @@ export const encode = (segment: string) => segment?.replace(/~/g, '~0').replace( /** * Decodes a given JSON Pointer segment to its "normal" representation */ -export const decode = (pointerSegment: string) => pointerSegment?.replace(/~1/g, '/').replace(/~0/, '~'); \ No newline at end of file +export const decode = (pointerSegment: string) => pointerSegment?.replace(/~1/g, '/').replace(/~0/, '~'); diff --git a/packages/core/src/util/runtime.ts b/packages/core/src/util/runtime.ts index d636f4c70..1f381aec0 100644 --- a/packages/core/src/util/runtime.ts +++ b/packages/core/src/util/runtime.ts @@ -27,11 +27,12 @@ import has from 'lodash/has'; import { AndCondition, Condition, + JsonSchema, LeafCondition, OrCondition, RuleEffect, SchemaBasedCondition, - Scopable, + Scoped, UISchemaElement } from '../models'; import { resolveData } from './resolvers'; @@ -39,7 +40,6 @@ import { composeWithUi } from './path'; import Ajv from 'ajv'; import { getAjv } from '../reducers'; import { JsonFormsState } from '../store'; -import { JsonSchema } from '../models/jsonSchema'; const isOrCondition = (condition: Condition): condition is OrCondition => condition.type === 'OR'; @@ -54,7 +54,7 @@ const isSchemaCondition = ( condition: Condition ): condition is SchemaBasedCondition => has(condition, 'schema'); -const getConditionScope = (condition: Scopable, path: string): string => { +const getConditionScope = (condition: Scoped, path: string): string => { return composeWithUi(condition, path); }; From 80971a8e9ffe8ebc4a31d1882130214835ad2a7e Mon Sep 17 00:00:00 2001 From: Stephan Wienczny Date: Mon, 16 May 2022 12:36:54 +0200 Subject: [PATCH 06/14] Revert changes to Scopable. toDataPathSegments: check for undefined, null and ''. null is not allowed by function definition, but it's included. Remove unnecessary type guards and improve the remaining ones. --- packages/core/src/models/uischema.ts | 27 +++++---------------------- packages/core/src/util/path.ts | 3 ++- packages/core/src/util/runtime.ts | 4 ++-- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/packages/core/src/models/uischema.ts b/packages/core/src/models/uischema.ts index 2e6730d60..fb0c23e62 100644 --- a/packages/core/src/models/uischema.ts +++ b/packages/core/src/models/uischema.ts @@ -25,22 +25,11 @@ import { JsonSchema } from './jsonSchema'; -/** - * Interface for describing an UI schema element that may reference - * a subschema. The value of the scope may be a JSON Pointer or null. - */ -export interface Scopable { - /** - * The scope that determines to which part this element may be bound to. - */ - scope?: string; -} - /** * Interface for describing an UI schema element that is referencing * a subschema. The value of the scope must be a JSON Pointer. */ -export interface Scoped extends Scopable { +export interface Scopable { /** * The scope that determines to which part this element should be bound to. */ @@ -112,7 +101,7 @@ export interface Condition { /** * A leaf condition. */ -export interface LeafCondition extends Condition, Scoped { +export interface LeafCondition extends Condition, Scopable { type: 'LEAF'; /** @@ -121,7 +110,7 @@ export interface LeafCondition extends Condition, Scoped { expectedValue: any; } -export interface SchemaBasedCondition extends Condition, Scoped { +export interface SchemaBasedCondition extends Condition, Scopable { schema: JsonSchema; } @@ -260,13 +249,7 @@ export const isLayout = (uischema: UISchemaElement): uischema is Layout => (uischema as Layout).elements !== undefined; export const isScopeable = (obj: object): obj is Scopable => - obj !== undefined && obj.hasOwnProperty('scope'); - -export const isScoped = (obj: object): obj is Scoped => - isScopeable(obj) && obj.scope !== null; - -export const isLabelable = (obj: object): obj is Lableable => - obj !== undefined && obj.hasOwnProperty('label'); + obj !== undefined && obj.hasOwnProperty('scope') && (obj as Scopable).scope !== undefined; export const isLabeled = (obj: object): obj is Labeled => - isLabelable(obj) && obj.label !== null; + obj !== undefined && obj.hasOwnProperty('label') && (obj as Lableable).label !== undefined; diff --git a/packages/core/src/util/path.ts b/packages/core/src/util/path.ts index ba5f85c56..c38628ab2 100644 --- a/packages/core/src/util/path.ts +++ b/packages/core/src/util/path.ts @@ -56,7 +56,8 @@ export { compose as composePaths }; * @returns {string[]} an array containing only non-schema-specific segments */ export const toDataPathSegments = (schemaPath?: string): string[] => { - if (schemaPath === null) { + if (!schemaPath) { + // handle undefined, null and '' return []; } diff --git a/packages/core/src/util/runtime.ts b/packages/core/src/util/runtime.ts index 1f381aec0..d6cee0505 100644 --- a/packages/core/src/util/runtime.ts +++ b/packages/core/src/util/runtime.ts @@ -32,7 +32,7 @@ import { OrCondition, RuleEffect, SchemaBasedCondition, - Scoped, + Scopable, UISchemaElement } from '../models'; import { resolveData } from './resolvers'; @@ -54,7 +54,7 @@ const isSchemaCondition = ( condition: Condition ): condition is SchemaBasedCondition => has(condition, 'schema'); -const getConditionScope = (condition: Scoped, path: string): string => { +const getConditionScope = (condition: Scopable, path: string): string => { return composeWithUi(condition, path); }; From 8b7861f345b7c62bcfa7af325ecfc5aa01b6d500 Mon Sep 17 00:00:00 2001 From: Stephan Wienczny Date: Tue, 31 May 2022 13:27:03 +0200 Subject: [PATCH 07/14] Add Scopeable and Scoped Replace Scopeable by Scoped where used Add type guards for Scoped and Labeled Make composeWithUI handle Scoped and Scopeable --- packages/core/src/models/uischema.ts | 29 ++++++++++++++++++++++------ packages/core/src/util/path.ts | 15 +++++++------- packages/core/src/util/util.ts | 16 +++++++-------- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/packages/core/src/models/uischema.ts b/packages/core/src/models/uischema.ts index fb0c23e62..d31d0f387 100644 --- a/packages/core/src/models/uischema.ts +++ b/packages/core/src/models/uischema.ts @@ -27,9 +27,20 @@ import { JsonSchema } from './jsonSchema'; /** * Interface for describing an UI schema element that is referencing - * a subschema. The value of the scope must be a JSON Pointer. + * a subschema. The value of the scope may be a JSON Pointer. */ export interface Scopable { + /** + * The scope that determines to which part this element should be bound to. + */ + scope?: string; +} + +/** + * Interface for describing an UI schema element that is referencing + * a subschema. The value of the scope must be a JSON Pointer. + */ +export interface Scoped extends Scopable { /** * The scope that determines to which part this element should be bound to. */ @@ -101,7 +112,7 @@ export interface Condition { /** * A leaf condition. */ -export interface LeafCondition extends Condition, Scopable { +export interface LeafCondition extends Condition, Scoped { type: 'LEAF'; /** @@ -110,7 +121,7 @@ export interface LeafCondition extends Condition, Scopable { expectedValue: any; } -export interface SchemaBasedCondition extends Condition, Scopable { +export interface SchemaBasedCondition extends Condition, Scoped { schema: JsonSchema; } @@ -217,7 +228,7 @@ export interface LabelElement extends UISchemaElement { * A control element. The scope property of the control determines * to which part of the schema the control should be bound. */ -export interface ControlElement extends UISchemaElement, Scopable, Lableable { +export interface ControlElement extends UISchemaElement, Scoped, Lableable { type: 'Control'; } @@ -249,7 +260,13 @@ export const isLayout = (uischema: UISchemaElement): uischema is Layout => (uischema as Layout).elements !== undefined; export const isScopeable = (obj: object): obj is Scopable => - obj !== undefined && obj.hasOwnProperty('scope') && (obj as Scopable).scope !== undefined; + obj !== undefined && obj.hasOwnProperty('scope'); + +export const isScoped = (obj: object): obj is Scoped => + isScopeable(obj) && obj.scope !== undefined; + +export const isLabelable = (obj: object): obj is Lableable => + obj !== undefined && obj.hasOwnProperty('label'); export const isLabeled = (obj: object): obj is Labeled => - obj !== undefined && obj.hasOwnProperty('label') && (obj as Lableable).label !== undefined; + isLabelable(obj) && obj.label !== undefined; diff --git a/packages/core/src/util/path.ts b/packages/core/src/util/path.ts index c38628ab2..df4449002 100644 --- a/packages/core/src/util/path.ts +++ b/packages/core/src/util/path.ts @@ -25,7 +25,7 @@ import isEmpty from 'lodash/isEmpty'; import range from 'lodash/range'; -import { Scopable } from '../models'; +import { isScoped, Scopable } from '../models'; export const compose = (path1: string, path2: string) => { let p1 = path1; @@ -52,15 +52,10 @@ export { compose as composePaths }; * and de-referencing the single segments to obtain a new object. * * - * @param {string?} schemaPath the schema path to be converted + * @param {string} schemaPath the schema path to be converted * @returns {string[]} an array containing only non-schema-specific segments */ -export const toDataPathSegments = (schemaPath?: string): string[] => { - if (!schemaPath) { - // handle undefined, null and '' - return []; - } - +export const toDataPathSegments = (schemaPath: string): string[] => { const s = schemaPath .replace(/(anyOf|allOf|oneOf)\/[\d]\//g, '') .replace(/(then|else)\//g, ''); @@ -86,6 +81,10 @@ export const toDataPath = (schemaPath: string): string => { }; export const composeWithUi = (scopableUi: Scopable, path: string): string => { + if (!isScoped(scopableUi)) { + return ''; + } + const segments = toDataPathSegments(scopableUi.scope); if (isEmpty(segments) && path === undefined) { diff --git a/packages/core/src/util/util.ts b/packages/core/src/util/util.ts index 1909d1f98..3b445380b 100644 --- a/packages/core/src/util/util.ts +++ b/packages/core/src/util/util.ts @@ -27,7 +27,7 @@ import isEmpty from 'lodash/isEmpty'; import isArray from 'lodash/isArray'; import includes from 'lodash/includes'; import find from 'lodash/find'; -import { JsonSchema, Scopable, UISchemaElement } from '..'; +import { JsonSchema, Scoped, UISchemaElement } from '..'; import { resolveData, resolveSchema } from './resolvers'; import { composePaths, toDataPathSegments } from './path'; import { isEnabled, isVisible } from './runtime'; @@ -56,8 +56,8 @@ export const hasType = (jsonSchema: JsonSchema, expected: string): boolean => { }; /** -* Derives the type of the jsonSchema element -*/ + * Derives the type of the jsonSchema element + */ export const deriveTypes = (jsonSchema: JsonSchema): string[] => { if (isEmpty(jsonSchema)) { return []; @@ -93,8 +93,8 @@ export const deriveTypes = (jsonSchema: JsonSchema): string[] => { }; /** -* Convenience wrapper around resolveData and resolveSchema. -*/ + * Convenience wrapper around resolveData and resolveSchema. + */ export const Resolve: { schema( schema: JsonSchema, @@ -108,18 +108,18 @@ export const Resolve: { }; // Paths -- -const fromScopable = (scopable: Scopable) => +const fromScoped = (scopable: Scoped): string => toDataPathSegments(scopable.scope).join('.'); export const Paths = { compose: composePaths, - fromScopable + fromScoped }; // Runtime -- export const Runtime = { isEnabled(uischema: UISchemaElement, data: any, ajv: Ajv): boolean { - return isEnabled(uischema, data,undefined, ajv); + return isEnabled(uischema, data, undefined, ajv); }, isVisible(uischema: UISchemaElement, data: any, ajv: Ajv): boolean { return isVisible(uischema, data, undefined, ajv); From 9576d5cc4a2cf05b1e69c74d6a782d37517399f1 Mon Sep 17 00:00:00 2001 From: Stephan Wienczny Date: Thu, 2 Jun 2022 11:17:16 +0200 Subject: [PATCH 08/14] Update packages/core/src/models/uischema.ts use typeof obj.scope to check for Scoped Co-authored-by: Stefan Dirix --- packages/core/src/models/uischema.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/models/uischema.ts b/packages/core/src/models/uischema.ts index d31d0f387..0012dc544 100644 --- a/packages/core/src/models/uischema.ts +++ b/packages/core/src/models/uischema.ts @@ -262,8 +262,8 @@ export const isLayout = (uischema: UISchemaElement): uischema is Layout => export const isScopeable = (obj: object): obj is Scopable => obj !== undefined && obj.hasOwnProperty('scope'); -export const isScoped = (obj: object): obj is Scoped => - isScopeable(obj) && obj.scope !== undefined; +export const isScoped = (obj: unknown): obj is Scoped => + isScopeable(obj) && typeof obj.scope === 'string'; export const isLabelable = (obj: object): obj is Lableable => obj !== undefined && obj.hasOwnProperty('label'); From 5cffbd4fa4e1dfaccba0b4480dfe20ec209376c4 Mon Sep 17 00:00:00 2001 From: Stephan Wienczny Date: Thu, 2 Jun 2022 12:57:33 +0200 Subject: [PATCH 09/14] composeWithUi return path ?? '' if scopableUi is not Scopable or segments is empty --- packages/core/src/util/path.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/util/path.ts b/packages/core/src/util/path.ts index df4449002..e5920cd54 100644 --- a/packages/core/src/util/path.ts +++ b/packages/core/src/util/path.ts @@ -82,16 +82,16 @@ export const toDataPath = (schemaPath: string): string => { export const composeWithUi = (scopableUi: Scopable, path: string): string => { if (!isScoped(scopableUi)) { - return ''; + return path ?? ''; } const segments = toDataPathSegments(scopableUi.scope); - if (isEmpty(segments) && path === undefined) { - return ''; + if (isEmpty(segments)) { + return path ?? ''; } - return isEmpty(segments) ? path : compose(path, segments.join('.')); + return compose(path, segments.join('.')); }; /** From d1e9da74b578e2e63fff883c0549f14a5901af46 Mon Sep 17 00:00:00 2001 From: Stephan Wienczny Date: Thu, 2 Jun 2022 12:58:18 +0200 Subject: [PATCH 10/14] Improve type guards by using typeof --- packages/core/src/models/uischema.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/src/models/uischema.ts b/packages/core/src/models/uischema.ts index 0012dc544..289ea661f 100644 --- a/packages/core/src/models/uischema.ts +++ b/packages/core/src/models/uischema.ts @@ -259,14 +259,14 @@ export const isGroup = (layout: Layout): layout is GroupLayout => export const isLayout = (uischema: UISchemaElement): uischema is Layout => (uischema as Layout).elements !== undefined; -export const isScopeable = (obj: object): obj is Scopable => - obj !== undefined && obj.hasOwnProperty('scope'); +export const isScopable = (obj: unknown): obj is Scopable => + obj && typeof obj === 'object'; export const isScoped = (obj: unknown): obj is Scoped => - isScopeable(obj) && typeof obj.scope === 'string'; + isScopable(obj) && typeof obj.scope === 'string'; -export const isLabelable = (obj: object): obj is Lableable => - obj !== undefined && obj.hasOwnProperty('label'); +export const isLabelable = (obj: unknown): obj is Lableable => + obj && typeof obj === 'object'; -export const isLabeled = (obj: object): obj is Labeled => - isLabelable(obj) && obj.label !== undefined; +export const isLabeled = (obj: unknown): obj is Labeled => + isLabelable(obj) && ['string', 'object'].includes(typeof obj.label); From c0635da8ab93134fc0ec914940c14b39ef1ba79e Mon Sep 17 00:00:00 2001 From: Stephan Wienczny Date: Fri, 3 Jun 2022 12:54:50 +0200 Subject: [PATCH 11/14] Fix merge conflicts --- packages/core/src/models/uischema.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/core/src/models/uischema.ts b/packages/core/src/models/uischema.ts index 48e95cf46..ec0f2e9f0 100644 --- a/packages/core/src/models/uischema.ts +++ b/packages/core/src/models/uischema.ts @@ -48,10 +48,12 @@ export interface Scoped extends Scopable { } /** - * Interface for describing an UI schema element that may be labeled. */ export interface Lableable { + /** + * Label for UI schema element. + */ label?: string|T; } @@ -59,7 +61,8 @@ export interface Lableable { * Interface for describing an UI schema element that is labeled. */ export interface Labeled extends Lableable { - label: string|T; + label: string | T; +} /* * Interface for describing an UI schema element that can provide an internationalization base key. @@ -237,7 +240,7 @@ export interface LabelElement extends UISchemaElement { * A control element. The scope property of the control determines * to which part of the schema the control should be bound. */ -export interface ControlElement extends UISchemaElement, Scoped, Lableable | Internationalizable { +export interface ControlElement extends UISchemaElement, Scoped, Lableable, Internationalizable { type: 'Control'; } @@ -262,9 +265,8 @@ export interface Categorization extends UISchemaElement, Labeled { elements: (Category | Categorization)[]; } -export const isInternationalized = (element: unknown): element is Required => { - return typeof element === 'object' && element !== null && typeof (element as Internationalizable).i18n === 'string'; -} +export const isInternationalized = (element: unknown): element is Required => + typeof element === 'object' && element !== null && typeof (element as Internationalizable).i18n === 'string'; export const isGroup = (layout: Layout): layout is GroupLayout => layout.type === 'Group'; From 5afe4e03ea4665aa6d37e9c24e519d723c107949 Mon Sep 17 00:00:00 2001 From: Stefan Dirix Date: Tue, 21 Jun 2022 16:56:50 +0200 Subject: [PATCH 12/14] Mention scopable changes in migration guide --- MIGRATION.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 65503ebc3..8a0b60657 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -104,6 +104,11 @@ There should not be any behavior changes. All React Material class components were refactored to functional components. Please check whether you extended any of our base renderers in your adaptation. +### Scopable interface change + +The `scope` attribute in `Scopable` is now optional. +Use `Scoped` instead for non optional scopes. + ## Migrating to JSON Forms 2.5 ### JsonForms Component for Angular From 46b341d9c84f589d5445626b2ec12075d83a387e Mon Sep 17 00:00:00 2001 From: Stefan Dirix Date: Tue, 21 Jun 2022 16:58:54 +0200 Subject: [PATCH 13/14] Update MIGRATION.md --- MIGRATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MIGRATION.md b/MIGRATION.md index 8a0b60657..0279c68e8 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -108,7 +108,7 @@ Please check whether you extended any of our base renderers in your adaptation. The `scope` attribute in `Scopable` is now optional. Use `Scoped` instead for non optional scopes. - +The utility function `fromScopable` was renamed to `fromScoped` accordingly. ## Migrating to JSON Forms 2.5 ### JsonForms Component for Angular From 1938d16c8cd3a28612bd50800824ce3b914e4ae9 Mon Sep 17 00:00:00 2001 From: Stefan Dirix Date: Tue, 21 Jun 2022 16:59:10 +0200 Subject: [PATCH 14/14] Update MIGRATION.md --- MIGRATION.md | 1 + 1 file changed, 1 insertion(+) diff --git a/MIGRATION.md b/MIGRATION.md index 0279c68e8..073af2deb 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -109,6 +109,7 @@ Please check whether you extended any of our base renderers in your adaptation. The `scope` attribute in `Scopable` is now optional. Use `Scoped` instead for non optional scopes. The utility function `fromScopable` was renamed to `fromScoped` accordingly. + ## Migrating to JSON Forms 2.5 ### JsonForms Component for Angular