Skip to content

Latest commit

 

History

History

README.md

test update cap

🚀 cap2UI5

Bringing the abap2UI5 concept to CAP/Node.js.

Important

Everything in this project is generated automatically. The entire codebase, all documentation, and the web version were created by AI (Claude) and by an automated sync pipeline that mirrors and transpiles the upstream abap2UI5 sources — nothing here is hand-written. See builder-abap2UI5-js and builder-cap2UI5 for how the pipeline works. Review and test before relying on it.

Features

  • XML View Generation - Create UI5 views programmatically in your backend
  • Data Binding & Exchange - Seamless two-way data binding between frontend and backend
  • Session Management - Built-in persistence and session handling (optional)

Benefits

  • Security
  • Speed

Getting Started

Prerequisites: Node.js ≥ 22 (see .nvmrc). The whole stack runs offline — the UI5 runtime is served locally from the pinned openui5-dist dependency, and CAP deploys an in-memory SQLite database automatically on startup.

npm install

# start the server (restarts on file changes)
npx cds watch
# or: start and open the app in the browser right away
npm run watch-z2ui5

The server listens on http://localhost:4004:

URL What you get
http://localhost:4004/z2ui5/webapp/index.html the app — without a parameter the startup app is shown
http://localhost:4004/z2ui5/webapp/index.html?app_start=z2ui5_cl_app_hello_world start a specific app class via the app_start parameter (works for every sample, e.g. z2ui5_cl_demo_app_001)
http://localhost:4004/index.html the minimal starter page — one roundtrip through all three layers
http://localhost:4004/rest/root/z2ui5 the roundtrip endpoint the frontend talks to
http://localhost:4004/odata/v4/admin/z2ui5_t_01 the draft table (session persistence) via OData

For a one-off run without file watching use npm start (cds-serve).

The minimal base

What it is

The base is the starting point of cap2UI5: a small but complete CAP project that works on its own, before any generated code is added. It brings the same basic setup as abap2UI5 — a frontend that talks JSON to a single http endpoint, and a draft table that persists the app state between roundtrips — reduced to the minimum:

Layer Where What it does
(1) mini frontend app/index.html a single self-contained UI5 page that POSTs one roundtrip, renders the returned view XML and shows the draft id + row count
(2) http service srv/z2ui5-service.cds / .js + srv/server.js rootService.z2ui5 — the REST action at POST /rest/root/z2ui5 (plus GET bootstrap HTML), same wire format as the abap2UI5 ICF endpoint
(3) persistence db/schema.cds entity cap2ui5.z2ui5_t_01 — one draft row per roundtrip, chained via id_prev (the abap2UI5 table Z2UI5_T_01)

The base is hand-maintained in builder-cap2UI5's src/ and published 1:1 into this repository. The framework itself — the engine, the transpiled classes, the z2ui5 frontend and the ~180 bundled samples — lives in the platform-neutral core package (core/, vendored here and linked as the npm dependency abap2UI5 via file:./core) and is generated by the sync pipeline in builder-abap2UI5-js. All commands below work in both places: in builder-cap2UI5's src/ (source only) and in this repository (base + the vendored core + the generated webapp overlay).

Minimal setup

A regular CAP project with the standard layout:

base/
├── app/              # UI content
│   └── index.html    #   the mini frontend (starter page)
├── db/               # domain model
│   └── schema.cds    #   cap2ui5.z2ui5_t_01 — the draft table
├── srv/              # services and implementation
│   ├── z2ui5-service.cds   # AdminService (OData) + rootService (REST)
│   ├── z2ui5-service.js    # wires POST /rest/root/z2ui5 → the engine roundtrip
│   ├── server.js           # GET bootstrap HTML, /resources, draft store + app dir ports
│   ├── app/                # custom apps (z2ui5_cl_app_read_odata, your own)
│   └── external/           # imported remote service model (Northwind)
├── test/             # jest: starter integration test + view builder test
└── package.json      # @sap/cds ^10, "abap2UI5": file-link to the vendored core/, Node ≥ 22

The z2ui5 runtime itself is not hand-written in this project — it comes from the vendored core package (require("abap2UI5/engine")), which srv/server.js wires to CAP: the draft store port to the CDS entity cap2ui5.z2ui5_t_01, the app-discovery port to this project's srv/app/.

