From feb8a16d5e5fa6c9f702b2904d948672e52ed58b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 10:36:32 +0000 Subject: [PATCH 1/2] Add package 11: RAP file upload through a deep action parameter A new package with its own floor (ABAP Platform 2025): the business object Z2UI5_R_SMPS_FIL takes a file through its static action uploadFile, whose deep parameter (abstract entities Z2UI5_A_SMPS_FILE_UPLOAD and Z2UI5_A_SMPS_FILE_STREAM, @Semantics.largeObject) carries content, MIME type and name. Z2UI5_CL_SMPS_APP_016 picks the file with the z2ui5 FileUploader, calls the action with EML, and lists, downloads and deletes the stored files. The package README also explains why the file size limit is the same as for Fiori Elements: OData V4 JSON carries Edm.Binary as base64 as well. Overview app, packages.json, both README tables, SAMPLES.md and the two catalogue files are updated; the package count in the prose goes from ten to eleven. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NNqpKVX6ESgcLVqeWXeHc5 --- .github/packages.json | 9 + AGENTS.md | 4 +- README.md | 17 +- SAMPLES.md | 11 +- catalogue-derived.json | 10 +- catalogue.json | 39 ++ src/01/README.md | 2 +- src/02/README.md | 2 +- src/03/README.md | 2 +- src/04/README.md | 2 +- src/05/README.md | 2 +- src/06/README.md | 2 +- src/07/README.md | 2 +- src/08/README.md | 2 +- src/09/README.md | 2 +- src/10/README.md | 2 +- src/11/01/package.devc.xml | 10 + .../01/z2ui5_a_smps_file_stream.bdef.asbdef | 10 + src/11/01/z2ui5_a_smps_file_stream.bdef.xml | 45 +++ .../01/z2ui5_a_smps_file_stream.ddls.asddls | 18 + src/11/01/z2ui5_a_smps_file_stream.ddls.xml | 13 + .../01/z2ui5_a_smps_file_upload.bdef.asbdef | 12 + src/11/01/z2ui5_a_smps_file_upload.bdef.xml | 45 +++ .../01/z2ui5_a_smps_file_upload.ddls.asddls | 14 + src/11/01/z2ui5_a_smps_file_upload.ddls.xml | 13 + src/11/01/z2ui5_cl_smps_bp_fil.clas.abap | 10 + .../z2ui5_cl_smps_bp_fil.clas.locals_imp.abap | 98 +++++ src/11/01/z2ui5_cl_smps_bp_fil.clas.xml | 18 + src/11/01/z2ui5_r_smps_fil.bdef.asbdef | 42 ++ src/11/01/z2ui5_r_smps_fil.bdef.xml | 45 +++ src/11/01/z2ui5_r_smps_fil.ddls.asddls | 26 ++ src/11/01/z2ui5_r_smps_fil.ddls.baseinfo | 7 + src/11/01/z2ui5_r_smps_fil.ddls.xml | 13 + src/11/01/z2ui5_t_smps_fil.tabl.xml | 129 +++++++ src/11/README.md | 108 ++++++ src/11/package.devc.xml | 10 + src/11/z2ui5_cl_smps_app_016.clas.abap | 363 ++++++++++++++++++ src/11/z2ui5_cl_smps_app_016.clas.xml | 16 + src/z2ui5_cl_smps_app_000.clas.abap | 14 + 39 files changed, 1164 insertions(+), 25 deletions(-) create mode 100644 src/11/01/package.devc.xml create mode 100644 src/11/01/z2ui5_a_smps_file_stream.bdef.asbdef create mode 100644 src/11/01/z2ui5_a_smps_file_stream.bdef.xml create mode 100644 src/11/01/z2ui5_a_smps_file_stream.ddls.asddls create mode 100644 src/11/01/z2ui5_a_smps_file_stream.ddls.xml create mode 100644 src/11/01/z2ui5_a_smps_file_upload.bdef.asbdef create mode 100644 src/11/01/z2ui5_a_smps_file_upload.bdef.xml create mode 100644 src/11/01/z2ui5_a_smps_file_upload.ddls.asddls create mode 100644 src/11/01/z2ui5_a_smps_file_upload.ddls.xml create mode 100644 src/11/01/z2ui5_cl_smps_bp_fil.clas.abap create mode 100644 src/11/01/z2ui5_cl_smps_bp_fil.clas.locals_imp.abap create mode 100644 src/11/01/z2ui5_cl_smps_bp_fil.clas.xml create mode 100644 src/11/01/z2ui5_r_smps_fil.bdef.asbdef create mode 100644 src/11/01/z2ui5_r_smps_fil.bdef.xml create mode 100644 src/11/01/z2ui5_r_smps_fil.ddls.asddls create mode 100644 src/11/01/z2ui5_r_smps_fil.ddls.baseinfo create mode 100644 src/11/01/z2ui5_r_smps_fil.ddls.xml create mode 100644 src/11/01/z2ui5_t_smps_fil.tabl.xml create mode 100644 src/11/README.md create mode 100644 src/11/package.devc.xml create mode 100644 src/11/z2ui5_cl_smps_app_016.clas.abap create mode 100644 src/11/z2ui5_cl_smps_app_016.clas.xml diff --git a/.github/packages.json b/.github/packages.json index c45dbcc..10b8a05 100644 --- a/.github/packages.json +++ b/.github/packages.json @@ -80,5 +80,14 @@ "syntax": "v751", "note": "The HTTP transport comes once per stack, and each half activates on its own stack only: src/10/01 (cl_http_client on an SM59 destination) on Standard, src/10/02 (cl_web_http_client_manager on a BTP destination) on ABAP Cloud, src/10/03 only where SAP's ABAP AI SDK is installed. An activation error for the half your system does not have is expected - the samples create the transport by name and use the one that is there. That is also why this branch is linted at 7.51 although the package runs from 7.40 SP08: everything but src/10/02 parses at 7.40 SP08, and src/10/02 is ABAP Cloud code whose released enums need 7.51 syntax.", "shared": [] + }, + { + "dir": "11", + "branch": "11-rap-file-upload", + "title": "RAP File Upload", + "runsOn": "Cloud + Standard ≥ 8.16 (2025)", + "syntax": "v757", + "note": "A deep action parameter whose abstract entity carries @Semantics.largeObject is younger than EML: the ABAP parses at 7.57, the feature itself arrived with ABAP Platform 2025 (and the matching BTP ABAP Environment and S/4HANA Cloud releases). If the static action uploadFile does not activate on your system, this package is out of reach.", + "shared": ["00"] } ] diff --git a/AGENTS.md b/AGENTS.md index f95e2c8..4c8742b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,7 +71,7 @@ is a link to the class file, which is the qualification. ## 3. The generated one-package branches -abapGit imports a whole repository; there is no sparse checkout. Ten packages +abapGit imports a whole repository; there is no sparse checkout. Eleven packages with different floors would mean a system that wants OData also takes APC and gets activation errors for technology it never asked for. So every package is force-pushed to its own branch by `create-package-branches.yaml`, and the @@ -409,7 +409,7 @@ pass over the classes on `main` — `stats.types` and the `*-too-new` findings. - **The overview app stays out of the catalogue page** for the reason it was always out: `Z2UI5_CL_SMPS_APP_000` is this same catalogue inside a system, and as a card it contradicts a list built around what a sample needs — *needs - nothing beyond abap2UI5* — plus a technology chip that filters ten groups + nothing beyond abap2UI5* — plus a technology chip that filters eleven groups down to itself. `SAMPLES.md` and both JSON files keep it; their reader is in the repository already. - **Thumbnails** were photographed on every deploy by diff --git a/README.md b/README.md index 5a74a3b..15980b3 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ The Claude Code plugin is two commands: ## Which package do I need? -Most readers need exactly one. The ten areas are unrelated technologies, none +Most readers need exactly one. The eleven areas are unrelated technologies, none depends on another — so find the row that says what you came to do, take that package, and skip the rest: @@ -80,6 +80,7 @@ package, and skip the rest: | Play or serve a file the MIME repository already holds | [`src/08`](src/08/README.md) — MIME Play Audio | on-premise; activate one ICF service in `SICF` | | Expose your app in the Fiori Launchpad — tile, startup parameters, cross-app navigation | [`src/09`](src/09/README.md) — Launchpad | a launchpad with a tile pointing at abap2UI5 | | Call a large language model from your app — a chat, or a table summarized by AI | [`src/10`](src/10/README.md) — AI / LLM | an HTTPS destination to an LLM endpoint (SM59 or BTP), or SAP's ABAP AI SDK | +| Upload a file into a RAP business object through an action with a deep parameter | [`src/11`](src/11/README.md) — RAP File Upload | ≥ 2025, the release that carries deep action parameters with `@Semantics.largeObject`; the business object ships with the package | Each package README opens with a **What you need** section that turns the last column into concrete steps; the table below adds the exact release floors. @@ -98,6 +99,7 @@ column into concrete steps; the table below adds the exact release floors. | [`src/08`](src/08) | **[MIME Play Audio](src/08/README.md)** — play a sound from the MIME repository | the ICF service `/SAP/PUBLIC/BC/ABAP/mime_demo` | Standard only, ≥ 7.50 | | [`src/09`](src/09) | **[Launchpad](src/09/README.md)** — startup parameters, shell title, cross-app navigation | a Fiori Launchpad with a tile pointing at abap2UI5 | Cloud + Standard ≥ 7.40 SP08 | | [`src/10`](src/10) | **[AI / LLM](src/10/README.md)** — chat with a language model, summarize a table with AI | an HTTPS destination to an LLM endpoint (SM59 or BTP), or SAP's ABAP AI SDK | Cloud + Standard ≥ 7.40 SP08 | +| [`src/11`](src/11) | **[RAP File Upload](src/11/README.md)** — a file into a business object, as a deep action parameter | ABAP Platform >= 2025; the BO ships with this repo | Cloud + Standard ≥ 8.16 (2025) | The numbering is a reading order, not a dependency chain: `01` starts where most systems already are — an activated OData service — and each package from @@ -135,9 +137,10 @@ is the higher of two floors: tree, and the number in the table is the lowest release the package parses clean at. - *the technology the package plays with.* EML lifts `03` and `04` to 1909, RAP - business events lift `05` to 2021. If `RAISE ENTITY EVENT` does not activate on - your system, `05` is out of reach and nothing else in this repository is - affected. + business events lift `05` to 2021, a deep action parameter carrying a file + lifts `11` to 2025. If `RAISE ENTITY EVENT` or the action `uploadFile` does not + activate on your system, that package is out of reach and nothing else in this + repository is affected. - *the HTTP client of the stack.* `10` calls a language model over HTTPS, and the two stacks release different clients for that — `cl_http_client` on an SM59 destination on Standard, `cl_web_http_client_manager` on ABAP Cloud. So @@ -151,7 +154,7 @@ of them. ## Setup 1. Install [abap2UI5](https://github.com/abap2UI5/abap2UI5). -2. Pull this repository with [abapGit](https://abapgit.org) — `main` for all ten +2. Pull this repository with [abapGit](https://abapgit.org) — `main` for all eleven packages, or the branch of the single package you came for (see [One package at a time](#one-package-at-a-time)). `main` as a whole runs on ABAP Platform >= 1909 or a BTP ABAP Environment — that is what EML asks for, @@ -185,7 +188,7 @@ It is also the honest answer to *what does my system actually support*: the overview looks every sample up at runtime instead of referencing it statically, so a package your release cannot activate — or one you never installed — is listed with its Open button disabled and a Status saying so, rather than taking the whole -overview down. Start it first, and the list tells you which of the ten packages +overview down. Start it first, and the list tells you which of the eleven packages this system can run. ## One package at a time @@ -286,7 +289,7 @@ in CI. The node checks carry no dependencies, so they take seconds. | `check-framework-pin` | the abaplint config pins abap2UI5 to a release tag, never to whatever is on `main` | | `check-released-api` | every package whose *Runs on* says Cloud, linted at Cloud against the released objects — an API ABAP Cloud does not release is an error | | `check-catalogue` | [`catalogue.json`](catalogue.json) and `catalogue-derived.json` still are what the generators would write — the same catalogue as data, committed for tooling that fetches one file instead of scanning the tree — and with them every package has a README row that parses, every app sits in a package, and none is missing its `@summary` or `@keywords` | -| `create-package-branches` | rebuilds the ten per-package branches, each verified with abaplint at its own release before it is pushed | +| `create-package-branches` | rebuilds the eleven per-package branches, each verified with abaplint at its own release before it is pushed | `check-overview` exists because the overview app names its samples as strings and resolves them at runtime — that is what lets it survive a package the system cannot diff --git a/SAMPLES.md b/SAMPLES.md index bdb496f..c2f5364 100644 --- a/SAMPLES.md +++ b/SAMPLES.md @@ -3,7 +3,7 @@ # The sample catalogue -Every app in this repository — 35 of them — with what it shows and a +Every app in this repository — 36 of them — with what it shows and a link to its source. This is the [overview app](src/z2ui5_cl_smps_app_000.clas.abap) as a page you can read here, before installing anything. @@ -116,11 +116,17 @@ type who does not know it exists. `Ctrl+F` on this page uses them. | Chat with a Language Model
a chat with a real language model over HTTPS - the conversation goes out, the answer comes back, the provider is configuration
ai llm chat chatbot feedinput feedlistitem anthropic openai | [`Z2UI5_CL_SMPS_APP_014`](src/10/z2ui5_cl_smps_app_014.clas.abap) | | Summarize a Table with AI
a table of sales figures and one button - the rows go to a language model as context, its summary comes back into a panel
ai llm chat summarize table prompt hygiene anthropic | [`Z2UI5_CL_SMPS_APP_015`](src/10/z2ui5_cl_smps_app_015.clas.abap) | +### RAP File Upload — `src/11` + +| Sample | Class | +|---|---| +| Deep Action Parameter
EXECUTE uploadFile with a deep parameter - the file travels as a stream property, download and delete included
file upload attachment rap action deep parameter largeobject stream eml fileuploader download | [`Z2UI5_CL_SMPS_APP_016`](src/11/z2ui5_cl_smps_app_016.clas.abap) | + --- ## Not samples -16 classes here are not apps and carry no tile: behavior pools, +17 classes here are not apps and carry no tile: behavior pools, demo data, an event consumer, the generated APC protocol class and the language-model layer the AI samples call. They are reached **by** a sample rather than looked up, which is why they are exempt from @@ -145,3 +151,4 @@ able to say they exist. | [`Z2UI5_CL_SMPS_LLM_SM59`](src/10/01/z2ui5_cl_smps_llm_sm59.clas.abap) | LLM - HTTP Transport over an SM59 Destination | | [`Z2UI5_CL_SMPS_LLM_CLOUD`](src/10/02/z2ui5_cl_smps_llm_cloud.clas.abap) | LLM - HTTP Transport for ABAP Cloud | | [`Z2UI5_CL_SMPS_LLM_ISLM`](src/10/03/z2ui5_cl_smps_llm_islm.clas.abap) | LLM - SAP ABAP AI SDK (ISLM) | +| [`Z2UI5_CL_SMPS_BP_FIL`](src/11/01/z2ui5_cl_smps_bp_fil.clas.abap) | abap2UI5 RAP sample - file behavior pool | diff --git a/catalogue-derived.json b/catalogue-derived.json index 46631b2..5be6c16 100644 --- a/catalogue-derived.json +++ b/catalogue-derived.json @@ -61,15 +61,16 @@ "sap.m.CheckBox", "sap.m.Select", "sap.ui.core.Item", - "sap.m.ObjectNumber" + "sap.m.ObjectNumber", + "z2ui5.cc.FileUploader" ], "counts": { - "samples": 35, - "controls": 54, + "samples": 36, + "controls": 55, "noChain": 0 }, "samples": [ - {"class":"Z2UI5_CL_SMPS_APP_000","minUi5":"1.71","needs":[],"controls":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"controlCount":248}, + {"class":"Z2UI5_CL_SMPS_APP_000","minUi5":"1.71","needs":[],"controls":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"controlCount":271}, {"class":"Z2UI5_CL_SMPS_APP_001","minUi5":"1.71","needs":[],"controls":[1,22,37,7,10,17,38],"controlCount":21}, {"class":"Z2UI5_CL_SMPS_APP_002","minUi5":"1.71","needs":[],"controls":[1,39,22,37,7,10,17,38],"controlCount":22}, {"class":"Z2UI5_CL_SMPS_APP_003","minUi5":"1.71","needs":[],"controls":[1,2,3,22,7,10,11,12,13,18,17],"controlCount":19}, @@ -85,6 +86,7 @@ {"class":"Z2UI5_CL_SMPS_APP_013","minUi5":"1.71","needs":[],"controls":[1,50,22,37,5,23,7,51,10,41,52,17,38],"controlCount":28}, {"class":"Z2UI5_CL_SMPS_APP_014","minUi5":"1.71","needs":[],"controls":[1,43,44,22,45,5,7,10,14,17],"controlCount":11}, {"class":"Z2UI5_CL_SMPS_APP_015","minUi5":"1.71","needs":[],"controls":[1,2,3,22,37,5,53,7,8,10,11,12,14,17],"controlCount":38}, + {"class":"Z2UI5_CL_SMPS_APP_016","minUi5":"1.71","needs":[],"controls":[1,2,3,31,22,37,5,7,10,11,12,13,18,41,17,38,54],"controlCount":35}, {"class":"Z2UI5_CL_SMPS_APP_313","minUi5":"1.71","needs":[],"controls":[7,10,19,20,21,17],"controlCount":6}, {"class":"Z2UI5_CL_SMPS_APP_314","minUi5":"1.71","needs":[],"controls":[2,3,22,7,10,11,12,13,18,17],"controlCount":31}, {"class":"Z2UI5_CL_SMPS_APP_315","minUi5":"1.71","needs":[],"controls":[2,3,7,10,11,12,13,18,17],"controlCount":35}, diff --git a/catalogue.json b/catalogue.json index 6c07690..c8f2987 100644 --- a/catalogue.json +++ b/catalogue.json @@ -136,6 +136,18 @@ "note": "The HTTP transport comes once per stack, and each half activates on its own stack only: src/10/01 (cl_http_client on an SM59 destination) on Standard, src/10/02 (cl_web_http_client_manager on a BTP destination) on ABAP Cloud, src/10/03 only where SAP's ABAP AI SDK is installed. An activation error for the half your system does not have is expected - the samples create the transport by name and use the one that is there. That is also why this branch is linted at 7.51 although the package runs from 7.40 SP08: everything but src/10/02 parses at 7.40 SP08, and src/10/02 is ABAP Cloud code whose released enums need 7.51 syntax.", "branch": "10-ai-llm", "readme": "src/10/README.md" + }, + { + "package": "src/11", + "technology": "RAP File Upload", + "topic": "a file into a business object, as a deep action parameter", + "needs": "ABAP Platform >= 2025; the BO ships with this repo", + "runsOn": "Cloud + Standard ≥ 8.16 (2025)", + "cloud": true, + "release": "8.16 (2025)", + "note": "A deep action parameter whose abstract entity carries @Semantics.largeObject is younger than EML: the ABAP parses at 7.57, the feature itself arrived with ABAP Platform 2025 (and the matching BTP ABAP Environment and S/4HANA Cloud releases). If the static action uploadFile does not activate on your system, this package is out of reach.", + "branch": "11-rap-file-upload", + "readme": "src/11/README.md" } ], "samples": [ @@ -902,6 +914,33 @@ "needs": "an HTTPS destination to an LLM endpoint (SM59 or BTP), or SAP's ABAP AI SDK", "branch": "10-ai-llm", "setup": "src/10/README.md" + }, + { + "class": "Z2UI5_CL_SMPS_APP_016", + "path": "src/11/z2ui5_cl_smps_app_016.clas.abap", + "package": "src/11", + "technology": "RAP File Upload", + "title": "Deep Action Parameter", + "summary": "EXECUTE uploadFile with a deep parameter - the file travels as a stream property, download and delete included", + "keywords": [ + "file", + "upload", + "attachment", + "rap", + "action", + "deep", + "parameter", + "largeobject", + "stream", + "eml", + "fileuploader", + "download" + ], + "runsOn": "Cloud + Standard ≥ 8.16 (2025)", + "cloud": true, + "needs": "ABAP Platform >= 2025; the BO ships with this repo", + "branch": "11-rap-file-upload", + "setup": "src/11/README.md" } ] } diff --git a/src/01/README.md b/src/01/README.md index f1c3f99..8714c9b 100644 --- a/src/01/README.md +++ b/src/01/README.md @@ -15,7 +15,7 @@ view and the service path, nothing more. paths, so nothing here reaches past the ABAP the package is written in. **Branch:** [`01-odata`](https://github.com/abap2UI5/samples-stack/tree/01-odata) -— this package alone, without the other nine on your system. +— this package alone, without the other ten on your system. An activated OData V2 service. The sample points at the services of the SAP flight reference scenario: diff --git a/src/02/README.md b/src/02/README.md index ed3a77e..6b711f8 100644 --- a/src/02/README.md +++ b/src/02/README.md @@ -16,7 +16,7 @@ side of these nine samples asks for nothing beyond the release the package is written in — what they need is the service, not the platform. **Branch:** [`02-smart-controls`](https://github.com/abap2UI5/samples-stack/tree/02-smart-controls) -— this package alone, without the other nine on your system. +— this package alone, without the other ten on your system. - **SAPUI5**, since `sap.ui.comp` is part of the SAPUI5 distribution. - **An activated OData V2 service.** Most samples point at the Gateway demo service diff --git a/src/03/README.md b/src/03/README.md index afcc5d7..142fc59 100644 --- a/src/03/README.md +++ b/src/03/README.md @@ -24,7 +24,7 @@ The draft enabled half lives next door in [`04` RAP with Draft](../04/README.md) **Release:** Cloud + Standard ≥ 7.54 (1909) — that is what EML asks for. **Branch:** [`03-rap`](https://github.com/abap2UI5/samples-stack/tree/03-rap) -— this package alone, without the other nine on your system. +— this package alone, without the other ten on your system. ABAP Platform >= 1909 or a BTP ABAP Environment. The business object `Z2UI5_R_SMPS_TRV` and its table come with this package ([`src/03/01`](01)), so diff --git a/src/04/README.md b/src/04/README.md index 4863236..4153b61 100644 --- a/src/04/README.md +++ b/src/04/README.md @@ -16,7 +16,7 @@ whole lifecycle fits in a handful of statements. [`03`](../03/README.md) — draft handling adds nothing on top of EML. **Branch:** [`04-rap-draft`](https://github.com/abap2UI5/samples-stack/tree/04-rap-draft) -— this package alone, without the other nine on your system. +— this package alone, without the other ten on your system. ABAP Platform >= 1909 or a BTP ABAP Environment. The draft enabled business object and its two tables come with this package ([`src/04/01`](01)). diff --git a/src/05/README.md b/src/05/README.md index c62d7db..5c87aa5 100644 --- a/src/05/README.md +++ b/src/05/README.md @@ -17,7 +17,7 @@ the second shows what arrived. RAP does the wiring in between. repository, and the only package whose limit is not EML. **Branch:** [`05-business-events`](https://github.com/abap2UI5/samples-stack/tree/05-business-events) -— this package alone, without the other nine on your system. +— this package alone, without the other ten on your system. ABAP Platform >= 1909 or a BTP ABAP Environment covers the EML part — but this package also needs a release that already carries **RAP business events**. They are diff --git a/src/06/README.md b/src/06/README.md index 039b786..b574857 100644 --- a/src/06/README.md +++ b/src/06/README.md @@ -18,7 +18,7 @@ function modules nor a stateful ICF session is a released cloud API — this pac is on-premise by design, not by omission. **Branch:** [`06-stateful-locks`](https://github.com/abap2UI5/samples-stack/tree/06-stateful-locks) -— this package alone, without the other nine on your system. +— this package alone, without the other ten on your system. ABAP Standard (on-premise). The locks go through the function modules `ENQUEUE_E_TABLE`, `DEQUEUE_E_TABLE` and `ENQUEUE_READ`, which are available diff --git a/src/07/README.md b/src/07/README.md index b0e028c..6dd8e09 100644 --- a/src/07/README.md +++ b/src/07/README.md @@ -18,7 +18,7 @@ ABAP Cloud counterpart; the 7.50 comes from the ABAP the two classes are written in, the channels themselves arrived earlier. **Branch:** [`07-amc-apc`](https://github.com/abap2UI5/samples-stack/tree/07-amc-apc) -— this package alone, without the other nine on your system. +— this package alone, without the other ten on your system. **Setup:** activate the ICF service `/sap/bc/apc/sap/z2ui5_apc_smp_2` in `SICF`. The app checks this itself and shows a friendly warning strip while the node is still diff --git a/src/08/README.md b/src/08/README.md index 8a0a3d1..7a39203 100644 --- a/src/08/README.md +++ b/src/08/README.md @@ -17,7 +17,7 @@ on-premise: in ABAP Cloud there is neither the repository nor the node to reach through. **Branch:** [`08-mime`](https://github.com/abap2UI5/samples-stack/tree/08-mime) -— this package alone, without the other nine on your system. +— this package alone, without the other ten on your system. **Setup:** activate the ICF service `/SAP/PUBLIC/BC/ABAP/mime_demo` in `SICF`. The app checks the node and warns if it is inactive. diff --git a/src/09/README.md b/src/09/README.md index 25dd007..56f4fa4 100644 --- a/src/09/README.md +++ b/src/09/README.md @@ -19,7 +19,7 @@ framework, so what decides here is whether you have a launchpad, not which ABAP release serves it. **Branch:** [`09-launchpad`](https://github.com/abap2UI5/samples-stack/tree/09-launchpad) -— this package alone, without the other nine on your system. +— this package alone, without the other ten on your system. A **Fiori Launchpad** with a tile pointing at abap2UI5 — an on-premise FLP (`/ui2/flp`), the launchpad sandbox (`test/flpSandbox`), or a launchpad site on BTP. diff --git a/src/10/README.md b/src/10/README.md index 79a2b08..c7c1158 100644 --- a/src/10/README.md +++ b/src/10/README.md @@ -27,7 +27,7 @@ with the settings sample. **Release:** Cloud + Standard ≥ 7.40 SP08. **Branch:** [`10-ai-llm`](https://github.com/abap2UI5/samples-stack/tree/10-ai-llm) -— this package alone, without the other nine on your system. +— this package alone, without the other ten on your system. **An endpoint the system can reach over HTTPS**, and that is the one step this package cannot take for you. The two stacks release different HTTP clients, so diff --git a/src/11/01/package.devc.xml b/src/11/01/package.devc.xml new file mode 100644 index 0000000..e0cda95 --- /dev/null +++ b/src/11/01/package.devc.xml @@ -0,0 +1,10 @@ + + + + + + test objects + + + + diff --git a/src/11/01/z2ui5_a_smps_file_stream.bdef.asbdef b/src/11/01/z2ui5_a_smps_file_stream.bdef.asbdef new file mode 100644 index 0000000..e9fe1dc --- /dev/null +++ b/src/11/01/z2ui5_a_smps_file_stream.bdef.asbdef @@ -0,0 +1,10 @@ +// The second level of the deep action parameter - see +// z2ui5_a_smps_file_upload. It needs a behavior definition of its own even +// though there is nothing to declare. +abstract; +strict ( 2 ); +with hierarchy; + +define behavior for z2ui5_a_smps_file_stream alias StreamProperties +{ +} diff --git a/src/11/01/z2ui5_a_smps_file_stream.bdef.xml b/src/11/01/z2ui5_a_smps_file_stream.bdef.xml new file mode 100644 index 0000000..d4ae2a8 --- /dev/null +++ b/src/11/01/z2ui5_a_smps_file_stream.bdef.xml @@ -0,0 +1,45 @@ + + + + + + Z2UI5_A_SMPS_FILE_STREAM + BDEF/BDO + abap2UI5 RAP sample - upload stream properties + 60 + EN + + + ./z2ui5_a_smps_file_stream/source/main/versions + http://www.sap.com/adt/relations/versions + Historic versions + + + /sap/bc/adt/repository/informationsystem/abaplanguageversions?uri=%2Fsap%2Fbc%2Fadt%2Fbo%2Fbehaviordefinitions%2Fz2ui5_a_smps_file_stream + http://www.sap.com/adt/relations/informationsystem/abaplanguageversions + application/vnd.sap.adt.nameditems.v1+xml + Allowed ABAP language versions + + + ./z2ui5_a_smps_file_stream/source/main + http://www.sap.com/adt/relations/source + text/plain + Source Content + + + ./z2ui5_a_smps_file_stream/source/main + http://www.sap.com/adt/relations/source + text/html + Source Content (HTML) + + + EN + X + ./z2ui5_a_smps_file_stream/source/main + ABAP_SOURCE + true + true + + + + diff --git a/src/11/01/z2ui5_a_smps_file_stream.ddls.asddls b/src/11/01/z2ui5_a_smps_file_stream.ddls.asddls new file mode 100644 index 0000000..8ca84ef --- /dev/null +++ b/src/11/01/z2ui5_a_smps_file_stream.ddls.asddls @@ -0,0 +1,18 @@ +// The file itself, one level below the action parameter +// z2ui5_a_smps_file_upload. The @Semantics.largeObject annotations are what +// a Fiori Elements action dialog reads to render a file picker; EML does not +// need them - an abap2UI5 app fills the three fields like any other. +@EndUserText.label: 'abap2UI5 sample - upload stream' +define root abstract entity z2ui5_a_smps_file_stream +{ + @Semantics.largeObject.mimeType : 'MimeType' + @Semantics.largeObject.fileName : 'FileName' + @EndUserText.label : 'File' + FileContent : abap.rawstring(0); + + @UI.hidden : true + MimeType : abap.char(128); + + @UI.hidden : true + FileName : abap.char(255); +} diff --git a/src/11/01/z2ui5_a_smps_file_stream.ddls.xml b/src/11/01/z2ui5_a_smps_file_stream.ddls.xml new file mode 100644 index 0000000..b8819b9 --- /dev/null +++ b/src/11/01/z2ui5_a_smps_file_stream.ddls.xml @@ -0,0 +1,13 @@ + + + + + + Z2UI5_A_SMPS_FILE_STREAM + E + abap2UI5 RAP sample - upload stream properties + A + + + + diff --git a/src/11/01/z2ui5_a_smps_file_upload.bdef.asbdef b/src/11/01/z2ui5_a_smps_file_upload.bdef.asbdef new file mode 100644 index 0000000..4b4f4fd --- /dev/null +++ b/src/11/01/z2ui5_a_smps_file_upload.bdef.asbdef @@ -0,0 +1,12 @@ +// An abstract behavior definition: no implementation, no persistence - it only +// declares the hierarchy, so the association to the stream properties +// becomes part of the action parameter. Without it "deep parameter" does not +// activate. +abstract; +strict ( 2 ); +with hierarchy; + +define behavior for z2ui5_a_smps_file_upload alias FileUpload +{ + association _StreamProperties with hierarchy; +} diff --git a/src/11/01/z2ui5_a_smps_file_upload.bdef.xml b/src/11/01/z2ui5_a_smps_file_upload.bdef.xml new file mode 100644 index 0000000..ca4c4d3 --- /dev/null +++ b/src/11/01/z2ui5_a_smps_file_upload.bdef.xml @@ -0,0 +1,45 @@ + + + + + + Z2UI5_A_SMPS_FILE_UPLOAD + BDEF/BDO + abap2UI5 RAP sample - upload action parameter + 60 + EN + + + ./z2ui5_a_smps_file_upload/source/main/versions + http://www.sap.com/adt/relations/versions + Historic versions + + + /sap/bc/adt/repository/informationsystem/abaplanguageversions?uri=%2Fsap%2Fbc%2Fadt%2Fbo%2Fbehaviordefinitions%2Fz2ui5_a_smps_file_upload + http://www.sap.com/adt/relations/informationsystem/abaplanguageversions + application/vnd.sap.adt.nameditems.v1+xml + Allowed ABAP language versions + + + ./z2ui5_a_smps_file_upload/source/main + http://www.sap.com/adt/relations/source + text/plain + Source Content + + + ./z2ui5_a_smps_file_upload/source/main + http://www.sap.com/adt/relations/source + text/html + Source Content (HTML) + + + EN + X + ./z2ui5_a_smps_file_upload/source/main + ABAP_SOURCE + true + true + + + + diff --git a/src/11/01/z2ui5_a_smps_file_upload.ddls.asddls b/src/11/01/z2ui5_a_smps_file_upload.ddls.asddls new file mode 100644 index 0000000..aafe497 --- /dev/null +++ b/src/11/01/z2ui5_a_smps_file_upload.ddls.asddls @@ -0,0 +1,14 @@ +// The parameter of the static action uploadFile of z2ui5_r_smps_fil. An +// action parameter is a flat structure - a file does not fit into one - so +// the file travels in a second abstract entity, reached through the +// association below. That is what makes the parameter DEEP: the behavior +// definition of this entity says "with hierarchy", the action says +// "deep parameter", and EML hands both levels over in one %param. +@EndUserText.label: 'abap2UI5 sample - upload parameter' +define root abstract entity z2ui5_a_smps_file_upload +{ + // a plain field next to the file - stored with it, shown in the list + Description : abap.char(60); + + _StreamProperties : association [1] to z2ui5_a_smps_file_stream on 1 = 1; +} diff --git a/src/11/01/z2ui5_a_smps_file_upload.ddls.xml b/src/11/01/z2ui5_a_smps_file_upload.ddls.xml new file mode 100644 index 0000000..7636b85 --- /dev/null +++ b/src/11/01/z2ui5_a_smps_file_upload.ddls.xml @@ -0,0 +1,13 @@ + + + + + + Z2UI5_A_SMPS_FILE_UPLOAD + E + abap2UI5 RAP sample - upload action parameter + A + + + + diff --git a/src/11/01/z2ui5_cl_smps_bp_fil.clas.abap b/src/11/01/z2ui5_cl_smps_bp_fil.clas.abap new file mode 100644 index 0000000..60fbbf5 --- /dev/null +++ b/src/11/01/z2ui5_cl_smps_bp_fil.clas.abap @@ -0,0 +1,10 @@ +"!

