Skip to content
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
5 changes: 1 addition & 4 deletions packages/angular/src/abstract-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,7 @@ export abstract class JsonFormsAbstractControl<
}

isEnabled(): boolean {
return (
this.enabled &&
(this.uischema.options ? !this.uischema.options.readonly : true)
);
return this.enabled;
}

protected getOwnProps(): OwnPropsOfControl {
Expand Down
54 changes: 35 additions & 19 deletions packages/core/src/util/cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,20 @@
import isEmpty from 'lodash/isEmpty';
import union from 'lodash/union';
import { getConfig, getData, getErrorAt, getSchema, getAjv } from '../reducers';
import {
AnyAction,
Dispatch,
formatErrorMessage,
isEnabled,
isVisible,
OwnPropsOfControl,
OwnPropsOfEnum,
Resolve,
StatePropsOfScopedRenderer
} from '.';
import {
DispatchPropsOfControl,
EnumOption,
enumToEnumOptionMapper,
mapDispatchToControlProps,
OwnPropsOfControl,
OwnPropsOfEnum,
StatePropsOfScopedRenderer
} from './renderer';
import { JsonFormsState } from '../store';
import { JsonFormsCellRendererRegistryEntry } from '../reducers/cells';
import { JsonSchema } from '..';
import { JsonSchema } from '../models/jsonSchema';
import { isInherentlyEnabled, isVisible } from './runtime';
import { AnyAction, Dispatch, formatErrorMessage, Resolve } from '.';

export { JsonFormsCellRendererRegistryEntry };

Expand Down Expand Up @@ -109,16 +103,38 @@ export const mapStateToCellProps = (
ownProps.visible !== undefined
? ownProps.visible
: isVisible(uischema, rootData, undefined, getAjv(state));
const readonly = state.jsonforms.readonly;
const enabled =
!readonly && (ownProps.enabled !== undefined
? ownProps.enabled
: isEnabled(uischema, rootData, undefined, getAjv(state)));

const rootSchema = getSchema(state);
const config = getConfig(state);

/* When determining the enabled state of cells we take a shortcut: At the
* moment it's only possible to configure enablement and disablement at the
* control level. Therefore the renderer using the cell, for example a
* table renderer, determines whether a cell is enabled and should hand
* over the prop themselves. If that prop was given, we prefer it over
* anything else to save evaluation effort (except for the global readonly
* flag). For example it would be quite expensive to evaluate the same ui schema
* rule again and again for each cell of a table. */
let enabled;
if (state.jsonforms.readonly === true) {
enabled = false;
} else if (typeof ownProps.enabled === 'boolean') {
enabled = ownProps.enabled;
} else {
enabled = isInherentlyEnabled(
state,
ownProps,
uischema,
schema || rootSchema,
rootData,
config
);
}

const errors = formatErrorMessage(
union(getErrorAt(path, schema)(state).map(error => error.message))
);
const isValid = isEmpty(errors);
const rootSchema = getSchema(state);

return {
data: Resolve.data(rootData, path),
Expand Down Expand Up @@ -166,7 +182,7 @@ export const defaultMapStateToEnumCellProps = (
const options: EnumOption[] =
ownProps.options ||
props.schema.enum?.map(enumToEnumOptionMapper) ||
props.schema.const && [enumToEnumOptionMapper(props.schema.const)];
(props.schema.const && [enumToEnumOptionMapper(props.schema.const)]);
return {
...props,
options
Expand Down
48 changes: 28 additions & 20 deletions packages/core/src/util/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,8 @@ import {
createLabelDescriptionFrom,
Dispatch,
formatErrorMessage,
hasEnableRule,
hasShowRule,
isEnabled,
isInherentlyEnabled,
isVisible,
moveDown,
moveUp,
Expand Down Expand Up @@ -405,11 +404,6 @@ export const mapStateToControlProps = (
ownProps.visible === undefined || hasShowRule(uischema)
? isVisible(uischema, rootData, ownProps.path, getAjv(state))
: ownProps.visible;
const readonly = state.jsonforms.readonly;
const enabled: boolean =
!readonly && (ownProps.enabled === undefined || hasEnableRule(uischema)
? isEnabled(uischema, rootData, ownProps.path, getAjv(state) )
: ownProps.enabled);
const controlElement = uischema as ControlElement;
const id = ownProps.id;
const rootSchema = getSchema(state);
Expand All @@ -429,6 +423,15 @@ export const mapStateToControlProps = (
const data = Resolve.data(rootData, path);
const labelDesc = createLabelDescriptionFrom(uischema, resolvedSchema);
const label = labelDesc.show ? labelDesc.text : '';
const config = getConfig(state);
const enabled: boolean = isInherentlyEnabled(
state,
ownProps,
uischema,
resolvedSchema || rootSchema,
rootData,
config
);
return {
data,
description,
Expand Down Expand Up @@ -474,9 +477,9 @@ export const mapStateToEnumControlProps = (
): StatePropsOfControl & OwnPropsOfEnum => {
const props: StatePropsOfControl = mapStateToControlProps(state, ownProps);
const options: EnumOption[] =
ownProps.options ||
props.schema.enum?.map(enumToEnumOptionMapper) ||
props.schema.const && [enumToEnumOptionMapper(props.schema.const)];
ownProps.options ||
props.schema.enum?.map(enumToEnumOptionMapper) ||
(props.schema.const && [enumToEnumOptionMapper(props.schema.const)]);
return {
...props,
options
Expand Down Expand Up @@ -517,7 +520,8 @@ export const mapStateToMultiEnumControlProps = (
const items = props.schema.items as JsonSchema;
const options: EnumOption[] =
ownProps.options ||
(items?.oneOf && (items.oneOf as JsonSchema[]).map(oneOfToEnumOptionMapper)) ||
(items?.oneOf &&
(items.oneOf as JsonSchema[]).map(oneOfToEnumOptionMapper)) ||
items?.enum?.map(enumToEnumOptionMapper);
return {
...props,
Expand Down Expand Up @@ -700,14 +704,14 @@ export const mapDispatchToArrayControlProps = (
});

export interface DispatchPropsOfMultiEnumControl {
addItem: (path:string, value: any) => void;
removeItem? :(path:string, toDelete: any) => void;
addItem: (path: string, value: any) => void;
removeItem?: (path: string, toDelete: any) => void;
}

export const mapDispatchToMultiEnumProps = (
dispatch: Dispatch<CoreActions>
): DispatchPropsOfMultiEnumControl => ({
addItem: (path:string, value: any) => {
addItem: (path: string, value: any) => {
dispatch(
update(path, data => {
if (data === undefined || data === null) {
Expand All @@ -718,7 +722,7 @@ export const mapDispatchToMultiEnumProps = (
})
);
},
removeItem: (path:string, toDelete: any) => {
removeItem: (path: string, toDelete: any) => {
dispatch(
update(path, data => {
const indexInData = data.indexOf(toDelete);
Expand Down Expand Up @@ -774,13 +778,17 @@ export const mapStateToLayoutProps = (
ownProps.visible === undefined || hasShowRule(uischema)
? isVisible(ownProps.uischema, rootData, ownProps.path, getAjv(state))
: ownProps.visible;
const readonly = state.jsonforms.readonly;
const enabled: boolean =
!readonly && (ownProps.enabled === undefined || hasEnableRule(uischema)
? isEnabled(ownProps.uischema, rootData, ownProps.path, getAjv(state))
: ownProps.enabled);

const data = Resolve.data(rootData, ownProps.path);
const config = getConfig(state);
const enabled: boolean = isInherentlyEnabled(
state,
ownProps,
uischema,
undefined, // layouts have no associated schema
rootData,
config
);

return {
...layoutDefaultProps,
Expand Down
43 changes: 43 additions & 0 deletions packages/core/src/util/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ import {
import { resolveData } from './resolvers';
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 Expand Up @@ -177,3 +180,43 @@ export const isEnabled = (

return true;
};

/**
* Indicates whether the given `uischema` element shall be enabled or disabled.
* Checks the global readonly flag, uischema rule, uischema options (including the config),
* the schema and the enablement indicator of the parent.
*/
export const isInherentlyEnabled = (
state: JsonFormsState,
ownProps: any,
uischema: UISchemaElement,
schema: JsonSchema & { readOnly?: boolean } | undefined,
rootData: any,
config: any
) => {
if (state?.jsonforms?.readonly) {
return false;
}
if (uischema && hasEnableRule(uischema)) {
return isEnabled(uischema, rootData, ownProps?.path, getAjv(state));
}
if (typeof uischema?.options?.readonly === 'boolean') {
return !uischema.options.readonly;
}
if (typeof uischema?.options?.readOnly === 'boolean') {
return !uischema.options.readOnly;
}
if (typeof config?.readonly === 'boolean') {
return !config.readonly;
}
if (typeof config?.readOnly === 'boolean') {
return !config.readOnly;
}
if (schema?.readOnly === true) {
return false;
}
if (typeof ownProps?.enabled === 'boolean') {
return ownProps.enabled;
}
return true;
};
5 changes: 4 additions & 1 deletion packages/core/test/util/cell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,10 @@ test('mapStateToEnumCellProps - set default options for dropdown list', t => {
};

const props = defaultMapStateToEnumCellProps(createState(uischema), ownProps);
t.deepEqual(props.options, ['DE', 'IT', 'JP', 'US', 'RU', 'Other'].map(enumToEnumOptionMapper));
t.deepEqual(
props.options,
['DE', 'IT', 'JP', 'US', 'RU', 'Other'].map(enumToEnumOptionMapper)
);
t.is(props.data, undefined);
});

Expand Down
Loading