Fiori Tree Views (#816)
* add read only tree table and value help
* rm tree table in catalogservice
* Update cat-service.cds
* i18n and object page
* hana workaround, local messaging and rm virtual
* move hierarchy to fiori
* Update admin-service.cds
* Update common.cds
* fix after moving to fiori
* fix UI filter
* file-based-messaging in hybrid
* review fixes
* Update services.cds
* make bookshop Books fiori.draft.enabled
* add simplest handler for sqlite
* adapt test and link in csvs
* .
* Reverting to human-readable UUIDs :)
* Less obstrusive workaround
* typo
* Update fiori/app/common.cds
* Update fiori/app/common.cds
* Workaround for stupid GUID check in Fiori client
* ...
* Simplified mock support for recursive hierarchies in SQLite :)
* missing comma
* Rudimentary tree support for Genres on SQLite
* ?.
* fixed copy error
* using subselect to determine leafs
* Revert "using subselect to determine leafs"
This reverts commit f01ddaea1f.
* Using scalar subselect for DrillState
* .
---------
Co-authored-by: D070615 <olena.timrova@sap.com>
Co-authored-by: D045778 <johannes.vogel@sap.com>
Co-authored-by: Daniel Hutzel <daniel.hutzel@sap.com>
This commit is contained in:
1
fiori/.env
Normal file
1
fiori/.env
Normal file
@@ -0,0 +1 @@
|
||||
cds.requires.[hybrid].messaging.kind=file-based-messaging
|
||||
@@ -1,7 +1,7 @@
|
||||
Age = Age
|
||||
Lifetime = Lifetime
|
||||
|
||||
SubGenres = Sub Genres
|
||||
SubGenres = Subgenre
|
||||
|
||||
NumCode = Numeric Code
|
||||
MinorUnit = Minor Unit
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using { AdminService } from '@capire/bookstore';
|
||||
using from '../common'; // to help UI linter get the complete annotations
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Books Object Page
|
||||
@@ -40,7 +42,48 @@ annotate AdminService.Books with @(
|
||||
}
|
||||
);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Value Help for Tree Table
|
||||
//
|
||||
annotate AdminService.Books with {
|
||||
genre @(Common: {
|
||||
Label : 'Genre',
|
||||
ValueList: {
|
||||
CollectionPath : 'Genres',
|
||||
Parameters : [
|
||||
{
|
||||
$Type : 'Common.ValueListParameterDisplayOnly',
|
||||
ValueListProperty: 'name',
|
||||
},
|
||||
{
|
||||
$Type : 'Common.ValueListParameterInOut',
|
||||
LocalDataProperty: genre_ID,
|
||||
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;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
@@ -82,5 +125,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; }
|
||||
|
||||
// Show Genre as drop down, not a dialog
|
||||
annotate AdminService.Books with { genre @Common.ValueListWithFixedValues; }
|
||||
|
||||
@@ -18,6 +18,14 @@
|
||||
"title": "Browse Books",
|
||||
"targetURL": "#Books-display"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BrowseGenres",
|
||||
"tileType": "sap.ushell.ui.tile.StaticTile",
|
||||
"properties": {
|
||||
"title": "Browse Genres",
|
||||
"targetURL": "#Genres-display"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -106,6 +114,24 @@
|
||||
"url": "/admin-authors/webapp"
|
||||
}
|
||||
},
|
||||
"BrowseGenres": {
|
||||
"semanticObject": "Genres",
|
||||
"action": "display",
|
||||
"title": "Browse Genres",
|
||||
"signature": {
|
||||
"parameters": {
|
||||
"Genre.ID": {
|
||||
"renameTo": "ID"
|
||||
}
|
||||
},
|
||||
"additionalParameters": "ignored"
|
||||
},
|
||||
"resolutionResult": {
|
||||
"applicationType": "SAPUI5",
|
||||
"additionalInformation": "SAPUI5.Component=genres",
|
||||
"url": "/genres/webapp"
|
||||
}
|
||||
},
|
||||
"ManageBooks": {
|
||||
"semanticObject": "Books",
|
||||
"action": "manage",
|
||||
|
||||
@@ -69,28 +69,81 @@ 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;
|
||||
Matched : Boolean = null;
|
||||
MatchedDescendantCount : Integer64 = null;
|
||||
LimitedRank : Integer64 = null;
|
||||
}
|
||||
|
||||
annotate Hierarchy with @Capabilities.FilterRestrictions.NonFilterableProperties: [
|
||||
'LimitedDescendantCount',
|
||||
'DistanceFromRoot',
|
||||
'DrillState',
|
||||
'Matched',
|
||||
'MatchedDescendantCount',
|
||||
'LimitedRank'
|
||||
];
|
||||
|
||||
annotate Hierarchy with @Capabilities.SortRestrictions.NonSortableProperties: [
|
||||
'LimitedDescendantCount',
|
||||
'DistanceFromRoot',
|
||||
'DrillState',
|
||||
'Matched',
|
||||
'MatchedDescendantCount',
|
||||
'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,
|
||||
Matched : Matched,
|
||||
MatchedDescendantCount: MatchedDescendantCount,
|
||||
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 },
|
||||
{
|
||||
Value : parent.name,
|
||||
Label: 'Main Genre'
|
||||
},
|
||||
],
|
||||
}
|
||||
Common.SemanticKey : [name],
|
||||
UI : {
|
||||
SelectionFields : [name],
|
||||
LineItem : [
|
||||
{ Value : name, Label : '{i18n>Name}' },
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
annotate my.Genres with {
|
||||
ID @Common.Text : name @Common.TextArrangement : #TextOnly;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Genre Details
|
||||
@@ -102,12 +155,7 @@ annotate my.Genres with @(UI : {
|
||||
TypeNamePlural : '{i18n>Genres}',
|
||||
Title : { Value: name },
|
||||
Description : { Value: ID }
|
||||
},
|
||||
Facets : [{
|
||||
$Type : 'UI.ReferenceFacet',
|
||||
Label : '{i18n>SubGenres}',
|
||||
Target : 'children/@UI.LineItem'
|
||||
}, ],
|
||||
}
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
@@ -115,7 +163,6 @@ annotate my.Genres with @(UI : {
|
||||
// Genres Elements
|
||||
//
|
||||
annotate my.Genres with {
|
||||
ID @title: '{i18n>ID}';
|
||||
name @title: '{i18n>Genre}';
|
||||
}
|
||||
|
||||
|
||||
3
fiori/app/genres/fiori-service.cds
Normal file
3
fiori/app/genres/fiori-service.cds
Normal file
@@ -0,0 +1,3 @@
|
||||
/*
|
||||
All annotations needed for UI5 Tree Table View are located in '../common'
|
||||
*/
|
||||
3
fiori/app/genres/webapp/Component.js
Normal file
3
fiori/app/genres/webapp/Component.js
Normal file
@@ -0,0 +1,3 @@
|
||||
sap.ui.define(["sap/fe/core/AppComponent"], ac => ac.extend("genres.Component", {
|
||||
metadata:{ manifest:'json' }
|
||||
}))
|
||||
4
fiori/app/genres/webapp/i18n/i18n.properties
Normal file
4
fiori/app/genres/webapp/i18n/i18n.properties
Normal file
@@ -0,0 +1,4 @@
|
||||
#XTIT
|
||||
appTitle=Browse Genres
|
||||
#XTXT
|
||||
appDescription=Genres as Tree View
|
||||
2
fiori/app/genres/webapp/i18n/i18n_de.properties
Normal file
2
fiori/app/genres/webapp/i18n/i18n_de.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
appTitle=Zeige Genres
|
||||
appDescription=Genres als Baumansicht
|
||||
124
fiori/app/genres/webapp/manifest.json
Normal file
124
fiori/app/genres/webapp/manifest.json
Normal file
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"_version": "1.8.0",
|
||||
"sap.app": {
|
||||
"id": "genres",
|
||||
"type": "application",
|
||||
"title": "{{appTitle}}",
|
||||
"description": "{{appDescription}}",
|
||||
"applicationVersion": {
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"dataSources": {
|
||||
"AdminService": {
|
||||
"uri": "admin/",
|
||||
"type": "OData",
|
||||
"settings": {
|
||||
"odataVersion": "4.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"crossNavigation": {
|
||||
"inbounds": {
|
||||
"Genres-display": {
|
||||
"signature": {
|
||||
"parameters": {},
|
||||
"additionalParameters": "allowed"
|
||||
},
|
||||
"semanticObject": "Genres",
|
||||
"action": "display"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sap.ui5": {
|
||||
"dependencies": {
|
||||
"minUI5Version": "1.122.0",
|
||||
"libs": {
|
||||
"sap.fe.templates": {}
|
||||
}
|
||||
},
|
||||
"models": {
|
||||
"i18n": {
|
||||
"type": "sap.ui.model.resource.ResourceModel",
|
||||
"uri": "i18n/i18n.properties"
|
||||
},
|
||||
"": {
|
||||
"dataSource": "AdminService",
|
||||
"settings": {
|
||||
"synchronizationMode": "None",
|
||||
"operationMode": "Server",
|
||||
"autoExpandSelect": true,
|
||||
"earlyRequests": true,
|
||||
"groupProperties": {
|
||||
"default": {
|
||||
"submit": "Auto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"routing": {
|
||||
"routes": [
|
||||
{
|
||||
"pattern": ":?query:",
|
||||
"name": "GenresList",
|
||||
"target": "GenresList"
|
||||
},
|
||||
{
|
||||
"pattern": "Genres({key}):?query:",
|
||||
"name": "GenresDetails",
|
||||
"target": "GenresDetails"
|
||||
}
|
||||
],
|
||||
"targets": {
|
||||
"GenresList": {
|
||||
"type": "Component",
|
||||
"id": "GenresList",
|
||||
"name": "sap.fe.templates.ListReport",
|
||||
"options": {
|
||||
"settings": {
|
||||
"contextPath": "/Genres",
|
||||
"navigation": {
|
||||
"Genres": {
|
||||
"detail": {
|
||||
"route": "GenresDetails"
|
||||
}
|
||||
}
|
||||
},
|
||||
"controlConfiguration": {
|
||||
"@com.sap.vocabularies.UI.v1.LineItem": {
|
||||
"tableSettings": {
|
||||
"hierarchyQualifier": "GenreHierarchy",
|
||||
"type": "TreeTable"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"GenresDetails": {
|
||||
"type": "Component",
|
||||
"id": "GenresDetails",
|
||||
"name": "sap.fe.templates.ObjectPage",
|
||||
"options": {
|
||||
"settings": {
|
||||
"contextPath": "/Genres"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"contentDensities": {
|
||||
"compact": true,
|
||||
"cozy": true
|
||||
}
|
||||
},
|
||||
"sap.ui": {
|
||||
"technology": "UI5",
|
||||
"fullWidth": false
|
||||
},
|
||||
"sap.fiori": {
|
||||
"registrationIds": [],
|
||||
"archeType": "transactional"
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace sap.capire.bookshop;
|
||||
|
||||
using { sap.capire.bookshop } from '@capire/bookstore/srv/mashup';
|
||||
|
||||
entity GenreHierarchy : bookshop.Genres {
|
||||
hierarchyLevel : Integer default 0;
|
||||
drillState : String default 'leaf';
|
||||
parent : Association to GenreHierarchy;
|
||||
children : Composition of many GenreHierarchy on children.parent = $self;
|
||||
}
|
||||
|
||||
extend service CatalogService with {
|
||||
@readonly entity GenreHierarchy as projection on bookshop.GenreHierarchy;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
ID;parent_ID;name;hierarchyLevel;drillState
|
||||
10;;Fiction;0;expanded
|
||||
11;10;Drama;1;leaf
|
||||
12;10;Poetry;1;leaf
|
||||
13;10;Fantasy;1;leaf
|
||||
14;10;Science Fiction;1;leaf
|
||||
15;10;Romance;1;leaf
|
||||
16;10;Mystery;1;leaf
|
||||
17;10;Thriller;1;leaf
|
||||
18;10;Dystopia;1;leaf
|
||||
20;;Non-Fiction;0;expanded
|
||||
19;10;Fairy Tale;1;leaf
|
||||
21;20;Biography;1;expanded
|
||||
22;21;Autobiography;2;leaf
|
||||
23;20;Essay;1;leaf
|
||||
24;20;Speech;1;leaf
|
||||
|
32
fiori/server.js
Normal file
32
fiori/server.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const cds = require('@sap/cds/lib')
|
||||
if (cds.requires.db?.kind === 'sqlite') {
|
||||
cds.on ('serving:AdminService', srv => srv.prepend(() => {
|
||||
const {Genres} = srv.entities
|
||||
// Register a simplistic handler for hierarchical queries
|
||||
srv.on('READ', Genres, (req,next) => {
|
||||
const q = req.query
|
||||
// Expand query on a single row
|
||||
if (q.SELECT.recurse?.where?.[0].ref[0] === 'Distance') {
|
||||
q.SELECT.where[0] = 'parent_ID'
|
||||
// Initial query
|
||||
} else if (!q.SELECT.search && !is_count(q)) {
|
||||
q.SELECT.where ??= [ 'parent_ID is null' ]
|
||||
}
|
||||
// Use scalar subselect for DrillState
|
||||
q.SELECT.from.as = 'g'
|
||||
q.SELECT.columns = q.SELECT.columns.map (c => {
|
||||
if (c.ref == 'DrillState') return { xpr:[`
|
||||
CASE WHEN ( SELECT count(1) from ${Genres} where parent_ID = g.ID ) > 0
|
||||
THEN 'collapsed' ELSE 'leaf' END`
|
||||
], as: 'DrillState' }
|
||||
else return c
|
||||
})
|
||||
// Suppress error message: Feature "recurse" queries not supported.
|
||||
delete q.SELECT.__proto__.recurse
|
||||
delete q.SELECT.recurse
|
||||
return next()
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
const is_count = q => q.SELECT.columns?.length === 1 && q.SELECT.columns[0].func === 'count'
|
||||
Reference in New Issue
Block a user