abap2UI5 RAP sample - file behavior pool

+"! Behavior pool of the business object z2ui5_r_smps_fil. The implementation +"! lives in the local types include - see +"! z2ui5_cl_smps_bp_fil.clas.locals_imp.abap. +CLASS z2ui5_cl_smps_bp_fil DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF z2ui5_r_smps_fil. +ENDCLASS. + + +CLASS z2ui5_cl_smps_bp_fil IMPLEMENTATION. +ENDCLASS. diff --git a/src/11/01/z2ui5_cl_smps_bp_fil.clas.locals_imp.abap b/src/11/01/z2ui5_cl_smps_bp_fil.clas.locals_imp.abap new file mode 100644 index 0000000..e072d1d --- /dev/null +++ b/src/11/01/z2ui5_cl_smps_bp_fil.clas.locals_imp.abap @@ -0,0 +1,98 @@ +CLASS lhc_file DEFINITION INHERITING FROM cl_abap_behavior_handler. + + PRIVATE SECTION. + + METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION + IMPORTING REQUEST requested_authorizations FOR file RESULT result. + + METHODS uploadfile FOR MODIFY + IMPORTING keys FOR ACTION file~uploadfile RESULT result. + +ENDCLASS. + + +CLASS lhc_file IMPLEMENTATION. + + METHOD get_global_authorizations. + + " the sample deliberately grants everything - a real business object would + " check an authorization object here. Only what was asked for is answered. + IF requested_authorizations-%delete = if_abap_behv=>mk-on. + result-%delete = if_abap_behv=>auth-allowed. + ENDIF. + + IF requested_authorizations-%action-uploadfile = if_abap_behv=>mk-on. + result-%action-uploadfile = if_abap_behv=>auth-allowed. + ENDIF. + + ENDMETHOD. + + + METHOD uploadfile. + + " A static action has no instance yet, so a key row carries only the %cid + " of the caller and the parameter. The parameter is DEEP: next to its own + " field Description it holds the association _StreamProperties as a + " component, and that one carries the file - content, MIME type, name. + DATA creates TYPE TABLE FOR CREATE z2ui5_r_smps_fil. + + LOOP AT keys INTO DATA(key) WHERE %param-_streamproperties-filecontent IS NOT INITIAL. + + DATA(stream) = key-%param-_streamproperties. + APPEND VALUE #( %cid = key-%cid + filename = stream-filename + mimetype = stream-mimetype + description = key-%param-description + filesize = xstrlen( stream-filecontent ) + content = stream-filecontent ) TO creates. + + ENDLOOP. + + " the CREATE is INTERNAL in the behavior definition - IN LOCAL MODE is the + " only way in, which is exactly what keeps every other caller out. The + " action's %cid is reused, so MAPPED reaches the caller under the very + " %cid it sent and it can read the new key from there. + MODIFY ENTITIES OF z2ui5_r_smps_fil IN LOCAL MODE + ENTITY file + CREATE FIELDS ( filename mimetype description filesize content ) + WITH creates + MAPPED DATA(create_mapped) + FAILED DATA(create_failed) + REPORTED DATA(create_reported). + + mapped = CORRESPONDING #( DEEP create_mapped ). + failed = CORRESPONDING #( DEEP create_failed ). + reported = CORRESPONDING #( DEEP create_reported ). + + " an empty file is not refused by the type system - it is refused here, + " with a message the caller can show + LOOP AT keys INTO key WHERE %param-_streamproperties-filecontent IS INITIAL. + + APPEND VALUE #( %cid = key-%cid ) TO failed-file. + APPEND VALUE #( %cid = key-%cid + %msg = new_message_with_text( + severity = if_abap_behv_message=>severity-error + text = 'The file is empty - nothing to upload' ) ) TO reported-file. + + ENDLOOP. + + IF create_mapped-file IS INITIAL. + RETURN. + ENDIF. + + " result [1] $self: the new instance goes back to the caller. Content is + " left out of the READ - the caller just sent it, no need to copy it twice. + READ ENTITIES OF z2ui5_r_smps_fil IN LOCAL MODE + ENTITY file + FIELDS ( filename mimetype description filesize createdby createdat lastchangedat ) + WITH VALUE #( FOR m IN create_mapped-file ( %tky = m-%tky ) ) + RESULT DATA(files). + + result = VALUE #( FOR m IN create_mapped-file + FOR f IN files WHERE ( fileuuid = m-fileuuid ) + ( %cid = m-%cid + %param = CORRESPONDING #( f ) ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/11/01/z2ui5_cl_smps_bp_fil.clas.xml b/src/11/01/z2ui5_cl_smps_bp_fil.clas.xml new file mode 100644 index 0000000..3a2ca72 --- /dev/null +++ b/src/11/01/z2ui5_cl_smps_bp_fil.clas.xml @@ -0,0 +1,18 @@ + + + + + + Z2UI5_CL_SMPS_BP_FIL + E + abap2UI5 RAP sample - file behavior pool + 06 + 1 + X + X + X + Z2UI5_R_SMPS_FIL + + + + diff --git a/src/11/01/z2ui5_r_smps_fil.bdef.asbdef b/src/11/01/z2ui5_r_smps_fil.bdef.asbdef new file mode 100644 index 0000000..c8c9385 --- /dev/null +++ b/src/11/01/z2ui5_r_smps_fil.bdef.asbdef @@ -0,0 +1,42 @@ +// Managed business object holding uploaded files. There is no public create: +// a file enters the BO only through the static action uploadFile, whose +// DEEP PARAMETER carries the file content, its MIME type and its name - the +// technique Fiori Elements renders as a file picker in the action dialog, and +// the one z2ui5_cl_smps_app_016 calls with EML instead. +managed implementation in class z2ui5_cl_smps_bp_fil unique; +strict ( 2 ); + +define behavior for z2ui5_r_smps_fil alias File +persistent table z2ui5_t_smps_fil +lock master +authorization master ( global ) +etag master LastChangedAt +{ + // only the action creates - INTERNAL keeps CREATE out of reach of every + // caller but the behavior pool itself + internal create; + delete; + + field ( numbering : managed, readonly ) FileUuid; + + // written by the action, never by the caller + field ( readonly ) FileName, MimeType, Description, FileSize, Content; + + // filled by the managed runtime from the @Semantics annotations of the CDS + field ( readonly ) CreatedBy, CreatedAt, LastChangedAt; + + static action uploadFile deep parameter z2ui5_a_smps_file_upload result [1] $self; + + mapping for z2ui5_t_smps_fil + { + FileUuid = file_uuid; + FileName = file_name; + MimeType = mime_type; + Description = description; + FileSize = file_size; + Content = content; + CreatedBy = created_by; + CreatedAt = created_at; + LastChangedAt = last_changed_at; + } +} diff --git a/src/11/01/z2ui5_r_smps_fil.bdef.xml b/src/11/01/z2ui5_r_smps_fil.bdef.xml new file mode 100644 index 0000000..d23ccd8 --- /dev/null +++ b/src/11/01/z2ui5_r_smps_fil.bdef.xml @@ -0,0 +1,45 @@ + + + + + + Z2UI5_R_SMPS_FIL + BDEF/BDO + abap2UI5 RAP sample - uploaded file + 60 + EN + + + ./z2ui5_r_smps_fil/source/main/versions + http://www.sap.com/adt/relations/versions + Historic versions + + + /sap/bc/adt/repository/informationsystem/abaplanguageversions?uri=%2Fsap%2Fbc%2Fadt%2Fbo%2Fbehaviordefinitions%2Fz2ui5_r_smps_fil + http://www.sap.com/adt/relations/informationsystem/abaplanguageversions + application/vnd.sap.adt.nameditems.v1+xml + Allowed ABAP language versions + + + ./z2ui5_r_smps_fil/source/main + http://www.sap.com/adt/relations/source + text/plain + Source Content + + + ./z2ui5_r_smps_fil/source/main + http://www.sap.com/adt/relations/source + text/html + Source Content (HTML) + + + EN + X + ./z2ui5_r_smps_fil/source/main + ABAP_SOURCE + true + true + + + + diff --git a/src/11/01/z2ui5_r_smps_fil.ddls.asddls b/src/11/01/z2ui5_r_smps_fil.ddls.asddls new file mode 100644 index 0000000..698424a --- /dev/null +++ b/src/11/01/z2ui5_r_smps_fil.ddls.asddls @@ -0,0 +1,26 @@ +@AccessControl.authorizationCheck: #NOT_REQUIRED +@EndUserText.label: 'abap2UI5 RAP sample - uploaded file' +@Metadata.ignorePropagatedAnnotations: true +define root view entity z2ui5_r_smps_fil + as select from z2ui5_t_smps_fil +{ + key file_uuid as FileUuid, + + file_name as FileName, + @Semantics.mimeType: true + mime_type as MimeType, + description as Description, + file_size as FileSize, + + @Semantics.largeObject: { mimeType: 'MimeType', + fileName: 'FileName', + contentDispositionPreference: #ATTACHMENT } + content as Content, + + @Semantics.user.createdBy: true + created_by as CreatedBy, + @Semantics.systemDateTime.createdAt: true + created_at as CreatedAt, + @Semantics.systemDateTime.localInstanceLastChangedAt: true + last_changed_at as LastChangedAt +} diff --git a/src/11/01/z2ui5_r_smps_fil.ddls.baseinfo b/src/11/01/z2ui5_r_smps_fil.ddls.baseinfo new file mode 100644 index 0000000..563a240 --- /dev/null +++ b/src/11/01/z2ui5_r_smps_fil.ddls.baseinfo @@ -0,0 +1,7 @@ +{ + "BASEINFO": { + "FROM": [ + "Z2UI5_T_SMPS_FIL" + ] + } +} diff --git a/src/11/01/z2ui5_r_smps_fil.ddls.xml b/src/11/01/z2ui5_r_smps_fil.ddls.xml new file mode 100644 index 0000000..d212b65 --- /dev/null +++ b/src/11/01/z2ui5_r_smps_fil.ddls.xml @@ -0,0 +1,13 @@ + + + + + + Z2UI5_R_SMPS_FIL + E + abap2UI5 RAP sample - uploaded file + W + + + + diff --git a/src/11/01/z2ui5_t_smps_fil.tabl.xml b/src/11/01/z2ui5_t_smps_fil.tabl.xml new file mode 100644 index 0000000..11f2686 --- /dev/null +++ b/src/11/01/z2ui5_t_smps_fil.tabl.xml @@ -0,0 +1,129 @@ + + + + + + Z2UI5_T_SMPS_FIL + E + TRANSP + X + abap2UI5 RAP sample - uploaded file + E + A + 1 + + + Z2UI5_T_SMPS_FIL + A + 0 + APPL1 + N + + + + CLIENT + X + 0 + C + 000006 + X + CLNT + 000003 + CLNT + + + FILE_UUID + X + 0 + X + 000016 + X + RAW + 000016 + RAW + + + FILE_NAME + 0 + C + 000510 + X + CHAR + 000255 + CHAR + + + MIME_TYPE + 0 + C + 000256 + X + CHAR + 000128 + CHAR + + + DESCRIPTION + 0 + C + 000120 + X + CHAR + 000060 + CHAR + + + FILE_SIZE + 0 + X + 000004 + X + INT4 + 000010 + INT4 + + + CONTENT + 0 + y + 000008 + RSTR + 000000 + RSTR + + + CREATED_BY + 0 + C + 000024 + X + CHAR + 000012 + CHAR + + + CREATED_AT + 0 + P + 000011 + X + DEC + 000021 + 000007 + DEC + + + LAST_CHANGED_AT + 0 + P + 000011 + X + DEC + 000021 + 000007 + DEC + + + + + diff --git a/src/11/README.md b/src/11/README.md new file mode 100644 index 0000000..71ce048 --- /dev/null +++ b/src/11/README.md @@ -0,0 +1,108 @@ +# 11 — RAP File Upload + +*[← all packages](../../README.md)* + +A file goes into a RAP business object as an **action parameter**. The static +action `uploadFile` of `Z2UI5_R_SMPS_FIL` takes a *deep parameter*: next to a +description, it carries the file content, its MIME type and its name, one level +down through an association. The business object decides what to store and what +to refuse. The app only picks the file and calls the action. + +Fiori Elements reads this action from the OData metadata and renders a file +picker into the action dialog, because the stream entity carries +`@Semantics.largeObject`. abap2UI5 skips that layer. The `z2ui5:FileUploader` +custom control reads the file in the browser, the roundtrip brings it to the +backend as base64, and the app hands it to the action with EML like any other +value. The business object is the same one a Fiori Elements app would use. + +## What you need + +**Release:** Cloud + Standard ≥ 8.16 (2025). A deep action parameter whose +abstract entity carries a `@Semantics.largeObject` stream is younger than EML. +The ABAP of this package parses at 7.57; the feature arrived with ABAP Platform +2025 and the matching BTP ABAP Environment and S/4HANA Cloud releases. If the +action `uploadFile` does not activate on your system, this package is out of +reach, and nothing else in this repository is affected. + +**Branch:** [`11-rap-file-upload`](https://github.com/abap2UI5/samples-stack/tree/11-rap-file-upload) +— this package alone, without the other ten on your system. + +The business object and its table come with this package ([`src/11/01`](01)). +There is no demo data: the first file you upload is the first row. + +## The business object + +| Object | What it is | +|---|---| +| `Z2UI5_T_SMPS_FIL` | the table. One row per file, the content in a `RAWSTRING` column | +| `Z2UI5_R_SMPS_FIL` | the root view entity, `Content` annotated as `@Semantics.largeObject` | +| `Z2UI5_R_SMPS_FIL` (BDEF) | managed. `create` is **internal**, so the action is the only way in. `delete` is public | +| `Z2UI5_A_SMPS_FILE_UPLOAD` | abstract entity, the action parameter: `Description` and the association `_StreamProperties` | +| `Z2UI5_A_SMPS_FILE_STREAM` | abstract entity, the file: `FileContent` (`rawstring`), `MimeType`, `FileName` | +| the two abstract BDEFs | `with hierarchy`. They make the association part of the parameter. Without them `deep parameter` does not activate | +| `Z2UI5_CL_SMPS_BP_FIL` | the behavior pool. `uploadFile` creates the instance `IN LOCAL MODE` and refuses an empty file with a message | + +The action in the behavior definition: + +```abap +static action uploadFile deep parameter z2ui5_a_smps_file_upload result [1] $self; +``` + +## The sample + +| You want to | Statement | Sample | +|---|---|---| +| upload a file, list, download and delete it | `MODIFY … EXECUTE uploadFile` with a deep `%param` | [`016`](z2ui5_cl_smps_app_016.clas.abap) | + +```abap +MODIFY ENTITIES OF z2ui5_r_smps_fil + ENTITY file + EXECUTE uploadfile + FROM VALUE #( ( %cid = `UPLOAD_1` + %param = VALUE #( description = description + _streamproperties = VALUE #( filecontent = content + mimetype = mime_type + filename = file_name ) ) ) ) + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + +COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_fil + FAILED DATA(s_failed_commit) + REPORTED DATA(s_reported_commit). +``` + +`content` is an `xstring`. The app gets it from the data URL the FileUploader +delivers (`data:;base64,`) with +`cl_web_http_utility=>decode_x_base64( )`. The way back is the same in +reverse: `READ ENTITIES` with the content, encoded to base64 again, and the +`download_b64_file` frontend action saves it in the browser. + +## How big can a file be? + +The limit is the same as for Fiori Elements. It comes from the transport, and +the transport works the same way in both cases: + +- **abap2UI5:** the file travels base64-encoded inside the JSON of the + roundtrip, about a third larger than the file itself. +- **Fiori Elements:** an action parameter travels in the JSON body of the + OData V4 request, and OData's JSON format writes binary values + (`Edm.Binary`, which a `rawstring` becomes) as base64 too. Same overhead, + same single request. + +In both cases the whole file sits in the memory of one work process, decoded +once and copied into the business object. Large files hit the request size +limits of the ICF and Gateway and the memory quota of the session long before +anything in this sample does. Uploading a stream property on its own with +`PUT …/$value` sends raw bytes and skips the base64 step, but it is a different +technique, and it still is one request holding the whole file. For really +large files neither path is right: they belong in a document or object store +(DMS, SAP Document Management service, an object store on BTP), with the +business object keeping only a reference. + +## Where to go next + +- [`03` RAP](../03/README.md) — the EML statements one by one, on a business + object without files. +- The upload without RAP: `Z2UI5_CL_SMP_APP_074` (FileUploader) and + `Z2UI5_CL_SMP_APP_517` (UploadSet, several files, drag and drop) in + [abap2UI5/samples](https://github.com/abap2UI5/samples). diff --git a/src/11/package.devc.xml b/src/11/package.devc.xml new file mode 100644 index 0000000..f245f59 --- /dev/null +++ b/src/11/package.devc.xml @@ -0,0 +1,10 @@ + + + + + + RAP File Upload + + + + diff --git a/src/11/z2ui5_cl_smps_app_016.clas.abap b/src/11/z2ui5_cl_smps_app_016.clas.abap new file mode 100644 index 0000000..303c10f --- /dev/null +++ b/src/11/z2ui5_cl_smps_app_016.clas.abap @@ -0,0 +1,363 @@ +" @keywords file upload attachment rap action deep parameter largeobject stream eml fileuploader download +" @summary EXECUTE uploadFile with a deep parameter - the file travels as a stream property, download and delete included +"!

RAP File Upload - Deep Action Parameter

