From db4c83287ce5040494f7e10c899d6258e7a14dde Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 01:33:39 +0000 Subject: [PATCH 1/5] Port the first nine abap2UI5 samples to cap2UI5, as examples A first cut of moving abap2UI5/samples here, to agree on the conventions before the other 120 apps follow: an ordinary CAP project on the published cap2ui5 0.1.0, one file per ABAP class under srv/apps/, registered under the same name so ?app_start= and the calls between samples stay as they are. The eight samples cover the basics (493, 494, 495), a table (011), event arguments (167), popups (161), navigation with a result (488 + 489) and one front-end action (125), which goes through c.raw because the facade has no follow_up_action( ) yet. test/samples.test.mjs drives every sample over the wire, as cap2UI5's own suite does: each one starts with a well-formed view, and its events, popups and navigation round trips do what the sample says. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XtDApnfmAncwrmW6fE4tCS --- .github/workflows/test.yml | 18 + .gitignore | 5 + .nvmrc | 1 + README.md | 91 ++- package-lock.json | 1289 ++++++++++++++++++++++++++++++ package.json | 33 + srv/apps/z2ui5_cl_smp_app_011.js | 103 +++ srv/apps/z2ui5_cl_smp_app_125.js | 70 ++ srv/apps/z2ui5_cl_smp_app_161.js | 82 ++ srv/apps/z2ui5_cl_smp_app_167.js | 68 ++ srv/apps/z2ui5_cl_smp_app_488.js | 87 ++ srv/apps/z2ui5_cl_smp_app_489.js | 69 ++ srv/apps/z2ui5_cl_smp_app_493.js | 38 + srv/apps/z2ui5_cl_smp_app_494.js | 60 ++ srv/apps/z2ui5_cl_smp_app_495.js | 66 ++ test/samples.test.mjs | 157 ++++ test/server.mjs | 59 ++ 17 files changed, 2294 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .nvmrc create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 srv/apps/z2ui5_cl_smp_app_011.js create mode 100644 srv/apps/z2ui5_cl_smp_app_125.js create mode 100644 srv/apps/z2ui5_cl_smp_app_161.js create mode 100644 srv/apps/z2ui5_cl_smp_app_167.js create mode 100644 srv/apps/z2ui5_cl_smp_app_488.js create mode 100644 srv/apps/z2ui5_cl_smp_app_489.js create mode 100644 srv/apps/z2ui5_cl_smp_app_493.js create mode 100644 srv/apps/z2ui5_cl_smp_app_494.js create mode 100644 srv/apps/z2ui5_cl_smp_app_495.js create mode 100644 test/samples.test.mjs create mode 100644 test/server.mjs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..59b43a1 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,18 @@ +name: test + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: .nvmrc + cache: npm + - run: npm ci + - run: npm test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf0d054 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +*.sqlite +*.sqlite-* +*.log +.DS_Store diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/README.md b/README.md index 9f0d6f2..9e24767 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,89 @@ -# samples -Samples of cap2UI5 +# cap2UI5 samples + +The [abap2UI5 samples](https://github.com/abap2UI5/samples) as +[cap2UI5](https://github.com/cap2UI5/cap2UI5) apps: every sample a plain +JavaScript class in `srv/apps/`, in an ordinary CAP project. + +> [!NOTE] +> **Status: first examples.** Nine of the 129 apps are ported, to agree on the +> conventions below before the rest follows. + +## Run + +```bash +npm install +npm run watch # cds watch - prints the address of every sample +npm test # every sample driven over the wire against the published cap2ui5 +``` + +Log in as `alice` with an empty password (CAP's mocked development user), then +open e.g. . + +## The samples so far + +| Sample | App | ABAP original | +|---|---|---| +| Basics I - Hello World, the Smallest App | [`Z2UI5_CL_SMP_APP_493`](srv/apps/z2ui5_cl_smp_app_493.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_493.clas.abap) | +| Basics II - Data Binding: Input and Button | [`Z2UI5_CL_SMP_APP_494`](srv/apps/z2ui5_cl_smp_app_494.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_494.clas.abap) | +| Basics III - Lifecycle: First Run, Event, Display | [`Z2UI5_CL_SMP_APP_495`](srv/apps/z2ui5_cl_smp_app_495.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_495.clas.abap) | +| Table - Editable Cells, Add and Delete Rows | [`Z2UI5_CL_SMP_APP_011`](srv/apps/z2ui5_cl_smp_app_011.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_011.clas.abap) | +| Event - Extra Arguments with the Event | [`Z2UI5_CL_SMP_APP_167`](srv/apps/z2ui5_cl_smp_app_167.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_167.clas.abap) | +| Popup - Dialog inside a Dialog | [`Z2UI5_CL_SMP_APP_161`](srv/apps/z2ui5_cl_smp_app_161.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_161.clas.abap) | +| Navigation - Return Data and Events to the Caller | [`Z2UI5_CL_SMP_APP_488`](srv/apps/z2ui5_cl_smp_app_488.js) (+ [`_489`](srv/apps/z2ui5_cl_smp_app_489.js)) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_488.clas.abap) | +| Browser - Set the Tab Title (A) | [`Z2UI5_CL_SMP_APP_125`](srv/apps/z2ui5_cl_smp_app_125.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_125.clas.abap) | + +## How a sample is ported + +**One ABAP class, one file, the same name.** `z2ui5_cl_smp_app_493.clas.abap` +becomes `srv/apps/z2ui5_cl_smp_app_493.js`, registered as +`Z2UI5_CL_SMP_APP_493` - so `?app_start=` is the same on both sides, a sample +that calls another one keeps calling it by its name, and `@origin` in the +file header points back at the original. + +| abap2UI5 | cap2UI5 | +|---|---| +| `DATA name TYPE string.` | `name = "";` - the initial value is the type | +| `DATA t_tab TYPE STANDARD TABLE OF ty_s_row` | `t_tab = t.table({ …one row… });` | +| `client->check_on_init( )` | `c.isFirstRun` - seed state here | +| `client->check_on_navigated( )` | `c.isDisplay` - render here | +| `client->check_on_event( 'X' )`, `get_event( )` | `c.eventName === "X"` | +| `client->_bind( name )` | `c.bind("name")` - the field's **name** | +| `client->_event( val = 'X' t_arg = … )` | `c.event("X", [ … ])` | +| `client->get_event_arg( 1 )` | `c.eventArg(1)` | +| `z2ui5_cl_ui5_view_builder` chain | an XML template literal | +| `client->view_display( )`, `popup_display( )` | `c.view( )`, `c.popup( )` | +| `client->check_app_prev_stack( )` | `c.canGoBack` | +| `client->_event_nav_app_leave( )` | `c.event("BACK")`, answered with `c.navBack( )` | +| `client->nav_app_call( NEW z…( ) )` | `c.navTo("Z…")` | +| `client->get_app_prev( )` | `c.prevApp` - the other app's fields as plain values | + +The page title says `cap2UI5 - …`, and the explaining texts name the +JavaScript API, not the ABAP one. + +### Four things that differ from ABAP + +1. **`this` reads plain copies.** Assign to write: + `this.t_tab = [...this.t_tab, row]`. A `push` on the copy is lost. +2. **A table field starts empty**, whatever rows its initializer lists: + declare it with `t.table({ …one row… })` and seed it in `c.isFirstRun`. + Scalars and structures keep their initial values. +3. **Helpers are functions, not methods.** In `cap2ui5` 0.1.0 a method called + from `main( )` sees the framework's ABAP boxes instead of the values, so + the view is built by a module function that gets `c` and the app: + `c.view(view(c, this))`. +4. **The render branch comes first.** A called app that returns arrives with + `c.isDisplay` *and* its event name set; `if (c.isDisplay) … else if + (c.eventName === …)` keeps the two apart. + +### What the facade does not have yet + +`c` covers what 39 of the 129 samples use; six more need only a small +adaptation (a structure's components bound relatively inside +`binding="${c.bind("s")}"`, as in `Z2UI5_CL_SMP_APP_488`). The rest need +parts of `z2ui5_if_client` the facade does not wrap yet - above all +`follow_up_action( )` (47 samples), then `popover_display( )`, `get( )`, and +the options of `_bind( )`, `_event( )` and `message_box_display( )`. + +Until it does, every one of them is reachable through `c.raw`, the transpiled +`z2ui5_if_client` - asynchronous and with ABAP-typed arguments, as +`Z2UI5_CL_SMP_APP_125` shows. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..680d627 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1289 @@ +{ + "name": "cap2ui5-samples", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cap2ui5-samples", + "dependencies": { + "@cap-js/sqlite": "^3", + "@sap/cds": "^10", + "cap2ui5": "0.1.0" + }, + "devDependencies": { + "@cap-js/cds-test": "^1.0.2", + "fast-xml-parser": "^5.11.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@abap2ui5/node-runtime": { + "version": "1.145.0", + "resolved": "https://registry.npmjs.org/@abap2ui5/node-runtime/-/node-runtime-1.145.0.tgz", + "integrity": "sha512-pB3Q0T6nuTE3mI0kvLPCTtMCJIxIx16nfcm3/L0epAs7W7pQGxL79SMWYm3DD5xPrhlRgmTeEbNBjXVpfYsS+w==", + "license": "MIT", + "dependencies": { + "@abaplint/database-sqlite": "2.13.83", + "@abaplint/runtime": "2.13.91" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "express": "^5.0.0" + }, + "peerDependenciesMeta": { + "express": { + "optional": true + } + } + }, + "node_modules/@abaplint/database-sqlite": { + "version": "2.13.83", + "resolved": "https://registry.npmjs.org/@abaplint/database-sqlite/-/database-sqlite-2.13.83.tgz", + "integrity": "sha512-L5+IPwq4rcPAHPOSeJYUukqbD7SY26VfgSsLqZL9m8H2BGB02MfiMqlzmZ4m+Vpggy6zzjLjFH/CKwoBwx4ocw==", + "license": "MIT", + "dependencies": { + "sql.js": "^1.14.2" + } + }, + "node_modules/@abaplint/runtime": { + "version": "2.13.91", + "resolved": "https://registry.npmjs.org/@abaplint/runtime/-/runtime-2.13.91.tgz", + "integrity": "sha512-Ux3BPc1MVTPsZggUgBs+lJm3xuPlPDydSr9RLMKEHxgq5BliNs959TkSpRhJjg/WUAmuR2WPgoeU+nQyElgpYQ==", + "license": "MIT", + "dependencies": { + "temporal-polyfill": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/larshp" + } + }, + "node_modules/@cap-js/cds-test": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@cap-js/cds-test/-/cds-test-1.0.2.tgz", + "integrity": "sha512-autSQg1l7nvHmBwxan0inQ71Cw3cqQdBvON/RFarhbwRJSeC6oC7nQ6d1RiVxCYYkeM0wqkmfn8WGQ9K89dWXQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "cds-test": "bin/chest.js", + "chest": "bin/chest.js" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@sap/cds": ">=9.8", + "chai": "^6", + "chai-as-promised": "^8" + } + }, + "node_modules/@cap-js/db-service": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@cap-js/db-service/-/db-service-3.1.1.tgz", + "integrity": "sha512-N1vgMkiDR0IrwwCGFr1e4CcSUsoHFsxiryMC9EYpUPR7mQnw6kfvWzYEwB3/9QXp1nmauV2RoETN6WFqBdwXZg==", + "license": "Apache-2.0", + "peerDependencies": { + "@sap/cds": "^10", + "generic-pool": "^3.9.0" + }, + "peerDependenciesMeta": { + "generic-pool": { + "optional": true + } + } + }, + "node_modules/@cap-js/sqlite": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@cap-js/sqlite/-/sqlite-3.1.1.tgz", + "integrity": "sha512-FHkenWLi7p8VMNg9Sa1CVqa7HZhAiY0RQkANfjP+torubFDd4fiTH98bLyLZ3Xo+vjhJYsE5Ztta8rmnUBzwPA==", + "license": "Apache-2.0", + "dependencies": { + "@cap-js/db-service": "^3.1.1" + }, + "peerDependencies": { + "@sap/cds": "^10", + "better-sqlite3": "^12.0.0 || ^13.0.0", + "sql.js": "^1.13.0" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "sql.js": { + "optional": true + } + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@nodable/entities": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.0.0.tgz", + "integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@sap/cds": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@sap/cds/-/cds-10.1.1.tgz", + "integrity": "sha512-0F2R0BqQS6q463WlDlkSqPaV8WK/YXDCKgVmAWcvYSRwcbJUFDBK1X4RF5obHFWjqOO1rXsf9Jh105inHXU3/g==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@sap/cds-compiler": "^7", + "@sap/cds-fiori": "^2", + "express": "^4.22.1 || ^5", + "yaml": "^2" + }, + "bin": { + "cds-deploy": "bin/deploy.js", + "cds-serve": "bin/serve.js" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@eslint/js": "^9 || ^10", + "tar": "^7.5.6" + }, + "peerDependenciesMeta": { + "tar": { + "optional": true + } + } + }, + "node_modules/@sap/cds-compiler": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@sap/cds-compiler/-/cds-compiler-7.1.1.tgz", + "integrity": "sha512-ep7rzRJgZtDBs9vVcXyxLVy/q4p+K+a1Q+yq1qn1mb7dYHyH7m88K+Pn761Ofnf7HXXbTo/FKpM1z/kPTbwy3w==", + "license": "SEE LICENSE IN LICENSE", + "bin": { + "cdsc": "bin/cdsc.js", + "cdshi": "bin/cdshi.js", + "cdsse": "bin/cdsse.js" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@sap/cds-fiori": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sap/cds-fiori/-/cds-fiori-2.3.0.tgz", + "integrity": "sha512-6oWov+DSpFrSTgxXR0dZhak6aZ/IVRZvaHERMi0EgSTzIJdlvZlpw3Kf18ePMcTrRrtEXwD4RIjKt8pbs0g2Hg==", + "license": "SEE LICENSE IN LICENSE", + "peerDependencies": { + "@sap/cds": ">=8" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cap2ui5": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cap2ui5/-/cap2ui5-0.1.0.tgz", + "integrity": "sha512-ZJYLzg7ezxkwcMQGMEJmToeTY78KpECFYne30JsktvmWKpLnBRVJGrx5k0tB4h52DYfTsp8MopfUfOHSPnjAyQ==", + "license": "MIT", + "dependencies": { + "@abap2ui5/node-runtime": "1.145.0", + "express": "^5" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@sap/cds": ">=9" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/chai-as-promised": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-8.0.2.tgz", + "integrity": "sha512-1GadL+sEJVLzDjcawPM4kjfnL+p/9vrxiEUonowKOAzvVg0PixJUdtuDzdkDeQhK3zfOE76GqGkZIQ7/Adcrqw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "check-error": "^2.1.1" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 7" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-xml-builder": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz", + "integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.1.tgz", + "integrity": "sha512-TBw6K/fxoQGGjCmZDw9w/ZwP3uDcnTM4YH/g+PFRWr8sbe5idXtxNN6vITh4+1ruCZaho6uBFurElsA7F0zzgw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^3.0.0", + "fast-xml-builder": "^1.2.0", + "is-unsafe": "^2.0.0", + "path-expression-matcher": "^1.6.2", + "strnum": "^2.4.2", + "xml-naming": "^0.3.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-unsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz", + "integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sql.js": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.2.tgz", + "integrity": "sha512-3ZGPovObMFrdw79zrUHbfdE/DLIsy8jdNdssmMSQuRAymedU6q84asPt0kgiqrdMYlPegDItiIMfmIXzZnYFcw==", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strnum": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.2.tgz", + "integrity": "sha512-rDG3Ah4TV0k1hWvLSzkZtMmLN9+eS+h3knq4MP6A42Y3Yh5qGNnOUs1jJkoSr8FG5dsL28c7KgkIBzSEykqtuw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } + }, + "node_modules/temporal-polyfill": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-1.0.5.tgz", + "integrity": "sha512-+H/mmY74i6wXCMcjIhGuSnODcyZnc4eois2myhOyX0UqRAKNXQY5m9iB1en5jQ/n4SXtjLXo1ElBI5bmn8tZwQ==", + "license": "MIT", + "dependencies": { + "temporal-spec": "1.0.1", + "temporal-utils": "1.0.3" + } + }, + "node_modules/temporal-spec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-1.0.1.tgz", + "integrity": "sha512-wxVoanmDeavXie1vu2JaQ3WIc3JZnWAOYFBsJyATaVsXsycKYUflGsyBmrRSnoCpZJpwPyr38VpgSUlQ8CbFxg==", + "license": "Apache-2.0" + }, + "node_modules/temporal-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/temporal-utils/-/temporal-utils-1.0.3.tgz", + "integrity": "sha512-9jbTSX3HvkOWvDpNpFzwX/d1mkCrgQgwLmVTOEiuocZGKHsrQJtttGZFmybJ2zYP5zCSZuU5ZyeS8+eOEPABiQ==", + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xml-naming": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..929b475 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "cap2ui5-samples", + "private": true, + "description": "The abap2UI5 samples as cap2UI5 apps: plain JavaScript classes in a CAP project.", + "type": "module", + "engines": { + "node": ">=22" + }, + "dependencies": { + "@cap-js/sqlite": "^3", + "@sap/cds": "^10", + "cap2ui5": "0.1.0" + }, + "devDependencies": { + "@cap-js/cds-test": "^1.0.2", + "fast-xml-parser": "^5.11.1" + }, + "scripts": { + "start": "cds-serve", + "watch": "cds watch", + "test": "node --test test/*.test.mjs" + }, + "cds": { + "requires": { + "db": { + "kind": "sqlite", + "credentials": { + "url": ":memory:" + } + } + } + } +} diff --git a/srv/apps/z2ui5_cl_smp_app_011.js b/srv/apps/z2ui5_cl_smp_app_011.js new file mode 100644 index 0000000..60129ef --- /dev/null +++ b/srv/apps/z2ui5_cl_smp_app_011.js @@ -0,0 +1,103 @@ +/** + * Table - Editable Cells, Add and Delete Rows + * + * An editable table: input cells, adding and deleting rows, multi-select and a + * toolbar over them. + * + * @keywords edit input add row delete multiselect toolbar + * @origin abap2UI5/samples src/z2ui5_cl_smp_app_011.clas.abap + */ +import { defineApp, t } from "cap2ui5"; + +const INFO = + "A MultiSelect table whose input cells switch between display and edit mode via the " + + "toolbar, which also adds new rows and deletes the currently selected ones."; + +defineApp("Z2UI5_CL_SMP_APP_011", class { + t_tab = t.table({ + selkz: false, + title: "", + value: "", + descr: "", + icon: "", + info: "", + editable: false, + checkbox: false, + }); + check_editable_active = false; + + main(c) { + if (c.isFirstRun) { + this.t_tab = [ + { title: "entry 01", value: "red", info: "completed", descr: "this is a description", checkbox: true }, + { title: "entry 02", value: "blue", info: "completed", descr: "this is a description", checkbox: true }, + { title: "entry 03", value: "green", info: "completed", descr: "this is a description", checkbox: true }, + { title: "entry 04", value: "orange", info: "completed", descr: "", checkbox: true }, + { title: "entry 05", value: "grey", info: "completed", descr: "this is a description", checkbox: true }, + {}, + ]; + } + + if (c.isDisplay) { + c.view(view(c, this)); + } else if (c.eventName === "BUTTON_EDIT") { + // `this` reads plain copies: a changed table is written back by assigning it + this.check_editable_active = !this.check_editable_active; + this.t_tab = this.t_tab.map((row) => ({ ...row, editable: this.check_editable_active })); + } else if (c.eventName === "BUTTON_DELETE") { + this.t_tab = this.t_tab.filter((row) => !row.selkz); + } else if (c.eventName === "BUTTON_ADD") { + this.t_tab = [...this.t_tab, { editable: this.check_editable_active }]; + } else if (c.eventName === "BACK") { + c.navBack(); + } + } +}); + +function view(c, app) { + return ` + + + + + + + + + <Button press="${c.event("BUTTON_TEST")}" text="test"/> + <ToolbarSpacer/> + <Button press="${c.event("BUTTON_DELETE")}" text="delete selected row" icon="sap-icon://delete"/> + <Button press="${c.event("BUTTON_ADD")}" text="add" icon="sap-icon://add"/> + <Button + press="${c.event("BUTTON_EDIT")}" + text="${app.check_editable_active ? "display" : "edit"}" + tooltip="Switch the cells between display and edit mode" + icon="sap-icon://edit"/> + </OverflowToolbar> + </headerToolbar> + <columns> + <Column><Text text="Title"/></Column> + <Column><Text text="Color"/></Column> + <Column><Text text="Info"/></Column> + <Column><Text text="Description"/></Column> + <Column><Text text="Checkbox"/></Column> + </columns> + <items> + <ColumnListItem selected="{SELKZ}"> + <cells> + <Input enabled="{EDITABLE}" value="{TITLE}"/> + <Input enabled="{EDITABLE}" value="{VALUE}"/> + <Input enabled="{EDITABLE}" value="{INFO}"/> + <Input enabled="{EDITABLE}" value="{DESCR}"/> + <CheckBox selected="{CHECKBOX}" enabled="{EDITABLE}"/> + </cells> + </ColumnListItem> + </items> + </Table> + </Page> + </Shell> + </mvc:View>`; +} diff --git a/srv/apps/z2ui5_cl_smp_app_125.js b/srv/apps/z2ui5_cl_smp_app_125.js new file mode 100644 index 0000000..bab2089 --- /dev/null +++ b/srv/apps/z2ui5_cl_smp_app_125.js @@ -0,0 +1,70 @@ +/** + * Browser - Set the Tab Title (A) + * + * Sets the browser tab title from the app, so a bookmarked or duplicated window + * says which app it holds. + * + * The facade `c` has no follow_up_action( ) yet, so this sample reaches the + * framework's own z2ui5_if_client through the escape hatch c.raw: asynchronous, + * and with the ABAP-typed arguments the transpiled method expects. + * + * @keywords document.title tab caption headline set_title follow_up_action raw + * @origin abap2UI5/samples src/z2ui5_cl_smp_app_125.clas.abap + */ +/* global abap */ +import { defineApp } from "cap2ui5"; + +const INFO = + "Enter a title and press the button to run the SET_TITLE front-end action, which updates " + + "the browser tab title (document.title) without reloading the page."; + +/** z2ui5_if_client~follow_up_action( val = action t_arg = args ), through c.raw */ +async function followUpAction(c, action, args = []) { + const t_arg = abap.types.TableFactory.construct( + new abap.types.String({ qualifiedName: "STRING" }), + { + withHeader: false, + keyType: "DEFAULT", + primaryKey: { isUnique: false, type: "STANDARD", keyFields: [], name: "primary_key" }, + secondary: [], + }, + "STRING_TABLE"); + for (const arg of args) t_arg.append(new abap.types.String().set(String(arg))); + await c.raw.z2ui5_if_client$follow_up_action({ val: action, t_arg }); +} + +defineApp("Z2UI5_CL_SMP_APP_125", class { + title = "my title"; + + async main(c) { + if (c.isDisplay) { + c.view(` + <mvc:View + xmlns:mvc="sap.ui.core.mvc" + xmlns="sap.m" + xmlns:form="sap.ui.layout.form" + displayBlock="true" + height="100%"> + <Shell> + <Page + title="cap2UI5 - Browser - Set the Tab Title" + showNavButton="${c.canGoBack}" + navButtonPress="${c.event("BACK")}"> + <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> + <form:SimpleForm title="Form Title" editable="true"> + <form:content> + <Label text="title"/> + <Input value="${c.bind("title")}"/> + <Button press="${c.event("SET_TITLE")}" text="Set Title"/> + </form:content> + </form:SimpleForm> + </Page> + </Shell> + </mvc:View>`); + } else if (c.eventName === "SET_TITLE") { + await followUpAction(c, "SET_TITLE", [this.title]); + } else if (c.eventName === "BACK") { + c.navBack(); + } + } +}); diff --git a/srv/apps/z2ui5_cl_smp_app_161.js b/srv/apps/z2ui5_cl_smp_app_161.js new file mode 100644 index 0000000..a76a937 --- /dev/null +++ b/srv/apps/z2ui5_cl_smp_app_161.js @@ -0,0 +1,82 @@ +/** + * Popup - Dialog inside a Dialog + * + * A dialog opened from inside a dialog, and what closing the inner one does to + * the stack. + * + * @keywords nested stack popup in popup second dialog + * @origin abap2UI5/samples src/z2ui5_cl_smp_app_161.clas.abap + */ +import { defineApp } from "cap2ui5"; + +const INFO = + "This sample opens a popup from a button and then chains to a second popup " + + "from within the first one."; + +defineApp("Z2UI5_CL_SMP_APP_161", class { + + main(c) { + if (c.isDisplay) { + c.view(` + <mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" displayBlock="true" height="100%"> + <Shell> + <Page + title="cap2UI5 - Popup - Dialog inside a Dialog" + showNavButton="${c.canGoBack}" + navButtonPress="${c.event("BACK")}"> + <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> + <Button press="${c.event("POPUP")}" text="Open Popup..."/> + </Page> + </Shell> + </mvc:View>`); + return; + } + + switch (c.eventName) { + case "POPUP": + c.popup(popup1(c)); + break; + case "GOTO_2ND": + c.popup(popup2(c)); + break; + case "BTN_OK_2ND": + c.popupClose(); + c.popup(popup1(c)); + break; + case "BTN_OK_1ND": + c.popupClose(); + break; + case "BACK": + c.navBack(); + break; + } + } +}); + +function popup1(c) { + return ` + <core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core"> + <Dialog afterClose="${c.event("BTN_OK_1ND")}"> + <content> + <Button press="${c.event("GOTO_2ND")}" text="Open 2nd popup"/> + </content> + <buttons> + <Button press="${c.event("BTN_OK_1ND")}" text="OK" type="Emphasized"/> + </buttons> + </Dialog> + </core:FragmentDefinition>`; +} + +function popup2(c) { + return ` + <core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core"> + <Dialog afterClose="${c.event("BTN_OK_2ND")}"> + <content> + <Label text="this is a second popup"/> + </content> + <buttons> + <Button press="${c.event("BTN_OK_2ND")}" text="GOTO 1ST POPUP" type="Emphasized"/> + </buttons> + </Dialog> + </core:FragmentDefinition>`; +} diff --git a/srv/apps/z2ui5_cl_smp_app_167.js b/srv/apps/z2ui5_cl_smp_app_167.js new file mode 100644 index 0000000..b887293 --- /dev/null +++ b/srv/apps/z2ui5_cl_smp_app_167.js @@ -0,0 +1,68 @@ +/** + * Event - Extra Arguments with the Event + * + * Sends extra arguments with an event (c.event( name, [args] )), so a handler + * knows which row, which value or which fixed payload it was called for. + * + * @keywords argument parameter payload event data fixed value eventArg + * @origin abap2UI5/samples src/z2ui5_cl_smp_app_167.clas.abap + */ +import { defineApp } from "cap2ui5"; + +const INFO = + "This sample shows how to pass extra arguments to an event via c.event( name, [args] ) - fixed " + + "values, model values, or client-side expressions - and read them in the backend with c.eventArg(1)."; + +const EVENTS = [ + "EVENT_FIX_VAL", + "EVENT_MODEL_VALUE", + "SOURCE_PROPERTY_TEXT", + "EVENT_PROPERTY_VALUE", + "PARENT_PROPERTY_ID", +]; + +defineApp("Z2UI5_CL_SMP_APP_167", class { + mv_value = "my value"; + + main(c) { + // An argument starting with $ is evaluated in the browser when the event + // fires: a binding, a property of the control that fired, an event parameter. + if (c.isDisplay) { + c.view(` + <mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" displayBlock="true" height="100%"> + <Shell> + <Page + title="cap2UI5 - Event - Extra Arguments with the Event" + showNavButton="${c.canGoBack}" + navButtonPress="${c.event("BACK")}"> + <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> + <Link + text="More information..." + target="_blank" + href="https://sdk.openui5.org/topic/b0fb4de7364f4bcbb053a99aa645affe"/> + <Button + press="${c.event("EVENT_FIX_VAL", ["FIX_VAL"])}" + text="EVENT_FIX_VAL"/> + <Input value="${c.bind("mv_value")}"/> + <Button + press="${c.event("EVENT_MODEL_VALUE", ["$" + c.bind("mv_value")])}" + text="EVENT_MODEL_VALUE"/> + <Button + press="${c.event("SOURCE_PROPERTY_TEXT", ["${$source>/text}"])}" + text="SOURCE_PROPERTY_TEXT"/> + <Input + description="make an input and press enter - " + submit="${c.event("EVENT_PROPERTY_VALUE", ["${$parameters>/value}"])}"/> + <Button + press="${c.event("PARENT_PROPERTY_ID", ["$event.oSource.oParent.sId"])}" + text="PARENT_PROPERTY_ID"/> + </Page> + </Shell> + </mvc:View>`); + } else if (EVENTS.includes(c.eventName)) { + c.messageBox(`backend event: ${c.eventArg(1)}`); + } else if (c.eventName === "BACK") { + c.navBack(); + } + } +}); diff --git a/srv/apps/z2ui5_cl_smp_app_488.js b/srv/apps/z2ui5_cl_smp_app_488.js new file mode 100644 index 0000000..3a6a1e0 --- /dev/null +++ b/srv/apps/z2ui5_cl_smp_app_488.js @@ -0,0 +1,87 @@ +/** + * Navigation - Return Data and Events to the Caller + * + * The way back carries data: the called app returns with an event name, and + * the caller reads what it entered from c.prevApp. + * + * Calls a second app (Z2UI5_CL_SMP_APP_489) with c.navTo( ). The called app + * comes back with c.navBack( { event } ), without knowing who called it. On + * return this app is shown again - c.isDisplay - and finds the event name in + * c.eventName and the called app's fields, as plain values, in c.prevApp. + * + * @keywords navTo navBack prevApp result return event payload + * @origin abap2UI5/samples src/z2ui5_cl_smp_app_488.clas.abap + */ +import { defineApp } from "cap2ui5"; + +const INFO = + "Calls a second app that returns via c.navBack( { event } ). On return this app reads the " + + "event from c.eventName and the entered data from c.prevApp in its c.isDisplay branch and " + + "shows them below."; + +defineApp("Z2UI5_CL_SMP_APP_488", class { + s_result = { product: "", quantity: "" }; + returned_event = ""; + + main(c) { + if (c.isFirstRun) { + c.view(view(c)); + } else if (c.isDisplay) { + this.returned_event = c.eventName; + if (c.eventName === "DATA_CONFIRMED") { + this.s_result = c.prevApp.s_result; + c.messageToast(`Returned event ${this.returned_event}, ` + + `product ${this.s_result.product}, quantity ${this.s_result.quantity}`); + } else if (c.eventName === "DATA_CANCELLED") { + this.s_result = { product: "", quantity: "" }; + c.messageToast("Returned event DATA_CANCELLED, no data passed"); + } + c.view(view(c)); + } else if (c.eventName === "CALL_APP") { + c.navTo("Z2UI5_CL_SMP_APP_489"); + } else if (c.eventName === "BACK") { + c.navBack(); + } + } +}); + +function view(c) { + // binding="{/S_RESULT}" makes the structure the form's context, so the + // fields inside bind its components relatively: {PRODUCT}, {QUANTITY} + return ` + <mvc:View + xmlns:mvc="sap.ui.core.mvc" + xmlns="sap.m" + xmlns:form="sap.ui.layout.form" + xmlns:layout="sap.ui.layout" + displayBlock="true" + height="100%"> + <Shell> + <Page + title="cap2UI5 - Navigation - Return Data and Events to the Caller" + showNavButton="${c.canGoBack}" + navButtonPress="${c.event("BACK")}"> + <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> + <layout:Grid defaultSpan="L6 M12 S12"> + <layout:content> + <form:SimpleForm + title="Result returned by the called app" + editable="true" + binding="${c.bind("s_result")}"> + <form:content> + <Label text="Open the input app"/> + <Button press="${c.event("CALL_APP")}" text="call app (c.navTo)" type="Emphasized"/> + <Label text="Returned event"/> + <Input enabled="false" value="${c.bind("returned_event")}"/> + <Label text="Returned product"/> + <Input enabled="false" value="{PRODUCT}"/> + <Label text="Returned quantity"/> + <Input enabled="false" value="{QUANTITY}"/> + </form:content> + </form:SimpleForm> + </layout:content> + </layout:Grid> + </Page> + </Shell> + </mvc:View>`; +} diff --git a/srv/apps/z2ui5_cl_smp_app_489.js b/srv/apps/z2ui5_cl_smp_app_489.js new file mode 100644 index 0000000..d25ae1b --- /dev/null +++ b/srv/apps/z2ui5_cl_smp_app_489.js @@ -0,0 +1,69 @@ +/** + * Navigation - Data Input App (called by Z2UI5_CL_SMP_APP_488) + * + * Returns to its caller with c.navBack( { event } ) - handing back an event + * name without knowing which app called it; the caller reads the entered data + * from c.prevApp. A hidden helper, never listed on its own in the overview. + * + * @origin abap2UI5/samples src/z2ui5_cl_smp_app_489.clas.abap + */ +import { defineApp } from "cap2ui5"; + +const INFO = + "Change the data and return: 'confirm' leaves with event DATA_CONFIRMED, the caller reads " + + "the entered data from c.prevApp; 'cancel' leaves with event DATA_CANCELLED. " + + "The nav-back button of the page leaves without an event."; + +defineApp("Z2UI5_CL_SMP_APP_489", class { + s_result = { product: "Notebook Basic 15", quantity: "2" }; + + main(c) { + if (c.isDisplay) { + c.view(view(c)); + } else if (c.eventName === "CONFIRM") { + c.navBack({ event: "DATA_CONFIRMED" }); + } else if (c.eventName === "CANCEL") { + c.navBack({ event: "DATA_CANCELLED" }); + } else if (c.eventName === "BACK") { + c.navBack(); + } + } +}); + +function view(c) { + return ` + <mvc:View + xmlns:mvc="sap.ui.core.mvc" + xmlns="sap.m" + xmlns:form="sap.ui.layout.form" + xmlns:layout="sap.ui.layout" + displayBlock="true" + height="100%"> + <Shell> + <Page + title="cap2UI5 - Navigation - Data Input App" + showNavButton="${c.canGoBack}" + navButtonPress="${c.event("BACK")}"> + <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> + <layout:Grid defaultSpan="L6 M12 S12"> + <layout:content> + <form:SimpleForm + title="Data returned to the caller" + editable="true" + binding="${c.bind("s_result")}"> + <form:content> + <Label text="Product"/> + <Input value="{PRODUCT}"/> + <Label text="Quantity"/> + <Input value="{QUANTITY}"/> + <Label text="Return to the caller"/> + <Button press="${c.event("CONFIRM")}" text="confirm (event, data in c.prevApp)" type="Emphasized"/> + <Button press="${c.event("CANCEL")}" text="cancel (event only)"/> + </form:content> + </form:SimpleForm> + </layout:content> + </layout:Grid> + </Page> + </Shell> + </mvc:View>`; +} diff --git a/srv/apps/z2ui5_cl_smp_app_493.js b/srv/apps/z2ui5_cl_smp_app_493.js new file mode 100644 index 0000000..bc97f64 --- /dev/null +++ b/srv/apps/z2ui5_cl_smp_app_493.js @@ -0,0 +1,38 @@ +/** + * Basics I - Hello World, the Smallest App + * + * The smallest app that runs: one class, one c.view( ), a Page with a title - + * the shape every other sample starts from. + * + * @keywords hello world smallest first app minimal start here template + * @origin abap2UI5/samples src/z2ui5_cl_smp_app_493.clas.abap + */ +import { defineApp } from "cap2ui5"; + +const INFO = + "The whole app is what you see below: a class handed to defineApp( ), one main( ) method, " + + "a view written as XML and handed to c.view( ). cap2UI5 calls main( ) on every roundtrip - " + + "here only the display matters, which is what c.isDisplay asks: true on the first start and " + + "whenever the app is shown again. Copy this file as the starting point for your own app."; + +defineApp("Z2UI5_CL_SMP_APP_493", class { + + main(c) { + if (c.isDisplay) { + c.view(` + <mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" displayBlock="true" height="100%"> + <Shell> + <Page + title="cap2UI5 - Basics I - Hello World, the Smallest App" + showNavButton="${c.canGoBack}" + navButtonPress="${c.event("BACK")}"> + <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> + <Title text="Hello World" class="sapUiSmallMargin" level="H2"/> + </Page> + </Shell> + </mvc:View>`); + } else if (c.eventName === "BACK") { + c.navBack(); + } + } +}); diff --git a/srv/apps/z2ui5_cl_smp_app_494.js b/srv/apps/z2ui5_cl_smp_app_494.js new file mode 100644 index 0000000..d9043f0 --- /dev/null +++ b/srv/apps/z2ui5_cl_smp_app_494.js @@ -0,0 +1,60 @@ +/** + * Basics II - Data Binding: Input and Button + * + * Binds a field of the class to an Input with c.bind( ), so what the user types + * is in the field on the next roundtrip - a Text shows it back and a MessageBox + * confirms the roundtrip. + * + * @keywords binding bind model field value input button roundtrip messagebox + * @origin abap2UI5/samples src/z2ui5_cl_smp_app_494.clas.abap + */ +import { defineApp } from "cap2ui5"; + +const INFO = + "c.bind('name') connects the field NAME with the input below. Type a name and leave the " + + "field: the text next to it changes without any backend code, because both are bound to the " + + "same field. Press Greet and the backend reads NAME - already filled in, no event argument " + + "needed -, writes GREETING back into the view and confirms the roundtrip with a MessageBox."; + +defineApp("Z2UI5_CL_SMP_APP_494", class { + name = "World"; + greeting = ""; + + main(c) { + if (c.isDisplay) { + c.view(` + <mvc:View + xmlns:mvc="sap.ui.core.mvc" + xmlns="sap.m" + xmlns:form="sap.ui.layout.form" + displayBlock="true" + height="100%"> + <Shell> + <Page + title="cap2UI5 - Basics II - Data Binding: Input and Button" + showNavButton="${c.canGoBack}" + navButtonPress="${c.event("BACK")}"> + <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> + <form:SimpleForm title="Data Binding" editable="true"> + <form:content> + <Label text="your name"/> + <Input value="${c.bind("name")}"/> + <Label text="bound to the same field"/> + <Text text="${c.bind("name")}"/> + <Label text="written by the backend"/> + <Text text="${c.bind("greeting")}"/> + <Button press="${c.event("GREET")}" text="Greet"/> + </form:content> + </form:SimpleForm> + </Page> + </Shell> + </mvc:View>`); + } else if (c.eventName === "GREET") { + this.greeting = `Hello ${this.name}!`; + c.messageBox( + `Roundtrip done: the backend read NAME = '${this.name}' and wrote GREETING back into the view.`); + } else if (c.eventName === "BACK") { + c.navBack(); + } + } +}); diff --git a/srv/apps/z2ui5_cl_smp_app_495.js b/srv/apps/z2ui5_cl_smp_app_495.js new file mode 100644 index 0000000..c92081f --- /dev/null +++ b/srv/apps/z2ui5_cl_smp_app_495.js @@ -0,0 +1,66 @@ +/** + * Basics III - Lifecycle: First Run, Event, Display + * + * The three questions main( ) asks - c.isFirstRun, c.eventName, c.isDisplay - + * as one dispatcher, showing what survives a roundtrip and what a navigation + * does to it. + * + * @keywords lifecycle roundtrip main dispatcher state draft isFirstRun isDisplay eventName + * @origin abap2UI5/samples src/z2ui5_cl_smp_app_495.clas.abap + */ +import { defineApp, t } from "cap2ui5"; + +const INFO = + "main( ) runs on every roundtrip - c.isFirstRun, c.isDisplay and c.eventName tell it what " + + "the roundtrip is about. The list logs each call, and it survives them all: every field of " + + "the class is kept in the draft between the roundtrips, so the app keeps its state without " + + "a table of its own. Press Log - only the model is pushed, the view is not rebuilt. Call the " + + "sub-app and come back with its back button - that is the roundtrip c.isDisplay answers " + + "without c.isFirstRun."; + +defineApp("Z2UI5_CL_SMP_APP_495", class { + t_log = t.table({ no: "", check: "" }); + + main(c) { + // `this` reads plain copies: a changed table is written back by assigning it + const log = (check) => { + this.t_log = [...this.t_log, { no: String(this.t_log.length + 1), check }]; + }; + + if (c.isFirstRun) { + log("c.isFirstRun - the very first call, nothing exists yet"); + c.view(view(c)); + } else if (c.isDisplay) { + log("c.isDisplay - the sub-app returned, re-display the view"); + c.view(view(c)); + } else if (c.eventName === "LOG") { + log("c.eventName - a button was pressed, the view stays as it is"); + } else if (c.eventName === "CALL") { + log("c.eventName - calling Basics I as a sub-app"); + c.navTo("Z2UI5_CL_SMP_APP_493"); + } else if (c.eventName === "BACK") { + c.navBack(); + } + } +}); + +function view(c) { + return ` + <mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" displayBlock="true" height="100%"> + <Shell> + <Page + title="cap2UI5 - Basics III - Lifecycle: First Run, Event, Display" + showNavButton="${c.canGoBack}" + navButtonPress="${c.event("BACK")}"> + <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> + <HBox class="sapUiSmallMargin"> + <Button press="${c.event("LOG")}" text="Log an Event"/> + <Button press="${c.event("CALL")}" text="Call a Sub-App" class="sapUiTinyMarginBegin"/> + </HBox> + <List headerText="Calls of main( )" items="${c.bind("t_log")}" class="sapUiSmallMargin"> + <StandardListItem title="{CHECK}" description="call {NO}"/> + </List> + </Page> + </Shell> + </mvc:View>`; +} diff --git a/test/samples.test.mjs b/test/samples.test.mjs new file mode 100644 index 0000000..0a03946 --- /dev/null +++ b/test/samples.test.mjs @@ -0,0 +1,157 @@ +// Every sample, driven over the wire against the published cap2ui5 package: +// it starts, its view is well-formed XML, and its events do what the sample +// says they do - including the navigation round trips between two apps. +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { test } from "node:test"; +import { actions, destroys, post, ROOT, serve, slot, wellFormed } from "./server.mjs"; + +const s = serve(); +const P = (o) => post(s.url, o); +const ok = (r) => assert.equal(r.status, 200, r.text.slice(0, 500)); + +const APPS = fs.readdirSync(path.join(ROOT, "srv/apps")) + .filter((f) => /^z2ui5_cl_smp_app_\d+\.js$/.test(f)) + .map((f) => f.replace(/\.js$/, "").toUpperCase()); + +test("every sample starts, and its view is well-formed XML titled cap2UI5", async () => { + assert.ok(APPS.length > 0); + for (const app of APPS) { + const r = await P({ app }); + ok(r); + const xml = slot(r, "MAIN"); + assert.ok(xml, `${app}: no MAIN view on the start`); + assert.equal(wellFormed(xml), true, `${app}: ${wellFormed(xml)}`); + assert.match(xml, /title="cap2UI5 - /, `${app}: the page title`); + assert.match(xml, /showNavButton="false"/, `${app}: started directly, there is nothing to go back to`); + } +}); + +test("493 Hello World: one view, no state", async () => { + const r = await P({ app: "Z2UI5_CL_SMP_APP_493" }); + ok(r); + assert.match(slot(r, "MAIN"), /<Title text="Hello World"/); + assert.equal(r.json.MODEL, undefined, "an app without fields sends no model"); +}); + +test("494 Data Binding: the typed name reaches the backend, the greeting comes back", async () => { + const start = await P({ app: "Z2UI5_CL_SMP_APP_494" }); + assert.deepEqual(start.json.MODEL, { NAME: "World", GREETING: "" }); + assert.match(slot(start, "MAIN"), /<Input value="\{\/NAME\}"\/>/); + + const greet = await P({ app: "Z2UI5_CL_SMP_APP_494", id: start.id, event: "GREET", model: { NAME: "Ada" } }); + ok(greet); + assert.deepEqual(greet.json.MODEL, { NAME: "Ada", GREETING: "Hello Ada!" }); + assert.deepEqual(actions(greet)[0].slice(0, 3), + ["MESSAGE_BOX", "show", "Roundtrip done: the backend read NAME = 'Ada' and wrote GREETING back into the view."]); +}); + +test("495 Lifecycle: first run, event, and the return from a called app", async () => { + const APP = "Z2UI5_CL_SMP_APP_495"; + const start = await P({ app: APP }); + assert.deepEqual(start.json.MODEL.T_LOG.map((r) => r.NO), ["1"]); + + const log = await P({ app: APP, id: start.id, event: "LOG" }); + assert.equal(slot(log, "MAIN"), undefined, "an event does not rebuild the view"); + assert.equal(log.json.MODEL.T_LOG.length, 2, "only the model is pushed"); + + const call = await P({ app: APP, id: log.id, event: "CALL" }); + assert.equal(call.app, "Z2UI5_CL_SMP_APP_493", "c.navTo( ) hands the screen to Basics I"); + assert.match(slot(call, "MAIN"), /Basics I - Hello World/); + assert.match(slot(call, "MAIN"), /showNavButton="true"/, "the called app can go back"); + + const back = await P({ app: call.app, id: call.id, event: "BACK" }); + assert.equal(back.app, APP, "c.navBack( ) returns to the caller"); + assert.match(slot(back, "MAIN") ?? "", /Basics III/, "the caller renders again (c.isDisplay)"); + assert.deepEqual(back.json.MODEL.T_LOG.map((r) => r.CHECK.split(" - ")[0]), + ["c.isFirstRun", "c.eventName", "c.eventName", "c.isDisplay"], + "the log survived the navigation, and the return is c.isDisplay without c.isFirstRun"); +}); + +test("011 Editable Table: edit mode, delete the selected rows, add a row", async () => { + const APP = "Z2UI5_CL_SMP_APP_011"; + const start = await P({ app: APP }); + const rows = start.json.MODEL.T_TAB; + assert.equal(rows.length, 6); + assert.deepEqual(rows[0], { SELKZ: false, TITLE: "entry 01", VALUE: "red", DESCR: "this is a description", + ICON: "", INFO: "completed", EDITABLE: false, CHECKBOX: true }); + + const edit = await P({ app: APP, id: start.id, event: "BUTTON_EDIT" }); + assert.ok(edit.json.MODEL.T_TAB.every((r) => r.EDITABLE), "every row is editable"); + + // the user selects rows 2 and 4 - the frontend sends the table back + const selected = edit.json.MODEL.T_TAB.map((r, i) => ({ ...r, SELKZ: i === 1 || i === 3 })); + const del = await P({ app: APP, id: edit.id, event: "BUTTON_DELETE", model: { T_TAB: selected } }); + ok(del); + assert.deepEqual(del.json.MODEL.T_TAB.map((r) => r.TITLE), ["entry 01", "entry 03", "entry 05", ""]); + + const add = await P({ app: APP, id: del.id, event: "BUTTON_ADD" }); + assert.equal(add.json.MODEL.T_TAB.length, 5); + assert.equal(add.json.MODEL.T_TAB.at(-1).EDITABLE, true, "a new row follows the edit mode"); +}); + +test("167 Event Arguments: fixed values and client-side expressions travel with the event", async () => { + const APP = "Z2UI5_CL_SMP_APP_167"; + const start = await P({ app: APP }); + // > is legal in an attribute; whether it arrives escaped depends on the cap2ui5 release + const press = slot(start, "MAIN").replaceAll(">", ">").match(/(press|submit)="[^"]*"/g); + assert.deepEqual(press, [ + `press=".eB(['EVENT_FIX_VAL'], 'FIX_VAL')"`, + `press=".eB(['EVENT_MODEL_VALUE'], \${/MV_VALUE})"`, + `press=".eB(['SOURCE_PROPERTY_TEXT'], \${$source>/text})"`, + `submit=".eB(['EVENT_PROPERTY_VALUE'], \${$parameters>/value})"`, + `press=".eB(['PARENT_PROPERTY_ID'], $event.oSource.oParent.sId)"`, + ]); + + const fix = await P({ app: APP, id: start.id, event: "EVENT_FIX_VAL", args: ["FIX_VAL"] }); + assert.deepEqual(actions(fix)[0].slice(0, 3), ["MESSAGE_BOX", "show", "backend event: FIX_VAL"]); +}); + +test("161 Dialog inside a Dialog: open, chain to the second, back to the first, close", async () => { + const APP = "Z2UI5_CL_SMP_APP_161"; + const start = await P({ app: APP }); + + const open = await P({ app: APP, id: start.id, event: "POPUP" }); + assert.match(slot(open, "POPUP") ?? "", /Open 2nd popup/); + assert.equal(wellFormed(slot(open, "POPUP")), true); + + const second = await P({ app: APP, id: open.id, event: "GOTO_2ND" }); + assert.match(slot(second, "POPUP") ?? "", /this is a second popup/); + + const first = await P({ app: APP, id: second.id, event: "BTN_OK_2ND" }); + assert.match(slot(first, "POPUP") ?? "", /Open 2nd popup/, "closing the second shows the first again"); + + const close = await P({ app: APP, id: first.id, event: "BTN_OK_1ND" }); + assert.ok(destroys(close, "POPUP"), "the popup is closed"); +}); + +test("488/489 Navigation: the called app returns an event, the caller reads its data", async () => { + const start = await P({ app: "Z2UI5_CL_SMP_APP_488" }); + assert.deepEqual(start.json.MODEL, { S_RESULT: { PRODUCT: "", QUANTITY: "" }, RETURNED_EVENT: "" }); + + const called = await P({ app: "Z2UI5_CL_SMP_APP_488", id: start.id, event: "CALL_APP" }); + assert.equal(called.app, "Z2UI5_CL_SMP_APP_489"); + assert.deepEqual(called.json.MODEL, { S_RESULT: { PRODUCT: "Notebook Basic 15", QUANTITY: "2" } }); + + // the user edits both fields - bound relatively inside binding="{/S_RESULT}" + const confirm = await P({ app: called.app, id: called.id, event: "CONFIRM", + model: { S_RESULT: { PRODUCT: "Notebook Pro 17", QUANTITY: "7" } } }); + assert.equal(confirm.app, "Z2UI5_CL_SMP_APP_488"); + assert.deepEqual(confirm.json.MODEL, + { S_RESULT: { PRODUCT: "Notebook Pro 17", QUANTITY: "7" }, RETURNED_EVENT: "DATA_CONFIRMED" }); + assert.match(slot(confirm, "MAIN") ?? "", /Result returned by the called app/, "the caller renders again"); + + const again = await P({ app: "Z2UI5_CL_SMP_APP_488", id: confirm.id, event: "CALL_APP" }); + const cancel = await P({ app: again.app, id: again.id, event: "CANCEL" }); + assert.deepEqual(cancel.json.MODEL, + { S_RESULT: { PRODUCT: "", QUANTITY: "" }, RETURNED_EVENT: "DATA_CANCELLED" }); +}); + +test("125 Tab Title: the front-end action goes out through c.raw", async () => { + const APP = "Z2UI5_CL_SMP_APP_125"; + const start = await P({ app: APP }); + const set = await P({ app: APP, id: start.id, event: "SET_TITLE", model: { TITLE: "Invoices" } }); + ok(set); + assert.deepEqual(actions(set), [["SET_TITLE", "Invoices"]]); +}); diff --git a/test/server.mjs b/test/server.mjs new file mode 100644 index 0000000..0ba6d4e --- /dev/null +++ b/test/server.mjs @@ -0,0 +1,59 @@ +// The samples project served in this process by cds.test, and the abap2UI5 +// wire to talk to it: the tests play the frontend's part, one POST per +// roundtrip, the way cap2UI5's own suite does (examples/bookshop/test). +import cds from "@sap/cds"; +import { XMLValidator } from "fast-xml-parser"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +export const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); + +/** Serve the project for the calling test file (cds.test registers the hooks). */ +export function serve() { + const t = cds.test(ROOT); + return { get url() { return `${t.url}/rest/root/z2ui5`; } }; +} + +/** + * One roundtrip. No `id` starts `app`; with `id` it answers an event on the + * draft the previous response named. `model` is what the frontend sends back: + * the bound values the user changed. + */ +export async function post(url, { app, id = "", event = "", args = [], model = {} } = {}) { + const body = { value: { S_FRONT: { + ID: id, APP: app, EVENT: event, T_EVENT_ARG: args, + ORIGIN: "http://127.0.0.1", PATHNAME: "/rest/root/z2ui5", + SEARCH: id ? "" : `?app_start=${app}`, HASH: "", CONFIG: {} }, + XX: {}, MODEL: model } }; + const r = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: "Basic " + Buffer.from("alice:").toString("base64"), // CAP's mocked user + }, + body: JSON.stringify(body), + }); + const text = await r.text(); + let json = null; + try { json = JSON.parse(text); } catch { /* an error page, not a wire envelope */ } + return { status: r.status, text, json, id: json?.S_FRONT?.ID, app: json?.S_FRONT?.APP }; +} + +/** Every action of a response, system and custom. */ +export const actions = (r) => [ + ...(r.json?.S_FRONT?.S_ACTION?.T_SYSTEM ?? []), + ...(r.json?.S_FRONT?.S_ACTION?.T_CUSTOM ?? []), +]; + +/** The XML a response displays into a view slot (MAIN, POPUP, NEST, …), or undefined. */ +export const slot = (r, name) => actions(r) + .find((a) => a[0] === "VIEW_SLOTS" && a[1] === "display" && a[2] === name)?.[3]; + +export const destroys = (r, name) => actions(r) + .some((a) => a[0] === "VIEW_SLOTS" && a[1] === "destroy" && a[2] === name); + +/** true, or the parser's complaint - a template literal typo shows up here, not only in the browser */ +export const wellFormed = (xml) => { + const v = XMLValidator.validate(xml); + return v === true ? true : `${v.err.code} at ${v.err.line}:${v.err.col} - ${v.err.msg}`; +}; From 0cef228ea7cfe15a07e784ab77ecd391325fa0ee Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Mon, 28 Sep 2026 11:08:18 +0000 Subject: [PATCH 2/5] Build the example views with ViewBuilder, on the extended facade The views are now built call for call as the ABAP originals build them, with ViewBuilder - abap2UI5's own view builder - and the apps keep the originals' dispatcher and methods. That makes a port a line-by-line translation, and its view the same tree as the original's. The facade gaps the first cut worked around are closed in cap2UI5 (branch claude/practical-archimedes-y329hi, the next release 0.2.0), so the ports use them instead: - the back button is c.eventNavBack( ), no BACK branch in every main( ) - 125 sets the tab title with c.followUpAction( ), not through c.raw - 011 binds its table with the bare path, as the original does - 488/489 hand the result back with navBack( { event, data } ) and read it as c.eventData - r_data, as in the original - and bind the structure components by name, c.bind( "s_result.product" ) - helpers are methods again: cap2UI5 binds them to what main( ) sees cap2ui5 0.2.0 is not on npm yet, so package.json names it and the lockfile goes until it is; the README says how to install the plugin from a checkout meanwhile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XtDApnfmAncwrmW6fE4tCS --- .github/workflows/test.yml | 5 +- README.md | 62 +- package-lock.json | 1289 ------------------------------ package.json | 4 +- srv/apps/z2ui5_cl_smp_app_011.js | 168 ++-- srv/apps/z2ui5_cl_smp_app_125.js | 95 +-- srv/apps/z2ui5_cl_smp_app_161.js | 156 ++-- srv/apps/z2ui5_cl_smp_app_167.js | 121 +-- srv/apps/z2ui5_cl_smp_app_488.js | 172 ++-- srv/apps/z2ui5_cl_smp_app_489.js | 127 +-- srv/apps/z2ui5_cl_smp_app_493.js | 48 +- srv/apps/z2ui5_cl_smp_app_494.js | 86 +- srv/apps/z2ui5_cl_smp_app_495.js | 96 ++- test/samples.test.mjs | 27 +- 14 files changed, 701 insertions(+), 1755 deletions(-) delete mode 100644 package-lock.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 59b43a1..a06c001 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,6 +13,7 @@ jobs: - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version-file: .nvmrc - cache: npm - - run: npm ci + # npm install, and no npm cache, until cap2ui5 0.2.0 is on npm and a + # package-lock.json can pin it - then `cache: npm` and npm ci again + - run: npm install - run: npm test diff --git a/README.md b/README.md index 9e24767..1c938ab 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,29 @@ The [abap2UI5 samples](https://github.com/abap2UI5/samples) as [cap2UI5](https://github.com/cap2UI5/cap2UI5) apps: every sample a plain -JavaScript class in `srv/apps/`, in an ordinary CAP project. +JavaScript class in `srv/apps/`, in an ordinary CAP project, its view built +with `ViewBuilder` - abap2UI5's own view builder - call for call as the ABAP +original builds it. > [!NOTE] > **Status: first examples.** Nine of the 129 apps are ported, to agree on the -> conventions below before the rest follows. +> conventions below before the rest follows. They need **cap2ui5 0.2.0**, +> which is not on npm yet (`ViewBuilder` and the facade members the samples +> use are in its `Unreleased` changelog). Until it is, install the plugin from +> a cap2UI5 checkout: +> +> ```bash +> (cd ../cap2UI5 && scripts/assemble-runtime.sh --package 1.145.0 && npm install \ +> && npm pack --workspace plugin --pack-destination /tmp) +> npm install && npm install --no-save /tmp/cap2ui5-*.tgz +> ``` ## Run ```bash npm install npm run watch # cds watch - prints the address of every sample -npm test # every sample driven over the wire against the published cap2ui5 +npm test # every sample driven over the wire ``` Log in as `alice` with an empty password (CAP's mocked development user), then @@ -40,6 +51,12 @@ becomes `srv/apps/z2ui5_cl_smp_app_493.js`, registered as that calls another one keeps calling it by its name, and `@origin` in the file header points back at the original. +**The same structure.** The dispatcher is the original's `IF` / `ELSEIF` +chain, its methods keep their roles (`view_display` becomes `viewDisplay(c)`), +and the view is the original's chain with the same verbs, the same tree and +the same layout: one call per line, four spaces per tree level, `end()` in +the column of the `ele()` it closes. + | abap2UI5 | cap2UI5 | |---|---| | `DATA name TYPE string.` | `name = "";` - the initial value is the type | @@ -47,43 +64,30 @@ file header points back at the original. | `client->check_on_init( )` | `c.isFirstRun` - seed state here | | `client->check_on_navigated( )` | `c.isDisplay` - render here | | `client->check_on_event( 'X' )`, `get_event( )` | `c.eventName === "X"` | -| `client->_bind( name )` | `c.bind("name")` - the field's **name** | +| `z2ui5_cl_ui5_view_builder=>factory( )->ele( n = … ns = … )` | `ViewBuilder.factory().ele(n, ns)` | +| `->a( n = … v = … )` / `b = …` / `t = …` | `.a(n, "…")` / `.a(n, true)` / `.a(n, { t: … })` | +| `client->view_display( view->stringify( ) )` | `c.view(view)` | +| `client->_bind( name )`, `_bind( s_order-customer )` | `c.bind("name")`, `c.bind("s_order.customer")` | +| `client->_bind( val = t_tab path = abap_true )` | `c.bind("t_tab", { path: true })` | | `client->_event( val = 'X' t_arg = … )` | `c.event("X", [ … ])` | | `client->get_event_arg( 1 )` | `c.eventArg(1)` | -| `z2ui5_cl_ui5_view_builder` chain | an XML template literal | -| `client->view_display( )`, `popup_display( )` | `c.view( )`, `c.popup( )` | | `client->check_app_prev_stack( )` | `c.canGoBack` | -| `client->_event_nav_app_leave( )` | `c.event("BACK")`, answered with `c.navBack( )` | +| `client->_event_nav_app_leave( )` | `c.eventNavBack()` | +| `client->follow_up_action( val = cs_event-set_title … )` | `c.followUpAction("set_title", [ … ])` | | `client->nav_app_call( NEW z…( ) )` | `c.navTo("Z…")` | +| `client->nav_app_leave( event = … r_data = … )` | `c.navBack({ event, data })` | +| `client->get( )-r_event_data` | `c.eventData` | | `client->get_app_prev( )` | `c.prevApp` - the other app's fields as plain values | The page title says `cap2UI5 - …`, and the explaining texts name the JavaScript API, not the ABAP one. -### Four things that differ from ABAP +### What differs from ABAP 1. **`this` reads plain copies.** Assign to write: `this.t_tab = [...this.t_tab, row]`. A `push` on the copy is lost. -2. **A table field starts empty**, whatever rows its initializer lists: - declare it with `t.table({ …one row… })` and seed it in `c.isFirstRun`. - Scalars and structures keep their initial values. -3. **Helpers are functions, not methods.** In `cap2ui5` 0.1.0 a method called - from `main( )` sees the framework's ABAP boxes instead of the values, so - the view is built by a module function that gets `c` and the app: - `c.view(view(c, this))`. -4. **The render branch comes first.** A called app that returns arrives with +2. **Every field is part of the model.** ABAP keeps a `PROTECTED` attribute out + of it; a JavaScript field with an initial value is always bound. +3. **The render branch comes first.** A called app that returns arrives with `c.isDisplay` *and* its event name set; `if (c.isDisplay) … else if (c.eventName === …)` keeps the two apart. - -### What the facade does not have yet - -`c` covers what 39 of the 129 samples use; six more need only a small -adaptation (a structure's components bound relatively inside -`binding="${c.bind("s")}"`, as in `Z2UI5_CL_SMP_APP_488`). The rest need -parts of `z2ui5_if_client` the facade does not wrap yet - above all -`follow_up_action( )` (47 samples), then `popover_display( )`, `get( )`, and -the options of `_bind( )`, `_event( )` and `message_box_display( )`. - -Until it does, every one of them is reachable through `c.raw`, the transpiled -`z2ui5_if_client` - asynchronous and with ABAP-typed arguments, as -`Z2UI5_CL_SMP_APP_125` shows. diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 680d627..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1289 +0,0 @@ -{ - "name": "cap2ui5-samples", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "cap2ui5-samples", - "dependencies": { - "@cap-js/sqlite": "^3", - "@sap/cds": "^10", - "cap2ui5": "0.1.0" - }, - "devDependencies": { - "@cap-js/cds-test": "^1.0.2", - "fast-xml-parser": "^5.11.1" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@abap2ui5/node-runtime": { - "version": "1.145.0", - "resolved": "https://registry.npmjs.org/@abap2ui5/node-runtime/-/node-runtime-1.145.0.tgz", - "integrity": "sha512-pB3Q0T6nuTE3mI0kvLPCTtMCJIxIx16nfcm3/L0epAs7W7pQGxL79SMWYm3DD5xPrhlRgmTeEbNBjXVpfYsS+w==", - "license": "MIT", - "dependencies": { - "@abaplint/database-sqlite": "2.13.83", - "@abaplint/runtime": "2.13.91" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "express": "^5.0.0" - }, - "peerDependenciesMeta": { - "express": { - "optional": true - } - } - }, - "node_modules/@abaplint/database-sqlite": { - "version": "2.13.83", - "resolved": "https://registry.npmjs.org/@abaplint/database-sqlite/-/database-sqlite-2.13.83.tgz", - "integrity": "sha512-L5+IPwq4rcPAHPOSeJYUukqbD7SY26VfgSsLqZL9m8H2BGB02MfiMqlzmZ4m+Vpggy6zzjLjFH/CKwoBwx4ocw==", - "license": "MIT", - "dependencies": { - "sql.js": "^1.14.2" - } - }, - "node_modules/@abaplint/runtime": { - "version": "2.13.91", - "resolved": "https://registry.npmjs.org/@abaplint/runtime/-/runtime-2.13.91.tgz", - "integrity": "sha512-Ux3BPc1MVTPsZggUgBs+lJm3xuPlPDydSr9RLMKEHxgq5BliNs959TkSpRhJjg/WUAmuR2WPgoeU+nQyElgpYQ==", - "license": "MIT", - "dependencies": { - "temporal-polyfill": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/larshp" - } - }, - "node_modules/@cap-js/cds-test": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@cap-js/cds-test/-/cds-test-1.0.2.tgz", - "integrity": "sha512-autSQg1l7nvHmBwxan0inQ71Cw3cqQdBvON/RFarhbwRJSeC6oC7nQ6d1RiVxCYYkeM0wqkmfn8WGQ9K89dWXQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "cds-test": "bin/chest.js", - "chest": "bin/chest.js" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "@sap/cds": ">=9.8", - "chai": "^6", - "chai-as-promised": "^8" - } - }, - "node_modules/@cap-js/db-service": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@cap-js/db-service/-/db-service-3.1.1.tgz", - "integrity": "sha512-N1vgMkiDR0IrwwCGFr1e4CcSUsoHFsxiryMC9EYpUPR7mQnw6kfvWzYEwB3/9QXp1nmauV2RoETN6WFqBdwXZg==", - "license": "Apache-2.0", - "peerDependencies": { - "@sap/cds": "^10", - "generic-pool": "^3.9.0" - }, - "peerDependenciesMeta": { - "generic-pool": { - "optional": true - } - } - }, - "node_modules/@cap-js/sqlite": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@cap-js/sqlite/-/sqlite-3.1.1.tgz", - "integrity": "sha512-FHkenWLi7p8VMNg9Sa1CVqa7HZhAiY0RQkANfjP+torubFDd4fiTH98bLyLZ3Xo+vjhJYsE5Ztta8rmnUBzwPA==", - "license": "Apache-2.0", - "dependencies": { - "@cap-js/db-service": "^3.1.1" - }, - "peerDependencies": { - "@sap/cds": "^10", - "better-sqlite3": "^12.0.0 || ^13.0.0", - "sql.js": "^1.13.0" - }, - "peerDependenciesMeta": { - "better-sqlite3": { - "optional": true - }, - "sql.js": { - "optional": true - } - } - }, - "node_modules/@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", - "license": "MIT", - "peer": true, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/@nodable/entities": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.0.0.tgz", - "integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/nodable" - } - ], - "license": "MIT" - }, - "node_modules/@sap/cds": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@sap/cds/-/cds-10.1.1.tgz", - "integrity": "sha512-0F2R0BqQS6q463WlDlkSqPaV8WK/YXDCKgVmAWcvYSRwcbJUFDBK1X4RF5obHFWjqOO1rXsf9Jh105inHXU3/g==", - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "@sap/cds-compiler": "^7", - "@sap/cds-fiori": "^2", - "express": "^4.22.1 || ^5", - "yaml": "^2" - }, - "bin": { - "cds-deploy": "bin/deploy.js", - "cds-serve": "bin/serve.js" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "@eslint/js": "^9 || ^10", - "tar": "^7.5.6" - }, - "peerDependenciesMeta": { - "tar": { - "optional": true - } - } - }, - "node_modules/@sap/cds-compiler": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@sap/cds-compiler/-/cds-compiler-7.1.1.tgz", - "integrity": "sha512-ep7rzRJgZtDBs9vVcXyxLVy/q4p+K+a1Q+yq1qn1mb7dYHyH7m88K+Pn761Ofnf7HXXbTo/FKpM1z/kPTbwy3w==", - "license": "SEE LICENSE IN LICENSE", - "bin": { - "cdsc": "bin/cdsc.js", - "cdshi": "bin/cdshi.js", - "cdsse": "bin/cdsse.js" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@sap/cds-fiori": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sap/cds-fiori/-/cds-fiori-2.3.0.tgz", - "integrity": "sha512-6oWov+DSpFrSTgxXR0dZhak6aZ/IVRZvaHERMi0EgSTzIJdlvZlpw3Kf18ePMcTrRrtEXwD4RIjKt8pbs0g2Hg==", - "license": "SEE LICENSE IN LICENSE", - "peerDependencies": { - "@sap/cds": ">=8" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/anynum": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", - "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/cap2ui5": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cap2ui5/-/cap2ui5-0.1.0.tgz", - "integrity": "sha512-ZJYLzg7ezxkwcMQGMEJmToeTY78KpECFYne30JsktvmWKpLnBRVJGrx5k0tB4h52DYfTsp8MopfUfOHSPnjAyQ==", - "license": "MIT", - "dependencies": { - "@abap2ui5/node-runtime": "1.145.0", - "express": "^5" - }, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "@sap/cds": ">=9" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/chai-as-promised": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-8.0.2.tgz", - "integrity": "sha512-1GadL+sEJVLzDjcawPM4kjfnL+p/9vrxiEUonowKOAzvVg0PixJUdtuDzdkDeQhK3zfOE76GqGkZIQ7/Adcrqw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "check-error": "^2.1.1" - }, - "peerDependencies": { - "chai": ">= 2.1.2 < 7" - } - }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 16" - } - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/fast-xml-builder": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz", - "integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "path-expression-matcher": "^1.6.2", - "xml-naming": "^0.3.0" - } - }, - "node_modules/fast-xml-parser": { - "version": "5.11.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.1.tgz", - "integrity": "sha512-TBw6K/fxoQGGjCmZDw9w/ZwP3uDcnTM4YH/g+PFRWr8sbe5idXtxNN6vITh4+1ruCZaho6uBFurElsA7F0zzgw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "@nodable/entities": "^3.0.0", - "fast-xml-builder": "^1.2.0", - "is-unsafe": "^2.0.0", - "path-expression-matcher": "^1.6.2", - "strnum": "^2.4.2", - "xml-naming": "^0.3.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/is-unsafe": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz", - "integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", - "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", - "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", - "license": "MIT", - "dependencies": { - "content-type": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/negotiator/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-expression-matcher": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", - "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", - "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/qs": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", - "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", - "license": "BSD-3-Clause", - "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", - "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sql.js": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.2.tgz", - "integrity": "sha512-3ZGPovObMFrdw79zrUHbfdE/DLIsy8jdNdssmMSQuRAymedU6q84asPt0kgiqrdMYlPegDItiIMfmIXzZnYFcw==", - "license": "MIT" - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/strnum": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.2.tgz", - "integrity": "sha512-rDG3Ah4TV0k1hWvLSzkZtMmLN9+eS+h3knq4MP6A42Y3Yh5qGNnOUs1jJkoSr8FG5dsL28c7KgkIBzSEykqtuw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "anynum": "^1.0.1" - } - }, - "node_modules/temporal-polyfill": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-1.0.5.tgz", - "integrity": "sha512-+H/mmY74i6wXCMcjIhGuSnODcyZnc4eois2myhOyX0UqRAKNXQY5m9iB1en5jQ/n4SXtjLXo1ElBI5bmn8tZwQ==", - "license": "MIT", - "dependencies": { - "temporal-spec": "1.0.1", - "temporal-utils": "1.0.3" - } - }, - "node_modules/temporal-spec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-1.0.1.tgz", - "integrity": "sha512-wxVoanmDeavXie1vu2JaQ3WIc3JZnWAOYFBsJyATaVsXsycKYUflGsyBmrRSnoCpZJpwPyr38VpgSUlQ8CbFxg==", - "license": "Apache-2.0" - }, - "node_modules/temporal-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/temporal-utils/-/temporal-utils-1.0.3.tgz", - "integrity": "sha512-9jbTSX3HvkOWvDpNpFzwX/d1mkCrgQgwLmVTOEiuocZGKHsrQJtttGZFmybJ2zYP5zCSZuU5ZyeS8+eOEPABiQ==", - "license": "MIT" - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/xml-naming": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", - "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/yaml": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", - "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - } - } -} diff --git a/package.json b/package.json index 929b475..16ba91c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cap2ui5-samples", "private": true, - "description": "The abap2UI5 samples as cap2UI5 apps: plain JavaScript classes in a CAP project.", + "description": "The abap2UI5 samples as cap2UI5 apps: plain JavaScript classes in a CAP project, views built with ViewBuilder.", "type": "module", "engines": { "node": ">=22" @@ -9,7 +9,7 @@ "dependencies": { "@cap-js/sqlite": "^3", "@sap/cds": "^10", - "cap2ui5": "0.1.0" + "cap2ui5": "^0.2.0" }, "devDependencies": { "@cap-js/cds-test": "^1.0.2", diff --git a/srv/apps/z2ui5_cl_smp_app_011.js b/srv/apps/z2ui5_cl_smp_app_011.js index 60129ef..a4bac02 100644 --- a/srv/apps/z2ui5_cl_smp_app_011.js +++ b/srv/apps/z2ui5_cl_smp_app_011.js @@ -7,11 +7,7 @@ * @keywords edit input add row delete multiselect toolbar * @origin abap2UI5/samples src/z2ui5_cl_smp_app_011.clas.abap */ -import { defineApp, t } from "cap2ui5"; - -const INFO = - "A MultiSelect table whose input cells switch between display and edit mode via the " + - "toolbar, which also adds new rows and deletes the currently selected ones."; +import { defineApp, t, ViewBuilder } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_011", class { t_tab = t.table({ @@ -26,8 +22,107 @@ defineApp("Z2UI5_CL_SMP_APP_011", class { }); check_editable_active = false; + viewDisplay(c) { + + const view = ViewBuilder.factory() + .ele("View", "mvc") + .a("displayBlock", "true") + .a("height", "100%") + .a("xmlns", "sap.m") + .a("xmlns:mvc", "sap.ui.core.mvc"); + const page = view.ele("Shell") + .ele("Page") + .a("title", "cap2UI5 - Table - Editable Cells, Add and Delete Rows") + .a("showNavButton", c.canGoBack) + .a("navButtonPress", c.eventNavBack()) + .a("id", "test2"); + + page.tag("MessageStrip") + .a("text", "A MultiSelect table whose input cells switch between display and edit mode via the " + + "toolbar, which also adds new rows and deletes the currently selected ones.") + .a("type", "Information") + .a("showIcon", true) + .a("class", "sapUiSmallMargin"); + + const tab = page.ele("Table") + .a("items", `{path: '${c.bind("t_tab", { path: true })}', templateShareable: false}`) + .a("mode", "MultiSelect") + .ele("headerToolbar") + .ele("OverflowToolbar") + .tag("Title") + .a("text", "title of the table") + .tag("Button") + // shows an OverflowToolbar filling up - the press is a plain roundtrip + .a("press", c.event("BUTTON_TEST")) + .a("text", "test") + .tag("ToolbarSpacer") + .tag("Button") + .a("press", c.event("BUTTON_DELETE")) + .a("text", "delete selected row") + .a("icon", "sap-icon://delete") + .tag("Button") + .a("press", c.event("BUTTON_ADD")) + .a("text", "add") + .a("icon", "sap-icon://add") + .tag("Button") + .a("press", c.event("BUTTON_EDIT")) + .a("text", { t: this.check_editable_active ? "display" : "edit" }) + .a("tooltip", "Switch the cells between display and edit mode") + .a("icon", "sap-icon://edit") + .end() + .end(); + + tab.ele("columns") + .ele("Column") + .tag("Text") + .a("text", "Title") + .end() + .ele("Column") + .tag("Text") + .a("text", "Color") + .end() + .ele("Column") + .tag("Text") + .a("text", "Info") + .end() + .ele("Column") + .tag("Text") + .a("text", "Description") + .end() + .ele("Column") + .tag("Text") + .a("text", "Checkbox"); + + tab.ele("items") + .ele("ColumnListItem") + .a("selected", "{SELKZ}") + .ele("cells") + .tag("Input") + .a("id", "test") + .a("enabled", "{EDITABLE}") + .a("value", "{TITLE}") + .tag("Input") + .a("enabled", "{EDITABLE}") + .a("value", "{VALUE}") + .tag("Input") + .a("enabled", "{EDITABLE}") + .a("value", "{INFO}") + .tag("Input") + .a("enabled", "{EDITABLE}") + .a("value", "{DESCR}") + .tag("CheckBox") + .a("selected", "{CHECKBOX}") + .a("enabled", "{EDITABLE}"); + + c.view(view); + + } + main(c) { + if (c.isFirstRun) { + + this.check_editable_active = false; this.t_tab = [ { title: "entry 01", value: "red", info: "completed", descr: "this is a description", checkbox: true }, { title: "entry 02", value: "blue", info: "completed", descr: "this is a description", checkbox: true }, @@ -36,68 +131,23 @@ defineApp("Z2UI5_CL_SMP_APP_011", class { { title: "entry 05", value: "grey", info: "completed", descr: "this is a description", checkbox: true }, {}, ]; - } - if (c.isDisplay) { - c.view(view(c, this)); + this.viewDisplay(c); + } else if (c.isDisplay) { + this.viewDisplay(c); + } else if (c.eventName === "BUTTON_EDIT") { - // `this` reads plain copies: a changed table is written back by assigning it + // a read is a copy of the table: the changed rows are written back whole this.check_editable_active = !this.check_editable_active; this.t_tab = this.t_tab.map((row) => ({ ...row, editable: this.check_editable_active })); + } else if (c.eventName === "BUTTON_DELETE") { this.t_tab = this.t_tab.filter((row) => !row.selkz); + } else if (c.eventName === "BUTTON_ADD") { + this.t_tab = [...this.t_tab, { editable: this.check_editable_active }]; - } else if (c.eventName === "BACK") { - c.navBack(); } + } }); - -function view(c, app) { - return ` - <mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" displayBlock="true" height="100%"> - <Shell> - <Page - title="cap2UI5 - Table - Editable Cells, Add and Delete Rows" - showNavButton="${c.canGoBack}" - navButtonPress="${c.event("BACK")}"> - <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> - <Table items="${c.bind("t_tab")}" mode="MultiSelect"> - <headerToolbar> - <OverflowToolbar> - <Title text="title of the table"/> - <Button press="${c.event("BUTTON_TEST")}" text="test"/> - <ToolbarSpacer/> - <Button press="${c.event("BUTTON_DELETE")}" text="delete selected row" icon="sap-icon://delete"/> - <Button press="${c.event("BUTTON_ADD")}" text="add" icon="sap-icon://add"/> - <Button - press="${c.event("BUTTON_EDIT")}" - text="${app.check_editable_active ? "display" : "edit"}" - tooltip="Switch the cells between display and edit mode" - icon="sap-icon://edit"/> - </OverflowToolbar> - </headerToolbar> - <columns> - <Column><Text text="Title"/></Column> - <Column><Text text="Color"/></Column> - <Column><Text text="Info"/></Column> - <Column><Text text="Description"/></Column> - <Column><Text text="Checkbox"/></Column> - </columns> - <items> - <ColumnListItem selected="{SELKZ}"> - <cells> - <Input enabled="{EDITABLE}" value="{TITLE}"/> - <Input enabled="{EDITABLE}" value="{VALUE}"/> - <Input enabled="{EDITABLE}" value="{INFO}"/> - <Input enabled="{EDITABLE}" value="{DESCR}"/> - <CheckBox selected="{CHECKBOX}" enabled="{EDITABLE}"/> - </cells> - </ColumnListItem> - </items> - </Table> - </Page> - </Shell> - </mvc:View>`; -} diff --git a/srv/apps/z2ui5_cl_smp_app_125.js b/srv/apps/z2ui5_cl_smp_app_125.js index bab2089..97a6533 100644 --- a/srv/apps/z2ui5_cl_smp_app_125.js +++ b/srv/apps/z2ui5_cl_smp_app_125.js @@ -4,67 +4,56 @@ * Sets the browser tab title from the app, so a bookmarked or duplicated window * says which app it holds. * - * The facade `c` has no follow_up_action( ) yet, so this sample reaches the - * framework's own z2ui5_if_client through the escape hatch c.raw: asynchronous, - * and with the ABAP-typed arguments the transpiled method expects. - * - * @keywords document.title tab caption headline set_title follow_up_action raw + * @keywords document.title tab caption headline set_title followUpAction * @origin abap2UI5/samples src/z2ui5_cl_smp_app_125.clas.abap */ -/* global abap */ -import { defineApp } from "cap2ui5"; - -const INFO = - "Enter a title and press the button to run the SET_TITLE front-end action, which updates " + - "the browser tab title (document.title) without reloading the page."; - -/** z2ui5_if_client~follow_up_action( val = action t_arg = args ), through c.raw */ -async function followUpAction(c, action, args = []) { - const t_arg = abap.types.TableFactory.construct( - new abap.types.String({ qualifiedName: "STRING" }), - { - withHeader: false, - keyType: "DEFAULT", - primaryKey: { isUnique: false, type: "STANDARD", keyFields: [], name: "primary_key" }, - secondary: [], - }, - "STRING_TABLE"); - for (const arg of args) t_arg.append(new abap.types.String().set(String(arg))); - await c.raw.z2ui5_if_client$follow_up_action({ val: action, t_arg }); -} +import { defineApp, ViewBuilder } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_125", class { title = "my title"; - async main(c) { + main(c) { + if (c.isDisplay) { - c.view(` - <mvc:View - xmlns:mvc="sap.ui.core.mvc" - xmlns="sap.m" - xmlns:form="sap.ui.layout.form" - displayBlock="true" - height="100%"> - <Shell> - <Page - title="cap2UI5 - Browser - Set the Tab Title" - showNavButton="${c.canGoBack}" - navButtonPress="${c.event("BACK")}"> - <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> - <form:SimpleForm title="Form Title" editable="true"> - <form:content> - <Label text="title"/> - <Input value="${c.bind("title")}"/> - <Button press="${c.event("SET_TITLE")}" text="Set Title"/> - </form:content> - </form:SimpleForm> - </Page> - </Shell> - </mvc:View>`); + + const view = ViewBuilder.factory() + .ele("View", "mvc") + .a("displayBlock", "true") + .a("height", "100%") + .a("xmlns", "sap.m") + .a("xmlns:mvc", "sap.ui.core.mvc") + .a("xmlns:form", "sap.ui.layout.form"); + const page = view.ele("Shell") + .ele("Page") + .a("title", "cap2UI5 - Browser - Set the Tab Title") + .a("showNavButton", c.canGoBack) + .a("navButtonPress", c.eventNavBack()); + + page.tag("MessageStrip") + .a("text", "Enter a title and press the button to run the set_title front-end action, which updates " + + "the browser tab title (document.title) without reloading the page.") + .a("type", "Information") + .a("showIcon", true) + .a("class", "sapUiSmallMargin"); + + page.ele("SimpleForm", "form") + .a("title", "Form Title") + .a("editable", true) + .ele("content", "form") + .tag("Label") + .a("text", "title") + .tag("Input") + .a("value", c.bind("title")) + .tag("Button") + .a("press", c.event("SET_TITLE")) + .a("text", "Set Title"); + c.view(view); + } else if (c.eventName === "SET_TITLE") { - await followUpAction(c, "SET_TITLE", [this.title]); - } else if (c.eventName === "BACK") { - c.navBack(); + + c.followUpAction("set_title", [this.title]); + } + } }); diff --git a/srv/apps/z2ui5_cl_smp_app_161.js b/srv/apps/z2ui5_cl_smp_app_161.js index a76a937..53425ad 100644 --- a/srv/apps/z2ui5_cl_smp_app_161.js +++ b/srv/apps/z2ui5_cl_smp_app_161.js @@ -7,76 +7,120 @@ * @keywords nested stack popup in popup second dialog * @origin abap2UI5/samples src/z2ui5_cl_smp_app_161.clas.abap */ -import { defineApp } from "cap2ui5"; - -const INFO = - "This sample opens a popup from a button and then chains to a second popup " + - "from within the first one."; +import { defineApp, ViewBuilder } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_161", class { - main(c) { - if (c.isDisplay) { - c.view(` - <mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" displayBlock="true" height="100%"> - <Shell> - <Page - title="cap2UI5 - Popup - Dialog inside a Dialog" - showNavButton="${c.canGoBack}" - navButtonPress="${c.event("BACK")}"> - <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> - <Button press="${c.event("POPUP")}" text="Open Popup..."/> - </Page> - </Shell> - </mvc:View>`); - return; - } + simplePopup1(c) { + + const popup = ViewBuilder.factory() + .ele("FragmentDefinition", "core") + .a("xmlns", "sap.m") + .a("xmlns:core", "sap.ui.core"); + + const dialog = popup.ele("Dialog") + .a("afterClose", c.event("BTN_OK_1ND")) + .ele("content"); + + dialog.tag("Button") + .a("press", c.event("GOTO_2ND")) + .a("text", "Open 2nd popup"); + + dialog.end() + .ele("buttons") + .tag("Button") + .a("press", c.event("BTN_OK_1ND")) + .a("text", "OK") + .a("type", "Emphasized"); + + c.popup(popup); + + } + + simplePopup2(c) { + + const popup = ViewBuilder.factory() + .ele("FragmentDefinition", "core") + .a("xmlns", "sap.m") + .a("xmlns:core", "sap.ui.core"); + + const dialog = popup.ele("Dialog") + .a("afterClose", c.event("BTN_OK_2ND")) + .ele("content"); + + dialog.tag("Label") + .a("text", "this is a second popup"); + + dialog.end() + .ele("buttons") + .tag("Button") + .a("press", c.event("BTN_OK_2ND")) + .a("text", "GOTO 1ST POPUP") + .a("type", "Emphasized"); + + c.popup(popup); + + } + + viewDisplay(c) { + + const view = ViewBuilder.factory() + .ele("View", "mvc") + .a("displayBlock", "true") + .a("height", "100%") + .a("xmlns", "sap.m") + .a("xmlns:mvc", "sap.ui.core.mvc"); + const page = view.ele("Shell") + .ele("Page") + .a("title", "cap2UI5 - Popup - Dialog inside a Dialog") + .a("showNavButton", c.canGoBack) + .a("navButtonPress", c.eventNavBack()); + + page.tag("MessageStrip") + .a("text", "This sample opens a popup from a button and then chains to a second popup " + + "from within the first one.") + .a("type", "Information") + .a("showIcon", true) + .a("class", "sapUiSmallMargin"); + + page.tag("Button") + .a("press", c.event("POPUP")) + .a("text", "Open Popup..."); + + c.view(view); + + } + + onEvent(c) { switch (c.eventName) { - case "POPUP": - c.popup(popup1(c)); - break; case "GOTO_2ND": - c.popup(popup2(c)); + this.simplePopup2(c); break; + case "BTN_OK_2ND": c.popupClose(); - c.popup(popup1(c)); + this.simplePopup1(c); break; + case "BTN_OK_1ND": c.popupClose(); break; - case "BACK": - c.navBack(); + + case "POPUP": + this.simplePopup1(c); break; } + } -}); -function popup1(c) { - return ` - <core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core"> - <Dialog afterClose="${c.event("BTN_OK_1ND")}"> - <content> - <Button press="${c.event("GOTO_2ND")}" text="Open 2nd popup"/> - </content> - <buttons> - <Button press="${c.event("BTN_OK_1ND")}" text="OK" type="Emphasized"/> - </buttons> - </Dialog> - </core:FragmentDefinition>`; -} - -function popup2(c) { - return ` - <core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core"> - <Dialog afterClose="${c.event("BTN_OK_2ND")}"> - <content> - <Label text="this is a second popup"/> - </content> - <buttons> - <Button press="${c.event("BTN_OK_2ND")}" text="GOTO 1ST POPUP" type="Emphasized"/> - </buttons> - </Dialog> - </core:FragmentDefinition>`; -} + main(c) { + + if (c.isDisplay) { + this.viewDisplay(c); + return; + } + this.onEvent(c); + + } +}); diff --git a/srv/apps/z2ui5_cl_smp_app_167.js b/srv/apps/z2ui5_cl_smp_app_167.js index b887293..69e1dd0 100644 --- a/srv/apps/z2ui5_cl_smp_app_167.js +++ b/srv/apps/z2ui5_cl_smp_app_167.js @@ -7,62 +7,81 @@ * @keywords argument parameter payload event data fixed value eventArg * @origin abap2UI5/samples src/z2ui5_cl_smp_app_167.clas.abap */ -import { defineApp } from "cap2ui5"; +import { defineApp, ViewBuilder } from "cap2ui5"; -const INFO = - "This sample shows how to pass extra arguments to an event via c.event( name, [args] ) - fixed " + - "values, model values, or client-side expressions - and read them in the backend with c.eventArg(1)."; +defineApp("Z2UI5_CL_SMP_APP_167", class { + mv_value = ""; -const EVENTS = [ - "EVENT_FIX_VAL", - "EVENT_MODEL_VALUE", - "SOURCE_PROPERTY_TEXT", - "EVENT_PROPERTY_VALUE", - "PARENT_PROPERTY_ID", -]; + viewDisplay(c) { -defineApp("Z2UI5_CL_SMP_APP_167", class { - mv_value = "my value"; + const view = ViewBuilder.factory() + .ele("View", "mvc") + .a("displayBlock", "true") + .a("height", "100%") + .a("xmlns", "sap.m") + .a("xmlns:mvc", "sap.ui.core.mvc"); + const page = view.ele("Shell") + .ele("Page") + .a("title", "cap2UI5 - Event - Extra Arguments with the Event") + .a("showNavButton", c.canGoBack) + .a("navButtonPress", c.eventNavBack()); + + page.tag("MessageStrip") + .a("text", "This sample shows how to pass extra arguments to an event via c.event( name, [args] ) - " + + "fixed values, model values, or client-side expressions - and read them in the backend.") + .a("type", "Information") + .a("showIcon", true) + .a("class", "sapUiSmallMargin"); + + page.tag("Link") + .a("text", "More information...") + .a("target", "_blank") + .a("href", "https://sdk.openui5.org/topic/b0fb4de7364f4bcbb053a99aa645affe"); + + page.tag("Button") + .a("press", c.event("EVENT_FIX_VAL", ["FIX_VAL"])) + .a("text", "EVENT_FIX_VAL"); + + page.tag("Input") + .a("value", c.bind("mv_value")); + page.tag("Button") + .a("press", c.event("EVENT_MODEL_VALUE", ["$" + c.bind("mv_value")])) + .a("text", "EVENT_MODEL_VALUE"); + + page.tag("Button") + .a("press", c.event("SOURCE_PROPERTY_TEXT", ["${$source>/text}"])) + .a("text", "SOURCE_PROPERTY_TEXT"); + + page.tag("Input") + .a("description", "make an input and press enter - ") + .a("submit", c.event("EVENT_PROPERTY_VALUE", ["${$parameters>/value}"])); + + page.tag("Button") + .a("press", c.event("PARENT_PROPERTY_ID", ["$event.oSource.oParent.sId"])) + .a("text", "PARENT_PROPERTY_ID"); + + c.view(view); + + } main(c) { - // An argument starting with $ is evaluated in the browser when the event - // fires: a binding, a property of the control that fired, an event parameter. - if (c.isDisplay) { - c.view(` - <mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" displayBlock="true" height="100%"> - <Shell> - <Page - title="cap2UI5 - Event - Extra Arguments with the Event" - showNavButton="${c.canGoBack}" - navButtonPress="${c.event("BACK")}"> - <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> - <Link - text="More information..." - target="_blank" - href="https://sdk.openui5.org/topic/b0fb4de7364f4bcbb053a99aa645affe"/> - <Button - press="${c.event("EVENT_FIX_VAL", ["FIX_VAL"])}" - text="EVENT_FIX_VAL"/> - <Input value="${c.bind("mv_value")}"/> - <Button - press="${c.event("EVENT_MODEL_VALUE", ["$" + c.bind("mv_value")])}" - text="EVENT_MODEL_VALUE"/> - <Button - press="${c.event("SOURCE_PROPERTY_TEXT", ["${$source>/text}"])}" - text="SOURCE_PROPERTY_TEXT"/> - <Input - description="make an input and press enter - " - submit="${c.event("EVENT_PROPERTY_VALUE", ["${$parameters>/value}"])}"/> - <Button - press="${c.event("PARENT_PROPERTY_ID", ["$event.oSource.oParent.sId"])}" - text="PARENT_PROPERTY_ID"/> - </Page> - </Shell> - </mvc:View>`); - } else if (EVENTS.includes(c.eventName)) { - c.messageBox(`backend event: ${c.eventArg(1)}`); - } else if (c.eventName === "BACK") { - c.navBack(); + + if (c.isFirstRun) { + this.mv_value = "my value"; + this.viewDisplay(c); + } else if (c.isDisplay) { + this.viewDisplay(c); + } + + switch (c.eventName) { + case "EVENT_FIX_VAL": + case "EVENT_MODEL_VALUE": + case "SOURCE_PROPERTY_TEXT": + case "EVENT_PROPERTY_VALUE": + case "PARENT_PROPERTY_ID": + c.messageBox(`backend event: ${c.eventArg(1)}`); + break; } + } }); diff --git a/srv/apps/z2ui5_cl_smp_app_488.js b/srv/apps/z2ui5_cl_smp_app_488.js index 3a6a1e0..b579f9d 100644 --- a/srv/apps/z2ui5_cl_smp_app_488.js +++ b/srv/apps/z2ui5_cl_smp_app_488.js @@ -1,87 +1,127 @@ /** * Navigation - Return Data and Events to the Caller * - * The way back carries data: the called app returns with an event name, and - * the caller reads what it entered from c.prevApp. + * The way back carries data: the called app returns an event name and a + * payload (data) that the caller reads as c.eventData. * * Calls a second app (Z2UI5_CL_SMP_APP_489) with c.navTo( ). The called app - * comes back with c.navBack( { event } ), without knowing who called it. On - * return this app is shown again - c.isDisplay - and finds the event name in - * c.eventName and the called app's fields, as plain values, in c.prevApp. + * comes back with c.navBack( { event, data } ), handing an event name and a + * data payload to its caller without knowing who called it. On return this + * app enters main( ) with c.isDisplay and reads both: the event name from + * c.eventName, the payload from c.eventData. * - * @keywords navTo navBack prevApp result return event payload + * @keywords navBack data eventData result return event payload * @origin abap2UI5/samples src/z2ui5_cl_smp_app_488.clas.abap */ -import { defineApp } from "cap2ui5"; - -const INFO = - "Calls a second app that returns via c.navBack( { event } ). On return this app reads the " + - "event from c.eventName and the entered data from c.prevApp in its c.isDisplay branch and " + - "shows them below."; +import { defineApp, ViewBuilder } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_488", class { s_result = { product: "", quantity: "" }; returned_event = ""; main(c) { + if (c.isFirstRun) { - c.view(view(c)); + this.viewDisplay(c); + } else if (c.isDisplay) { - this.returned_event = c.eventName; - if (c.eventName === "DATA_CONFIRMED") { - this.s_result = c.prevApp.s_result; - c.messageToast(`Returned event ${this.returned_event}, ` + - `product ${this.s_result.product}, quantity ${this.s_result.quantity}`); - } else if (c.eventName === "DATA_CANCELLED") { - this.s_result = { product: "", quantity: "" }; - c.messageToast("Returned event DATA_CANCELLED, no data passed"); - } - c.view(view(c)); + this.onNavigation(c); + } else if (c.eventName === "CALL_APP") { c.navTo("Z2UI5_CL_SMP_APP_489"); - } else if (c.eventName === "BACK") { - c.navBack(); } + } -}); -function view(c) { - // binding="{/S_RESULT}" makes the structure the form's context, so the - // fields inside bind its components relatively: {PRODUCT}, {QUANTITY} - return ` - <mvc:View - xmlns:mvc="sap.ui.core.mvc" - xmlns="sap.m" - xmlns:form="sap.ui.layout.form" - xmlns:layout="sap.ui.layout" - displayBlock="true" - height="100%"> - <Shell> - <Page - title="cap2UI5 - Navigation - Return Data and Events to the Caller" - showNavButton="${c.canGoBack}" - navButtonPress="${c.event("BACK")}"> - <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> - <layout:Grid defaultSpan="L6 M12 S12"> - <layout:content> - <form:SimpleForm - title="Result returned by the called app" - editable="true" - binding="${c.bind("s_result")}"> - <form:content> - <Label text="Open the input app"/> - <Button press="${c.event("CALL_APP")}" text="call app (c.navTo)" type="Emphasized"/> - <Label text="Returned event"/> - <Input enabled="false" value="${c.bind("returned_event")}"/> - <Label text="Returned product"/> - <Input enabled="false" value="{PRODUCT}"/> - <Label text="Returned quantity"/> - <Input enabled="false" value="{QUANTITY}"/> - </form:content> - </form:SimpleForm> - </layout:content> - </layout:Grid> - </Page> - </Shell> - </mvc:View>`; -} + onNavigation(c) { + + this.returned_event = c.eventName; + + switch (this.returned_event) { + + case "DATA_CONFIRMED": + + // the payload handed over by navBack( { data } ) arrives typed, as + // plain values - the receiver decides what to do with it + if (c.eventData) { + + this.s_result = c.eventData; + c.messageToast(`Returned event ${this.returned_event}, ` + + `product ${this.s_result.product}, quantity ${this.s_result.quantity}`); + + } + break; + + case "DATA_CANCELLED": + + this.s_result = { product: "", quantity: "" }; + c.messageToast("Returned event DATA_CANCELLED, no data passed"); + break; + + } + + this.viewDisplay(c); + + } + + viewDisplay(c) { + + const view = ViewBuilder.factory() + .ele("View", "mvc") + .a("displayBlock", "true") + .a("height", "100%") + .a("xmlns", "sap.m") + .a("xmlns:mvc", "sap.ui.core.mvc") + .a("xmlns:form", "sap.ui.layout.form") + .a("xmlns:layout", "sap.ui.layout"); + const page = view.ele("Shell") + .ele("Page") + .a("title", "cap2UI5 - Navigation - Return Data and Events to the Caller") + .a("showNavButton", c.canGoBack) + .a("navButtonPress", c.eventNavBack()); + + page.tag("MessageStrip") + .a("text", "Calls a second app that returns via c.navBack( ) with an event and a data payload. " + + "On return this app reads both - c.eventName and c.eventData - in its c.isDisplay " + + "branch and shows them below.") + .a("type", "Information") + .a("showIcon", true) + .a("class", "sapUiSmallMargin"); + + const form = page.ele("Grid", "layout") + .a("defaultSpan", "L6 M12 S12") + .ele("content", "layout") + .ele("SimpleForm", "form") + .a("title", "Result returned by the called app") + .a("editable", true) + .ele("content", "form"); + + form.tag("Label") + .a("text", "Open the input app"); + form.tag("Button") + .a("press", c.event("CALL_APP")) + .a("text", "call app (c.navTo)") + .a("type", "Emphasized"); + + form.tag("Label") + .a("text", "Returned event"); + form.tag("Input") + .a("enabled", false) + .a("value", c.bind("returned_event")); + + form.tag("Label") + .a("text", "Returned product"); + form.tag("Input") + .a("enabled", false) + .a("value", c.bind("s_result.product")); + + form.tag("Label") + .a("text", "Returned quantity"); + form.tag("Input") + .a("enabled", false) + .a("value", c.bind("s_result.quantity")); + + c.view(view); + + } +}); diff --git a/srv/apps/z2ui5_cl_smp_app_489.js b/srv/apps/z2ui5_cl_smp_app_489.js index d25ae1b..6029c0f 100644 --- a/srv/apps/z2ui5_cl_smp_app_489.js +++ b/srv/apps/z2ui5_cl_smp_app_489.js @@ -1,69 +1,90 @@ /** * Navigation - Data Input App (called by Z2UI5_CL_SMP_APP_488) * - * Returns to its caller with c.navBack( { event } ) - handing back an event - * name without knowing which app called it; the caller reads the entered data - * from c.prevApp. A hidden helper, never listed on its own in the overview. + * Returns to its caller with c.navBack( { event, data } ) - handing back an + * event name and the entered data without knowing which app called it (no + * c.prevApp). A hidden helper, never listed on its own in the overview. * * @origin abap2UI5/samples src/z2ui5_cl_smp_app_489.clas.abap */ -import { defineApp } from "cap2ui5"; - -const INFO = - "Change the data and return: 'confirm' leaves with event DATA_CONFIRMED, the caller reads " + - "the entered data from c.prevApp; 'cancel' leaves with event DATA_CANCELLED. " + - "The nav-back button of the page leaves without an event."; +import { defineApp, ViewBuilder } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_489", class { - s_result = { product: "Notebook Basic 15", quantity: "2" }; + s_result = { product: "", quantity: "" }; main(c) { - if (c.isDisplay) { - c.view(view(c)); + + if (c.isFirstRun) { + + this.s_result = { product: "Notebook Basic 15", quantity: "2" }; + + this.viewDisplay(c); + + } else if (c.isDisplay) { + this.viewDisplay(c); + } else if (c.eventName === "CONFIRM") { - c.navBack({ event: "DATA_CONFIRMED" }); + c.navBack({ event: "DATA_CONFIRMED", data: this.s_result }); + } else if (c.eventName === "CANCEL") { c.navBack({ event: "DATA_CANCELLED" }); - } else if (c.eventName === "BACK") { - c.navBack(); } + } -}); -function view(c) { - return ` - <mvc:View - xmlns:mvc="sap.ui.core.mvc" - xmlns="sap.m" - xmlns:form="sap.ui.layout.form" - xmlns:layout="sap.ui.layout" - displayBlock="true" - height="100%"> - <Shell> - <Page - title="cap2UI5 - Navigation - Data Input App" - showNavButton="${c.canGoBack}" - navButtonPress="${c.event("BACK")}"> - <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> - <layout:Grid defaultSpan="L6 M12 S12"> - <layout:content> - <form:SimpleForm - title="Data returned to the caller" - editable="true" - binding="${c.bind("s_result")}"> - <form:content> - <Label text="Product"/> - <Input value="{PRODUCT}"/> - <Label text="Quantity"/> - <Input value="{QUANTITY}"/> - <Label text="Return to the caller"/> - <Button press="${c.event("CONFIRM")}" text="confirm (event, data in c.prevApp)" type="Emphasized"/> - <Button press="${c.event("CANCEL")}" text="cancel (event only)"/> - </form:content> - </form:SimpleForm> - </layout:content> - </layout:Grid> - </Page> - </Shell> - </mvc:View>`; -} + viewDisplay(c) { + + const view = ViewBuilder.factory() + .ele("View", "mvc") + .a("displayBlock", "true") + .a("height", "100%") + .a("xmlns", "sap.m") + .a("xmlns:mvc", "sap.ui.core.mvc") + .a("xmlns:form", "sap.ui.layout.form") + .a("xmlns:layout", "sap.ui.layout"); + const page = view.ele("Shell") + .ele("Page") + .a("title", "cap2UI5 - Navigation - Data Input App") + .a("showNavButton", c.canGoBack) + .a("navButtonPress", c.eventNavBack()); + + page.tag("MessageStrip") + .a("text", "Change the data and return: 'confirm' leaves with event DATA_CONFIRMED plus the " + + "entered data, 'cancel' leaves with event DATA_CANCELLED and no data. " + + "The nav-back button of the page leaves without an event.") + .a("type", "Information") + .a("showIcon", true) + .a("class", "sapUiSmallMargin"); + + const form = page.ele("Grid", "layout") + .a("defaultSpan", "L6 M12 S12") + .ele("content", "layout") + .ele("SimpleForm", "form") + .a("title", "Data returned to the caller") + .a("editable", true) + .ele("content", "form"); + + form.tag("Label") + .a("text", "Product"); + form.tag("Input") + .a("value", c.bind("s_result.product")); + + form.tag("Label") + .a("text", "Quantity"); + form.tag("Input") + .a("value", c.bind("s_result.quantity")); + + form.tag("Label") + .a("text", "Return to the caller"); + form.tag("Button") + .a("press", c.event("CONFIRM")) + .a("text", "confirm (event + data)") + .a("type", "Emphasized"); + form.tag("Button") + .a("press", c.event("CANCEL")) + .a("text", "cancel (event only)"); + + c.view(view); + + } +}); diff --git a/srv/apps/z2ui5_cl_smp_app_493.js b/srv/apps/z2ui5_cl_smp_app_493.js index bc97f64..01acfc3 100644 --- a/srv/apps/z2ui5_cl_smp_app_493.js +++ b/srv/apps/z2ui5_cl_smp_app_493.js @@ -7,32 +7,44 @@ * @keywords hello world smallest first app minimal start here template * @origin abap2UI5/samples src/z2ui5_cl_smp_app_493.clas.abap */ -import { defineApp } from "cap2ui5"; +import { defineApp, ViewBuilder } from "cap2ui5"; const INFO = "The whole app is what you see below: a class handed to defineApp( ), one main( ) method, " + - "a view written as XML and handed to c.view( ). cap2UI5 calls main( ) on every roundtrip - " + - "here only the display matters, which is what c.isDisplay asks: true on the first start and " + - "whenever the app is shown again. Copy this file as the starting point for your own app."; + "a view built with ViewBuilder and handed to c.view( ). cap2UI5 calls main( ) on every " + + "roundtrip - here only the display matters, which is what c.isDisplay asks: true on the " + + "first start and whenever the app is shown again. Copy this file as the starting point for " + + "your own app."; defineApp("Z2UI5_CL_SMP_APP_493", class { main(c) { if (c.isDisplay) { - c.view(` - <mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" displayBlock="true" height="100%"> - <Shell> - <Page - title="cap2UI5 - Basics I - Hello World, the Smallest App" - showNavButton="${c.canGoBack}" - navButtonPress="${c.event("BACK")}"> - <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> - <Title text="Hello World" class="sapUiSmallMargin" level="H2"/> - </Page> - </Shell> - </mvc:View>`); - } else if (c.eventName === "BACK") { - c.navBack(); + + const view = ViewBuilder.factory() + .ele("View", "mvc") + .a("displayBlock", "true") + .a("height", "100%") + .a("xmlns", "sap.m") + .a("xmlns:mvc", "sap.ui.core.mvc"); + const page = view.ele("Shell") + .ele("Page") + .a("title", "cap2UI5 - Basics I - Hello World, the Smallest App") + .a("showNavButton", c.canGoBack) + .a("navButtonPress", c.eventNavBack()); + + page.tag("MessageStrip") + .a("text", INFO) + .a("type", "Information") + .a("showIcon", true) + .a("class", "sapUiSmallMargin"); + + page.tag("Title") + .a("text", "Hello World") + .a("class", "sapUiSmallMargin") + .a("level", "H2"); + c.view(view); + } } }); diff --git a/srv/apps/z2ui5_cl_smp_app_494.js b/srv/apps/z2ui5_cl_smp_app_494.js index d9043f0..1f8e9c6 100644 --- a/srv/apps/z2ui5_cl_smp_app_494.js +++ b/srv/apps/z2ui5_cl_smp_app_494.js @@ -8,7 +8,7 @@ * @keywords binding bind model field value input button roundtrip messagebox * @origin abap2UI5/samples src/z2ui5_cl_smp_app_494.clas.abap */ -import { defineApp } from "cap2ui5"; +import { defineApp, ViewBuilder } from "cap2ui5"; const INFO = "c.bind('name') connects the field NAME with the input below. Type a name and leave the " + @@ -17,44 +17,62 @@ const INFO = "needed -, writes GREETING back into the view and confirms the roundtrip with a MessageBox."; defineApp("Z2UI5_CL_SMP_APP_494", class { - name = "World"; + name = ""; greeting = ""; main(c) { - if (c.isDisplay) { - c.view(` - <mvc:View - xmlns:mvc="sap.ui.core.mvc" - xmlns="sap.m" - xmlns:form="sap.ui.layout.form" - displayBlock="true" - height="100%"> - <Shell> - <Page - title="cap2UI5 - Basics II - Data Binding: Input and Button" - showNavButton="${c.canGoBack}" - navButtonPress="${c.event("BACK")}"> - <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> - <form:SimpleForm title="Data Binding" editable="true"> - <form:content> - <Label text="your name"/> - <Input value="${c.bind("name")}"/> - <Label text="bound to the same field"/> - <Text text="${c.bind("name")}"/> - <Label text="written by the backend"/> - <Text text="${c.bind("greeting")}"/> - <Button press="${c.event("GREET")}" text="Greet"/> - </form:content> - </form:SimpleForm> - </Page> - </Shell> - </mvc:View>`); + if (c.isFirstRun) { + this.name = "World"; + this.viewDisplay(c); + } else if (c.isDisplay) { + this.viewDisplay(c); } else if (c.eventName === "GREET") { this.greeting = `Hello ${this.name}!`; - c.messageBox( - `Roundtrip done: the backend read NAME = '${this.name}' and wrote GREETING back into the view.`); - } else if (c.eventName === "BACK") { - c.navBack(); + c.messageBox(`Roundtrip done: the backend read NAME = '${this.name}' and wrote GREETING back into the view.`); } } + + viewDisplay(c) { + + const view = ViewBuilder.factory() + .ele("View", "mvc") + .a("displayBlock", "true") + .a("height", "100%") + .a("xmlns", "sap.m") + .a("xmlns:mvc", "sap.ui.core.mvc") + .a("xmlns:form", "sap.ui.layout.form"); + const page = view.ele("Shell") + .ele("Page") + .a("title", "cap2UI5 - Basics II - Data Binding: Input and Button") + .a("showNavButton", c.canGoBack) + .a("navButtonPress", c.eventNavBack()); + + page.tag("MessageStrip") + .a("text", INFO) + .a("type", "Information") + .a("showIcon", true) + .a("class", "sapUiSmallMargin"); + + page.ele("SimpleForm", "form") + .a("title", "Data Binding") + .a("editable", true) + .ele("content", "form") + .tag("Label") + .a("text", "your name") + .tag("Input") + .a("value", c.bind("name")) + .tag("Label") + .a("text", "bound to the same field") + .tag("Text") + .a("text", c.bind("name")) + .tag("Label") + .a("text", "written by the backend") + .tag("Text") + .a("text", c.bind("greeting")) + .tag("Button") + .a("press", c.event("GREET")) + .a("text", "Greet"); + c.view(view); + + } }); diff --git a/srv/apps/z2ui5_cl_smp_app_495.js b/srv/apps/z2ui5_cl_smp_app_495.js index c92081f..2b3fff7 100644 --- a/srv/apps/z2ui5_cl_smp_app_495.js +++ b/srv/apps/z2ui5_cl_smp_app_495.js @@ -8,7 +8,7 @@ * @keywords lifecycle roundtrip main dispatcher state draft isFirstRun isDisplay eventName * @origin abap2UI5/samples src/z2ui5_cl_smp_app_495.clas.abap */ -import { defineApp, t } from "cap2ui5"; +import { defineApp, t, ViewBuilder } from "cap2ui5"; const INFO = "main( ) runs on every roundtrip - c.isFirstRun, c.isDisplay and c.eventName tell it what " + @@ -22,45 +22,71 @@ defineApp("Z2UI5_CL_SMP_APP_495", class { t_log = t.table({ no: "", check: "" }); main(c) { - // `this` reads plain copies: a changed table is written back by assigning it - const log = (check) => { - this.t_log = [...this.t_log, { no: String(this.t_log.length + 1), check }]; - }; - if (c.isFirstRun) { - log("c.isFirstRun - the very first call, nothing exists yet"); - c.view(view(c)); + + this.logStep("c.isFirstRun - the very first call, nothing exists yet"); + this.viewDisplay(c); + } else if (c.isDisplay) { - log("c.isDisplay - the sub-app returned, re-display the view"); - c.view(view(c)); + + this.logStep("c.isDisplay - the sub-app returned, re-display the view"); + this.viewDisplay(c); + } else if (c.eventName === "LOG") { - log("c.eventName - a button was pressed, the view stays as it is"); + this.logStep("c.eventName - a button was pressed, the view stays as it is"); + } else if (c.eventName === "CALL") { - log("c.eventName - calling Basics I as a sub-app"); + + this.logStep("c.eventName - calling Basics I as a sub-app"); c.navTo("Z2UI5_CL_SMP_APP_493"); - } else if (c.eventName === "BACK") { - c.navBack(); + } } -}); -function view(c) { - return ` - <mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" displayBlock="true" height="100%"> - <Shell> - <Page - title="cap2UI5 - Basics III - Lifecycle: First Run, Event, Display" - showNavButton="${c.canGoBack}" - navButtonPress="${c.event("BACK")}"> - <MessageStrip text="${INFO}" type="Information" showIcon="true" class="sapUiSmallMargin"/> - <HBox class="sapUiSmallMargin"> - <Button press="${c.event("LOG")}" text="Log an Event"/> - <Button press="${c.event("CALL")}" text="Call a Sub-App" class="sapUiTinyMarginBegin"/> - </HBox> - <List headerText="Calls of main( )" items="${c.bind("t_log")}" class="sapUiSmallMargin"> - <StandardListItem title="{CHECK}" description="call {NO}"/> - </List> - </Page> - </Shell> - </mvc:View>`; -} + logStep(val) { + // a read is a copy of the table: the changed table is written back whole + this.t_log = [...this.t_log, { no: String(this.t_log.length + 1), check: val }]; + } + + viewDisplay(c) { + + const view = ViewBuilder.factory() + .ele("View", "mvc") + .a("displayBlock", "true") + .a("height", "100%") + .a("xmlns", "sap.m") + .a("xmlns:mvc", "sap.ui.core.mvc"); + const page = view.ele("Shell") + .ele("Page") + .a("title", "cap2UI5 - Basics III - Lifecycle: First Run, Event, Display") + .a("showNavButton", c.canGoBack) + .a("navButtonPress", c.eventNavBack()); + + page.tag("MessageStrip") + .a("text", INFO) + .a("type", "Information") + .a("showIcon", true) + .a("class", "sapUiSmallMargin"); + + page.ele("HBox") + .a("class", "sapUiSmallMargin") + .tag("Button") + .a("press", c.event("LOG")) + .a("text", "Log an Event") + .tag("Button") + .a("press", c.event("CALL")) + .a("text", "Call a Sub-App") + .a("class", "sapUiTinyMarginBegin"); + + page.ele("List") + .a("headerText", "Calls of main( )") + .a("items", c.bind("t_log")) + .a("class", "sapUiSmallMargin") + .tag("StandardListItem") + .a("title", "{CHECK}") + .a("description", "call {NO}"); + + c.view(view); + + } +}); diff --git a/test/samples.test.mjs b/test/samples.test.mjs index 0a03946..2101603 100644 --- a/test/samples.test.mjs +++ b/test/samples.test.mjs @@ -1,6 +1,7 @@ -// Every sample, driven over the wire against the published cap2ui5 package: -// it starts, its view is well-formed XML, and its events do what the sample -// says they do - including the navigation round trips between two apps. +// Every sample, driven over the wire: it starts, its view is well-formed XML, +// and its events do what the sample says they do - including the navigation +// round trips between two apps, where the tests send back the event the +// page's back button carries, as the browser does. import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; @@ -11,6 +12,9 @@ const s = serve(); const P = (o) => post(s.url, o); const ok = (r) => assert.equal(r.status, 200, r.text.slice(0, 500)); +/** the event a handler attribute's wire fires, as the browser sends it back */ +const firedBy = (xml, attr) => xml.match(new RegExp(`${attr}="\\.eB\\(\\['([^']+)'`))?.[1]; + const APPS = fs.readdirSync(path.join(ROOT, "srv/apps")) .filter((f) => /^z2ui5_cl_smp_app_\d+\.js$/.test(f)) .map((f) => f.replace(/\.js$/, "").toUpperCase()); @@ -25,6 +29,7 @@ test("every sample starts, and its view is well-formed XML titled cap2UI5", asyn assert.equal(wellFormed(xml), true, `${app}: ${wellFormed(xml)}`); assert.match(xml, /title="cap2UI5 - /, `${app}: the page title`); assert.match(xml, /showNavButton="false"/, `${app}: started directly, there is nothing to go back to`); + assert.ok(firedBy(xml, "navButtonPress"), `${app}: the back button carries the nav-back wire`); } }); @@ -61,8 +66,9 @@ test("495 Lifecycle: first run, event, and the return from a called app", async assert.match(slot(call, "MAIN"), /Basics I - Hello World/); assert.match(slot(call, "MAIN"), /showNavButton="true"/, "the called app can go back"); - const back = await P({ app: call.app, id: call.id, event: "BACK" }); - assert.equal(back.app, APP, "c.navBack( ) returns to the caller"); + // the called app's back button: c.eventNavBack( ), no branch in its main( ) + const back = await P({ app: call.app, id: call.id, event: firedBy(slot(call, "MAIN"), "navButtonPress") }); + assert.equal(back.app, APP, "the back button returns to the caller"); assert.match(slot(back, "MAIN") ?? "", /Basics III/, "the caller renders again (c.isDisplay)"); assert.deepEqual(back.json.MODEL.T_LOG.map((r) => r.CHECK.split(" - ")[0]), ["c.isFirstRun", "c.eventName", "c.eventName", "c.isDisplay"], @@ -72,6 +78,8 @@ test("495 Lifecycle: first run, event, and the return from a called app", async test("011 Editable Table: edit mode, delete the selected rows, add a row", async () => { const APP = "Z2UI5_CL_SMP_APP_011"; const start = await P({ app: APP }); + assert.match(slot(start, "MAIN"), /items="\{path: '\/T_TAB', templateShareable: false\}"/, + "the bare path of c.bind( t_tab, { path: true } ) in a composed binding"); const rows = start.json.MODEL.T_TAB; assert.equal(rows.length, 6); assert.deepEqual(rows[0], { SELKZ: false, TITLE: "entry 01", VALUE: "red", DESCR: "this is a description", @@ -126,21 +134,24 @@ test("161 Dialog inside a Dialog: open, chain to the second, back to the first, assert.ok(destroys(close, "POPUP"), "the popup is closed"); }); -test("488/489 Navigation: the called app returns an event, the caller reads its data", async () => { +test("488/489 Navigation: the called app returns an event and data, the caller reads both", async () => { const start = await P({ app: "Z2UI5_CL_SMP_APP_488" }); assert.deepEqual(start.json.MODEL, { S_RESULT: { PRODUCT: "", QUANTITY: "" }, RETURNED_EVENT: "" }); + assert.match(slot(start, "MAIN"), /value="\{\/S_RESULT\/PRODUCT\}"/, "a structure component, bound by name"); const called = await P({ app: "Z2UI5_CL_SMP_APP_488", id: start.id, event: "CALL_APP" }); assert.equal(called.app, "Z2UI5_CL_SMP_APP_489"); assert.deepEqual(called.json.MODEL, { S_RESULT: { PRODUCT: "Notebook Basic 15", QUANTITY: "2" } }); - // the user edits both fields - bound relatively inside binding="{/S_RESULT}" + // the user edits both fields, then confirms: the data travels with navBack const confirm = await P({ app: called.app, id: called.id, event: "CONFIRM", model: { S_RESULT: { PRODUCT: "Notebook Pro 17", QUANTITY: "7" } } }); assert.equal(confirm.app, "Z2UI5_CL_SMP_APP_488"); assert.deepEqual(confirm.json.MODEL, { S_RESULT: { PRODUCT: "Notebook Pro 17", QUANTITY: "7" }, RETURNED_EVENT: "DATA_CONFIRMED" }); assert.match(slot(confirm, "MAIN") ?? "", /Result returned by the called app/, "the caller renders again"); + assert.deepEqual(actions(confirm).find((a) => a[0] === "MESSAGE_TOAST")?.[2], + "Returned event DATA_CONFIRMED, product Notebook Pro 17, quantity 7"); const again = await P({ app: "Z2UI5_CL_SMP_APP_488", id: confirm.id, event: "CALL_APP" }); const cancel = await P({ app: again.app, id: again.id, event: "CANCEL" }); @@ -148,7 +159,7 @@ test("488/489 Navigation: the called app returns an event, the caller reads its { S_RESULT: { PRODUCT: "", QUANTITY: "" }, RETURNED_EVENT: "DATA_CANCELLED" }); }); -test("125 Tab Title: the front-end action goes out through c.raw", async () => { +test("125 Tab Title: the set_title front-end action", async () => { const APP = "Z2UI5_CL_SMP_APP_125"; const start = await P({ app: APP }); const set = await P({ app: APP, id: start.id, event: "SET_TITLE", model: { TITLE: "Invoices" } }); From 71147d5c0944abf34e4eec1ba2cfac1dbdc39013 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Mon, 28 Sep 2026 11:08:58 +0000 Subject: [PATCH 3/5] Install the plugin tarball alone until cap2ui5 0.2.0 is on npm A plain npm install answers ETARGET for cap2ui5@^0.2.0 until the release; installing the packed plugin brings the other dependencies along, measured on a fresh clone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XtDApnfmAncwrmW6fE4tCS --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1c938ab..43298e8 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,14 @@ original builds it. > **Status: first examples.** Nine of the 129 apps are ported, to agree on the > conventions below before the rest follows. They need **cap2ui5 0.2.0**, > which is not on npm yet (`ViewBuilder` and the facade members the samples -> use are in its `Unreleased` changelog). Until it is, install the plugin from -> a cap2UI5 checkout: +> use are in its `Unreleased` changelog). Until it is, a plain `npm install` +> answers `ETARGET`; install the plugin packed from a cap2UI5 checkout instead, +> which brings the other dependencies along: > > ```bash > (cd ../cap2UI5 && scripts/assemble-runtime.sh --package 1.145.0 && npm install \ > && npm pack --workspace plugin --pack-destination /tmp) -> npm install && npm install --no-save /tmp/cap2ui5-*.tgz +> npm install --no-save /tmp/cap2ui5-*.tgz > ``` ## Run From 5a9591cfac9c31b13dcf81e064144326b86bae95 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Mon, 28 Sep 2026 12:23:07 +0000 Subject: [PATCH 4/5] Port the samples line for line: the client and view builder by their ABAP names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cap2ui5 now hands main( ) abap2UI5's z2ui5_if_client under its own names and exports z2ui5_cl_ui5_view_builder called the way ABAP calls it. So the ports no longer translate - they are the originals, spelled the JavaScript way: `client->check_app_prev_stack( )` is `client.check_app_prev_stack()`, `->a( n = `title` v = … )` is `.a({ n: "title", v: … })`, and `me->client = client` is `this.client = client`, with the helpers under their ABAP names (view_display( ), on_event( ), log_step( ), …) in the original's order. Each file keeps the original's header (@keywords, @summary, @docs), its dispatcher, its texts - only the page title says cap2UI5, and a text that names an ABAP construct names the JavaScript one - and its layout, down to the aligned v column. 488 clears its result with `this.s_result = {}` as the original does with VALUE #( ), which needs the structure fix in the cap2ui5 branch. The README's translation table shrinks to what JavaScript actually changes: a field bound by its name, writes by assignment, and every field in the model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XtDApnfmAncwrmW6fE4tCS --- README.md | 103 ++++++++++--------- srv/apps/z2ui5_cl_smp_app_011.js | 166 +++++++++++++++---------------- srv/apps/z2ui5_cl_smp_app_125.js | 76 +++++++------- srv/apps/z2ui5_cl_smp_app_161.js | 121 +++++++++++----------- srv/apps/z2ui5_cl_smp_app_167.js | 99 +++++++++--------- srv/apps/z2ui5_cl_smp_app_488.js | 156 +++++++++++++++-------------- srv/apps/z2ui5_cl_smp_app_489.js | 116 ++++++++++----------- srv/apps/z2ui5_cl_smp_app_493.js | 66 ++++++------ srv/apps/z2ui5_cl_smp_app_494.js | 103 ++++++++++--------- srv/apps/z2ui5_cl_smp_app_495.js | 115 +++++++++++---------- test/samples.test.mjs | 16 +-- 11 files changed, 562 insertions(+), 575 deletions(-) diff --git a/README.md b/README.md index 43298e8..fd84fda 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,19 @@ The [abap2UI5 samples](https://github.com/abap2UI5/samples) as [cap2UI5](https://github.com/cap2UI5/cap2UI5) apps: every sample a plain -JavaScript class in `srv/apps/`, in an ordinary CAP project, its view built -with `ViewBuilder` - abap2UI5's own view builder - call for call as the ABAP -original builds it. +JavaScript class in `srv/apps/`, in an ordinary CAP project - and line for +line the ABAP original. The client is abap2UI5's `z2ui5_if_client` by its own +names, the view is built with `z2ui5_cl_ui5_view_builder`, called the way ABAP +calls it: `client->check_app_prev_stack( )` is +`client.check_app_prev_stack()`. > [!NOTE] > **Status: first examples.** Nine of the 129 apps are ported, to agree on the > conventions below before the rest follows. They need **cap2ui5 0.2.0**, -> which is not on npm yet (`ViewBuilder` and the facade members the samples -> use are in its `Unreleased` changelog). Until it is, a plain `npm install` -> answers `ETARGET`; install the plugin packed from a cap2UI5 checkout instead, -> which brings the other dependencies along: +> which is not on npm yet (the client under its ABAP names and the view +> builder are in its `Unreleased` changelog). Until it is, a plain +> `npm install` answers `ETARGET`; install the plugin packed from a cap2UI5 +> checkout instead, which brings the other dependencies along: > > ```bash > (cd ../cap2UI5 && scripts/assemble-runtime.sh --package 1.145.0 && npm install \ @@ -37,58 +39,63 @@ open e.g. <http://localhost:4004/sap/bc/z2ui5?app_start=Z2UI5_CL_SMP_APP_493>. |---|---|---| | Basics I - Hello World, the Smallest App | [`Z2UI5_CL_SMP_APP_493`](srv/apps/z2ui5_cl_smp_app_493.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_493.clas.abap) | | Basics II - Data Binding: Input and Button | [`Z2UI5_CL_SMP_APP_494`](srv/apps/z2ui5_cl_smp_app_494.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_494.clas.abap) | -| Basics III - Lifecycle: First Run, Event, Display | [`Z2UI5_CL_SMP_APP_495`](srv/apps/z2ui5_cl_smp_app_495.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_495.clas.abap) | +| Basics III - Lifecycle: Init, Event, Navigated | [`Z2UI5_CL_SMP_APP_495`](srv/apps/z2ui5_cl_smp_app_495.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_495.clas.abap) | | Table - Editable Cells, Add and Delete Rows | [`Z2UI5_CL_SMP_APP_011`](srv/apps/z2ui5_cl_smp_app_011.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_011.clas.abap) | -| Event - Extra Arguments with the Event | [`Z2UI5_CL_SMP_APP_167`](srv/apps/z2ui5_cl_smp_app_167.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_167.clas.abap) | +| Event - Extra Arguments with t_arg | [`Z2UI5_CL_SMP_APP_167`](srv/apps/z2ui5_cl_smp_app_167.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_167.clas.abap) | | Popup - Dialog inside a Dialog | [`Z2UI5_CL_SMP_APP_161`](srv/apps/z2ui5_cl_smp_app_161.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_161.clas.abap) | | Navigation - Return Data and Events to the Caller | [`Z2UI5_CL_SMP_APP_488`](srv/apps/z2ui5_cl_smp_app_488.js) (+ [`_489`](srv/apps/z2ui5_cl_smp_app_489.js)) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_488.clas.abap) | -| Browser - Set the Tab Title (A) | [`Z2UI5_CL_SMP_APP_125`](srv/apps/z2ui5_cl_smp_app_125.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_125.clas.abap) | +| Browser - Set the Tab Title | [`Z2UI5_CL_SMP_APP_125`](srv/apps/z2ui5_cl_smp_app_125.js) | [abap](https://github.com/abap2UI5/samples/blob/main/src/z2ui5_cl_smp_app_125.clas.abap) | ## How a sample is ported -**One ABAP class, one file, the same name.** `z2ui5_cl_smp_app_493.clas.abap` -becomes `srv/apps/z2ui5_cl_smp_app_493.js`, registered as -`Z2UI5_CL_SMP_APP_493` - so `?app_start=` is the same on both sides, a sample -that calls another one keeps calling it by its name, and `@origin` in the -file header points back at the original. +**1:1, so that it is recognized at a glance.** Put the ABAP original and the +port side by side and every line has its counterpart: -**The same structure.** The dispatcher is the original's `IF` / `ELSEIF` -chain, its methods keep their roles (`view_display` becomes `viewDisplay(c)`), -and the view is the original's chain with the same verbs, the same tree and -the same layout: one call per line, four spaces per tree level, `end()` in -the column of the `ele()` it closes. +- **One ABAP class, one file, the same name.** `z2ui5_cl_smp_app_493.clas.abap` + becomes `srv/apps/z2ui5_cl_smp_app_493.js`, registered as + `Z2UI5_CL_SMP_APP_493` - so `?app_start=` is the same on both sides, a + sample that calls another one keeps calling it by its name. The header + keeps the original's `@keywords`, `@summary` and `@docs`, and `@origin` + points back at it. +- **The same class.** The same attributes under the same names, the same + methods (`view_display( )`, `on_event( )`, …) in the same order, the same + dispatcher in `main( )`, `me->client = client` as `this.client = client`. +- **The same calls.** The client's methods and the view builder's are the ABAP + ones; a method's preferred parameter is its one positional argument and + parameters by name are one object with the ABAP names. The view keeps the + original's tree and layout: one call per line, four spaces per tree level, + `end()` in the column of the `ele()` it closes, the `v =` column aligned. +- **The same texts.** Only the page title says `cap2UI5 - …`, and a text that + names an ABAP construct (`z2ui5_if_app`, `client->…`) names the JavaScript + one. | abap2UI5 | cap2UI5 | |---|---| | `DATA name TYPE string.` | `name = "";` - the initial value is the type | -| `DATA t_tab TYPE STANDARD TABLE OF ty_s_row` | `t_tab = t.table({ …one row… });` | -| `client->check_on_init( )` | `c.isFirstRun` - seed state here | -| `client->check_on_navigated( )` | `c.isDisplay` - render here | -| `client->check_on_event( 'X' )`, `get_event( )` | `c.eventName === "X"` | -| `z2ui5_cl_ui5_view_builder=>factory( )->ele( n = … ns = … )` | `ViewBuilder.factory().ele(n, ns)` | -| `->a( n = … v = … )` / `b = …` / `t = …` | `.a(n, "…")` / `.a(n, true)` / `.a(n, { t: … })` | -| `client->view_display( view->stringify( ) )` | `c.view(view)` | -| `client->_bind( name )`, `_bind( s_order-customer )` | `c.bind("name")`, `c.bind("s_order.customer")` | -| `client->_bind( val = t_tab path = abap_true )` | `c.bind("t_tab", { path: true })` | -| `client->_event( val = 'X' t_arg = … )` | `c.event("X", [ … ])` | -| `client->get_event_arg( 1 )` | `c.eventArg(1)` | -| `client->check_app_prev_stack( )` | `c.canGoBack` | -| `client->_event_nav_app_leave( )` | `c.eventNavBack()` | -| `client->follow_up_action( val = cs_event-set_title … )` | `c.followUpAction("set_title", [ … ])` | -| `client->nav_app_call( NEW z…( ) )` | `c.navTo("Z…")` | -| `client->nav_app_leave( event = … r_data = … )` | `c.navBack({ event, data })` | -| `client->get( )-r_event_data` | `c.eventData` | -| `client->get_app_prev( )` | `c.prevApp` - the other app's fields as plain values | - -The page title says `cap2UI5 - …`, and the explaining texts name the -JavaScript API, not the ABAP one. +| `DATA t_tab TYPE STANDARD TABLE OF ty_s_row WITH EMPTY KEY.` | `t_tab = t.table({ …one row… });` | +| `METHOD z2ui5_if_app~main.` | `main(client) {` | +| `me->client = client.` / `client->…` in a method | `this.client = client;` / `this.client.…` | +| `IF client->check_on_navigated( ).` | `if (client.check_on_navigated()) {` | +| `client->_bind( name )`, `client->_bind( s_result-product )` | `client._bind("name")`, `client._bind("s_result-product")` | +| `client->_bind( val = t_tab path = abap_true )` | `client._bind({ val: "t_tab", path: true })` | +| ``client->_event( val = `X` arg = `…` )`` | `client._event({ val: "X", arg: "…" })` | +| ``z2ui5_cl_ui5_view_builder=>factory( )->ele( n = `View` ns = `mvc` )`` | `z2ui5_cl_ui5_view_builder.factory().ele({ n: "View", ns: "mvc" })` | +| ``)->a( n = `title` v = `…` )``, `b = abap_true`, `t = …` | `.a({ n: "title", v: "…" })`, `b: true`, `t: …` | +| `client->view_display( view->stringify( ) ).` | `client.view_display(view.stringify());` | +| `z2ui5_if_client=>cs_event-set_title` | `z2ui5_if_client.cs_event.set_title` | +| `client->nav_app_call( NEW z2ui5_cl_smp_app_493( ) )` | `client.nav_app_call("Z2UI5_CL_SMP_APP_493")` | +| `client->nav_app_leave( event = … r_data = … )` | `client.nav_app_leave({ event: …, r_data: … })` | +| `ASSIGN client->get( )-r_event_data->* TO …` | `client.get().r_event_data` - the data itself | +| ``CASE client->get_event( ). WHEN `A` OR `B`.`` | `switch (client.get_event()) { case "A": case "B":` | ### What differs from ABAP -1. **`this` reads plain copies.** Assign to write: - `this.t_tab = [...this.t_tab, row]`. A `push` on the copy is lost. -2. **Every field is part of the model.** ABAP keeps a `PROTECTED` attribute out - of it; a JavaScript field with an initial value is always bound. -3. **The render branch comes first.** A called app that returns arrives with - `c.isDisplay` *and* its event name set; `if (c.isDisplay) … else if - (c.eventName === …)` keeps the two apart. +1. **A field is bound by its name**, `client._bind("name")`: ABAP's `_bind( )` + finds the attribute by reference, which a JavaScript value cannot carry. +2. **`this` reads plain copies.** Assign to write: + `this.t_tab = [...this.t_tab, row]` for `INSERT … INTO TABLE t_tab`. A + `push` on the copy is lost. Assigning an object replaces a structure, so + `this.s_result = {}` is `s_result = VALUE #( )`. +3. **Every field is part of the model.** ABAP keeps a `PROTECTED` attribute out + of it; a JavaScript field with an initial value is always bound. The client + is the exception: assigned in `main( )` and not declared, it stays out. diff --git a/srv/apps/z2ui5_cl_smp_app_011.js b/srv/apps/z2ui5_cl_smp_app_011.js index a4bac02..68ddc9e 100644 --- a/srv/apps/z2ui5_cl_smp_app_011.js +++ b/srv/apps/z2ui5_cl_smp_app_011.js @@ -1,150 +1,148 @@ -/** - * Table - Editable Cells, Add and Delete Rows - * - * An editable table: input cells, adding and deleting rows, multi-select and a - * toolbar over them. - * - * @keywords edit input add row delete multiselect toolbar - * @origin abap2UI5/samples src/z2ui5_cl_smp_app_011.clas.abap - */ -import { defineApp, t, ViewBuilder } from "cap2ui5"; +// @keywords edit input add row delete multiselect toolbar +// @summary An editable table: input cells, adding and deleting rows, multi-select and a toolbar over them. +// @docs https://abap2ui5.github.io/docs/cookbook/model/tables https://abap2ui5.github.io/docs/tutorials/walkthrough/step-8 https://abap2ui5.github.io/docs/tutorials/walkthrough/step-10 +// @origin abap2UI5/samples src/z2ui5_cl_smp_app_011.clas.abap +import { defineApp, t, z2ui5_cl_ui5_view_builder } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_011", class { + t_tab = t.table({ - selkz: false, - title: "", - value: "", - descr: "", - icon: "", - info: "", + selkz: false, + title: "", + value: "", + descr: "", + icon: "", + info: "", editable: false, checkbox: false, }); + check_editable_active = false; - viewDisplay(c) { + view_display() { - const view = ViewBuilder.factory() - .ele("View", "mvc") - .a("displayBlock", "true") - .a("height", "100%") - .a("xmlns", "sap.m") - .a("xmlns:mvc", "sap.ui.core.mvc"); + const 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" }); const page = view.ele("Shell") .ele("Page") - .a("title", "cap2UI5 - Table - Editable Cells, Add and Delete Rows") - .a("showNavButton", c.canGoBack) - .a("navButtonPress", c.eventNavBack()) - .a("id", "test2"); + .a({ n: "title", v: "cap2UI5 - Table - Editable Cells, Add and Delete Rows" }) + .a({ n: "showNavButton", b: this.client.check_app_prev_stack() }) + .a({ n: "navButtonPress", v: this.client._event_nav_app_leave() }) + .a({ n: "id", v: "test2" }); page.tag("MessageStrip") - .a("text", "A MultiSelect table whose input cells switch between display and edit mode via the " + - "toolbar, which also adds new rows and deletes the currently selected ones.") - .a("type", "Information") - .a("showIcon", true) - .a("class", "sapUiSmallMargin"); + .a({ n: "text", v: "A MultiSelect table whose input cells switch between display and edit mode via the " + + "toolbar, which also adds new rows and deletes the currently selected ones." }) + .a({ n: "type", v: "Information" }) + .a({ n: "showIcon", b: true }) + .a({ n: "class", v: "sapUiSmallMargin" }); const tab = page.ele("Table") - .a("items", `{path: '${c.bind("t_tab", { path: true })}', templateShareable: false}`) - .a("mode", "MultiSelect") + .a({ n: "items", v: `{path: '${this.client._bind({ val: "t_tab", path: true })}', templateShareable: false}` }) + .a({ n: "mode", v: "MultiSelect" }) .ele("headerToolbar") .ele("OverflowToolbar") .tag("Title") - .a("text", "title of the table") + .a({ n: "text", v: "title of the table" }) .tag("Button") // shows an OverflowToolbar filling up - the press is a plain roundtrip - .a("press", c.event("BUTTON_TEST")) - .a("text", "test") + .a({ n: "press", v: this.client._event("BUTTON_TEST") }) + .a({ n: "text", v: "test" }) .tag("ToolbarSpacer") .tag("Button") - .a("press", c.event("BUTTON_DELETE")) - .a("text", "delete selected row") - .a("icon", "sap-icon://delete") + .a({ n: "press", v: this.client._event("BUTTON_DELETE") }) + .a({ n: "text", v: "delete selected row" }) + .a({ n: "icon", v: "sap-icon://delete" }) .tag("Button") - .a("press", c.event("BUTTON_ADD")) - .a("text", "add") - .a("icon", "sap-icon://add") + .a({ n: "press", v: this.client._event("BUTTON_ADD") }) + .a({ n: "text", v: "add" }) + .a({ n: "icon", v: "sap-icon://add" }) .tag("Button") - .a("press", c.event("BUTTON_EDIT")) - .a("text", { t: this.check_editable_active ? "display" : "edit" }) - .a("tooltip", "Switch the cells between display and edit mode") - .a("icon", "sap-icon://edit") + .a({ n: "press", v: this.client._event("BUTTON_EDIT") }) + .a({ n: "text", t: this.check_editable_active ? "display" : "edit" }) + .a({ n: "tooltip", v: "Switch the cells between display and edit mode" }) + .a({ n: "icon", v: "sap-icon://edit" }) .end() .end(); tab.ele("columns") .ele("Column") .tag("Text") - .a("text", "Title") + .a({ n: "text", v: "Title" }) .end() .ele("Column") .tag("Text") - .a("text", "Color") + .a({ n: "text", v: "Color" }) .end() .ele("Column") .tag("Text") - .a("text", "Info") + .a({ n: "text", v: "Info" }) .end() .ele("Column") .tag("Text") - .a("text", "Description") + .a({ n: "text", v: "Description" }) .end() .ele("Column") .tag("Text") - .a("text", "Checkbox"); + .a({ n: "text", v: "Checkbox" }); tab.ele("items") .ele("ColumnListItem") - .a("selected", "{SELKZ}") + .a({ n: "selected", v: "{SELKZ}" }) .ele("cells") .tag("Input") - .a("id", "test") - .a("enabled", "{EDITABLE}") - .a("value", "{TITLE}") + .a({ n: "id", v: "test" }) + .a({ n: "enabled", v: "{EDITABLE}" }) + .a({ n: "value", v: "{TITLE}" }) .tag("Input") - .a("enabled", "{EDITABLE}") - .a("value", "{VALUE}") + .a({ n: "enabled", v: "{EDITABLE}" }) + .a({ n: "value", v: "{VALUE}" }) .tag("Input") - .a("enabled", "{EDITABLE}") - .a("value", "{INFO}") + .a({ n: "enabled", v: "{EDITABLE}" }) + .a({ n: "value", v: "{INFO}" }) .tag("Input") - .a("enabled", "{EDITABLE}") - .a("value", "{DESCR}") + .a({ n: "enabled", v: "{EDITABLE}" }) + .a({ n: "value", v: "{DESCR}" }) .tag("CheckBox") - .a("selected", "{CHECKBOX}") - .a("enabled", "{EDITABLE}"); + .a({ n: "selected", v: "{CHECKBOX}" }) + .a({ n: "enabled", v: "{EDITABLE}" }); - c.view(view); + this.client.view_display(view.stringify()); } - main(c) { + main(client) { + + this.client = client; - if (c.isFirstRun) { + if (client.check_on_init()) { this.check_editable_active = false; - this.t_tab = [ - { title: "entry 01", value: "red", info: "completed", descr: "this is a description", checkbox: true }, - { title: "entry 02", value: "blue", info: "completed", descr: "this is a description", checkbox: true }, - { title: "entry 03", value: "green", info: "completed", descr: "this is a description", checkbox: true }, - { title: "entry 04", value: "orange", info: "completed", descr: "", checkbox: true }, - { title: "entry 05", value: "grey", info: "completed", descr: "this is a description", checkbox: true }, - {}, - ]; - - this.viewDisplay(c); - } else if (c.isDisplay) { - this.viewDisplay(c); - - } else if (c.eventName === "BUTTON_EDIT") { - // a read is a copy of the table: the changed rows are written back whole + this.t_tab = [ + { title: "entry 01", value: "red", info: "completed", descr: "this is a description", checkbox: true }, + { title: "entry 02", value: "blue", info: "completed", descr: "this is a description", checkbox: true }, + { title: "entry 03", value: "green", info: "completed", descr: "this is a description", checkbox: true }, + { title: "entry 04", value: "orange", info: "completed", descr: "", checkbox: true }, + { title: "entry 05", value: "grey", info: "completed", descr: "this is a description", checkbox: true }, + { } ]; + + this.view_display(); + } else if (client.check_on_navigated()) { + this.view_display(); + + } else if (client.check_on_event("BUTTON_EDIT")) { this.check_editable_active = !this.check_editable_active; + // a read of t_tab is a copy: the changed rows are written back whole this.t_tab = this.t_tab.map((row) => ({ ...row, editable: this.check_editable_active })); - } else if (c.eventName === "BUTTON_DELETE") { + } else if (client.check_on_event("BUTTON_DELETE")) { this.t_tab = this.t_tab.filter((row) => !row.selkz); - } else if (c.eventName === "BUTTON_ADD") { + } else if (client.check_on_event("BUTTON_ADD")) { this.t_tab = [...this.t_tab, { editable: this.check_editable_active }]; } diff --git a/srv/apps/z2ui5_cl_smp_app_125.js b/srv/apps/z2ui5_cl_smp_app_125.js index 97a6533..905b295 100644 --- a/srv/apps/z2ui5_cl_smp_app_125.js +++ b/srv/apps/z2ui5_cl_smp_app_125.js @@ -1,57 +1,55 @@ -/** - * Browser - Set the Tab Title (A) - * - * Sets the browser tab title from the app, so a bookmarked or duplicated window - * says which app it holds. - * - * @keywords document.title tab caption headline set_title followUpAction - * @origin abap2UI5/samples src/z2ui5_cl_smp_app_125.clas.abap - */ -import { defineApp, ViewBuilder } from "cap2ui5"; +// @keywords document.title tab caption headline set_title +// @summary Sets the browser tab title from the app, so a bookmarked or duplicated window says which app it holds. +// @docs https://abap2ui5.github.io/docs/cookbook/browser_interaction/title +// @origin abap2UI5/samples src/z2ui5_cl_smp_app_125.clas.abap +import { defineApp, z2ui5_cl_ui5_view_builder, z2ui5_if_client } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_125", class { + title = "my title"; - main(c) { + main(client) { - if (c.isDisplay) { + if (client.check_on_navigated()) { - const view = ViewBuilder.factory() - .ele("View", "mvc") - .a("displayBlock", "true") - .a("height", "100%") - .a("xmlns", "sap.m") - .a("xmlns:mvc", "sap.ui.core.mvc") - .a("xmlns:form", "sap.ui.layout.form"); + const 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" }); const page = view.ele("Shell") .ele("Page") - .a("title", "cap2UI5 - Browser - Set the Tab Title") - .a("showNavButton", c.canGoBack) - .a("navButtonPress", c.eventNavBack()); + .a({ n: "title", v: "cap2UI5 - Browser - Set the Tab Title" }) + .a({ n: "showNavButton", b: client.check_app_prev_stack() }) + .a({ n: "navButtonPress", v: client._event_nav_app_leave() }); page.tag("MessageStrip") - .a("text", "Enter a title and press the button to run the set_title front-end action, which updates " + - "the browser tab title (document.title) without reloading the page.") - .a("type", "Information") - .a("showIcon", true) - .a("class", "sapUiSmallMargin"); - - page.ele("SimpleForm", "form") - .a("title", "Form Title") - .a("editable", true) - .ele("content", "form") + .a({ n: "text", v: "Enter a title and press the button to run the set_title front-end action, which updates " + + "the browser tab title (document.title) without reloading the page." }) + .a({ n: "type", v: "Information" }) + .a({ n: "showIcon", b: true }) + .a({ n: "class", v: "sapUiSmallMargin" }); + + page.ele({ n: "SimpleForm", ns: "form" }) + .a({ n: "title", v: "Form Title" }) + .a({ n: "editable", b: true }) + .ele({ n: "content", ns: "form" }) .tag("Label") - .a("text", "title") + .a({ n: "text", v: "title" }) .tag("Input") - .a("value", c.bind("title")) + .a({ n: "value", v: client._bind("title") }) .tag("Button") - .a("press", c.event("SET_TITLE")) - .a("text", "Set Title"); - c.view(view); + .a({ n: "press", v: client._event("SET_TITLE") }) + .a({ n: "text", v: "Set Title" }); + client.view_display(view.stringify()); - } else if (c.eventName === "SET_TITLE") { + } else if (client.check_on_event("SET_TITLE")) { - c.followUpAction("set_title", [this.title]); + client.follow_up_action({ + val: z2ui5_if_client.cs_event.set_title, + t_arg: [this.title] }); } diff --git a/srv/apps/z2ui5_cl_smp_app_161.js b/srv/apps/z2ui5_cl_smp_app_161.js index 53425ad..865f9ee 100644 --- a/srv/apps/z2ui5_cl_smp_app_161.js +++ b/srv/apps/z2ui5_cl_smp_app_161.js @@ -1,126 +1,123 @@ -/** - * Popup - Dialog inside a Dialog - * - * A dialog opened from inside a dialog, and what closing the inner one does to - * the stack. - * - * @keywords nested stack popup in popup second dialog - * @origin abap2UI5/samples src/z2ui5_cl_smp_app_161.clas.abap - */ -import { defineApp, ViewBuilder } from "cap2ui5"; +// @keywords nested stack popup in popup second dialog +// @summary A dialog opened from inside a dialog, and what closing the inner one does to the stack. +// @docs https://abap2ui5.github.io/docs/cookbook/popup_popover/popup +// @origin abap2UI5/samples src/z2ui5_cl_smp_app_161.clas.abap +import { defineApp, z2ui5_cl_ui5_view_builder } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_161", class { - simplePopup1(c) { + simple_popup1() { - const popup = ViewBuilder.factory() - .ele("FragmentDefinition", "core") - .a("xmlns", "sap.m") - .a("xmlns:core", "sap.ui.core"); + const 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" }); const dialog = popup.ele("Dialog") - .a("afterClose", c.event("BTN_OK_1ND")) + .a({ n: "afterClose", v: this.client._event("BTN_OK_1ND") }) .ele("content"); dialog.tag("Button") - .a("press", c.event("GOTO_2ND")) - .a("text", "Open 2nd popup"); + .a({ n: "press", v: this.client._event("GOTO_2ND") }) + .a({ n: "text", v: "Open 2nd popup" }); dialog.end() .ele("buttons") .tag("Button") - .a("press", c.event("BTN_OK_1ND")) - .a("text", "OK") - .a("type", "Emphasized"); + .a({ n: "press", v: this.client._event("BTN_OK_1ND") }) + .a({ n: "text", v: "OK" }) + .a({ n: "type", v: "Emphasized" }); - c.popup(popup); + this.client.popup_display(popup.stringify()); } - simplePopup2(c) { + simple_popup2() { - const popup = ViewBuilder.factory() - .ele("FragmentDefinition", "core") - .a("xmlns", "sap.m") - .a("xmlns:core", "sap.ui.core"); + const 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" }); const dialog = popup.ele("Dialog") - .a("afterClose", c.event("BTN_OK_2ND")) + .a({ n: "afterClose", v: this.client._event("BTN_OK_2ND") }) .ele("content"); dialog.tag("Label") - .a("text", "this is a second popup"); + .a({ n: "text", v: "this is a second popup" }); dialog.end() .ele("buttons") .tag("Button") - .a("press", c.event("BTN_OK_2ND")) - .a("text", "GOTO 1ST POPUP") - .a("type", "Emphasized"); + .a({ n: "press", v: this.client._event("BTN_OK_2ND") }) + .a({ n: "text", v: "GOTO 1ST POPUP" }) + .a({ n: "type", v: "Emphasized" }); - c.popup(popup); + this.client.popup_display(popup.stringify()); } - viewDisplay(c) { + view_display() { - const view = ViewBuilder.factory() - .ele("View", "mvc") - .a("displayBlock", "true") - .a("height", "100%") - .a("xmlns", "sap.m") - .a("xmlns:mvc", "sap.ui.core.mvc"); + const 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" }); const page = view.ele("Shell") .ele("Page") - .a("title", "cap2UI5 - Popup - Dialog inside a Dialog") - .a("showNavButton", c.canGoBack) - .a("navButtonPress", c.eventNavBack()); + .a({ n: "title", v: "cap2UI5 - Popup - Dialog inside a Dialog" }) + .a({ n: "showNavButton", b: this.client.check_app_prev_stack() }) + .a({ n: "navButtonPress", v: this.client._event_nav_app_leave() }); page.tag("MessageStrip") - .a("text", "This sample opens a popup from a button and then chains to a second popup " + - "from within the first one.") - .a("type", "Information") - .a("showIcon", true) - .a("class", "sapUiSmallMargin"); + .a({ n: "text", v: "This sample opens a popup from a button and then chains to a second popup " + + "from within the first one." }) + .a({ n: "type", v: "Information" }) + .a({ n: "showIcon", b: true }) + .a({ n: "class", v: "sapUiSmallMargin" }); page.tag("Button") - .a("press", c.event("POPUP")) - .a("text", "Open Popup..."); + .a({ n: "press", v: this.client._event("POPUP") }) + .a({ n: "text", v: "Open Popup..." }); - c.view(view); + this.client.view_display(view.stringify()); } - onEvent(c) { + on_event() { - switch (c.eventName) { + switch (this.client.get_event()) { case "GOTO_2ND": - this.simplePopup2(c); + this.simple_popup2(); break; case "BTN_OK_2ND": - c.popupClose(); - this.simplePopup1(c); + this.client.popup_destroy(); + this.simple_popup1(); break; case "BTN_OK_1ND": - c.popupClose(); + this.client.popup_destroy(); break; case "POPUP": - this.simplePopup1(c); + this.simple_popup1(); break; } } - main(c) { + main(client) { - if (c.isDisplay) { - this.viewDisplay(c); + this.client = client; + if (client.check_on_navigated()) { + this.view_display(); return; } - this.onEvent(c); + this.on_event(); } + }); diff --git a/srv/apps/z2ui5_cl_smp_app_167.js b/srv/apps/z2ui5_cl_smp_app_167.js index 69e1dd0..531e477 100644 --- a/srv/apps/z2ui5_cl_smp_app_167.js +++ b/srv/apps/z2ui5_cl_smp_app_167.js @@ -1,86 +1,79 @@ -/** - * Event - Extra Arguments with the Event - * - * Sends extra arguments with an event (c.event( name, [args] )), so a handler - * knows which row, which value or which fixed payload it was called for. - * - * @keywords argument parameter payload event data fixed value eventArg - * @origin abap2UI5/samples src/z2ui5_cl_smp_app_167.clas.abap - */ -import { defineApp, ViewBuilder } from "cap2ui5"; +// @keywords argument parameter payload event data fixed value +// @summary Sends extra arguments with an event (t_arg), so a handler knows which row, which value or which fixed payload it was called for. +// @docs https://abap2ui5.github.io/docs/cookbook/event_navigation/backend https://abap2ui5.github.io/docs/tutorials/walkthrough/step-6 +// @origin abap2UI5/samples src/z2ui5_cl_smp_app_167.clas.abap +import { defineApp, z2ui5_cl_ui5_view_builder } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_167", class { + mv_value = ""; - viewDisplay(c) { + view_display() { - const view = ViewBuilder.factory() - .ele("View", "mvc") - .a("displayBlock", "true") - .a("height", "100%") - .a("xmlns", "sap.m") - .a("xmlns:mvc", "sap.ui.core.mvc"); + const 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" }); const page = view.ele("Shell") .ele("Page") - .a("title", "cap2UI5 - Event - Extra Arguments with the Event") - .a("showNavButton", c.canGoBack) - .a("navButtonPress", c.eventNavBack()); + .a({ n: "title", v: "cap2UI5 - Event - Extra Arguments with t_arg" }) + .a({ n: "showNavButton", b: this.client.check_app_prev_stack() }) + .a({ n: "navButtonPress", v: this.client._event_nav_app_leave() }); page.tag("MessageStrip") - .a("text", "This sample shows how to pass extra arguments to an event via c.event( name, [args] ) - " + - "fixed values, model values, or client-side expressions - and read them in the backend.") - .a("type", "Information") - .a("showIcon", true) - .a("class", "sapUiSmallMargin"); + .a({ n: "text", v: "This sample shows how to pass extra arguments to an event via t_arg - fixed " + + "values, model values, or client-side expressions - and read them in the backend." }) + .a({ n: "type", v: "Information" }) + .a({ n: "showIcon", b: true }) + .a({ n: "class", v: "sapUiSmallMargin" }); page.tag("Link") - .a("text", "More information...") - .a("target", "_blank") - .a("href", "https://sdk.openui5.org/topic/b0fb4de7364f4bcbb053a99aa645affe"); + .a({ n: "text", v: "More information..." }) + .a({ n: "target", v: "_blank" }) + .a({ n: "href", v: "https://sdk.openui5.org/topic/b0fb4de7364f4bcbb053a99aa645affe" }); page.tag("Button") - .a("press", c.event("EVENT_FIX_VAL", ["FIX_VAL"])) - .a("text", "EVENT_FIX_VAL"); + .a({ n: "press", v: this.client._event({ val: "EVENT_FIX_VAL", arg: "FIX_VAL" }) }) + .a({ n: "text", v: "EVENT_FIX_VAL" }); page.tag("Input") - .a("value", c.bind("mv_value")); + .a({ n: "value", v: this.client._bind("mv_value") }); page.tag("Button") - .a("press", c.event("EVENT_MODEL_VALUE", ["$" + c.bind("mv_value")])) - .a("text", "EVENT_MODEL_VALUE"); + .a({ n: "press", v: this.client._event({ val: "EVENT_MODEL_VALUE", arg: "$" + this.client._bind("mv_value") }) }) + .a({ n: "text", v: "EVENT_MODEL_VALUE" }); page.tag("Button") - .a("press", c.event("SOURCE_PROPERTY_TEXT", ["${$source>/text}"])) - .a("text", "SOURCE_PROPERTY_TEXT"); + .a({ n: "press", v: this.client._event({ val: "SOURCE_PROPERTY_TEXT", arg: "${$source>/text}" }) }) + .a({ n: "text", v: "SOURCE_PROPERTY_TEXT" }); page.tag("Input") - .a("description", "make an input and press enter - ") - .a("submit", c.event("EVENT_PROPERTY_VALUE", ["${$parameters>/value}"])); + .a({ n: "description", v: "make an input and press enter - " }) + .a({ n: "submit", v: this.client._event({ val: "EVENT_PROPERTY_VALUE", arg: "${$parameters>/value}" }) }); page.tag("Button") - .a("press", c.event("PARENT_PROPERTY_ID", ["$event.oSource.oParent.sId"])) - .a("text", "PARENT_PROPERTY_ID"); + .a({ n: "press", v: this.client._event({ val: "PARENT_PROPERTY_ID", arg: "$event.oSource.oParent.sId" }) }) + .a({ n: "text", v: "PARENT_PROPERTY_ID" }); - c.view(view); + this.client.view_display(view.stringify()); } - main(c) { + main(client) { + + this.client = client; - if (c.isFirstRun) { + if (client.check_on_init()) { this.mv_value = "my value"; - this.viewDisplay(c); - } else if (c.isDisplay) { - this.viewDisplay(c); + this.view_display(); + } else if (client.check_on_navigated()) { + this.view_display(); } - switch (c.eventName) { - case "EVENT_FIX_VAL": - case "EVENT_MODEL_VALUE": - case "SOURCE_PROPERTY_TEXT": - case "EVENT_PROPERTY_VALUE": - case "PARENT_PROPERTY_ID": - c.messageBox(`backend event: ${c.eventArg(1)}`); - break; + switch (client.get_event()) { + case "EVENT_FIX_VAL": case "EVENT_MODEL_VALUE": case "SOURCE_PROPERTY_TEXT": case "EVENT_PROPERTY_VALUE": case "PARENT_PROPERTY_ID": + client.message_box_display(`backend event: ${client.get_event_arg()}`); } } diff --git a/srv/apps/z2ui5_cl_smp_app_488.js b/srv/apps/z2ui5_cl_smp_app_488.js index b579f9d..91655a7 100644 --- a/srv/apps/z2ui5_cl_smp_app_488.js +++ b/srv/apps/z2ui5_cl_smp_app_488.js @@ -1,127 +1,131 @@ -/** - * Navigation - Return Data and Events to the Caller - * - * The way back carries data: the called app returns an event name and a - * payload (data) that the caller reads as c.eventData. - * - * Calls a second app (Z2UI5_CL_SMP_APP_489) with c.navTo( ). The called app - * comes back with c.navBack( { event, data } ), handing an event name and a - * data payload to its caller without knowing who called it. On return this - * app enters main( ) with c.isDisplay and reads both: the event name from - * c.eventName, the payload from c.eventData. - * - * @keywords navBack data eventData result return event payload - * @origin abap2UI5/samples src/z2ui5_cl_smp_app_488.clas.abap - */ -import { defineApp, ViewBuilder } from "cap2ui5"; +// @keywords r_data result get_app_prev return event payload +// @summary The way back carries data: the called app returns an event name and a payload (r_data) that the caller reads from get_app_prev. +// @docs https://abap2ui5.github.io/docs/cookbook/event_navigation/navigation/inner_app +// @origin abap2UI5/samples src/z2ui5_cl_smp_app_488.clas.abap +// +// Calls a second app (z2ui5_cl_smp_app_489) via client.nav_app_call( ). The +// called app comes back with client.nav_app_leave( { event, r_data } ), +// handing an event name and a data payload to its caller without knowing who +// called it. On return this app enters main( ) via check_on_navigated( ) and +// reads both from client.get( ): the event name from .event, the payload from +// .r_event_data. +import { defineApp, z2ui5_cl_ui5_view_builder } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_488", class { - s_result = { product: "", quantity: "" }; + + s_result = { product: "", quantity: "" }; // z2ui5_cl_smp_app_489=>ty_s_result returned_event = ""; - main(c) { + main(client) { + + this.client = client; - if (c.isFirstRun) { - this.viewDisplay(c); + if (client.check_on_init()) { + this.view_display(); - } else if (c.isDisplay) { - this.onNavigation(c); + } else if (client.check_on_navigated()) { + this.on_navigation(); - } else if (c.eventName === "CALL_APP") { - c.navTo("Z2UI5_CL_SMP_APP_489"); + } else if (client.check_on_event("CALL_APP")) { + client.nav_app_call("Z2UI5_CL_SMP_APP_489"); } } - onNavigation(c) { + on_navigation() { - this.returned_event = c.eventName; + const ls_get = this.client.get(); + this.returned_event = ls_get.event; switch (this.returned_event) { - case "DATA_CONFIRMED": + case "DATA_CONFIRMED": { - // the payload handed over by navBack( { data } ) arrives typed, as - // plain values - the receiver decides what to do with it - if (c.eventData) { + // the payload handed over by nav_app_leave( r_data = ... ) arrives as + // plain data - the receiver decides what it is + const s_result = ls_get.r_event_data; - this.s_result = c.eventData; - c.messageToast(`Returned event ${this.returned_event}, ` + - `product ${this.s_result.product}, quantity ${this.s_result.quantity}`); + if (s_result) { + + this.s_result = s_result; + this.client.message_toast_display(`Returned event ${this.returned_event}, ` + + `product ${this.s_result.product}, quantity ${this.s_result.quantity}`); } break; + } case "DATA_CANCELLED": - this.s_result = { product: "", quantity: "" }; - c.messageToast("Returned event DATA_CANCELLED, no data passed"); + this.s_result = {}; + this.client.message_toast_display("Returned event DATA_CANCELLED, no data passed"); break; } - this.viewDisplay(c); + this.view_display(); } - viewDisplay(c) { + view_display() { - const view = ViewBuilder.factory() - .ele("View", "mvc") - .a("displayBlock", "true") - .a("height", "100%") - .a("xmlns", "sap.m") - .a("xmlns:mvc", "sap.ui.core.mvc") - .a("xmlns:form", "sap.ui.layout.form") - .a("xmlns:layout", "sap.ui.layout"); + const 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:layout", v: "sap.ui.layout" }); const page = view.ele("Shell") .ele("Page") - .a("title", "cap2UI5 - Navigation - Return Data and Events to the Caller") - .a("showNavButton", c.canGoBack) - .a("navButtonPress", c.eventNavBack()); + .a({ n: "title", v: "cap2UI5 - Navigation - Return Data and Events to the Caller" }) + .a({ n: "showNavButton", b: this.client.check_app_prev_stack() }) + .a({ n: "navButtonPress", v: this.client._event_nav_app_leave() }); page.tag("MessageStrip") - .a("text", "Calls a second app that returns via c.navBack( ) with an event and a data payload. " + - "On return this app reads both - c.eventName and c.eventData - in its c.isDisplay " + - "branch and shows them below.") - .a("type", "Information") - .a("showIcon", true) - .a("class", "sapUiSmallMargin"); - - const form = page.ele("Grid", "layout") - .a("defaultSpan", "L6 M12 S12") - .ele("content", "layout") - .ele("SimpleForm", "form") - .a("title", "Result returned by the called app") - .a("editable", true) - .ele("content", "form"); + .a({ n: "text", v: "Calls a second app that returns via nav_app_leave with an event and a data " + + "payload (r_data). On return this app reads both from client.get( ) in its " + + "check_on_navigated( ) branch and shows them below." }) + .a({ n: "type", v: "Information" }) + .a({ n: "showIcon", b: true }) + .a({ n: "class", v: "sapUiSmallMargin" }); + + const form = page.ele({ n: "Grid", ns: "layout" }) + .a({ n: "defaultSpan", v: "L6 M12 S12" }) + .ele({ n: "content", ns: "layout" }) + .ele({ n: "SimpleForm", ns: "form" }) + .a({ n: "title", v: "Result returned by the called app" }) + .a({ n: "editable", b: true }) + .ele({ n: "content", ns: "form" }); form.tag("Label") - .a("text", "Open the input app"); + .a({ n: "text", v: "Open the input app" }); form.tag("Button") - .a("press", c.event("CALL_APP")) - .a("text", "call app (c.navTo)") - .a("type", "Emphasized"); + .a({ n: "press", v: this.client._event("CALL_APP") }) + .a({ n: "text", v: "call app (nav_app_call)" }) + .a({ n: "type", v: "Emphasized" }); form.tag("Label") - .a("text", "Returned event"); + .a({ n: "text", v: "Returned event" }); form.tag("Input") - .a("enabled", false) - .a("value", c.bind("returned_event")); + .a({ n: "enabled", b: false }) + .a({ n: "value", v: this.client._bind("returned_event") }); form.tag("Label") - .a("text", "Returned product"); + .a({ n: "text", v: "Returned product" }); form.tag("Input") - .a("enabled", false) - .a("value", c.bind("s_result.product")); + .a({ n: "enabled", b: false }) + .a({ n: "value", v: this.client._bind("s_result-product") }); form.tag("Label") - .a("text", "Returned quantity"); + .a({ n: "text", v: "Returned quantity" }); form.tag("Input") - .a("enabled", false) - .a("value", c.bind("s_result.quantity")); + .a({ n: "enabled", b: false }) + .a({ n: "value", v: this.client._bind("s_result-quantity") }); - c.view(view); + this.client.view_display(view.stringify()); } + }); diff --git a/srv/apps/z2ui5_cl_smp_app_489.js b/srv/apps/z2ui5_cl_smp_app_489.js index 6029c0f..8c50400 100644 --- a/srv/apps/z2ui5_cl_smp_app_489.js +++ b/srv/apps/z2ui5_cl_smp_app_489.js @@ -1,90 +1,92 @@ -/** - * Navigation - Data Input App (called by Z2UI5_CL_SMP_APP_488) - * - * Returns to its caller with c.navBack( { event, data } ) - handing back an - * event name and the entered data without knowing which app called it (no - * c.prevApp). A hidden helper, never listed on its own in the overview. - * - * @origin abap2UI5/samples src/z2ui5_cl_smp_app_489.clas.abap - */ -import { defineApp, ViewBuilder } from "cap2ui5"; +// @origin abap2UI5/samples src/z2ui5_cl_smp_app_489.clas.abap +// +// Input page called by z2ui5_cl_smp_app_488. It returns to its caller with +// client.nav_app_leave( { event, r_data } ) - handing back an event name and +// the entered data without knowing which app called it (no get_app_prev( ), +// no look at the caller's fields). This app is a hidden helper (never listed +// on its own in the overview). +import { defineApp, z2ui5_cl_ui5_view_builder } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_489", class { - s_result = { product: "", quantity: "" }; - main(c) { + s_result = { product: "", quantity: "" }; // ty_s_result - if (c.isFirstRun) { + main(client) { + + this.client = client; + + if (client.check_on_init()) { this.s_result = { product: "Notebook Basic 15", quantity: "2" }; - this.viewDisplay(c); + this.view_display(); - } else if (c.isDisplay) { - this.viewDisplay(c); + } else if (client.check_on_navigated()) { + this.view_display(); - } else if (c.eventName === "CONFIRM") { - c.navBack({ event: "DATA_CONFIRMED", data: this.s_result }); + } else if (client.check_on_event("CONFIRM")) { + client.nav_app_leave({ event: "DATA_CONFIRMED", r_data: this.s_result }); - } else if (c.eventName === "CANCEL") { - c.navBack({ event: "DATA_CANCELLED" }); + } else if (client.check_on_event("CANCEL")) { + client.nav_app_leave({ event: "DATA_CANCELLED" }); } } - viewDisplay(c) { + view_display() { - const view = ViewBuilder.factory() - .ele("View", "mvc") - .a("displayBlock", "true") - .a("height", "100%") - .a("xmlns", "sap.m") - .a("xmlns:mvc", "sap.ui.core.mvc") - .a("xmlns:form", "sap.ui.layout.form") - .a("xmlns:layout", "sap.ui.layout"); + const 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:layout", v: "sap.ui.layout" }); const page = view.ele("Shell") .ele("Page") - .a("title", "cap2UI5 - Navigation - Data Input App") - .a("showNavButton", c.canGoBack) - .a("navButtonPress", c.eventNavBack()); + .a({ n: "title", v: "cap2UI5 - Navigation - Data Input App" }) + .a({ n: "showNavButton", b: this.client.check_app_prev_stack() }) + .a({ n: "navButtonPress", v: this.client._event_nav_app_leave() }); page.tag("MessageStrip") - .a("text", "Change the data and return: 'confirm' leaves with event DATA_CONFIRMED plus the " + - "entered data, 'cancel' leaves with event DATA_CANCELLED and no data. " + - "The nav-back button of the page leaves without an event.") - .a("type", "Information") - .a("showIcon", true) - .a("class", "sapUiSmallMargin"); - - const form = page.ele("Grid", "layout") - .a("defaultSpan", "L6 M12 S12") - .ele("content", "layout") - .ele("SimpleForm", "form") - .a("title", "Data returned to the caller") - .a("editable", true) - .ele("content", "form"); + .a({ n: "text", v: "Change the data and return: 'confirm' leaves with event DATA_CONFIRMED plus the " + + "entered data as r_data, 'cancel' leaves with event DATA_CANCELLED and no data. " + + "The nav-back button of the page leaves without an event." }) + .a({ n: "type", v: "Information" }) + .a({ n: "showIcon", b: true }) + .a({ n: "class", v: "sapUiSmallMargin" }); + + const form = page.ele({ n: "Grid", ns: "layout" }) + .a({ n: "defaultSpan", v: "L6 M12 S12" }) + .ele({ n: "content", ns: "layout" }) + .ele({ n: "SimpleForm", ns: "form" }) + .a({ n: "title", v: "Data returned to the caller" }) + .a({ n: "editable", b: true }) + .ele({ n: "content", ns: "form" }); form.tag("Label") - .a("text", "Product"); + .a({ n: "text", v: "Product" }); form.tag("Input") - .a("value", c.bind("s_result.product")); + .a({ n: "value", v: this.client._bind("s_result-product") }); form.tag("Label") - .a("text", "Quantity"); + .a({ n: "text", v: "Quantity" }); form.tag("Input") - .a("value", c.bind("s_result.quantity")); + .a({ n: "value", v: this.client._bind("s_result-quantity") }); form.tag("Label") - .a("text", "Return to the caller"); + .a({ n: "text", v: "Return to the caller" }); form.tag("Button") - .a("press", c.event("CONFIRM")) - .a("text", "confirm (event + data)") - .a("type", "Emphasized"); + .a({ n: "press", v: this.client._event("CONFIRM") }) + .a({ n: "text", v: "confirm (event + r_data)" }) + .a({ n: "type", v: "Emphasized" }); form.tag("Button") - .a("press", c.event("CANCEL")) - .a("text", "cancel (event only)"); + .a({ n: "press", v: this.client._event("CANCEL") }) + .a({ n: "text", v: "cancel (event only)" }); - c.view(view); + this.client.view_display(view.stringify()); } + }); diff --git a/srv/apps/z2ui5_cl_smp_app_493.js b/srv/apps/z2ui5_cl_smp_app_493.js index 01acfc3..bc7732e 100644 --- a/srv/apps/z2ui5_cl_smp_app_493.js +++ b/srv/apps/z2ui5_cl_smp_app_493.js @@ -1,50 +1,44 @@ -/** - * Basics I - Hello World, the Smallest App - * - * The smallest app that runs: one class, one c.view( ), a Page with a title - - * the shape every other sample starts from. - * - * @keywords hello world smallest first app minimal start here template - * @origin abap2UI5/samples src/z2ui5_cl_smp_app_493.clas.abap - */ -import { defineApp, ViewBuilder } from "cap2ui5"; - -const INFO = - "The whole app is what you see below: a class handed to defineApp( ), one main( ) method, " + - "a view built with ViewBuilder and handed to c.view( ). cap2UI5 calls main( ) on every " + - "roundtrip - here only the display matters, which is what c.isDisplay asks: true on the " + - "first start and whenever the app is shown again. Copy this file as the starting point for " + - "your own app."; +// @keywords hello world smallest first app minimal start here template +// @summary The smallest app that runs: one class, one view_display( ), a Page with a title - the shape every other sample starts from. +// @docs https://abap2ui5.github.io/docs/get_started/hello_world https://abap2ui5.github.io/docs/cookbook/view/definition https://abap2ui5.github.io/docs/cookbook/expert_more/snippets https://abap2ui5.github.io/docs/tutorials/walkthrough/step-1 +// @origin abap2UI5/samples src/z2ui5_cl_smp_app_493.clas.abap +import { defineApp, z2ui5_cl_ui5_view_builder } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_493", class { - main(c) { - if (c.isDisplay) { + main(client) { - const view = ViewBuilder.factory() - .ele("View", "mvc") - .a("displayBlock", "true") - .a("height", "100%") - .a("xmlns", "sap.m") - .a("xmlns:mvc", "sap.ui.core.mvc"); + if (client.check_on_navigated()) { + + const 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" }); const page = view.ele("Shell") .ele("Page") - .a("title", "cap2UI5 - Basics I - Hello World, the Smallest App") - .a("showNavButton", c.canGoBack) - .a("navButtonPress", c.eventNavBack()); + .a({ n: "title", v: "cap2UI5 - Basics I - Hello World, the Smallest App" }) + .a({ n: "showNavButton", b: client.check_app_prev_stack() }) + .a({ n: "navButtonPress", v: client._event_nav_app_leave() }); page.tag("MessageStrip") - .a("text", INFO) - .a("type", "Information") - .a("showIcon", true) - .a("class", "sapUiSmallMargin"); + .a({ n: "text", v: "The whole app is what you see below: a class handed to defineApp( ), " + + "one main( ) method, a view built as XML and handed to client.view_display( ). " + + "cap2UI5 calls main( ) on every roundtrip - here only the display matters, " + + "which is what check_on_navigated( ) asks: true on the first start and whenever " + + "the app is shown again. Copy this file as the starting point for your own app." }) + .a({ n: "type", v: "Information" }) + .a({ n: "showIcon", b: true }) + .a({ n: "class", v: "sapUiSmallMargin" }); page.tag("Title") - .a("text", "Hello World") - .a("class", "sapUiSmallMargin") - .a("level", "H2"); - c.view(view); + .a({ n: "text", v: "Hello World" }) + .a({ n: "class", v: "sapUiSmallMargin" }) + .a({ n: "level", v: "H2" }); + client.view_display(view.stringify()); } + } }); diff --git a/srv/apps/z2ui5_cl_smp_app_494.js b/srv/apps/z2ui5_cl_smp_app_494.js index 1f8e9c6..618898e 100644 --- a/srv/apps/z2ui5_cl_smp_app_494.js +++ b/srv/apps/z2ui5_cl_smp_app_494.js @@ -1,78 +1,75 @@ -/** - * Basics II - Data Binding: Input and Button - * - * Binds a field of the class to an Input with c.bind( ), so what the user types - * is in the field on the next roundtrip - a Text shows it back and a MessageBox - * confirms the roundtrip. - * - * @keywords binding bind model field value input button roundtrip messagebox - * @origin abap2UI5/samples src/z2ui5_cl_smp_app_494.clas.abap - */ -import { defineApp, ViewBuilder } from "cap2ui5"; - -const INFO = - "c.bind('name') connects the field NAME with the input below. Type a name and leave the " + - "field: the text next to it changes without any backend code, because both are bound to the " + - "same field. Press Greet and the backend reads NAME - already filled in, no event argument " + - "needed -, writes GREETING back into the view and confirms the roundtrip with a MessageBox."; +// @keywords binding _bind model attribute value input button roundtrip messagebox serialize +// @summary Binds a class attribute to an Input with _bind( ), so what the user types is in the variable on the next roundtrip - a Text shows it back and a MessageBox confirms the roundtrip. +// @docs https://abap2ui5.github.io/docs/cookbook/model/binding https://abap2ui5.github.io/docs/tutorials/walkthrough/step-4 +// @origin abap2UI5/samples src/z2ui5_cl_smp_app_494.clas.abap +import { defineApp, z2ui5_cl_ui5_view_builder } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_494", class { - name = ""; + + name = ""; greeting = ""; - main(c) { - if (c.isFirstRun) { + main(client) { + + this.client = client; + if (client.check_on_init()) { this.name = "World"; - this.viewDisplay(c); - } else if (c.isDisplay) { - this.viewDisplay(c); - } else if (c.eventName === "GREET") { + this.view_display(); + } else if (client.check_on_navigated()) { + this.view_display(); + } else if (client.check_on_event("GREET")) { this.greeting = `Hello ${this.name}!`; - c.messageBox(`Roundtrip done: the backend read NAME = '${this.name}' and wrote GREETING back into the view.`); + client.message_box_display(`Roundtrip done: the backend read NAME = '${this.name}' and wrote GREETING back into the view.`); } + } - viewDisplay(c) { + view_display() { - const view = ViewBuilder.factory() - .ele("View", "mvc") - .a("displayBlock", "true") - .a("height", "100%") - .a("xmlns", "sap.m") - .a("xmlns:mvc", "sap.ui.core.mvc") - .a("xmlns:form", "sap.ui.layout.form"); + const 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" }); const page = view.ele("Shell") .ele("Page") - .a("title", "cap2UI5 - Basics II - Data Binding: Input and Button") - .a("showNavButton", c.canGoBack) - .a("navButtonPress", c.eventNavBack()); + .a({ n: "title", v: "cap2UI5 - Basics II - Data Binding: Input and Button" }) + .a({ n: "showNavButton", b: this.client.check_app_prev_stack() }) + .a({ n: "navButtonPress", v: this.client._event_nav_app_leave() }); page.tag("MessageStrip") - .a("text", INFO) - .a("type", "Information") - .a("showIcon", true) - .a("class", "sapUiSmallMargin"); + .a({ n: "text", v: "client._bind( 'name' ) connects the field NAME with the input " + + "below. Type a name and leave the field: the text " + + "next to it changes without any backend code, because both are bound to the " + + "same field. Press Greet and the backend reads NAME - already filled " + + "in, no event argument needed -, writes GREETING back into the view and " + + "confirms the roundtrip with a MessageBox." }) + .a({ n: "type", v: "Information" }) + .a({ n: "showIcon", b: true }) + .a({ n: "class", v: "sapUiSmallMargin" }); - page.ele("SimpleForm", "form") - .a("title", "Data Binding") - .a("editable", true) - .ele("content", "form") + page.ele({ n: "SimpleForm", ns: "form" }) + .a({ n: "title", v: "Data Binding" }) + .a({ n: "editable", b: true }) + .ele({ n: "content", ns: "form" }) .tag("Label") - .a("text", "your name") + .a({ n: "text", v: "your name" }) .tag("Input") - .a("value", c.bind("name")) + .a({ n: "value", v: this.client._bind("name") }) .tag("Label") - .a("text", "bound to the same field") + .a({ n: "text", v: "bound to the same field" }) .tag("Text") - .a("text", c.bind("name")) + .a({ n: "text", v: this.client._bind("name") }) .tag("Label") - .a("text", "written by the backend") + .a({ n: "text", v: "written by the backend" }) .tag("Text") - .a("text", c.bind("greeting")) + .a({ n: "text", v: this.client._bind("greeting") }) .tag("Button") - .a("press", c.event("GREET")) - .a("text", "Greet"); - c.view(view); + .a({ n: "press", v: this.client._event("GREET") }) + .a({ n: "text", v: "Greet" }); + this.client.view_display(view.stringify()); } }); diff --git a/srv/apps/z2ui5_cl_smp_app_495.js b/srv/apps/z2ui5_cl_smp_app_495.js index 2b3fff7..038d54d 100644 --- a/srv/apps/z2ui5_cl_smp_app_495.js +++ b/srv/apps/z2ui5_cl_smp_app_495.js @@ -1,92 +1,89 @@ -/** - * Basics III - Lifecycle: First Run, Event, Display - * - * The three questions main( ) asks - c.isFirstRun, c.eventName, c.isDisplay - - * as one dispatcher, showing what survives a roundtrip and what a navigation - * does to it. - * - * @keywords lifecycle roundtrip main dispatcher state draft isFirstRun isDisplay eventName - * @origin abap2UI5/samples src/z2ui5_cl_smp_app_495.clas.abap - */ -import { defineApp, t, ViewBuilder } from "cap2ui5"; - -const INFO = - "main( ) runs on every roundtrip - c.isFirstRun, c.isDisplay and c.eventName tell it what " + - "the roundtrip is about. The list logs each call, and it survives them all: every field of " + - "the class is kept in the draft between the roundtrips, so the app keeps its state without " + - "a table of its own. Press Log - only the model is pushed, the view is not rebuilt. Call the " + - "sub-app and come back with its back button - that is the roundtrip c.isDisplay answers " + - "without c.isFirstRun."; +// @keywords lifecycle roundtrip main dispatcher state serialize check_on_init check_on_event check_on_navigated +// @summary The three questions main( ) asks - init, event, navigated - as one dispatcher, showing what survives a roundtrip and what a navigation does to it. +// @docs https://abap2ui5.github.io/docs/cookbook/event_navigation/life_cycle https://abap2ui5.github.io/docs/cookbook/expert_more/snippets https://abap2ui5.github.io/docs/tutorials/walkthrough/step-3 +// @origin abap2UI5/samples src/z2ui5_cl_smp_app_495.clas.abap +import { defineApp, t, z2ui5_cl_ui5_view_builder } from "cap2ui5"; defineApp("Z2UI5_CL_SMP_APP_495", class { + t_log = t.table({ no: "", check: "" }); - main(c) { - if (c.isFirstRun) { + main(client) { + + this.client = client; + if (client.check_on_init()) { - this.logStep("c.isFirstRun - the very first call, nothing exists yet"); - this.viewDisplay(c); + this.log_step("check_on_init( ) - the very first call, nothing exists yet"); + this.view_display(); - } else if (c.isDisplay) { + } else if (client.check_on_navigated()) { - this.logStep("c.isDisplay - the sub-app returned, re-display the view"); - this.viewDisplay(c); + this.log_step("check_on_navigated( ) - the sub-app returned, re-display the view"); + this.view_display(); - } else if (c.eventName === "LOG") { - this.logStep("c.eventName - a button was pressed, the view stays as it is"); + } else if (client.check_on_event("LOG")) { + this.log_step("check_on_event( ) - a button was pressed, the view stays as it is"); - } else if (c.eventName === "CALL") { + } else if (client.check_on_event("CALL")) { - this.logStep("c.eventName - calling Basics I as a sub-app"); - c.navTo("Z2UI5_CL_SMP_APP_493"); + this.log_step("check_on_event( ) - calling Basics I as a sub-app"); + client.nav_app_call("Z2UI5_CL_SMP_APP_493"); } + } - logStep(val) { - // a read is a copy of the table: the changed table is written back whole + log_step(val) { + + // a read of t_log is a copy: the table with the new row is written back whole this.t_log = [...this.t_log, { no: String(this.t_log.length + 1), check: val }]; + } - viewDisplay(c) { + view_display() { - const view = ViewBuilder.factory() - .ele("View", "mvc") - .a("displayBlock", "true") - .a("height", "100%") - .a("xmlns", "sap.m") - .a("xmlns:mvc", "sap.ui.core.mvc"); + const 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" }); const page = view.ele("Shell") .ele("Page") - .a("title", "cap2UI5 - Basics III - Lifecycle: First Run, Event, Display") - .a("showNavButton", c.canGoBack) - .a("navButtonPress", c.eventNavBack()); + .a({ n: "title", v: "cap2UI5 - Basics III - Lifecycle: Init, Event, Navigated" }) + .a({ n: "showNavButton", b: this.client.check_app_prev_stack() }) + .a({ n: "navButtonPress", v: this.client._event_nav_app_leave() }); page.tag("MessageStrip") - .a("text", INFO) - .a("type", "Information") - .a("showIcon", true) - .a("class", "sapUiSmallMargin"); + .a({ n: "text", v: "main( ) runs on every roundtrip - the three checks tell it what the " + + "roundtrip is about. The list logs each call, and it survives them all: " + + "every field is serialized between the roundtrips, so the app " + + "keeps its state without a table of its own. Press Log - only the model is pushed, " + + "the view is not rebuilt. Call the sub-app and come back with its back " + + "button - that is the roundtrip check_on_navigated( ) answers." }) + .a({ n: "type", v: "Information" }) + .a({ n: "showIcon", b: true }) + .a({ n: "class", v: "sapUiSmallMargin" }); page.ele("HBox") - .a("class", "sapUiSmallMargin") + .a({ n: "class", v: "sapUiSmallMargin" }) .tag("Button") - .a("press", c.event("LOG")) - .a("text", "Log an Event") + .a({ n: "press", v: this.client._event("LOG") }) + .a({ n: "text", v: "Log an Event" }) .tag("Button") - .a("press", c.event("CALL")) - .a("text", "Call a Sub-App") - .a("class", "sapUiTinyMarginBegin"); + .a({ n: "press", v: this.client._event("CALL") }) + .a({ n: "text", v: "Call a Sub-App" }) + .a({ n: "class", v: "sapUiTinyMarginBegin" }); page.ele("List") - .a("headerText", "Calls of main( )") - .a("items", c.bind("t_log")) - .a("class", "sapUiSmallMargin") + .a({ n: "headerText", v: "Calls of main( )" }) + .a({ n: "items", v: this.client._bind("t_log") }) + .a({ n: "class", v: "sapUiSmallMargin" }) .tag("StandardListItem") - .a("title", "{CHECK}") - .a("description", "call {NO}"); + .a({ n: "title", v: "{CHECK}" }) + .a({ n: "description", v: "call {NO}" }); - c.view(view); + this.client.view_display(view.stringify()); } }); diff --git a/test/samples.test.mjs b/test/samples.test.mjs index 2101603..9d7721e 100644 --- a/test/samples.test.mjs +++ b/test/samples.test.mjs @@ -52,7 +52,7 @@ test("494 Data Binding: the typed name reaches the backend, the greeting comes b ["MESSAGE_BOX", "show", "Roundtrip done: the backend read NAME = 'Ada' and wrote GREETING back into the view."]); }); -test("495 Lifecycle: first run, event, and the return from a called app", async () => { +test("495 Lifecycle: init, event, and the return from a called app", async () => { const APP = "Z2UI5_CL_SMP_APP_495"; const start = await P({ app: APP }); assert.deepEqual(start.json.MODEL.T_LOG.map((r) => r.NO), ["1"]); @@ -62,24 +62,24 @@ test("495 Lifecycle: first run, event, and the return from a called app", async assert.equal(log.json.MODEL.T_LOG.length, 2, "only the model is pushed"); const call = await P({ app: APP, id: log.id, event: "CALL" }); - assert.equal(call.app, "Z2UI5_CL_SMP_APP_493", "c.navTo( ) hands the screen to Basics I"); + assert.equal(call.app, "Z2UI5_CL_SMP_APP_493", "nav_app_call( ) hands the screen to Basics I"); assert.match(slot(call, "MAIN"), /Basics I - Hello World/); assert.match(slot(call, "MAIN"), /showNavButton="true"/, "the called app can go back"); - // the called app's back button: c.eventNavBack( ), no branch in its main( ) + // the called app's back button: _event_nav_app_leave( ), no branch in its main( ) const back = await P({ app: call.app, id: call.id, event: firedBy(slot(call, "MAIN"), "navButtonPress") }); assert.equal(back.app, APP, "the back button returns to the caller"); - assert.match(slot(back, "MAIN") ?? "", /Basics III/, "the caller renders again (c.isDisplay)"); + assert.match(slot(back, "MAIN") ?? "", /Basics III/, "the caller renders again (check_on_navigated)"); assert.deepEqual(back.json.MODEL.T_LOG.map((r) => r.CHECK.split(" - ")[0]), - ["c.isFirstRun", "c.eventName", "c.eventName", "c.isDisplay"], - "the log survived the navigation, and the return is c.isDisplay without c.isFirstRun"); + ["check_on_init( )", "check_on_event( )", "check_on_event( )", "check_on_navigated( )"], + "the log survived the navigation, and the return is check_on_navigated( ) without check_on_init( )"); }); test("011 Editable Table: edit mode, delete the selected rows, add a row", async () => { const APP = "Z2UI5_CL_SMP_APP_011"; const start = await P({ app: APP }); assert.match(slot(start, "MAIN"), /items="\{path: '\/T_TAB', templateShareable: false\}"/, - "the bare path of c.bind( t_tab, { path: true } ) in a composed binding"); + "the bare path of _bind( { val: t_tab, path: true } ) in a composed binding"); const rows = start.json.MODEL.T_TAB; assert.equal(rows.length, 6); assert.deepEqual(rows[0], { SELKZ: false, TITLE: "entry 01", VALUE: "red", DESCR: "this is a description", @@ -143,7 +143,7 @@ test("488/489 Navigation: the called app returns an event and data, the caller r assert.equal(called.app, "Z2UI5_CL_SMP_APP_489"); assert.deepEqual(called.json.MODEL, { S_RESULT: { PRODUCT: "Notebook Basic 15", QUANTITY: "2" } }); - // the user edits both fields, then confirms: the data travels with navBack + // the user edits both fields, then confirms: the data travels with nav_app_leave( r_data ) const confirm = await P({ app: called.app, id: called.id, event: "CONFIRM", model: { S_RESULT: { PRODUCT: "Notebook Pro 17", QUANTITY: "7" } } }); assert.equal(confirm.app, "Z2UI5_CL_SMP_APP_488"); From 75d22e31b23ce1f55e336500c9f7c5ad9f22f376 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Mon, 28 Sep 2026 12:44:36 +0000 Subject: [PATCH 5/5] Test the samples against cap2UI5's main until cap2ui5 0.2.0 is on npm CI ran `npm install`, which cannot resolve cap2ui5 ^0.2.0 before 0.2.0 is published, so every run would have ended in ETARGET before a single sample ran. It now checks out cap2UI5, packs its plugin as `npm publish` packs it, and installs that tarball alone - the recipe the README gives for a local checkout, tried in a clean copy of this repository: 95 packages, the nine samples green. Once 0.2.0 is published this goes, and a package-lock.json with `cache: npm` and npm ci come back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XtDApnfmAncwrmW6fE4tCS --- .github/workflows/test.yml | 16 +++++++++++++--- README.md | 3 ++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a06c001..46749f2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,17 @@ jobs: - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version-file: .nvmrc - # npm install, and no npm cache, until cap2ui5 0.2.0 is on npm and a - # package-lock.json can pin it - then `cache: npm` and npm ci again - - run: npm install + # Until cap2ui5 0.2.0 is on npm, the samples run against cap2UI5's main: + # its plugin packed as `npm publish` packs it, and installed alone - + # which brings the other dependencies along, as the README says for a + # local checkout. Once 0.2.0 is published this goes, a package-lock.json + # pins it, and `cache: npm` and npm ci come back. + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: cap2UI5/cap2UI5 + path: .cap2ui5 + persist-credentials: false + - run: npm pack --pack-destination "$RUNNER_TEMP" + working-directory: .cap2ui5/plugin + - run: npm install --no-save --no-audit --no-fund "$RUNNER_TEMP"/cap2ui5-*.tgz - run: npm test diff --git a/README.md b/README.md index fd84fda..876ae9a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ calls it: `client->check_app_prev_stack( )` is > which is not on npm yet (the client under its ABAP names and the view > builder are in its `Unreleased` changelog). Until it is, a plain > `npm install` answers `ETARGET`; install the plugin packed from a cap2UI5 -> checkout instead, which brings the other dependencies along: +> checkout instead, which brings the other dependencies along - CI does the +> same with cap2UI5's `main`: > > ```bash > (cd ../cap2UI5 && scripts/assemble-runtime.sh --package 1.145.0 && npm install \