The abap2UI5 samples as
cap2UI5 apps, published as
@cap2ui5/samples: add the package to a CAP project and they run beside
its own apps, as abap2UI5's samples run in the system they are pulled into.
Every sample is a plain JavaScript class in srv/apps/ - translated from
its ABAP original, not rewritten. cap2UI5's abap2js reads the ABAP class
and writes the module line for line: the same class name, the same attributes
and methods, the same calls - the client is abap2UI5's z2ui5_if_client by its
own names, the view is built with z2ui5_cl_ui5_view_builder - and the
original's comments and texts, all of them, as they are.
client->check_app_prev_stack( ) is client.check_app_prev_stack().
Note
Status: 71 of the 129 samples. 69 are generated by abap2js from
abap2UI5/samples at the commit in ABAP2UI5_SAMPLES_PIN,
two are ported by hand; the differential test holds
every one of them to its ABAP original.
In a CAP project with @cap2ui5/cds-plugin
0.3 or later:
npm add -D @cap2ui5/samples
cds watch # lists every sample beside your own appsThe plugin loads the apps a dependency declares in its package.json -
here "cap2ui5": { "apps": "srv/apps" } - beside the project's own, so every
sample starts under its ABAP name, e.g.
/sap/bc/z2ui5?app_start=Z2UI5_CL_SMP_APP_493. As a devDependency they are
there in development only: with NODE_ENV=production the plugin leaves a
devDependency's apps out, as CAP leaves out its plugins. npm add without
-D brings them to production too. An app of the project's own that has the
name of a sample stays the project's; the log says so.
npm install
npm run watch # cds watch - prints the address of every sample
npm test # every sample driven over the wire
npm run consumer-test # the package: packed, installed into a throwaway CAP project, servedLog in as alice with an empty password (CAP's mocked development user), then
open e.g. http://localhost:4004/sap/bc/z2ui5?app_start=Z2UI5_CL_SMP_APP_493.
| Sample | App | ABAP original |
|---|---|---|
| Basics I - Hello World, the Smallest App | Z2UI5_CL_SMP_APP_493 |
abap |
| Basics II - Data Binding: Input and Button | Z2UI5_CL_SMP_APP_494 |
abap |
| Basics III - Lifecycle: Init, Event, Navigated | Z2UI5_CL_SMP_APP_495 |
abap |
| Basics V - The Developer Tools (Ctrl+F12) | Z2UI5_CL_SMP_APP_496 |
abap |
| Basics VI - Unit Tests for the App Logic | Z2UI5_CL_SMP_APP_503 |
abap |
| Basics VII - Translatable Texts (Text Elements) | Z2UI5_CL_SMP_APP_519 |
abap |
| Binding - Currency Amounts (sap.ui.model.type.Currency) | Z2UI5_CL_SMP_APP_067 |
abap |
| Binding - Expression Binding, Types and Composite Parts | Z2UI5_CL_SMP_APP_027 |
abap |
| Binding - Omit Initial Values | Z2UI5_CL_SMP_APP_507 |
abap |
| Binding - Path Only (_bind_path) | Z2UI5_CL_SMP_APP_508 |
abap |
| Binding - Pre-serialized JSON (json) | Z2UI5_CL_SMP_APP_509 |
abap |
| Binding - Structure Fields and INCLUDEs | Z2UI5_CL_SMP_APP_166 |
abap |
| Browser - Copy to Clipboard | Z2UI5_CL_SMP_APP_325 |
abap |
| Browser - Keyboard Layout of an Input (inputmode) | Z2UI5_CL_SMP_APP_516 |
abap |
| Browser - Local and Session Storage | Z2UI5_CL_SMP_APP_327 |
abap |
| Browser - Logout from the Client | Z2UI5_CL_SMP_APP_361 |
abap |
| Browser - Open Mail, Phone and SMS Links | Z2UI5_CL_SMP_APP_316 |
abap |
| Browser - Reload the Page | Z2UI5_CL_SMP_APP_492 |
abap |
| Browser - Set the Tab Favicon | Z2UI5_CL_SMP_APP_491 |
abap |
| Browser - Set the Tab Title | Z2UI5_CL_SMP_APP_125 |
abap |
| CSS - Color Table Cells from the Backend | Z2UI5_CL_SMP_APP_305 |
abap |
| CSS - FlexBox Layouts with Custom Classes | Z2UI5_CL_SMP_APP_255 |
abap |
| CSS - Ship Your Own CSS with the View | Z2UI5_CL_SMP_APP_050 |
abap |
| Control Behaviour - Aggregation Item by Index | Z2UI5_CL_SMP_APP_514 |
abap |
| Control Behaviour - Expand a Panel by ID (setExpanded) | Z2UI5_CL_SMP_APP_448 |
abap |
| Control Behaviour - Inline CSS on a Control (css) | Z2UI5_CL_SMP_APP_513 |
abap |
| Control Behaviour - Open the PDF Viewer by ID | Z2UI5_CL_SMP_APP_449 |
abap |
| Control Behaviour - Register an Icon Font | Z2UI5_CL_SMP_APP_518 |
abap |
| Control Behaviour - Switch NavContainer Page by ID | Z2UI5_CL_SMP_APP_088 |
abap |
| Control Behaviour - The Global Busy Indicator | Z2UI5_CL_SMP_APP_515 |
abap |
| Control Behaviour - Wizard with Steps | Z2UI5_CL_SMP_APP_202 |
abap |
| Device - Device Model: Phone, Tablet, Desktop | Z2UI5_CL_SMP_APP_445 |
abap |
| Device - Frontend Info: UI5 Version, Theme, OS, Browser | Z2UI5_CL_SMP_APP_122 |
abap |
| Device - Geolocation from the Browser | Z2UI5_CL_SMP_APP_120 |
abap |
| Event - Extra Arguments with t_arg | Z2UI5_CL_SMP_APP_167 |
abap |
| Event - Keep the Last Keystroke with check_queue_last | Z2UI5_CL_SMP_APP_511 |
abap |
| Event - Link with preventDefault | Z2UI5_CL_SMP_APP_472 |
abap |
| Event - Literal Arguments (check_arg_literal) | Z2UI5_CL_SMP_APP_506 |
abap |
| File - Download to the Browser | Z2UI5_CL_SMP_APP_186 |
abap |
| Focus - Jump to the Next Input on Enter | Z2UI5_CL_SMP_APP_189 |
abap |
| Focus - Set Focus and Select Text in an Input | Z2UI5_CL_SMP_APP_133 |
abap |
| Formatter - ABAP Date and Time Strings (DATS/TIMS) | Z2UI5_CL_SMP_APP_450 |
abap |
| Formatter - Date Object for the DatePicker | Z2UI5_CL_SMP_APP_457 |
abap |
| Formatter - Date Objects for the PlanningCalendar | Z2UI5_CL_SMP_APP_456 |
abap |
| Formatter - Inline Icons in a Text | Z2UI5_CL_SMP_APP_466 |
abap |
| Grid Table - Keep Column Filters on Refresh | Z2UI5_CL_SMP_APP_143 |
abap |
| Hash - App State, Bookmark and Share | Z2UI5_CL_SMP_APP_498 |
abap |
| Hash - App-Owned Routing (#/detail) | Z2UI5_CL_SMP_APP_499 |
abap |
| Menu - Full Path of the Selected Item | Z2UI5_CL_SMP_APP_473 |
abap |
| Menu - Menu as Popover with core:require | Z2UI5_CL_SMP_APP_163 |
abap |
| Message - Message Model and MessageManager | Z2UI5_CL_SMP_APP_467 |
abap |
| Message - MessageBox, Types and Custom Actions | Z2UI5_CL_SMP_APP_382 |
abap |
| Message - MessagePopover URL Policy | Z2UI5_CL_SMP_APP_474 |
abap |
| Navigation - Data Input App | Z2UI5_CL_SMP_APP_489 |
abap |
| Navigation - Data Loss Protection on Leaving | Z2UI5_CL_SMP_APP_279 |
abap |
| Navigation - Detail Page | Z2UI5_CL_SMP_APP_469 |
abap |
| Navigation - Return Data and Events to the Caller | Z2UI5_CL_SMP_APP_488 - by hand |
abap |
| Nested View - Basic Example (nest_view_display) | Z2UI5_CL_SMP_APP_065 |
abap |
| Nested View - Destroy and Target a Slot | Z2UI5_CL_SMP_APP_510 |
abap |
| Popover - Basic Example with Placement | Z2UI5_CL_SMP_APP_026 |
abap |
| Popover - Open Together with the View Build | Z2UI5_CL_SMP_APP_490 |
abap |
| Popover - QuickView Contact Card | Z2UI5_CL_SMP_APP_109 |
abap |
| Popover - Toggle by ID (toggleBy) | Z2UI5_CL_SMP_APP_465 |
abap |
| Popup - Dialog inside a Dialog | Z2UI5_CL_SMP_APP_161 |
abap |
| Popup - Element Binding to the Selected Row | Z2UI5_CL_SMP_APP_470 |
abap |
| Popup - Navigate between Dialogs (NavContainer) | Z2UI5_CL_SMP_APP_170 |
abap |
| Table - Editable Cells, Add and Delete Rows | Z2UI5_CL_SMP_APP_011 - by hand |
abap |
| Templating - Build Columns Dynamically (template:repeat) | Z2UI5_CL_SMP_APP_173 |
abap |
| Templating - Dynamic Content in a Nested View | Z2UI5_CL_SMP_APP_176 |
abap |
| Tree - Inside a Dialog | Z2UI5_CL_SMP_APP_462 |
abap |
| Tree - Nested ABAP Table in a sap.m.Tree | Z2UI5_CL_SMP_APP_460 |
abap |
scripts/samples.json names every sample and how it
is made:
generated-npm run generatereads the class from an abap2UI5/samples checkout at the pin and writes what abap2js makes of it intosrv/apps/<class>.js. These files are never edited by hand: a change goes into the ABAP sample, the pin moves, andnpm run generateruns again.npm run check:generated- a CI gate - fails when a file is not what the translation writes.handwritten- a class abap2js refuses, ported by hand the way abap2js would write it, with the construct it refuses as the reason:Z2UI5_CL_SMP_APP_011(LOOP AT t_tab REFERENCE INTOwrites through the row, andDELETE t_tab WHERE) andZ2UI5_CL_SMP_APP_488(ASSIGN … ->* TO FIELD-SYMBOL( )). A sample moves togeneratedwhen abap2js learns the construct.
The checkout is --samples <dir>, else $ABAP2UI5_SAMPLES, else
../abap2UI5-samples, and it has to be at the pin - @origin in a module
names the source it really came from:
git clone https://github.com/abap2UI5/samples ../abap2UI5-samples
git -C ../abap2UI5-samples checkout "$(cat ABAP2UI5_SAMPLES_PIN)"
npm run generateAdding a sample: its class name into generated, then npm run generate.
abap2js translates it, or refuses with file, row, column and the construct -
then the sample waits for abap2js, or is ported by hand and listed under
handwritten. Either way it passes the differential test before it is
committed. Moving the pin: the new commit into ABAP2UI5_SAMPLES_PIN and
the checkout, npm run generate, npm run differential - and a hand-written
port follows its original's diff by hand.
"Line for line" is a claim about the source; npm run differential is the one
about what the source does. It transpiles every sample's ABAP original with
@abaplint/transpiler - the version @abap2ui5/node-runtime was built with,
against the ABAP the runtime ships - renames it ZABAP_SMP_APP_nnn and serves
it beside the translation in one cap2UI5 server. Both are started, every event
the original's first view wires is fired on a fresh start of each, and what
comes back is compared: the XML of every view slot, the model the original
sends, every other action and which app has the screen - today 71 samples in
239 roundtrips, without a difference. One is known and left on purpose:
Z2UI5_CL_SMP_APP_122 prints a number with CONV string( ), which keeps ABAP's
trailing sign position ("0 ") and JavaScript's String( ) does not.
It needs the network once (open-abap-core is cloned into .deps/) and
@abaplint/transpiler-cli at the runtime's version, a devDependency.
Put the ABAP original and the module side by side and every line has its counterpart:
- One ABAP class, one file, the same name.
z2ui5_cl_smp_app_493.clas.abapbecomessrv/apps/z2ui5_cl_smp_app_493.js, registered asZ2UI5_CL_SMP_APP_493- so?app_start=is the same on both sides, and a sample that calls another one keeps calling it by its name. The header keeps the original's@keywords,@summaryand@docs, and@originpoints back at it. - The same class. The same attributes under the same names, the same
methods in the same order, the same dispatcher in
main( ),me->client = clientasthis.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. A statement starts where the ABAP one starts and breaks where it breaks - the view keeps the original's tree, one call per line.
- The same texts, all of them. The page title says
abap2UI5 - …as the original does, a text that names an ABAP construct (z2ui5_if_app,client->…) still names it, and every comment comes along.
| abap2UI5 | cap2UI5 |
|---|---|
DATA name TYPE string., DATA flag TYPE abap_bool. |
name = "";, flag = false; - the initial value is the type |
DATA amount TYPE p LENGTH 14 DECIMALS 3. |
amount = t.packed(14, 3); - and t.numc( ), t.date( ), t.time( ) |
TYPES: BEGIN OF ty_s_row, … END OF ty_s_row. |
const ty_s_row = { … }; - the row's initial values |
DATA t_tab TYPE STANDARD TABLE OF ty_s_row WITH EMPTY KEY. |
t_tab = t.table(ty_s_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: … }) |
CASE client->get_event( ). WHEN `A` OR `B`. |
switch (client.get_event()) { case "A": case "B": |
INSERT row INTO TABLE t_tab. |
this.t_tab = [...this.t_tab, row]; |
s_struc-title = `x`. |
this.s_struc = { ...this.s_struc, title: "x" }; |
|Hello { name }|, a && b |
`Hello ${this.name}`, a + b |
COND #( WHEN a THEN b ELSE c ) |
a ? b : c |
- A field is bound by its name,
client._bind("name"): ABAP's_bind( )finds the attribute by reference, which a JavaScript value cannot carry. thisreads plain copies. Assign to write:INSERT … INTO TABLE t_tabisthis.t_tab = [...this.t_tab, row], a component is written by assigning the structure. Apushon the copy is lost.- Every field is part of the model. ABAP keeps a
PROTECTEDattribute out of it; a JavaScript field with an initial value is always bound. The client is the exception: assigned inmain( )and not declared, it stays out.
A tag v<version> publishes the package as @cap2ui5/samples
(.github/workflows/release.yml). It runs everything test.yml runs on the
tagged commit - the samples over the wire, the package in a throwaway
project, the translation and the differential test - and publishes by
trusted publishing, with provenance and no token. The version is
package.json's: the pull request that prepares a release bumps it and moves
the changelog's Unreleased lines under it. The package carries srv/apps/,
this README, the changelog and the license - not the tests, the scripts or
the pin. A sample that needs a newer plugin raises the peer dependency on
@cap2ui5/cds-plugin with it.
npm points a package at a workflow only once the package exists, so the
first version goes out by hand, once, from a clean checkout of main whose
CI is green - and the Trusted Publisher is set right after, or the next tag
fails to publish:
npm install && npm test && npm run consumer-test
npm login
npm publish --access public
npx npm@11 trust github @cap2ui5/samples --file release.yml --repo cap2UI5/samples --allow-publish
git tag v0.1.0 && git push origin v0.1.0 # finds 0.1.0 on npm and publishes nothing