+"! A file enters the business object z2ui5_r_smps_fil through its static +"! action uploadFile. The action parameter is DEEP: the abstract entity +"! z2ui5_a_smps_file_upload carries a description and, through an +"! association, the abstract entity z2ui5_a_smps_file_stream with the file +"! content, its MIME type and its name. +"! +"! MODIFY ENTITIES OF z2ui5_r_smps_fil +"! ENTITY file +"! EXECUTE uploadfile +"! FROM VALUE #( ( %cid = `UPLOAD_1` +"! %param = VALUE #( +"! description = ... +"! _streamproperties = VALUE #( filecontent = ... +"! mimetype = ... +"! filename = ... ) ) ) ) +"! FAILED DATA(s_failed) +"! REPORTED DATA(s_reported). +"! +"! Fiori Elements reads the @Semantics.largeObject annotations of the stream +"! entity and renders a file picker into the action dialog. abap2UI5 needs +"! neither the annotations nor an OData service: the FileUploader custom +"! control reads the file in the browser, the roundtrip brings it to the +"! backend as base64, and the app hands it to the action like any other +"! value. The business logic - what is stored, what is refused - stays in +"! the business object. +CLASS z2ui5_cl_smps_app_016 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + "! the picked file as the FileUploader delivers it - a data URL, + "! data:<mime>;base64,<payload> + DATA file TYPE string. + "! the name of the picked file + DATA file_name TYPE string. + DATA description TYPE string. + + TYPES: + BEGIN OF ty_s_file, + file_uuid TYPE string, + file_name TYPE string, + mime_type TYPE string, + file_size TYPE string, + description TYPE string, + created_at TYPE string, + END OF ty_s_file. + TYPES ty_t_file TYPE STANDARD TABLE OF ty_s_file WITH EMPTY KEY. + DATA t_files TYPE ty_t_file. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS on_event. + METHODS file_upload. + METHODS file_download. + METHODS file_delete. + METHODS data_read. + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smps_app_016 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + data_read( ). + view_display( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD on_event. + + CASE client->get_event( ). + + WHEN `UPLOAD`. + file_upload( ). + + WHEN `DOWNLOAD`. + file_download( ). + + WHEN `DELETE`. + file_delete( ). + + WHEN `REFRESH`. + " the table is bound - changing it is enough, the framework sends the + " changed model back on its own + data_read( ). + + ENDCASE. + + ENDMETHOD. + + + METHOD file_upload. + + " the FileUploader delivers a data URL - data:;base64,. + " The MIME type is the browser's guess from the file extension and may be + " missing altogether, so it falls back to the generic binary type. + SPLIT file AT `,` INTO DATA(header) DATA(base64). + DATA(mime_type) = substring_before( val = substring_after( val = header sub = `data:` ) sub = `;` ). + IF mime_type IS INITIAL. + mime_type = `application/octet-stream`. + ENDIF. + DATA(content) = cl_web_http_utility=>decode_x_base64( base64 ). + + " a static action needs no key - the %cid is the caller's own id for it. + " The parameter is deep: description on the first level, the file one + " level below under the association _streamproperties. + MODIFY ENTITIES OF z2ui5_r_smps_fil + ENTITY file + EXECUTE uploadfile + FROM VALUE #( ( %cid = `UPLOAD_1` + %param = VALUE #( description = description + _streamproperties = VALUE #( filecontent = content + mimetype = mime_type + filename = file_name ) ) ) ) + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + + IF s_failed-file IS NOT INITIAL. + + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-file ). + RETURN. + + ENDIF. + + COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_fil + FAILED DATA(s_failed_commit) + REPORTED DATA(s_reported_commit). + + IF s_failed_commit IS NOT INITIAL. + + " a failed save keeps the changes in the transactional buffer - discard + " them, or abap2UI5's own COMMIT WORK at the end of the roundtrip runs + " into them + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported_commit-file ). + RETURN. + + ENDIF. + + client->message_toast_display( |{ file_name } - { xstrlen( content ) } bytes stored in the business object| ). + + file = VALUE #( ). + file_name = VALUE #( ). + description = VALUE #( ). + data_read( ). + view_display( ). + + ENDMETHOD. + + + METHOD file_download. + + DATA(file_uuid) = CONV z2ui5_t_smps_fil-file_uuid( client->get_event_arg( ) ). + + READ ENTITIES OF z2ui5_r_smps_fil + ENTITY file + FIELDS ( filename mimetype content ) + WITH VALUE #( ( fileuuid = file_uuid ) ) + RESULT DATA(t_result). + + " a file deleted in another tab is not an exception - RESULT is just empty + IF t_result IS INITIAL. + client->message_box_display( text = `The file is no longer there - press Refresh` type = `error` ). + RETURN. + ENDIF. + + " the same road back: the content becomes a data URL again, and the + " download_b64_file frontend action saves it under its name + DATA(s_file) = t_result[ 1 ]. + client->follow_up_action( + val = client->cs_event-download_b64_file + t_arg = VALUE #( ( |data:{ s_file-mimetype };base64,{ cl_web_http_utility=>encode_x_base64( s_file-content ) }| ) + ( |{ s_file-filename }| ) ) ). + + ENDMETHOD. + + + METHOD file_delete. + + DATA(file_uuid) = CONV z2ui5_t_smps_fil-file_uuid( client->get_event_arg( ) ). + + MODIFY ENTITIES OF z2ui5_r_smps_fil + ENTITY file + DELETE FROM VALUE #( ( fileuuid = file_uuid ) ) + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + + IF s_failed-file IS NOT INITIAL. + + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-file ). + RETURN. + + ENDIF. + + COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_fil + FAILED DATA(s_failed_commit) + REPORTED DATA(s_reported_commit). + + IF s_failed_commit IS NOT INITIAL. + + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported_commit-file ). + RETURN. + + ENDIF. + + client->message_toast_display( `File deleted` ). + data_read( ). + + ENDMETHOD. + + + METHOD data_read. + + " EML reads by key - a list of every file is a SELECT on the CDS view. + " The content stays in the database: the list needs its size, not its + " bytes, and every row would otherwise travel to the browser. + SELECT FROM z2ui5_r_smps_fil "#EC CI_NOWHERE + FIELDS FileUuid, + FileName, + MimeType, + FileSize, + Description, + CreatedAt + ORDER BY CreatedAt DESCENDING + INTO TABLE @DATA(t_result) UP TO 50 ROWS. + + t_files = VALUE #( FOR s_result IN t_result + ( file_uuid = |{ s_result-fileuuid }| + file_name = |{ s_result-filename }| + mime_type = |{ s_result-mimetype }| + file_size = |{ s_result-filesize } bytes| + description = |{ s_result-description }| + created_at = |{ s_result-createdat TIMESTAMP = ISO }| ) ). + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + )->a( n = `xmlns:form` v = `sap.ui.layout.form` + )->a( n = `xmlns:z2ui5` v = `z2ui5.cc` ). + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - RAP File Upload - Deep Action Parameter` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `Pick a file and press Upload: the FileUploader hands it to the backend as base64, the app ` && + `calls the static action uploadFile of Z2UI5_R_SMPS_FIL with EML and passes the file in ` && + `its deep parameter - no OData service, no annotations needed.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + page->ele( n = `SimpleForm` ns = `form` + )->a( n = `title` v = `MODIFY ENTITIES OF Z2UI5_R_SMPS_FIL ... EXECUTE uploadFile` + )->a( n = `editable` b = abap_true + )->ele( n = `content` ns = `form` + )->tag( `Label` + )->a( n = `text` v = `Description` + )->tag( `Input` + )->a( n = `placeholder` v = `optional - stored with the file` + )->a( n = `value` v = client->_bind( description ) + )->tag( `Label` + )->a( n = `text` v = `File` + )->tag( n = `FileUploader` ns = `z2ui5` + )->a( n = `placeholder` v = `pick a file...` + )->a( n = `uploadButtonText` v = `Upload` + )->a( n = `upload` v = client->_event( `UPLOAD` ) + )->a( n = `path` v = client->_bind( file_name ) + )->a( n = `value` v = client->_bind( file ) ). + + DATA(table) = page->ele( `Table` + )->a( n = `items` v = client->_bind( t_files ) + )->a( n = `noDataText` v = `No files yet - upload one above` ). + table->ele( `headerToolbar` + )->ele( `Toolbar` + )->tag( `Title` + )->a( n = `text` v = `Files (Z2UI5_R_SMPS_FIL)` + )->tag( `ToolbarSpacer` + )->tag( `Button` + )->a( n = `press` v = client->_event( `REFRESH` ) + )->a( n = `icon` v = `sap-icon://refresh` + )->a( n = `tooltip` v = `Refresh` ). + + table->ele( `columns` + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Name` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `MIME Type` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Size` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Description` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Created` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Actions` ). + + table->ele( `items` + )->ele( `ColumnListItem` + )->ele( `cells` + )->tag( `Text` + )->a( n = `text` v = `{FILE_NAME}` + )->tag( `Text` + )->a( n = `text` v = `{MIME_TYPE}` + )->tag( `Text` + )->a( n = `text` v = `{FILE_SIZE}` + )->tag( `Text` + )->a( n = `text` v = `{DESCRIPTION}` + )->tag( `Text` + )->a( n = `text` v = `{CREATED_AT}` + )->ele( `HBox` + )->tag( `Button` + )->a( n = `press` v = client->_event( val = `DOWNLOAD` arg = `${FILE_UUID}` ) + )->a( n = `icon` v = `sap-icon://download` + )->a( n = `tooltip` v = `Download File` + )->tag( `Button` + )->a( n = `press` v = client->_event( val = `DELETE` arg = `${FILE_UUID}` ) + )->a( n = `icon` v = `sap-icon://delete` + )->a( n = `tooltip` v = `Delete File` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/11/z2ui5_cl_smps_app_016.clas.xml b/src/11/z2ui5_cl_smps_app_016.clas.xml new file mode 100644 index 0000000..dc0874b --- /dev/null +++ b/src/11/z2ui5_cl_smps_app_016.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMPS_APP_016 + E + RAP File Upload - Deep Action Parameter + 1 + X + X + X + + + + diff --git a/src/z2ui5_cl_smps_app_000.clas.abap b/src/z2ui5_cl_smps_app_000.clas.abap index 930dc78..59b1946 100644 --- a/src/z2ui5_cl_smps_app_000.clas.abap +++ b/src/z2ui5_cl_smps_app_000.clas.abap @@ -69,6 +69,7 @@ CLASS z2ui5_cl_smps_app_000 DEFINITION PUBLIC. DATA t_mime TYPE ty_t_sample. DATA t_launchpad TYPE ty_t_sample. DATA t_ai TYPE ty_t_sample. + DATA t_upload TYPE ty_t_sample. PROTECTED SECTION. DATA client TYPE REF TO z2ui5_if_client. @@ -456,6 +457,13 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION. samples = t_ai items = client->_bind( t_ai ) ). + render_package( page = page + title = `11 - RAP File Upload` + hint = `a file into a business object through a deep action parameter - needs ABAP Platform 2025 or a current cloud release` + branch = `11-rap-file-upload` + samples = t_upload + items = client->_bind( t_upload ) ). + client->view_display( view->stringify( ) ). ENDMETHOD. @@ -1029,6 +1037,12 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION. detail = `a table of sales figures and one button - the rows go to a language model as context, its summary comes back into a panel` classname = `Z2UI5_CL_SMPS_APP_015` ) ) ). + t_upload = VALUE #( + ( sample( no = `016` + title = `Upload a file through a RAP action` + detail = `EXECUTE uploadFile with a deep parameter - the file travels as a stream property, download and delete included` + classname = `Z2UI5_CL_SMPS_APP_016` ) ) ). + ENDMETHOD. From a49f71da692d193cc4216d577ebb9382ed2fd6df Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 11:26:48 +0000 Subject: [PATCH 2/2] Add packages 12-14 and a launchpad URL sample 12 - RAP Actions & Permissions: the business object Z2UI5_R_SMPS_REQ with instance feature control, an action with a parameter, an instance and a static factory action, a default function, a determine action and a projection. Samples Z2UI5_CL_SMPS_APP_017 to _020: GET PERMISSIONS drives the buttons, parameters travel in %param, a determine action validates while the user types, and the handler tells the two contracts apart with cl_abap_behv_aux. GET PERMISSIONS sits in Z2UI5_CL_SMPS_PERM_REQ, which is listed in noIssues because abaplint does not parse the statement. 13 - Excel & E-Mail: Z2UI5_CL_SMPS_XLSX wraps the XCO XLSX library; Z2UI5_CL_SMPS_APP_021 reads an uploaded .xlsx, _022 exports a table as .xlsx, _023 sends it as a mail attachment with cl_bcs_mail_message. 14 - Adobe Forms: Z2UI5_CL_SMPS_APP_024 renders forms to PDF through FP_JOB_OPEN and the generated function module, one as a PDF download, several as one ZIP. 09 - Launchpad: Z2UI5_CL_SMPS_APP_025 builds the URL of a launchpad app, through cl_lsapi_manager (called by name, signature read with RTTI) where the system has it, as an intent URL of its own where it does not. Overview app, packages.json, both README tables, the package READMEs, SAMPLES.md and the two catalogue files are updated; Regenerate Demo Data in the overview also fills the request business object. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NNqpKVX6ESgcLVqeWXeHc5 --- .github/packages.json | 27 + AGENTS.md | 4 +- README.md | 26 +- SAMPLES.md | 32 +- abaplint.jsonc | 9 +- catalogue-derived.json | 22 +- catalogue.json | 266 ++++++++++ src/01/README.md | 2 +- src/02/README.md | 2 +- src/03/README.md | 2 +- src/04/README.md | 2 +- src/05/README.md | 2 +- src/06/README.md | 2 +- src/07/README.md | 2 +- src/08/README.md | 2 +- src/09/README.md | 3 +- src/09/z2ui5_cl_smps_app_025.clas.abap | 331 ++++++++++++ src/09/z2ui5_cl_smps_app_025.clas.xml | 16 + src/10/README.md | 2 +- src/11/README.md | 2 +- src/12/01/package.devc.xml | 10 + src/12/01/z2ui5_a_smps_req_copy.ddls.asddls | 7 + src/12/01/z2ui5_a_smps_req_copy.ddls.xml | 13 + src/12/01/z2ui5_a_smps_req_create.ddls.asddls | 9 + src/12/01/z2ui5_a_smps_req_create.ddls.xml | 13 + src/12/01/z2ui5_a_smps_req_reject.ddls.asddls | 7 + src/12/01/z2ui5_a_smps_req_reject.ddls.xml | 13 + src/12/01/z2ui5_c_smps_req.bdef.asbdef | 14 + src/12/01/z2ui5_c_smps_req.bdef.xml | 45 ++ src/12/01/z2ui5_c_smps_req.ddls.asddls | 21 + src/12/01/z2ui5_c_smps_req.ddls.baseinfo | 7 + src/12/01/z2ui5_c_smps_req.ddls.xml | 13 + src/12/01/z2ui5_cl_smps_bp_req.clas.abap | 10 + .../z2ui5_cl_smps_bp_req.clas.locals_imp.abap | 313 ++++++++++++ src/12/01/z2ui5_cl_smps_bp_req.clas.xml | 18 + src/12/01/z2ui5_cl_smps_data_req.clas.abap | 112 ++++ src/12/01/z2ui5_cl_smps_data_req.clas.xml | 16 + src/12/01/z2ui5_cl_smps_perm_req.clas.abap | 65 +++ src/12/01/z2ui5_cl_smps_perm_req.clas.xml | 16 + src/12/01/z2ui5_r_smps_req.bdef.asbdef | 62 +++ src/12/01/z2ui5_r_smps_req.bdef.xml | 45 ++ src/12/01/z2ui5_r_smps_req.ddls.asddls | 22 + src/12/01/z2ui5_r_smps_req.ddls.baseinfo | 7 + src/12/01/z2ui5_r_smps_req.ddls.xml | 13 + src/12/01/z2ui5_t_smps_req.tabl.xml | 140 +++++ src/12/README.md | 96 ++++ src/12/package.devc.xml | 10 + src/12/z2ui5_cl_smps_app_017.clas.abap | 364 +++++++++++++ src/12/z2ui5_cl_smps_app_017.clas.xml | 16 + src/12/z2ui5_cl_smps_app_018.clas.abap | 483 ++++++++++++++++++ src/12/z2ui5_cl_smps_app_018.clas.xml | 16 + src/12/z2ui5_cl_smps_app_019.clas.abap | 248 +++++++++ src/12/z2ui5_cl_smps_app_019.clas.xml | 16 + src/12/z2ui5_cl_smps_app_020.clas.abap | 267 ++++++++++ src/12/z2ui5_cl_smps_app_020.clas.xml | 16 + src/13/README.md | 63 +++ src/13/package.devc.xml | 10 + src/13/z2ui5_cl_smps_app_021.clas.abap | 192 +++++++ src/13/z2ui5_cl_smps_app_021.clas.xml | 16 + src/13/z2ui5_cl_smps_app_022.clas.abap | 157 ++++++ src/13/z2ui5_cl_smps_app_022.clas.xml | 16 + src/13/z2ui5_cl_smps_app_023.clas.abap | 162 ++++++ src/13/z2ui5_cl_smps_app_023.clas.xml | 16 + src/13/z2ui5_cl_smps_xlsx.clas.abap | 95 ++++ src/13/z2ui5_cl_smps_xlsx.clas.xml | 16 + src/14/README.md | 56 ++ src/14/package.devc.xml | 10 + src/14/z2ui5_cl_smps_app_024.clas.abap | 342 +++++++++++++ src/14/z2ui5_cl_smps_app_024.clas.xml | 16 + src/z2ui5_cl_smps_app_000.clas.abap | 95 +++- 70 files changed, 4521 insertions(+), 40 deletions(-) create mode 100644 src/09/z2ui5_cl_smps_app_025.clas.abap create mode 100644 src/09/z2ui5_cl_smps_app_025.clas.xml create mode 100644 src/12/01/package.devc.xml create mode 100644 src/12/01/z2ui5_a_smps_req_copy.ddls.asddls create mode 100644 src/12/01/z2ui5_a_smps_req_copy.ddls.xml create mode 100644 src/12/01/z2ui5_a_smps_req_create.ddls.asddls create mode 100644 src/12/01/z2ui5_a_smps_req_create.ddls.xml create mode 100644 src/12/01/z2ui5_a_smps_req_reject.ddls.asddls create mode 100644 src/12/01/z2ui5_a_smps_req_reject.ddls.xml create mode 100644 src/12/01/z2ui5_c_smps_req.bdef.asbdef create mode 100644 src/12/01/z2ui5_c_smps_req.bdef.xml create mode 100644 src/12/01/z2ui5_c_smps_req.ddls.asddls create mode 100644 src/12/01/z2ui5_c_smps_req.ddls.baseinfo create mode 100644 src/12/01/z2ui5_c_smps_req.ddls.xml create mode 100644 src/12/01/z2ui5_cl_smps_bp_req.clas.abap create mode 100644 src/12/01/z2ui5_cl_smps_bp_req.clas.locals_imp.abap create mode 100644 src/12/01/z2ui5_cl_smps_bp_req.clas.xml create mode 100644 src/12/01/z2ui5_cl_smps_data_req.clas.abap create mode 100644 src/12/01/z2ui5_cl_smps_data_req.clas.xml create mode 100644 src/12/01/z2ui5_cl_smps_perm_req.clas.abap create mode 100644 src/12/01/z2ui5_cl_smps_perm_req.clas.xml create mode 100644 src/12/01/z2ui5_r_smps_req.bdef.asbdef create mode 100644 src/12/01/z2ui5_r_smps_req.bdef.xml create mode 100644 src/12/01/z2ui5_r_smps_req.ddls.asddls create mode 100644 src/12/01/z2ui5_r_smps_req.ddls.baseinfo create mode 100644 src/12/01/z2ui5_r_smps_req.ddls.xml create mode 100644 src/12/01/z2ui5_t_smps_req.tabl.xml create mode 100644 src/12/README.md create mode 100644 src/12/package.devc.xml create mode 100644 src/12/z2ui5_cl_smps_app_017.clas.abap create mode 100644 src/12/z2ui5_cl_smps_app_017.clas.xml create mode 100644 src/12/z2ui5_cl_smps_app_018.clas.abap create mode 100644 src/12/z2ui5_cl_smps_app_018.clas.xml create mode 100644 src/12/z2ui5_cl_smps_app_019.clas.abap create mode 100644 src/12/z2ui5_cl_smps_app_019.clas.xml create mode 100644 src/12/z2ui5_cl_smps_app_020.clas.abap create mode 100644 src/12/z2ui5_cl_smps_app_020.clas.xml create mode 100644 src/13/README.md create mode 100644 src/13/package.devc.xml create mode 100644 src/13/z2ui5_cl_smps_app_021.clas.abap create mode 100644 src/13/z2ui5_cl_smps_app_021.clas.xml create mode 100644 src/13/z2ui5_cl_smps_app_022.clas.abap create mode 100644 src/13/z2ui5_cl_smps_app_022.clas.xml create mode 100644 src/13/z2ui5_cl_smps_app_023.clas.abap create mode 100644 src/13/z2ui5_cl_smps_app_023.clas.xml create mode 100644 src/13/z2ui5_cl_smps_xlsx.clas.abap create mode 100644 src/13/z2ui5_cl_smps_xlsx.clas.xml create mode 100644 src/14/README.md create mode 100644 src/14/package.devc.xml create mode 100644 src/14/z2ui5_cl_smps_app_024.clas.abap create mode 100644 src/14/z2ui5_cl_smps_app_024.clas.xml diff --git a/.github/packages.json b/.github/packages.json index 10b8a05..b390ab5 100644 --- a/.github/packages.json +++ b/.github/packages.json @@ -89,5 +89,32 @@ "syntax": "v757", "note": "A deep action parameter whose abstract entity carries @Semantics.largeObject is younger than EML: the ABAP parses at 7.57, the feature itself arrived with ABAP Platform 2025 (and the matching BTP ABAP Environment and S/4HANA Cloud releases). If the static action uploadFile does not activate on your system, this package is out of reach.", "shared": ["00"] + }, + { + "dir": "12", + "branch": "12-rap-actions", + "title": "RAP Actions & Permissions", + "runsOn": "Cloud + Standard ≥ 8.16 (2025)", + "syntax": "v757", + "note": "The package is set to the release that carries all of its features. Instance feature control, actions with parameters and factory actions are older; the default function GetDefaultsForCreate and cl_abap_behv_aux are the young ones. GET PERMISSIONS sits in z2ui5_cl_smps_perm_req, which abaplint cannot parse and therefore does not check.", + "shared": ["00"] + }, + { + "dir": "13", + "branch": "13-excel-email", + "title": "Excel & E-Mail", + "runsOn": "Cloud + Standard ≥ 7.58 (2023)", + "syntax": "v757", + "note": "The XCO XLSX library and cl_bcs_mail_message are released ABAP Cloud APIs; on-premise the package needs a release that carries both. The mail sample also needs outbound mail configured.", + "shared": [] + }, + { + "dir": "14", + "branch": "14-adobe-forms", + "title": "Adobe Forms", + "runsOn": "Standard only, ≥ 7.50", + "syntax": "v750", + "note": "Renders through the classic Adobe Forms runtime (FP_JOB_OPEN, the generated function module, FP_JOB_CLOSE), which is not released for ABAP Cloud. Needs Adobe Document Services configured.", + "shared": [] } ] diff --git a/AGENTS.md b/AGENTS.md index 4c8742b..4ac8657 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,7 +71,7 @@ is a link to the class file, which is the qualification. ## 3. The generated one-package branches -abapGit imports a whole repository; there is no sparse checkout. Eleven packages +abapGit imports a whole repository; there is no sparse checkout. Fourteen packages with different floors would mean a system that wants OData also takes APC and gets activation errors for technology it never asked for. So every package is force-pushed to its own branch by `create-package-branches.yaml`, and the @@ -409,7 +409,7 @@ pass over the classes on `main` — `stats.types` and the `*-too-new` findings. - **The overview app stays out of the catalogue page** for the reason it was always out: `Z2UI5_CL_SMPS_APP_000` is this same catalogue inside a system, and as a card it contradicts a list built around what a sample needs — *needs - nothing beyond abap2UI5* — plus a technology chip that filters eleven groups + nothing beyond abap2UI5* — plus a technology chip that filters fourteen groups down to itself. `SAMPLES.md` and both JSON files keep it; their reader is in the repository already. - **Thumbnails** were photographed on every deploy by diff --git a/README.md b/README.md index 15980b3..b8d3f33 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ The Claude Code plugin is two commands: ## Which package do I need? -Most readers need exactly one. The eleven areas are unrelated technologies, none +Most readers need exactly one. The fourteen areas are unrelated technologies, none depends on another — so find the row that says what you came to do, take that package, and skip the rest: @@ -81,6 +81,9 @@ package, and skip the rest: | Expose your app in the Fiori Launchpad — tile, startup parameters, cross-app navigation | [`src/09`](src/09/README.md) — Launchpad | a launchpad with a tile pointing at abap2UI5 | | Call a large language model from your app — a chat, or a table summarized by AI | [`src/10`](src/10/README.md) — AI / LLM | an HTTPS destination to an LLM endpoint (SM59 or BTP), or SAP's ABAP AI SDK | | Upload a file into a RAP business object through an action with a deep parameter | [`src/11`](src/11/README.md) — RAP File Upload | ≥ 2025, the release that carries deep action parameters with `@Semantics.largeObject`; the business object ships with the package | +| Ask a business object what is allowed, call actions with parameters, factory actions and default values, validate while the user types | [`src/12`](src/12/README.md) — RAP Actions & Permissions | ≥ 2025; the business object ships with the package | +| Read an uploaded Excel file, export a table as Excel, send it as a mail attachment | [`src/13`](src/13/README.md) — Excel & E-Mail | ≥ 2023 or ABAP Cloud; outbound mail configured for the mail sample | +| Render Adobe Forms to PDF and download one or several as a ZIP | [`src/14`](src/14/README.md) — Adobe Forms | on-premise; Adobe Document Services configured | Each package README opens with a **What you need** section that turns the last column into concrete steps; the table below adds the exact release floors. @@ -100,6 +103,9 @@ column into concrete steps; the table below adds the exact release floors. | [`src/09`](src/09) | **[Launchpad](src/09/README.md)** — startup parameters, shell title, cross-app navigation | a Fiori Launchpad with a tile pointing at abap2UI5 | Cloud + Standard ≥ 7.40 SP08 | | [`src/10`](src/10) | **[AI / LLM](src/10/README.md)** — chat with a language model, summarize a table with AI | an HTTPS destination to an LLM endpoint (SM59 or BTP), or SAP's ABAP AI SDK | Cloud + Standard ≥ 7.40 SP08 | | [`src/11`](src/11) | **[RAP File Upload](src/11/README.md)** — a file into a business object, as a deep action parameter | ABAP Platform >= 2025; the BO ships with this repo | Cloud + Standard ≥ 8.16 (2025) | +| [`src/12`](src/12) | **[RAP Actions & Permissions](src/12/README.md)** — feature control, parameters, factory actions, defaults, projections | ABAP Platform >= 2025; the BO ships with this repo | Cloud + Standard ≥ 8.16 (2025) | +| [`src/13`](src/13) | **[Excel & E-Mail](src/13/README.md)** — read and write .xlsx with XCO, send it by mail | the XCO XLSX library; outbound mail for the mail sample | Cloud + Standard ≥ 7.58 (2023) | +| [`src/14`](src/14) | **[Adobe Forms](src/14/README.md)** — render forms to PDF, one file or a ZIP | Adobe Document Services (ADS) | Standard only, ≥ 7.50 | The numbering is a reading order, not a dependency chain: `01` starts where most systems already are — an activated OData service — and each package from @@ -124,8 +130,9 @@ earlier: development tenant. Three packages cannot go there, and it is the technology, not the sample, that keeps them out: `06` locks through the function modules `ENQUEUE_E_TABLE` / `ENQUEUE_READ` and holds a stateful ICF session, `07` needs -on-premise APC/AMC, `08` reads the MIME repository over an ICF path. None of those -is a released ABAP Cloud API. +on-premise APC/AMC, `08` reads the MIME repository over an ICF path, `14` renders +Adobe Forms through the classic `FP_JOB_OPEN` runtime. None of those is a released +ABAP Cloud API. **Standard** is the on-premise release, given as the `SAP_BASIS` version with the ABAP Platform name where there is one — `7.54` is 1909, `7.56` is 2021. The number @@ -138,9 +145,10 @@ is the higher of two floors: at. - *the technology the package plays with.* EML lifts `03` and `04` to 1909, RAP business events lift `05` to 2021, a deep action parameter carrying a file - lifts `11` to 2025. If `RAISE ENTITY EVENT` or the action `uploadFile` does not - activate on your system, that package is out of reach and nothing else in this - repository is affected. + lifts `11` to 2025, default functions and the RAP context class lift `12` to + 2025, the XCO XLSX library and the ABAP Cloud mail API lift `13` to 2023. If one + of them does not activate on your system, that package is out of reach and + nothing else in this repository is affected. - *the HTTP client of the stack.* `10` calls a language model over HTTPS, and the two stacks release different clients for that — `cl_http_client` on an SM59 destination on Standard, `cl_web_http_client_manager` on ABAP Cloud. So @@ -154,7 +162,7 @@ of them. ## Setup 1. Install [abap2UI5](https://github.com/abap2UI5/abap2UI5). -2. Pull this repository with [abapGit](https://abapgit.org) — `main` for all eleven +2. Pull this repository with [abapGit](https://abapgit.org) — `main` for all fourteen packages, or the branch of the single package you came for (see [One package at a time](#one-package-at-a-time)). `main` as a whole runs on ABAP Platform >= 1909 or a BTP ABAP Environment — that is what EML asks for, @@ -188,7 +196,7 @@ It is also the honest answer to *what does my system actually support*: the overview looks every sample up at runtime instead of referencing it statically, so a package your release cannot activate — or one you never installed — is listed with its Open button disabled and a Status saying so, rather than taking the whole -overview down. Start it first, and the list tells you which of the eleven packages +overview down. Start it first, and the list tells you which of the fourteen packages this system can run. ## One package at a time @@ -289,7 +297,7 @@ in CI. The node checks carry no dependencies, so they take seconds. | `check-framework-pin` | the abaplint config pins abap2UI5 to a release tag, never to whatever is on `main` | | `check-released-api` | every package whose *Runs on* says Cloud, linted at Cloud against the released objects — an API ABAP Cloud does not release is an error | | `check-catalogue` | [`catalogue.json`](catalogue.json) and `catalogue-derived.json` still are what the generators would write — the same catalogue as data, committed for tooling that fetches one file instead of scanning the tree — and with them every package has a README row that parses, every app sits in a package, and none is missing its `@summary` or `@keywords` | -| `create-package-branches` | rebuilds the eleven per-package branches, each verified with abaplint at its own release before it is pushed | +| `create-package-branches` | rebuilds the fourteen per-package branches, each verified with abaplint at its own release before it is pushed | `check-overview` exists because the overview app names its samples as strings and resolves them at runtime — that is what lets it survive a package the system cannot diff --git a/SAMPLES.md b/SAMPLES.md index c2f5364..d5bfdfa 100644 --- a/SAMPLES.md +++ b/SAMPLES.md @@ -3,7 +3,7 @@ # The sample catalogue -Every app in this repository — 36 of them — with what it shows and a +Every app in this repository — 45 of them — with what it shows and a link to its source. This is the [overview app](src/z2ui5_cl_smps_app_000.clas.abap) as a page you can read here, before installing anything. @@ -103,6 +103,7 @@ type who does not know it exists. `Ctrl+F` on this page uses them. | Sample | Class | |---|---| +| Build the URL of a Launchpad App
Builds the URL of a launchpad app from semantic object, action and parameters - through cl_lsapi_manager where the system has it, as an intent URL of its own where it does not
launchpad fiori flp url intent semantic object action link cl_lsapi_manager open_new_tab | [`Z2UI5_CL_SMPS_APP_025`](src/09/z2ui5_cl_smps_app_025.clas.abap) | | Read Startup Parameters
what the tile passed in - client->get( )-t_comp_params
launchpad fiori flp startup parameters intent | [`Z2UI5_CL_SMPS_APP_481`](src/09/z2ui5_cl_smps_app_481.clas.abap) | | Set Shell Title
follow_up_action( cs_event-set_title_launchpad )
launchpad fiori flp shell title follow_up_action | [`Z2UI5_CL_SMPS_APP_482`](src/09/z2ui5_cl_smps_app_482.clas.abap) | | Cross-App Navigation Sender
hands two values over to another tile
launchpad fiori flp cross app navigation sender intent | [`Z2UI5_CL_SMPS_APP_483`](src/09/z2ui5_cl_smps_app_483.clas.abap) | @@ -122,11 +123,34 @@ type who does not know it exists. `Ctrl+F` on this page uses them. |---|---| | Deep Action Parameter
EXECUTE uploadFile with a deep parameter - the file travels as a stream property, download and delete included
file upload attachment rap action deep parameter largeobject stream eml fileuploader download | [`Z2UI5_CL_SMPS_APP_016`](src/11/z2ui5_cl_smps_app_016.clas.abap) | +### RAP Actions and Permissions — `src/12` + +| Sample | Class | +|---|---| +| **RAP Actions** — Feature Control with GET PERMISSIONS
GET PERMISSIONS asks the business object what may happen to each request - the buttons follow its answer, and the business object refuses the rest anyway
rap eml feature control instance features get permissions enabled disabled buttons | [`Z2UI5_CL_SMPS_APP_017`](src/12/z2ui5_cl_smps_app_017.clas.abap) | +| **RAP Actions** — Parameters, Factory Actions, Defaults
Actions with a parameter - a default function proposes a new request, a static factory action creates it, a factory action copies one, and reject asks for its reason in a popup
rap eml action parameter abstract entity factory action default function popup create copy reject | [`Z2UI5_CL_SMPS_APP_018`](src/12/z2ui5_cl_smps_app_018.clas.abap) | +| **RAP Actions** — Validate on Change with a Determine Action
A determine action runs the validation of the business object while the user types - the quantity is checked on every change, and nothing is saved until Save
rap eml determine action validation check on change valuestate messages without saving | [`Z2UI5_CL_SMPS_APP_019`](src/12/z2ui5_cl_smps_app_019.clas.abap) | +| **RAP Actions** — One Business Object, Two Contracts
One business object, two contracts - approve through the projection Z2UI5_C_SMPS_REQ or on the business object itself, and the handler records which one it was
rap eml projection behavior definition contract cl_abap_behv_aux interface consumer approve | [`Z2UI5_CL_SMPS_APP_020`](src/12/z2ui5_cl_smps_app_020.clas.abap) | + +### Excel and E-Mail — `src/13` + +| Sample | Class | +|---|---| +| **Excel** — Read an Uploaded XLSX File with XCO
An .xlsx file from the FileUploader read with the XCO XLSX library - the first worksheet becomes an ABAP table and is shown as it arrived
excel xlsx upload import read spreadsheet xco fileuploader table | [`Z2UI5_CL_SMPS_APP_021`](src/13/z2ui5_cl_smps_app_021.clas.abap) | +| **Excel** — Export a Table as XLSX File with XCO
The table on the screen written into an .xlsx file with the XCO XLSX library and handed to the browser as a download - a header row, then one row per line
excel xlsx export download write spreadsheet xco table download_b64_file | [`Z2UI5_CL_SMPS_APP_022`](src/13/z2ui5_cl_smps_app_022.clas.abap) | +| **E-Mail** — Send a Table as XLSX Attachment
A mail with an .xlsx attachment, sent with cl_bcs_mail_message - recipient, subject and text from the screen, the attachment written with the XCO XLSX library
email mail send attachment xlsx excel cl_bcs_mail_message smtp | [`Z2UI5_CL_SMPS_APP_023`](src/13/z2ui5_cl_smps_app_023.clas.abap) | + +### Adobe Forms — `src/14` + +| Sample | Class | +|---|---| +| Render to PDF, Download One or a ZIP
Renders Adobe Forms of the system to PDF with FP_JOB_OPEN and the generated function module - one form as a PDF download, several at once as one ZIP file
adobe forms pdf ads fp_job_open print form output zip download cl_abap_zip | [`Z2UI5_CL_SMPS_APP_024`](src/14/z2ui5_cl_smps_app_024.clas.abap) | + --- ## Not samples -17 classes here are not apps and carry no tile: behavior pools, +21 classes here are not apps and carry no tile: behavior pools, demo data, an event consumer, the generated APC protocol class and the language-model layer the AI samples call. They are reached **by** a sample rather than looked up, which is why they are exempt from @@ -152,3 +176,7 @@ able to say they exist. | [`Z2UI5_CL_SMPS_LLM_CLOUD`](src/10/02/z2ui5_cl_smps_llm_cloud.clas.abap) | LLM - HTTP Transport for ABAP Cloud | | [`Z2UI5_CL_SMPS_LLM_ISLM`](src/10/03/z2ui5_cl_smps_llm_islm.clas.abap) | LLM - SAP ABAP AI SDK (ISLM) | | [`Z2UI5_CL_SMPS_BP_FIL`](src/11/01/z2ui5_cl_smps_bp_fil.clas.abap) | abap2UI5 RAP sample - file behavior pool | +| [`Z2UI5_CL_SMPS_BP_REQ`](src/12/01/z2ui5_cl_smps_bp_req.clas.abap) | abap2UI5 RAP sample - request behavior pool | +| [`Z2UI5_CL_SMPS_DATA_REQ`](src/12/01/z2ui5_cl_smps_data_req.clas.abap) | abap2UI5 RAP sample - request demo data | +| [`Z2UI5_CL_SMPS_PERM_REQ`](src/12/01/z2ui5_cl_smps_perm_req.clas.abap) | abap2UI5 RAP sample - request permissions | +| [`Z2UI5_CL_SMPS_XLSX`](src/13/z2ui5_cl_smps_xlsx.clas.abap) | abap2UI5 sample - Excel read and write with XCO | diff --git a/abaplint.jsonc b/abaplint.jsonc index 6130349..07e7242 100644 --- a/abaplint.jsonc +++ b/abaplint.jsonc @@ -12,9 +12,16 @@ // ABAP: the class .clas.xml is not listed, so object_naming and // xml_consistency still cover the object. Drop this once abaplint parses // the RAP event syntax. + // + // z2ui5_cl_smps_perm_req is the same case for the EML statement GET + // PERMISSIONS, which abaplint does not parse at any language version. It + // holds that one statement and nothing else, so the sample app calling + // it, z2ui5_cl_smps_app_017, stays fully checked. Drop it once abaplint + // parses GET PERMISSIONS. "noIssues": [ "z2ui5_cl_smps_evt_tck\\.clas\\.abap$", - "z2ui5_cl_smps_evt_tck\\.clas\\.locals_imp\\.abap$" + "z2ui5_cl_smps_evt_tck\\.clas\\.locals_imp\\.abap$", + "z2ui5_cl_smps_perm_req\\.clas\\.abap$" ] }, "dependencies": [ diff --git a/catalogue-derived.json b/catalogue-derived.json index 5be6c16..3d437f4 100644 --- a/catalogue-derived.json +++ b/catalogue-derived.json @@ -62,15 +62,20 @@ "sap.m.Select", "sap.ui.core.Item", "sap.m.ObjectNumber", - "z2ui5.cc.FileUploader" + "z2ui5.cc.FileUploader", + "sap.m.Switch", + "sap.m.SegmentedButton", + "sap.m.SegmentedButtonItem", + "sap.m.StepInput", + "sap.m.TextArea" ], "counts": { - "samples": 36, - "controls": 55, + "samples": 45, + "controls": 60, "noChain": 0 }, "samples": [ - {"class":"Z2UI5_CL_SMPS_APP_000","minUi5":"1.71","needs":[],"controls":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"controlCount":271}, + {"class":"Z2UI5_CL_SMPS_APP_000","minUi5":"1.71","needs":[],"controls":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"controlCount":340}, {"class":"Z2UI5_CL_SMPS_APP_001","minUi5":"1.71","needs":[],"controls":[1,22,37,7,10,17,38],"controlCount":21}, {"class":"Z2UI5_CL_SMPS_APP_002","minUi5":"1.71","needs":[],"controls":[1,39,22,37,7,10,17,38],"controlCount":22}, {"class":"Z2UI5_CL_SMPS_APP_003","minUi5":"1.71","needs":[],"controls":[1,2,3,22,7,10,11,12,13,18,17],"controlCount":19}, @@ -87,6 +92,15 @@ {"class":"Z2UI5_CL_SMPS_APP_014","minUi5":"1.71","needs":[],"controls":[1,43,44,22,45,5,7,10,14,17],"controlCount":11}, {"class":"Z2UI5_CL_SMPS_APP_015","minUi5":"1.71","needs":[],"controls":[1,2,3,22,37,5,53,7,8,10,11,12,14,17],"controlCount":38}, {"class":"Z2UI5_CL_SMPS_APP_016","minUi5":"1.71","needs":[],"controls":[1,2,3,31,22,37,5,7,10,11,12,13,18,41,17,38,54],"controlCount":35}, + {"class":"Z2UI5_CL_SMPS_APP_017","minUi5":"1.71","needs":[],"controls":[1,2,3,31,37,5,6,7,10,55,11,12,13,18,41,17],"controlCount":31}, + {"class":"Z2UI5_CL_SMPS_APP_018","minUi5":"1.71","needs":[],"controls":[1,2,3,40,31,22,37,5,7,56,57,10,58,11,12,59,13,18,41,15,17,38],"controlCount":50}, + {"class":"Z2UI5_CL_SMPS_APP_019","minUi5":"1.71","needs":[],"controls":[1,22,37,5,23,7,10,12,41,17,38],"controlCount":15}, + {"class":"Z2UI5_CL_SMPS_APP_020","minUi5":"1.71","needs":[],"controls":[1,2,3,31,5,7,10,11,12,13,18,41,17],"controlCount":25}, + {"class":"Z2UI5_CL_SMPS_APP_021","minUi5":"1.71","needs":[],"controls":[1,2,3,31,5,7,10,11,12,17,54],"controlCount":27}, + {"class":"Z2UI5_CL_SMPS_APP_022","minUi5":"1.71","needs":[],"controls":[1,2,3,22,5,7,10,11,12,13,18,41,17],"controlCount":22}, + {"class":"Z2UI5_CL_SMPS_APP_023","minUi5":"1.71","needs":[],"controls":[1,22,37,5,23,7,10,12,59,41,17,38],"controlCount":18}, + {"class":"Z2UI5_CL_SMPS_APP_024","minUi5":"1.71","needs":[],"controls":[1,2,3,37,5,6,23,7,10,11,12,59,41,17,38],"controlCount":22}, + {"class":"Z2UI5_CL_SMPS_APP_025","minUi5":"1.71","needs":[],"controls":[1,2,3,22,37,5,23,7,10,11,12,13,18,41,17,38],"controlCount":31}, {"class":"Z2UI5_CL_SMPS_APP_313","minUi5":"1.71","needs":[],"controls":[7,10,19,20,21,17],"controlCount":6}, {"class":"Z2UI5_CL_SMPS_APP_314","minUi5":"1.71","needs":[],"controls":[2,3,22,7,10,11,12,13,18,17],"controlCount":31}, {"class":"Z2UI5_CL_SMPS_APP_315","minUi5":"1.71","needs":[],"controls":[2,3,7,10,11,12,13,18,17],"controlCount":35}, diff --git a/catalogue.json b/catalogue.json index c8f2987..bf7cbc0 100644 --- a/catalogue.json +++ b/catalogue.json @@ -148,6 +148,42 @@ "note": "A deep action parameter whose abstract entity carries @Semantics.largeObject is younger than EML: the ABAP parses at 7.57, the feature itself arrived with ABAP Platform 2025 (and the matching BTP ABAP Environment and S/4HANA Cloud releases). If the static action uploadFile does not activate on your system, this package is out of reach.", "branch": "11-rap-file-upload", "readme": "src/11/README.md" + }, + { + "package": "src/12", + "technology": "RAP Actions & Permissions", + "topic": "feature control, parameters, factory actions, defaults, projections", + "needs": "ABAP Platform >= 2025; the BO ships with this repo", + "runsOn": "Cloud + Standard ≥ 8.16 (2025)", + "cloud": true, + "release": "8.16 (2025)", + "note": "The package is set to the release that carries all of its features. Instance feature control, actions with parameters and factory actions are older; the default function GetDefaultsForCreate and cl_abap_behv_aux are the young ones. GET PERMISSIONS sits in z2ui5_cl_smps_perm_req, which abaplint cannot parse and therefore does not check.", + "branch": "12-rap-actions", + "readme": "src/12/README.md" + }, + { + "package": "src/13", + "technology": "Excel & E-Mail", + "topic": "read and write .xlsx with XCO, send it by mail", + "needs": "the XCO XLSX library; outbound mail for the mail sample", + "runsOn": "Cloud + Standard ≥ 7.58 (2023)", + "cloud": true, + "release": "7.58 (2023)", + "note": "The XCO XLSX library and cl_bcs_mail_message are released ABAP Cloud APIs; on-premise the package needs a release that carries both. The mail sample also needs outbound mail configured.", + "branch": "13-excel-email", + "readme": "src/13/README.md" + }, + { + "package": "src/14", + "technology": "Adobe Forms", + "topic": "render forms to PDF, one file or a ZIP", + "needs": "Adobe Document Services (ADS)", + "runsOn": "Standard only, ≥ 7.50", + "cloud": false, + "release": "7.50", + "note": "Renders through the classic Adobe Forms runtime (FP_JOB_OPEN, the generated function module, FP_JOB_CLOSE), which is not released for ABAP Cloud. Needs Adobe Document Services configured.", + "branch": "14-adobe-forms", + "readme": "src/14/README.md" } ], "samples": [ @@ -758,6 +794,32 @@ "branch": "08-mime", "setup": "src/08/README.md" }, + { + "class": "Z2UI5_CL_SMPS_APP_025", + "path": "src/09/z2ui5_cl_smps_app_025.clas.abap", + "package": "src/09", + "technology": "Launchpad", + "title": "Build the URL of a Launchpad App", + "summary": "Builds the URL of a launchpad app from semantic object, action and parameters - through cl_lsapi_manager where the system has it, as an intent URL of its own where it does not", + "keywords": [ + "launchpad", + "fiori", + "flp", + "url", + "intent", + "semantic", + "object", + "action", + "link", + "cl_lsapi_manager", + "open_new_tab" + ], + "runsOn": "Cloud + Standard ≥ 7.40 SP08", + "cloud": true, + "needs": "a Fiori Launchpad with a tile pointing at abap2UI5", + "branch": "09-launchpad", + "setup": "src/09/README.md" + }, { "class": "Z2UI5_CL_SMPS_APP_481", "path": "src/09/z2ui5_cl_smps_app_481.clas.abap", @@ -941,6 +1003,210 @@ "needs": "ABAP Platform >= 2025; the BO ships with this repo", "branch": "11-rap-file-upload", "setup": "src/11/README.md" + }, + { + "class": "Z2UI5_CL_SMPS_APP_017", + "path": "src/12/z2ui5_cl_smps_app_017.clas.abap", + "package": "src/12", + "technology": "RAP Actions & Permissions", + "title": "RAP Actions — Feature Control with GET PERMISSIONS", + "summary": "GET PERMISSIONS asks the business object what may happen to each request - the buttons follow its answer, and the business object refuses the rest anyway", + "keywords": [ + "rap", + "eml", + "feature", + "control", + "instance", + "features", + "get", + "permissions", + "enabled", + "disabled", + "buttons" + ], + "runsOn": "Cloud + Standard ≥ 8.16 (2025)", + "cloud": true, + "needs": "ABAP Platform >= 2025; the BO ships with this repo", + "branch": "12-rap-actions", + "setup": "src/12/README.md" + }, + { + "class": "Z2UI5_CL_SMPS_APP_018", + "path": "src/12/z2ui5_cl_smps_app_018.clas.abap", + "package": "src/12", + "technology": "RAP Actions & Permissions", + "title": "RAP Actions — Parameters, Factory Actions, Defaults", + "summary": "Actions with a parameter - a default function proposes a new request, a static factory action creates it, a factory action copies one, and reject asks for its reason in a popup", + "keywords": [ + "rap", + "eml", + "action", + "parameter", + "abstract", + "entity", + "factory", + "action", + "default", + "function", + "popup", + "create", + "copy", + "reject" + ], + "runsOn": "Cloud + Standard ≥ 8.16 (2025)", + "cloud": true, + "needs": "ABAP Platform >= 2025; the BO ships with this repo", + "branch": "12-rap-actions", + "setup": "src/12/README.md" + }, + { + "class": "Z2UI5_CL_SMPS_APP_019", + "path": "src/12/z2ui5_cl_smps_app_019.clas.abap", + "package": "src/12", + "technology": "RAP Actions & Permissions", + "title": "RAP Actions — Validate on Change with a Determine Action", + "summary": "A determine action runs the validation of the business object while the user types - the quantity is checked on every change, and nothing is saved until Save", + "keywords": [ + "rap", + "eml", + "determine", + "action", + "validation", + "check", + "on", + "change", + "valuestate", + "messages", + "without", + "saving" + ], + "runsOn": "Cloud + Standard ≥ 8.16 (2025)", + "cloud": true, + "needs": "ABAP Platform >= 2025; the BO ships with this repo", + "branch": "12-rap-actions", + "setup": "src/12/README.md" + }, + { + "class": "Z2UI5_CL_SMPS_APP_020", + "path": "src/12/z2ui5_cl_smps_app_020.clas.abap", + "package": "src/12", + "technology": "RAP Actions & Permissions", + "title": "RAP Actions — One Business Object, Two Contracts", + "summary": "One business object, two contracts - approve through the projection Z2UI5_C_SMPS_REQ or on the business object itself, and the handler records which one it was", + "keywords": [ + "rap", + "eml", + "projection", + "behavior", + "definition", + "contract", + "cl_abap_behv_aux", + "interface", + "consumer", + "approve" + ], + "runsOn": "Cloud + Standard ≥ 8.16 (2025)", + "cloud": true, + "needs": "ABAP Platform >= 2025; the BO ships with this repo", + "branch": "12-rap-actions", + "setup": "src/12/README.md" + }, + { + "class": "Z2UI5_CL_SMPS_APP_021", + "path": "src/13/z2ui5_cl_smps_app_021.clas.abap", + "package": "src/13", + "technology": "Excel & E-Mail", + "title": "Excel — Read an Uploaded XLSX File with XCO", + "summary": "An .xlsx file from the FileUploader read with the XCO XLSX library - the first worksheet becomes an ABAP table and is shown as it arrived", + "keywords": [ + "excel", + "xlsx", + "upload", + "import", + "read", + "spreadsheet", + "xco", + "fileuploader", + "table" + ], + "runsOn": "Cloud + Standard ≥ 7.58 (2023)", + "cloud": true, + "needs": "the XCO XLSX library; outbound mail for the mail sample", + "branch": "13-excel-email", + "setup": "src/13/README.md" + }, + { + "class": "Z2UI5_CL_SMPS_APP_022", + "path": "src/13/z2ui5_cl_smps_app_022.clas.abap", + "package": "src/13", + "technology": "Excel & E-Mail", + "title": "Excel — Export a Table as XLSX File with XCO", + "summary": "The table on the screen written into an .xlsx file with the XCO XLSX library and handed to the browser as a download - a header row, then one row per line", + "keywords": [ + "excel", + "xlsx", + "export", + "download", + "write", + "spreadsheet", + "xco", + "table", + "download_b64_file" + ], + "runsOn": "Cloud + Standard ≥ 7.58 (2023)", + "cloud": true, + "needs": "the XCO XLSX library; outbound mail for the mail sample", + "branch": "13-excel-email", + "setup": "src/13/README.md" + }, + { + "class": "Z2UI5_CL_SMPS_APP_023", + "path": "src/13/z2ui5_cl_smps_app_023.clas.abap", + "package": "src/13", + "technology": "Excel & E-Mail", + "title": "E-Mail — Send a Table as XLSX Attachment", + "summary": "A mail with an .xlsx attachment, sent with cl_bcs_mail_message - recipient, subject and text from the screen, the attachment written with the XCO XLSX library", + "keywords": [ + "email", + "mail", + "send", + "attachment", + "xlsx", + "excel", + "cl_bcs_mail_message", + "smtp" + ], + "runsOn": "Cloud + Standard ≥ 7.58 (2023)", + "cloud": true, + "needs": "the XCO XLSX library; outbound mail for the mail sample", + "branch": "13-excel-email", + "setup": "src/13/README.md" + }, + { + "class": "Z2UI5_CL_SMPS_APP_024", + "path": "src/14/z2ui5_cl_smps_app_024.clas.abap", + "package": "src/14", + "technology": "Adobe Forms", + "title": "Render to PDF, Download One or a ZIP", + "summary": "Renders Adobe Forms of the system to PDF with FP_JOB_OPEN and the generated function module - one form as a PDF download, several at once as one ZIP file", + "keywords": [ + "adobe", + "forms", + "pdf", + "ads", + "fp_job_open", + "print", + "form", + "output", + "zip", + "download", + "cl_abap_zip" + ], + "runsOn": "Standard only, ≥ 7.50", + "cloud": false, + "needs": "Adobe Document Services (ADS)", + "branch": "14-adobe-forms", + "setup": "src/14/README.md" } ] } diff --git a/src/01/README.md b/src/01/README.md index 8714c9b..0d22d69 100644 --- a/src/01/README.md +++ b/src/01/README.md @@ -15,7 +15,7 @@ view and the service path, nothing more. paths, so nothing here reaches past the ABAP the package is written in. **Branch:** [`01-odata`](https://github.com/abap2UI5/samples-stack/tree/01-odata) -— this package alone, without the other ten on your system. +— this package alone, without the other thirteen on your system. An activated OData V2 service. The sample points at the services of the SAP flight reference scenario: diff --git a/src/02/README.md b/src/02/README.md index 6b711f8..d1046d4 100644 --- a/src/02/README.md +++ b/src/02/README.md @@ -16,7 +16,7 @@ side of these nine samples asks for nothing beyond the release the package is written in — what they need is the service, not the platform. **Branch:** [`02-smart-controls`](https://github.com/abap2UI5/samples-stack/tree/02-smart-controls) -— this package alone, without the other ten on your system. +— this package alone, without the other thirteen on your system. - **SAPUI5**, since `sap.ui.comp` is part of the SAPUI5 distribution. - **An activated OData V2 service.** Most samples point at the Gateway demo service diff --git a/src/03/README.md b/src/03/README.md index 142fc59..41a14a3 100644 --- a/src/03/README.md +++ b/src/03/README.md @@ -24,7 +24,7 @@ The draft enabled half lives next door in [`04` RAP with Draft](../04/README.md) **Release:** Cloud + Standard ≥ 7.54 (1909) — that is what EML asks for. **Branch:** [`03-rap`](https://github.com/abap2UI5/samples-stack/tree/03-rap) -— this package alone, without the other ten on your system. +— this package alone, without the other thirteen on your system. ABAP Platform >= 1909 or a BTP ABAP Environment. The business object `Z2UI5_R_SMPS_TRV` and its table come with this package ([`src/03/01`](01)), so diff --git a/src/04/README.md b/src/04/README.md index 4153b61..7b1d149 100644 --- a/src/04/README.md +++ b/src/04/README.md @@ -16,7 +16,7 @@ whole lifecycle fits in a handful of statements. [`03`](../03/README.md) — draft handling adds nothing on top of EML. **Branch:** [`04-rap-draft`](https://github.com/abap2UI5/samples-stack/tree/04-rap-draft) -— this package alone, without the other ten on your system. +— this package alone, without the other thirteen on your system. ABAP Platform >= 1909 or a BTP ABAP Environment. The draft enabled business object and its two tables come with this package ([`src/04/01`](01)). diff --git a/src/05/README.md b/src/05/README.md index 5c87aa5..76bc0d2 100644 --- a/src/05/README.md +++ b/src/05/README.md @@ -17,7 +17,7 @@ the second shows what arrived. RAP does the wiring in between. repository, and the only package whose limit is not EML. **Branch:** [`05-business-events`](https://github.com/abap2UI5/samples-stack/tree/05-business-events) -— this package alone, without the other ten on your system. +— this package alone, without the other thirteen on your system. ABAP Platform >= 1909 or a BTP ABAP Environment covers the EML part — but this package also needs a release that already carries **RAP business events**. They are diff --git a/src/06/README.md b/src/06/README.md index b574857..6b5c0b0 100644 --- a/src/06/README.md +++ b/src/06/README.md @@ -18,7 +18,7 @@ function modules nor a stateful ICF session is a released cloud API — this pac is on-premise by design, not by omission. **Branch:** [`06-stateful-locks`](https://github.com/abap2UI5/samples-stack/tree/06-stateful-locks) -— this package alone, without the other ten on your system. +— this package alone, without the other thirteen on your system. ABAP Standard (on-premise). The locks go through the function modules `ENQUEUE_E_TABLE`, `DEQUEUE_E_TABLE` and `ENQUEUE_READ`, which are available diff --git a/src/07/README.md b/src/07/README.md index 6dd8e09..b279f25 100644 --- a/src/07/README.md +++ b/src/07/README.md @@ -18,7 +18,7 @@ ABAP Cloud counterpart; the 7.50 comes from the ABAP the two classes are written in, the channels themselves arrived earlier. **Branch:** [`07-amc-apc`](https://github.com/abap2UI5/samples-stack/tree/07-amc-apc) -— this package alone, without the other ten on your system. +— this package alone, without the other thirteen on your system. **Setup:** activate the ICF service `/sap/bc/apc/sap/z2ui5_apc_smp_2` in `SICF`. The app checks this itself and shows a friendly warning strip while the node is still diff --git a/src/08/README.md b/src/08/README.md index 7a39203..885c502 100644 --- a/src/08/README.md +++ b/src/08/README.md @@ -17,7 +17,7 @@ on-premise: in ABAP Cloud there is neither the repository nor the node to reach through. **Branch:** [`08-mime`](https://github.com/abap2UI5/samples-stack/tree/08-mime) -— this package alone, without the other ten on your system. +— this package alone, without the other thirteen on your system. **Setup:** activate the ICF service `/SAP/PUBLIC/BC/ABAP/mime_demo` in `SICF`. The app checks the node and warns if it is inactive. diff --git a/src/09/README.md b/src/09/README.md index 56f4fa4..6b5f102 100644 --- a/src/09/README.md +++ b/src/09/README.md @@ -19,7 +19,7 @@ framework, so what decides here is whether you have a launchpad, not which ABAP release serves it. **Branch:** [`09-launchpad`](https://github.com/abap2UI5/samples-stack/tree/09-launchpad) -— this package alone, without the other ten on your system. +— this package alone, without the other thirteen on your system. A **Fiori Launchpad** with a tile pointing at abap2UI5 — an on-premise FLP (`/ui2/flp`), the launchpad sandbox (`test/flpSandbox`), or a launchpad site on BTP. @@ -44,6 +44,7 @@ buttons instead. | [`482`](z2ui5_cl_smps_app_482.clas.abap) | set the shell title from the backend — `follow_up_action( cs_event-set_title_launchpad )` | | [`483`](z2ui5_cl_smps_app_483.clas.abap) | cross-app navigation, **sender** — hands two values over to another tile | | [`484`](z2ui5_cl_smps_app_484.clas.abap) | cross-app navigation, **receiver** — reads them back out of its startup parameters | +| [`025`](z2ui5_cl_smps_app_025.clas.abap) | build the URL of a launchpad app from semantic object, action and parameters — `cl_lsapi_manager` where the system has it, an intent URL of its own where it does not. Runs standalone too | Start any of them with `?app_start=z2ui5_cl_smps_app_` — from a tile, that is what the target mapping's URL carries. The overview app diff --git a/src/09/z2ui5_cl_smps_app_025.clas.abap b/src/09/z2ui5_cl_smps_app_025.clas.abap new file mode 100644 index 0000000..1c169bc --- /dev/null +++ b/src/09/z2ui5_cl_smps_app_025.clas.abap @@ -0,0 +1,331 @@ +" @keywords launchpad fiori flp url intent semantic object action link cl_lsapi_manager open_new_tab +" @summary Builds the URL of a launchpad app from semantic object, action and parameters - through cl_lsapi_manager where the system has it, as an intent URL of its own where it does not +"!

