diff --git a/README.md b/README.md index b652330c..29311a48 100644 --- a/README.md +++ b/README.md @@ -90,9 +90,10 @@ User Interface related Features: - [Model Localization](https://cap.cloud.sap/docs/guides/i18n) for [English](app/_i18n/i18n.properties) and [German](app/_i18n/i18n_de.properties) language for static texts - [Custom File Upload extension](app/admin/webapp/extension/Upload.js) which provides a button for uploading `CSV` files - A simple Swagger UI for the CatalogService API at -- UI5 [Tree Table](app/genres/webapp/manifest.json) with Value Help for [GenreHierarchy](app/admin/fiori-service.cds) +- UI5 [Tree Table](app/genres/webapp/manifest.json) with CRUD requests and Value Help for [GenreHierarchy](app/admin/fiori-service.cds). Only on HANA. To check tree table `Contents` embedded into `Books`, please remove `@UI.Hidden` annotation from [Fiori Service](app/admin/fiori-service.cds) - [Custom event handlers](https://cap.cloud.sap/docs/java/provisioning-api) for Tree Table such as the [Custom business logic for GenreHierarchy](srv/src/main/java/my/bookshop/handlers/HierarchyHandler.java). Please note, that Tree Tables must be used with HANA. Custom event handler in this case provides a limited support ment for local testing. +- [Custom actions](https://cap.cloud.sap/docs/cds/cdl#actions) such as `moveSibling` in the [Admin Service](srv/admin-service.cds). This action demonstrates experimental UI feature [ChangeNextSiblingAction](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/Hierarchy.md#template_changenextsiblingaction-experimental). The Action implementation is in the [Hierarchy Sibling Action Handler](srv/src/main/java/my/bookshop/handlers/HierarchySiblingActionHandler.java) CDS Maven Plugin Features: diff --git a/app/_i18n/i18n.properties b/app/_i18n/i18n.properties index a12b718d..94df904d 100644 --- a/app/_i18n/i18n.properties +++ b/app/_i18n/i18n.properties @@ -5,6 +5,7 @@ Created = Created Modified = Modified ModifiedBy = Modified By General = General +Contents = Contents Details = Details Admin = Administrative Translations = Translations @@ -63,7 +64,11 @@ Text = Text Image = Image Genres = Genres Genre = Genre - +PageNumber = Page Number +Informations = Informations +Page = Page +ContentsLevel = Contents Level +ContentsLevels = Contents Levels AddReview = Add Review Notes = Notes diff --git a/app/_i18n/i18n_de.properties b/app/_i18n/i18n_de.properties index 63c111fb..a134ff55 100644 --- a/app/_i18n/i18n_de.properties +++ b/app/_i18n/i18n_de.properties @@ -5,6 +5,7 @@ Created = Angelegt Modified = Bearbeitet ModifiedBy = Bearbeitet von General = Generelles +Contents = Buchinhalt Details = Details Admin = Administratives Translations = Übersetzungen @@ -63,6 +64,11 @@ Text = Text Image = Bild Genres = Genre Genre = Genre +PageNumber = Seitenzahl +Informations = Informationen +Page = Seite +ContentsLevel = Inhaltsebene +ContentsLevels = Inhaltsebenen AddReview = Rezension hinzufügen diff --git a/app/admin/fiori-service.cds b/app/admin/fiori-service.cds index c848916b..813afea9 100644 --- a/app/admin/fiori-service.cds +++ b/app/admin/fiori-service.cds @@ -36,6 +36,13 @@ annotate AdminService.Books with @(UI : { $Type : 'UI.ReferenceFacet', Label : '{i18n>Admin}', Target : '@UI.FieldGroup#Admin' + }, + { + // TODO: should work dynamically + @UI.Hidden, + $Type : 'UI.ReferenceFacet', + Label : '{i18n>Contents}', + Target : 'contents/@UI.PresentationVariant' } ], FieldGroup #General : {Data : [ @@ -60,10 +67,14 @@ annotate AdminService.Books with @(UI : { ]} }); -// Add Value Help for Tree Table + +//////////////////////////////////////////////////////////////////////////// +// +// Value Help for Tree Table +// annotate AdminService.Books with { genre @(Common: { - Label : 'Genre', + Label : '{i18n>Genre}', ValueList: { CollectionPath : 'GenreHierarchy', Parameters : [ @@ -87,6 +98,12 @@ annotate AdminService.GenreHierarchy with { ID @UI.Hidden; }; +@Hierarchy.RecursiveHierarchyActions #GenreHierarchy: { + $Type : 'Hierarchy.RecursiveHierarchyActionsType', + // any name can be the action name with namespace/no bound action name + ChangeNextSiblingAction: 'AdminService.moveSibling', +} + annotate AdminService.GenreHierarchy with @UI: { PresentationVariant #VH: { $Type : 'UI.PresentationVariantType', @@ -96,7 +113,48 @@ annotate AdminService.GenreHierarchy with @UI: { LineItem : [{ $Type: 'UI.DataField', Value: name, - }] + Label : '{i18n>Genre}' + }], +}; + +annotate AdminService.ContentsHierarchy with @UI: { + PresentationVariant : { + $Type : 'UI.PresentationVariantType', + RequestAtLeast: [name], + Visualizations: ['@UI.LineItem'], + }, + LineItem : [{ + $Type: 'UI.DataField', + Value: name, + Label : '{i18n>Name}' + }, + { + $Type: 'UI.DataField', + Value: page, + Label : '{i18n>Page}' + }], + HeaderInfo : { + $Type : 'UI.HeaderInfoType', + TypeName : '{i18n>ContentsLevel}', + TypeNamePlural: '{i18n>ContentsLevels}', + Title : { + $Type: 'UI.DataField', + Value: name, + } + }, + FieldGroup : { + $Type: 'UI.FieldGroupType', + Data : [{ + $Type: 'UI.DataField', + Value: page, + Label : '{i18n>PageNumber}' + }], + }, + Facets : [{ + $Type : 'UI.ReferenceFacet', + Target: '@UI.FieldGroup', + Label : '{i18n>Informations}', + }], }; //////////////////////////////////////////////////////////// @@ -138,6 +196,26 @@ annotate AdminService.Books.texts { } } +//////////////////////////////////////////////////////////// +// +// Annotations for hierarchy ContentsHierarchy +// +annotate AdminService.ContentsHierarchy with @Aggregation.RecursiveHierarchy#ContentsHierarchy: { + $Type: 'Aggregation.RecursiveHierarchyType', + NodeProperty: ID, // identifies a node + ParentNavigationProperty: parent // navigates to a node's parent + }; + +annotate AdminService.ContentsHierarchy with @Hierarchy.RecursiveHierarchy#ContentsHierarchy: { + $Type: 'Hierarchy.RecursiveHierarchyType', + LimitedDescendantCount: LimitedDescendantCount, + DistanceFromRoot: DistanceFromRoot, + DrillState: DrillState, + Matched: Matched, + MatchedDescendantCount: MatchedDescendantCount, + LimitedRank: LimitedRank +}; + annotate AdminService.Books actions { @( Common.SideEffects : { diff --git a/app/admin/webapp/manifest.json b/app/admin/webapp/manifest.json index 29ac7e46..49d05c2c 100644 --- a/app/admin/webapp/manifest.json +++ b/app/admin/webapp/manifest.json @@ -51,7 +51,7 @@ "settings": { "synchronizationMode": "None", "operationMode": "Server", - "autoExpandSelect" : true, + "autoExpandSelect": true, "earlyRequests": true, "groupProperties": { "default": { @@ -74,9 +74,9 @@ "target": "BooksDetails" }, { - "pattern": "Books({key}/author({key2}):?query:", - "name": "AuthorsDetails", - "target": "AuthorsDetails" + "pattern": "Books({key})/contents({key2}):?query:", + "name": "ContentsDetails", + "target": "ContentsDetails" } ], "targets": { @@ -115,22 +115,30 @@ "settings" : { "contextPath" : "/Books", "navigation" : { - "Authors" : { - "detail" : { - "route" : "AuthorsDetails" + "contents": { + "detail": { + "route": "ContentsDetails" + } + } + }, + "controlConfiguration": { + "contents/@com.sap.vocabularies.UI.v1.LineItem": { + "tableSettings": { + "hierarchyQualifier": "ContentsHierarchy", + "type": "TreeTable" } } } } } }, - "AuthorsDetails": { + "ContentsDetails": { "type": "Component", - "id": "AuthorsDetailsList", + "id": "ContentsDetails", "name": "sap.fe.templates.ObjectPage", "options": { - "settings" : { - "contextPath" : "/Authors" + "settings": { + "contextPath": "/Books/contents" } } } diff --git a/app/appconfig/fioriSandboxConfig.json b/app/appconfig/fioriSandboxConfig.json index 48b2ef6a..5c030843 100644 --- a/app/appconfig/fioriSandboxConfig.json +++ b/app/appconfig/fioriSandboxConfig.json @@ -20,7 +20,8 @@ "title": "Browse Books", "description": "Find your favorite book" } - }, { + }, + { "id": "browse-genres", "tileType": "sap.ushell.ui.tile.StaticTile", "properties": { @@ -64,6 +65,15 @@ "title": "Manage Reviews", "description": "Add/edit/delete reviews" } + }, + { + "id": "manage-genres", + "tileType": "sap.ushell.ui.tile.StaticTile", + "properties": { + "targetURL": "#Genres-manage", + "title": "Manage Genres", + "description": "Add/edit/delete genres" + } } ] }, @@ -129,8 +139,8 @@ }, "resolutionResult": { "applicationType": "SAPUI5", - "additionalInformation": "SAPUI5.Component=genres", - "url": "/genres/webapp" + "additionalInformation": "SAPUI5.Component=browse-genres", + "url": "/browse-genres/webapp" } }, "manage-books": { @@ -146,6 +156,19 @@ "url": "/admin/webapp" } }, + "manage-genres": { + "semanticObject": "Genres", + "action": "manage", + "signature": { + "parameters": {}, + "additionalParameters": "allowed" + }, + "resolutionResult": { + "applicationType": "SAPUI5", + "additionalInformation": "SAPUI5.Component=genres", + "url": "/genres/webapp" + } + }, "manage-orders": { "semanticObject": "Orders", "action": "manage", diff --git a/app/browse-genres/fiori-service.cds b/app/browse-genres/fiori-service.cds new file mode 100644 index 00000000..0fe951c0 --- /dev/null +++ b/app/browse-genres/fiori-service.cds @@ -0,0 +1,22 @@ +/* + UI annotations for the Browse GenreHierarchy App +*/ + +using CatalogService from '../../srv/cat-service'; + + +annotate CatalogService.GenreHierarchy with @Aggregation.RecursiveHierarchy#GenreHierarchy: { + $Type: 'Aggregation.RecursiveHierarchyType', + NodeProperty: ID, // identifies a node + ParentNavigationProperty: parent // navigates to a node's parent + }; + +annotate CatalogService.GenreHierarchy with @Hierarchy.RecursiveHierarchy#GenreHierarchy: { + $Type: 'Hierarchy.RecursiveHierarchyType', + LimitedDescendantCount: LimitedDescendantCount, + DistanceFromRoot: DistanceFromRoot, + DrillState: DrillState, + Matched: Matched, + MatchedDescendantCount: MatchedDescendantCount, + LimitedRank: LimitedRank +}; diff --git a/app/browse-genres/package.json b/app/browse-genres/package.json new file mode 100644 index 00000000..2809c302 --- /dev/null +++ b/app/browse-genres/package.json @@ -0,0 +1,12 @@ +{ + "name": "browse-genres", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/app/browse-genres/webapp/Component.js b/app/browse-genres/webapp/Component.js new file mode 100644 index 00000000..5fa2ad5d --- /dev/null +++ b/app/browse-genres/webapp/Component.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/fe/core/AppComponent"], ac => ac.extend("browse-genres.Component", { + metadata:{ manifest:'json' } +})) diff --git a/app/browse-genres/webapp/i18n/i18n.properties b/app/browse-genres/webapp/i18n/i18n.properties new file mode 100644 index 00000000..d2792ee9 --- /dev/null +++ b/app/browse-genres/webapp/i18n/i18n.properties @@ -0,0 +1,2 @@ +appTitle=Browse Genres +appDescription=Genres as Tree View diff --git a/app/browse-genres/webapp/i18n/i18n_de.properties b/app/browse-genres/webapp/i18n/i18n_de.properties new file mode 100644 index 00000000..e8714e92 --- /dev/null +++ b/app/browse-genres/webapp/i18n/i18n_de.properties @@ -0,0 +1,2 @@ +appTitle=Zeige Genres +appDescription=Genres als Baumansicht diff --git a/app/browse-genres/webapp/index.html b/app/browse-genres/webapp/index.html new file mode 100644 index 00000000..167596a7 --- /dev/null +++ b/app/browse-genres/webapp/index.html @@ -0,0 +1,35 @@ + + + + + + + Browse Genres + + + + +
+ + diff --git a/app/browse-genres/webapp/manifest.json b/app/browse-genres/webapp/manifest.json new file mode 100644 index 00000000..875c6231 --- /dev/null +++ b/app/browse-genres/webapp/manifest.json @@ -0,0 +1,124 @@ +{ + "_version": "1.8.0", + "sap.app": { + "id": "browse-genres", + "type": "application", + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "applicationVersion": { + "version": "1.0.0" + }, + "dataSources": { + "CatalogService": { + "uri": "/api/browse/", + "type": "OData", + "settings": { + "odataVersion": "4.0" + } + } + }, + "crossNavigation": { + "inbounds": { + "Genres-show": { + "signature": { + "parameters": {}, + "additionalParameters": "allowed" + }, + "semanticObject": "GenreHierarchy", + "action": "show" + } + } + } + }, + "sap.ui5": { + "dependencies": { + "minUI5Version": "1.122.0", + "libs": { + "sap.fe.templates": {} + } + }, + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "uri": "i18n/i18n.properties" + }, + "": { + "dataSource": "CatalogService", + "settings": { + "synchronizationMode": "None", + "operationMode": "Server", + "autoExpandSelect" : true, + "earlyRequests": true, + "groupProperties": { + "default": { + "submit": "Auto" + } + } + } + } + }, + "routing": { + "routes": [ + { + "pattern": ":?query:", + "name": "GenreHierarchyList", + "target": "GenreHierarchyList" + }, + { + "pattern": "GenreHierarchy({key}):?query:", + "name": "GenreHierarchyDetails", + "target": "GenreHierarchyDetails" + } + ], + "targets": { + "GenreHierarchyList": { + "type": "Component", + "id": "GenreHierarchyList", + "name": "sap.fe.templates.ListReport", + "options": { + "settings" : { + "contextPath" : "/GenreHierarchy", + "navigation" : { + "GenreHierarchy" : { + "detail" : { + "route" : "GenreHierarchyDetails" + } + } + }, + "controlConfiguration": { + "@com.sap.vocabularies.UI.v1.LineItem": { + "tableSettings": { + "hierarchyQualifier": "GenreHierarchy", + "type": "TreeTable" + } + } + } + } + } + }, + "GenreHierarchyDetails": { + "type": "Component", + "id": "GenreHierarchyDetails", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings" : { + "contextPath" : "/GenreHierarchy" + } + } + } + } + }, + "contentDensities": { + "compact": true, + "cozy": true + } + }, + "sap.ui": { + "technology": "UI5", + "fullWidth": false + }, + "sap.fiori": { + "registrationIds": [], + "archeType": "transactional" + } +} diff --git a/app/browse/fiori-service.cds b/app/browse/fiori-service.cds index 953c39dc..07ecb564 100644 --- a/app/browse/fiori-service.cds +++ b/app/browse/fiori-service.cds @@ -161,6 +161,19 @@ annotate CatalogService.Reviews with @(UI : { ]} }); +annotate CatalogService.GenreHierarchy with @UI: { + PresentationVariant : { + $Type : 'UI.PresentationVariantType', + RequestAtLeast: [name], + Visualizations: ['@UI.LineItem'], + }, + LineItem : [{ + $Type: 'UI.DataField', + Value: name, + Label : '{i18n>Genre}' + }], +}; + annotate CatalogService.Books actions { @( Common.SideEffects : { diff --git a/app/common.cds b/app/common.cds index ba638052..f9df40b2 100644 --- a/app/common.cds +++ b/app/common.cds @@ -206,8 +206,6 @@ annotate my.Genres with // Genres Elements // annotate my.Genres with { - ID - @title : '{i18n>ID}'; name @title : '{i18n>Genre}'; } diff --git a/app/fiori.html b/app/fiori.html index 7fa46503..845ec889 100644 --- a/app/fiori.html +++ b/app/fiori.html @@ -14,8 +14,8 @@ }; - - +