Skip to content

Commit

Permalink
feat(public forms): default values can now include dynamic values lik…
Browse files Browse the repository at this point in the history
…e current date (#2635)

closes #2401

---------
Co-authored-by: Sebastian <[email protected]>
  • Loading branch information
Abhinegi2 authored Nov 25, 2024
1 parent cfcc585 commit c02ed26
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 87 deletions.
50 changes: 26 additions & 24 deletions src/app/core/common-components/entity-form/entity-form.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { Subscription } from "rxjs";
import { filter } from "rxjs/operators";
import { EntitySchemaField } from "../../entity/schema/entity-schema-field";
import { DefaultValueService } from "../../default-values/default-value.service";
import { DefaultValueConfig } from "../../entity/schema/default-value-config";

/**
* These are utility types that allow to define the type of `FormGroup` the way it is returned by `EntityFormService.create`
Expand All @@ -35,9 +34,9 @@ export interface EntityForm<T extends Entity> {
entity: T;

/**
* map of field ids to the default value configuration for that field
* (possible overridden) field configurations for that form
*/
defaultValueConfigs: Map<string, DefaultValueConfig>;
fieldConfigs: FormFieldConfig[];

/**
* map of field ids to the current value to be inherited from the referenced parent entities' field
Expand Down Expand Up @@ -87,6 +86,7 @@ export class EntityFormService {
forTable = false,
): FormFieldConfig {
const fullField = toFormFieldConfig(formField);

try {
return this.addSchemaToFormField(
fullField,
Expand All @@ -102,11 +102,15 @@ export class EntityFormService {

private addSchemaToFormField(
formField: FormFieldConfig,
propertySchema: EntitySchemaField,
propertySchema: EntitySchemaField | undefined,
forTable: boolean,
): FormFieldConfig {
// formField config has precedence over schema
const fullField = Object.assign({}, propertySchema, formField);
const fullField = Object.assign(
{},
JSON.parse(JSON.stringify(propertySchema ?? {})), // deep copy to avoid modifying the original schema
formField,
);

fullField.editComponent =
fullField.editComponent ||
Expand Down Expand Up @@ -143,20 +147,20 @@ export class EntityFormService {
forTable = false,
withPermissionCheck = true,
): Promise<EntityForm<T>> {
const fields = formFields.map((f) =>
this.extendFormFieldConfig(f, entity.getConstructor(), forTable),
);

const typedFormGroup: TypedFormGroup<Partial<T>> = this.createFormGroup(
formFields,
fields,
entity,
forTable,
withPermissionCheck,
);

const defaultValueConfigs =
DefaultValueService.getDefaultValueConfigs(entity);

const entityForm: EntityForm<T> = {
formGroup: typedFormGroup,
entity: entity,
defaultValueConfigs: defaultValueConfigs,
fieldConfigs: fields,
inheritedParentValues: new Map(),
watcher: new Map(),
};
Expand All @@ -166,10 +170,16 @@ export class EntityFormService {
return entityForm;
}

/**
*
* @param formFields The field configs in their final form (will not be extended by schema automatically)
* @param entity
* @param withPermissionCheck
* @private
*/
private createFormGroup<T extends Entity>(
formFields: ColumnConfig[],
formFields: FormFieldConfig[],
entity: T,
forTable = false,
withPermissionCheck = true,
): EntityFormGroup<T> {
const formConfig = {};
Expand All @@ -180,7 +190,7 @@ export class EntityFormService {
);

for (const f of formFields) {
this.addFormControlConfig(formConfig, f, copy, forTable);
this.addFormControlConfig(formConfig, f, copy);
}
const group = this.fb.group<Partial<T>>(formConfig);

Expand All @@ -204,23 +214,15 @@ export class EntityFormService {
/**
* Add a property with form control initialization config to the given formConfig object.
* @param formConfig
* @param fieldConfig
* @param field The final field config (will not be automatically extended by schema)
* @param entity
* @param forTable
* @private
*/
private addFormControlConfig(
formConfig: { [key: string]: FormControl },
fieldConfig: ColumnConfig,
field: FormFieldConfig,
entity: Entity,
forTable: boolean,
) {
const field = this.extendFormFieldConfig(
fieldConfig,
entity.getConstructor(),
forTable,
);

let value = entity[field.id];

const controlOptions: FormControlOptions = { nonNullable: true };
Expand Down
11 changes: 6 additions & 5 deletions src/app/core/default-values/default-value-strategy.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { EntitySchemaField } from "../entity/schema/entity-schema-field";
import { EntityForm } from "../common-components/entity-form/entity-form.service";
import { DefaultValueConfig } from "../entity/schema/default-value-config";
import { Entity } from "../entity/model/entity";
import { FormFieldConfig } from "../common-components/entity-form/FormConfig";

/**
* A special strategy to define and set default values, which can be used by the DefaultValueService,
Expand Down Expand Up @@ -30,18 +31,18 @@ export abstract class DefaultValueStrategy {

/**
* Get the default value configs filtered for the given mode.
* @param defaultValueConfigs
* @param fieldConfigs
* @param mode
*/
export function getConfigsByMode(
defaultValueConfigs: Map<string, DefaultValueConfig>,
fieldConfigs: FormFieldConfig[],
mode: ("inherited" | "static" | "dynamic")[],
): Map<string, DefaultValueConfig> {
let configs: Map<string, DefaultValueConfig> = new Map();

for (const [key, defaultValueConfig] of defaultValueConfigs) {
if (mode.indexOf(defaultValueConfig.mode) !== -1) {
configs.set(key, defaultValueConfig);
for (const field of fieldConfigs) {
if (mode.includes(field.defaultValue?.mode)) {
configs.set(field.id, field.defaultValue);
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/app/core/default-values/default-value.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ export function getDefaultInheritedForm(

return {
entity: entity,
defaultValueConfigs: DefaultValueService.getDefaultValueConfigs(entity),
fieldConfigs: Array.from(entity.getSchema().entries()).map(
([key, fieldConfig]) => ({ id: key, ...fieldConfig }),
),
inheritedParentValues: new Map(),
watcher: new Map(),
formGroup: new FormBuilder().group<any>({
Expand All @@ -38,6 +40,7 @@ export function getDefaultInheritedForm(
}),
};
}

/**
* Helper function to remove custom schema fields from Entity
* that have been created using getDefaultInheritedForm().
Expand Down
29 changes: 15 additions & 14 deletions src/app/core/default-values/default-value.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,40 +24,38 @@ export class DefaultValueService {
form: EntityForm<T>,
entity: Entity,
): Promise<void> {
if (!(form.defaultValueConfigs?.size > 0)) {
if (!(form.fieldConfigs?.length > 0)) {
return;
}

const entitySchema: EntitySchema = entity.getSchema();
await this.inheritedValueService.initEntityForm(form);
this.enableChangeListener(form);

for (const [key, entitySchemaField] of entitySchema) {
let targetFormControl = form.formGroup.get(key);
for (const fieldConfig of form.fieldConfigs) {
let targetFormControl = form.formGroup.get(fieldConfig.id);
if (
!this.preConditionsFulfilled(
entity.isNew,
targetFormControl,
entitySchemaField,
fieldConfig,
)
) {
continue;
}

switch (entitySchemaField.defaultValue?.mode) {
switch (fieldConfig.defaultValue?.mode) {
case "static":
this.handleStaticMode(targetFormControl, entitySchemaField);
this.handleStaticMode(targetFormControl, fieldConfig);
break;
case "dynamic":
this.dynamicPlaceholderValueService.setDefaultValue(
targetFormControl,
entitySchemaField,
fieldConfig,
);
break;
case "inherited":
this.inheritedValueService.setDefaultValue(
await this.inheritedValueService.setDefaultValue(
targetFormControl,
entitySchemaField,
fieldConfig,
form,
);
break;
Expand Down Expand Up @@ -121,9 +119,12 @@ export class DefaultValueService {
return;
}

const mode = form?.defaultValueConfigs?.get(fieldId)?.mode;
if (mode === "inherited") {
return this.inheritedValueService.getDefaultValueUiHint(form, fieldId);
const fieldConfig = form?.fieldConfigs?.find((x) => x.id === fieldId);
if (fieldConfig?.defaultValue?.mode === "inherited") {
return this.inheritedValueService.getDefaultValueUiHint(
form,
fieldConfig,
);
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/app/core/default-values/inherited-value.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe("InheritedValueService", () => {
field1: new FormControl(),
}),
entity: entity,
defaultValueConfigs: new Map(),
fieldConfigs: [],
watcher: new Map(),
inheritedParentValues: new Map(),
};
Expand Down Expand Up @@ -86,7 +86,7 @@ describe("InheritedValueService", () => {
field2: new FormControl(),
}),
entity: entity,
defaultValueConfigs: new Map(),
fieldConfigs: [],
watcher: new Map(),
inheritedParentValues: new Map(),
};
Expand Down Expand Up @@ -129,7 +129,7 @@ describe("InheritedValueService", () => {
field2: new FormControl(),
}),
entity: entity,
defaultValueConfigs: new Map(),
fieldConfigs: [],
watcher: new Map(),
inheritedParentValues: new Map(),
};
Expand Down
17 changes: 9 additions & 8 deletions src/app/core/default-values/inherited-value.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { EntityMapperService } from "../entity/entity-mapper/entity-mapper.servi
import { DefaultValueConfig } from "../entity/schema/default-value-config";
import { DefaultValueHint } from "./default-value.service";
import { asArray } from "../../utils/utils";
import { FormFieldConfig } from "../common-components/entity-form/FormConfig";

/**
* An advanced default-value strategy that sets values based on the value in a referenced related entity.
Expand Down Expand Up @@ -147,13 +148,13 @@ export class InheritedValueService extends DefaultValueStrategy {
* Get details about the status and context of an inherited value field
* to display to the user.
* @param form
* @param fieldId
* @param field
*/
getDefaultValueUiHint<T extends Entity>(
form: EntityForm<T>,
fieldId: string,
field: FormFieldConfig,
): DefaultValueHint | undefined {
const defaultConfig = form?.defaultValueConfigs?.get(fieldId);
const defaultConfig = field?.defaultValue;
if (!defaultConfig) {
return;
}
Expand All @@ -172,19 +173,19 @@ export class InheritedValueService extends DefaultValueStrategy {
? Entity.extractTypeFromId(parentRefValue)
: undefined,
isInSync:
JSON.stringify(form.inheritedParentValues.get(fieldId)) ===
JSON.stringify(form.formGroup.get(fieldId)?.value),
JSON.stringify(form.inheritedParentValues.get(field.id)) ===
JSON.stringify(form.formGroup.get(field.id)?.value),
syncFromParentField: () => {
form.formGroup
.get(fieldId)
.setValue(form.inheritedParentValues.get(fieldId));
.get(field.id)
.setValue(form.inheritedParentValues.get(field.id));
},
};
}

private async updateLinkedEntities<T extends Entity>(form: EntityForm<T>) {
let inheritedConfigs: Map<string, DefaultValueConfig> = getConfigsByMode(
form.defaultValueConfigs,
form.fieldConfigs,
["inherited"],
);

Expand Down
21 changes: 19 additions & 2 deletions src/app/features/public-form/demo-public-form-generator.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injectable } from "@angular/core";
import { DemoDataGenerator } from "../../core/demo-data/demo-data-generator";
import { PublicFormConfig } from "./public-form-config";
import { PLACEHOLDERS } from "../../core/entity/schema/entity-schema-field";

@Injectable()
export class DemoPublicFormGeneratorService extends DemoDataGenerator<PublicFormConfig> {
Expand All @@ -18,8 +19,24 @@ export class DemoPublicFormGeneratorService extends DemoDataGenerator<PublicForm
form.title = $localize`Example form`;
form.description = $localize`This is a form that can be shared as a link or embedded in a website. It can be filled by users without having an account. For example you can let participants self-register their details and just review the records within Aam Digital.`;
form.entity = "Child";
form.prefilled = { status: "new" };
form.columns = [["name", "phone", "gender", "dateOfBirth", "center"]];
form.columns = [
{
fields: [
"name",
"phone",
"gender",
"dateOfBirth",
"center",
{
id: "admissionDate",
defaultValue: {
mode: "dynamic",
value: PLACEHOLDERS.NOW,
},
},
],
},
];
return [form];
}
}
5 changes: 4 additions & 1 deletion src/app/features/public-form/public-form-config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Entity } from "../../core/entity/model/entity";
import { DatabaseEntity } from "../../core/entity/database-entity.decorator";
import { DatabaseField } from "../../core/entity/database-field.decorator";
import { FieldGroup } from "app/core/entity-details/form/field-group";

@DatabaseEntity("PublicFormConfig")
export class PublicFormConfig extends Entity {
@DatabaseField() title: string;
@DatabaseField() description: string;
@DatabaseField() entity: string;
@DatabaseField() columns: string[][];
@DatabaseField() columns: FieldGroup[];

/** @deprecated use ColumnConfig directly in the columns array instead */
@DatabaseField() prefilled: { [key in string]: any };
}
Loading

0 comments on commit c02ed26

Please sign in to comment.