Launchpad - Build the URL of a Launchpad App

+"! A link into the launchpad is an intent - semantic object, action and +"! parameters behind the hash of the launchpad URL: +"! +"! /sap/bc/ui2/flp#SalesOrder-display?SalesOrder=42 +"! +"! On-premise the class cl_lsapi_manager builds it, the system alias and the +"! launchpad address of the system included: +"! +"! DATA(url) = cl_lsapi_manager=>create_flp_url( +"! object = 'SalesOrder' +"! action = 'display' +"! parameters = VALUE #( ( name = 'SalesOrder' value = '42' ) ) ). +"! +"! The class is not on every system - not on ABAP Cloud, not on older +"! releases - so the sample calls it by name and reads its signature at +"! runtime with RTTI. Where it is missing, the sample builds the intent URL +"! itself. Either way the URL opens in a new tab. +CLASS z2ui5_cl_smps_app_025 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA semantic_object TYPE string. + DATA action TYPE string. + + TYPES: + BEGIN OF ty_s_param, + name TYPE string, + value TYPE string, + END OF ty_s_param. + TYPES ty_t_param TYPE STANDARD TABLE OF ty_s_param WITH EMPTY KEY. + DATA t_params TYPE ty_t_param. + + "! the URL the last press built, and who built it + DATA url TYPE string. + DATA builder TYPE string. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS on_event. + + "! cl_lsapi_manager=>create_flp_url( ), called by name - empty when the + "! class or the method is not on this system or does not answer + METHODS url_by_lsapi + RETURNING + VALUE(result) TYPE string. + + "! the intent URL built here, for every system + METHODS url_by_intent + RETURNING + VALUE(result) TYPE string. + + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smps_app_025 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + semantic_object = `SalesOrder`. + action = `display`. + t_params = VALUE #( ( name = `SalesOrder` value = `42` ) ). + view_display( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD on_event. + + CASE client->get_event( ). + + WHEN `BUILD` OR `OPEN`. + url = url_by_lsapi( ). + builder = `cl_lsapi_manager=>create_flp_url( )`. + IF url IS INITIAL. + url = url_by_intent( ). + builder = `the intent URL of the sample - cl_lsapi_manager is not on this system`. + ENDIF. + + IF client->get_event( ) = `OPEN`. + client->follow_up_action( val = client->cs_event-open_new_tab + t_arg = VALUE #( ( url ) ) ). + ENDIF. + + WHEN `ADD`. + INSERT VALUE #( ) INTO TABLE t_params. + + WHEN `REMOVE`. + DELETE t_params INDEX lines( t_params ). + + ENDCASE. + + ENDMETHOD. + + + METHOD url_by_lsapi. + + CONSTANTS class_name TYPE string VALUE `CL_LSAPI_MANAGER`. + CONSTANTS method_name TYPE abap_methname VALUE `CREATE_FLP_URL`. + + DATA descr TYPE REF TO cl_abap_typedescr. + DATA t_ptab TYPE abap_parmbind_tab. + DATA value_ref TYPE REF TO data. + DATA instance TYPE REF TO object. + FIELD-SYMBOLS TYPE any. + FIELD-SYMBOLS TYPE STANDARD TABLE. + FIELD-SYMBOLS TYPE any. + FIELD-SYMBOLS TYPE any. + + " is the class on this system at all - a type that is not there is an + " exception of describe_by_name, not a syntax error of this class + cl_abap_typedescr=>describe_by_name( EXPORTING p_name = class_name + RECEIVING p_descr_ref = descr + EXCEPTIONS type_not_found = 1 + OTHERS = 2 ). + IF sy-subrc <> 0. + RETURN. + ENDIF. + + DATA(class_descr) = CAST cl_abap_objectdescr( descr ). + READ TABLE class_descr->methods INTO DATA(s_method) WITH KEY name = method_name. + IF sy-subrc <> 0. + RETURN. + ENDIF. + + TRY. + " one entry per parameter of the method, typed exactly as the method + " declares it - the names and types come from the system, not from + " this sample + LOOP AT s_method-parameters INTO DATA(s_param). + + DATA(type) = class_descr->get_method_parameter_type( p_method_name = method_name + p_parameter_name = s_param-name ). + CREATE DATA value_ref TYPE HANDLE type. + ASSIGN value_ref->* TO . + + CASE s_param-name. + WHEN 'OBJECT'. + = semantic_object. + WHEN 'ACTION'. + = action. + WHEN 'PARAMETERS'. + " a table of name/value pairs - filled by component name, so + " its row type may be whatever the system declares + ASSIGN value_ref->* TO
. + LOOP AT t_params INTO DATA(s_nav_param) WHERE name IS NOT INITIAL. + APPEND INITIAL LINE TO
ASSIGNING . + ASSIGN COMPONENT 'NAME' OF STRUCTURE TO . + IF sy-subrc = 0. + = s_nav_param-name. + ENDIF. + ASSIGN COMPONENT 'VALUE' OF STRUCTURE TO . + IF sy-subrc = 0. + = s_nav_param-value. + ENDIF. + ENDLOOP. + WHEN OTHERS. + " a parameter this sample does not fill - an optional one is + " left out, a mandatory one goes in initial + IF s_param-is_optional = abap_true AND s_param-parm_kind <> cl_abap_objectdescr=>returning. + CONTINUE. + ENDIF. + ENDCASE. + + " the kind from the CALLER's side: what the method imports, the + " caller exports - and a RETURNING value is received + INSERT VALUE #( name = s_param-name + kind = SWITCH #( s_param-parm_kind + WHEN cl_abap_objectdescr=>importing THEN cl_abap_objectdescr=>exporting + WHEN cl_abap_objectdescr=>exporting THEN cl_abap_objectdescr=>importing + WHEN cl_abap_objectdescr=>changing THEN cl_abap_objectdescr=>changing + ELSE cl_abap_objectdescr=>receiving ) + value = value_ref ) INTO TABLE t_ptab. + + ENDLOOP. + + IF s_method-is_class = abap_true. + CALL METHOD (class_name)=>(method_name) + PARAMETER-TABLE t_ptab. + ELSE. + CALL METHOD (class_name)=>('GET_INSTANCE') + RECEIVING + result = instance. + CALL METHOD instance->(method_name) + PARAMETER-TABLE t_ptab. + ENDIF. + + " the URL is whatever the method hands back + LOOP AT t_ptab INTO DATA(s_ptab) WHERE kind = cl_abap_objectdescr=>receiving. + ASSIGN s_ptab-value->* TO . + result = . + ENDLOOP. + + CATCH cx_root. + " the method exists but does not answer the way this sample asks - + " the intent URL of the sample takes over + CLEAR result. + ENDTRY. + + ENDMETHOD. + + + METHOD url_by_intent. + + " #-?=&... behind the launchpad of + " this system - every value URL-encoded + DATA(query) = concat_lines_of( + table = VALUE string_table( FOR s_param IN t_params WHERE ( name IS NOT INITIAL ) + ( |{ escape( val = s_param-name format = cl_abap_format=>e_url_full ) }={ + escape( val = s_param-value format = cl_abap_format=>e_url_full ) }| ) ) + sep = `&` ). + + result = |/sap/bc/ui2/flp#{ semantic_object }-{ action }|. + IF query IS NOT INITIAL. + result = |{ result }?{ query }|. + ENDIF. + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + )->a( n = `xmlns:form` v = `sap.ui.layout.form` ). + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - Launchpad - Build the URL of a Launchpad App` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `Semantic object, action and parameters make the intent of a launchpad app. Build URL ` && + `asks cl_lsapi_manager where the system has it and builds the intent URL itself where ` && + `it does not - Open opens the result in a new tab.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + page->ele( n = `SimpleForm` ns = `form` + )->a( n = `title` v = `The intent` + )->a( n = `editable` b = abap_true + )->ele( n = `content` ns = `form` + )->tag( `Label` + )->a( n = `text` v = `Semantic Object` + )->tag( `Input` + )->a( n = `value` v = client->_bind( semantic_object ) + )->tag( `Label` + )->a( n = `text` v = `Action` + )->tag( `Input` + )->a( n = `value` v = client->_bind( action ) ). + + DATA(table) = page->ele( `Table` + )->a( n = `items` v = client->_bind( t_params ) ). + table->ele( `headerToolbar` + )->ele( `Toolbar` + )->tag( `Title` + )->a( n = `text` v = `Parameters` + )->tag( `ToolbarSpacer` + )->tag( `Button` + )->a( n = `press` v = client->_event( `ADD` ) + )->a( n = `icon` v = `sap-icon://add` + )->a( n = `tooltip` v = `Add Parameter` + )->tag( `Button` + )->a( n = `press` v = client->_event( `REMOVE` ) + )->a( n = `icon` v = `sap-icon://less` + )->a( n = `tooltip` v = `Remove Last Parameter` ). + table->ele( `columns` + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Name` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Value` ). + table->ele( `items` + )->ele( `ColumnListItem` + )->ele( `cells` + )->tag( `Input` + )->a( n = `value` v = `{NAME}` + )->tag( `Input` + )->a( n = `value` v = `{VALUE}` ). + + page->ele( n = `SimpleForm` ns = `form` + )->a( n = `title` v = `The URL` + )->a( n = `editable` b = abap_false + )->ele( n = `content` ns = `form` + )->tag( `Label` + )->a( n = `text` v = `URL` + )->tag( `Text` + )->a( n = `text` v = client->_bind( url ) + )->tag( `Label` + )->a( n = `text` v = `Built by` + )->tag( `Text` + )->a( n = `text` v = client->_bind( builder ) ). + + page->ele( `footer` + )->ele( `OverflowToolbar` + )->tag( `ToolbarSpacer` + )->tag( `Button` + )->a( n = `press` v = client->_event( `BUILD` ) + )->a( n = `text` v = `Build URL` + )->tag( `Button` + )->a( n = `press` v = client->_event( `OPEN` ) + )->a( n = `text` v = `Open` + )->a( n = `icon` v = `sap-icon://action` + )->a( n = `type` v = `Emphasized` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/09/z2ui5_cl_smps_app_025.clas.xml b/src/09/z2ui5_cl_smps_app_025.clas.xml new file mode 100644 index 0000000..c8a526f --- /dev/null +++ b/src/09/z2ui5_cl_smps_app_025.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMPS_APP_025 + E + Launchpad - Build the URL of a Launchpad App + 1 + X + X + X + + + + diff --git a/src/10/README.md b/src/10/README.md index c7c1158..dac8248 100644 --- a/src/10/README.md +++ b/src/10/README.md @@ -27,7 +27,7 @@ with the settings sample. **Release:** Cloud + Standard ≥ 7.40 SP08. **Branch:** [`10-ai-llm`](https://github.com/abap2UI5/samples-stack/tree/10-ai-llm) -— this package alone, without the other ten on your system. +— this package alone, without the other thirteen on your system. **An endpoint the system can reach over HTTPS**, and that is the one step this package cannot take for you. The two stacks release different HTTP clients, so diff --git a/src/11/README.md b/src/11/README.md index 71ce048..421f3ec 100644 --- a/src/11/README.md +++ b/src/11/README.md @@ -25,7 +25,7 @@ action `uploadFile` does not activate on your system, this package is out of reach, and nothing else in this repository is affected. **Branch:** [`11-rap-file-upload`](https://github.com/abap2UI5/samples-stack/tree/11-rap-file-upload) -— this package alone, without the other ten on your system. +— this package alone, without the other thirteen on your system. The business object and its table come with this package ([`src/11/01`](01)). There is no demo data: the first file you upload is the first row. diff --git a/src/12/01/package.devc.xml b/src/12/01/package.devc.xml new file mode 100644 index 0000000..e0cda95 --- /dev/null +++ b/src/12/01/package.devc.xml @@ -0,0 +1,10 @@ + + + + + + test objects + + + + diff --git a/src/12/01/z2ui5_a_smps_req_copy.ddls.asddls b/src/12/01/z2ui5_a_smps_req_copy.ddls.asddls new file mode 100644 index 0000000..c358f6e --- /dev/null +++ b/src/12/01/z2ui5_a_smps_req_copy.ddls.asddls @@ -0,0 +1,7 @@ +// The parameter of the factory action copyRequest of z2ui5_r_smps_req: the +// title of the copy. Left empty, the copy keeps the title of its source. +@EndUserText.label: 'abap2UI5 sample - copy parameter' +define abstract entity z2ui5_a_smps_req_copy +{ + Title : abap.char(60); +} diff --git a/src/12/01/z2ui5_a_smps_req_copy.ddls.xml b/src/12/01/z2ui5_a_smps_req_copy.ddls.xml new file mode 100644 index 0000000..0cd747b --- /dev/null +++ b/src/12/01/z2ui5_a_smps_req_copy.ddls.xml @@ -0,0 +1,13 @@ + + + + + + Z2UI5_A_SMPS_REQ_COPY + E + abap2UI5 RAP sample - copy parameter + A + + + + diff --git a/src/12/01/z2ui5_a_smps_req_create.ddls.asddls b/src/12/01/z2ui5_a_smps_req_create.ddls.asddls new file mode 100644 index 0000000..ebb6e6d --- /dev/null +++ b/src/12/01/z2ui5_a_smps_req_create.ddls.asddls @@ -0,0 +1,9 @@ +// The parameter of the static factory action createRequest of +// z2ui5_r_smps_req - everything a new request is created from. +@EndUserText.label: 'abap2UI5 sample - create parameter' +define abstract entity z2ui5_a_smps_req_create +{ + Title : abap.char(60); + Quantity : abap.int4; + Priority : abap.char(1); +} diff --git a/src/12/01/z2ui5_a_smps_req_create.ddls.xml b/src/12/01/z2ui5_a_smps_req_create.ddls.xml new file mode 100644 index 0000000..e90daf9 --- /dev/null +++ b/src/12/01/z2ui5_a_smps_req_create.ddls.xml @@ -0,0 +1,13 @@ + + + + + + Z2UI5_A_SMPS_REQ_CREATE + E + abap2UI5 RAP sample - create parameter + A + + + + diff --git a/src/12/01/z2ui5_a_smps_req_reject.ddls.asddls b/src/12/01/z2ui5_a_smps_req_reject.ddls.asddls new file mode 100644 index 0000000..8addd6d --- /dev/null +++ b/src/12/01/z2ui5_a_smps_req_reject.ddls.asddls @@ -0,0 +1,7 @@ +// The parameter of the action reject of z2ui5_r_smps_req: why the request +// was rejected. It is stored in the field Note of the request. +@EndUserText.label: 'abap2UI5 sample - reject parameter' +define abstract entity z2ui5_a_smps_req_reject +{ + Reason : abap.char(255); +} diff --git a/src/12/01/z2ui5_a_smps_req_reject.ddls.xml b/src/12/01/z2ui5_a_smps_req_reject.ddls.xml new file mode 100644 index 0000000..de824e3 --- /dev/null +++ b/src/12/01/z2ui5_a_smps_req_reject.ddls.xml @@ -0,0 +1,13 @@ + + + + + + Z2UI5_A_SMPS_REQ_REJECT + E + abap2UI5 RAP sample - reject parameter + A + + + + diff --git a/src/12/01/z2ui5_c_smps_req.bdef.asbdef b/src/12/01/z2ui5_c_smps_req.bdef.asbdef new file mode 100644 index 0000000..d7970f0 --- /dev/null +++ b/src/12/01/z2ui5_c_smps_req.bdef.asbdef @@ -0,0 +1,14 @@ +// The projection exposes a SUBSET of z2ui5_r_smps_req: a consumer of this +// contract may change a request and approve it - nothing else. Create, +// delete, reject and the factory actions exist in the business object, but +// not here, so an EML statement against z2ui5_c_smps_req that names them +// does not even compile. +projection; +strict ( 2 ); + +define behavior for z2ui5_c_smps_req alias Request +{ + use update; + + use action approve; +} diff --git a/src/12/01/z2ui5_c_smps_req.bdef.xml b/src/12/01/z2ui5_c_smps_req.bdef.xml new file mode 100644 index 0000000..62c7c61 --- /dev/null +++ b/src/12/01/z2ui5_c_smps_req.bdef.xml @@ -0,0 +1,45 @@ + + + + + + Z2UI5_C_SMPS_REQ + BDEF/BDO + abap2UI5 RAP sample - request projection + 60 + EN + + + ./z2ui5_c_smps_req/source/main/versions + http://www.sap.com/adt/relations/versions + Historic versions + + + /sap/bc/adt/repository/informationsystem/abaplanguageversions?uri=%2Fsap%2Fbc%2Fadt%2Fbo%2Fbehaviordefinitions%2Fz2ui5_c_smps_req + http://www.sap.com/adt/relations/informationsystem/abaplanguageversions + application/vnd.sap.adt.nameditems.v1+xml + Allowed ABAP language versions + + + ./z2ui5_c_smps_req/source/main + http://www.sap.com/adt/relations/source + text/plain + Source Content + + + ./z2ui5_c_smps_req/source/main + http://www.sap.com/adt/relations/source + text/html + Source Content (HTML) + + + EN + X + ./z2ui5_c_smps_req/source/main + ABAP_SOURCE + true + true + + + + diff --git a/src/12/01/z2ui5_c_smps_req.ddls.asddls b/src/12/01/z2ui5_c_smps_req.ddls.asddls new file mode 100644 index 0000000..be4f1c4 --- /dev/null +++ b/src/12/01/z2ui5_c_smps_req.ddls.asddls @@ -0,0 +1,21 @@ +// The projection of z2ui5_r_smps_req - the second contract on the same +// business object. Its behavior definition exposes less than the business +// object offers, see z2ui5_c_smps_req.bdef.asbdef. +@AccessControl.authorizationCheck: #NOT_REQUIRED +@EndUserText.label: 'abap2UI5 RAP sample - request projection' +@Metadata.ignorePropagatedAnnotations: true +define root view entity z2ui5_c_smps_req + provider contract transactional_query + as projection on z2ui5_r_smps_req +{ + key RequestUuid, + Title, + Quantity, + Priority, + Status, + Note, + ApprovedVia, + CreatedBy, + CreatedAt, + LastChangedAt +} diff --git a/src/12/01/z2ui5_c_smps_req.ddls.baseinfo b/src/12/01/z2ui5_c_smps_req.ddls.baseinfo new file mode 100644 index 0000000..4afbb47 --- /dev/null +++ b/src/12/01/z2ui5_c_smps_req.ddls.baseinfo @@ -0,0 +1,7 @@ +{ + "BASEINFO": { + "FROM": [ + "Z2UI5_R_SMPS_REQ" + ] + } +} diff --git a/src/12/01/z2ui5_c_smps_req.ddls.xml b/src/12/01/z2ui5_c_smps_req.ddls.xml new file mode 100644 index 0000000..7d49e84 --- /dev/null +++ b/src/12/01/z2ui5_c_smps_req.ddls.xml @@ -0,0 +1,13 @@ + + + + + + Z2UI5_C_SMPS_REQ + E + abap2UI5 RAP sample - request projection + P + + + + diff --git a/src/12/01/z2ui5_cl_smps_bp_req.clas.abap b/src/12/01/z2ui5_cl_smps_bp_req.clas.abap new file mode 100644 index 0000000..15390e0 --- /dev/null +++ b/src/12/01/z2ui5_cl_smps_bp_req.clas.abap @@ -0,0 +1,10 @@ +"!

