Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Commit 600afb7

Browse files
authored
Fiori Tree Views towards GA (#839)
* Fiori Tree Views towards GA * Also automate addition of Aggregation.RecursiveHierarchy * Cleanup models for Genres Tree View * Implementing @hierarchy shortcut * . * Formatting * Using verbose config
1 parent 6fa2aae commit 600afb7

File tree

8 files changed

+143
-119
lines changed

8 files changed

+143
-119
lines changed

fiori/app/admin-books/fiori-service.cds

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,24 +62,10 @@ annotate AdminService.Books with {
6262
ValueListProperty: 'ID',
6363
}
6464
],
65-
PresentationVariantQualifier: 'VH',
6665
}
6766
});
6867
}
6968

70-
annotate AdminService.Genres with @UI: {
71-
PresentationVariant #VH: {
72-
$Type : 'UI.PresentationVariantType',
73-
Visualizations : ['@UI.LineItem'],
74-
RecursiveHierarchyQualifier: 'GenreHierarchy'
75-
},
76-
LineItem : [{
77-
$Type: 'UI.DataField',
78-
Value: name,
79-
Label :'{i18n>Name}'
80-
}],
81-
};
82-
8369
// Hide ID because of the ValueHelp
8470
annotate AdminService.Genres with {
8571
ID @UI.Hidden;
@@ -124,4 +110,3 @@ extend service AdminService {
124110

125111
// Workaround for Fiori popup for asking user to enter a new UUID on Create
126112
annotate AdminService.Books with { ID @Core.Computed; }
127-

fiori/app/common.cds

Lines changed: 1 addition & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
using { sap.capire.bookshop as my } from '@capire/bookstore';
66
using { sap.common } from '@capire/common';
7-
using { sap.common.Currencies } from '@sap/cds/common';
87

98
////////////////////////////////////////////////////////////////////////////
109
//
@@ -38,7 +37,7 @@ annotate my.Books with @(
3837
author @ValueList.entity : 'Authors';
3938
};
4039

41-
annotate Currencies with {
40+
annotate common.Currencies with {
4241
symbol @Common.Label : '{i18n>Currency}';
4342
}
4443

@@ -69,95 +68,6 @@ annotate my.Books with {
6968
image @title: '{i18n>Image}';
7069
}
7170

72-
////////////////////////////////////////////////////////////////////////////
73-
//
74-
// Computed Fields for Tree Tables
75-
//
76-
// DISCLAIMER: The below are an alpha version implementation and will change in final release !!!
77-
//
78-
aspect Hierarchy {
79-
LimitedDescendantCount : Integer64 = null;
80-
DistanceFromRoot : Integer64 = null;
81-
DrillState : String = null;
82-
LimitedRank : Integer64 = null;
83-
}
84-
85-
annotate Hierarchy with @Capabilities.FilterRestrictions.NonFilterableProperties: [
86-
'LimitedDescendantCount',
87-
'DistanceFromRoot',
88-
'DrillState',
89-
'LimitedRank'
90-
];
91-
92-
annotate Hierarchy with @Capabilities.SortRestrictions.NonSortableProperties: [
93-
'LimitedDescendantCount',
94-
'DistanceFromRoot',
95-
'DrillState',
96-
'LimitedRank'
97-
];
98-
99-
extend my.Genres with Hierarchy;
100-
101-
////////////////////////////////////////////////////////////////////////////
102-
//
103-
// Genres Tree Table Annotations
104-
//
105-
// DISCLAIMER: The below are an alpha version implementation and will change in final release !!!
106-
//
107-
annotate my.Genres with @Aggregation.RecursiveHierarchy #GenreHierarchy: {
108-
$Type : 'Aggregation.RecursiveHierarchyType',
109-
NodeProperty : ID, // identifies a node
110-
ParentNavigationProperty: parent // navigates to a node's parent
111-
};
112-
113-
annotate my.Genres with @Hierarchy.RecursiveHierarchy #GenreHierarchy: {
114-
$Type : 'Hierarchy.RecursiveHierarchyType',
115-
LimitedDescendantCount: LimitedDescendantCount,
116-
DistanceFromRoot : DistanceFromRoot,
117-
DrillState : DrillState,
118-
LimitedRank : LimitedRank
119-
};
120-
121-
annotate my.Genres with @(
122-
readonly,
123-
cds.search: {name}
124-
);
125-
////////////////////////////////////////////////////////////////////////////
126-
//
127-
// Genres List
128-
//
129-
annotate my.Genres with @(
130-
Common.SemanticKey : [name],
131-
UI : {
132-
SelectionFields : [name],
133-
LineItem : [
134-
{ Value : name, Label : '{i18n>Name}' },
135-
],
136-
}
137-
);
138-
139-
////////////////////////////////////////////////////////////////////////////
140-
//
141-
// Genre Details
142-
//
143-
annotate my.Genres with @(UI : {
144-
Identification : [{ Value: name}],
145-
HeaderInfo : {
146-
TypeName : '{i18n>Genre}',
147-
TypeNamePlural : '{i18n>Genres}',
148-
Title : { Value: name },
149-
Description : { Value: ID }
150-
}
151-
});
152-
153-
////////////////////////////////////////////////////////////////////////////
154-
//
155-
// Genres Elements
156-
//
157-
annotate my.Genres with {
158-
name @title: '{i18n>Genre}';
159-
}
160-
16171
////////////////////////////////////////////////////////////////////////////
16272
//
16373
// Authors List

fiori/app/genres/fiori-service.cds

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,33 @@
1-
/*
2-
All annotations needed for UI5 Tree Table View are located in '../common'
3-
*/
1+
using { sap.capire.bookshop.Genres } from '@capire/bookstore';
2+
3+
annotate Genres with @cds.search: {name};
4+
annotate Genres with @readonly;
5+
annotate Genres with {
6+
name @title: '{i18n>Genre}';
7+
}
8+
9+
// Lists
10+
annotate Genres with @(
11+
Common.SemanticKey : [name],
12+
UI.SelectionFields : [name],
13+
UI.LineItem : [
14+
{ Value: name, Label: '{i18n>Name}' },
15+
],
16+
);
17+
18+
// Details
19+
annotate Genres with @(UI : {
20+
Identification : [{ Value: name }],
21+
HeaderInfo : {
22+
TypeName : '{i18n>Genre}',
23+
TypeNamePlural : '{i18n>Genres}',
24+
Title : { Value: name },
25+
Description : { Value: ID }
26+
}
27+
});
28+
29+
30+
// Tree Views
31+
// annotate AdminService.Genres with @hierarchy; // upcomming simplification
32+
using from './tree-view';
33+
using from './value-help';

fiori/app/genres/tree-view.cds

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using { AdminService } from '@capire/bookstore';
2+
3+
////////////////////////////////////////////////////////////////////////////
4+
//
5+
// Genres Tree View
6+
//
7+
8+
// Tell Fiori about the structure of the hierarchy
9+
annotate AdminService.Genres with @Aggregation.RecursiveHierarchy #GenresHierarchy : {
10+
ParentNavigationProperty : parent, // navigates to a node's parent
11+
NodeProperty : ID, // identifies a node, usually the key
12+
};
13+
14+
// Fiori expects the following to be defined explicitly, even though they're always the same
15+
extend AdminService.Genres with @(
16+
// The columns expected by Fiori to be present in hierarchy entities
17+
Hierarchy.RecursiveHierarchy #GenresHierarchy : {
18+
LimitedDescendantCount : LimitedDescendantCount,
19+
DistanceFromRoot : DistanceFromRoot,
20+
DrillState : DrillState,
21+
LimitedRank : LimitedRank
22+
},
23+
// Disallow filtering on these properties from Fiori UIs
24+
Capabilities.FilterRestrictions.NonFilterableProperties: [
25+
'LimitedDescendantCount',
26+
'DistanceFromRoot',
27+
'DrillState',
28+
'LimitedRank'
29+
],
30+
// Disallow sorting on these properties from Fiori UIs
31+
Capabilities.SortRestrictions.NonSortableProperties : [
32+
'LimitedDescendantCount',
33+
'DistanceFromRoot',
34+
'DrillState',
35+
'LimitedRank'
36+
],
37+
) columns { // Ensure we can query these fields from database
38+
null as LimitedDescendantCount : Int16,
39+
null as DistanceFromRoot : Int16,
40+
null as DrillState : String,
41+
null as LimitedRank : Int16,
42+
};

