From 3c53cb37cb108552f4d6e3a85062f45e4509ad35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johnny=20Marie=CC=81thoz?= Date: Wed, 16 Dec 2020 17:37:34 +0100 Subject: [PATCH] editor: fix issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixes tester document search routing. * Removes useless log messages. * Fixes import paths. * Replaces the css classes such as `editor-title` by a `card` wrapper. * Moves some common code lines in the editor service. * Moves the field label html code into a specific component. * Adds new `containerCSSClass`, `itemCSSClass` and `cssClass` to allow field grid positions. * Renders the hide/show/clone button in the children field instead of the parent (array, object) component. * Adds external link support for remote typeahead editor component. * Closes rero/ng-core#328. * Closes rero/ng-core#327. * Closes rero/ng-core#325. * Closes rero/ng-core#248. * Closes rero/ng-core#242. * Closes rero/rero-ils#1604. * Closes rero/rero-ils#1601. Co-Authored-by: Johnny MarieĢthoz --- package-lock.json | 12 +- package.json | 4 +- .../src/app/app-routing.module.ts | 13 +- .../src/app/record/editor/editor.component.ts | 5 +- .../src/app/record/editor/schema.json | 298 ++++++++++-------- .../src/app/routes/documents-route.ts | 6 +- .../ng-core/src/lib/menu/menu-factory.spec.ts | 1 - .../record/detail/detail.component.spec.ts | 2 +- .../lib/record/editor/editor.component.html | 4 +- .../lib/record/editor/editor.component.scss | 64 +--- .../src/lib/record/editor/editor.component.ts | 122 ++----- .../src/lib/record/editor/extensions.ts | 199 ++++++++---- .../record/editor/services/editor.service.ts | 43 ++- .../type/array-type/array-type.component.html | 117 +------ .../type/array-type/array-type.component.ts | 62 +--- .../type/multischema/multischema.component.ts | 6 +- .../object-type/object-type.component.html | 79 +---- .../type/object-type/object-type.component.ts | 115 +------ .../remote-typeahead.component.html | 25 +- .../remote-typeahead.component.ts | 1 + .../add-field-editor.component.html | 0 .../add-field-editor.component.spec.ts | 0 .../add-field-editor.component.ts | 2 +- .../dropdown-label-editor.component.html | 12 +- .../dropdown-label-editor.component.ts | 10 + .../editor/widgets/label/label.component.html | 86 +++++ .../widgets/label/label.component.spec.ts | 44 +++ .../editor/widgets/label/label.component.ts | 200 ++++++++++++ .../card-wrapper/card-wrapper.component.ts | 51 +++ .../horizontal-wrapper.component.ts | 95 +++++- .../ng-core/src/lib/record/record.module.ts | 22 +- .../buckets/buckets.component.html | 2 +- .../search/record-search.component.html | 2 +- projects/rero/ng-core/src/public-api.ts | 4 +- 34 files changed, 975 insertions(+), 733 deletions(-) rename projects/rero/ng-core/src/lib/record/editor/{ => widgets}/add-field-editor/add-field-editor.component.html (100%) rename projects/rero/ng-core/src/lib/record/editor/{ => widgets}/add-field-editor/add-field-editor.component.spec.ts (100%) rename projects/rero/ng-core/src/lib/record/editor/{ => widgets}/add-field-editor/add-field-editor.component.ts (98%) rename projects/rero/ng-core/src/lib/record/editor/{ => widgets}/dropdown-label-editor/dropdown-label-editor.component.html (86%) rename projects/rero/ng-core/src/lib/record/editor/{ => widgets}/dropdown-label-editor/dropdown-label-editor.component.ts (86%) create mode 100644 projects/rero/ng-core/src/lib/record/editor/widgets/label/label.component.html create mode 100644 projects/rero/ng-core/src/lib/record/editor/widgets/label/label.component.spec.ts create mode 100644 projects/rero/ng-core/src/lib/record/editor/widgets/label/label.component.ts create mode 100644 projects/rero/ng-core/src/lib/record/editor/wrappers/card-wrapper/card-wrapper.component.ts diff --git a/package-lock.json b/package-lock.json index a89bdfa6f..d3695f46d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1954,9 +1954,9 @@ } }, "@ngx-formly/bootstrap": { - "version": "5.10.11", - "resolved": "https://registry.npmjs.org/@ngx-formly/bootstrap/-/bootstrap-5.10.11.tgz", - "integrity": "sha512-BOhf7lxiqMR5RGG+RUOf7EVZS7M5h+vQ5rcjpMFlcwe+jdYcoCqGH7NzUB+0/l6SXeDVvNMRndEVs2ObLSpHTg==", + "version": "5.10.12", + "resolved": "https://registry.npmjs.org/@ngx-formly/bootstrap/-/bootstrap-5.10.12.tgz", + "integrity": "sha512-8cbVVA80UF9rr2rXhqWvfK99ytuYzAPd9SY7pxzJEoed/KRaZXS6a36j/JgsqKKR1O6BO59WOuFDTyA6Cir1JA==", "requires": { "tslib": "^1.9.0" }, @@ -1969,9 +1969,9 @@ } }, "@ngx-formly/core": { - "version": "5.10.11", - "resolved": "https://registry.npmjs.org/@ngx-formly/core/-/core-5.10.11.tgz", - "integrity": "sha512-vi+UZC+eSnPQjwyMfZs42Zl8TucAedtpNC5YV44AknFNvdPqWU/ZXGi1zVXWWAcSS/0sIbfFyUrSlW++zW/b6A==", + "version": "5.10.12", + "resolved": "https://registry.npmjs.org/@ngx-formly/core/-/core-5.10.12.tgz", + "integrity": "sha512-fv9tBjjXZnqqzUim03vta6xIXEUi2cKL80N1xk7t7BLurlygfNgPQQaeTb1hJw1grfQfLAEg6noxVFnvLo8QpQ==", "requires": { "tslib": "^1.7.1" }, diff --git a/package.json b/package.json index fd6287b35..8febf470a 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "@angular/router": "^11.0.9", "@biesbjerg/ngx-translate-extract": "^7.0.3", "@biesbjerg/ngx-translate-extract-marker": "^1.0.0", - "@ngx-formly/bootstrap": "^5.10.11", - "@ngx-formly/core": "^5.10.11", + "@ngx-formly/bootstrap": "^5.10.12", + "@ngx-formly/core": "^5.10.12", "@ngx-translate/core": "^13.0.0", "@ngx-translate/http-loader": "^6.0.0", "bootstrap": "^4.5.3", diff --git a/projects/ng-core-tester/src/app/app-routing.module.ts b/projects/ng-core-tester/src/app/app-routing.module.ts index 9665ff4c5..d9e283a93 100644 --- a/projects/ng-core-tester/src/app/app-routing.module.ts +++ b/projects/ng-core-tester/src/app/app-routing.module.ts @@ -104,21 +104,17 @@ const canUpdateFilesMetadata = (): Observable => { // Permissions override simple canRead, canUpdate and canDelete if defined const permissions = (record: any) => { const perms = record.metadata.permissions; - perms.read = true; - perms.update = true; - perms.delete = false; - return of({ canRead: { - can: perms.read, + can: perms ? perms.read : true, message: '', }, canUpdate: { - can: perms.update, + can: perms ? perms.update : false, message: '', }, canDelete: { - can: perms.delete, + can: perms ? perms.delete : false, message: 'This record cannot be deleted.', }, }); @@ -226,6 +222,9 @@ const routes: Routes = [ { key: 'documents', label: 'Documents', + editorSettings: { + longMode: true + }, component: DocumentComponent } ] diff --git a/projects/ng-core-tester/src/app/record/editor/editor.component.ts b/projects/ng-core-tester/src/app/record/editor/editor.component.ts index 39e1ccbbb..c11d02d52 100644 --- a/projects/ng-core-tester/src/app/record/editor/editor.component.ts +++ b/projects/ng-core-tester/src/app/record/editor/editor.component.ts @@ -32,10 +32,13 @@ export class EditorComponent { } }; + // TODO: + // hide as expression + // JSONSchema schema = {}; - // form intial values + // form initial values model = {}; /** Constructor */ diff --git a/projects/ng-core-tester/src/app/record/editor/schema.json b/projects/ng-core-tester/src/app/record/editor/schema.json index e92194348..9f68fad1a 100644 --- a/projects/ng-core-tester/src/app/record/editor/schema.json +++ b/projects/ng-core-tester/src/app/record/editor/schema.json @@ -14,12 +14,14 @@ "object", "object_of_object", "array", + "array_min_items", + "array_of_objects", "array_of_array", "array_of_object_of_object", "hidden_with_default", "readonly_changeable", - "simple_not_required", - "hide_expression_cannot_be_added", + "hide_with_required_expression_control", + "hide_with_required_expression", "hidden_sub_property_hide_expr", "array_of_objects_with_sub_objects" ], @@ -46,20 +48,22 @@ "name": { "title": "Name", "type": "string", + "minLength": 3, "form": { "templateOptions": { - "cssClass": "col-lg-6" + "itemCssClass": "col-lg-6" } } }, "hidden": { "title": "Hidden name", "type": "string", + "minLength": 3, "form": { + "templateOptions": { + "itemCssClass": "col-lg-6" + }, "hide": true - }, - "templateOptions": { - "cssClass": "col-lg-6" } }, "type": { @@ -70,45 +74,65 @@ "readOnly": true, "form": { "templateOptions": { - "cssClass": "col-lg-6" + "wrappers": [ + "hide" + ] } } } }, "form": { "templateOptions": { - "cssClass": "row" + "containerCssClass": "row" } } }, { "title": "Property 2", - "required": [ - "name" - ], "properties": { "name": { "title": "Name", - "type": "string" + "type": "string", + "minLength": 3, + "form": { + "templateOptions": { + "itemCssClass": "col-lg-6" + } + } }, "type": { "title": "Type", "type": "string", "const": "type2", "default": "type2", - "readOnly": true + "readOnly": true, + "form": { + "templateOptions": { + "itemCssClass": "col-lg-6" + } + } }, "bool": { "title": "boolean", "type": "boolean", - "default": true + "default": true, + "form": { + "templateOptions": { + "itemCssClass": "col-lg-6" + } + } + } + }, + "form": { + "templateOptions": { + "containerCssClass": "row" } } } ], "form": { "templateOptions": { - "cssClass": "editor-title" + "cssClass": "border border-info rounded p-2" } } }, @@ -117,7 +141,7 @@ "description": "Not present in the form, not present in the model.", "type": "string", "default": "default", - "minLength": 1 + "minLength": 3 }, "hidden": { "title": "Hidden", @@ -132,15 +156,19 @@ "description": "Hidden field with a default value.", "type": "string", "default": "default value", - "minLength": 1, + "minLength": 3, "form": { - "hide": true + "templateOptions": { + "wrappers": [ + "hide" + ] + } } }, "essential": { "title": "Essential", "type": "string", - "minLength": 1, + "minLength": 3, "form": { "hide": true, "navigation": { @@ -151,21 +179,22 @@ "optional": { "title": "Optional", "type": "string", - "minLength": 1 - }, - "required": { - "title": "Required", - "type": "string", "minLength": 3, "form": { "templateOptions": { - "cssClass": "editor-title editor-max-width" + "cssClass": "w-md-50" } } }, + "required": { + "title": "Required", + "type": "string", + "minLength": 3 + }, "enum": { "title": "List of values", "type": "string", + "minLength": 3, "default": "val1", "enum": [ "val1", @@ -173,21 +202,18 @@ "val3" ], "form": { - "templateOptions": { - "cssClass": "editor-title editor-max-width" - }, "options": [ { - "value": "Value 1", - "label": "val1" + "value": "val1", + "label": "Value 1" }, { - "value": "Value 2", - "label": "val2" + "value": "val2", + "label": "Value 2" }, { - "value": "Value 3", - "label": "val3" + "value": "val3", + "label": "Value 3" } ] } @@ -206,16 +232,29 @@ "properties": { "prop1": { "title": "Property 1", - "type": "string" + "type": "string", + "minLength": 3, + "form": { + "templateOptions": { + "itemCssClass": "col-md-6" + } + } }, "prop2": { "title": "Property 2", - "type": "string" + "type": "string", + "minLength": 3, + "form": { + "templateOptions": { + "itemCssClass": "col-md-6" + } + } } }, "form": { "templateOptions": { - "cssClass": "editor-title" + "containerCssClass": "row", + "hideLabel": true } } }, @@ -242,27 +281,64 @@ "properties": { "prop1": { "title": "Property 1", - "type": "string" + "type": "string", + "minLength": 3 }, "prop2": { "title": "Property 2", - "type": "string" + "type": "string", + "minLength": 3 } } }, "prop3": { "title": "Property 3", - "type": "string" + "type": "string", + "minLength": 3 + } + } + }, + "array": { + "title": "Array", + "type": "array", + "items": { + "title": "Values", + "type": "string", + "minLength": 3, + "form": { + "templateOptions": { + "itemCssClass": "col-lg-6" + } } }, "form": { "templateOptions": { - "cssClass": "editor-title" + "containerCssClass": "row" } } }, - "array": { - "title": "Array", + "array_min_items": { + "title": "Array with a minimum of 2 items", + "type": "array", + "minItems": 2, + "items": { + "title": "Values", + "type": "string", + "minLength": 3, + "form": { + "templateOptions": { + "itemCssClass": "col-lg-6" + } + } + }, + "form": { + "templateOptions": { + "containerCssClass": "row" + } + } + }, + "array_of_objects": { + "title": "Array of Objects", "type": "array", "minItems": 1, "items": { @@ -280,33 +356,30 @@ "prop1": { "title": "Property 1", "type": "string", + "minLength": 3, "form": { "templateOptions": { - "cssClass": "col" + "itemCssClass": "col-6" } } }, "prop2": { "title": "Property 2", "type": "string", + "minLength": 3, "form": { "hide": true, "templateOptions": { - "cssClass": "col-lg-6" + "itemCssClass": "col-6" } } } }, "form": { "templateOptions": { - "cssClass": "row" + "containerCssClass": "row" } } - }, - "form": { - "templateOptions": { - "cssClass": "editor-title" - } } }, "array_of_array": { @@ -329,18 +402,10 @@ "items": { "title": "Values", "type": "string", - "minLength": 1 - }, - "form": { - "templateOptions": { - "cssClass": "row" - } + "minLength": 3 } }, "form": { - "templateOptions": { - "cssClass": "editor-title" - }, "navigation": { "essential": true } @@ -373,46 +438,33 @@ "properties": { "prop1": { "title": "Property 1", - "type": "string" + "type": "string", + "minLength": 3 }, "prop2": { "title": "Property 2", - "type": "string" + "type": "string", + "minLength": 3 } } }, "prop3": { "title": "Property 3", - "type": "string" - } - }, - "form": { - "templateOptions": { - "cssClass": "row" + "type": "string", + "minLength": 3 } } - }, - "form": { - "templateOptions": { - "cssClass": "editor-title" - } } }, "hidden_with_default": { "title": "[issue] Hidden with default", "description": "Hidden field with a default value must not be added to the form.", "type": "string", - "default": "Value 1", - "enum": [ - "Value 1" - ], + "default": "Default hidden", "form": { "hide": true, "navigation": { "essential": true - }, - "templateOptions": { - "cssClass": "editor-title" } } }, @@ -425,50 +477,43 @@ "enum": [ "Value 1", "Value 2" - ], - "form": { - "templateOptions": { - "cssClass": "editor-title" - } - } + ] }, - "simple_not_required": { - "title": "[issue] Simple element of list are not required.", - "description": "Simple element must not pass the validation if they are required and empty.", - "type": "array", - "minItems": 1, - "items": { - "title": "Item", - "type": "string", - "pattern": "^.+$", - "minLength": 1 - }, + "hide_with_required_expression_control": { + "title": "Hidden Property with required expression control", + "type": "string", + "minLength": 3, + "default": "optional", + "enum": [ + "optional", + "required" + ], "form": { "templateOptions": { - "cssClass": "editor-title" + "cssClass": "w-md-25" } } }, - "hide_expression_cannot_be_added": { - "title": "[issue] Property with hide expression.", - "description": "Property with hide expression cannot be added.", + "hide_with_required_expression": { + "title": "Hidden Property with required expression.", + "description": "Hidden Property should be shown when it becomes required.", "type": "string", - "minLength": 1, + "minLength": 3, "form": { - "hideExpression": "true", - "navigation": { - "essential": true - }, - "templateOptions": { - "cssClass": "editor-title" + "hide": true, + "expressionProperties": { + "templateOptions.required": "field.parent.model.hide_with_required_expression_control === 'required'" } } }, "hidden_sub_property_hide_expr": { - "title": "[issue] Hidden sub-property with hide expression.", + "title": "Hidden sub-property with hide expression.", "description": "Hidden sub-property with hide expression can be added to the form.", "type": "object", "additionalProperties": false, + "required": [ + "statement" + ], "properties": { "statement": { "title": "Statements", @@ -477,24 +522,28 @@ "items": { "title": "Statement", "type": "string", - "minLength": 1 - }, + "minLength": 3 + } + }, + "prop": { + "title": "Hidden Property", + "type": "string", + "minLength": 3, "form": { - "hideExpresssion": "true" + "hide": true, + "expressionProperties": { + "templateOptions.required": "field.parent.parent.model.hide_with_required_expression_control === 'required'" + } } } }, "propertiesOrder": [ - "statement" - ], - "form": { - "templateOptions": { - "cssClass": "editor-title" - } - } + "statement", + "prop" + ] }, "array_of_objects_with_sub_objects": { - "title": "[issue] Array of objects containing sub-objects", + "title": "Array of objects containing sub-objects", "description": "Array of objects containing sub-objects, which label is not displayed", "type": "array", "minItems": 1, @@ -511,7 +560,7 @@ "subprop1": { "title": "Sub property 1", "type": "string", - "minLength": 1 + "minLength": 3 } }, "required": [ @@ -521,18 +570,13 @@ "prop2": { "title": "Property 2", "type": "string", - "minLength": 1 + "minLength": 3 } }, "required": [ "prop1", "prop2" ] - }, - "form": { - "templateOptions": { - "cssClass": "editor-title" - } } } } diff --git a/projects/ng-core-tester/src/app/routes/documents-route.ts b/projects/ng-core-tester/src/app/routes/documents-route.ts index 7c263244e..50ad938ff 100644 --- a/projects/ng-core-tester/src/app/routes/documents-route.ts +++ b/projects/ng-core-tester/src/app/routes/documents-route.ts @@ -20,6 +20,7 @@ import { ActionStatus, DetailComponent as RecordDetailComponent, EditorComponent, JSONSchema7, RecordSearchComponent, + RecordSearchPageComponent, RouteInterface } from '@rero/ng-core'; import { Observable, of } from 'rxjs'; @@ -42,7 +43,7 @@ export class DocumentsRoute implements RouteInterface { return { path: 'record/search', children: [ - { path: ':type', component: RecordSearchComponent }, + { path: ':type', component: RecordSearchPageComponent }, { path: ':type/new', component: EditorComponent }, { path: ':type/edit/:pid', component: EditorComponent }, { path: ':type/detail/:pid', component: RecordDetailComponent } @@ -55,6 +56,9 @@ export class DocumentsRoute implements RouteInterface { label: 'Documents', component: DocumentComponent, detailComponent: DetailComponent, + editorSettings: { + longMode: true + }, aggregationsOrder: [ 'document_type', 'author', diff --git a/projects/rero/ng-core/src/lib/menu/menu-factory.spec.ts b/projects/rero/ng-core/src/lib/menu/menu-factory.spec.ts index 77b7b782d..96cb0ebbf 100644 --- a/projects/rero/ng-core/src/lib/menu/menu-factory.spec.ts +++ b/projects/rero/ng-core/src/lib/menu/menu-factory.spec.ts @@ -44,7 +44,6 @@ describe('MenuFactory', () => { }); it('should add an extension', () => { - console.log(menuFactory.getExtensions()); expect(Object.keys(menuFactory.getExtensions()).length).toEqual(1); menuFactory.addExtension(new ExtensionMock(), 1); expect(Object.keys(menuFactory.getExtensions()).length).toEqual(2); diff --git a/projects/rero/ng-core/src/lib/record/detail/detail.component.spec.ts b/projects/rero/ng-core/src/lib/record/detail/detail.component.spec.ts index b46185fa4..55fb747a3 100644 --- a/projects/rero/ng-core/src/lib/record/detail/detail.component.spec.ts +++ b/projects/rero/ng-core/src/lib/record/detail/detail.component.spec.ts @@ -20,8 +20,8 @@ import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/t import { ActivatedRoute, convertToParamMap, ParamMap, Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { ActionStatus } from '@rero/ng-core/public-api'; import { BehaviorSubject, Observable, of, throwError } from 'rxjs'; +import { ActionStatus } from '../action-status'; import { RecordModule } from '../record.module'; import { RecordService } from '../record.service'; import { DetailComponent } from './detail.component'; diff --git a/projects/rero/ng-core/src/lib/record/editor/editor.component.html b/projects/rero/ng-core/src/lib/record/editor/editor.component.html index c6f1f7db8..2f82f5eed 100644 --- a/projects/rero/ng-core/src/lib/record/editor/editor.component.html +++ b/projects/rero/ng-core/src/lib/record/editor/editor.component.html @@ -82,7 +82,7 @@
@@ -95,7 +95,7 @@ > diff --git a/projects/rero/ng-core/src/lib/record/editor/editor.component.scss b/projects/rero/ng-core/src/lib/record/editor/editor.component.scss index ccf18d520..bea3e24ec 100644 --- a/projects/rero/ng-core/src/lib/record/editor/editor.component.scss +++ b/projects/rero/ng-core/src/lib/record/editor/editor.component.scss @@ -17,6 +17,20 @@ @import "node_modules/bootstrap/scss/functions"; @import "node_modules/bootstrap/scss/mixins"; @import "node_modules/bootstrap/scss/variables"; + +// add w-xx-100, w-xx-50 for bootstrap +// TODO: add to global +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + @each $prop, $abbrev in (width: w, height: h) { + @each $size, $length in $sizes { + .#{$abbrev}#{$infix}-#{$size} { #{$prop}: $length !important; } + } + } + } +} + .editor { > .header { @@ -176,57 +190,9 @@ color: rgba(0, 0, 0, 0.85); background-color: transparent; } - .content, - .header { - margin-left: map-get($spacers, 3); - } - - .editor-title { - position: relative; - display: flex; - flex-direction: column; - min-width: 0; // See https://github.com/twbs/bootstrap/pull/22740#issuecomment-305868106 - height: $card-height; - word-wrap: break-word; - background-color: $card-bg; - background-clip: border-box; - border: $card-border-width solid $card-border-color; - margin-bottom: map-get($spacers, 2); - @include border-radius($card-border-radius); - > .header { - padding: map-get($spacers, 1) map-get($spacers, 3); - margin-bottom: 0 !important; // Removes the default margin-bottom of - margin-left: 0 !important; // Removes the default margin-bottom of - color: $card-cap-color; - background-color: $card-cap-bg; - border-bottom: $card-border-width solid $card-border-color; - - &:first-child { - @include border-radius( - $card-inner-border-radius $card-inner-border-radius 0 0 - ); - } - } - > .header > label { - margin-bottom: 0; - } - > .content { - margin-bottom: 0 !important; - margin-left: 0 !important; - padding: map-get($spacers, 2) map-get($spacers, 3); - } - .header:hover ~ .content { - @include border-radius($card-border-radius); - } - } .header:hover ~ .content { - background-color: $gray-100; + background-color: $gray-200; } - .editor-max-width > div { - @include media-breakpoint-up(lg) { - max-width: 500px; - } - } } diff --git a/projects/rero/ng-core/src/lib/record/editor/editor.component.ts b/projects/rero/ng-core/src/lib/record/editor/editor.component.ts index f7ed0ede1..1b7a88270 100644 --- a/projects/rero/ng-core/src/lib/record/editor/editor.component.ts +++ b/projects/rero/ng-core/src/lib/record/editor/editor.component.ts @@ -15,7 +15,6 @@ * along with this program. If not, see . */ import { Location } from '@angular/common'; -import { HttpErrorResponse } from '@angular/common/http'; import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; @@ -29,7 +28,7 @@ import { BsModalService } from 'ngx-bootstrap/modal'; import { NgxSpinnerService } from 'ngx-spinner'; import { ToastrService } from 'ngx-toastr'; import { combineLatest, Observable, of, Subscription, throwError } from 'rxjs'; -import { catchError, map, switchMap } from 'rxjs/operators'; +import { catchError, debounceTime, map, switchMap } from 'rxjs/operators'; import { ApiService } from '../../api/api.service'; import { Error } from '../../error/error'; import { RouteCollectionService } from '../../route/route-collection.service'; @@ -37,7 +36,7 @@ import { Record } from '../record'; import { RecordUiService } from '../record-ui.service'; import { RecordService } from '../record.service'; import { EditorService } from './services/editor.service'; -import { isEmpty, orderedJsonSchema, removeEmptyValues } from './utils'; +import { orderedJsonSchema, removeEmptyValues } from './utils'; import { LoadTemplateFormComponent } from './widgets/load-template-form/load-template-form.component'; import { SaveTemplateFormComponent } from './widgets/save-template-form/save-template-form.component'; @@ -76,7 +75,7 @@ export class EditorComponent implements OnInit, OnChanges, OnDestroy { rootFomlyConfig: FormlyFieldConfig; // list of fields to display in the TOC - tocFields = []; + tocFields$: Observable; // JSONSchema @Input() schema: any; @@ -110,25 +109,6 @@ export class EditorComponent implements OnInit, OnChanges, OnDestroy { // Config for resource private _resourceConfig: any; - // Types to apply horizontal wrapper on - private _horizontalWrapperTypes = [ - 'enum', - 'string', - 'remoteTypeahead', - 'selectWithSort', - 'integer', - 'textarea', - 'datepicker' - ]; - - // Types to apply field wrapper on - private _fieldWrapperTypes = [ - 'boolean', - 'datepicker', - 'remoteTypeahead', - 'selectWithSort' - ]; - /** * Constructor. * @param _formlyJsonschema Formly JSON schema. @@ -184,10 +164,25 @@ export class EditorComponent implements OnInit, OnChanges, OnDestroy { * Component initialisation */ ngOnInit() { - this._subscribers.push( - this._editorService.hiddenFields$.subscribe(() => - this.getTocFields() - ) + this.tocFields$ = this._editorService.hiddenFields$.pipe( + debounceTime(300), + switchMap(() => { + if (this.fields && this.fields.length > 0) { + return of( + this.fields[0].fieldGroup.filter(f => { + // hidden properties should not be in the navigation + if (f.hide === true) { + return false; + } + // properties with hide wrapper should not be in the navigation + if (f.wrappers && f.wrappers.some(w => w === 'hide')) { + return false; + } + return true; + })); + } + return of([]); + }) ); combineLatest([this._route.params, this._route.queryParams]).subscribe( ([params, queryParams]) => { @@ -420,30 +415,6 @@ export class EditorComponent implements OnInit, OnChanges, OnDestroy { this._setRemoteTypeahead(field, formOptions); } - if (this.editorSettings.longMode === true) { - // show the field if the model contains a value useful for edition - field.hooks = { - ...field.hooks, - onPopulate: (f: any) => { - this.hideShowEmptyField(f); - } - }; - // Add an horizontal wrapper - if (this._horizontalWrapperTypes.some(elem => elem === field.type)) { - field.wrappers = [ - ...(field.wrappers ? field.wrappers : []), - 'form-field-horizontal' - ]; - } - } else { - if (this._fieldWrapperTypes.some(elem => elem === field.type)) { - field.wrappers = [ - ...(field.wrappers ? field.wrappers : []), - 'form-field' - ]; - } - } - field.templateOptions.longMode = this.editorSettings.longMode; if (this._resourceConfig != null && this._resourceConfig.formFieldMap) { @@ -454,6 +425,8 @@ export class EditorComponent implements OnInit, OnChanges, OnDestroy { } }) ]; + // mark the root field + fields[0].templateOptions.isRoot = true; this.fields = fields; // set root element if (this.fields) { @@ -461,35 +434,6 @@ export class EditorComponent implements OnInit, OnChanges, OnDestroy { } } - /** - * Hide of show the field depending on the model value. - * @param field formly field config - */ - private hideShowEmptyField(field: FormlyFieldConfig) { - let model = field.model; - // for simple object the model is the parent dict - if ( - field.model != null && !['object', 'multischema', 'array'].some(f => f === field.type) - ) { - // New from ngx-formly v5.9.0 - model = field.model[Array.isArray(field.key) ? field.key[0] : field.key]; - } - model = removeEmptyValues(model); - const modelEmpty = isEmpty(model); - if (!modelEmpty && (field.templateOptions.hide === true)) { - setTimeout(() => { - field.hide = false; - this._editorService.removeHiddenField(field); - }); - } - if (modelEmpty && (field.templateOptions.hide === true && field.hide === undefined)) { - setTimeout(() => { - field.hide = true; - this._editorService.addHiddenField(field); - }); - } - } - /** * Save the data on the server. */ @@ -607,15 +551,6 @@ export class EditorComponent implements OnInit, OnChanges, OnDestroy { this._editorService.setFocus(field, true); } - /** - * Populate the field to add to the TOC - */ - getTocFields() { - if (this.fields && this.fields.length > 0) { - this.tocFields = this.fields[0].fieldGroup.filter(f => f.hide !== true); - } - } - /** * Cancel editing and back to previous page */ @@ -877,13 +812,6 @@ export class EditorComponent implements OnInit, OnChanges, OnDestroy { if (formOptions.hide === true) { field.templateOptions.hide = true; } - // wrappers - if (formOptions.wrappers && formOptions.wrappers.length > 0) { - field.wrappers = [ - ...(field.wrappers ? field.wrappers : []), - ...formOptions.wrappers - ]; - } // custom type if (formOptions.type != null) { field.type = formOptions.type; @@ -912,7 +840,6 @@ export class EditorComponent implements OnInit, OnChanges, OnDestroy { if (formOptions.hideExpression) { field.hideExpression = formOptions.hideExpression; } - // non ngx formly options // custom help URL displayed in the object dropdown if (formOptions.helpURL) { @@ -922,7 +849,6 @@ export class EditorComponent implements OnInit, OnChanges, OnDestroy { if (formOptions.navigation) { field.templateOptions.navigation = formOptions.navigation; } - // template options if (formOptions.templateOptions) { field.templateOptions = { diff --git a/projects/rero/ng-core/src/lib/record/editor/extensions.ts b/projects/rero/ng-core/src/lib/record/editor/extensions.ts index 2fd4aea23..d74c57ba5 100644 --- a/projects/rero/ng-core/src/lib/record/editor/extensions.ts +++ b/projects/rero/ng-core/src/lib/record/editor/extensions.ts @@ -15,76 +15,166 @@ * along with this program. If not, see . */ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { FormlyExtension, FormlyFieldConfig } from '@ngx-formly/core'; +import { FormlyFieldConfig } from '@ngx-formly/core'; import { TranslateService } from '@ngx-translate/core'; import sha256 from 'crypto-js/sha256'; import { BehaviorSubject, isObservable } from 'rxjs'; +import { EditorService } from './services/editor.service'; +import { isEmpty, removeEmptyValues } from './utils'; -/** - * Add onPopulate hook at the field level. - * - * This is a function because of https://github.com/ngx-formly/ngx-formly/issues/1624. - * @param field formly field config - */ -export function onPopulateHook(field: FormlyFieldConfig) { - if (field.hooks && field.hooks.onPopulate) { - field.hooks.onPopulate(field); - } -} -export const hooksFormlyExtension: FormlyExtension = { +export class NgCoreFormlyExtension { + + // Types to apply horizontal wrapper on + private _horizontalWrapperTypes = [ + 'enum', + 'string', + 'remoteTypeahead', + 'selectWithSort', + 'integer', + 'textarea', + 'datepicker' + ]; + + // Types to apply field wrapper on + private _fieldWrapperTypes = [ + 'boolean', + 'datepicker', + 'remoteTypeahead', + 'selectWithSort' + ]; /** - * Call the corresponding field hook. - * - * Apply when the DefaultOptions, Model, formControl are set (which is suitable to update the child element). - * See: https://github.com/ngx-formly/ngx-formly/issues/1109 for more detail. - * @param field formly field config + * Constructor + * @param _editorService - editor service */ - onPopulate: onPopulateHook -}; + constructor(private _editorService: EditorService) { } -/** - * Add a custom id before populating the form - * @param field formly field config - */ -export function prePopulateFieldIdGenerator(field: FormlyFieldConfig) { - if (field.key) { - field.id = getKey(field); + /** + * prePopulate Formly hook + * @param field - FormlyFieldConfig + */ + prePopulate(field: FormlyFieldConfig) { + if (field.key) { + field.id = this._getKey(field); + } } - } -/** - * Call the field id generator - */ -export const fieldIdGenerator: FormlyExtension = { - prePopulate: prePopulateFieldIdGenerator -}; + /** + * onPopulate Formly hook + * @param field - FormlyFieldConfig + */ + onPopulate(field: FormlyFieldConfig) { + this._hideShowEmptyField(field); + this._setWrappers(field); + field.options.fieldChanges.subscribe(changes => { + if (changes.type === 'expressionChanges' && changes.property === 'templateOptions.required' && changes.value === true) { + if (changes.field.hide === true) { + changes.field.hide = false; + } + } + }); + } -/** - * Create an id html attribute by joining field and parents keys - * @param field formly field config - * @returns array - keys to create the id html attribute - */ -export function getKey(field: any): string { - let parentKey = null; - const keyParams = []; + /** + * Add formly wrappers + * @param field - FormlyFieldConfig + */ + private _setWrappers(field: FormlyFieldConfig) { + + // get wrappers from the templateOptions (JSONSchema) + if (field.templateOptions) { + field.wrappers = [ + ...(field.templateOptions.wrappers ? field.templateOptions.wrappers : []), + ...(field.wrappers ? field.wrappers : []) + ]; + } - if (field.parent != null) { - parentKey = getKey(field.parent); - keyParams.push(parentKey); + if (field.templateOptions && field.templateOptions.longMode === true) { + // add automatically a card wrapper for the first level fields + const parent = field.parent; + if (parent && parent.templateOptions && parent.templateOptions.isRoot === true) { + if (field.templateOptions.wrappers == null) { + field.wrappers.push('card'); + } + } + // Add an horizontal wrapper for all given field types + if (this._horizontalWrapperTypes.some(elem => elem === field.type)) { + field.wrappers = field.wrappers.filter(w => w !== 'form-field'); + field.wrappers.push('form-field-horizontal'); + } + } + else { + // adds form-fields for non standard field types + if (this._fieldWrapperTypes.some(elem => elem === field.type)) { + field.wrappers = [ + ...(field.wrappers ? field.wrappers : []), + 'form-field' + ]; + } + } + // TODO: this can be fixed in a future formly release + // due to multiple calls (sometimes) we need to make the value unique + field.wrappers = [...new Set(field.wrappers)]; } - if (parentKey != null) { - if (field.key != null) { - keyParams.push(field.key); - } else { - keyParams.push(field.id.replace(/.*__(\d+)$/, '$1')); + /** + * Create an id html attribute by joining field and parents keys + * @param field formly field config + * @returns array - keys to create the id html attribute + */ + private _getKey(field: any): string { + let parentKey = null; + const keyParams = []; + + if (field.parent != null) { + parentKey = this._getKey(field.parent); + keyParams.push(parentKey); } - return keyParams.join('-'); + if (parentKey != null) { + if (field.key != null) { + keyParams.push(field.key); + } else { + keyParams.push(field.id.replace(/.*__(\d+)$/, '$1')); + } + + return keyParams.join('-'); + } + return field.key; + } + + /** + * Hide or show field depending of the data content and the hide property + * @param field - FormlyFieldConfig + */ + private _hideShowEmptyField(field: FormlyFieldConfig) { + let model = field.model; + if (field.templateOptions == null) { + return; + } + // for simple object the model is the parent dict + if ( + field.model != null && !['object', 'multischema', 'array'].some(f => f === field.type) + ) { + // New from ngx-formly v5.9.0 + model = field.model[Array.isArray(field.key) ? field.key[0] : field.key]; + } + model = removeEmptyValues(model); + const modelEmpty = isEmpty(model); + if (!modelEmpty && field.templateOptions.hide === true) { + setTimeout(() => { + field.hide = false; + this._editorService.removeHiddenField(field); + }); + } + if (modelEmpty && (field.templateOptions.hide === true && field.hide === undefined)) { + setTimeout(() => { + field.hide = true; + this._editorService.addHiddenField(field); + }); + } } - return field.key; } export class TranslateExtension { @@ -187,7 +277,7 @@ export class TranslateExtension { * @param translate ngx-translate service * @returns FormlyConfig object configuration */ -export function registerTranslateExtension(translate: TranslateService) { +export function registerNgCoreFormlyExtension(translate: TranslateService, editorService: EditorService) { return { // translate the default validators messages // widely inspired from ngx-formly example @@ -272,6 +362,9 @@ export function registerTranslateExtension(translate: TranslateService) { extensions: [{ name: 'translate', extension: new TranslateExtension(translate) + }, { + name: 'ng-core', + extension: new NgCoreFormlyExtension(editorService) }], }; } diff --git a/projects/rero/ng-core/src/lib/record/editor/services/editor.service.ts b/projects/rero/ng-core/src/lib/record/editor/services/editor.service.ts index fba28b173..c006829f0 100644 --- a/projects/rero/ng-core/src/lib/record/editor/services/editor.service.ts +++ b/projects/rero/ng-core/src/lib/record/editor/services/editor.service.ts @@ -83,7 +83,7 @@ export class EditorService { * @param field - FormlyFieldConfig, form config to be added */ addHiddenField(field: FormlyFieldConfig) { - if (!this._hiddenFields.some(f => f.id === field.id) && this.isFieldRoot(field)) { + if (!this._hiddenFields.some(f => f.id === field.id) && this.isRoot(field.parent)) { this._hiddenFields.push(field); this._hiddenFieldsSubject.next(this._hiddenFields); } @@ -94,9 +94,48 @@ export class EditorService { * @param field - FormlyFieldConfig, form config to be removed */ removeHiddenField(field: FormlyFieldConfig) { - if (this._hiddenFields.some(f => f === field) && this.isFieldRoot(field)) { + if (this._hiddenFields.some(f => f === field) && this.isRoot(field.parent)) { this._hiddenFields = this._hiddenFields.filter(f => f.id !== field.id); this._hiddenFieldsSubject.next(this._hiddenFields); } } + + /** + * Can the field be hidden? + * @param field - FormlyFieldConfig, the field to hide + * @returns boolean, true if the field can be hidden + */ + canHide(field: FormlyFieldConfig) { + if (!field.templateOptions.longMode) { + return false; + } + return ( + !field.templateOptions.required && + !field.hide && + field.hideExpression == null + ); + } + + /** + * Am I at the root of the form? + * @param field - FormlyFieldConfig, the field to hide + * @returns boolean, true if I'm the root + */ + isRoot(field: FormlyFieldConfig) { + if (!field) { + return false; + } + return field.templateOptions && field.templateOptions.isRoot && field.templateOptions.isRoot === true; + } + + /** + * Hide the given formly field. + * @param field - FormlyFieldConfig, the field to hide + */ + hide(field: FormlyFieldConfig) { + field.hide = true; + if (this.isRoot(field.parent)) { + this.addHiddenField(field); + } + } } diff --git a/projects/rero/ng-core/src/lib/record/editor/type/array-type/array-type.component.html b/projects/rero/ng-core/src/lib/record/editor/type/array-type/array-type.component.html index c39eb9bfa..11e27e569 100644 --- a/projects/rero/ng-core/src/lib/record/editor/type/array-type/array-type.component.html +++ b/projects/rero/ng-core/src/lib/record/editor/type/array-type/array-type.component.html @@ -15,109 +15,24 @@  along with this program. If not, see . --> - - - + + + +
+ + - - - - -
-
- -
- - - - -
- -
- - - -
-
- -
+ +
+ +
+
+
-
- -
+
-
- - - - - - - - - - - - - - - Help - - - - - - - - - - - - - +
diff --git a/projects/rero/ng-core/src/lib/record/editor/type/array-type/array-type.component.ts b/projects/rero/ng-core/src/lib/record/editor/type/array-type/array-type.component.ts index b9d23448f..fcfdd927f 100644 --- a/projects/rero/ng-core/src/lib/record/editor/type/array-type/array-type.component.ts +++ b/projects/rero/ng-core/src/lib/record/editor/type/array-type/array-type.component.ts @@ -26,26 +26,15 @@ import { TranslateService } from '@ngx-translate/core'; templateUrl: 'array-type.component.html' }) export class ArrayTypeComponent extends FieldArrayType implements OnInit { - /** True if children are of type object */ - isChildrenObject = false; - - /** True if the children are of type array */ - isChildrenArray = false; - - /** Constructor - * - * @param _translateService - TranslateService, that translate the labels of the hidden fields - */ - constructor(private _translateService: TranslateService) { - super(); - } /** * Component initialization */ ngOnInit() { - this.isChildrenObject = this.field.fieldArray.type === 'object'; - this.isChildrenArray = this.field.fieldArray.type === 'array'; + this.field.templateOptions.remove = this.remove.bind(this); + this.field.templateOptions.add = this.add.bind(this); + this.field.templateOptions.canAdd = this.canAdd.bind(this); + this.field.templateOptions.canRemove = this.canRemove.bind(this); } /** @@ -77,7 +66,6 @@ export class ArrayTypeComponent extends FieldArrayType implements OnInit { * @param i - number, the position to remove the element */ remove(i: number) { - super.remove(i); } @@ -86,7 +74,6 @@ export class ArrayTypeComponent extends FieldArrayType implements OnInit { * @param i - number, the position to add the element */ add(i: number, initialModel?: any) { - // TODO: focus in the first input child super.add(i, initialModel); this.setFocusInChildren(this.field.fieldGroup[i]); } @@ -112,45 +99,4 @@ export class ArrayTypeComponent extends FieldArrayType implements OnInit { return false; } - /** - * Hide the array and remove all the elements - */ - hide() { - this.field.hide = true; - } - - /** - * Is the dropdown menu displayed? - * @param field - FormlyFieldConfig, the correspondig form field config - * @returns boolean, true if the menu should be displayed - */ - hasMenu(field: FormlyFieldConfig) { - // TODO: add support for anyOf - const f = field; - // if (field.type === 'multischema') { - // f = f.fieldGroup[1]; - // } - return ( - (f.type === 'object' && this.hiddenFieldGroup(f.fieldGroup).length > 0) || - f.templateOptions.helpURL - ); - } - - /** - * Translate the label of a given formly field. - * - * @param field ngx-formly field - */ - translateLabel(field: FormlyFieldConfig) { - return this._translateService.stream(field.templateOptions.untranslatedLabel); - } - - /** - * Filter the fieldGroup to return the list of hidden field. - * @param fieldGroup - FormlyFieldConfig[], the fieldGroup to filter - * @returns FormlyFieldConfig[], the filtered list - */ - hiddenFieldGroup(fieldGroup: FormlyFieldConfig[]): FormlyFieldConfig[] { - return fieldGroup.filter(f => f.hide && f.hideExpression == null); - } } diff --git a/projects/rero/ng-core/src/lib/record/editor/type/multischema/multischema.component.ts b/projects/rero/ng-core/src/lib/record/editor/type/multischema/multischema.component.ts index df1b8d5e1..517ec02c5 100644 --- a/projects/rero/ng-core/src/lib/record/editor/type/multischema/multischema.component.ts +++ b/projects/rero/ng-core/src/lib/record/editor/type/multischema/multischema.component.ts @@ -14,14 +14,14 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { FieldType } from '@ngx-formly/core'; @Component({ selector: 'ng-core-editor-formly-multi-schema-type', template: ` -
- {{ to.label }} +
+ {{ to.label }} diff --git a/projects/rero/ng-core/src/lib/record/editor/type/object-type/object-type.component.html b/projects/rero/ng-core/src/lib/record/editor/type/object-type/object-type.component.html index 3c5228103..037076fbe 100644 --- a/projects/rero/ng-core/src/lib/record/editor/type/object-type/object-type.component.html +++ b/projects/rero/ng-core/src/lib/record/editor/type/object-type/object-type.component.html @@ -14,74 +14,21 @@  You should have received a copy of the GNU Affero General Public License  along with this program. If not, see . --> - - -