abap2UI5 RAP sample - request behavior pool

+"! Behavior pool of the business object z2ui5_r_smps_req. The implementation +"! lives in the local types include - see +"! z2ui5_cl_smps_bp_req.clas.locals_imp.abap. +CLASS z2ui5_cl_smps_bp_req DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF z2ui5_r_smps_req. +ENDCLASS. + + +CLASS z2ui5_cl_smps_bp_req IMPLEMENTATION. +ENDCLASS. diff --git a/src/12/01/z2ui5_cl_smps_bp_req.clas.locals_imp.abap b/src/12/01/z2ui5_cl_smps_bp_req.clas.locals_imp.abap new file mode 100644 index 0000000..9c76642 --- /dev/null +++ b/src/12/01/z2ui5_cl_smps_bp_req.clas.locals_imp.abap @@ -0,0 +1,313 @@ +CLASS lhc_request DEFINITION INHERITING FROM cl_abap_behavior_handler. + + PRIVATE SECTION. + + CONSTANTS: + BEGIN OF cs_status, + new TYPE c LENGTH 1 VALUE 'N', + approved TYPE c LENGTH 1 VALUE 'A', + rejected TYPE c LENGTH 1 VALUE 'R', + END OF cs_status. + + METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION + IMPORTING REQUEST requested_authorizations FOR request RESULT result. + + METHODS get_instance_features FOR INSTANCE FEATURES + IMPORTING keys REQUEST requested_features FOR request RESULT result. + + METHODS getdefaultsforcreate FOR READ + IMPORTING keys FOR FUNCTION request~getdefaultsforcreate RESULT result. + + METHODS setinitialstatus FOR DETERMINE ON MODIFY + IMPORTING keys FOR request~setinitialstatus. + + METHODS validatequantity FOR VALIDATE ON SAVE + IMPORTING keys FOR request~validatequantity. + + METHODS approve FOR MODIFY + IMPORTING keys FOR ACTION request~approve RESULT result. + + METHODS reject FOR MODIFY + IMPORTING keys FOR ACTION request~reject RESULT result. + + METHODS copyrequest FOR MODIFY + IMPORTING keys FOR ACTION request~copyrequest. + + METHODS createrequest FOR MODIFY + IMPORTING keys FOR ACTION request~createrequest. + +ENDCLASS. + + +CLASS lhc_request IMPLEMENTATION. + + METHOD get_global_authorizations. + + " the sample deliberately grants everything - a real business object would + " check an authorization object here. Only what was asked for is answered. + IF requested_authorizations-%create = if_abap_behv=>mk-on. + result-%create = if_abap_behv=>auth-allowed. + ENDIF. + + IF requested_authorizations-%update = if_abap_behv=>mk-on. + result-%update = if_abap_behv=>auth-allowed. + ENDIF. + + IF requested_authorizations-%delete = if_abap_behv=>mk-on. + result-%delete = if_abap_behv=>auth-allowed. + ENDIF. + + IF requested_authorizations-%action-approve = if_abap_behv=>mk-on. + result-%action-approve = if_abap_behv=>auth-allowed. + ENDIF. + + IF requested_authorizations-%action-reject = if_abap_behv=>mk-on. + result-%action-reject = if_abap_behv=>auth-allowed. + ENDIF. + + IF requested_authorizations-%action-copyrequest = if_abap_behv=>mk-on. + result-%action-copyrequest = if_abap_behv=>auth-allowed. + ENDIF. + + IF requested_authorizations-%action-createrequest = if_abap_behv=>mk-on. + result-%action-createrequest = if_abap_behv=>auth-allowed. + ENDIF. + + ENDMETHOD. + + + METHOD get_instance_features. + + " Feature control answers ONE question per instance: what may happen to + " it right now. The runtime asks it before every update, delete and + " action - so a disabled operation is refused no matter who calls it - and + " a UI asks the same question with GET PERMISSIONS to grey out what would + " be refused anyway. + " + " requested_features says which of the answers the caller wants. Nothing + " here is expensive, so all four are computed - a check against it pays + " off once one of them needs a READ of its own. + READ ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE + ENTITY request + FIELDS ( status ) + WITH CORRESPONDING #( keys ) + RESULT DATA(requests). + + " A New request may be changed, approved, rejected and deleted. Once it + " is decided nothing changes any more - and an approved one is a record + " that stays: it cannot even be deleted, a rejected one can. + result = VALUE #( FOR request IN requests + LET open = COND #( WHEN request-status = cs_status-new + THEN if_abap_behv=>fc-o-enabled + ELSE if_abap_behv=>fc-o-disabled ) IN + ( %tky = request-%tky + %update = open + %delete = COND #( WHEN request-status = cs_status-approved + THEN if_abap_behv=>fc-o-disabled + ELSE if_abap_behv=>fc-o-enabled ) + %action-approve = open + %action-reject = open ) ). + + ENDMETHOD. + + + METHOD getdefaultsforcreate. + + " A default function proposes the values of a new instance - it creates + " nothing. The caller reads the proposal, lets the user change it, and + " sends the CREATE itself. %param is a whole instance; whatever is left + " initial here has no proposal. + DATA(today) = cl_abap_context_info=>get_system_date( ). + + result = VALUE #( FOR key IN keys + ( %cid = key-%cid + %param = VALUE #( title = |Request of { today DATE = ISO }| + quantity = 1 + priority = 'M' ) ) ). + + ENDMETHOD. + + + METHOD setinitialstatus. + + READ ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE + ENTITY request + FIELDS ( status ) + WITH CORRESPONDING #( keys ) + RESULT DATA(requests). + + DELETE requests WHERE status IS NOT INITIAL. + IF requests IS INITIAL. + RETURN. + ENDIF. + + MODIFY ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE + ENTITY request + UPDATE FIELDS ( status ) + WITH VALUE #( FOR request IN requests + ( %tky = request-%tky + status = cs_status-new ) ) + REPORTED DATA(update_reported). + + reported = CORRESPONDING #( DEEP update_reported ). + + ENDMETHOD. + + + METHOD validatequantity. + + READ ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE + ENTITY request + FIELDS ( quantity ) + WITH CORRESPONDING #( keys ) + RESULT DATA(requests). + + LOOP AT requests INTO DATA(request). + + " %state_area groups the messages of one validation: the runtime clears + " the area before the validation runs again, so a corrected value does + " not keep the old message - which matters once the determine action + " checkRequest runs it on every change + APPEND VALUE #( %tky = request-%tky + %state_area = 'VALIDATE_QUANTITY' ) TO reported-request. + + IF request-quantity BETWEEN 1 AND 1000. + CONTINUE. + ENDIF. + + APPEND VALUE #( %tky = request-%tky ) TO failed-request. + APPEND VALUE #( %tky = request-%tky + %state_area = 'VALIDATE_QUANTITY' + %element-quantity = if_abap_behv=>mk-on + %msg = new_message_with_text( + severity = if_abap_behv_message=>severity-error + text = 'Quantity must be between 1 and 1000' ) ) TO reported-request. + + ENDLOOP. + + ENDMETHOD. + + + METHOD approve. + + " cl_abap_behv_aux tells the handler HOW it was called: through the + " projection z2ui5_c_smps_req, or on the business object itself. The + " sample stores the answer, so the list shows which contract approved. + cl_abap_behv_aux=>get_current_context( IMPORTING from_projection = DATA(projection) ). + + MODIFY ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE + ENTITY request + UPDATE FIELDS ( status approvedvia ) + WITH VALUE #( FOR key IN keys + ( %tky = key-%tky + status = cs_status-approved + approvedvia = COND #( WHEN projection IS INITIAL + THEN 'Z2UI5_R_SMPS_REQ' + ELSE projection ) ) ) + FAILED DATA(update_failed) + REPORTED DATA(update_reported). + + failed = CORRESPONDING #( DEEP update_failed ). + reported = CORRESPONDING #( DEEP update_reported ). + + READ ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE + ENTITY request + ALL FIELDS WITH CORRESPONDING #( keys ) + RESULT DATA(requests). + + result = VALUE #( FOR request IN requests + ( %tky = request-%tky + %param = request ) ). + + ENDMETHOD. + + + METHOD reject. + + " the parameter arrives per key in %param - one reason per request + LOOP AT keys INTO DATA(key) WHERE %param-reason IS INITIAL. + + APPEND VALUE #( %tky = key-%tky ) TO failed-request. + APPEND VALUE #( %tky = key-%tky + %msg = new_message_with_text( + severity = if_abap_behv_message=>severity-error + text = 'A rejection needs a reason' ) ) TO reported-request. + + ENDLOOP. + + MODIFY ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE + ENTITY request + UPDATE FIELDS ( status note ) + WITH VALUE #( FOR k IN keys WHERE ( %param-reason IS NOT INITIAL ) + ( %tky = k-%tky + status = cs_status-rejected + note = k-%param-reason ) ). + + READ ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE + ENTITY request + ALL FIELDS WITH VALUE #( FOR k IN keys WHERE ( %param-reason IS NOT INITIAL ) ( %tky = k-%tky ) ) + RESULT DATA(requests). + + result = VALUE #( FOR request IN requests + ( %tky = request-%tky + %param = request ) ). + + ENDMETHOD. + + + METHOD copyrequest. + + " A factory action creates a NEW instance from an existing one. The new + " instance is created under the %cid of the action call, so MAPPED reaches + " the caller under the very %cid it sent, together with the new key. + READ ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE + ENTITY request + FIELDS ( title quantity priority ) + WITH CORRESPONDING #( keys ) + RESULT DATA(sources). + + MODIFY ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE + ENTITY request + CREATE FIELDS ( title quantity priority ) + WITH VALUE #( FOR key IN keys + LET source = VALUE #( sources[ %tky = key-%tky ] OPTIONAL ) IN + ( %cid = key-%cid + title = COND #( WHEN key-%param-title IS NOT INITIAL + THEN key-%param-title + ELSE source-title ) + quantity = source-quantity + priority = source-priority ) ) + MAPPED DATA(create_mapped) + FAILED DATA(create_failed) + REPORTED DATA(create_reported). + + mapped = CORRESPONDING #( DEEP create_mapped ). + failed = CORRESPONDING #( DEEP create_failed ). + reported = CORRESPONDING #( DEEP create_reported ). + + ENDMETHOD. + + + METHOD createrequest. + + " the static twin of copyRequest: no source instance, everything comes + " from the parameter + MODIFY ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE + ENTITY request + CREATE FIELDS ( title quantity priority ) + WITH VALUE #( FOR key IN keys + ( %cid = key-%cid + title = key-%param-title + quantity = key-%param-quantity + priority = key-%param-priority ) ) + MAPPED DATA(create_mapped) + FAILED DATA(create_failed) + REPORTED DATA(create_reported). + + mapped = CORRESPONDING #( DEEP create_mapped ). + failed = CORRESPONDING #( DEEP create_failed ). + reported = CORRESPONDING #( DEEP create_reported ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/12/01/z2ui5_cl_smps_bp_req.clas.xml b/src/12/01/z2ui5_cl_smps_bp_req.clas.xml new file mode 100644 index 0000000..c8cef11 --- /dev/null +++ b/src/12/01/z2ui5_cl_smps_bp_req.clas.xml @@ -0,0 +1,18 @@ + + + + + + Z2UI5_CL_SMPS_BP_REQ + E + abap2UI5 RAP sample - request behavior pool + 06 + 1 + X + X + X + Z2UI5_R_SMPS_REQ + + + + diff --git a/src/12/01/z2ui5_cl_smps_data_req.clas.abap b/src/12/01/z2ui5_cl_smps_data_req.clas.abap new file mode 100644 index 0000000..e1389ce --- /dev/null +++ b/src/12/01/z2ui5_cl_smps_data_req.clas.abap @@ -0,0 +1,112 @@ +"!

abap2UI5 RAP sample - request demo data

+"! Demo data for the business object z2ui5_r_smps_req: three requests, two +"! of them New and one Rejected. +"! +"! Run it in ADT with F9 - it is a console application - or press the button +"! in one of the sample apps, which calls the same method. Everything goes +"! through the business object with EML, the creates through its static +"! factory action createRequest. +CLASS z2ui5_cl_smps_data_req DEFINITION PUBLIC FINAL CREATE PUBLIC. + + PUBLIC SECTION. + INTERFACES if_oo_adt_classrun. + + "! Deletes every request the business object lets go of, then creates + "! the demo set. Approved requests stay: the feature control of the + "! business object refuses to delete them, and the demo data respects + "! that like any other caller. + CLASS-METHODS data_reset + RETURNING + VALUE(result) TYPE string. + + PROTECTED SECTION. + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smps_data_req IMPLEMENTATION. + + METHOD if_oo_adt_classrun~main. + + out->write( data_reset( ) ). + + ENDMETHOD. + + + METHOD data_reset. + + SELECT FROM z2ui5_r_smps_req + FIELDS RequestUuid + WHERE Status <> 'A' + ORDER BY RequestUuid + INTO TABLE @DATA(t_keys). + + IF t_keys IS NOT INITIAL. + + MODIFY ENTITIES OF z2ui5_r_smps_req + ENTITY request + DELETE FROM VALUE #( FOR s_key IN t_keys ( requestuuid = s_key-requestuuid ) ) + FAILED DATA(s_failed_delete). + + IF s_failed_delete IS NOT INITIAL. + ROLLBACK ENTITIES. + result = `Deletion refused by the business object - no demo data created.`. + RETURN. + ENDIF. + + ENDIF. + + MODIFY ENTITIES OF z2ui5_r_smps_req + ENTITY request + EXECUTE createrequest + FROM VALUE #( ( %cid = `DEMO_1` %param = VALUE #( title = 'Laptop for the new colleague' quantity = 1 priority = 'H' ) ) + ( %cid = `DEMO_2` %param = VALUE #( title = 'Printer paper' quantity = 50 priority = 'L' ) ) + ( %cid = `DEMO_3` %param = VALUE #( title = 'Monitor stands' quantity = 4 priority = 'M' ) ) ) + MAPPED DATA(s_mapped) + FAILED DATA(s_failed). + + IF s_failed IS NOT INITIAL. + ROLLBACK ENTITIES. + result = `Demo data rejected by the business object.`. + RETURN. + ENDIF. + + COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_req + FAILED DATA(s_failed_commit). + + IF s_failed_commit IS NOT INITIAL. + ROLLBACK ENTITIES. + result = `Demo data rejected by the business object on commit.`. + RETURN. + ENDIF. + + " the keys are known now - MAPPED carries them under the %cid sent above. + " One request is rejected, so a decided request is there from the start. + DATA(key_3) = VALUE #( s_mapped-request[ %cid = `DEMO_3` ]-requestuuid OPTIONAL ). + + MODIFY ENTITIES OF z2ui5_r_smps_req + ENTITY request + EXECUTE reject FROM VALUE #( ( requestuuid = key_3 + %param = VALUE #( reason = 'Not in this year''s budget' ) ) ) + FAILED DATA(s_failed_reject). + + IF s_failed_reject IS NOT INITIAL. + ROLLBACK ENTITIES. + result = `3 demo requests created, all of them New.`. + RETURN. + ENDIF. + + COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_req + FAILED DATA(s_failed_reject_commit). + + IF s_failed_reject_commit IS NOT INITIAL. + ROLLBACK ENTITIES. + result = `3 demo requests created, all of them New.`. + RETURN. + ENDIF. + + result = `3 demo requests created.`. + + ENDMETHOD. + +ENDCLASS. diff --git a/src/12/01/z2ui5_cl_smps_data_req.clas.xml b/src/12/01/z2ui5_cl_smps_data_req.clas.xml new file mode 100644 index 0000000..546b59d --- /dev/null +++ b/src/12/01/z2ui5_cl_smps_data_req.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMPS_DATA_REQ + E + abap2UI5 RAP sample - request demo data + 1 + X + X + X + + + + diff --git a/src/12/01/z2ui5_cl_smps_perm_req.clas.abap b/src/12/01/z2ui5_cl_smps_perm_req.clas.abap new file mode 100644 index 0000000..065eec7 --- /dev/null +++ b/src/12/01/z2ui5_cl_smps_perm_req.clas.abap @@ -0,0 +1,65 @@ +"!

abap2UI5 RAP sample - request permissions