fiori/app/genres/value-help.cds

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Value help with Tree View
2+
using from '../admin-books/fiori-service';
3+
annotate AdminService.Books:genre with @Common.ValueList.PresentationVariantQualifier: 'VH';
4+
annotate AdminService.Genres with @UI.PresentationVariant #VH: {
5+
RecursiveHierarchyQualifier : 'GenresHierarchy',
6+
};

fiori/app/genres/webapp/manifest.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"earlyRequests": true,
5252
"groupProperties": {
5353
"default": {
54-
"submit": "Auto"
54+
"submit": "Auto"
5555
}
5656
}
5757
}
@@ -82,17 +82,17 @@
8282
"Genres": {
8383
"detail": {
8484
"route": "GenresDetails"
85-
}
85+
}
8686
}
8787
},
8888
"controlConfiguration": {
89-
"@com.sap.vocabularies.UI.v1.LineItem": {
90-
"tableSettings": {
91-
"hierarchyQualifier": "GenreHierarchy",
92-
"type": "TreeTable"
93-
}
94-
}
95-
}
89+
"@com.sap.vocabularies.UI.v1.LineItem": {
90+
"tableSettings": {
91+
"hierarchyQualifier": "GenresHierarchy",
92+
"type": "TreeTable"
93+
}
94+
}
95+
}
9696
}
9797
}
9898
},
@@ -121,4 +121,4 @@
121121
"registrationIds": [],
122122
"archeType": "transactional"
123123
}
124-
}
124+
}

