Skip to content

Commit 5ccb67e

Browse files
committed
Update cell enablement algorithm
Update cell `enabled` determination to the new enablement algorithm. Note that the cell `enabled` still prefers `ownProps` (except for the form wide readonly flag) over any other determination for performance gains. The Table (or other renderers dispatching to cells) usually already determined whether the cell shall be enabled and therefore hand the correct `enabled` prop over. If that is not wanted, the new enablement algorithm will kick in when not setting this prop. Also adds more `isInherentlyEnabled` test cases and fixes the JSON Schema `readOnly` lookup.
1 parent f494b48 commit 5ccb67e

File tree

4 files changed

+107
-25
lines changed

4 files changed

+107
-25
lines changed

packages/core/src/util/cell.ts

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,20 @@
2525
import isEmpty from 'lodash/isEmpty';
2626
import union from 'lodash/union';
2727
import { getConfig, getData, getErrorAt, getSchema, getAjv } from '../reducers';
28-
import {
29-
AnyAction,
30-
Dispatch,
31-
formatErrorMessage,
32-
isEnabled,
33-
isVisible,
34-
OwnPropsOfControl,
35-
OwnPropsOfEnum,
36-
Resolve,
37-
StatePropsOfScopedRenderer
38-
} from '.';
3928
import {
4029
DispatchPropsOfControl,
4130
EnumOption,
4231
enumToEnumOptionMapper,
43-
mapDispatchToControlProps
32+
mapDispatchToControlProps,
33+
OwnPropsOfControl,
34+
OwnPropsOfEnum,
35+
StatePropsOfScopedRenderer
4436
} from './renderer';
4537
import { JsonFormsState } from '../store';
4638
import { JsonFormsCellRendererRegistryEntry } from '../reducers/cells';
47-
import { JsonSchema } from '..';
39+
import { JsonSchema } from '../models/jsonSchema';
40+
import { isInherentlyEnabled, isVisible } from './runtime';
41+
import { AnyAction, Dispatch, formatErrorMessage, Resolve } from '.';
4842

4943
export { JsonFormsCellRendererRegistryEntry };
5044

