Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions fiori/app/admin-books/fiori-service.cds
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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; }

92 changes: 1 addition & 91 deletions fiori/app/common.cds
Original file line number Diff line number Diff line change
Expand Up @@ -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';

////////////////////////////////////////////////////////////////////////////
//
Expand Down Expand Up @@ -38,7 +37,7 @@ annotate my.Books with @(
author @ValueList.entity : 'Authors';
};

annotate Currencies with {
annotate common.Currencies with {
symbol @Common.Label : '{i18n>Currency}';
}

Expand Down Expand Up @@ -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
Expand Down
36 changes: 33 additions & 3 deletions fiori/app/genres/fiori-service.cds
Original file line number Diff line number Diff line change
@@ -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';
42 changes: 42 additions & 0 deletions fiori/app/genres/tree-view.cds
Original file line number Diff line number Diff line change
@@ -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,
};
Comment on lines +24 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should capture these as a reusable aspect. Unfortunately, an aspect can't have virtual or computed elements.

6 changes: 6 additions & 0 deletions fiori/app/genres/value-help.cds
Original file line number Diff line number Diff line change
@@ -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',
};
20 changes: 10 additions & 10 deletions fiori/app/genres/webapp/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"earlyRequests": true,
"groupProperties": {
"default": {
"submit": "Auto"
"submit": "Auto"
}
}
}
Expand Down Expand Up @@ -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"
}
}
}
}
}
},
Expand Down Expand Up @@ -121,4 +121,4 @@
"registrationIds": [],
"archeType": "transactional"
}
}
}
1 change: 1 addition & 0 deletions fiori/app/services.cds
Original file line number Diff line number Diff line change
Expand Up @@ -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';
50 changes: 50 additions & 0 deletions fiori/server.js
Original file line number Diff line number Diff line change
@@ -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