There is nothing to configure: the UI5 runtime is served locally from the pinned openui5-dist dependency (works offline) and CAP deploys an in-memory SQLite database automatically on startup.

Start it

npm install
npx cds watch     # → http://localhost:4004/index.html

Open http://localhost:4004/index.html: every click on POST /rest/root/z2ui5 runs one full roundtrip — the backend app class builds the view, the response's draft id is the new row key in cap2ui5.z2ui5_t_01, and the row count (read back via the AdminService OData endpoint) increases by one.

Or exercise the service from the command line:

curl -s -X POST http://localhost:4004/rest/root/z2ui5 \
  -H "Content-Type: application/json" \
  -d '{"value":{"S_FRONT":{"ORIGIN":"http://localhost:4004","PATHNAME":"/index.html","SEARCH":"","HASH":""}}}'

curl -s http://localhost:4004/odata/v4/admin/z2ui5_t_01/\$count

Test it

npm test

runs the jest suite: test/starter.test.js boots the real server via cds.test() and asserts all three layers end-to-end (starter page + bootstrap HTML, roundtrip returning view XML, draft row persisted per roundtrip), and test/z2ui5_cl_xml_view.test.js covers the view builder.

Security note (demo setup — no authentication)

This project deliberately ships without authentication: xs-security.json declares no scopes, attributes or role templates, and package.json sets cds.requires.[production].auth: false, so every endpoint (the z2ui5 roundtrip, the OData admin service and the draft table) is open. That is intentional for the demo/sample character of this repository. For a real BTP deployment, configure authentication before going productive: define scopes and role templates in xs-security.json, remove the [production].auth: false override and wire the CAP services to XSUAA (kind: xsuaa) — see the CAP authorization guide.

Transpiling from ABAP

App classes can be transpiled automatically from the abap2UI5 ABAP sources — the transpiler and all other dev tooling live in builder-abap2UI5-js.

Samples

All samples demonstrate complete view definition and data exchange handled entirely by the CAP server, using the same and static frontend from abap2UI5.

Each app is a single .js file whose basename matches the class name it exports (module.exports). Put your own apps into srv/app/ (scanned automatically when resolving ?app_start=<class>, see the custom apps README) or into any folder registered via Z2UI5_APP_DIRS / require("abap2UI5/register-apps")(dir) — see the discovery API. The bundled samples and the framework classes live in the vendored core package (core/), which is owned by the sync pipeline.

1. Hello World

App
// z2ui5_cl_app_hello_world.js — ships with the core package
const z2ui5_cl_xml_view = require("abap2UI5/z2ui5_cl_xml_view");
const z2ui5_if_app = require("abap2UI5/z2ui5_if_app");

class z2ui5_cl_app_hello_world extends z2ui5_if_app {
  name = ``;

  async main(client) {
    if (client.check_on_init()) {
      const view = z2ui5_cl_xml_view.factory()
        .shell()
        .page(`abap2UI5 - Hello World`)
        .simple_form({ editable: true })
        .content(`form`)
        .title({ ns: `core`, text: `Enter a value and send it to the server...` })
        .label(`Name`)
        .input(client._bind_edit(this.name))
        .button({ text: `Send`, press: client._event(`BUTTON_POST`) });
      client.view_display(view.stringify());
    } else if (client.check_on_event(`BUTTON_POST`)) {
      client.message_box_display(`Your name is ${this.name}`);
    }
  }
}

module.exports = z2ui5_cl_app_hello_world;
Demo
image

2. Fetch Data via Remote Odata

Package.json
      "northwind": {
        "kind": "odata-v2",
        "model": "srv/external/northwind",
        "credentials": {
          "url": "https://services.odata.org/V2/Northwind/Northwind.svc/"
        }
      }
App
// srv/app/z2ui5_cl_app_read_odata.js — ships with the project
const cds = require("@sap/cds");
const z2ui5_cl_xml_view = require("abap2UI5/z2ui5_cl_xml_view");
const z2ui5_if_app = require("abap2UI5/z2ui5_if_app");

class z2ui5_cl_app_read_odata extends z2ui5_if_app {
  customers = [];