+"! Asks the business object z2ui5_r_smps_req which operations its feature +"! control allows per request - the one GET PERMISSIONS statement of package +"! 12, kept in a class of its own because abaplint has no grammar for it (see +"! the noIssues entry in abaplint.jsonc). z2ui5_cl_smps_app_017 calls it. +CLASS z2ui5_cl_smps_perm_req DEFINITION PUBLIC FINAL CREATE PUBLIC. + + PUBLIC SECTION. + + TYPES ty_t_uuid TYPE STANDARD TABLE OF z2ui5_t_smps_req-request_uuid WITH EMPTY KEY. + + TYPES: + BEGIN OF ty_s_permission, + request_uuid TYPE z2ui5_t_smps_req-request_uuid, + update TYPE abap_bool, + delete TYPE abap_bool, + approve TYPE abap_bool, + reject TYPE abap_bool, + END OF ty_s_permission. + TYPES ty_t_permission TYPE STANDARD TABLE OF ty_s_permission WITH EMPTY KEY. + + "! one row per request the business object knows - a key it does not + "! know is left out + CLASS-METHODS get + IMPORTING + t_uuid TYPE ty_t_uuid + RETURNING + VALUE(result) TYPE ty_t_permission. + + PROTECTED SECTION. + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smps_perm_req IMPLEMENTATION. + + METHOD get. + + IF t_uuid IS INITIAL. + RETURN. + ENDIF. + + " ONLY INSTANCE FEATURES restricts the question to feature control - GET + " PERMISSIONS can also ask for the authorizations, global and per + " instance. REQUEST names what we want to know; what is not requested + " comes back initial. + GET PERMISSIONS ONLY INSTANCE FEATURES OF z2ui5_r_smps_req + ENTITY request + FROM VALUE #( FOR uuid IN t_uuid ( requestuuid = uuid ) ) + REQUEST VALUE #( %update = if_abap_behv=>mk-on + %delete = if_abap_behv=>mk-on + %action-approve = if_abap_behv=>mk-on + %action-reject = if_abap_behv=>mk-on ) + RESULT DATA(s_permissions). + + result = VALUE #( FOR s_instance IN s_permissions-instances + ( request_uuid = s_instance-requestuuid + update = xsdbool( s_instance-%update = if_abap_behv=>fc-o-enabled ) + delete = xsdbool( s_instance-%delete = if_abap_behv=>fc-o-enabled ) + approve = xsdbool( s_instance-%action-approve = if_abap_behv=>fc-o-enabled ) + reject = xsdbool( s_instance-%action-reject = if_abap_behv=>fc-o-enabled ) ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/12/01/z2ui5_cl_smps_perm_req.clas.xml b/src/12/01/z2ui5_cl_smps_perm_req.clas.xml new file mode 100644 index 0000000..1a6c47d --- /dev/null +++ b/src/12/01/z2ui5_cl_smps_perm_req.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMPS_PERM_REQ + E + abap2UI5 RAP sample - request permissions + 1 + X + X + X + + + + diff --git a/src/12/01/z2ui5_r_smps_req.bdef.asbdef b/src/12/01/z2ui5_r_smps_req.bdef.asbdef new file mode 100644 index 0000000..c24ac52 --- /dev/null +++ b/src/12/01/z2ui5_r_smps_req.bdef.asbdef @@ -0,0 +1,62 @@ +// Managed business object of package 12. A request is created New (N) and +// ends Approved (A) or Rejected (R). Everything the samples +// z2ui5_cl_smps_app_017 to z2ui5_cl_smps_app_020 call is declared here: +// +// - instance feature control: update, approve and reject are only enabled +// while a request is New, and an Approved request cannot be deleted - +// asked with GET PERMISSIONS +// - an action with a parameter (reject), an instance factory action with a +// parameter (copyRequest) and a static factory action (createRequest) +// - a default function that proposes the values of a new request +// - a determine action that runs a validation on demand, before the save +managed implementation in class z2ui5_cl_smps_bp_req unique; +strict ( 2 ); + +define behavior for z2ui5_r_smps_req alias Request +persistent table z2ui5_t_smps_req +lock master +authorization master ( global ) +etag master LastChangedAt +{ + create { default function GetDefaultsForCreate; } + update ( features : instance ); + delete ( features : instance ); + + field ( numbering : managed, readonly ) RequestUuid; + + // set by the determination and the actions, never by the caller + field ( readonly ) Status, Note, ApprovedVia; + + // filled by the managed runtime from the @Semantics annotations of the CDS + field ( readonly ) CreatedBy, CreatedAt, LastChangedAt; + + field ( mandatory ) Title; + + determination setInitialStatus on modify { create; } + + validation validateQuantity on save { create; update; field Quantity; } + + // runs the validation NOW, without saving - the caller gets its messages + // in REPORTED while the user is still typing + determine action checkRequest { validation validateQuantity; } + + action ( features : instance ) approve result [1] $self; + action ( features : instance ) reject parameter z2ui5_a_smps_req_reject result [1] $self; + + factory action copyRequest parameter z2ui5_a_smps_req_copy [1]; + static factory action createRequest parameter z2ui5_a_smps_req_create [1]; + + mapping for z2ui5_t_smps_req + { + RequestUuid = request_uuid; + Title = title; + Quantity = quantity; + Priority = priority; + Status = status; + Note = note; + ApprovedVia = approved_via; + CreatedBy = created_by; + CreatedAt = created_at; + LastChangedAt = last_changed_at; + } +} diff --git a/src/12/01/z2ui5_r_smps_req.bdef.xml b/src/12/01/z2ui5_r_smps_req.bdef.xml new file mode 100644 index 0000000..05d5c96 --- /dev/null +++ b/src/12/01/z2ui5_r_smps_req.bdef.xml @@ -0,0 +1,45 @@ + + + + + + Z2UI5_R_SMPS_REQ + BDEF/BDO + abap2UI5 RAP sample - request + 60 + EN + + + ./z2ui5_r_smps_req/source/main/versions + http://www.sap.com/adt/relations/versions + Historic versions + + + /sap/bc/adt/repository/informationsystem/abaplanguageversions?uri=%2Fsap%2Fbc%2Fadt%2Fbo%2Fbehaviordefinitions%2Fz2ui5_r_smps_req + http://www.sap.com/adt/relations/informationsystem/abaplanguageversions + application/vnd.sap.adt.nameditems.v1+xml + Allowed ABAP language versions + + + ./z2ui5_r_smps_req/source/main + http://www.sap.com/adt/relations/source + text/plain + Source Content + + + ./z2ui5_r_smps_req/source/main + http://www.sap.com/adt/relations/source + text/html + Source Content (HTML) + + + EN + X + ./z2ui5_r_smps_req/source/main + ABAP_SOURCE + true + true + + + + diff --git a/src/12/01/z2ui5_r_smps_req.ddls.asddls b/src/12/01/z2ui5_r_smps_req.ddls.asddls new file mode 100644 index 0000000..32d9c50 --- /dev/null +++ b/src/12/01/z2ui5_r_smps_req.ddls.asddls @@ -0,0 +1,22 @@ +@AccessControl.authorizationCheck: #NOT_REQUIRED +@EndUserText.label: 'abap2UI5 RAP sample - request' +@Metadata.ignorePropagatedAnnotations: true +define root view entity z2ui5_r_smps_req + as select from z2ui5_t_smps_req +{ + key request_uuid as RequestUuid, + + title as Title, + quantity as Quantity, + priority as Priority, + status as Status, + note as Note, + approved_via as ApprovedVia, + + @Semantics.user.createdBy: true + created_by as CreatedBy, + @Semantics.systemDateTime.createdAt: true + created_at as CreatedAt, + @Semantics.systemDateTime.localInstanceLastChangedAt: true + last_changed_at as LastChangedAt +} diff --git a/src/12/01/z2ui5_r_smps_req.ddls.baseinfo b/src/12/01/z2ui5_r_smps_req.ddls.baseinfo new file mode 100644 index 0000000..80e83c8 --- /dev/null +++ b/src/12/01/z2ui5_r_smps_req.ddls.baseinfo @@ -0,0 +1,7 @@ +{ + "BASEINFO": { + "FROM": [ + "Z2UI5_T_SMPS_REQ" + ] + } +} diff --git a/src/12/01/z2ui5_r_smps_req.ddls.xml b/src/12/01/z2ui5_r_smps_req.ddls.xml new file mode 100644 index 0000000..3728652 --- /dev/null +++ b/src/12/01/z2ui5_r_smps_req.ddls.xml @@ -0,0 +1,13 @@ + + + + + + Z2UI5_R_SMPS_REQ + E + abap2UI5 RAP sample - request + W + + + + diff --git a/src/12/01/z2ui5_t_smps_req.tabl.xml b/src/12/01/z2ui5_t_smps_req.tabl.xml new file mode 100644 index 0000000..6b275d6 --- /dev/null +++ b/src/12/01/z2ui5_t_smps_req.tabl.xml @@ -0,0 +1,140 @@ + + + + + + Z2UI5_T_SMPS_REQ + E + TRANSP + X + abap2UI5 RAP sample - request + E + A + 1 + + + Z2UI5_T_SMPS_REQ + A + 0 + APPL1 + N + + + + CLIENT + X + 0 + C + 000006 + X + CLNT + 000003 + CLNT + + + REQUEST_UUID + X + 0 + X + 000016 + X + RAW + 000016 + RAW + + + TITLE + 0 + C + 000120 + X + CHAR + 000060 + CHAR + + + QUANTITY + 0 + X + 000004 + X + INT4 + 000010 + INT4 + + + PRIORITY + 0 + C + 000002 + X + CHAR + 000001 + CHAR + + + STATUS + 0 + C + 000002 + X + CHAR + 000001 + CHAR + + + NOTE + 0 + C + 000510 + X + CHAR + 000255 + CHAR + + + APPROVED_VIA + 0 + C + 000060 + X + CHAR + 000030 + CHAR + + + CREATED_BY + 0 + C + 000024 + X + CHAR + 000012 + CHAR + + + CREATED_AT + 0 + P + 000011 + X + DEC + 000021 + 000007 + DEC + + + LAST_CHANGED_AT + 0 + P + 000011 + X + DEC + 000021 + 000007 + DEC + + + + + diff --git a/src/12/README.md b/src/12/README.md new file mode 100644 index 0000000..952499e --- /dev/null +++ b/src/12/README.md @@ -0,0 +1,96 @@ +# 12 — RAP Actions & Permissions + +*[← all packages](../../README.md)* + +A business object does more than create, update and delete. It decides what is +allowed right now, offers actions that need input, proposes values for a new +instance, and checks values before they are saved. This package shows all of it +from the consumer's side, with EML, on a small business object that ships with +it: `Z2UI5_R_SMPS_REQ`, a **request** that is created New and ends Approved or +Rejected. + +Everything a Fiori Elements app gets from the same behavior definition is there +for an abap2UI5 app too: the rules live in the business object, the app asks +for them and builds its screen from the answer. + +## What you need + +**Release:** Cloud + Standard ≥ 8.16 (2025). The package is set to the release +that carries all of its features. Instance feature control, actions with +parameters and factory actions are older; the default function +`GetDefaultsForCreate` and `cl_abap_behv_aux` are the young ones. If the +business object does not activate on your system, this package is out of reach, +and nothing else in this repository is affected. + +**Branch:** [`12-rap-actions`](https://github.com/abap2UI5/samples-stack/tree/12-rap-actions) +— this package alone, without the other thirteen on your system. + +The business object, its projection and its table come with this package +([`src/12/01`](01)). Fill the table with `Z2UI5_CL_SMPS_DATA_REQ` (F9 in ADT), +with *Reset Demo Data* in any of the four samples, or with *Regenerate Demo +Data* in the overview app. An approved request is never deleted: the feature +control of the business object refuses it, and the demo data respects that like +any other caller. + +## The business object + +| Object | What it is | +|---|---| +| `Z2UI5_T_SMPS_REQ` | the table | +| `Z2UI5_R_SMPS_REQ` | root view entity and behavior definition, managed | +| `Z2UI5_C_SMPS_REQ` | the projection — a second contract that exposes only `update` and `approve` | +| `Z2UI5_A_SMPS_REQ_REJECT`, `_COPY`, `_CREATE` | abstract entities, the parameters of the actions | +| `Z2UI5_CL_SMPS_BP_REQ` | the behavior pool | +| `Z2UI5_CL_SMPS_PERM_REQ` | the one `GET PERMISSIONS` statement of the package, see below | +| `Z2UI5_CL_SMPS_DATA_REQ` | demo data | + +What the behavior definition declares, and what the samples use: + +| Declaration | What it does | +|---|---| +| `update ( features : instance )`, `delete ( features : instance )`, `action ( features : instance ) approve / reject` | a New request may be changed, approved, rejected and deleted. A decided one may not be changed, and an approved one cannot even be deleted | +| `action reject parameter z2ui5_a_smps_req_reject` | an action that needs input: the reason, stored in `Note` | +| `factory action copyRequest parameter z2ui5_a_smps_req_copy [1]` | creates a new request from an existing one | +| `static factory action createRequest parameter z2ui5_a_smps_req_create [1]` | creates a request from its parameter alone | +| `create { default function GetDefaultsForCreate; }` | proposes the values of a new request | +| `validation validateQuantity on save` + `determine action checkRequest { validation validateQuantity; }` | the quantity must be between 1 and 1000 — checked at the save, or on demand through the determine action | + +## The samples + +| You want to | Statement | Sample | +|---|---|---| +| ask what is allowed and grey out the rest | `GET PERMISSIONS ONLY INSTANCE FEATURES` | [`017`](z2ui5_cl_smps_app_017.clas.abap) | +| propose, create, copy and reject with parameters | `READ … EXECUTE getdefaultsforcreate`, `EXECUTE createrequest / copyrequest / reject` with `%param` | [`018`](z2ui5_cl_smps_app_018.clas.abap) | +| check a value while the user types, without saving | `UPDATE` + `EXECUTE checkrequest`, then `ROLLBACK ENTITIES` | [`019`](z2ui5_cl_smps_app_019.clas.abap) | +| use the same business object through two contracts | EML on `Z2UI5_C_SMPS_REQ` and on `Z2UI5_R_SMPS_REQ`, `cl_abap_behv_aux` in the handler | [`020`](z2ui5_cl_smps_app_020.clas.abap) | + +**Ask, then act — but the business object decides.** `017` greys out what the +feature control would refuse. Its switch *Ignore permissions* enables every +button anyway: press one on a decided request and the business object answers +with `FAILED`. The UI mirrors the rule, it does not enforce it. + +**A factory action answers in `MAPPED`.** It has no `RESULT`: the new instance +comes back in `MAPPED`, under the `%cid` the caller sent. + +**A default function changes nothing.** It is called with `READ ENTITIES … +EXECUTE`, and its `RESULT` carries a whole instance in `%param` — the proposal. +The create is a second step, sent by the caller. + +**A determine action checks without saving.** `019` writes the new value into +the transactional buffer, runs `checkRequest` in the same statement, reads the +messages from `REPORTED` and rolls back. Save runs the validation once more — +that one counts. + +## Why `GET PERMISSIONS` sits in a class of its own + +abaplint has no grammar for `GET PERMISSIONS` at any language version, so a file +containing it does not parse. `Z2UI5_CL_SMPS_PERM_REQ` holds that one statement +and nothing else, and is listed in `noIssues` in `abaplint.jsonc` — the same way +the RAP event handler of [`05`](../05/README.md) is. The sample app that calls +it, `Z2UI5_CL_SMPS_APP_017`, stays fully checked. + +## Where to go next + +- [`03` RAP](../03/README.md) — the basic EML statements, one per sample. +- [`11` RAP File Upload](../11/README.md) — a deep action parameter that carries + a file. diff --git a/src/12/package.devc.xml b/src/12/package.devc.xml new file mode 100644 index 0000000..0d5b084 --- /dev/null +++ b/src/12/package.devc.xml @@ -0,0 +1,10 @@ + + + + + + RAP Actions and Permissions + + + + diff --git a/src/12/z2ui5_cl_smps_app_017.clas.abap b/src/12/z2ui5_cl_smps_app_017.clas.abap new file mode 100644 index 0000000..ee7dad1 --- /dev/null +++ b/src/12/z2ui5_cl_smps_app_017.clas.abap @@ -0,0 +1,364 @@ +" @keywords rap eml feature control instance features get permissions enabled disabled buttons +" @summary GET PERMISSIONS asks the business object what may happen to each request - the buttons follow its answer, and the business object refuses the rest anyway +"!

RAP Actions - Feature Control with GET PERMISSIONS

+"! The business object z2ui5_r_smps_req decides per instance what may happen +"! to it: a New request may be changed, approved, rejected and deleted, a +"! decided one may not - and an approved one cannot even be deleted. That is +"! instance feature control, implemented in get_instance_features of the +"! behavior pool. +"! +"! The runtime asks it before every operation, so a disabled one is refused +"! whoever calls it. A UI asks the very same question up front, so it can +"! grey out what would be refused: +"! +"! GET PERMISSIONS ONLY INSTANCE FEATURES OF z2ui5_r_smps_req +"! ENTITY request +"! FROM VALUE #( ( requestuuid = ... ) ) +"! REQUEST VALUE #( %update = if_abap_behv=>mk-on +"! %delete = if_abap_behv=>mk-on +"! %action-approve = if_abap_behv=>mk-on +"! %action-reject = if_abap_behv=>mk-on ) +"! RESULT DATA(s_permissions) +"! FAILED DATA(s_failed) +"! REPORTED DATA(s_reported). +"! +"! The switch "Ignore permissions" enables every button anyway - press one on +"! a decided request and the business object answers with FAILED: the UI only +"! mirrors the rule, the business object enforces it. +CLASS z2ui5_cl_smps_app_017 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + TYPES: + BEGIN OF ty_s_request, + request_uuid TYPE string, + title TYPE string, + quantity TYPE string, + status TYPE string, + status_state TYPE string, + note TYPE string, + "! the answer of GET PERMISSIONS, one flag per operation + update_enabled TYPE abap_bool, + delete_enabled TYPE abap_bool, + approve_enabled TYPE abap_bool, + reject_enabled TYPE abap_bool, + END OF ty_s_request. + TYPES ty_t_request TYPE STANDARD TABLE OF ty_s_request WITH EMPTY KEY. + DATA t_requests TYPE ty_t_request. + + "! enables every button, whatever the business object says + DATA ignore_permissions TYPE abap_bool. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS on_event. + METHODS data_read. + METHODS permissions_read. + METHODS request_approve. + METHODS request_reject. + METHODS request_delete. + METHODS data_save + RETURNING + VALUE(result) TYPE abap_bool. + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smps_app_017 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + data_read( ). + view_display( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD on_event. + + CASE client->get_event( ). + + WHEN `APPROVE`. + request_approve( ). + + WHEN `REJECT`. + request_reject( ). + + WHEN `DELETE`. + request_delete( ). + + WHEN `REFRESH`. + data_read( ). + + WHEN `GENERATE`. + client->message_toast_display( z2ui5_cl_smps_data_req=>data_reset( ) ). + data_read( ). + + WHEN `IGNORE`. + " the switch changes nothing in the business object - it only decides + " whether the buttons follow the permissions, see view_display( ) + view_display( ). + + ENDCASE. + + ENDMETHOD. + + + METHOD data_read. + + SELECT FROM z2ui5_r_smps_req "#EC CI_NOWHERE + FIELDS RequestUuid, + Title, + Quantity, + Status, + Note + ORDER BY CreatedAt DESCENDING + INTO TABLE @DATA(t_result) UP TO 50 ROWS. + + t_requests = VALUE #( FOR s_result IN t_result + ( request_uuid = |{ s_result-requestuuid }| + title = |{ s_result-title }| + quantity = |{ s_result-quantity }| + status = SWITCH #( s_result-status + WHEN 'N' THEN `New` + WHEN 'A' THEN `Approved` + WHEN 'R' THEN `Rejected` ) + status_state = SWITCH #( s_result-status + WHEN 'N' THEN `Information` + WHEN 'A' THEN `Success` + WHEN 'R' THEN `Error` ) + note = |{ s_result-note }| ) ). + + permissions_read( ). + + ENDMETHOD. + + + METHOD permissions_read. + + " the question itself sits in z2ui5_cl_smps_perm_req - one GET + " PERMISSIONS statement for the whole list, see the class header + DATA(t_permissions) = z2ui5_cl_smps_perm_req=>get( + VALUE #( FOR s_row IN t_requests ( CONV #( s_row-request_uuid ) ) ) ). + + LOOP AT t_requests REFERENCE INTO DATA(row). + + DATA(s_permission) = VALUE #( t_permissions[ request_uuid = CONV #( row->request_uuid ) ] OPTIONAL ). + row->update_enabled = s_permission-update. + row->delete_enabled = s_permission-delete. + row->approve_enabled = s_permission-approve. + row->reject_enabled = s_permission-reject. + + ENDLOOP. + + ENDMETHOD. + + + METHOD request_approve. + + DATA(request_uuid) = CONV z2ui5_t_smps_req-request_uuid( client->get_event_arg( ) ). + + " the action is refused for a decided request - the feature control runs + " before the handler, and FAILED says why (%fail-cause = disabled) + MODIFY ENTITIES OF z2ui5_r_smps_req + ENTITY request + EXECUTE approve FROM VALUE #( ( requestuuid = request_uuid ) ) + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + + IF s_failed-request IS NOT INITIAL. + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_failed ). + RETURN. + ENDIF. + + IF data_save( ). + client->message_toast_display( `Request approved` ). + ENDIF. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ). + + ENDMETHOD. + + + METHOD request_reject. + + DATA(request_uuid) = CONV z2ui5_t_smps_req-request_uuid( client->get_event_arg( ) ). + + " the reason is a parameter of the action - z2ui5_cl_smps_app_018 asks + " the user for it, this sample passes a fixed one + MODIFY ENTITIES OF z2ui5_r_smps_req + ENTITY request + EXECUTE reject FROM VALUE #( ( requestuuid = request_uuid + %param = VALUE #( reason = 'Rejected in the feature control sample' ) ) ) + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + + IF s_failed-request IS NOT INITIAL. + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_failed ). + RETURN. + ENDIF. + + IF data_save( ). + client->message_toast_display( `Request rejected` ). + ENDIF. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ). + + ENDMETHOD. + + + METHOD request_delete. + + DATA(request_uuid) = CONV z2ui5_t_smps_req-request_uuid( client->get_event_arg( ) ). + + MODIFY ENTITIES OF z2ui5_r_smps_req + ENTITY request + DELETE FROM VALUE #( ( requestuuid = request_uuid ) ) + FAILED DATA(s_failed). + + IF s_failed-request IS NOT INITIAL. + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_failed ). + RETURN. + ENDIF. + + IF data_save( ). + client->message_toast_display( `Request deleted` ). + ENDIF. + + ENDMETHOD. + + + METHOD data_save. + + COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_req + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + + IF s_failed IS INITIAL. + result = abap_true. + ELSE. + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ). + ENDIF. + + " the list shows what was saved - and the permissions that follow from it + data_read( ). + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ). + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - RAP Actions - 17 Feature Control with GET PERMISSIONS` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `The buttons of each row follow GET PERMISSIONS: a New request may be approved, rejected and ` && + `deleted, a decided one may not, and an approved one stays for good. Switch on Ignore ` && + `permissions and try anyway - the business object refuses it on its own.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + " the expression binding decides per row: the permission, or - with the + " switch on - always true + DATA(enabled) = COND string( WHEN ignore_permissions = abap_true THEN `true` ). + + DATA(table) = page->ele( `Table` + )->a( n = `items` v = client->_bind( t_requests ) + )->a( n = `noDataText` v = `No requests yet - press Reset Demo Data` ). + table->ele( `headerToolbar` + )->ele( `Toolbar` + )->tag( `Title` + )->a( n = `text` v = `Requests (Z2UI5_R_SMPS_REQ)` + )->tag( `ToolbarSpacer` + )->tag( `Label` + )->a( n = `text` v = `Ignore permissions` + )->tag( `Switch` + )->a( n = `state` v = client->_bind( ignore_permissions ) + )->a( n = `change` v = client->_event( `IGNORE` ) + )->tag( `Button` + )->a( n = `press` v = client->_event( `GENERATE` ) + )->a( n = `text` v = `Reset Demo Data` + )->a( n = `tooltip` v = `Deletes every request the business object lets go of, then creates three new ones` + )->tag( `Button` + )->a( n = `press` v = client->_event( `REFRESH` ) + )->a( n = `icon` v = `sap-icon://refresh` + )->a( n = `tooltip` v = `Refresh` ). + + table->ele( `columns` + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Title` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Quantity` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Status` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Note` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Actions` ). + + table->ele( `items` + )->ele( `ColumnListItem` + )->ele( `cells` + )->tag( `Text` + )->a( n = `text` v = `{TITLE}` + )->tag( `Text` + )->a( n = `text` v = `{QUANTITY}` + )->tag( `ObjectStatus` + )->a( n = `text` v = `{STATUS}` + )->a( n = `state` v = `{= ${STATUS_STATE} || 'None' }` + )->tag( `Text` + )->a( n = `text` v = `{NOTE}` + )->ele( `HBox` + )->tag( `Button` + )->a( n = `press` v = client->_event( val = `APPROVE` arg = `${REQUEST_UUID}` ) + )->a( n = `icon` v = `sap-icon://accept` + )->a( n = `tooltip` v = `Approve` + )->a( n = `enabled` v = COND #( WHEN enabled IS INITIAL THEN `{APPROVE_ENABLED}` ELSE enabled ) + )->tag( `Button` + )->a( n = `press` v = client->_event( val = `REJECT` arg = `${REQUEST_UUID}` ) + )->a( n = `icon` v = `sap-icon://decline` + )->a( n = `tooltip` v = `Reject` + )->a( n = `enabled` v = COND #( WHEN enabled IS INITIAL THEN `{REJECT_ENABLED}` ELSE enabled ) + )->tag( `Button` + )->a( n = `press` v = client->_event( val = `DELETE` arg = `${REQUEST_UUID}` ) + )->a( n = `icon` v = `sap-icon://delete` + )->a( n = `tooltip` v = `Delete` + )->a( n = `enabled` v = COND #( WHEN enabled IS INITIAL THEN `{DELETE_ENABLED}` ELSE enabled ) ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/12/z2ui5_cl_smps_app_017.clas.xml b/src/12/z2ui5_cl_smps_app_017.clas.xml new file mode 100644 index 0000000..03dc477 --- /dev/null +++ b/src/12/z2ui5_cl_smps_app_017.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMPS_APP_017 + E + RAP Actions - Feature Control with GET PERMISSIONS + 1 + X + X + X + + + + diff --git a/src/12/z2ui5_cl_smps_app_018.clas.abap b/src/12/z2ui5_cl_smps_app_018.clas.abap new file mode 100644 index 0000000..b255450 --- /dev/null +++ b/src/12/z2ui5_cl_smps_app_018.clas.abap @@ -0,0 +1,483 @@ +" @keywords rap eml action parameter abstract entity factory action default function popup create copy reject +" @summary Actions with a parameter - a default function proposes a new request, a static factory action creates it, a factory action copies one, and reject asks for its reason in a popup +"!

RAP Actions - Parameters, Factory Actions, Defaults

+"! Four ways into the business object z2ui5_r_smps_req that all carry a +"! PARAMETER - an abstract entity, filled in a popup and handed over in +"! %param: +"! +"! " the default function proposes the values of a new request +"! READ ENTITIES OF z2ui5_r_smps_req +"! ENTITY request +"! EXECUTE getdefaultsforcreate FROM VALUE #( ( %cid = `DEFAULTS` ) ) +"! RESULT DATA(t_defaults). +"! +"! " a static factory action creates a request from its parameter +"! MODIFY ENTITIES OF z2ui5_r_smps_req +"! ENTITY request +"! EXECUTE createrequest FROM VALUE #( ( %cid = `CREATE_1` %param = ... ) ) +"! MAPPED DATA(s_mapped) ... +"! +"! " an instance factory action creates a copy of an existing one +"! EXECUTE copyrequest FROM VALUE #( ( %cid = `COPY_1` requestuuid = ... %param = ... ) ) +"! +"! " an ordinary action with a parameter +"! EXECUTE reject FROM VALUE #( ( requestuuid = ... %param = VALUE #( reason = ... ) ) ) +"! +"! A factory action returns no RESULT - the new instance comes back in MAPPED, +"! under the %cid the caller sent. +CLASS z2ui5_cl_smps_app_018 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + TYPES: + BEGIN OF ty_s_request, + request_uuid TYPE string, + title TYPE string, + quantity TYPE string, + priority TYPE string, + status TYPE string, + note TYPE string, + END OF ty_s_request. + TYPES ty_t_request TYPE STANDARD TABLE OF ty_s_request WITH EMPTY KEY. + DATA t_requests TYPE ty_t_request. + + TYPES: + "! what the popup edits - the parameter of the action it was opened for + BEGIN OF ty_s_popup, + title TYPE string, + quantity TYPE i, + priority TYPE string, + reason TYPE string, + END OF ty_s_popup. + DATA s_popup TYPE ty_s_popup. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + CONSTANTS: + "! which action the open popup belongs to + BEGIN OF cs_mode, + create TYPE string VALUE `CREATE`, + copy TYPE string VALUE `COPY`, + reject TYPE string VALUE `REJECT`, + END OF cs_mode. + + "! the popup that is open, and the request it was opened for + DATA popup_mode TYPE string. + DATA request_uuid TYPE string. + + METHODS on_event. + METHODS popup_open_create. + METHODS popup_confirm. + METHODS request_create. + METHODS request_copy. + METHODS request_reject. + METHODS data_save + RETURNING + VALUE(result) TYPE abap_bool. + METHODS data_read. + METHODS view_display. + METHODS popup_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smps_app_018 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + data_read( ). + view_display( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD on_event. + + CASE client->get_event( ). + + WHEN `CREATE`. + popup_open_create( ). + + WHEN `COPY`. + popup_mode = cs_mode-copy. + request_uuid = client->get_event_arg( ). + s_popup = VALUE #( title = |{ client->get_event_arg( 2 ) } (copy)| ). + popup_display( ). + + WHEN `REJECT`. + popup_mode = cs_mode-reject. + request_uuid = client->get_event_arg( ). + s_popup = VALUE #( ). + popup_display( ). + + WHEN `POPUP_CONFIRM`. + popup_confirm( ). + + WHEN `POPUP_CANCEL`. + client->popup_destroy( ). + + WHEN `REFRESH`. + data_read( ). + + WHEN `GENERATE`. + client->message_toast_display( z2ui5_cl_smps_data_req=>data_reset( ) ). + data_read( ). + + ENDCASE. + + ENDMETHOD. + + + METHOD popup_open_create. + + " A default function is a FUNCTION: it reads, it changes nothing. It is + " called with READ ... EXECUTE, and its RESULT carries a whole instance + " in %param - the proposal the popup opens with. + READ ENTITIES OF z2ui5_r_smps_req + ENTITY request + EXECUTE getdefaultsforcreate FROM VALUE #( ( %cid = `DEFAULTS` ) ) + RESULT DATA(t_defaults). + + DATA(s_default) = VALUE #( t_defaults[ 1 ]-%param OPTIONAL ). + s_popup = VALUE #( title = |{ s_default-title }| + quantity = s_default-quantity + priority = |{ s_default-priority }| ). + + popup_mode = cs_mode-create. + popup_display( ). + + ENDMETHOD. + + + METHOD popup_confirm. + + CASE popup_mode. + WHEN cs_mode-create. + request_create( ). + WHEN cs_mode-copy. + request_copy( ). + WHEN cs_mode-reject. + request_reject( ). + ENDCASE. + + ENDMETHOD. + + + METHOD request_create. + + " a static factory action needs no instance - like a CREATE, but the + " business object decides what the parameter becomes + MODIFY ENTITIES OF z2ui5_r_smps_req + ENTITY request + EXECUTE createrequest + FROM VALUE #( ( %cid = `CREATE_1` + %param = VALUE #( title = s_popup-title + quantity = s_popup-quantity + priority = s_popup-priority ) ) ) + MAPPED DATA(s_mapped) + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + + IF s_failed-request IS NOT INITIAL. + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ). + RETURN. + ENDIF. + + IF data_save( ). + client->popup_destroy( ). + client->message_toast_display( COND #( WHEN s_mapped-request IS INITIAL + THEN `Request created` + ELSE `Request created - its key came back in MAPPED` ) ). + ENDIF. + + ENDMETHOD. + + + METHOD request_copy. + + " an instance factory action: the key names the source, %cid names the + " copy - MAPPED returns the copy's new key under that %cid + MODIFY ENTITIES OF z2ui5_r_smps_req + ENTITY request + EXECUTE copyrequest + FROM VALUE #( ( %cid = `COPY_1` + requestuuid = CONV #( request_uuid ) + %param = VALUE #( title = s_popup-title ) ) ) + MAPPED DATA(s_mapped) + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + + IF s_failed-request IS NOT INITIAL. + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ). + RETURN. + ENDIF. + + IF data_save( ). + client->popup_destroy( ). + client->message_toast_display( COND #( WHEN s_mapped-request IS INITIAL + THEN `Request copied` + ELSE `Request copied - the new key came back in MAPPED` ) ). + ENDIF. + + ENDMETHOD. + + + METHOD request_reject. + + " an empty reason is not refused here - the business object refuses it, + " and its message is what the user sees + MODIFY ENTITIES OF z2ui5_r_smps_req + ENTITY request + EXECUTE reject + FROM VALUE #( ( requestuuid = CONV #( request_uuid ) + %param = VALUE #( reason = s_popup-reason ) ) ) + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + + IF s_failed-request IS NOT INITIAL. + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ). + RETURN. + ENDIF. + + IF data_save( ). + client->popup_destroy( ). + client->message_toast_display( `Request rejected` ). + ENDIF. + + ENDMETHOD. + + + METHOD data_save. + + " the validation of the quantity runs here, at the save - a factory + " action that came back clean can still be refused now + COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_req + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + + IF s_failed IS INITIAL. + result = abap_true. + data_read( ). + ELSE. + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ). + ENDIF. + + ENDMETHOD. + + + METHOD data_read. + + SELECT FROM z2ui5_r_smps_req "#EC CI_NOWHERE + FIELDS RequestUuid, + Title, + Quantity, + Priority, + Status, + Note + ORDER BY CreatedAt DESCENDING + INTO TABLE @DATA(t_result) UP TO 50 ROWS. + + t_requests = VALUE #( FOR s_result IN t_result + ( request_uuid = |{ s_result-requestuuid }| + title = |{ s_result-title }| + quantity = |{ s_result-quantity }| + priority = SWITCH #( s_result-priority + WHEN 'H' THEN `High` + WHEN 'M' THEN `Medium` + WHEN 'L' THEN `Low` ) + status = SWITCH #( s_result-status + WHEN 'N' THEN `New` + WHEN 'A' THEN `Approved` + WHEN 'R' THEN `Rejected` ) + note = |{ s_result-note }| ) ). + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ). + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - RAP Actions - 18 Parameters, Factory Actions, Defaults` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `Create opens with the proposal of the default function and creates through a static ` && + `factory action, Copy runs a factory action on the row, Reject an action whose parameter ` && + `is the reason - every popup fills an abstract entity that travels in %param.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + DATA(table) = page->ele( `Table` + )->a( n = `items` v = client->_bind( t_requests ) + )->a( n = `noDataText` v = `No requests yet - press Create or Reset Demo Data` ). + table->ele( `headerToolbar` + )->ele( `Toolbar` + )->tag( `Title` + )->a( n = `text` v = `Requests (Z2UI5_R_SMPS_REQ)` + )->tag( `ToolbarSpacer` + )->tag( `Button` + )->a( n = `press` v = client->_event( `CREATE` ) + )->a( n = `text` v = `Create` + )->a( n = `icon` v = `sap-icon://add` + )->a( n = `type` v = `Emphasized` + )->tag( `Button` + )->a( n = `press` v = client->_event( `GENERATE` ) + )->a( n = `text` v = `Reset Demo Data` + )->a( n = `tooltip` v = `Deletes every request the business object lets go of, then creates three new ones` + )->tag( `Button` + )->a( n = `press` v = client->_event( `REFRESH` ) + )->a( n = `icon` v = `sap-icon://refresh` + )->a( n = `tooltip` v = `Refresh` ). + + table->ele( `columns` + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Title` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Quantity` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Priority` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Status` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Note` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Actions` ). + + table->ele( `items` + )->ele( `ColumnListItem` + )->ele( `cells` + )->tag( `Text` + )->a( n = `text` v = `{TITLE}` + )->tag( `Text` + )->a( n = `text` v = `{QUANTITY}` + )->tag( `Text` + )->a( n = `text` v = `{PRIORITY}` + )->tag( `Text` + )->a( n = `text` v = `{STATUS}` + )->tag( `Text` + )->a( n = `text` v = `{NOTE}` + )->ele( `HBox` + )->tag( `Button` + )->a( n = `press` v = client->_event( val = `COPY` t_arg = VALUE #( ( `${REQUEST_UUID}` ) ( `${TITLE}` ) ) ) + )->a( n = `icon` v = `sap-icon://copy` + )->a( n = `tooltip` v = `Copy - factory action copyRequest` + )->tag( `Button` + )->a( n = `press` v = client->_event( val = `REJECT` arg = `${REQUEST_UUID}` ) + )->a( n = `icon` v = `sap-icon://decline` + )->a( n = `tooltip` v = `Reject - action with a parameter` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + + + METHOD popup_display. + + DATA(popup) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `FragmentDefinition` ns = `core` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:core` v = `sap.ui.core` + )->a( n = `xmlns:form` v = `sap.ui.layout.form` ). + DATA(dialog) = popup->ele( `Dialog` + )->a( n = `title` v = SWITCH #( popup_mode + WHEN cs_mode-create THEN `Create - static factory action createRequest` + WHEN cs_mode-copy THEN `Copy - factory action copyRequest` + WHEN cs_mode-reject THEN `Reject - action with a parameter` ) + )->a( n = `contentWidth` v = `30rem` ). + + DATA(content) = dialog->ele( n = `SimpleForm` ns = `form` + )->a( n = `editable` b = abap_true + )->ele( n = `content` ns = `form` ). + + CASE popup_mode. + + WHEN cs_mode-create. + content->tag( `Label` + )->a( n = `text` v = `Title` + )->tag( `Input` + )->a( n = `value` v = client->_bind( s_popup-title ) + )->tag( `Label` + )->a( n = `text` v = `Quantity` + )->tag( `StepInput` + )->a( n = `value` v = client->_bind( s_popup-quantity ) + )->a( n = `min` v = `0` + )->tag( `Label` + )->a( n = `text` v = `Priority` + )->ele( `SegmentedButton` + )->a( n = `selectedKey` v = client->_bind( s_popup-priority ) + )->ele( `items` + )->tag( `SegmentedButtonItem` + )->a( n = `key` v = `L` + )->a( n = `text` v = `Low` + )->tag( `SegmentedButtonItem` + )->a( n = `key` v = `M` + )->a( n = `text` v = `Medium` + )->tag( `SegmentedButtonItem` + )->a( n = `key` v = `H` + )->a( n = `text` v = `High` ). + + WHEN cs_mode-copy. + content->tag( `Label` + )->a( n = `text` v = `Title of the copy` + )->tag( `Input` + )->a( n = `value` v = client->_bind( s_popup-title ) ). + + WHEN cs_mode-reject. + content->tag( `Label` + )->a( n = `text` v = `Reason` + )->tag( `TextArea` + )->a( n = `value` v = client->_bind( s_popup-reason ) + )->a( n = `placeholder` v = `required - the business object refuses an empty one` + )->a( n = `rows` v = `3` ). + + ENDCASE. + + dialog->ele( `beginButton` + )->tag( `Button` + )->a( n = `press` v = client->_event( `POPUP_CONFIRM` ) + )->a( n = `text` v = `OK` + )->a( n = `type` v = `Emphasized` ). + dialog->ele( `endButton` + )->tag( `Button` + )->a( n = `press` v = client->_event( `POPUP_CANCEL` ) + )->a( n = `text` v = `Cancel` ). + + client->popup_display( popup->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/12/z2ui5_cl_smps_app_018.clas.xml b/src/12/z2ui5_cl_smps_app_018.clas.xml new file mode 100644 index 0000000..bb301a6 --- /dev/null +++ b/src/12/z2ui5_cl_smps_app_018.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMPS_APP_018 + E + RAP Actions - Parameters, Factory Actions, Defaults + 1 + X + X + X + + + + diff --git a/src/12/z2ui5_cl_smps_app_019.clas.abap b/src/12/z2ui5_cl_smps_app_019.clas.abap new file mode 100644 index 0000000..7004039 --- /dev/null +++ b/src/12/z2ui5_cl_smps_app_019.clas.abap @@ -0,0 +1,248 @@ +" @keywords rap eml determine action validation check on change valuestate messages without saving +" @summary A determine action runs the validation of the business object while the user types - the quantity is checked on every change, and nothing is saved until Save +"!