@@ -109,17 +103,38 @@ export const mapStateToCellProps = (
109103
ownProps.visible !== undefined
110104
? ownProps.visible
111105
: isVisible(uischema, rootData, undefined, getAjv(state));
112-
const readonly = state.jsonforms.readonly;
113-
const enabled =
114-
!readonly &&
115-
(ownProps.enabled !== undefined
116-
? ownProps.enabled
117-
: isEnabled(uischema, rootData, undefined, getAjv(state)));
106+
107+
const rootSchema = getSchema(state);
108+
const config = getConfig(state);
109+
110+
/* When determining the enabled state of cells we take a shortcut: At the
111+
* moment it's only possible to configure enablement and disablement at the
112+
* control level. Therefore the renderer using the cell, for example a
113+
* table renderer, determines whether a cell is enabled and should hand
114+
* over the prop themselves. If that prop was given, we prefer it over
115+
* anything else to save evaluation effort (except for the global readonly
116+
* flag). For example it would be quite expensive to evaluate the same ui schema
117+
* rule again and again for each cell of a table. */
118+
let enabled;
119+
if (state.jsonforms.readonly === true) {
120+
enabled = false;
121+
} else if (typeof ownProps.enabled === 'boolean') {
122+
enabled = ownProps.enabled;
123+
} else {
124+
enabled = isInherentlyEnabled(
125+
state,
126+
ownProps,
127+
uischema,
128+
schema || rootSchema,
129+
rootData,
130+
config
131+
);
132+
}
133+
118134
const errors = formatErrorMessage(
119135
union(getErrorAt(path, schema)(state).map(error => error.message))
120136
);
121137
const isValid = isEmpty(errors);
122-
const rootSchema = getSchema(state);
123138

124139
return {
125140
data: Resolve.data(rootData, path),

packages/core/src/util/renderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,7 @@ export const mapStateToLayoutProps = (
785785
state,
786786
ownProps,
787787
uischema,
788-
ownProps.schema,
788+
undefined, // layouts have no associated schema
789789
rootData,
790790
config
791791
);

packages/core/src/util/runtime.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,16 @@ export const isEnabled = (
181181
return true;
182182
};
183183

184+
/**
185+
* Indicates whether the given `uischema` element shall be enabled or disabled.
186+
* Checks the global readonly flag, uischema rule, uischema options (including the config),
187+
* the schema and the enablement indicator of the parent.
188+
*/
184189
export const isInherentlyEnabled = (
185190
state: JsonFormsState,
186191
ownProps: any,
187192
uischema: UISchemaElement,
188-
schema: JsonSchema & { readOnly?: boolean },
193+
schema: JsonSchema & { readOnly?: boolean } | undefined,
189194
rootData: any,
190195
config: any
191196
) => {
@@ -207,8 +212,8 @@ export const isInherentlyEnabled = (
207212
if (typeof config?.readOnly === 'boolean') {
208213
return !config.readOnly;
209214
}
210-
if (typeof schema?.readOnly === 'boolean') {
211-
return !schema.readOnly;
215+
if (schema?.readOnly === true) {
216+
return false;
212217
}
213218
if (typeof ownProps?.enabled === 'boolean') {
214219
return ownProps.enabled;

packages/core/test/util/runtime.test.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ test('isInherentlyEnabled disabled by uischema over ownProps', t => {
592592
t.false(
593593
isInherentlyEnabled(
594594
null,
595-
{ enabled: false },
595+
{ enabled: true },
596596
({ options: { readonly: true } } as unknown) as ControlElement,
597597
null,
598598
null,
@@ -601,6 +601,32 @@ test('isInherentlyEnabled disabled by uischema over ownProps', t => {
601601
);
602602
});
603603

604+
test('isInherentlyEnabled enabled by uischema over schema', t => {
605+
t.true(
606+
isInherentlyEnabled(
607+
null,
608+
null,
609+
({ options: { readonly: false } } as unknown) as ControlElement,
610+
{ readOnly: true },
611+
null,
612+
null
613+
)
614+
);
615+
});
616+
617+
test('isInherentlyEnabled disabled by ownProps over schema enablement', t => {
618+
t.false(
619+
isInherentlyEnabled(
620+
null,
621+
{ enabled: false},
622+
null,
623+
{ readOnly: false },
624+
null,
625+
null
626+
)
627+
);
628+
});
629+
604630
test('isInherentlyEnabled disabled by uischema over schema', t => {
605631
t.false(
606632
isInherentlyEnabled(
@@ -712,6 +738,42 @@ test('isInherentlyEnabled enabled by config over ownProps', t => {
712738
);
713739
});
714740

741+
test('isInherentlyEnabled enabled by uischema over config', t => {
742+
t.true(
743+
isInherentlyEnabled(
744+
null,
745+
null,
746+
({ options: { readonly: false } } as unknown) as ControlElement,
747+
null,
748+
null,
749+
{ readonly: true }
750+
)
751+
);
752+
});
753+
754+
test('isInherentlyEnabled prefer readonly over readOnly', t => {
755+
t.true(
756+
isInherentlyEnabled(
757+
null,
758+
null,
759+
({ options: { readonly: false, readOnly: true } } as unknown) as ControlElement,
760+
null,
761+
null,
762+
null
763+
)
764+
);
765+
t.false(
766+
isInherentlyEnabled(
767+
null,
768+
null,
769+
({ options: { readonly: true, readOnly: false } } as unknown) as ControlElement,
770+
null,
771+
null,
772+
null
773+
)
774+
);
775+
});
776+
715777
test('isInherentlyEnabled enabled', t => {
716778
t.true(isInherentlyEnabled(null, null, null, null, null, null));
717779
});

0 commit comments

Comments
 (0)