Skip to content

Commit a51b723

Browse files
committed
Add context for array operation
Currently it is very hard to figure out in the middleware what array operation happend to lead to the current array state. There are cases were this information is valuable. To support this all update actions can now have a 'context'. Here we added a specific 'UpdateArrayContext'. Contributed on behalf of STMicroelectronics
1 parent bb7a255 commit a51b723

File tree

3 files changed

+171
-25
lines changed

3 files changed

+171
-25
lines changed

packages/core/src/actions/actions.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,57 @@ export const UPDATE_I18N = 'jsonforms/UPDATE_I18N' as const;
5656
export const ADD_DEFAULT_DATA = 'jsonforms/ADD_DEFAULT_DATA' as const;
5757
export const REMOVE_DEFAULT_DATA = 'jsonforms/REMOVE_DEFAULT_DATA' as const;
5858

59+
export type UpdateArrayContext =
60+
| { type: 'ADD'; values: any[] }
61+
| { type: 'REMOVE'; indices: number[] }
62+
| { type: 'MOVE'; moves: { from: number; to: number }[] };
63+
64+
export const isUpdateArrayContext = (
65+
context: object
66+
): context is UpdateArrayContext => {
67+
if (!('type' in context)) {
68+
return false;
69+
}
70+
if (typeof context.type !== 'string') {
71+
return false;
72+
}
73+
switch (context.type) {
74+
case 'ADD': {
75+
return (
76+
'values' in context &&
77+
Array.isArray(context.values) &&
78+
context.values.length > 0
79+
);
80+
}
81+
case 'REMOVE': {
82+
return (
83+
'indices' in context &&
84+
Array.isArray(context.indices) &&
85+
context.indices.length > 0 &&
86+
context.indices.every((i) => typeof i === 'number')
87+
);
88+
}
89+
case 'MOVE': {
90+
return (
91+
'moves' in context &&
92+
Array.isArray(context.moves) &&
93+
context.moves.length > 0 &&
94+
context.moves.every(
95+
(m) =>
96+
typeof m === 'object' &&
97+
m !== null &&
98+
'from' in m &&
99+
'to' in m &&
100+
typeof m.from === 'number' &&
101+
typeof m.to === 'number'
102+
)
103+
);
104+
}
105+
default:
106+
return false;
107+
}
108+
};
109+
59110
export type CoreActions =
60111
| InitAction
61112
| UpdateCoreAction
@@ -70,6 +121,7 @@ export interface UpdateAction {
70121
type: 'jsonforms/UPDATE';
71122
path: string;
72123
updater(existingData?: any): any;
124+
context?: object;
73125
}
74126

