Skip to content

Latest commit

 

History

22 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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: 76 of the 138 samples. 73 are generated by abap2js from abap2UI5/samples at the commit in ABAP2UI5_SAMPLES_PIN, three 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.4 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
CSS - Style by Data with CustomData writeToDom Z2UI5_CL_SMP_APP_535 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 - Custom Data Attached to Controls Z2UI5_CL_SMP_APP_536 abap
Event - Expressions, Formatters and Literals in t_arg Z2UI5_CL_SMP_APP_537 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
Navigation - Wizard Steps with Page Transitions (the app Z2UI5_CL_SMP_APP_531 calls) Z2UI5_CL_SMP_APP_533 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
Popup - Popup-as-App: a Dialog and no View (the app Z2UI5_CL_SMP_APP_532 calls) Z2UI5_CL_SMP_APP_534 abap
Table - Editable Cells, Add and Delete Rows Z2UI5_CL_SMP_APP_011 - by hand abap
Templating - Metadata-Driven Table and Form Z2UI5_CL_SMP_APP_173 abap
Templating - Dynamic Content in a Nested View Z2UI5_CL_SMP_APP_176 - by hand 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), Z2UI5_CL_SMP_APP_176 (ASSIGN mt_layout[ … ] TO FIELD-SYMBOL( ) writes through the row) 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 76 samples in 260 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 fetched into .deps/ at the commit @abap2ui5/node-runtime was built against - its package.json records it as abap2ui5.openAbapCore - so that the original runs on the open-abap-core the translation runs on) 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