Skip to content

Repository files navigation

cap2UI5 samples

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.

Add them to your project

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 apps

The 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.

Run this repository

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, served

Log 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.

The samples

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

How a sample gets here

scripts/samples.json names every sample and how it is made:

  • generated - npm run generate reads the class from an abap2UI5/samples checkout at the pin and writes what abap2js makes of it into srv/apps/<class>.js. These files are never edited by hand: a change goes into the ABAP sample, the pin moves, and npm run generate runs 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 INTO writes through the row, and DELETE t_tab WHERE) and Z2UI5_CL_SMP_APP_488 (ASSIGN … ->* TO FIELD-SYMBOL( )). A sample moves to generated when 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 generate

Adding 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.

The differential test

"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.

What a translated sample looks like

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.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, and 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 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. 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

What differs from ABAP

  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: INSERT … INTO TABLE t_tab is this.t_tab = [...this.t_tab, row], a component is written by assigning the structure. A push on the copy is lost.
  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.

Publishing

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

About

Samples of cap2UI5

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages