diff --git a/fiori/app/admin-books/fiori-service.cds b/fiori/app/admin-books/fiori-service.cds index 6be07610..d432f1c5 100644 --- a/fiori/app/admin-books/fiori-service.cds +++ b/fiori/app/admin-books/fiori-service.cds @@ -62,24 +62,10 @@ annotate AdminService.Books with { ValueListProperty: 'ID', } ], - PresentationVariantQualifier: 'VH', } }); } -annotate AdminService.Genres with @UI: { - PresentationVariant #VH: { - $Type : 'UI.PresentationVariantType', - Visualizations : ['@UI.LineItem'], - RecursiveHierarchyQualifier: 'GenreHierarchy' - }, - LineItem : [{ - $Type: 'UI.DataField', - Value: name, - Label :'{i18n>Name}' - }], -}; - // Hide ID because of the ValueHelp annotate AdminService.Genres with { ID @UI.Hidden; @@ -124,4 +110,3 @@ extend service AdminService { // Workaround for Fiori popup for asking user to enter a new UUID on Create annotate AdminService.Books with { ID @Core.Computed; } - diff --git a/fiori/app/common.cds b/fiori/app/common.cds index 7561f118..f6e2e94f 100644 --- a/fiori/app/common.cds +++ b/fiori/app/common.cds @@ -4,7 +4,6 @@ using { sap.capire.bookshop as my } from '@capire/bookstore'; using { sap.common } from '@capire/common'; -using { sap.common.Currencies } from '@sap/cds/common'; //////////////////////////////////////////////////////////////////////////// // @@ -38,7 +37,7 @@ annotate my.Books with @( author @ValueList.entity : 'Authors'; }; -annotate Currencies with { +annotate common.Currencies with { symbol @Common.Label : '{i18n>Currency}'; } @@ -69,95 +68,6 @@ annotate my.Books with { image @title: '{i18n>Image}'; } -//////////////////////////////////////////////////////////////////////////// -// -// Computed Fields for Tree Tables -// -// DISCLAIMER: The below are an alpha version implementation and will change in final release !!! -// -aspect Hierarchy { - LimitedDescendantCount : Integer64 = null; - DistanceFromRoot : Integer64 = null; - DrillState : String = null; - LimitedRank : Integer64 = null; -} - -annotate Hierarchy with @Capabilities.FilterRestrictions.NonFilterableProperties: [ - 'LimitedDescendantCount', - 'DistanceFromRoot', - 'DrillState', - 'LimitedRank' -]; - -annotate Hierarchy with @Capabilities.SortRestrictions.NonSortableProperties: [ - 'LimitedDescendantCount', - 'DistanceFromRoot', - 'DrillState', - 'LimitedRank' -]; - -extend my.Genres with Hierarchy; - -//////////////////////////////////////////////////////////////////////////// -// -// Genres Tree Table Annotations -// -// DISCLAIMER: The below are an alpha version implementation and will change in final release !!! -// -annotate my.Genres with @Aggregation.RecursiveHierarchy #GenreHierarchy: { - $Type : 'Aggregation.RecursiveHierarchyType', - NodeProperty : ID, // identifies a node - ParentNavigationProperty: parent // navigates to a node's parent -}; - -annotate my.Genres with @Hierarchy.RecursiveHierarchy #GenreHierarchy: { - $Type : 'Hierarchy.RecursiveHierarchyType', - LimitedDescendantCount: LimitedDescendantCount, - DistanceFromRoot : DistanceFromRoot, - DrillState : DrillState, - LimitedRank : LimitedRank -}; - -annotate my.Genres with @( - readonly, - cds.search: {name} -); -//////////////////////////////////////////////////////////////////////////// -// -// Genres List -// -annotate my.Genres with @( - Common.SemanticKey : [name], - UI : { - SelectionFields : [name], - LineItem : [ - { Value : name, Label : '{i18n>Name}' }, - ], - } -); - -//////////////////////////////////////////////////////////////////////////// -// -// Genre Details -// -annotate my.Genres with @(UI : { - Identification : [{ Value: name}], - HeaderInfo : { - TypeName : '{i18n>Genre}', - TypeNamePlural : '{i18n>Genres}', - Title : { Value: name }, - Description : { Value: ID } - } -}); - -//////////////////////////////////////////////////////////////////////////// -// -// Genres Elements -// -annotate my.Genres with { - name @title: '{i18n>Genre}'; -} - //////////////////////////////////////////////////////////////////////////// // // Authors List diff --git a/fiori/app/genres/fiori-service.cds b/fiori/app/genres/fiori-service.cds index e1961cf0..834f2b57 100644 --- a/fiori/app/genres/fiori-service.cds +++ b/fiori/app/genres/fiori-service.cds @@ -1,3 +1,33 @@ -/* -All annotations needed for UI5 Tree Table View are located in '../common' -*/ +using { sap.capire.bookshop.Genres } from '@capire/bookstore'; + +annotate Genres with @cds.search: {name}; +annotate Genres with @readonly; +annotate Genres with { + name @title: '{i18n>Genre}'; +} + +// Lists +annotate Genres with @( + Common.SemanticKey : [name], + UI.SelectionFields : [name], + UI.LineItem : [ + { Value: name, Label: '{i18n>Name}' }, + ], +); + +// Details +annotate Genres with @(UI : { + Identification : [{ Value: name }], + HeaderInfo : { + TypeName : '{i18n>Genre}', + TypeNamePlural : '{i18n>Genres}', + Title : { Value: name }, + Description : { Value: ID } + } +}); + + +// Tree Views +// annotate AdminService.Genres with @hierarchy; // upcomming simplification +using from './tree-view'; +using from './value-help'; diff --git a/fiori/app/genres/tree-view.cds b/fiori/app/genres/tree-view.cds new file mode 100644 index 00000000..eeb80ea7 --- /dev/null +++ b/fiori/app/genres/tree-view.cds @@ -0,0 +1,42 @@ +using { AdminService } from '@capire/bookstore'; + +//////////////////////////////////////////////////////////////////////////// +// +// Genres Tree View +// + +// Tell Fiori about the structure of the hierarchy +annotate AdminService.Genres with @Aggregation.RecursiveHierarchy #GenresHierarchy : { + ParentNavigationProperty : parent, // navigates to a node's parent + NodeProperty : ID, // identifies a node, usually the key +}; + +// Fiori expects the following to be defined explicitly, even though they're always the same +extend AdminService.Genres with @( + // The columns expected by Fiori to be present in hierarchy entities + Hierarchy.RecursiveHierarchy #GenresHierarchy : { + LimitedDescendantCount : LimitedDescendantCount, + DistanceFromRoot : DistanceFromRoot, + DrillState : DrillState, + LimitedRank : LimitedRank + }, + // Disallow filtering on these properties from Fiori UIs + Capabilities.FilterRestrictions.NonFilterableProperties: [ + 'LimitedDescendantCount', + 'DistanceFromRoot', + 'DrillState', + 'LimitedRank' + ], + // Disallow sorting on these properties from Fiori UIs + Capabilities.SortRestrictions.NonSortableProperties : [ + 'LimitedDescendantCount', + 'DistanceFromRoot', + 'DrillState', + 'LimitedRank' + ], +) columns { // Ensure we can query these fields from database + null as LimitedDescendantCount : Int16, + null as DistanceFromRoot : Int16, + null as DrillState : String, + null as LimitedRank : Int16, +}; diff --git a/fiori/app/genres/value-help.cds b/fiori/app/genres/value-help.cds new file mode 100644 index 00000000..a0f29e85 --- /dev/null +++ b/fiori/app/genres/value-help.cds @@ -0,0 +1,6 @@ +// Value help with Tree View +using from '../admin-books/fiori-service'; +annotate AdminService.Books:genre with @Common.ValueList.PresentationVariantQualifier: 'VH'; +annotate AdminService.Genres with @UI.PresentationVariant #VH: { + RecursiveHierarchyQualifier : 'GenresHierarchy', +}; diff --git a/fiori/app/genres/webapp/manifest.json b/fiori/app/genres/webapp/manifest.json index a43f4a83..857a4203 100644 --- a/fiori/app/genres/webapp/manifest.json +++ b/fiori/app/genres/webapp/manifest.json @@ -51,7 +51,7 @@ "earlyRequests": true, "groupProperties": { "default": { - "submit": "Auto" + "submit": "Auto" } } } @@ -82,17 +82,17 @@ "Genres": { "detail": { "route": "GenresDetails" - } + } } }, "controlConfiguration": { - "@com.sap.vocabularies.UI.v1.LineItem": { - "tableSettings": { - "hierarchyQualifier": "GenreHierarchy", - "type": "TreeTable" - } - } - } + "@com.sap.vocabularies.UI.v1.LineItem": { + "tableSettings": { + "hierarchyQualifier": "GenresHierarchy", + "type": "TreeTable" + } + } + } } } }, @@ -121,4 +121,4 @@ "registrationIds": [], "archeType": "transactional" } -} +} \ No newline at end of file diff --git a/fiori/app/services.cds b/fiori/app/services.cds index 6949caae..8b47e508 100644 --- a/fiori/app/services.cds +++ b/fiori/app/services.cds @@ -5,6 +5,7 @@ using from './admin-authors/fiori-service'; using from './admin-books/fiori-service'; using from './browse/fiori-service'; +using from './genres/fiori-service'; using from './common'; using from '@capire/bookstore/srv/mashup'; diff --git a/fiori/server.js b/fiori/server.js new file mode 100644 index 00000000..0d030429 --- /dev/null +++ b/fiori/server.js @@ -0,0 +1,50 @@ +const cds = require('@sap/cds/lib') + +// PoC for simplified Fiori Tree Views +cds.on('compile.for.runtime', csn => { + for (let each of cds.linked(csn).definitions) { + if (each.is_entity && each._service && each['@hierarchy']) _hierarchy (each) + } +}) + + +const _hierarchy = entity => { + + // Add annotations explaining the hierarchy structure to Fiori + const Qualifier = entity.name.slice (entity._service.name.length+1) + 'Hierarchy' + const parent = _parent4(entity) + entity[`@Aggregation.RecursiveHierarchy#${Qualifier}.ParentNavigationProperty`] ??= {'=': parent.name } + entity[`@Aggregation.RecursiveHierarchy#${Qualifier}.NodeProperty`] ??= {'=': parent.keys[0].ref[0] } + + // Add expected hierarchy elements to the entity + const columns = entity.projection.columns ??= ['*'] + const elements = entity.elements + for (let e of Hierarchy.elements) { + entity[`@Hierarchy.RecursiveHierarchy#${Qualifier}.${e.name}`] = {'=': e.name } + if (e.name in elements) continue + const { name, value, ...rest } = e + elements[e.name] = Object.defineProperty ({ __proto__:e, ...rest }, 'parent', { value: entity }) + columns.push ({ ...value, as: name, cast: { type: e.type } }) + } + + // Disable filter and sort for hierarchy elements + entity['@Capabilities.FilterRestrictions.NonFilterableProperties'] = + entity['@Capabilities.SortRestrictions.NonSortableProperties'] = + Object.keys (Hierarchy.elements) +} + + +const _parent4 = entity => { + const parent = entity['@hierarchy.parent'] || entity['@hierarchy.via'] + if (parent) return entity.elements [parent['=']||parent] + else for (let e of entity.elements) // use first recursive uplink association + if (e.is2one && e._target === entity) return e +} + + +const { Hierarchy } = cds.linked `aspect Hierarchy { + LimitedDescendantCount : Int16 = null; + DistanceFromRoot : Int16 = null; + DrillState : String = null; + LimitedRank : Int16 = null; +}`.definitions