RAP Actions - Validate on Change with a Determine Action

+"! Validations run at the save - a user who types a wrong value hears about it +"! only when pressing Save. A DETERMINE ACTION runs them on demand instead. +"! The business object z2ui5_r_smps_req declares +"! +"! determine action checkRequest { validation validateQuantity; } +"! +"! and the app calls it on every change of the quantity, with the new value +"! in the transactional buffer: +"! +"! MODIFY ENTITIES OF z2ui5_r_smps_req +"! ENTITY request +"! UPDATE FIELDS ( quantity ) WITH VALUE #( ( requestuuid = ... quantity = ... ) ) +"! EXECUTE checkrequest FROM VALUE #( ( requestuuid = ... ) ) +"! REPORTED DATA(s_reported). +"! +"! ROLLBACK ENTITIES. +"! +"! The messages of the validation come back in REPORTED, with %element naming +"! the field - the app puts them on the input as its value state. The +"! ROLLBACK discards the change again: the check saves nothing, Save does. +CLASS z2ui5_cl_smps_app_019 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + "! the request being edited + DATA title TYPE string. + DATA quantity TYPE i. + + "! what the last check said - bound to the input + DATA value_state TYPE string. + DATA value_state_text TYPE string. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + DATA request_uuid TYPE string. + + METHODS on_event. + METHODS request_load. + METHODS request_check. + METHODS request_save. + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smps_app_019 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + request_load( ). + view_display( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD on_event. + + CASE client->get_event( ). + + WHEN `CHECK`. + request_check( ). + + WHEN `SAVE`. + request_save( ). + + WHEN `NEXT`. + request_load( ). + view_display( ). + + WHEN `GENERATE`. + client->message_toast_display( z2ui5_cl_smps_data_req=>data_reset( ) ). + request_load( ). + view_display( ). + + ENDCASE. + + ENDMETHOD. + + + METHOD request_load. + + " the newest New request - only those may be changed, the feature + " control of the business object refuses an update of a decided one + SELECT FROM z2ui5_r_smps_req + FIELDS RequestUuid, + Title, + Quantity + WHERE Status = 'N' + ORDER BY CreatedAt DESCENDING + INTO TABLE @DATA(t_result) UP TO 1 ROWS. + + DATA(s_result) = VALUE #( t_result[ 1 ] OPTIONAL ). + request_uuid = |{ s_result-requestuuid }|. + title = |{ s_result-title }|. + quantity = s_result-quantity. + value_state = `None`. + value_state_text = ``. + + ENDMETHOD. + + + METHOD request_check. + + " the change goes into the transactional buffer, the determine action + " runs the validation against it - in ONE statement, so the validation + " sees the new value + MODIFY ENTITIES OF z2ui5_r_smps_req + ENTITY request + UPDATE FIELDS ( quantity ) + WITH VALUE #( ( requestuuid = CONV #( request_uuid ) + quantity = quantity ) ) + EXECUTE checkrequest + FROM VALUE #( ( requestuuid = CONV #( request_uuid ) ) ) + REPORTED DATA(s_reported). + + " nothing is saved - the check only asks. Without the ROLLBACK the change + " would stay in the buffer and abap2UI5's COMMIT WORK at the end of the + " roundtrip would run into it. + ROLLBACK ENTITIES. + + " a message on the field quantity is what the validation reports - the + " %element flag says which field it belongs to + DATA(s_message) = VALUE #( s_reported-request[ %element-quantity = if_abap_behv=>mk-on ] OPTIONAL ). + + IF s_message-%msg IS BOUND. + value_state = `Error`. + value_state_text = s_message-%msg->if_message~get_text( ). + ELSE. + value_state = `Success`. + value_state_text = `The business object accepts this quantity`. + ENDIF. + + ENDMETHOD. + + + METHOD request_save. + + MODIFY ENTITIES OF z2ui5_r_smps_req + ENTITY request + UPDATE FIELDS ( quantity ) + WITH VALUE #( ( requestuuid = CONV #( request_uuid ) + quantity = quantity ) ) + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + + IF s_failed-request IS NOT INITIAL. + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ). + RETURN. + ENDIF. + + " the same validation once more, now at the save - it is the one that + " counts, the check above only asked early + COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_req + FAILED DATA(s_failed_commit) + REPORTED DATA(s_reported_commit). + + IF s_failed_commit IS NOT INITIAL. + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported_commit-request ). + RETURN. + ENDIF. + + client->message_toast_display( |Quantity { quantity } saved| ). + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + )->a( n = `xmlns:form` v = `sap.ui.layout.form` ). + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - RAP Actions - 19 Validate on Change` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `Change the quantity: every change runs the determine action checkRequest, which runs the ` && + `validation of the business object without saving - try 0 or 5000. Save stores the value.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + IF request_uuid IS INITIAL. + page->tag( `MessageStrip` + )->a( n = `text` v = `There is no New request to change - press Reset Demo Data below` + )->a( n = `type` v = `Warning` + )->a( n = `class` v = `sapUiSmallMargin` ). + ENDIF. + + page->ele( n = `SimpleForm` ns = `form` + )->a( n = `title` v = `EXECUTE checkRequest - a determine action` + )->a( n = `editable` b = abap_true + )->ele( n = `content` ns = `form` + )->tag( `Label` + )->a( n = `text` v = `Request` + )->tag( `Text` + )->a( n = `text` v = client->_bind( title ) + )->tag( `Label` + )->a( n = `text` v = `Quantity` + )->tag( `Input` + )->a( n = `value` v = client->_bind( quantity ) + )->a( n = `type` v = `Number` + )->a( n = `valueLiveUpdate` b = abap_true + )->a( n = `valueState` v = client->_bind( value_state ) + )->a( n = `valueStateText` v = client->_bind( value_state_text ) + )->a( n = `change` v = client->_event( `CHECK` ) + )->a( n = `enabled` b = xsdbool( request_uuid IS NOT INITIAL ) ). + + page->ele( `footer` + )->ele( `OverflowToolbar` + )->tag( `Button` + )->a( n = `press` v = client->_event( `GENERATE` ) + )->a( n = `text` v = `Reset Demo Data` + )->tag( `Button` + )->a( n = `press` v = client->_event( `NEXT` ) + )->a( n = `text` v = `Reload` + )->tag( `ToolbarSpacer` + )->tag( `Button` + )->a( n = `press` v = client->_event( `SAVE` ) + )->a( n = `text` v = `Save` + )->a( n = `type` v = `Emphasized` + )->a( n = `enabled` b = xsdbool( request_uuid IS NOT INITIAL ) ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/12/z2ui5_cl_smps_app_019.clas.xml b/src/12/z2ui5_cl_smps_app_019.clas.xml new file mode 100644 index 0000000..72bd3ef --- /dev/null +++ b/src/12/z2ui5_cl_smps_app_019.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMPS_APP_019 + E + RAP Actions - Validate on Change with a Determine Action + 1 + X + X + X + + + + diff --git a/src/12/z2ui5_cl_smps_app_020.clas.abap b/src/12/z2ui5_cl_smps_app_020.clas.abap new file mode 100644 index 0000000..2f25d65 --- /dev/null +++ b/src/12/z2ui5_cl_smps_app_020.clas.abap @@ -0,0 +1,267 @@ +" @keywords rap eml projection behavior definition contract cl_abap_behv_aux interface consumer approve +" @summary One business object, two contracts - approve through the projection Z2UI5_C_SMPS_REQ or on the business object itself, and the handler records which one it was +"!

RAP Actions - One Business Object, Two Contracts

