Skip to content

Commit c2adb17

Browse files
committed
change path to JSON pointer
1 parent e80a873 commit c2adb17

39 files changed

+441
-377
lines changed

packages/angular-material/src/other/master-detail/master.ts

+6-7
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const removeSchemaKeywords = (path: string) => {
5959
path
6060
.split('/')
6161
.filter((s) => !some(keywords, (key) => key === s))
62-
.join('.')
62+
.join('/')
6363
);
6464
};
6565

@@ -87,7 +87,7 @@ export const removeSchemaKeywords = (path: string) => {
8787
<button
8888
mat-icon-button
8989
class="button hide"
90-
(click)="onDeleteClick(i)"
90+
(click)="onDeleteClick($event, i)"
9191
[ngClass]="{ show: highlightedIdx == i }"
9292
*ngIf="isEnabled()"
9393
>
@@ -206,16 +206,14 @@ export class MasterListComponent
206206
this.translations = props.translations;
207207

208208
const masterItems = (data || []).map((d: any, index: number) => {
209-
const labelRefInstancePath =
210-
controlElement.options?.labelRef &&
211-
removeSchemaKeywords(controlElement.options.labelRef);
209+
const labelRefInstancePath = controlElement.options?.labelRef;
212210
const isPrimitive = d !== undefined && typeof d !== 'object';
213211
const masterItem = {
214212
label: isPrimitive
215213
? d.toString()
216214
: get(d, labelRefInstancePath ?? getFirstPrimitiveProp(schema)),
217215
data: d,
218-
path: `${path}.${index}`,
216+
path: `${path}/${index}`,
219217
schema,
220218
uischema: detailUISchema,
221219
};
@@ -266,7 +264,8 @@ export class MasterListComponent
266264
this.addItem(this.propsPath, createDefaultValue(this.scopedSchema))();
267265
}
268266

269-
onDeleteClick(item: number) {
267+
onDeleteClick(e: any, item: number) {
268+
e.stopPropagation();
270269
this.removeItems(this.propsPath, [item])();
271270
}
272271

packages/angular-material/test/master-detail.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ describe('Master detail', () => {
229229
// delete 1st item
230230
spyOn(component, 'removeItems').and.callFake(() => () => {
231231
getJsonFormsService(component).updateCore(
232-
Actions.update('orders', () => moreData.orders.slice(1))
232+
Actions.update('/orders', () => moreData.orders.slice(1))
233233
);
234234
fixture.detectChanges();
235235
});
@@ -273,7 +273,7 @@ describe('Master detail', () => {
273273
const copy = moreData.orders.slice();
274274
copy.splice(1, 1);
275275
getJsonFormsService(component).updateCore(
276-
Actions.update('orders', () => copy)
276+
Actions.update('/orders', () => copy)
277277
);
278278
fixture.detectChanges();
279279
});
@@ -388,7 +388,7 @@ describe('Master detail', () => {
388388
customer: { name: 'ACME' },
389389
title: 'Carrots',
390390
},
391-
path: 'orders.0',
391+
path: '/orders/0',
392392
schema: schema.definitions.order,
393393
uischema: {
394394
type: 'VerticalLayout',

packages/core/src/i18n/i18nUtil.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ErrorObject } from 'ajv';
22
import { isInternationalized, Labelable, UISchemaElement } from '../models';
33
import { getControlPath } from '../reducers';
4-
import { formatErrorMessage } from '../util';
4+
import { formatErrorMessage, toLodashPath } from '../util';
55
import type { i18nJsonSchema, ErrorTranslator, Translator } from './i18nTypes';
66
import {
77
ArrayDefaultTranslation,
@@ -28,7 +28,7 @@ export const getI18nKeyPrefixBySchema = (
2828
*/
2929
export const transformPathToI18nPrefix = (path: string): string => {
3030
return (
31-
path
31+
toLodashPath(path)
3232
?.split('.')
3333
.filter((segment) => !/^\d+$/.test(segment))
3434
.join('.') || 'root'
@@ -159,7 +159,7 @@ export const deriveLabelForUISchemaElement = (
159159
const i18nKeyPrefix = getI18nKeyPrefixBySchema(undefined, uischema);
160160
const i18nKey =
161161
typeof i18nKeyPrefix === 'string'
162-
? `${i18nKeyPrefix}.label`
162+
? `${i18nKeyPrefix}/label`
163163
: stringifiedLabel;
164164
return t(i18nKey, stringifiedLabel, { uischema: uischema });
165165
};

packages/core/src/reducers/core.ts

+13-14
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,13 @@ import {
4646
UPDATE_CORE,
4747
UpdateCoreAction,
4848
} from '../actions';
49-
import { createAjv, decode, isOneOfEnumSchema, Reducer } from '../util';
49+
import {
50+
composePaths,
51+
createAjv,
52+
isOneOfEnumSchema,
53+
Reducer,
54+
toLodashSegments,
55+
} from '../util';
5056
import type { JsonSchema, UISchemaElement } from '../models';
5157

5258
export const validate = (
@@ -284,18 +290,19 @@ export const coreReducer: Reducer<JsonFormsCore, CoreActions> = (
284290
errors,
285291
};
286292
} else {
287-
const oldData: any = get(state.data, action.path);
293+
const lodashDataPathSegments = toLodashSegments(action.path);
294+
const oldData: any = get(state.data, lodashDataPathSegments);
288295
const newData = action.updater(cloneDeep(oldData));
289296
let newState: any;
290297
if (newData !== undefined) {
291298
newState = setFp(
292-
action.path,
299+
lodashDataPathSegments,
293300
newData,
294301
state.data === undefined ? {} : state.data
295302
);
296303
} else {
297304
newState = unsetFp(
298-
action.path,
305+
lodashDataPathSegments,
299306
state.data === undefined ? {} : state.data
300307
);
301308
}
@@ -369,19 +376,11 @@ export const getControlPath = (error: ErrorObject) => {
369376
// With AJV v8 the property was renamed to 'instancePath'
370377
let controlPath = (error as any).dataPath || error.instancePath || '';
371378

372-
// change '/' chars to '.'
373-
controlPath = controlPath.replace(/\//g, '.');
374-
375379
const invalidProperty = getInvalidProperty(error);
376380
if (invalidProperty !== undefined && !controlPath.endsWith(invalidProperty)) {
377-
controlPath = `${controlPath}.${invalidProperty}`;
381+
controlPath = composePaths(controlPath, invalidProperty);
378382
}
379383

380-
// remove '.' chars at the beginning of paths
381-
controlPath = controlPath.replace(/^./, '');
382-
383-
// decode JSON Pointer escape sequences
384-
controlPath = decode(controlPath);
385384
return controlPath;
386385
};
387386

@@ -479,5 +478,5 @@ export const errorAt = (instancePath: string, schema: JsonSchema) =>
479478
getErrorsAt(instancePath, schema, (path) => path === instancePath);
480479
export const subErrorsAt = (instancePath: string, schema: JsonSchema) =>
481480
getErrorsAt(instancePath, schema, (path) =>
482-
path.startsWith(instancePath + '.')
481+
path.startsWith(instancePath + '/')
483482
);

packages/core/src/reducers/i18n.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,19 @@
2626
import {
2727
defaultErrorTranslator,
2828
defaultTranslator,
29+
ErrorTranslator,
2930
JsonFormsI18nState,
31+
Translator,
3032
} from '../i18n';
3133
import {
3234
I18nActions,
3335
SET_LOCALE,
3436
SET_TRANSLATOR,
3537
UPDATE_I18N,
3638
} from '../actions';
37-
import type { Reducer } from '../util';
39+
import { Reducer, toLodashPath } from '../util';
40+
import { ErrorObject } from 'ajv';
41+
import { UISchemaElement } from '../models';
3842

3943
export const defaultJsonFormsI18nState: Required<JsonFormsI18nState> = {
4044
locale: 'en',
@@ -53,7 +57,6 @@ export const i18nReducer: Reducer<JsonFormsI18nState, I18nActions> = (
5357
action.translator ?? defaultJsonFormsI18nState.translate;
5458
const translateError =
5559
action.errorTranslator ?? defaultJsonFormsI18nState.translateError;
56-
5760
if (
5861
locale !== state.locale ||
5962
translate !== state.translate ||
@@ -84,6 +87,22 @@ export const i18nReducer: Reducer<JsonFormsI18nState, I18nActions> = (
8487
}
8588
};
8689

90+
export const wrapTranslateFunction = (translator: Translator): Translator => {
91+
return (id: string, defaultMessage?: string | undefined, values?: any) => {
92+
return translator(toLodashPath(id), defaultMessage, values);
93+
};
94+
};
95+
96+
export const wrapErrorTranslateFunction = (
97+
translator: ErrorTranslator
98+
): ErrorTranslator => {
99+
return (
100+
error: ErrorObject,
101+
translate: Translator,
102+
uischema?: UISchemaElement
103+
) => translator(error, wrapTranslateFunction(translate), uischema);
104+
};
105+
87106
export const fetchLocale = (state?: JsonFormsI18nState) => {
88107
if (state === undefined) {
89108
return undefined;

packages/core/src/util/path.ts

+22-10
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,17 @@ import range from 'lodash/range';
2828
import { isScoped, Scopable } from '../models';
2929

3030
export const compose = (path1: string, path2: string) => {
31-
let p1 = path1;
32-
if (!isEmpty(path1) && !isEmpty(path2) && !path2.startsWith('[')) {
33-
p1 = path1 + '.';
31+
let p2 = path2;
32+
if (!isEmpty(path2) && !path2.startsWith('[') && !path2.startsWith('/')) {
33+
p2 = '/' + path2;
3434
}
3535

36-
if (isEmpty(p1)) {
37-
return path2;
36+
if (isEmpty(path1)) {
37+
return p2;
3838
} else if (isEmpty(path2)) {
39-
return p1;
39+
return path1;
4040
} else {
41-
return `${p1}${path2}`;
41+
return `${path1}${p2}`;
4242
}
4343
};
4444

@@ -76,13 +76,13 @@ export const toDataPathSegments = (schemaPath: string): string[] => {
7676
* Data paths can be used in field change event handlers like handleChange.
7777
*
7878
* @example
79-
* toDataPath('#/properties/foo/properties/bar') === 'foo.bar')
79+
* toDataPath('#/properties/foo/properties/bar') === '/foo/bar')
8080
*
8181
* @param {string} schemaPath the schema path to be converted
8282
* @returns {string} the data path
8383
*/
8484
export const toDataPath = (schemaPath: string): string => {
85-
return toDataPathSegments(schemaPath).join('.');
85+
return '/' + toDataPathSegments(schemaPath).join('/');
8686
};
8787

8888
export const composeWithUi = (scopableUi: Scopable, path: string): string => {
@@ -96,7 +96,19 @@ export const composeWithUi = (scopableUi: Scopable, path: string): string => {
9696
return path ?? '';
9797
}
9898

99-
return compose(path, segments.join('.'));
99+
return compose(path, '/' + segments.join('/'));
100+
};
101+
102+
export const toLodashSegments = (jsonPointer: string): string[] => {
103+
let path = jsonPointer;
104+
if (jsonPointer && jsonPointer.startsWith('/')) {
105+
path = jsonPointer.substring(1);
106+
}
107+
return path ? path.split('/').map(decode) : [];
108+
};
109+
110+
export const toLodashPath = (jsonPointer: string) => {
111+
return toLodashSegments(jsonPointer).join('.');
100112
};
101113

102114
/**

packages/core/src/util/resolvers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const resolveData = (instance: any, dataPath: string): any => {
4646
if (isEmpty(dataPath)) {
4747
return instance;
4848
}
49-
const dataPathSegments = dataPath.split('.');
49+
const dataPathSegments = dataPath.split('/').slice(1);
5050

5151
return dataPathSegments.reduce((curInstance, decodedSegment) => {
5252
if (

packages/core/src/util/util.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,9 @@ export const Resolve: {
108108
};
109109

110110
// Paths --
111-
const fromScoped = (scopable: Scoped): string =>
112-
toDataPathSegments(scopable.scope).join('.');
111+
const fromScoped = (scopable: Scoped): string => {
112+
return '/' + toDataPathSegments(scopable.scope).join('/');
113+
};
113114

114115
export const Paths = {
115116
compose: composePaths,

packages/core/test/i18n/i18nUtil.test.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,19 @@ test('transformPathToI18nPrefix returns root when empty', (t) => {
3636
});
3737

3838
test('transformPathToI18nPrefix does not modify non-array paths', (t) => {
39-
t.is(transformPathToI18nPrefix('foo'), 'foo');
40-
t.is(transformPathToI18nPrefix('foo.bar'), 'foo.bar');
41-
t.is(transformPathToI18nPrefix('bar3.foo2'), 'bar3.foo2');
39+
t.is(transformPathToI18nPrefix('/foo'), 'foo');
40+
t.is(transformPathToI18nPrefix('/foo/bar'), 'foo.bar');
41+
t.is(transformPathToI18nPrefix('/bar3/foo2'), 'bar3.foo2');
4242
});
4343

4444
test('transformPathToI18nPrefix removes array indices', (t) => {
45-
t.is(transformPathToI18nPrefix('foo.2.bar'), 'foo.bar');
46-
t.is(transformPathToI18nPrefix('foo.234324234.bar'), 'foo.bar');
47-
t.is(transformPathToI18nPrefix('foo.0.bar'), 'foo.bar');
48-
t.is(transformPathToI18nPrefix('foo.0.bar.1.foobar'), 'foo.bar.foobar');
49-
t.is(transformPathToI18nPrefix('3.foobar'), 'foobar');
50-
t.is(transformPathToI18nPrefix('foobar.3'), 'foobar');
51-
t.is(transformPathToI18nPrefix('foo1.23.b2ar3.1.5.foo'), 'foo1.b2ar3.foo');
45+
t.is(transformPathToI18nPrefix('foo/2/bar'), 'foo.bar');
46+
t.is(transformPathToI18nPrefix('foo/234324234/bar'), 'foo.bar');
47+
t.is(transformPathToI18nPrefix('foo/0/bar'), 'foo.bar');
48+
t.is(transformPathToI18nPrefix('foo/0/bar/1/foobar'), 'foo.bar.foobar');
49+
t.is(transformPathToI18nPrefix('3/foobar'), 'foobar');
50+
t.is(transformPathToI18nPrefix('foobar/3'), 'foobar');
51+
t.is(transformPathToI18nPrefix('foo1/23/b2ar3/1/5/foo'), 'foo1.b2ar3.foo');
5252
t.is(transformPathToI18nPrefix('3'), 'root');
5353
});
5454

0 commit comments

Comments
 (0)