  async main(client) {
    if (client.check_on_init()) {
      // The remote demo service may be unreachable (offline, proxy, service
      // down) — show the error in the UI instead of breaking the app init.
      try {
        const northwind = await cds.connect.to(`northwind`);
        this.customers = await northwind.run(
          SELECT.from(`Customers`).columns(`CompanyName`, `ContactName`).limit(20)
        );
      } catch (e) {
        const view = z2ui5_cl_xml_view.factory();
        view.shell()
          .page(`abap2UI5 - Table with Data Fetched via Remote OData`)
          .message_strip({
            text: `Remote Northwind service not reachable: ${e.message}`,
            type: `Error`,
            showicon: true,
            class: `sapUiSmallMargin`,
          });
        client.view_display(view.stringify());
        client.message_box_display(`Remote Northwind service not reachable: ${e.message}`, `error`);
        return;
      }

      const view = z2ui5_cl_xml_view.factory();
      const tab = view.shell()
        .page(`abap2UI5 - Table with Data Fetched via Remote OData`)
        .table({ items: client._bind_edit(this.customers) });
      tab.columns()
        .column().text(`CompanyName`).get_parent()
        .column().text(`ContactName`);
      tab.items()
        .column_list_item()
        .cells()
        .input({ value: `{COMPANYNAME}`, enabled: true })
        .input({ value: `{CONTACTNAME}`, enabled: true });
      client.view_display(view.stringify());
    }
  }
}

module.exports = z2ui5_cl_app_read_odata;

Note: the client model uppercases all property names — the cells bind {COMPANYNAME}, not {CompanyName}.

Demo

alt text

3. Display a Server Side XML

View1.view.xml
<mvc:View
	controllerName="Quickstart.App"
	displayBlock="true"
	xmlns:mvc="sap.ui.core.mvc"
	xmlns:l="sap.ui.layout"
	xmlns:core="sap.ui.core"
	xmlns:tnt="sap.tnt"
	xmlns="sap.m">
	<App id="app">
		<Page title="Create Enterprise-ready Web Apps with Ease">
			<l:BlockLayout background="Light">
				<l:BlockLayoutRow>
					<l:BlockLayoutCell>
						<core:Icon color="#1873B4" src="sap-icon://sap-ui5" size="5rem" class="sapUiSmallMarginBottom" width="100%"/>
						<Title level="H1" titleStyle="H1" text="This is UI5!" width="100%" textAlign="Center"/>
					</l:BlockLayoutCell>
				</l:BlockLayoutRow>
				<l:BlockLayoutRow>
					<l:BlockLayoutCell>
						<FlexBox items="{/features}" justifyContent="Center" wrap="Wrap" class="sapUiSmallMarginBottom">
							<tnt:InfoLabel text="{}" class="sapUiSmallMarginTop sapUiSmallMarginEnd"/>
						</FlexBox>
					</l:BlockLayoutCell>
				</l:BlockLayoutRow>
				<l:BlockLayoutRow>
					<l:BlockLayoutCell>
						<Panel headerText="Are you ready?" expandable="true">
							<Switch change=".onChange" customTextOn="yes" customTextOff="no"/>
							<l:HorizontalLayout id="ready" visible="false" class="sapUiSmallMargin">
								<Text text="Ok, let's get you started!" class="sapUiTinyMarginEnd"/>
								<Link text="Learn more" href="https://openui5.hana.ondemand.com/"/>
							</l:HorizontalLayout>
						</Panel>
					</l:BlockLayoutCell>
				</l:BlockLayoutRow>
			</l:BlockLayout>
		</Page>
	</App>
</mvc:View>
z2ui5_cl_app_read_view
// z2ui5_cl_app_read_view.js — ships with the core package
const z2ui5_if_app = require("abap2UI5/z2ui5_if_app");

class z2ui5_cl_app_read_view extends z2ui5_if_app {
  async main(client) {
    const fs = require("fs");
    const path = require("path");
    const viewPath = path.join(__dirname, "View1.view.xml");
    const viewContent = fs.readFileSync(viewPath, "utf8");
    client.view_display(viewContent);
  }
}

module.exports = z2ui5_cl_app_read_view;
Demo

alt text

Contribution

Contributions are welcome! Feel free to fork the project, submit issues, or create pull requests.

License

This project is licensed under the MIT License.