Skip to content

Commit 4eff17b

Browse files
AlexandraBuzilasdirix
authored andcommitted
feat: respect "default" values when creating new elements in an array
Initialize new array elements with their default values. Fixes default value returned for string schemas with a date format. Fixes #2195 Contributed on behalf of STMicroelectronics Signed-off-by: Alexandra Buzila <[email protected]>
1 parent c060472 commit 4eff17b

File tree

17 files changed

+421
-62
lines changed

17 files changed

+421
-62
lines changed

packages/angular-material/src/layouts/array-layout.renderer.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,10 @@ export class ArrayLayoutRenderer
180180
this.removeItems(this.propsPath, [index])();
181181
}
182182
add(): void {
183-
this.addItem(this.propsPath, createDefaultValue(this.scopedSchema))();
183+
this.addItem(
184+
this.propsPath,
185+
createDefaultValue(this.scopedSchema, this.rootSchema)
186+
)();
184187
}
185188
up(index: number): void {
186189
this.moveItemUp(this.propsPath, index)();

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,10 @@ export class MasterListComponent
263263
}
264264

265265
onAddClick() {
266-
this.addItem(this.propsPath, createDefaultValue(this.scopedSchema))();
266+
this.addItem(
267+
this.propsPath,
268+
createDefaultValue(this.scopedSchema, this.rootSchema)
269+
)();
267270
}
268271

