Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional interfaces for Scoped, Labelable and Labeled ui elements. #1935

Merged
merged 19 commits into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ 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.
The utility function `fromScopable` was renamed to `fromScoped` accordingly.
sdirix marked this conversation as resolved.
Show resolved Hide resolved

### Localization of Date Picker in Angular Material

Date Picker in Angular Material will use the global configuration of your Angular Material application.
Expand Down
75 changes: 49 additions & 26 deletions packages/core/src/models/uischema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,44 @@ 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.
*/
scope: string;
}

/**
* Interface for describing an UI schema element that may be labeled.
*/
export interface Lableable<T = string> {
sdirix marked this conversation as resolved.
Show resolved Hide resolved
/**
* Label for UI schema element.
*/
label?: string|T;
}

/**
* Interface for describing an UI schema element that is labeled.
*/
export interface Labeled<T = string> extends Lableable<T> {
sdirix marked this conversation as resolved.
Show resolved Hide resolved
label: string | T;
}

/*
* Interface for describing an UI schema element that can provide an internationalization base key.
* If defined, this key is suffixed to derive applicable message keys for the UI schema element.
* For example, such suffixes are `.label` or `.description` to derive the corresponding message keys for a control element.
Expand Down Expand Up @@ -96,7 +124,7 @@ export interface Condition {
/**
* A leaf condition.
*/
export interface LeafCondition extends Condition, Scopable {
export interface LeafCondition extends Condition, Scoped {
type: 'LEAF';

/**
Expand All @@ -105,7 +133,7 @@ export interface LeafCondition extends Condition, Scopable {
expectedValue: any;
}

export interface SchemaBasedCondition extends Condition, Scopable {
export interface SchemaBasedCondition extends Condition, Scoped {
schema: JsonSchema;
}

Expand Down Expand Up @@ -179,12 +207,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;
}

/**
Expand Down Expand Up @@ -216,49 +240,48 @@ 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, Internationalizable {
export interface ControlElement extends UISchemaElement, Scoped, Lableable<string | boolean | LabelDescription>, Internationalizable {
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;
}

/**
* The categorization element, which may have children elements.
* 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}.
*/
elements: (Category | Categorization)[];
}

export const isInternationalized = (element: unknown): element is Required<Internationalizable> => {
return typeof element === 'object' && element !== null && typeof (element as Internationalizable).i18n === 'string';
}
export const isInternationalized = (element: unknown): element is Required<Internationalizable> =>
typeof element === 'object' && element !== null && typeof (element as Internationalizable).i18n === 'string';

export const isGroup = (layout: Layout): layout is GroupLayout =>
layout.type === 'Group';

export const isLayout = (uischema: UISchemaElement): uischema is Layout =>
(uischema as Layout).elements !== undefined;

sdirix marked this conversation as resolved.
Show resolved Hide resolved
export const isScopable = (obj: unknown): obj is Scopable =>
obj && typeof obj === 'object';

export const isScoped = (obj: unknown): obj is Scoped =>
isScopable(obj) && typeof obj.scope === 'string';

export const isLabelable = (obj: unknown): obj is Lableable =>
obj && typeof obj === 'object';

export const isLabeled = (obj: unknown): obj is Labeled =>
isLabelable(obj) && ['string', 'object'].includes(typeof obj.label);
14 changes: 9 additions & 5 deletions packages/core/src/util/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -81,13 +81,17 @@ export const toDataPath = (schemaPath: string): string => {
};

export const composeWithUi = (scopableUi: Scopable, path: string): string => {
if (!isScoped(scopableUi)) {
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('.'));
};

/**
Expand All @@ -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/, '~');
export const decode = (pointerSegment: string) => pointerSegment?.replace(/~1/g, '/').replace(/~0/, '~');
2 changes: 1 addition & 1 deletion packages/core/src/util/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import has from 'lodash/has';
import {
AndCondition,
Condition,
JsonSchema,
LeafCondition,
OrCondition,
RuleEffect,
Expand All @@ -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';
Expand Down
16 changes: 8 additions & 8 deletions packages/core/src/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 [];
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down