75127
export interface UpdateErrorsAction {
@@ -165,11 +217,13 @@ export const setAjv = (ajv: AJV) => ({
165217

166218
export const update = (
167219
path: string,
168-
updater: (existingData: any) => any
220+
updater: (existingData: any) => any,
221+
context?: object
169222
): UpdateAction => ({
170223
type: UPDATE_DATA,
171224
path,
172225
updater,
226+
context,
173227
});
174228

175229
export const updateErrors = (errors: ErrorObject[]): UpdateErrorsAction => ({

packages/core/src/util/renderer.ts

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import { moveDown, moveUp } from './array';
5959
import type { AnyAction, Dispatch } from './type';
6060
import { Resolve, convertDateToString, hasType } from './util';
6161
import { composePaths, composeWithUi } from './path';
62-
import { CoreActions, update } from '../actions';
62+
import { CoreActions, update, UpdateArrayContext } from '../actions';
6363
import type { ErrorObject } from 'ajv';
6464
import type { JsonFormsState } from '../store';
6565
import {
@@ -823,41 +823,63 @@ export const mapDispatchToArrayControlProps = (
823823
): DispatchPropsOfArrayControl => ({
824824
addItem: (path: string, value: any) => () => {
825825
dispatch(
826-
update(path, (array) => {
827-
if (array === undefined || array === null) {
828-
return [value];
829-
}
830-
831-
array.push(value);
832-
return array;
833-
})
826+
update(
827+
path,
828+
(array) => {
829+
if (array === undefined || array === null) {
830+
return [value];
831+
}
832+
833+
array.push(value);
834+
return array;
835+
},
836+
{ type: 'ADD', values: [value] } as UpdateArrayContext
837+
)
834838
);
835839
},
836840
removeItems: (path: string, toDelete: number[]) => () => {
837841
dispatch(
838-
update(path, (array) => {
839-
toDelete
840-
.sort((a, b) => a - b)
841-
.reverse()
842-
.forEach((s) => array.splice(s, 1));
843-
return array;
844-
})
842+
update(
843+
path,
844+
(array) => {
845+
toDelete
846+
.sort((a, b) => a - b)
847+
.reverse()
848+
.forEach((s) => array.splice(s, 1));
849+
return array;
850+
},
851+
{ type: 'REMOVE', indices: toDelete } as UpdateArrayContext
852+
)
845853
);
846854
},
847855
moveUp: (path, toMove: number) => () => {
848856
dispatch(
849-
update(path, (array) => {
850-
moveUp(array, toMove);
851-
return array;
852-
})
857+
update(
858+
path,
859+
(array) => {
860+
moveUp(array, toMove);
861+
return array;
862+
},
863+
{
864+
type: 'MOVE',
865+
moves: [{ from: toMove, to: toMove - 1 }],
866+
} as UpdateArrayContext
867+
)
853868
);
854869
},
855870
moveDown: (path, toMove: number) => () => {
856871
dispatch(
857-
update(path, (array) => {
858-
moveDown(array, toMove);
859-
return array;
860-
})
872+
update(
873+
path,
874+
(array) => {
875+
moveDown(array, toMove);
876+
return array;
877+
},
878+
{
879+
type: 'MOVE',
880+
moves: [{ from: toMove, to: toMove + 1 }],
881+
} as UpdateArrayContext
882+
)
861883
);
862884
},
863885
});

packages/core/test/actions/actions.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,73 @@ test('Init Action generates ui schema when not valid', (t) => {
9191
],
9292
} as UISchemaElement);
9393
});
94+
95+
test('isUpdateArrayContext correctly identifies ', (t) => {
96+
t.deepEqual(Actions.isUpdateArrayContext({}), false);
97+
t.deepEqual(Actions.isUpdateArrayContext({ type: 'ADD' }), false);
98+
t.deepEqual(Actions.isUpdateArrayContext({ type: 'ADD', values: [] }), false);
99+
t.deepEqual(
100+
Actions.isUpdateArrayContext({ type: 'ADD', values: [0, ''] }),
101+
true
102+
);
103+
104+
t.deepEqual(Actions.isUpdateArrayContext({ type: 'REMOVE' }), false);
105+
t.deepEqual(
106+
Actions.isUpdateArrayContext({ type: 'REMOVE', indices: [] }),
107+
false
108+
);
109+
t.deepEqual(
110+
Actions.isUpdateArrayContext({ type: 'REMOVE', indices: [0, ''] }),
111+
false
112+
);
113+
t.deepEqual(
114+
Actions.isUpdateArrayContext({ type: 'REMOVE', indices: [0, 2] }),
115+
true
116+
);
117+
118+
t.deepEqual(Actions.isUpdateArrayContext({ type: 'MOVE' }), false);
119+
t.deepEqual(Actions.isUpdateArrayContext({ type: 'MOVE', moves: [] }), false);
120+
t.deepEqual(
121+
Actions.isUpdateArrayContext({ type: 'MOVE', moves: [0] }),
122+
false
123+
);
124+
t.deepEqual(
125+
Actions.isUpdateArrayContext({ type: 'MOVE', moves: [null] }),
126+
false
127+
);
128+
t.deepEqual(
129+
Actions.isUpdateArrayContext({
130+
type: 'MOVE',
131+
moves: [{ from: 0, to: 1 }, { from: 2 }],
132+
}),
133+
false
134+
);
135+
t.deepEqual(
136+
Actions.isUpdateArrayContext({
137+
type: 'MOVE',
138+
moves: [{ from: 0, to: 1 }, { to: 0 }],
139+
}),
140+
false
141+
);
142+
t.deepEqual(
143+
Actions.isUpdateArrayContext({
144+
type: 'MOVE',
145+
moves: [{ from: 0, to: '' }],
146+
}),
147+
false
148+
);
149+
t.deepEqual(
150+
Actions.isUpdateArrayContext({
151+
type: 'MOVE',
152+
moves: [
153+
{ from: 0, to: 1 },
154+
{ from: 0, to: '' },
155+
],
156+
}),
157+
false
158+
);
159+
t.deepEqual(
160+
Actions.isUpdateArrayContext({ type: 'MOVE', moves: [{ from: 0, to: 1 }] }),
161+
true
162+
);
163+
});

0 commit comments

Comments
 (0)