Skip to content

Commit 1a8a2e5

Browse files
jmaGarfield-fr
andcommitted
in progess
Co-Authored-by: Bertrand Zuchuat <[email protected]> Co-Authored-by: Johnny Mariéthoz <[email protected]>
1 parent a531250 commit 1a8a2e5

File tree

10 files changed

+288
-256
lines changed

10 files changed

+288
-256
lines changed

projects/rero/ng-core/src/lib/record/editor/editor.component.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<div class="row editor">
1919
<!-- Editor title and editor actions buttons -->
2020
<div class="header py-2 mb-3 col-12 border-bottom">
21-
@if (rootFormlyConfig) {
21+
@if (rootField) {
2222
<legend>
2323
<span [tooltip]="description">
2424
{{ title || recordType | ucfirst | translate }}

projects/rero/ng-core/src/lib/record/editor/editor.component.ts

+28-184
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717
import { Location } from '@angular/common';
1818
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
19-
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
19+
import { Form, UntypedFormGroup } from '@angular/forms';
2020
import { ActivatedRoute } from '@angular/router';
2121
import { FormlyFieldConfig, FormlyFormOptions } from '@ngx-formly/core';
2222
import { FormlyJsonschema } from '@ngx-formly/core/json-schema';
@@ -32,9 +32,9 @@ import { AbstractCanDeactivateComponent } from '../../component/abstract-can-dea
3232
import { Error } from '../../error/error';
3333
import { RouteCollectionService } from '../../route/route-collection.service';
3434
import { LoggerService } from '../../service/logger.service';
35-
import { Record } from '../record';
3635
import { RecordUiService } from '../record-ui.service';
3736
import { RecordService } from '../record.service';
37+
import { JSONSchemaService } from './services/jsonschema.service';
3838
import { processJsonSchema, removeEmptyValues, resolve$ref } from './utils';
3939
import { LoadTemplateFormComponent } from './widgets/load-template-form/load-template-form.component';
4040
import { SaveTemplateFormComponent } from './widgets/save-template-form/save-template-form.component';
@@ -83,7 +83,7 @@ export class EditorComponent extends AbstractCanDeactivateComponent implements O
8383
fields: FormlyFieldConfig[];
8484

8585
// root element of the editor
86-
rootFormlyConfig: FormlyFieldConfig;
86+
rootField: FormlyFieldConfig;
8787

8888
// list of fields to display in the TOC
8989
tocFields$: Observable<any>;
@@ -123,15 +123,6 @@ export class EditorComponent extends AbstractCanDeactivateComponent implements O
123123
// Config for resource
124124
private _resourceConfig: any;
125125

126-
// list of custom validators
127-
private _customValidators = [
128-
'valueAlreadyExists',
129-
'uniqueValueKeysInObject',
130-
'numberOfSpecificValuesInObject',
131-
'dateMustBeGreaterThan',
132-
'dateMustBeLessThan'
133-
];
134-
135126
// list of fields to be hidden
136127
private _hiddenFields: FormlyFieldConfig[] = [];
137128

@@ -148,16 +139,6 @@ export class EditorComponent extends AbstractCanDeactivateComponent implements O
148139
return this.editorSettings.longMode;
149140
}
150141

151-
// Editor edit mode
152-
public get editMode(): boolean {
153-
return this.pid ? true : false;
154-
}
155-
156-
// Editor root field
157-
public get rootField(): FormlyFieldConfig {
158-
return this.rootFormlyConfig;
159-
}
160-
161142
// Editor function
162143
public get editorComponent(): () => EditorComponent {
163144
return () => this;
@@ -188,7 +169,8 @@ export class EditorComponent extends AbstractCanDeactivateComponent implements O
188169
protected location: Location,
189170
protected modalService: BsModalService,
190171
protected routeCollectionService: RouteCollectionService,
191-
protected loggerService: LoggerService
172+
protected loggerService: LoggerService,
173+
protected jsonschemaService: JSONSchemaService
192174
) {
193175
super();
194176
this.form = new UntypedFormGroup({});
@@ -458,39 +440,23 @@ export class EditorComponent extends AbstractCanDeactivateComponent implements O
458440
this.clearHiddenFields();
459441

460442
// form configuration
443+
const editorConfig = {
444+
pid: this.pid,
445+
longMode: this.longMode,
446+
recordType: this.recordType
447+
}
461448
const fields = [
462449
this.formlyJsonschema.toFieldConfig(this.schema, {
463450
// post process JSONSChema7 to FormlyFieldConfig conversion
464451
map: (field: FormlyFieldConfig, jsonSchema: JSONSchema7) => {
465452
/**** additional JSONSchema configurations *******/
466-
467-
// initial population of arrays with a minItems constraints
468-
if (jsonSchema.minItems && !jsonSchema.hasOwnProperty('default')) {
469-
field.defaultValue = new Array(jsonSchema.minItems);
470-
}
471-
// If 'format' is defined into the jsonSchema, use it as props to try a validation on this field.
472-
// See: `email.validator.ts` file
473-
if (jsonSchema.format) {
474-
field.props.type = jsonSchema.format;
475-
}
476-
477-
if (jsonSchema?.widget?.formlyConfig) {
478-
const { props } = jsonSchema.widget.formlyConfig;
479-
480-
if (props) {
481-
this._setSimpleOptions(field, props);
482-
this._setValidation(field, props);
483-
this._setRemoteSelectOptions(field, props);
484-
this._setRemoteTypeahead(field, props);
485-
}
486-
}
487-
// Add editor component function on the field
488-
field.props.editorComponent = this.editorComponent;
489-
453+
field = this.jsonschemaService.processField(field, jsonSchema);
454+
field.props.editorConfig = editorConfig;
455+
field.props.getRoot = (() => this.rootField);
456+
field.props.setHide = ((field: FormlyFieldConfig, value: boolean) => this.setHide(field, value));
490457
if (this._resourceConfig != null && this._resourceConfig.formFieldMap) {
491458
return this._resourceConfig.formFieldMap(field, jsonSchema);
492459
}
493-
494460
return field;
495461
}
496462
})
@@ -504,7 +470,7 @@ export class EditorComponent extends AbstractCanDeactivateComponent implements O
504470
if (this.fields) {
505471
this.title = this.fields[0].props?.label;
506472
this.description = this.fields[0].props?.description;
507-
this.rootFormlyConfig = this.fields[0];
473+
this.rootField = this.fields[0];
508474
}
509475
}
510476

@@ -775,147 +741,25 @@ export class EditorComponent extends AbstractCanDeactivateComponent implements O
775741
* Hide the given formly field.
776742
* @param field - FormlyFieldConfig, the field to hide
777743
*/
778-
hide(field: FormlyFieldConfig): void {
779-
field.hide = true;
780-
if (this.isRoot(field.parent)) {
781-
this.addHiddenField(field);
744+
setHide(field: FormlyFieldConfig, value: boolean): void {
745+
if (value) {
746+
if (field.parent.props.isRoot) {
747+
this.addHiddenField(field);
748+
}
749+
} else {
750+
this.removeHiddenField(field);
751+
// scroll at the right position
752+
// to avoid: Expression has changed after it was checked
753+
// See: https://blog.angular-university.io/angular-debugging/
754+
// wait that the component is present in the DOM
755+
setTimeout(() => this.setFieldFocus(field, true));
782756
}
757+
field.hide = value;
783758
}
784759

785760
/********************* Private ***************************************/
786761

787-
/**
788-
* Populate a select options with a remote API call.
789-
* @param field formly field config
790-
* @param formOptions JSONSchema object
791-
*/
792-
private _setRemoteSelectOptions(
793-
field: FormlyFieldConfig,
794-
formOptions: any
795-
): void {
796-
if (formOptions.remoteOptions && formOptions.remoteOptions.type) {
797-
field.type = 'select';
798-
field.hooks = {
799-
...field.hooks,
800-
afterContentInit: (f: FormlyFieldConfig) => {
801-
const recordType = formOptions.remoteOptions.type;
802-
const query = formOptions.remoteOptions.query || '';
803-
f.props.options = this.recordService
804-
.getRecords(recordType, query, 1, RecordService.MAX_REST_RESULTS_SIZE)
805-
.pipe(
806-
map((data: Record) =>
807-
data.hits.hits.map((record: any) => {
808-
return {
809-
label: formOptions.remoteOptions.labelField && formOptions.remoteOptions.labelField in record.metadata
810-
? record.metadata[formOptions.remoteOptions.labelField]
811-
: record.metadata.name,
812-
value: this.apiService.getRefEndpoint(
813-
recordType,
814-
record.id
815-
)
816-
};
817-
})
818-
)
819-
);
820-
}
821-
};
822-
}
823-
}
824762

825-
/**
826-
* Store the remote typeahead options.
827-
* @param field formly field config
828-
* @param formOptions JSONSchema object
829-
*/
830-
private _setRemoteTypeahead(
831-
field: FormlyFieldConfig,
832-
formOptions: any
833-
): void {
834-
if (formOptions.remoteTypeahead && formOptions.remoteTypeahead.type) {
835-
field.type = 'remoteTypeahead';
836-
field.props = {
837-
...field.props,
838-
...{ remoteTypeahead: formOptions.remoteTypeahead }
839-
};
840-
}
841-
}
842-
843-
/**
844-
*
845-
* @param field formly field config
846-
* @param formOptions JSONSchema object
847-
*/
848-
private _setValidation(field: FormlyFieldConfig, formOptions: any): void {
849-
if (formOptions.validation) {
850-
// custom validation messages
851-
// TODO: use widget instead
852-
const { messages } = formOptions.validation;
853-
if (messages) {
854-
if (!field.validation) {
855-
field.validation = {};
856-
}
857-
if (!field.validation.messages) {
858-
field.validation.messages = {};
859-
}
860-
for (const key of Object.keys(messages)) {
861-
const msg = messages[key];
862-
// add support of key with or without Message suffix (required == requiredMessage),
863-
// this is useful for backend translation extraction
864-
field.validation.messages[key.replace(/Message$/, '')] = (error, f: FormlyFieldConfig) =>
865-
// translate the validation messages coming from the JSONSchema
866-
// TODO: need to remove `as any` once it is fixed in ngx-formly v.5.7.2
867-
this.translateService.stream(msg) as any;
868-
}
869-
}
870-
871-
// store the custom validators config
872-
field.props.customValidators = {};
873-
if (formOptions.validation && formOptions.validation.validators) {
874-
for (const customValidator of this._customValidators) {
875-
const validatorConfig = formOptions.validation.validators[customValidator];
876-
if (validatorConfig != null) {
877-
field.props.customValidators[customValidator] = validatorConfig;
878-
}
879-
}
880-
}
881-
882-
if (formOptions.validation.validators) {
883-
// validators: add validator with expressions
884-
// TODO: use widget
885-
const validatorsKey = Object.keys(formOptions.validation.validators);
886-
validatorsKey.map(validatorKey => {
887-
const validator = formOptions.validation.validators[validatorKey];
888-
if ('expression' in validator && 'message' in validator) {
889-
const { expression } = validator;
890-
const expressionFn = Function('formControl', `return ${expression};`);
891-
const validatorExpression = {
892-
expression: (fc: UntypedFormControl) => expressionFn(fc),
893-
// translate the validation message coming form the JSONSchema
894-
message: this.translateService.stream(validator.message)
895-
};
896-
field.validators = field.validators !== undefined ? field.validators : {};
897-
field.validators[validatorKey] = validatorExpression;
898-
}
899-
});
900-
}
901-
}
902-
}
903-
904-
/**
905-
* Convert JSONSchema form options to formly field options.
906-
* @param field formly field config
907-
* @param formOptions JSONSchema object
908-
*/
909-
private _setSimpleOptions(field: FormlyFieldConfig, formOptions: any): void {
910-
// some fields should not submit the form when enter key is pressed
911-
if (field.props.doNotSubmitOnEnter != null) {
912-
field.props.keydown = (f: FormlyFieldConfig, event?: any) => {
913-
if (event.key === 'Enter') {
914-
event.preventDefault();
915-
}
916-
};
917-
}
918-
}
919763

920764
/**
921765
* Handle form error

projects/rero/ng-core/src/lib/record/editor/extensions.ts

+13-14
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { isObservable, of } from 'rxjs';
2424
import { map, switchMap } from 'rxjs/operators';
2525
import { RecordService } from '../record.service';
2626
import { isEmpty, removeEmptyValues } from './utils';
27+
import { FormlyFieldConfigCache, FormlyValueChangeEvent } from '@ngx-formly/core/lib/models';
2728

2829
export class NgCoreFormlyExtension {
2930
// Types to apply horizontal wrapper on
@@ -44,8 +45,8 @@ export class NgCoreFormlyExtension {
4445
* Constructor
4546
* @params _recordService - ng core record service
4647
*/
47-
constructor(private _recordService: RecordService) {}
48-
48+
constructor(private _recordService: RecordService) {
49+
}
4950
/**
5051
* prePopulate Formly hook
5152
* @param field - FormlyFieldConfig
@@ -103,8 +104,7 @@ export class NgCoreFormlyExtension {
103104
];
104105
}
105106

106-
const editorComponent = field.props?.editorComponent;
107-
if (field.props && editorComponent && editorComponent().longMode) {
107+
if (field?.props?.editorConfig?.longMode) {
108108
// add automatically a card wrapper for the first level fields
109109
const { parent } = field;
110110
if (parent && parent.props && parent.props.isRoot === true && !field.wrappers.includes('card')) {
@@ -206,11 +206,10 @@ export class NgCoreFormlyExtension {
206206
* @param field - FormlyFieldConfig
207207
*/
208208
private _hideEmptyField(field: FormlyFieldConfig): void {
209-
// find the root field in the form tree
210-
if (!field.props?.editorComponent) {
209+
if (!field.props?.editorConfig) {
211210
return;
212211
}
213-
const {rootField, editMode, longMode} = field.props.editorComponent();
212+
const {pid, longMode} = field.props?.editorConfig;
214213
if (
215214
// only in longMode else it will not be possible to unhide a field
216215
!longMode
@@ -241,11 +240,11 @@ export class NgCoreFormlyExtension {
241240
// do not hide field has been already manipulated
242241
&& field.hide === undefined)
243242
// in edition empty fields should be hidden
244-
|| (editMode === true
243+
|| (pid != null
245244
// only during the editor initialization
246-
&& !rootField?.formControl?.touched)
245+
&& !field?.props?.getRoot()?.formControl?.touched)
247246
) {
248-
field.props.editorComponent().hide(field);
247+
field.props.setHide ? field.props.setHide(field, true): field.hide = true;
249248
}
250249
}
251250
}
@@ -261,15 +260,14 @@ export class NgCoreFormlyExtension {
261260
const customValidators = field.props.customValidators ? field.props.customValidators : {};
262261
// asyncValidators: valueAlreadyExists
263262
if (customValidators.valueAlreadyExists) {
264-
const { filter, limitToValues, remoteRecordType, term } = customValidators.valueAlreadyExists;
265-
const { editorComponent } = field.props;
263+
const { filter, limitToValues, term } = customValidators.valueAlreadyExists;
266264
field.asyncValidators = {
267265
validation: [
268266
(control: UntypedFormControl) => {
269267
return this._recordService.uniqueValue(
270268
field,
271-
remoteRecordType ? remoteRecordType : editorComponent().recordType,
272-
editorComponent().pid,
269+
field.props.editorConfig.recordType,
270+
field.props.editorConfig.pid,
273271
term ? term : null,
274272
limitToValues ? limitToValues : [],
275273
filter ? filter : null
@@ -413,6 +411,7 @@ export class TranslateExtension implements FormlyExtension {
413411
* It translates the label, the description and the placeholder.
414412
* @param field formly field config
415413
*/
414+
416415
prePopulate(field: FormlyFieldConfig): void {
417416
const props = field.props || {};
418417

0 commit comments

Comments
 (0)