+"! A projection is a second contract on the same business object. Its +"! behavior definition z2ui5_c_smps_req exposes a SUBSET of what +"! z2ui5_r_smps_req offers - update and approve, nothing else: +"! +"! projection; +"! define behavior for z2ui5_c_smps_req alias Request +"! { +"! use update; +"! use action approve; +"! } +"! +"! EML works against both. What the projection does not expose cannot even be +"! written: an EXECUTE reject on z2ui5_c_smps_req does not compile. +"! +"! MODIFY ENTITIES OF z2ui5_c_smps_req +"! ENTITY request +"! EXECUTE approve FROM VALUE #( ( requestuuid = ... ) ) ... +"! +"! MODIFY ENTITIES OF z2ui5_r_smps_req +"! ENTITY request +"! EXECUTE approve FROM VALUE #( ( requestuuid = ... ) ) ... +"! +"! Both end in the same handler. It asks cl_abap_behv_aux which contract it +"! was called through and stores the answer in ApprovedVia - the column of the +"! list below. +CLASS z2ui5_cl_smps_app_020 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + TYPES: + BEGIN OF ty_s_request, + request_uuid TYPE string, + title TYPE string, + status TYPE string, + approved_via TYPE string, + is_new TYPE abap_bool, + END OF ty_s_request. + TYPES ty_t_request TYPE STANDARD TABLE OF ty_s_request WITH EMPTY KEY. + DATA t_requests TYPE ty_t_request. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS on_event. + METHODS approve_via_projection. + METHODS approve_via_business_object. + METHODS data_save + RETURNING + VALUE(result) TYPE abap_bool. + METHODS data_read. + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smps_app_020 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + data_read( ). + view_display( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD on_event. + + CASE client->get_event( ). + + WHEN `APPROVE_C`. + approve_via_projection( ). + + WHEN `APPROVE_R`. + approve_via_business_object( ). + + WHEN `REFRESH`. + data_read( ). + + WHEN `GENERATE`. + client->message_toast_display( z2ui5_cl_smps_data_req=>data_reset( ) ). + data_read( ). + + ENDCASE. + + ENDMETHOD. + + + METHOD approve_via_projection. + + " the entity is the projection - this statement only compiles for what + " z2ui5_c_smps_req exposes + MODIFY ENTITIES OF z2ui5_c_smps_req + ENTITY request + EXECUTE approve FROM VALUE #( ( requestuuid = CONV #( client->get_event_arg( ) ) ) ) + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + + IF s_failed-request IS NOT INITIAL. + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ). + RETURN. + ENDIF. + + IF data_save( ). + client->message_toast_display( `Approved through the projection Z2UI5_C_SMPS_REQ` ). + ENDIF. + + ENDMETHOD. + + + METHOD approve_via_business_object. + + MODIFY ENTITIES OF z2ui5_r_smps_req + ENTITY request + EXECUTE approve FROM VALUE #( ( requestuuid = CONV #( client->get_event_arg( ) ) ) ) + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + + IF s_failed-request IS NOT INITIAL. + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ). + RETURN. + ENDIF. + + IF data_save( ). + client->message_toast_display( `Approved on the business object Z2UI5_R_SMPS_REQ` ). + ENDIF. + + ENDMETHOD. + + + METHOD data_save. + + " one save for both contracts - the transactional buffer belongs to the + " business object, the projection only shapes the access to it + COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_req + FAILED DATA(s_failed) + REPORTED DATA(s_reported). + + IF s_failed IS INITIAL. + result = abap_true. + data_read( ). + ELSE. + ROLLBACK ENTITIES. + z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ). + ENDIF. + + ENDMETHOD. + + + METHOD data_read. + + " read through the projection - it is a CDS view like any other + SELECT FROM z2ui5_c_smps_req "#EC CI_NOWHERE + FIELDS RequestUuid, + Title, + Status, + ApprovedVia + ORDER BY CreatedAt DESCENDING + INTO TABLE @DATA(t_result) UP TO 50 ROWS. + + t_requests = VALUE #( FOR s_result IN t_result + ( request_uuid = |{ s_result-requestuuid }| + title = |{ s_result-title }| + status = SWITCH #( s_result-status + WHEN 'N' THEN `New` + WHEN 'A' THEN `Approved` + WHEN 'R' THEN `Rejected` ) + approved_via = |{ s_result-approvedvia }| + is_new = xsdbool( s_result-status = 'N' ) ) ). + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ). + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - RAP Actions - 20 One Business Object, Two Contracts` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `Approve a New request through the projection Z2UI5_C_SMPS_REQ or on the business object ` && + `Z2UI5_R_SMPS_REQ - the same handler runs, asks cl_abap_behv_aux which contract called it ` && + `and writes the answer into Approved via.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + DATA(table) = page->ele( `Table` + )->a( n = `items` v = client->_bind( t_requests ) + )->a( n = `noDataText` v = `No requests yet - press Reset Demo Data` ). + table->ele( `headerToolbar` + )->ele( `Toolbar` + )->tag( `Title` + )->a( n = `text` v = `Requests (Z2UI5_C_SMPS_REQ)` + )->tag( `ToolbarSpacer` + )->tag( `Button` + )->a( n = `press` v = client->_event( `GENERATE` ) + )->a( n = `text` v = `Reset Demo Data` + )->a( n = `tooltip` v = `Deletes every request the business object lets go of, then creates three new ones` + )->tag( `Button` + )->a( n = `press` v = client->_event( `REFRESH` ) + )->a( n = `icon` v = `sap-icon://refresh` + )->a( n = `tooltip` v = `Refresh` ). + + table->ele( `columns` + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Title` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Status` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Approved via` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Approve` ). + + table->ele( `items` + )->ele( `ColumnListItem` + )->ele( `cells` + )->tag( `Text` + )->a( n = `text` v = `{TITLE}` + )->tag( `Text` + )->a( n = `text` v = `{STATUS}` + )->tag( `Text` + )->a( n = `text` v = `{APPROVED_VIA}` + )->ele( `HBox` + )->tag( `Button` + )->a( n = `press` v = client->_event( val = `APPROVE_C` arg = `${REQUEST_UUID}` ) + )->a( n = `text` v = `via projection` + )->a( n = `enabled` v = `{IS_NEW}` + )->a( n = `class` v = `sapUiTinyMarginEnd` + )->tag( `Button` + )->a( n = `press` v = client->_event( val = `APPROVE_R` arg = `${REQUEST_UUID}` ) + )->a( n = `text` v = `on the business object` + )->a( n = `enabled` v = `{IS_NEW}` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/12/z2ui5_cl_smps_app_020.clas.xml b/src/12/z2ui5_cl_smps_app_020.clas.xml new file mode 100644 index 0000000..9fcdaa5 --- /dev/null +++ b/src/12/z2ui5_cl_smps_app_020.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMPS_APP_020 + E + RAP Actions - One Business Object, Two Contracts + 1 + X + X + X + + + + diff --git a/src/13/README.md b/src/13/README.md new file mode 100644 index 0000000..e2cbc81 --- /dev/null +++ b/src/13/README.md @@ -0,0 +1,63 @@ +# 13 — Excel & E-Mail + +*[← all packages](../../README.md)* + +Spreadsheets in, spreadsheets out, and one by mail. The **XCO XLSX library** +reads and writes `.xlsx` files in plain ABAP, and **`cl_bcs_mail_message`** +sends them — both released APIs of ABAP Cloud. abap2UI5 adds the two ends a +user touches: the FileUploader that brings a file into the backend, and the +`download_b64_file` frontend action that hands one to the browser. + +## What you need + +**Release:** Cloud + Standard ≥ 7.58 (2023). The XCO XLSX library and +`cl_bcs_mail_message` come with ABAP Cloud; on-premise the package needs a +release that carries both. If `xco_cp_xlsx` is not on your system, this package +is out of reach, and nothing else in this repository is affected. + +**Branch:** [`13-excel-email`](https://github.com/abap2UI5/samples-stack/tree/13-excel-email) +— this package alone, without the other thirteen on your system. + +The mail sample also needs **outbound mail** configured: on ABAP Cloud a +communication arrangement for the scenario `SAP_COM_0548`, on-premise SMTP in +transaction `SCOT`. Without it the send fails, and the sample shows the reason. + +## The samples + +| You want to | Sample | +|---|---| +| read an uploaded `.xlsx` file into an ABAP table | [`021`](z2ui5_cl_smps_app_021.clas.abap) | +| export a table as an `.xlsx` download | [`022`](z2ui5_cl_smps_app_022.clas.abap) | +| send a table as an `.xlsx` mail attachment | [`023`](z2ui5_cl_smps_app_023.clas.abap) | + +The XCO calls sit in [`Z2UI5_CL_SMPS_XLSX`](z2ui5_cl_smps_xlsx.clas.abap), which +the three samples share: an `.xlsx` in, a table of strings out, and the way +back. + +```abap +" read - the first worksheet, columns A to F +DATA(worksheet) = xco_cp_xlsx=>document->for_file_content( xlsx + )->read_access( )->get_workbook( )->worksheet->at_position( 1 ). +worksheet->select( pattern )->row_stream( + )->operation->write_to( REF #( t_rows ) + )->set_value_transformation( xco_cp_xlsx_read_access=>value_transformation->string_value + )->execute( ). + +" write - a new document, one worksheet +DATA(document) = xco_cp_xlsx=>document->empty( )->write_access( ). +document->get_workbook( )->worksheet->at_position( 1 + )->select( pattern )->row_stream( + )->operation->write_from( REF #( t_rows ) )->execute( ). +DATA(xlsx) = document->get_file_content( ). +``` + +Every cell travels as a string, so one table type fits every sheet; the app +converts what it needs. Numbers are written with a decimal point and no +thousands separator, the way a spreadsheet reads them back. + +## Where to go next + +- [`11` RAP File Upload](../11/README.md) — a file stored in a business object. +- [`14` Adobe Forms](../14/README.md) — PDF output, one file or a ZIP. +- The ZIP and CSV download without anything from the system: + `Z2UI5_CL_SMP_APP_542` in [abap2UI5/samples](https://github.com/abap2UI5/samples). diff --git a/src/13/package.devc.xml b/src/13/package.devc.xml new file mode 100644 index 0000000..7f7a6fc --- /dev/null +++ b/src/13/package.devc.xml @@ -0,0 +1,10 @@ + + + + + + Excel and E-Mail + + + + diff --git a/src/13/z2ui5_cl_smps_app_021.clas.abap b/src/13/z2ui5_cl_smps_app_021.clas.abap new file mode 100644 index 0000000..51b0e44 --- /dev/null +++ b/src/13/z2ui5_cl_smps_app_021.clas.abap @@ -0,0 +1,192 @@ +" @keywords excel xlsx upload import read spreadsheet xco fileuploader table +" @summary An .xlsx file from the FileUploader read with the XCO XLSX library - the first worksheet becomes an ABAP table and is shown as it arrived +"!

Excel - Read an Uploaded XLSX File with XCO

+"! The FileUploader custom control delivers the file as a base64 data URL, +"! the app decodes it into an xstring and hands it to the XCO XLSX library: +"! +"! DATA(worksheet) = xco_cp_xlsx=>document->for_file_content( file +"! )->read_access( )->get_workbook( )->worksheet->at_position( 1 ). +"! +"! worksheet->select( pattern )->row_stream( +"! )->operation->write_to( REF #( t_rows ) +"! )->set_value_transformation( xco_cp_xlsx_read_access=>value_transformation->string_value +"! )->execute( ). +"! +"! The calls sit in z2ui5_cl_smps_xlsx, which the other samples of the +"! package share. No file at hand? Download Example File writes one with the +"! same library. +CLASS z2ui5_cl_smps_app_021 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + "! the picked file - a data URL, data:<mime>;base64,<payload> + DATA file TYPE string. + DATA file_name TYPE string. + + "! the first worksheet as it arrived - every cell a string + DATA t_rows TYPE z2ui5_cl_smps_xlsx=>ty_t_row. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS on_event. + METHODS file_read. + METHODS example_download. + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smps_app_021 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD on_event. + + CASE client->get_event( ). + WHEN `UPLOAD`. + file_read( ). + WHEN `EXAMPLE`. + example_download( ). + ENDCASE. + + ENDMETHOD. + + + METHOD file_read. + + " data:;base64, - only the payload is the file + DATA(base64) = substring_after( val = file sub = `,` ). + + TRY. + t_rows = z2ui5_cl_smps_xlsx=>read( cl_web_http_utility=>decode_x_base64( base64 ) ). + client->message_toast_display( |{ file_name } - { lines( t_rows ) } rows read| ). + + CATCH cx_root INTO DATA(error). + " not an .xlsx at all, or a damaged one - XCO says why + CLEAR t_rows. + client->message_box_display( text = |{ file_name }: { error->get_text( ) }| type = `error` ). + ENDTRY. + + CLEAR file. + view_display( ). + + ENDMETHOD. + + + METHOD example_download. + + " the same library the other way round - see z2ui5_cl_smps_app_022 + DATA(xlsx) = z2ui5_cl_smps_xlsx=>write( VALUE #( + ( a = `Product` b = `Quantity` c = `Price` d = `Currency` ) + ( a = `Notebook` b = `2` c = `899.00` d = `EUR` ) + ( a = `Docking station` b = `2` c = `189.50` d = `EUR` ) + ( a = `Headset` b = `5` c = `79.90` d = `EUR` ) ) ). + + client->follow_up_action( + val = client->cs_event-download_b64_file + t_arg = VALUE #( ( |data:{ z2ui5_cl_smps_xlsx=>mime_type };base64,{ cl_web_http_utility=>encode_x_base64( xlsx ) }| ) + ( `example.xlsx` ) ) ). + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + )->a( n = `xmlns:z2ui5` v = `z2ui5.cc` ). + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - Excel - 21 Read an Uploaded XLSX File` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `Pick an .xlsx file and press Upload - the XCO XLSX library reads its first worksheet, ` && + `columns A to F, into an ABAP table. No file at hand? Download Example File first.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + page->ele( `HBox` + )->a( n = `class` v = `sapUiSmallMargin` + )->a( n = `alignItems` v = `Center` + )->tag( n = `FileUploader` ns = `z2ui5` + )->a( n = `placeholder` v = `pick an .xlsx file...` + )->a( n = `fileType` v = `xlsx` + )->a( n = `uploadButtonText` v = `Upload` + )->a( n = `upload` v = client->_event( `UPLOAD` ) + )->a( n = `path` v = client->_bind( file_name ) + )->a( n = `value` v = client->_bind( file ) + )->tag( `Button` + )->a( n = `press` v = client->_event( `EXAMPLE` ) + )->a( n = `text` v = `Download Example File` + )->a( n = `icon` v = `sap-icon://download` + )->a( n = `class` v = `sapUiSmallMarginBegin` ). + + DATA(table) = page->ele( `Table` + )->a( n = `items` v = client->_bind( t_rows ) + )->a( n = `noDataText` v = `Nothing read yet` ). + table->ele( `columns` + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `A` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `B` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `C` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `D` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `E` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `F` ). + + table->ele( `items` + )->ele( `ColumnListItem` + )->ele( `cells` + )->tag( `Text` + )->a( n = `text` v = `{A}` + )->tag( `Text` + )->a( n = `text` v = `{B}` + )->tag( `Text` + )->a( n = `text` v = `{C}` + )->tag( `Text` + )->a( n = `text` v = `{D}` + )->tag( `Text` + )->a( n = `text` v = `{E}` + )->tag( `Text` + )->a( n = `text` v = `{F}` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/13/z2ui5_cl_smps_app_021.clas.xml b/src/13/z2ui5_cl_smps_app_021.clas.xml new file mode 100644 index 0000000..c33e654 --- /dev/null +++ b/src/13/z2ui5_cl_smps_app_021.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMPS_APP_021 + E + Excel - Read an Uploaded XLSX File with XCO + 1 + X + X + X + + + + diff --git a/src/13/z2ui5_cl_smps_app_022.clas.abap b/src/13/z2ui5_cl_smps_app_022.clas.abap new file mode 100644 index 0000000..7a2cedc --- /dev/null +++ b/src/13/z2ui5_cl_smps_app_022.clas.abap @@ -0,0 +1,157 @@ +" @keywords excel xlsx export download write spreadsheet xco table download_b64_file +" @summary The table on the screen written into an .xlsx file with the XCO XLSX library and handed to the browser as a download - a header row, then one row per line +"!

Excel - Export a Table as XLSX File with XCO

+"! The XCO XLSX library writes an internal table into a new worksheet: +"! +"! DATA(document) = xco_cp_xlsx=>document->empty( )->write_access( ). +"! document->get_workbook( )->worksheet->at_position( 1 +"! )->select( pattern )->row_stream( +"! )->operation->write_from( REF #( t_rows ) )->execute( ). +"! DATA(xlsx) = document->get_file_content( ). +"! +"! The file goes to the browser as a base64 data URL through the +"! download_b64_file frontend action - no download service, no extra request. +"! The calls sit in z2ui5_cl_smps_xlsx, which the other samples of the +"! package share. +CLASS z2ui5_cl_smps_app_022 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + TYPES: + BEGIN OF ty_s_product, + product TYPE string, + quantity TYPE i, + price TYPE p LENGTH 9 DECIMALS 2, + currency TYPE string, + END OF ty_s_product. + TYPES ty_t_product TYPE STANDARD TABLE OF ty_s_product WITH EMPTY KEY. + DATA t_products TYPE ty_t_product. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS file_download. + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smps_app_022 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + t_products = VALUE #( currency = `EUR` + ( product = `Notebook` quantity = 2 price = '899.00' ) + ( product = `Docking station` quantity = 2 price = '189.50' ) + ( product = `Headset` quantity = 5 price = '79.90' ) + ( product = `Monitor` quantity = 3 price = '249.00' ) ). + view_display( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( `DOWNLOAD` ). + file_download( ). + ENDIF. + + ENDMETHOD. + + + METHOD file_download. + + " every cell is a string in the worksheet - the header row first, then + " one row per product, the numbers written the way a spreadsheet reads + " them back: a decimal point and no thousands separator + DATA(t_rows) = VALUE z2ui5_cl_smps_xlsx=>ty_t_row( + ( a = `Product` b = `Quantity` c = `Price` d = `Currency` e = `Total` ) ). + + LOOP AT t_products INTO DATA(s_product). + APPEND VALUE #( a = s_product-product + b = |{ s_product-quantity }| + c = |{ s_product-price }| + d = s_product-currency + e = |{ s_product-quantity * s_product-price DECIMALS = 2 }| ) TO t_rows. + ENDLOOP. + + DATA(xlsx) = z2ui5_cl_smps_xlsx=>write( t_rows ). + + client->follow_up_action( + val = client->cs_event-download_b64_file + t_arg = VALUE #( ( |data:{ z2ui5_cl_smps_xlsx=>mime_type };base64,{ cl_web_http_utility=>encode_x_base64( xlsx ) }| ) + ( `products.xlsx` ) ) ). + + client->message_toast_display( |products.xlsx - { lines( t_products ) } rows, { xstrlen( xlsx ) } bytes| ). + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ). + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - Excel - 22 Export a Table as XLSX File` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `Change the table, then press Download XLSX - the XCO XLSX library writes it into a new ` && + `worksheet and the browser saves the file.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + DATA(table) = page->ele( `Table` + )->a( n = `items` v = client->_bind( t_products ) ). + table->ele( `headerToolbar` + )->ele( `Toolbar` + )->tag( `Title` + )->a( n = `text` v = `Products` + )->tag( `ToolbarSpacer` + )->tag( `Button` + )->a( n = `press` v = client->_event( `DOWNLOAD` ) + )->a( n = `text` v = `Download XLSX` + )->a( n = `icon` v = `sap-icon://excel-attachment` + )->a( n = `type` v = `Emphasized` ). + + table->ele( `columns` + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Product` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Quantity` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Price` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Currency` ). + + table->ele( `items` + )->ele( `ColumnListItem` + )->ele( `cells` + )->tag( `Input` + )->a( n = `value` v = `{PRODUCT}` + )->tag( `Input` + )->a( n = `value` v = `{QUANTITY}` + )->tag( `Input` + )->a( n = `value` v = `{PRICE}` + )->tag( `Input` + )->a( n = `value` v = `{CURRENCY}` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/13/z2ui5_cl_smps_app_022.clas.xml b/src/13/z2ui5_cl_smps_app_022.clas.xml new file mode 100644 index 0000000..43b97c4 --- /dev/null +++ b/src/13/z2ui5_cl_smps_app_022.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMPS_APP_022 + E + Excel - Export a Table as XLSX File with XCO + 1 + X + X + X + + + + diff --git a/src/13/z2ui5_cl_smps_app_023.clas.abap b/src/13/z2ui5_cl_smps_app_023.clas.abap new file mode 100644 index 0000000..395a88c --- /dev/null +++ b/src/13/z2ui5_cl_smps_app_023.clas.abap @@ -0,0 +1,162 @@ +" @keywords email mail send attachment xlsx excel cl_bcs_mail_message smtp +" @summary A mail with an .xlsx attachment, sent with cl_bcs_mail_message - recipient, subject and text from the screen, the attachment written with the XCO XLSX library +"!

E-Mail - Send a Table as XLSX Attachment

+"! cl_bcs_mail_message is the mail API of ABAP Cloud: +"! +"! DATA(mail) = cl_bcs_mail_message=>create_instance( ). +"! mail->set_sender( sender ). +"! mail->add_recipient( recipient ). +"! mail->set_subject( subject ). +"! mail->set_main( cl_bcs_mail_textpart=>create_text_plain( text ) ). +"! mail->add_attachment( cl_bcs_mail_binarypart=>create_instance( +"! iv_content = xlsx +"! iv_content_type = mime_type +"! iv_filename = `products.xlsx` ) ). +"! mail->send( IMPORTING ev_mail_status = DATA(status) ). +"! +"! The mail leaves the system through its outbound mail configuration - on +"! ABAP Cloud a communication arrangement for SAP_COM_0548, on-premise SMTP +"! in transaction SCOT. Without one the send fails, and the app says why. +CLASS z2ui5_cl_smps_app_023 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA sender TYPE string. + DATA recipient TYPE string. + DATA subject TYPE string. + DATA text TYPE string. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS mail_send. + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smps_app_023 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + subject = `Products as XLSX`. + text = |Hello,\n\nthe current product list is attached.\n\nSent from an abap2UI5 app.|. + view_display( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( `SEND` ). + mail_send( ). + ENDIF. + + ENDMETHOD. + + + METHOD mail_send. + + IF sender IS INITIAL OR recipient IS INITIAL. + client->message_box_display( text = `Sender and recipient are both needed` type = `error` ). + RETURN. + ENDIF. + + " the attachment - written exactly as z2ui5_cl_smps_app_022 writes it + DATA(xlsx) = z2ui5_cl_smps_xlsx=>write( VALUE #( + ( a = `Product` b = `Quantity` c = `Price` d = `Currency` ) + ( a = `Notebook` b = `2` c = `899.00` d = `EUR` ) + ( a = `Docking station` b = `2` c = `189.50` d = `EUR` ) + ( a = `Headset` b = `5` c = `79.90` d = `EUR` ) ) ). + + TRY. + DATA(mail) = cl_bcs_mail_message=>create_instance( ). + mail->set_sender( CONV #( sender ) ). + mail->add_recipient( CONV #( recipient ) ). + mail->set_subject( CONV #( subject ) ). + mail->set_main( cl_bcs_mail_textpart=>create_text_plain( text ) ). + mail->add_attachment( cl_bcs_mail_binarypart=>create_instance( + iv_content = xlsx + iv_content_type = CONV #( z2ui5_cl_smps_xlsx=>mime_type ) + iv_filename = `products.xlsx` ) ). + + " the send hands the mail to the outbound queue - the status says + " whether it left already or is still waiting there + mail->send( IMPORTING ev_mail_status = DATA(status) ). + + client->message_box_display( text = |Mail handed over to the mail system, status { status }| type = `success` ). + + CATCH cx_bcs_mail INTO DATA(error). + " an invalid address, or no outbound mail configured on this system + client->message_box_display( text = error->get_text( ) type = `error` ). + ENDTRY. + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + )->a( n = `xmlns:form` v = `sap.ui.layout.form` ). + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - E-Mail - 23 Send a Table as XLSX Attachment` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `Sends a mail with the product list as .xlsx attachment through cl_bcs_mail_message. The ` && + `system needs outbound mail configured - a communication arrangement for SAP_COM_0548 ` && + `on ABAP Cloud, SMTP in SCOT on-premise.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + page->ele( n = `SimpleForm` ns = `form` + )->a( n = `title` v = `cl_bcs_mail_message` + )->a( n = `editable` b = abap_true + )->ele( n = `content` ns = `form` + )->tag( `Label` + )->a( n = `text` v = `From` + )->tag( `Input` + )->a( n = `value` v = client->_bind( sender ) + )->a( n = `type` v = `Email` + )->a( n = `placeholder` v = `a sender address your mail configuration allows` + )->tag( `Label` + )->a( n = `text` v = `To` + )->tag( `Input` + )->a( n = `value` v = client->_bind( recipient ) + )->a( n = `type` v = `Email` + )->tag( `Label` + )->a( n = `text` v = `Subject` + )->tag( `Input` + )->a( n = `value` v = client->_bind( subject ) + )->tag( `Label` + )->a( n = `text` v = `Text` + )->tag( `TextArea` + )->a( n = `value` v = client->_bind( text ) + )->a( n = `rows` v = `6` + )->tag( `Label` + )->a( n = `text` v = `Attachment` + )->tag( `Text` + )->a( n = `text` v = `products.xlsx - four products, written with XCO` ). + + page->ele( `footer` + )->ele( `OverflowToolbar` + )->tag( `ToolbarSpacer` + )->tag( `Button` + )->a( n = `press` v = client->_event( `SEND` ) + )->a( n = `text` v = `Send` + )->a( n = `icon` v = `sap-icon://email` + )->a( n = `type` v = `Emphasized` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/13/z2ui5_cl_smps_app_023.clas.xml b/src/13/z2ui5_cl_smps_app_023.clas.xml new file mode 100644 index 0000000..cd453e4 --- /dev/null +++ b/src/13/z2ui5_cl_smps_app_023.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMPS_APP_023 + E + E-Mail - Send a Table as XLSX Attachment + 1 + X + X + X + + + + diff --git a/src/13/z2ui5_cl_smps_xlsx.clas.abap b/src/13/z2ui5_cl_smps_xlsx.clas.abap new file mode 100644 index 0000000..9a8fc4b --- /dev/null +++ b/src/13/z2ui5_cl_smps_xlsx.clas.abap @@ -0,0 +1,95 @@ +"!

abap2UI5 sample - Excel read and write with XCO

+"! The XCO XLSX library behind the samples of package 13: an .xlsx file in, +"! a table of strings out - and the way back. Every cell travels as a string, +"! so one table type serves every sheet, and the app converts what it needs. +"! +"! Six columns, A to F, are read and written - enough for the samples, and a +"! fixed row structure is what the XCO row stream maps the cells onto. +CLASS z2ui5_cl_smps_xlsx DEFINITION PUBLIC FINAL CREATE PUBLIC. + + PUBLIC SECTION. + + TYPES: + BEGIN OF ty_s_row, + a TYPE string, + b TYPE string, + c TYPE string, + d TYPE string, + e TYPE string, + f TYPE string, + END OF ty_s_row. + TYPES ty_t_row TYPE STANDARD TABLE OF ty_s_row WITH EMPTY KEY. + + "! the MIME type of an .xlsx file - for a data URL or a mail attachment + CONSTANTS mime_type TYPE string + VALUE `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` ##NO_TEXT. + + "! the first worksheet, every row from the first one on, columns A to F + CLASS-METHODS read + IMPORTING + val TYPE xstring + RETURNING + VALUE(result) TYPE ty_t_row. + + "! one worksheet, the rows from A1 on + CLASS-METHODS write + IMPORTING + t_row TYPE ty_t_row + RETURNING + VALUE(result) TYPE xstring. + + PROTECTED SECTION. + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smps_xlsx IMPLEMENTATION. + + METHOD read. + + DATA(worksheet) = xco_cp_xlsx=>document->for_file_content( val + )->read_access( + )->get_workbook( + )->worksheet->at_position( 1 ). + + " A1 down to the last row: a pattern without an end reads as far as the + " sheet has rows, and the six columns of ty_s_row decide how wide + DATA(pattern) = xco_cp_xlsx_selection=>pattern_builder->simple_from_to( + )->from_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( `A` ) + )->to_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( `F` ) + )->from_row( xco_cp_xlsx=>coordinate->for_numeric_value( 1 ) + )->get_pattern( ). + + " the string value transformation turns every cell into its text - a + " number comes back as it is stored, a date as its serial number + worksheet->select( pattern + )->row_stream( + )->operation->write_to( REF #( result ) + )->set_value_transformation( xco_cp_xlsx_read_access=>value_transformation->string_value + )->execute( ). + + ENDMETHOD. + + + METHOD write. + + DATA(document) = xco_cp_xlsx=>document->empty( )->write_access( ). + + DATA(pattern) = xco_cp_xlsx_selection=>pattern_builder->simple_from_to( + )->from_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( `A` ) + )->to_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( `F` ) + )->from_row( xco_cp_xlsx=>coordinate->for_numeric_value( 1 ) + )->get_pattern( ). + + document->get_workbook( + )->worksheet->at_position( 1 + )->select( pattern + )->row_stream( + )->operation->write_from( REF #( t_row ) + )->execute( ). + + result = document->get_file_content( ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/13/z2ui5_cl_smps_xlsx.clas.xml b/src/13/z2ui5_cl_smps_xlsx.clas.xml new file mode 100644 index 0000000..5dbeb37 --- /dev/null +++ b/src/13/z2ui5_cl_smps_xlsx.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMPS_XLSX + E + abap2UI5 sample - Excel read and write with XCO + 1 + X + X + X + + + + diff --git a/src/14/README.md b/src/14/README.md new file mode 100644 index 0000000..509f4da --- /dev/null +++ b/src/14/README.md @@ -0,0 +1,56 @@ +# 14 — Adobe Forms + +*[← all packages](../../README.md)* + +The print forms a system already has, rendered to PDF from an abap2UI5 app. An +Adobe Form is rendered by the function module it generates: between +`FP_JOB_OPEN` and `FP_JOB_CLOSE` it hands back the PDF instead of printing it. +The app offers it as a download — one form as a PDF, several at once as one ZIP +file built with `cl_abap_zip`. + +## What you need + +**Release:** Standard only, ≥ 7.50. The classic Adobe Forms runtime +(`FP_JOB_OPEN`, the generated function module, `FP_JOB_CLOSE`) is not released +for ABAP Cloud, where forms are rendered by the Forms Service by Adobe through +`cl_fp_ads_util` instead. + +**Branch:** [`14-adobe-forms`](https://github.com/abap2UI5/samples-stack/tree/14-adobe-forms) +— this package alone, without the other thirteen on your system. + +**Adobe Document Services (ADS)** configured for the system — the RFC +destination `ADS` in `SM59` — and an Adobe Form to render. The sample calls the +generated function module with a parameter table carrying only +`/1BCDWB/DOCPARAMS` and `/1BCDWB/FORMOUTPUT`, so it renders forms whose +interface needs no parameters of its own. A form that wants more says so in the +result list. + +## The sample + +| You want to | Sample | +|---|---| +| render forms to PDF, download one or a ZIP of several | [`024`](z2ui5_cl_smps_app_024.clas.abap) | + +```abap +CALL FUNCTION 'FP_FUNCTION_MODULE_NAME' + EXPORTING i_name = form IMPORTING e_funcname = function. + +outputparams-getpdf = abap_true. " the PDF back, no printer +outputparams-nodialog = abap_true. " no print dialog +CALL FUNCTION 'FP_JOB_OPEN' CHANGING ie_outputparams = outputparams. + +CALL FUNCTION function + PARAMETER-TABLE t_param " /1BCDWB/DOCPARAMS, /1BCDWB/FORMOUTPUT + EXCEPTION-TABLE t_exc. + +CALL FUNCTION 'FP_JOB_CLOSE'. " output-pdf is the file +``` + +The job is closed in every case — an open one makes the next `FP_JOB_OPEN` of +the session fail. + +## Where to go next + +- [`13` Excel & E-Mail](../13/README.md) — a file as mail attachment. +- Showing a PDF inside the page: the PDFViewer sample `Z2UI5_CL_SMP_APP_449` in + [abap2UI5/samples](https://github.com/abap2UI5/samples). diff --git a/src/14/package.devc.xml b/src/14/package.devc.xml new file mode 100644 index 0000000..d6b90c9 --- /dev/null +++ b/src/14/package.devc.xml @@ -0,0 +1,10 @@ + + + + + + Adobe Forms + + + + diff --git a/src/14/z2ui5_cl_smps_app_024.clas.abap b/src/14/z2ui5_cl_smps_app_024.clas.abap new file mode 100644 index 0000000..f929bb2 --- /dev/null +++ b/src/14/z2ui5_cl_smps_app_024.clas.abap @@ -0,0 +1,342 @@ +" @keywords adobe forms pdf ads fp_job_open print form output zip download cl_abap_zip +" @summary Renders Adobe Forms of the system to PDF with FP_JOB_OPEN and the generated function module - one form as a PDF download, several at once as one ZIP file +"!

Adobe Forms - Render to PDF, Download One or a ZIP

+"! An Adobe Form is rendered by the function module the form generates. Its +"! name comes from FP_FUNCTION_MODULE_NAME, and between FP_JOB_OPEN and +"! FP_JOB_CLOSE it returns the PDF instead of printing it: +"! +"! CALL FUNCTION 'FP_FUNCTION_MODULE_NAME' +"! EXPORTING i_name = form IMPORTING e_funcname = function. +"! +"! CALL FUNCTION 'FP_JOB_OPEN' +"! CHANGING ie_outputparams = VALUE sfpoutputparams( getpdf = abap_true nodialog = abap_true ). +"! +"! CALL FUNCTION function +"! EXPORTING /1bcdwb/docparams = docparams +"! IMPORTING /1bcdwb/formoutput = output. " output-pdf is the file +"! +"! CALL FUNCTION 'FP_JOB_CLOSE'. +"! +"! The generated function module has the interface of its form, so the app +"! calls it with a PARAMETER-TABLE: the form name is typed in at runtime, and +"! a form whose interface has mandatory parameters of its own says so. +"! Several forms go into one ZIP file with cl_abap_zip. +"! +"! Needs Adobe Document Services (ADS) configured for the system - the +"! destination ADS in SM59. On ABAP Cloud forms are rendered by the Forms +"! Service by Adobe through cl_fp_ads_util instead; this sample is Standard +"! only. +CLASS z2ui5_cl_smps_app_024 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + "! one form name per line + DATA forms TYPE string. + + TYPES: + BEGIN OF ty_s_result, + form TYPE string, + size TYPE string, + status TYPE string, + state TYPE string, + END OF ty_s_result. + TYPES ty_t_result TYPE STANDARD TABLE OF ty_s_result WITH EMPTY KEY. + "! what the last run produced, one row per form + DATA t_results TYPE ty_t_result. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + TYPES: + BEGIN OF ty_s_pdf, + form TYPE string, + pdf TYPE xstring, + END OF ty_s_pdf. + TYPES ty_t_pdf TYPE STANDARD TABLE OF ty_s_pdf WITH EMPTY KEY. + + METHODS on_event. + + "! renders every form of FORMS - the ones that worked come back, every + "! form gets its row in T_RESULTS + METHODS forms_render + RETURNING + VALUE(result) TYPE ty_t_pdf. + + "! one form to PDF - ERROR says why when it does not render + METHODS form_render + IMPORTING + form TYPE string + EXPORTING + pdf TYPE xstring + error TYPE string. + + METHODS file_download + IMPORTING + name TYPE string + mime_type TYPE string + content TYPE xstring. + + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smps_app_024 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD on_event. + + DATA(t_pdf) = forms_render( ). + + IF t_pdf IS INITIAL. + view_display( ). + RETURN. + ENDIF. + + CASE client->get_event( ). + + WHEN `PDF`. + " the first form that rendered - one file, no ZIP needed + DATA(s_pdf) = t_pdf[ 1 ]. + file_download( name = |{ to_lower( s_pdf-form ) }.pdf| + mime_type = `application/pdf` + content = s_pdf-pdf ). + + WHEN `ZIP`. + DATA(zip) = NEW cl_abap_zip( ). + LOOP AT t_pdf INTO s_pdf. + zip->add( name = |{ to_lower( s_pdf-form ) }.pdf| + content = s_pdf-pdf ). + ENDLOOP. + file_download( name = `forms.zip` + mime_type = `application/zip` + content = zip->save( ) ). + + ENDCASE. + + view_display( ). + + ENDMETHOD. + + + METHOD forms_render. + + CLEAR t_results. + SPLIT forms AT cl_abap_char_utilities=>newline INTO TABLE DATA(t_form). + + LOOP AT t_form INTO DATA(form). + + form = to_upper( condense( form ) ). + IF form IS INITIAL. + CONTINUE. + ENDIF. + + form_render( EXPORTING form = form + IMPORTING pdf = DATA(pdf) + error = DATA(error) ). + + IF error IS INITIAL. + APPEND VALUE #( form = form pdf = pdf ) TO result. + APPEND VALUE #( form = form + size = |{ xstrlen( pdf ) } bytes| + status = `rendered` + state = `Success` ) TO t_results. + ELSE. + APPEND VALUE #( form = form + status = error + state = `Error` ) TO t_results. + ENDIF. + + ENDLOOP. + + ENDMETHOD. + + + METHOD form_render. + + DATA function TYPE funcname. + DATA output TYPE fpformoutput. + DATA docparams TYPE sfpdocparams. + DATA outputpar TYPE sfpoutputparams. + + CLEAR: pdf, error. + + " the name of the function module the form generated - it differs per + " system, which is why it is asked for and never written down + TRY. + CALL FUNCTION 'FP_FUNCTION_MODULE_NAME' + EXPORTING + i_name = CONV fpname( form ) + IMPORTING + e_funcname = function. + CATCH cx_fp_api INTO DATA(api_error). + error = api_error->get_text( ). + RETURN. + ENDTRY. + + " getpdf: hand the PDF back instead of sending it to a printer, + " nodialog: no print dialog - there is no SAP GUI to show it in + outputpar-getpdf = abap_true. + outputpar-nodialog = abap_true. + + CALL FUNCTION 'FP_JOB_OPEN' + CHANGING + ie_outputparams = outputpar + EXCEPTIONS + cancel = 1 + usage_error = 2 + system_error = 3 + internal_error = 4 + OTHERS = 5. + IF sy-subrc <> 0. + error = |FP_JOB_OPEN failed with sy-subrc { sy-subrc } - is ADS configured?|. + RETURN. + ENDIF. + + " the generated function module has the interface of its form - called + " with a parameter table, so any form name typed in at runtime works + DATA(t_param) = VALUE abap_func_parmbind_tab( + ( name = '/1BCDWB/DOCPARAMS' + kind = abap_func_exporting + value = REF #( docparams ) ) + ( name = '/1BCDWB/FORMOUTPUT' + kind = abap_func_importing + value = REF #( output ) ) ). + DATA(t_exc) = VALUE abap_func_excpbind_tab( + ( name = 'USAGE_ERROR' value = 1 ) + ( name = 'SYSTEM_ERROR' value = 2 ) + ( name = 'INTERNAL_ERROR' value = 3 ) + ( name = 'OTHERS' value = 4 ) ). + + TRY. + CALL FUNCTION function + PARAMETER-TABLE t_param + EXCEPTION-TABLE t_exc. + IF sy-subrc <> 0. + error = |{ function } failed with sy-subrc { sy-subrc }|. + ENDIF. + + CATCH cx_sy_dyn_call_error INTO DATA(call_error). + " a form whose interface wants more than the two parameters above + error = call_error->get_text( ). + ENDTRY. + + " closed in every case - an open job would make the next FP_JOB_OPEN of + " this session fail + CALL FUNCTION 'FP_JOB_CLOSE' + EXCEPTIONS + OTHERS = 1. + IF sy-subrc <> 0 AND error IS INITIAL. + error = |FP_JOB_CLOSE failed with sy-subrc { sy-subrc }|. + ENDIF. + + IF error IS INITIAL. + pdf = output-pdf. + ENDIF. + + ENDMETHOD. + + + METHOD file_download. + + " base64 through the classic utility class - this package is Standard + " only, and cl_http_utility is there on every release it runs on + client->follow_up_action( + val = client->cs_event-download_b64_file + t_arg = VALUE #( ( |data:{ mime_type };base64,{ cl_http_utility=>encode_x_base64( content ) }| ) + ( name ) ) ). + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + )->a( n = `xmlns:form` v = `sap.ui.layout.form` ). + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - Adobe Forms - 24 Render to PDF` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `Type one Adobe Form name per line. Download PDF renders the first one, Download ZIP all of ` && + `them into one ZIP file. Needs Adobe Document Services configured for this system, and ` && + `forms whose interface needs no parameters of its own.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + page->ele( n = `SimpleForm` ns = `form` + )->a( n = `title` v = `FP_JOB_OPEN - generated function module - FP_JOB_CLOSE` + )->a( n = `editable` b = abap_true + )->ele( n = `content` ns = `form` + )->tag( `Label` + )->a( n = `text` v = `Forms` + )->tag( `TextArea` + )->a( n = `value` v = client->_bind( forms ) + )->a( n = `rows` v = `4` + )->a( n = `placeholder` v = `one form name per line` ). + + DATA(table) = page->ele( `Table` + )->a( n = `items` v = client->_bind( t_results ) + )->a( n = `noDataText` v = `Nothing rendered yet` ). + table->ele( `columns` + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Form` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Size` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Result` ). + table->ele( `items` + )->ele( `ColumnListItem` + )->ele( `cells` + )->tag( `Text` + )->a( n = `text` v = `{FORM}` + )->tag( `Text` + )->a( n = `text` v = `{SIZE}` + )->tag( `ObjectStatus` + )->a( n = `text` v = `{STATUS}` + )->a( n = `state` v = `{= ${STATE} || 'None' }` ). + + page->ele( `footer` + )->ele( `OverflowToolbar` + )->tag( `ToolbarSpacer` + )->tag( `Button` + )->a( n = `press` v = client->_event( `PDF` ) + )->a( n = `text` v = `Download PDF` + )->a( n = `icon` v = `sap-icon://pdf-attachment` + )->tag( `Button` + )->a( n = `press` v = client->_event( `ZIP` ) + )->a( n = `text` v = `Download ZIP` + )->a( n = `icon` v = `sap-icon://attachment-zip-file` + )->a( n = `type` v = `Emphasized` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/14/z2ui5_cl_smps_app_024.clas.xml b/src/14/z2ui5_cl_smps_app_024.clas.xml new file mode 100644 index 0000000..7bba708 --- /dev/null +++ b/src/14/z2ui5_cl_smps_app_024.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMPS_APP_024 + E + Adobe Forms - Render to PDF, Download One or a ZIP + 1 + X + X + X + + + + diff --git a/src/z2ui5_cl_smps_app_000.clas.abap b/src/z2ui5_cl_smps_app_000.clas.abap index 59b1946..c1d83e9 100644 --- a/src/z2ui5_cl_smps_app_000.clas.abap +++ b/src/z2ui5_cl_smps_app_000.clas.abap @@ -70,11 +70,14 @@ CLASS z2ui5_cl_smps_app_000 DEFINITION PUBLIC. DATA t_launchpad TYPE ty_t_sample. DATA t_ai TYPE ty_t_sample. DATA t_upload TYPE ty_t_sample. + DATA t_actions TYPE ty_t_sample. + DATA t_office TYPE ty_t_sample. + DATA t_forms TYPE ty_t_sample. PROTECTED SECTION. DATA client TYPE REF TO z2ui5_if_client. - "! at least one of the two demo data classes is on the system, so the + "! at least one of the demo data classes is on the system, so the "! Regenerate Demo Data button has something to call DATA demo_data_installed TYPE abap_bool. @@ -125,11 +128,12 @@ CLASS z2ui5_cl_smps_app_000 DEFINITION PUBLIC. END OF cs_url. CONSTANTS: - "! the demo data of the two RAP packages - called by name for the same + "! the demo data of the RAP packages - called by name for the same "! reason the samples are, see the class documentation BEGIN OF cs_class, data_trv TYPE string VALUE `Z2UI5_CL_SMPS_DATA_TRV` ##NO_TEXT, data_trd TYPE string VALUE `Z2UI5_CL_SMPS_DATA_TRD` ##NO_TEXT, + data_req TYPE string VALUE `Z2UI5_CL_SMPS_DATA_REQ` ##NO_TEXT, END OF cs_class. METHODS on_event. @@ -311,11 +315,12 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION. WHEN cs_backend_event-regenerate. - " both business objects at once - the samples of src/03 run against - " the one without draft, those of src/04 against the draft enabled - " one, and an empty table is the most common reason a sample looks - " broken. data_reset( ) deletes first, so the travel ids stay 1, 2, 3. - " Both answers read alike, so each one says which package it is from. + " every business object at once - the samples of src/03 run against + " the travel without draft, those of src/04 against the draft enabled + " one, those of src/12 against the request, and an empty table is the + " most common reason a sample looks broken. data_reset( ) deletes + " first, so the travel ids stay 1, 2, 3. The answers read alike, so + " each one says which package it is from. DATA(text) = data_reset( cs_class-data_trv ). IF text IS NOT INITIAL. text = |RAP: { text }|. @@ -324,8 +329,12 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION. IF text_draft IS NOT INITIAL. text = condense( |{ text } RAP with Draft: { text_draft }| ). ENDIF. + DATA(text_actions) = data_reset( cs_class-data_req ). + IF text_actions IS NOT INITIAL. + text = condense( |{ text } RAP Actions: { text_actions }| ). + ENDIF. IF text IS INITIAL. - text = `No demo data on this system - the two RAP packages are not installed`. + text = `No demo data on this system - none of the RAP packages is installed`. ENDIF. client->message_toast_display( text ). @@ -464,6 +473,27 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION. samples = t_upload items = client->_bind( t_upload ) ). + render_package( page = page + title = `12 - RAP Actions & Permissions` + hint = `feature control, parameters, factory actions, defaults, projections on Z2UI5_R_SMPS_REQ - the business object ships with the package` + branch = `12-rap-actions` + samples = t_actions + items = client->_bind( t_actions ) ). + + render_package( page = page + title = `13 - Excel & E-Mail` + hint = `read and write .xlsx with the XCO library, send it by mail - the mail sample needs outbound mail configured` + branch = `13-excel-email` + samples = t_office + items = client->_bind( t_office ) ). + + render_package( page = page + title = `14 - Adobe Forms` + hint = `on-premise - needs Adobe Document Services configured for the system` + branch = `14-adobe-forms` + samples = t_forms + items = client->_bind( t_forms ) ). + client->view_display( view->stringify( ) ). ENDMETHOD. @@ -506,7 +536,7 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION. )->a( n = `text` v = `Regenerate Demo Data` )->a( n = `icon` v = `sap-icon://refresh` " it deletes before it creates - say so before the press, not after - )->a( n = `tooltip` v = `Deletes every travel of both RAP packages, drafts included, and creates the demo travels again` + )->a( n = `tooltip` v = `Deletes the demo data of the RAP packages, drafts included, and creates it again` )->a( n = `type` v = `Transparent` )->a( n = `visible` b = demo_data_installed )->a( n = `press` v = client->_event( cs_backend_event-regenerate ) ). @@ -879,7 +909,8 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION. METHOD model_init. demo_data_installed = xsdbool( class_check_installed( cs_class-data_trv ) = abap_true - OR class_check_installed( cs_class-data_trd ) = abap_true ). + OR class_check_installed( cs_class-data_trd ) = abap_true + OR class_check_installed( cs_class-data_req ) = abap_true ). t_odata = VALUE #( ( sample( no = `315` @@ -1021,7 +1052,11 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION. ( sample( no = `484` title = `Cross-app navigation - receiver` detail = `reads them back out of its startup parameters` - classname = `Z2UI5_CL_SMPS_APP_484` ) ) ). + classname = `Z2UI5_CL_SMPS_APP_484` ) ) + ( sample( no = `025` + title = `Build the URL of a launchpad app` + detail = `Builds the URL of a launchpad app from semantic object, action and parameters - through cl_lsapi_manager where the system has it, as an intent URL of its own where it does not` + classname = `Z2UI5_CL_SMPS_APP_025` ) ) ). t_ai = VALUE #( ( sample( no = `013` @@ -1043,6 +1078,44 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION. detail = `EXECUTE uploadFile with a deep parameter - the file travels as a stream property, download and delete included` classname = `Z2UI5_CL_SMPS_APP_016` ) ) ). + t_actions = VALUE #( + ( sample( no = `017` + title = `Feature control with GET PERMISSIONS` + detail = `GET PERMISSIONS asks the business object what may happen to each request - the buttons follow its answer, and the business object refuses the rest anyway` + classname = `Z2UI5_CL_SMPS_APP_017` ) ) + ( sample( no = `018` + title = `Parameters, factory actions, defaults` + detail = `Actions with a parameter - a default function proposes a new request, a static factory action creates it, a factory action copies one, and reject asks for its reason in a popup` + classname = `Z2UI5_CL_SMPS_APP_018` ) ) + ( sample( no = `019` + title = `Validate on change with a determine action` + detail = `A determine action runs the validation of the business object while the user types - the quantity is checked on every change, and nothing is saved until Save` + classname = `Z2UI5_CL_SMPS_APP_019` ) ) + ( sample( no = `020` + title = `One business object, two contracts` + detail = `One business object, two contracts - approve through the projection Z2UI5_C_SMPS_REQ or on the business object itself, and the handler records which one it was` + classname = `Z2UI5_CL_SMPS_APP_020` ) ) ). + + t_office = VALUE #( + ( sample( no = `021` + title = `Read an uploaded XLSX file` + detail = `An .xlsx file from the FileUploader read with the XCO XLSX library - the first worksheet becomes an ABAP table and is shown as it arrived` + classname = `Z2UI5_CL_SMPS_APP_021` ) ) + ( sample( no = `022` + title = `Export a table as XLSX file` + detail = `The table on the screen written into an .xlsx file with the XCO XLSX library and handed to the browser as a download - a header row, then one row per line` + classname = `Z2UI5_CL_SMPS_APP_022` ) ) + ( sample( no = `023` + title = `Send a table as XLSX attachment` + detail = `A mail with an .xlsx attachment, sent with cl_bcs_mail_message - recipient, subject and text from the screen, the attachment written with the XCO XLSX library` + classname = `Z2UI5_CL_SMPS_APP_023` ) ) ). + + t_forms = VALUE #( + ( sample( no = `024` + title = `Render Adobe Forms to PDF, one file or a ZIP` + detail = `Renders Adobe Forms of the system to PDF with FP_JOB_OPEN and the generated function module - one form as a PDF download, several at once as one ZIP file` + classname = `Z2UI5_CL_SMPS_APP_024` ) ) ). + ENDMETHOD.