269272
onDeleteClick(item: number) {

packages/angular-material/src/other/table.renderer.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,10 @@ export class TableRenderer extends JsonFormsArrayControl implements OnInit {
175175
this.removeItems(this.propsPath, [index])();
176176
}
177177
add(): void {
178-
this.addItem(this.propsPath, createDefaultValue(this.scopedSchema))();
178+
this.addItem(
179+
this.propsPath,
180+
createDefaultValue(this.scopedSchema, this.rootSchema)
181+
)();
179182
}
180183
up(index: number): void {
181184
this.moveItemUp(this.propsPath, index)();

packages/core/src/util/renderer.ts

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import { createLabelDescriptionFrom } from './label';
5757
import type { CombinatorKeyword } from './combinators';
5858
import { moveDown, moveUp } from './array';
5959
import type { AnyAction, Dispatch } from './type';
60-
import { Resolve } from './util';
60+
import { Resolve, convertDateToString, hasType } from './util';
6161
import { composePaths, composeWithUi } from './path';
6262
import { CoreActions, update } from '../actions';
6363
import type { ErrorObject } from 'ajv';
@@ -79,6 +79,7 @@ import {
7979
ArrayTranslations,
8080
} from '../i18n/arrayTranslations';
8181
import { resolveSchema } from './resolvers';
82+
import cloneDeep from 'lodash/cloneDeep';
8283

8384
const isRequired = (
8485
schema: JsonSchema,
@@ -138,33 +139,65 @@ export const showAsRequired = (
138139
};
139140

140141
/**
141-
* Create a default value based on the given scheam.
142+
* Create a default value based on the given schema.
142143
* @param schema the schema for which to create a default value.
143144
* @returns {any}
144145
*/
145-
export const createDefaultValue = (schema: JsonSchema) => {
146-
switch (schema.type) {
147-
case 'string':
148-
if (
149-
schema.format === 'date-time' ||
150-
schema.format === 'date' ||
151-
schema.format === 'time'
152-
) {
153-
return new Date();
146+
export const createDefaultValue = (
147+
schema: JsonSchema,
148+
rootSchema: JsonSchema
149+
) => {
150+
const resolvedSchema = Resolve.schema(schema, schema.$ref, rootSchema);
151+
if (resolvedSchema.default !== undefined) {
152+
return extractDefaults(resolvedSchema, rootSchema);
153+
}
154+
if (hasType(resolvedSchema, 'string')) {
155+
if (
156+
resolvedSchema.format === 'date-time' ||
157+
resolvedSchema.format === 'date' ||
158+
resolvedSchema.format === 'time'
159+
) {
160+
return convertDateToString(new Date(), resolvedSchema.format);
161+
}
162+
return '';
163+
} else if (
164+
hasType(resolvedSchema, 'integer') ||
165+
hasType(resolvedSchema, 'number')
166+
) {
167+
return 0;
168+
} else if (hasType(resolvedSchema, 'boolean')) {
169+
return false;
170+
} else if (hasType(resolvedSchema, 'array')) {
171+
return [];
172+
} else if (hasType(resolvedSchema, 'object')) {
173+
return extractDefaults(resolvedSchema, rootSchema);
174+
} else if (hasType(resolvedSchema, 'null')) {
175+
return null;
176+
} else {
177+
return {};
178+
}
179+
};
180+
181+
/**
182+
* Returns the default value defined in the given schema.
183+
* @param schema the schema for which to create a default value.
184+
* @returns {any}
185+
*/
186+
export const extractDefaults = (schema: JsonSchema, rootSchema: JsonSchema) => {
187+
if (hasType(schema, 'object') && schema.default === undefined) {
188+
const result: { [key: string]: any } = {};
189+
for (const key in schema.properties) {
190+
const property = schema.properties[key];
191+
const resolvedProperty = property.$ref
192+
? Resolve.schema(rootSchema, property.$ref, rootSchema)
193+
: property;
194+
if (resolvedProperty.default !== undefined) {
195+
result[key] = cloneDeep(resolvedProperty.default);
154196
}
155-
return '';
156-
case 'integer':
157-
case 'number':
158-
return 0;
159-
case 'boolean':
160-
return false;
161-
case 'array':
162-
return [];
163-
case 'null':
164-
return null;
165-
default:
166-
return {};
197+
}
198+
return result;
167199
}
200+
return cloneDeep(schema.default);
168201
};
169202

170203
/**

packages/core/src/util/util.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,49 @@ import { composePaths, toDataPathSegments } from './path';
3333
import { isEnabled, isVisible } from './runtime';
3434
import type Ajv from 'ajv';
3535

36+
/**
37+
* Returns the string representation of the given date. The format of the output string can be specified:
38+
* - 'date' for a date-only string (YYYY-MM-DD),
39+
* - 'time' for a time-only string (HH:mm:ss), or
40+
* - 'date-time' for a full date-time string in ISO format (YYYY-MM-DDTHH:mm:ss.sssZ).
41+
* If no format is specified, the full date-time ISO string is returned by default.
42+
*
43+
* @returns {string} A string representation of the date in the specified format.
44+
*
45+
* @example
46+
* // returns '2023-11-09'
47+
* convertDateToString(new Date('2023-11-09T14:22:54.131Z'), 'date');
48+
*
49+
* @example
50+
* // returns '14:22:54'
51+
* convertDateToString(new Date('2023-11-09T14:22:54.131Z'), 'time');
52+
*
53+
* @example
54+
* // returns '2023-11-09T14:22:54.131Z'
55+
* convertDateToString(new Date('2023-11-09T14:22:54.131Z'), 'date-time');
56+
*
57+
* @example
58+
* // returns '2023-11-09T14:22:54.131Z'
59+
* convertDateToString(new Date('2023-11-09T14:22:54.131Z'));
60+
*/
61+
export const convertDateToString = (
62+
date: Date,
63+
format?: 'date' | 'time' | 'date-time'
64+
): string => {
65+
//e.g. '2023-11-09T14:22:54.131Z'
66+
const dateString = date.toISOString();
67+
if (format === 'date-time') {
68+
return dateString;
69+
} else if (format === 'date') {
70+
// e.g. '2023-11-09'
71+
return dateString.split('T')[0];
72+
} else if (format === 'time') {
73+
//e.g. '14:22:54'
74+
return dateString.split('T')[1].split('.')[0];
75+
}
76+
return dateString;
77+
};
78+
3679
/**
3780
* Escape the given string such that it can be used as a class name,
3881
* i.e. hashes and slashes will be replaced.

0 commit comments

Comments
 (0)