fiori/app/services.cds

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using from './admin-authors/fiori-service';
66
using from './admin-books/fiori-service';
77
using from './browse/fiori-service';
8+
using from './genres/fiori-service';
89

910
using from './common';
1011
using from '@capire/bookstore/srv/mashup';

fiori/server.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const cds = require('@sap/cds/lib')
2+
3+
// PoC for simplified Fiori Tree Views
4+
cds.on('compile.for.runtime', csn => {
5+
for (let each of cds.linked(csn).definitions) {
6+
if (each.is_entity && each._service && each['@hierarchy']) _hierarchy (each)
7+
}
8+
})
9+
10+
11+
const _hierarchy = entity => {
12+
13+
// Add annotations explaining the hierarchy structure to Fiori
14+
const Qualifier = entity.name.slice (entity._service.name.length+1) + 'Hierarchy'
15+
const parent = _parent4(entity)
16+
entity[`@Aggregation.RecursiveHierarchy#${Qualifier}.ParentNavigationProperty`] ??= {'=': parent.name }
17+
entity[`@Aggregation.RecursiveHierarchy#${Qualifier}.NodeProperty`] ??= {'=': parent.keys[0].ref[0] }
18+
19+
// Add expected hierarchy elements to the entity
20+
const columns = entity.projection.columns ??= ['*']
21+
const elements = entity.elements
22+
for (let e of Hierarchy.elements) {
23+
entity[`@Hierarchy.RecursiveHierarchy#${Qualifier}.${e.name}`] = {'=': e.name }
24+
if (e.name in elements) continue
25+
const { name, value, ...rest } = e
26+
elements[e.name] = Object.defineProperty ({ __proto__:e, ...rest }, 'parent', { value: entity })
27+
columns.push ({ ...value, as: name, cast: { type: e.type } })
28+
}
29+
30+
// Disable filter and sort for hierarchy elements
31+
entity['@Capabilities.FilterRestrictions.NonFilterableProperties'] =
32+
entity['@Capabilities.SortRestrictions.NonSortableProperties'] =
33+
Object.keys (Hierarchy.elements)
34+
}
35+
36+
37+
const _parent4 = entity => {
38+
const parent = entity['@hierarchy.parent'] || entity['@hierarchy.via']
39+
if (parent) return entity.elements [parent['=']||parent]
40+
else for (let e of entity.elements) // use first recursive uplink association
41+
if (e.is2one && e._target === entity) return e
42+
}
43+
44+
45+
const { Hierarchy } = cds.linked `aspect Hierarchy {
46+
LimitedDescendantCount : Int16 = null;
47+
DistanceFromRoot : Int16 = null;
48+
DrillState : String = null;
49+
LimitedRank : Int16 = null;
50+
}`.definitions

0 commit comments

Comments
 (0)