diff --git a/AGENTS.md b/AGENTS.md index 802043fd..2f1239df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -234,14 +234,14 @@ The last full measurement: **61 complete app classes, 39 with a button, all 39 started and rendered.** The site has since grown, and the hand-kept copy of those figures here went stale without anyone noticing — which is what `check:playground` now exists to prevent. The bookkeeping today, printed by the -gate on every run: **68 complete app classes, 47 with a button, 21 excluded on +gate on every run: **82 complete app classes, 63 with a button, 19 excluded on purpose**, every exclusion a marker on its page. The growth is examples whose shapes the measured rules already covered, plus one page completed so its example could run at all; a spot-check of six buttoned examples in a served playground build — the newly buttoned life-cycle class driven through its whole event roundtrip, the quickstart and About classes, tutorial Step 12, the tables page, and the `SELECT FROM t100` example — started and rendered, every -one. The next full measurement opens all 47. +one. The next full measurement opens all 63. **The published playground is what readers get**, not the checkout you tested against. A change to the rules here can ship on its own; a change that depends diff --git a/blog/assets/make-diagrams.py b/blog/assets/make-diagrams.py index 559716f4..b094a31e 100644 --- a/blog/assets/make-diagrams.py +++ b/blog/assets/make-diagrams.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 -"""The diagrams for the abap2UI5 Know-How series. +"""The diagrams for the Technical Insights series. Run from the repository root: python3 blog/assets/make-diagrams.py Writes docs/public/insights/*.svg, one per article that needs one - the pages -reference them absolutely (/insights/...), because generate-llms.mjs copies +reference them absolutely (/insights/...), and the number in a file name is +the number of the article that shows it (00 is the series index), because generate-llms.mjs copies every page into docs/public/ as raw markdown and a relative asset path does not resolve from that copy. @@ -99,17 +100,18 @@ def svg(name, w, h, body): # ---------------------------------------------------------------- 05 the wire def d05(): - b = [head(28, 34, 'What the backend answers with')] - b.append(box(28, 56, 150, 62, 'ABAP class', 'builds both', accent=True)) + b = [head(28, 34, 'One roundtrip - what travels each way')] + b.append(box(28, 56, 150, 88, 'ABAP class', 'builds both', accent=True)) b.append(arrow(186, 74, 300, 74, 'XML view', accent=True)) b.append(arrow(186, 100, 300, 100, 'JSON model', accent=True)) - b.append(box(308, 44, 150, 86, 'UI5', 'renders the HTML')) - b.append(label(28, 152, 'Classic split - the backend sends only the data,' + b.append(arrow(300, 130, 186, 130, 'event + changed values', above=False)) + b.append(box(308, 56, 150, 88, 'UI5', 'renders the HTML')) + b.append(label(28, 182, 'Classic split - the backend sends only the data,' ' the view was deployed with the app', size=11.5)) - b.append(box(28, 166, 150, 44, 'ABAP service', mono=False)) - b.append(arrow(186, 188, 300, 188, 'data')) - b.append(box(308, 166, 150, 44, 'UI5 + view file')) - svg('05-two-strings.svg', 486, 232, ''.join(b)) + b.append(box(28, 196, 150, 44, 'ABAP service', mono=False)) + b.append(arrow(186, 218, 300, 218, 'data')) + b.append(box(308, 196, 150, 44, 'UI5 + view file')) + svg('05-two-strings.svg', 486, 262, ''.join(b)) # ------------------------------------------------------- 06 MPA / SPA / HDA @@ -194,8 +196,8 @@ def d09(): svg('09-draft.svg', 612, 244, ''.join(b)) -# --------------------------------------------------------------- 17 bootstrap -def d17(): +# --------------------------------------------------------------- 27 bootstrap +def d27(): b = [head(28, 34, 'UI5 served by the system')] b.append(box(28, 54, 200, 44, 'SAP release')) b.append(arrow(128, 102, 128, 128)) @@ -209,27 +211,14 @@ def d17(): f'stroke-dasharray="4 4"/>') b.append(label(392, 120, 'independent', size=11.5, fill=R2, weight=600)) b.append(caption(282, 200, 'a current control on a system that never had it')) - svg('17-bootstrap.svg', 540, 226, ''.join(b)) + svg('27-bootstrap.svg', 540, 226, ''.join(b)) -# --------------------------------------------------------------- 18 footprint -def d18(): - b = [head(28, 34, 'Everything in the request path')] - items = [('1', 'HTTP handler class', True), ('2', 'interfaces', False), - ('1', 'database table', False)] - for i, (n, t, acc) in enumerate(items): - x = 28 + i * 168 - b.append(box(x, 58, 148, 74, n, t, accent=acc)) - b.append(caption(28, 158, 'Originally about 2,300 lines of ABAP. Views, program flow ' - 'and controls live in the apps, not in here.')) - svg('18-footprint.svg', 552, 178, ''.join(b)) - - -# ---------------------------------------------------------------- 21 timeline -def d21(): +# ---------------------------------------------------------------- 32 timeline +def d32(): b = [head(28, 32, 'Where the HTML is built')] - yrs = [('2000', 'ITS'), ('2001', 'BSP'), ('2003', 'Web Dynpro'), - ('2010', 'UI5 Freestyle'), ('2019', 'RAP / FE'), ('2023', 'abap2UI5')] + yrs = [('1996', 'ITS'), ('2001', 'BSP'), ('2006', 'Web Dynpro'), + ('2012', 'UI5 Freestyle'), ('2019', 'RAP / FE'), ('2023', 'abap2UI5')] x0, step = 128, 104 xe = x0 + step * 5 b.append(f'') @@ -249,11 +238,11 @@ def d21(): b.append(label(28, 164, 'the browser', size=11.5, fill=MUTED, weight=600)) b.append(caption(28, 214, 'The rendering moved once and stayed. What has been coming ' 'back since is the definition of the screen.')) - svg('21-timeline.svg', 720, 234, ''.join(b)) + svg('32-timeline.svg', 720, 234, ''.join(b)) -# ------------------------------------------------------- 22 where the view lives -def d22(): +# ------------------------------------------------------- 00 where the view lives +def d00(): b = [head(28, 34, 'When the view is fixed')] cols = [('UI5 Freestyle', 'build time', 'a file in a\nfrontend project', False), ('RAP / Fiori Elements', 'activation time', 'UI annotations\non CDS', False), @@ -268,7 +257,7 @@ def d22(): b.append(f'') b.append(caption(28, 218, 'All three render in the browser, with the same controls. ' 'This is the axis the rest follows from.')) - svg('22-where-the-view-lives.svg', 624, 240, ''.join(b)) + svg('00-where-the-view-lives.svg', 624, 240, ''.join(b)) # ------------------------------------------------------------- 10 view swap @@ -339,8 +328,8 @@ def d13(): svg('13-four-verbs.svg', 480, 256, ''.join(b)) -# --------------------------------------------------------------------- 20 VDM -def d20(): +# --------------------------------------------------------------------- 28 VDM +def d28(): b = [head(28, 34, 'What the app reads')] b.append(box(28, 56, 180, 38, 'your app')) b.append(arrow(118, 98, 118, 124, accent=True)) @@ -356,8 +345,146 @@ def d20(): b.append(box(274, 202, 180, 38, 'tables', dashed=True)) b.append(label(376, 149, 'nothing in between', size=11.5)) b.append(label(364, 262, 'the shape of this release', size=11.5, anchor='middle')) - svg('20-vdm.svg', 482, 292, ''.join(b)) - - -for f in (d05, d06, d07, d08, d09, d10, d11, d13, d17, d18, d20, d21, d22): + svg('28-vdm.svg', 482, 292, ''.join(b)) + + + +# ---------------------------------------------------------- 01 runtime model +def d01(): + b = [head(28, 34, 'Model at design time')] + b.append(box(28, 54, 176, 40, 'entity type', 'declared in CDS')) + b.append(arrow(116, 98, 116, 124)) + b.append(box(28, 130, 176, 40, 'OData metadata', 'fixed for every client')) + b.append(arrow(116, 174, 116, 200)) + b.append(box(28, 206, 176, 40, 'data', 'in that shape', dashed=True)) + b.append(f'') + b.append(head(268, 34, 'Model at runtime')) + b.append(box(268, 54, 176, 40, 'any internal table', 'a SELECT, a BAPI, an EML read', + dashed=True)) + b.append(arrow(356, 98, 356, 124, accent=True)) + b.append(box(268, 130, 176, 40, 'RTTS', 'reads the components', accent=True)) + b.append(arrow(356, 174, 356, 200, accent=True)) + b.append(box(268, 206, 176, 40, 'columns + bindings', 'built for this table')) + svg('01-runtime-model.svg', 472, 276, ''.join(b)) + + +# ------------------------------------------------------------ 12 three seams +def d12(): + b = [head(28, 34, 'Where an app reaches past the XML view')] + rows = [('a method by control id', 'follow_up_action( cs_event-control_by_id )', + 'no JavaScript at all'), + ('an expression in the view', '{= ${STATUS} === \'E\' ? \'Error\' : \'None\' }', + 'evaluated in the browser'), + ('a custom control', 'z2ui5_ccc - a BSP the UI5 loader resolves', + 'a module, not a string')] + for i, (t, how, note) in enumerate(rows): + y = 56 + i * 54 + b.append(box(28, y, 178, 40, t, accent=(i == 0))) + b.append(arrow(214, y + 20, 246, y + 20, accent=(i == 0))) + b.append(label(254, y + 17, how, size=11.5, fill=INK, mono=True)) + b.append(label(254, y + 33, note, size=11, fill=MUTED)) + b.append(f'') + b.append(caption(28, 246, 'Everything else - JavaScript for the first page - ' + 'is one setting in the user exit, for the whole system')) + svg('12-three-seams.svg', 588, 264, ''.join(b)) + + +# ------------------------------------------------------ 15 PARAMETERS / _bind +def d15(): + b = [head(28, 34, 'PARAMETERS')] + b.append(box(28, 54, 150, 62, 'pa_arbgb', 'variable = field', accent=True, mono=True)) + b.append(caption(28, 140, 'one declaration')) + b.append(f'') + b.append(head(232, 34, 'OData')) + b.append(box(232, 54, 96, 62, 'variable', 'in ABAP')) + b.append(arrow(336, 85, 386, 85, 'service', dashed=True)) + b.append(box(392, 54, 96, 62, 'field', 'in the view')) + b.append(caption(232, 140, 'two artefacts, kept in step by a contract')) + b.append(f'') + b.append(head(542, 34, '_bind')) + b.append(box(542, 54, 150, 62, 'client->_bind( pa_arbgb )', 'the variable, by reference', + accent=True, mono=True)) + b.append(caption(542, 140, 'the field is the variable again')) + svg('15-parameters-bind.svg', 720, 168, ''.join(b)) + + +# ---------------------------------------------------------------- 16 dispatch +def d16(): + b = [head(28, 34, 'main( ) asks three questions, in this order')] + rows = [('check_on_init( )', 'once - the instance has never run', 'read data, show the view', True), + ('check_on_navigated( )', 'back from a sub-app or popup', 'show the view again', False), + ('check_on_event( )', 'the user did something', 'change attributes - the view stands', False)] + for i, (q, when, owes, acc) in enumerate(rows): + y = 56 + i * 54 + b.append(box(28, y, 190, 40, q, accent=acc, mono=True)) + b.append(label(232, y + 17, when, size=12, fill=INK)) + b.append(label(232, y + 33, owes, size=11.5, fill=R2 if acc else MUTED, + weight=600 if acc else 400)) + b.append(f'') + b.append(caption(28, 246, 'A first call raises init AND navigated - so init stands first, ' + 'or it never runs')) + svg('16-dispatch.svg', 548, 264, ''.join(b)) + + +# ------------------------------------------------------------ 21 lock at save +def d21(): + b = [head(28, 34, 'Where the lock lives')] + b.append(label(28, 60, 'GUI dialog', size=12, fill=INK, weight=600)) + b.append(f'') + b.append(label(320, 61, 'ENQUEUE held for the whole session', size=11, anchor='middle')) + b.append(label(28, 106, 'abap2UI5', size=12, fill=R2, weight=600)) + for i, x in enumerate((120, 262, 404)): + b.append(f'') + b.append(label(x + 58, 106, ['open', 'edit', 'save'][i], size=11, anchor='middle')) + b.append(f'') + b.append(label(478, 106, 'lock', size=11, fill=R2, weight=700, anchor='middle')) + b.append(f'') + b.append(label(320, 142, 'minutes - nobody holds anything', size=11, anchor='middle')) + b.append(label(320, 158, 'the timestamp read at open is compared at save', size=11, + fill=R2, anchor='middle')) + b.append(f'') + b.append(caption(28, 198, 'The lock exists for milliseconds, inside one roundtrip; ' + 'the optimistic check guards the rest')) + svg('21-lock-at-save.svg', 548, 216, ''.join(b)) + + +# ------------------------------------------------------ 23 what a click costs +def d23(): + b = [head(28, 34, 'What one click moves')] + b.append(box(28, 62, 118, 48, 'browser', 'event + changes')) + b.append(arrow(154, 86, 214, 86, 'POST')) + b.append(box(220, 62, 132, 48, 'app instance', 'runs main( )', accent=True)) + b.append(arrow(360, 86, 420, 86, 'model')) + b.append(box(426, 62, 118, 48, 'browser', 'renders')) + b.append(f'') + b.append(f'') + b.append(label(298, 140, 'the whole instance', size=11.5, fill=R, weight=600)) + b.append(box(204, 156, 164, 40, 'z2ui5_t_01', accent=True, mono=True)) + b.append(caption(28, 224, 'The model is the bound attributes; the instance is all of them. ' + 'Both are paid on every click.')) + svg('23-what-a-click-costs.svg', 572, 244, ''.join(b)) + + +# ------------------------------------------------- 25 more than one class +def d25(): + b = [head(28, 34, 'One app, several classes')] + b.append(box(28, 56, 150, 48, 'zcl_app_list', 'a screen', accent=True, mono=True)) + b.append(arrow(186, 80, 296, 80, 'nav_app_call', accent=True)) + b.append(box(302, 56, 150, 48, 'zcl_app_detail', 'a screen', accent=True, mono=True)) + b.append(arrow(377, 108, 377, 148, accent=True)) + b.append(box(302, 154, 150, 48, 'zcl_app_confirm', 'a popup on the stack', mono=True)) + b.append(arrow(103, 108, 103, 148)) + b.append(box(28, 154, 150, 48, 'nest_view_display', 'a part of the screen', mono=True)) + b.append(arrow(460, 80, 530, 80, 'calls')) + b.append(box(536, 56, 160, 48, 'zcl_invoice_service', 'no screen', mono=True)) + b.append(f'') + b.append(caption(28, 244, 'Classes, method calls and one stack - ' + 'no component, no manifest, no router')) + svg('25-more-than-one-class.svg', 724, 262, ''.join(b)) + + +for f in (d00, d01, d05, d06, d07, d08, d09, d10, d11, d12, d13, d15, d16, d21, d23, + d25, d27, d28, d32): f() diff --git a/blog/teaser-posts.md b/blog/teaser-posts.md index 2e5f7354..f37a62b7 100644 --- a/blog/teaser-posts.md +++ b/blog/teaser-posts.md @@ -27,7 +27,7 @@ attached article preview, so the post carries no inline URL. 744 characters. > > #ABAP #SAP #UI5 -## abap2UI5 is not a Programming Model +## abap2UI5 Is Not a Programming Model Plain text — LinkedIn renders no markdown. @@ -139,7 +139,7 @@ Plain text — LinkedIn renders no markdown. > > #ABAP #SAP #UI5 -## The Same Service for Every App +## One Handler for Every App Plain text — LinkedIn renders no markdown. @@ -183,7 +183,7 @@ Plain text — LinkedIn renders no markdown. > > #ABAP #SAP #UI5 -## PUBLIC Means Persisted +## A New Instance on Every Request Plain text — LinkedIn renders no markdown. @@ -227,7 +227,7 @@ Plain text — LinkedIn renders no markdown. > > #ABAP #SAP #UI5 -## index.html Lives in a String +## No Build, No Deploy, No Cache Plain text — LinkedIn renders no markdown. @@ -271,7 +271,7 @@ Plain text — LinkedIn renders no markdown. > > #ABAP #SAP #UI5 -## Four Verbs +## Four Verbs, Every Control Plain text — LinkedIn renders no markdown. @@ -292,7 +292,7 @@ Plain text — LinkedIn renders no markdown. > > #ABAP #SAP #UI5 -## The Class That Runs +## A Classrun for the Browser Plain text — LinkedIn renders no markdown. @@ -485,7 +485,7 @@ Plain text — LinkedIn renders no markdown. > > #ABAP #SAP #UI5 -## RAP or abap2UI5 — When to Use Which +## RAP or abap2UI5? Plain text — LinkedIn renders no markdown. @@ -508,7 +508,7 @@ Plain text — LinkedIn renders no markdown. > > #ABAP #SAP #UI5 -## UI5 Freestyle or abap2UI5 — When to Use Which +## UI5 Freestyle or abap2UI5? Plain text — LinkedIn renders no markdown. @@ -530,7 +530,7 @@ Plain text — LinkedIn renders no markdown. > > #ABAP #SAP #UI5 -## Low-Code or abap2UI5 — When to Use Which +## Low-Code or abap2UI5? Plain text — LinkedIn renders no markdown. diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index fa4fd484..719a5d5e 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -585,37 +585,49 @@ export default defineConfig({ // who opens Advanced Topics for a how-to should meet the how-tos // first. text: "Technical Insights", - link: "/advanced/insights/01-somewhere-on-the-way-to-ui5", + link: "/advanced/insights/", collapsed: true, items: [ + // Four blocks - why, how it works, a working day, where it + // belongs - and the index page at the link above names them. The + // numbers are part of each title on purpose: the articles are + // posted one at a time, and "#21" is how a reader refers to one. { text: "#1 Somewhere on the Way to UI5, We Lost RTTS", link: "/advanced/insights/01-somewhere-on-the-way-to-ui5" }, - { text: "#2 abap2UI5 is not a Programming Model", link: "/advanced/insights/02-not-a-programming-model" }, + { text: "#2 abap2UI5 Is Not a Programming Model", link: "/advanced/insights/02-not-a-programming-model" }, { text: "#3 The Cost of a Screen", link: "/advanced/insights/03-the-cost-of-a-screen" }, { text: "#4 No Annotation in Between", link: "/advanced/insights/04-no-annotation-in-between" }, { text: "#5 UI5 Over-the-Wire", link: "/advanced/insights/05-ui5-over-the-wire" }, - { text: "#6 The Frontend Knows Nothing", link: "/advanced/insights/06-the-frontend-that-does-not-know" }, - { text: "#7 The Same Service for Every App", link: "/advanced/insights/07-one-service-for-every-app" }, + { text: "#6 The Frontend Knows Nothing", link: "/advanced/insights/06-the-frontend-knows-nothing" }, + { text: "#7 One Handler for Every App", link: "/advanced/insights/07-one-handler-for-every-app" }, { text: "#8 Only the Changed Part", link: "/advanced/insights/08-only-the-changed-part" }, - { text: "#9 PUBLIC Means Persisted", link: "/advanced/insights/09-public-means-persisted" }, + { text: "#9 A New Instance on Every Request", link: "/advanced/insights/09-a-new-instance-on-every-request" }, { text: "#10 Swapping the View at Runtime", link: "/advanced/insights/10-swapping-the-view-at-runtime" }, - { text: "#11 index.html Lives in a String", link: "/advanced/insights/11-index-html-lives-in-a-string" }, + { text: "#11 No Build, No Deploy, No Cache", link: "/advanced/insights/11-no-build-no-deploy-no-cache" }, { text: "#12 Where Your Own JavaScript Goes", link: "/advanced/insights/12-where-your-own-javascript-goes" }, - { text: "#13 Four Verbs", link: "/advanced/insights/13-four-verbs" }, - { text: "#14 The Class That Runs", link: "/advanced/insights/14-the-class-that-runs" }, + { text: "#13 Four Verbs, Every Control", link: "/advanced/insights/13-four-verbs-every-control" }, + { text: "#14 A Classrun for the Browser", link: "/advanced/insights/14-a-classrun-for-the-browser" }, { text: "#15 Where the Selection Screen Went", link: "/advanced/insights/15-where-the-selection-screen-went" }, - { text: "#16 No Cache, No Deploy, Any IDE", link: "/advanced/insights/16-no-cache-no-deploy-any-ide" }, - { text: "#17 One Codebase, 7.02 to ABAP Cloud", link: "/advanced/insights/17-one-codebase-702-to-abap-cloud" }, - { text: "#18 Cloud-Ready Is a Property of Your App", link: "/advanced/insights/18-cloud-ready-is-a-property-of-your-app" }, - { text: "#19 When the API Is Not Released", link: "/advanced/insights/19-when-the-api-is-not-released" }, - { text: "#20 On-Stack or Side-by-Side", link: "/advanced/insights/20-on-stack-or-side-by-side" }, - { text: "#21 One App, Many Systems", link: "/advanced/insights/21-one-app-many-systems" }, - { text: "#22 2,300 Lines", link: "/advanced/insights/22-2300-lines" }, - { text: "#23 From ITS to abap2UI5", link: "/advanced/insights/23-abap-on-the-web" }, - { text: "#24 What It Does Not Do", link: "/advanced/insights/24-where-the-line-is" }, - { text: "#25 Where the View Lives", link: "/advanced/insights/25-where-the-view-lives" }, - { text: "#26 RAP or abap2UI5 — When to Use Which", link: "/advanced/insights/26-rap-or-abap2ui5" }, - { text: "#27 UI5 Freestyle or abap2UI5 — When to Use Which", link: "/advanced/insights/27-freestyle-or-abap2ui5" }, - { text: "#28 Low-Code or abap2UI5 — When to Use Which", link: "/advanced/insights/28-low-code-or-abap2ui5" }, + { text: "#16 One Click, One Request", link: "/advanced/insights/16-one-click-one-request" }, + { text: "#17 What the Client Can Do", link: "/advanced/insights/17-what-the-client-can-do" }, + { text: "#18 CALL SCREEN, LEAVE SCREEN", link: "/advanced/insights/18-call-screen-leave-screen" }, + { text: "#19 Where F4 Went", link: "/advanced/insights/19-where-f4-went" }, + { text: "#20 MESSAGE Still Works", link: "/advanced/insights/20-message-still-works" }, + { text: "#21 The Lock Is Gone by the Next Click", link: "/advanced/insights/21-the-lock-is-gone-by-the-next-click" }, + { text: "#22 Who May Start Which App", link: "/advanced/insights/22-who-may-start-which-app" }, + { text: "#23 100 Rows, Four Hours, One Request", link: "/advanced/insights/23-100-rows-four-hours-one-request" }, + { text: "#24 ABAP Unit for a Screen", link: "/advanced/insights/24-abap-unit-for-a-screen" }, + { text: "#25 When One Class Is Not Enough", link: "/advanced/insights/25-when-one-class-is-not-enough" }, + { text: "#26 A Tile Like Any Other", link: "/advanced/insights/26-a-tile-like-any-other" }, + { text: "#27 One Codebase, 7.02 to ABAP Cloud", link: "/advanced/insights/27-one-codebase-702-to-abap-cloud" }, + { text: "#28 Cloud-Ready Is a Property of Your App", link: "/advanced/insights/28-cloud-ready-is-a-property-of-your-app" }, + { text: "#29 When the API Is Not Released", link: "/advanced/insights/29-when-the-api-is-not-released" }, + { text: "#30 On-Stack or Side-by-Side", link: "/advanced/insights/30-on-stack-or-side-by-side" }, + { text: "#31 One App, Many Systems", link: "/advanced/insights/31-one-app-many-systems" }, + { text: "#32 From ITS to abap2UI5", link: "/advanced/insights/32-from-its-to-abap2ui5" }, + { text: "#33 RAP or abap2UI5?", link: "/advanced/insights/33-rap-or-abap2ui5" }, + { text: "#34 UI5 Freestyle or abap2UI5?", link: "/advanced/insights/34-freestyle-or-abap2ui5" }, + { text: "#35 Low-Code or abap2UI5?", link: "/advanced/insights/35-low-code-or-abap2ui5" }, + { text: "#36 Written for Agents", link: "/advanced/insights/36-written-for-agents" }, ], }, ], diff --git a/docs/advanced/insights/01-somewhere-on-the-way-to-ui5.md b/docs/advanced/insights/01-somewhere-on-the-way-to-ui5.md index 6a057f05..0d46ed36 100644 --- a/docs/advanced/insights/01-somewhere-on-the-way-to-ui5.md +++ b/docs/advanced/insights/01-somewhere-on-the-way-to-ui5.md @@ -26,8 +26,6 @@ a typed OData service. That holds for freestyle UI5, for Fiori Elements, and for a fully backend-driven RAP application with a consumption view. The shape of the model is decided when the code is written and baked into the contract. -*[image: RESTful APIs in a simple and standard way — odata.org]* - And that is powerful! The point of OData is that a client can trust the API and discover everything it needs from the metadata document — it does not have to know SAP at all. Whenever you do not know the client, or the client does not @@ -45,7 +43,9 @@ on design-time metadata is aimed at a different problem. The model is assembled at runtime, and in ABAP that can be done with RTTS, a service nearly every ABAP developer already knows (the reading half is RTTI): -*[image: RTTS — read metadata at runtime]* +![Design time: the entity type is declared before any data exists. Runtime: RTTS reads the shape from the data that is there.](/insights/01-runtime-model.svg) + +*Design time: the entity type is declared before any data exists. Runtime: RTTS reads the shape from the data that is there.* But how do we get that data into a UI5 app? diff --git a/docs/advanced/insights/02-not-a-programming-model.md b/docs/advanced/insights/02-not-a-programming-model.md index 55d04049..87b361bd 100644 --- a/docs/advanced/insights/02-not-a-programming-model.md +++ b/docs/advanced/insights/02-not-a-programming-model.md @@ -1,4 +1,4 @@ -# #2 abap2UI5 is not a Programming Model +# #2 abap2UI5 Is Not a Programming Model The useful question about a UI framework is not what it can do. It is what it wants from you: a structure to follow, a lifecycle to fit into, layers to fill diff --git a/docs/advanced/insights/05-ui5-over-the-wire.md b/docs/advanced/insights/05-ui5-over-the-wire.md index 21bb3bed..185c3bb0 100644 --- a/docs/advanced/insights/05-ui5-over-the-wire.md +++ b/docs/advanced/insights/05-ui5-over-the-wire.md @@ -28,9 +28,9 @@ and the model that fills it: { "MODEL": { "NAME": "test" } } ``` -![The backend answers with a view and its model; the browser renders both.](/insights/05-two-strings.svg) +![The browser sends an event, the backend answers with a view and its model, the browser renders both.](/insights/05-two-strings.svg) -*The backend answers with a view and its model; the browser renders both.* +*The browser sends an event, the backend answers with a view and its model, the browser renders both.* Nothing there is a protocol abap2UI5 invented. The XML is UI5's own view format, the JSON is an ordinary UI5 JSON model, and the frontend does what it @@ -53,7 +53,3 @@ it. A view, not a page. else in this series is a consequence of that one move.** Happy ABAPing! 🦖🦕🦣 - -*This article and the ones that follow it are cut from -[Under the Hood of abap2UI5](https://community.sap.com/t5/technology-blog-posts-by-members/abap2ui5-7-technical-background-under-the-hood-of-abap2ui5/ba-p/13566459), -published on the SAP Community — the long version, with the diagrams.* diff --git a/docs/advanced/insights/06-the-frontend-that-does-not-know.md b/docs/advanced/insights/06-the-frontend-knows-nothing.md similarity index 100% rename from docs/advanced/insights/06-the-frontend-that-does-not-know.md rename to docs/advanced/insights/06-the-frontend-knows-nothing.md diff --git a/docs/advanced/insights/07-one-service-for-every-app.md b/docs/advanced/insights/07-one-handler-for-every-app.md similarity index 55% rename from docs/advanced/insights/07-one-service-for-every-app.md rename to docs/advanced/insights/07-one-handler-for-every-app.md index 2c8825dd..1e0a9400 100644 --- a/docs/advanced/insights/07-one-service-for-every-app.md +++ b/docs/advanced/insights/07-one-handler-for-every-app.md @@ -1,4 +1,4 @@ -# #7 The Same Service for Every App +# #7 One Handler for Every App Count what a screen usually costs in backend artefacts. A CDS view or two, a service definition, a service binding, a behavior definition and its @@ -24,13 +24,30 @@ data follows within that shape for the rest of the session. Here the model travels **with** every response, so each response may carry a different one. There is no contract to violate because there is no contract to register. +## How Much Handler That Is + +The communication core of abap2UI5 is one HTTP handler class, two interfaces +and one database table. Originally about 2,300 lines of ABAP. The framework has +grown since — most visibly the optional view builder — but the part that +carries every request is still that. + +It is small because of what it leaves to the apps. The framework does not build +views; apps do. It does not decide program flow; apps do. It does not wrap UI5 +controls, so it does not grow when UI5 does. It does not implement a protocol, +because the protocol is a POST with two strings. + +That has a practical consequence. A framework in the request path of a business +application sees every input, every response and every user, and a dependency +that cannot be read cannot really be reviewed. Here one class holds the logic, +and reading it is an afternoon. The audit answers are short for the same +reason: no CDS artefacts, no RAP objects, no generated code, no build step, no +transitive package tree. The system footprint is the source in the repository. + What that removes is not effort — the app still decides everything — but artefacts. A screen stops being a set of objects to create, name, transport and -govern, and becomes a class. In a landscape where every new object is something -someone has to review, keep clean and eventually migrate, the cheapest artefact -is the one that was never created. +govern, and becomes a class. -**A service that knows nothing about the application never has to be written +**A handler that knows nothing about the application never has to be written again for the next one.** Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/08-only-the-changed-part.md b/docs/advanced/insights/08-only-the-changed-part.md index 03ba9694..5295996b 100644 --- a/docs/advanced/insights/08-only-the-changed-part.md +++ b/docs/advanced/insights/08-only-the-changed-part.md @@ -13,17 +13,23 @@ So the view is not sent every time. Sending it is a decision the app makes: METHOD z2ui5_if_app~main. me->client = client. - text = text && ` text`. - IF client->check_on_navigated( ) OR partly = abap_false. - set_view( ). + IF client->check_on_navigated( ). + set_view( ). " first display, and every return from a sub-app + RETURN. ENDIF. - " no ELSE - a model-only change travels with the response by itself + + IF client->get_event( ) = `COUNT`. + count = count + 1. " changes the model - and nothing else happens + ENDIF. + " no set_view( ) here: the response carries the model alone ENDMETHOD. ``` -When `set_view( )` is skipped, the response carries the model alone. The view +`count` is a public attribute bound to a `Text` in the view. Every press of +the button runs `main( )`, and `main( )` changes a number and returns. +When `set_view( )` is not called, the response carries the model alone. The view in the browser is the one already standing, and UI5 does what UI5 does with a changed model: data binding updates the controls bound to what changed, and touches nothing else. diff --git a/docs/advanced/insights/09-a-new-instance-on-every-request.md b/docs/advanced/insights/09-a-new-instance-on-every-request.md new file mode 100644 index 00000000..c47089e6 --- /dev/null +++ b/docs/advanced/insights/09-a-new-instance-on-every-request.md @@ -0,0 +1,56 @@ +# #9 A New Instance on Every Request + +Every request lands on a fresh instance of the app class. No session holds the +previous one, and the next request may not even reach the same application +server. + +So how does anything the user typed three clicks ago still exist? + +`z2ui5_if_app` inherits `if_serializable_object`. After each request the +framework serializes the app instance into `z2ui5_t_01` — one generic table +for every app, not a typed draft table per data model — and reads it back on the +next one. The browser gets a draft id, and sends the draft id back. The state +itself never leaves the server. + +![Every request is a new instance; z2ui5_t_01 is what carries the last one forward.](/insights/09-draft.svg) + +*Every request is a new instance; z2ui5_t_01 is what carries the last one forward.* + +What survives is the whole instance, whatever the visibility of an attribute. +`PUBLIC` is not what keeps a value alive — it is what `_bind( )` needs in order +to reach it, because the framework resolves a bound attribute by name from +outside the class. So the two sections have two different jobs: + +```abap + PUBLIC SECTION. + " bound to the view - the framework reads and writes these by name + DATA customer TYPE string. + DATA items TYPE ty_t_item. + + PROTECTED SECTION. + " never bound - but serialized all the same + DATA client TYPE REF TO z2ui5_if_client. + DATA t_cache TYPE ty_t_catalog. +``` + +What does *not* survive is what the serializer cannot write: a local variable, +an open cursor, an acquired lock, a reference to a class that is not +serializable. The `client` reference above is set again on every call, which +is why `me->client = client` is the first line of every `main( )`. + +And that single fact is the convention that catches people: a catalogue read +once and parked in an attribute is not a cached catalogue. It is a payload that +is written to the database and read back after every single click, in either +section, and it makes every click slower for as long as the app runs. The fix +is not a keyword. Anything large is re-read per request instead of carried, and +the instance stays the size of what the screen needs. + +What is bought with that discipline is worth the rule. The app feels stateful — +PBO, PAI, cancel, back — while every request is genuinely independent. Any +server can answer any click. Nothing has to be sticky, drained before a restart, +or replicated between nodes. + +**Stateful for the user, stateless for the system — and the price is keeping +the instance small.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/09-public-means-persisted.md b/docs/advanced/insights/09-public-means-persisted.md deleted file mode 100644 index 678cce05..00000000 --- a/docs/advanced/insights/09-public-means-persisted.md +++ /dev/null @@ -1,47 +0,0 @@ -# #9 PUBLIC Means Persisted - -Every request lands on a fresh instance of the app class. No session holds the -previous one, and the next request may not even reach the same application -server. - -So how does anything the user typed three clicks ago still exist? - -`z2ui5_if_app` inherits `if_serializable_object`. After each request the -framework serializes the app instance into `z2ui5_t_01` — one generic table -for every app, not a typed draft table per data model — and reads it back on the -next one. What survives is exactly the `PUBLIC SECTION`; nothing else is part of -the serialized state. - -![Every request is a new instance; z2ui5_t_01 is what carries the last one forward.](/insights/09-draft.svg) - -*Every request is a new instance; z2ui5_t_01 is what carries the last one forward.* - -That single fact is the convention that catches people: - -```abap - PUBLIC SECTION. - " state - serialized after every request, sent to the browser, read back - DATA customer TYPE string. - DATA items TYPE ty_t_item. - - PROTECTED SECTION. - " working data - rebuilt per request, never travels - DATA client TYPE REF TO z2ui5_if_client. - DATA t_cache TYPE ty_t_catalog. -``` - -A catalogue read once and parked in a public attribute is not a cached -catalogue. It is a payload that is written to the database and shipped to the -browser after every single click, and it makes every click slower for as long -as the app runs. The fix is one keyword: state stays public, working data goes -protected, and anything large is re-read per request instead of carried. - -What is bought with that discipline is worth the rule. The app feels stateful — -PBO, PAI, cancel, back — while every request is genuinely independent. Any -server can answer any click. Nothing has to be sticky, drained before a restart, -or replicated between nodes. - -**Stateful for the user, stateless for the system — and the price is knowing -which section an attribute belongs in.** - -Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/11-index-html-lives-in-a-string.md b/docs/advanced/insights/11-index-html-lives-in-a-string.md deleted file mode 100644 index 84d9c2f1..00000000 --- a/docs/advanced/insights/11-index-html-lives-in-a-string.md +++ /dev/null @@ -1,36 +0,0 @@ -# #11 index.html Lives in a String - -Somebody has to deliver the first HTML page. In the SAP world that normally -means a BSP application: a frontend artefact, built somewhere, deployed to the -ABAP stack, transported on its own path, and invalidated from its own caches -when it changes. - -abap2UI5 does not have one. The initial GET is answered from ABAP source code — -the page and the frontend files it needs are strings inside the handler, and -serving them is a method call. - -![Four steps, or a method that returns a string.](/insights/11-initial-request.svg) - -*Four steps, or a method that returns a string.* - -The consequence is a project made of nothing but ABAP. - -That is what makes the whole thing installable with abapGit and nothing else. -No npm install, no bundler, no dist folder, no separate deployment path that -has to succeed for the app to exist. Pull the repository, activate, call the -ICF node. - -It is also the reason a change is visible immediately. There is no build output -to go stale, so there is no cache to invalidate — the loop that costs the most -patience in frontend development on the ABAP stack simply is not present. - -And there is a quieter benefit that has become harder to ignore. A frontend -build pipeline is a dependency tree, and a dependency tree is a supply chain -that somebody now has to audit, pin, renew and answer for. A project whose -frontend ships as ABAP source has one thing to review: the source. Every file -that reaches the browser is in the repository, readable, diffable, and -transported by the system that already governs everything else. - -**The cheapest build pipeline is the one that was never introduced.** - -Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/11-no-build-no-deploy-no-cache.md b/docs/advanced/insights/11-no-build-no-deploy-no-cache.md new file mode 100644 index 00000000..246292fa --- /dev/null +++ b/docs/advanced/insights/11-no-build-no-deploy-no-cache.md @@ -0,0 +1,55 @@ +# #11 No Build, No Deploy, No Cache + +The loop is: change the class, activate, refresh the browser. + +That is the whole article, and it is worth spelling out what is missing from it, +starting with the first page. Somebody has to deliver the initial HTML. In the +SAP world that normally means a BSP application: a frontend artefact, built +somewhere, deployed to the ABAP stack, transported on its own path, and +invalidated from its own caches when it changes. + +abap2UI5 does not have one. The initial GET is answered from ABAP source code — +the page and the frontend files it needs are strings inside the handler, and +serving them is a method call. + +![Four steps, or a method that returns a string.](/insights/11-initial-request.svg) + +*Four steps, or a method that returns a string.* + +The consequence is a project made of nothing but ABAP, and everything below +follows from it. + +**No build.** Pull the repository with abapGit, activate, call the ICF node. No +npm install, no bundler, no dist folder, no separate deployment path that has +to succeed for the app to exist. + +**No deployment.** The app is a class. Activating it is the deployment, and the +standard transport system moves it to production like any other ABAP object. +There is no state in which the backend is live and the frontend is not. + +**No cache to invalidate.** The UI is built on every request, so there is no +build output that can be stale. Nobody runs a cache transaction, nobody asks a +colleague to hard-refresh, and a change that does not appear is a change that +was not activated. + +**No IDE agreement.** It is ABAP. ADT, SE80, or a VS Code setup — that stays a +personal preference rather than a project decision, and nobody has to install a +frontend toolchain to fix a label. + +**No context switch to debug.** A breakpoint in the method that built the view +stops in the method that built the view. The browser dev tools stay closed, +because the logic that produced the screen never left the backend. + +**No supply chain to answer for.** A frontend build pipeline is a dependency +tree, and a dependency tree is something somebody has to audit, pin and renew. +A project whose frontend ships as ABAP source has one thing to review: the +source. Every file that reaches the browser is in the repository, readable, +diffable, and transported by the system that already governs everything else. + +Individually these are conveniences. Together they are the reason a screen gets +tried at all: when an experiment costs a class and a refresh, the answer to +"could we just show this on a screen?" stops being a project. + +**Iteration speed is not a nice-to-have. It decides which ideas get built.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/12-where-your-own-javascript-goes.md b/docs/advanced/insights/12-where-your-own-javascript-goes.md index 83e4fc01..1d90090c 100644 --- a/docs/advanced/insights/12-where-your-own-javascript-goes.md +++ b/docs/advanced/insights/12-where-your-own-javascript-goes.md @@ -8,6 +8,10 @@ else, and no property will do it for you. abap2UI5 has no plugin system for this, and that is deliberate. It has three seams, each one a declared place rather than an escape hatch. +![Three seams, from the cheapest to the widest: a method call by control id, an expression in the view, a custom control in its own BSP.](/insights/12-three-seams.svg) + +*Three seams, from the cheapest to the widest: a method call by control id, an expression in the view, a custom control in its own BSP.* + **An imperative method needs no JavaScript at all.** The client can call one on a control by id: @@ -36,9 +40,9 @@ has to escape every brace, and one missed escape is a parser error on the whole statement instead of a wrong string. **Everything else is a system decision, not an app decision.** Extra JavaScript -for the initial page is `custom_js` in the HTTP GET configuration, set through -`z2ui5_if_exit` — one place, reviewable, and the same for every app in the -system. +for the initial page is `custom_js` in the HTTP GET configuration, set in the +[user exit](/advanced/extensibility/user_exits) through `z2ui5_if_ui5_exit` — +one place, reviewable, and the same for every app in the system. None of them lets an app change the framework, and none requires the framework to change for an app. No plugin registry to learn, and no pull request to wait diff --git a/docs/advanced/insights/13-four-verbs.md b/docs/advanced/insights/13-four-verbs-every-control.md similarity index 98% rename from docs/advanced/insights/13-four-verbs.md rename to docs/advanced/insights/13-four-verbs-every-control.md index cc6ce517..bf941afc 100644 --- a/docs/advanced/insights/13-four-verbs.md +++ b/docs/advanced/insights/13-four-verbs-every-control.md @@ -1,4 +1,4 @@ -# #13 Four Verbs +# #13 Four Verbs, Every Control The first view builder in abap2UI5 had one method per UI5 control. `button( )`, `input( )`, `table( )` — hundreds of them, each with the control's properties as diff --git a/docs/advanced/insights/14-the-class-that-runs.md b/docs/advanced/insights/14-a-classrun-for-the-browser.md similarity index 59% rename from docs/advanced/insights/14-the-class-that-runs.md rename to docs/advanced/insights/14-a-classrun-for-the-browser.md index c82dfe64..00707709 100644 --- a/docs/advanced/insights/14-the-class-that-runs.md +++ b/docs/advanced/insights/14-a-classrun-for-the-browser.md @@ -1,4 +1,4 @@ -# #14 The Class That Runs +# #14 A Classrun for the Browser Every ABAP developer already knows the smallest program that does something: @@ -41,16 +41,15 @@ destination: it runs in a browser instead of the console, follows the Fiori design guidelines, and can be sent to a colleague as a URL rather than as an instruction to open ADT and press F9. -There is a property here that has quietly become valuable. The entire -application is one file. Not a class plus a service plus a binding plus a -frontend project — one artefact, read from top to bottom, with its state, its -screen and its logic in the same place. +There is a property here that is easy to underrate. A small application is one +class, and a class is one thing to read: state, screen and logic in the same +place, top to bottom. A reviewer, a colleague inheriting it, a search across the +system, an agent asked to change something — each can hold the whole thing. +Code that is understood in one piece is code that is changed confidently. -Anything that has to reason about the app — a reviewer, a colleague inheriting -it, a search across the system, an agent asked to change something — can hold it -in full. Code that is understood in one piece is code that is changed -confidently, and there is not much of that around. - -**The unit of an abap2UI5 application is a file somebody can read.** +That holds for the small screens this series keeps coming back to. A larger app +splits into several classes like any other ABAP program does, and how is +[its own article](/advanced/insights/25-when-one-class-is-not-enough). The +point is where it starts: one class, one method, and it runs. Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/15-where-the-selection-screen-went.md b/docs/advanced/insights/15-where-the-selection-screen-went.md index e05b3c50..271364f2 100644 --- a/docs/advanced/insights/15-where-the-selection-screen-went.md +++ b/docs/advanced/insights/15-where-the-selection-screen-went.md @@ -21,6 +21,10 @@ places, maintained by two people, kept in step by a service in between. abap2UI5 puts them back together with `_bind`: +![PARAMETERS: one declaration is the variable and the field. OData: variable and field are two artefacts with a service between. _bind: the field is the variable again.](/insights/15-parameters-bind.svg) + +*PARAMETERS: one declaration is the variable and the field. OData: variable and field are two artefacts with a service between. _bind: the field is the variable again.* + ```abap )->tag( `Input` )->a( n = `value` v = client->_bind( pa_arbgb ) diff --git a/docs/advanced/insights/16-no-cache-no-deploy-any-ide.md b/docs/advanced/insights/16-no-cache-no-deploy-any-ide.md deleted file mode 100644 index bfcb982d..00000000 --- a/docs/advanced/insights/16-no-cache-no-deploy-any-ide.md +++ /dev/null @@ -1,34 +0,0 @@ -# #16 No Cache, No Deploy, Any IDE - -The loop is: change the class, activate, refresh the browser. - -That is the whole article, and it is worth spelling out what is missing from it. - -**No deployment.** The app is a class. Activating it is the deployment, and the -standard transport system moves it to production like any other ABAP object. -There is no second path that also has to succeed, and no state in which the -backend is live and the frontend is not. - -**No cache to invalidate.** The UI is built on every request, so there is no -build output that can be stale. Nobody runs a cache transaction, nobody asks a -colleague to hard-refresh, and a change that does not appear is a change that -was not activated. - -**No IDE agreement.** It is ABAP. ADT, SE80, or a VS Code setup — that stays a -personal preference rather than a project decision, and nobody has to install a -frontend toolchain to fix a label. - -**No context switch to debug.** A breakpoint in the method that built the view -stops in the method that built the view. The browser dev tools stay closed, -because the logic that produced the screen never left the backend. - -**No handover to share.** Sending someone an app means sending them a class — -abapGit, a transport, or paste. Reviewing one means reading ABAP. - -Individually these are conveniences. Together they are the reason a screen gets -tried at all: when an experiment costs a class and a refresh, the answer to -"could we just show this on a screen?" stops being a project. - -**Iteration speed is not a nice-to-have. It decides which ideas get built.** - -Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/16-one-click-one-request.md b/docs/advanced/insights/16-one-click-one-request.md new file mode 100644 index 00000000..1871e5fe --- /dev/null +++ b/docs/advanced/insights/16-one-click-one-request.md @@ -0,0 +1,62 @@ +# #16 One Click, One Request + +The mental model that everything else in the framework rests on fits in one +sentence: **every click is one HTTP request, and every request is one call to +`main( )` on a fresh instance.** + +That is PBO and PAI with the names removed. A request arrives carrying the +event and what the user changed. `main( )` runs, decides what happens, and +returns. Whatever it built travels back, the work process is released, and the +next click starts the same way. Nothing runs between two clicks, because nothing +is there to run. + +So `main( )` is a dispatcher, and it dispatches on three questions the client +can answer: + +```abap + METHOD z2ui5_if_app~main. + + me->client = client. + + CASE abap_true. + WHEN client->check_on_init( ). + data_read( ). " once - this instance has never run + view_display( ). + WHEN client->check_on_navigated( ). + view_display( ). " back from a sub-app or a popup: hand a view back + WHEN client->check_on_event( `SAVE` ). + on_save( ). " the model changed - the view stands + ENDCASE. + + ENDMETHOD. +``` + +![Three questions, in this order, and what each branch owes the browser.](/insights/16-dispatch.svg) + +*Three questions, in this order, and what each branch owes the browser.* + +Three things about that shape are not taste. + +**Init before navigated.** `check_on_init( )` is true exactly once, on the very +first call. Every path to a first call raises `check_on_navigated( )` as well, +so in a `CASE abap_true` the first match wins and the init branch has to stand +first, or the one-time setup never runs. + +**A return owes a view.** Coming back from a sub-app or a value help does not +fire `check_on_init( )` again. It fires `check_on_navigated( )`, and the browser +is still showing whatever the sub-app left. An app that builds its view only +under init comes back to a blank screen, and nothing reports it — the response +simply carries no view. + +**An event owes nothing.** The handler changes attributes and returns. Bound +controls update from the model, focus and scroll stay where they were, and +`view_display( )` is called only when the structure of the screen changes. +Article [#8](/advanced/insights/08-only-the-changed-part) is about exactly that. + +The variable that survives between the three is the instance itself, +serialized after every request — [#9](/advanced/insights/09-a-new-instance-on-every-request). +Local variables, an open cursor, a lock: gone with the work process, every time. + +**PBO builds, PAI decides, and the dialog step is a POST.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/17-what-the-client-can-do.md b/docs/advanced/insights/17-what-the-client-can-do.md new file mode 100644 index 00000000..3abed6f3 --- /dev/null +++ b/docs/advanced/insights/17-what-the-client-can-do.md @@ -0,0 +1,58 @@ +# #17 What the Client Can Do + +The app implements one interface with one method. The one parameter of that +method is the other interface — `z2ui5_if_client` — and it is the whole of what +an app can ask the framework for. It is worth knowing by shape before knowing +it by name, because the shape is small: + +| The app wants to… | …and calls | +|---|---| +| show a screen | `view_display( )`, `popup_display( )`, `popover_display( )`, `nest_view_display( )` | +| put ABAP data on that screen | `_bind( )` — the attribute, by reference, both directions | +| find out what the user did | `check_on_event( )`, `get_event( )`, `get_event_arg( )` | +| find out why `main( )` was called | `check_on_init( )`, `check_on_navigated( )` | +| talk to the user | `message_toast_display( )`, `message_box_display( )` | +| move to another app and back | `nav_app_call( )`, `nav_app_leave( )`, `get_app_prev( )` | +| ask the browser to do something | `follow_up_action( )` with a `cs_event` constant | +| know where it runs | `get( )` — device, launchpad, URL parameters, the raw event | + +The two that carry the most weight are the smallest. `_bind( )` takes an ABAP +variable and returns the binding path the view needs; the framework builds the +model around whatever was bound, ships it, and writes the user's changes back +into the same variable: + +```abap + )->tag( `Input` + )->a( n = `value` v = client->_bind( customer ) +``` + +And `_event( )` names what the frontend should send when a control fires: + +```abap + )->tag( `Button` + )->a( n = `text` v = `Save` + )->a( n = `press` v = client->_event( `SAVE` ) ). +``` + +Both are string generators. Nothing is registered, nothing is declared: the +attribute name and the event name are written into the view, and read back +out of the request. + +The last row is the escape hatch for everything that is a browser matter rather +than an ABAP matter — focus a field, scroll, copy to the clipboard, download a +file, set the tab title, raise the model size limit. Each is a constant in +`cs_event`, and each is one call: + +```abap + client->follow_up_action( val = client->cs_event-set_focus + t_arg = VALUE #( ( `inputCustomer` ) ) ). +``` + +That is the whole API surface an app ever touches. The full list with every +parameter is on the [Client API](/resources/api) page, generated from the +interface itself. + +**One method in, one interface out. Everything an app can do is a method on +`client`.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/18-call-screen-leave-screen.md b/docs/advanced/insights/18-call-screen-leave-screen.md new file mode 100644 index 00000000..ad0f920e --- /dev/null +++ b/docs/advanced/insights/18-call-screen-leave-screen.md @@ -0,0 +1,58 @@ +# #18 CALL SCREEN, LEAVE SCREEN + +Module pools had a call stack of screens. `CALL SCREEN 200` pushed one, +`LEAVE TO SCREEN 0` popped it, and the screen underneath came back with its +fields intact. abap2UI5 has the same stack — the elements on it are app +instances. + +**Within one class** there is no stack to speak of. A flag or a step number +decides which view `view_display( )` builds, and the event handlers move it +along. Two views over the same internal table are two branches of one class, +and that is the right shape whenever the screens share their data. + +**Between classes** the framework keeps the stack: + +```abap + " push: the called app takes the screen, this instance waits underneath + client->nav_app_call( NEW zcl_app_detail( order_id = order_id ) ). +``` + +```abap + " pop: back to the caller, which is called again with check_on_navigated( ) + client->nav_app_leave( ). +``` + +The caller gets `main( )` called again, `check_on_navigated( )` is true, and +its own attributes are still there — the instance was serialized while it +waited. What it has to do is show its view again, because the browser is still +displaying the sub-app's. And it can read what the sub-app left behind: + +```abap + WHEN client->check_on_navigated( ). + DATA(detail) = CAST zcl_app_detail( client->get_app_prev( ) ). + IF detail IS BOUND. + message = detail->result. + ENDIF. + view_display( ). +``` + +`nav_app_leave( )` called *with* an app instance is `LEAVE TO TRANSACTION`: +it starts the given app without pushing the current one, so there is nothing to +return to. + +**A popup is the same thing in a smaller slot.** `popup_display( )` takes a +fragment instead of a view, `popup_destroy( )` closes it, and everything in +between is ordinary binding — the popup's inputs are attributes of the same +class. For a popup that is used from several apps, make it a class of its own: +an ordinary `z2ui5_if_app` that displays into the popup slot, and ends with +`nav_app_leave( )`. The caller reads the result through `get_app_prev( )`, as +above. + +What is not on the stack: the Fiori Launchpad. Navigating between *Fiori* apps +goes through the launchpad's cross-app navigation, so that the shell's history +and back button keep working — that is a `cs_event` constant rather than a +`nav_app_call( )`, and [its own page](/cookbook/event_navigation/navigation/cross_app). + +**The stack came along. Its elements are instances now, not screen numbers.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/19-where-f4-went.md b/docs/advanced/insights/19-where-f4-went.md new file mode 100644 index 00000000..96aaef7f --- /dev/null +++ b/docs/advanced/insights/19-where-f4-went.md @@ -0,0 +1,56 @@ +# #19 Where F4 Went + +`PARAMETERS pa_arbgb TYPE t100-arbgb` got a value help for free, because the +DDIC knew the search help and the screen knew the DDIC. On the way to the +browser that link broke: an `Input` in a UI5 view has no idea what type stands +behind it. + +abap2UI5 does not put the automatic version back. It gives the three shapes an +F4 actually comes in, each one a few lines, from cheapest to widest. + +**Suggestions while typing.** The candidates are an internal table bound to +the input; UI5 filters it in the browser, and no roundtrip is involved after +the first render: + +```abap + )->ele( `Input` + )->a( n = `value` v = client->_bind( country ) + )->a( n = `suggestionItems` v = client->_bind( t_countries ) + )->a( n = `showSuggestion` b = abap_true + )->ele( `suggestionItems` + )->tag( n = `ListItem` ns = `core` + )->a( n = `text` v = `{CODE}` + )->a( n = `additionalText` v = `{NAME}` ). +``` + +**A selection popup.** The input shows the value-help icon and raises an event +when it is pressed. The handler runs the `SELECT`, fills a table, and opens a +popup with a list in it. The row the user picks is an event with an argument, +and the handler writes it into the bound attribute: + +```abap + )->tag( `Input` + )->a( n = `value` v = client->_bind( carrid ) + )->a( n = `valueHelpRequest` v = client->_event( `F4` ) + )->a( n = `showValueHelp` b = abap_true ). +``` + +No sub-app, no navigation — the popup is a second view of the same class, so +the chosen row is simply an attribute. + +**A reusable value help.** When the same F4 is needed from ten screens, it +becomes a class: a `z2ui5_if_app` that displays into the popup slot, called +with `nav_app_call( )`, and read back through `get_app_prev( )` after it +leaves — the stack from [#18](/advanced/insights/18-call-screen-leave-screen). +Written once, the DDIC search help it wraps is whatever `SELECT` it runs. + +What is honestly gone is the part where nothing had to be written. The screen +does not know the type behind the field any more, so the developer names the +candidates. What came back is that the candidates can be anything an ABAP +`SELECT` can produce — including a table that did not exist a millisecond +ago, which is where [#1](/advanced/insights/01-somewhere-on-the-way-to-ui5) started. + +**F4 is three lines and a SELECT now. It is no longer free, and it is no +longer limited to the DDIC either.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/20-message-still-works.md b/docs/advanced/insights/20-message-still-works.md new file mode 100644 index 00000000..70aa4c06 --- /dev/null +++ b/docs/advanced/insights/20-message-still-works.md @@ -0,0 +1,60 @@ +# #20 MESSAGE Still Works + +An ABAP developer has thirty years of habits around messages, and most of them +carry over unchanged. What changes is where the message ends up. + +**A toast for what needs no acknowledgement**, a box for what does: + +```abap + client->message_toast_display( `Saved` ). + + client->message_box_display( text = `Order is locked by another user` + type = `error` ). +``` + +**The old structures go straight in.** `sy` after a `MESSAGE ... INTO`, a +`BAPIRET2` table from a BAPI, an exception object from a `CATCH` — the +framework reads the text, the type and the details out of each: + +```abap + MESSAGE ID `NET` TYPE `I` NUMBER `001` INTO DATA(dummy). + client->message_box_display( sy ). + + client->message_box_display( t_bapiret ). + + TRY. + DATA(x) = 1 / 0. + CATCH cx_root INTO DATA(lx). + client->message_box_display( lx ). + ENDTRY. +``` + +Which means the message class, the T100 text and the translation tooling +around them are exactly as useful as before. A text element or a message class +is still the way a string gets translated, because the app is an ABAP class +and there is no i18n file beside it. + +**What is different is the exception that gets away.** There is one catch in +the framework, in the HTTP handler, and an exception that escapes `main( )` +travels all the way up to it. The roundtrip ends with HTTP 500, the database +work of that roundtrip is rolled back, and the browser shows an *Application +Error, please restart* overlay with the exception chain behind a *Details* +button. There is no *continue*: the roundtrip that would have carried the +app's next state is the one that failed, so the user restarts, and the draft +brings them back to where they were. + +That is the fallback, and reaching it means the user has lost the screen. +Anything predictable — a failed conversion, a locked object, a service that is +not there — is caught where it happens and shown as a box, which costs the +user one click. + +One setting belongs in every production system. The 500 body carries source +positions, RTTI names and system context — everything a developer wants and +more than a browser should get. `check_hide_error_details` in the +[user exit](/advanced/extensibility/user_exits) turns the body into a bare +*Internal Server Error* and leaves everything else as it is. + +**The message classes, the texts and the translation stay. Only the dump +looks different.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/21-the-lock-is-gone-by-the-next-click.md b/docs/advanced/insights/21-the-lock-is-gone-by-the-next-click.md new file mode 100644 index 00000000..837c85dd --- /dev/null +++ b/docs/advanced/insights/21-the-lock-is-gone-by-the-next-click.md @@ -0,0 +1,73 @@ +# #21 The Lock Is Gone by the Next Click + +`VA02` calls `ENQUEUE_EVVBAK` when the order opens and holds the lock for as +long as the dialog session lives. That works because the session lives. In +abap2UI5 every click is a fresh session — [#16](/advanced/insights/16-one-click-one-request) — +so a lock set while the order opens is released before the user has read the +screen. The ABAP is still valid. The assumption underneath it is not. + +That is not a framework limitation to be worked around. It is the same +question every stateless web application has answered, and the answers are +known. Locking splits into two questions: + +| Phase | Question | +|---|---| +| **Edit** | What happens while the user is thinking and typing? | +| **Save** | What happens the moment they hit save? | + +**Lock at save.** Nothing is held while the user thinks. The handler acquires +the lock, writes, commits and releases, all inside one roundtrip. The lock +exists for milliseconds: + +```abap + METHOD on_save. + + CALL FUNCTION 'ENQUEUE_EVVBAK' + EXPORTING mode_vbak = 'E' + mandt = sy-mandt + vbeln = vbeln + EXCEPTIONS foreign_lock = 1 + system_failure = 2 + OTHERS = 3. + IF sy-subrc <> 0. + client->message_box_display( `Locked by another user.` ). + RETURN. + ENDIF. + + UPDATE vbak SET auart = @auart WHERE vbeln = @vbeln. + COMMIT WORK. + + CALL FUNCTION 'DEQUEUE_EVVBAK' + EXPORTING mode_vbak = 'E' + mandt = sy-mandt + vbeln = vbeln. + + ENDMETHOD. +``` + +**Plus an optimistic check.** Two users can still read the same order, edit +for ten minutes each, and save one after the other — the second silently wins. +So the app remembers the change timestamp it read, and compares before it +writes: if the row changed underneath, the save is refused and the user sees +what happened. Together, lock at save and the optimistic check are the +production default for a stateless app. + +![The lock lives inside one roundtrip; the timestamp guards the minutes between.](/insights/21-lock-at-save.svg) + +*The lock lives inside one roundtrip; the timestamp guards the minutes between.* + +**A soft lock** — a row in a table of your own saying who is editing what, +with a timestamp that expires — gives the *this order is being edited by +Müller* warning back, without a work process holding anything. + +**And the GUI way still exists.** `client->set_session_stateful( )` pins one +work process to this user for the rest of the app, and a classic enqueue +survives between clicks again. It is the right tool for a one-to-one migration +of a dialog flow and for a resource that is expensive to rebuild per request. +It is also one pinned work process per active user, has to be released on +every exit path, and is not available on public cloud. Use it knowingly. + +**A lock is a promise about a session. Where the session is one request long, +the promise has to be, too.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/22-2300-lines.md b/docs/advanced/insights/22-2300-lines.md deleted file mode 100644 index a9fe92d3..00000000 --- a/docs/advanced/insights/22-2300-lines.md +++ /dev/null @@ -1,35 +0,0 @@ -# #22 2,300 Lines - -The communication core of abap2UI5 is one HTTP handler class, two interfaces -and one database table. Originally about 2,300 lines of ABAP. The framework has -grown since — most visibly the optional view builder — but the part that carries -every request is still that. - -It is a small number, and the interesting thing is what it is small *because* -of. - -![The whole request path.](/insights/18-footprint.svg) - -*The whole request path.* - -The framework does not build views; apps do. It does not decide program flow; -apps do. It does not wrap UI5 controls, so it does not grow when UI5 does. It -does not implement a protocol, because the protocol is a POST with two strings. -Most of what a framework usually accumulates lives outside this one, in the -apps, where it is written per case and not carried by everybody. - -That has a consequence beyond elegance. A dependency that cannot be read cannot -really be reviewed, and a framework in the request path of a business -application is a dependency in the strongest sense — it sees every input, every -response and every user. There is a difference between trusting a package -because it is popular and being able to open it and find out. Here, one class -holds the logic, and finding out is an afternoon. - -The same property is why the audit answers are short. No CDS artefacts, no RAP -objects, no generated code, no build step, no transitive package tree — the -system footprint is the source in the repository, and the source is ABAP that a -developer in the room can read. - -**A framework you can read all of is a framework you can be responsible for.** - -Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/22-who-may-start-which-app.md b/docs/advanced/insights/22-who-may-start-which-app.md new file mode 100644 index 00000000..49062586 --- /dev/null +++ b/docs/advanced/insights/22-who-may-start-which-app.md @@ -0,0 +1,53 @@ +# #22 Who May Start Which App + +One ICF node serves every abap2UI5 app in the system, and the URL names the +class: `?app_start=zcl_job_monitor`. The first question a Basis colleague asks +is the right one — so anybody with the node can start any class? + +Only if nobody decided otherwise, and the deciding happens in two familiar +places. + +**Authentication is the ICF node's.** Logon procedure, SSO, certificate, +visibility — the node is configured like the node of any other UI5 app, and +abap2UI5 adds nothing to it and takes nothing away. + +**Authorization is yours, and the natural place is the handler.** The handler +class the node points to is a class you write, and `run( )` is one line in it. +Everything before that line is an ordinary ABAP authorization check: + +```abap + METHOD if_http_extension~handle_request. + + DATA(app) = to_upper( server->request->get_form_field( `app_start` ) ). + + AUTHORITY-CHECK OBJECT 'Z_APP_AUTH' + ID 'APP' FIELD app. + IF sy-subrc <> 0. + RETURN. + ENDIF. + + z2ui5_cl_ui5_http_handler=>run( server ). + + ENDMETHOD. +``` + +One authorization object with one field, the app class as the value, roles as +usual in PFCG. A department that needs its own set of apps gets its own node +and its own handler — the framework does not mind how many there are. + +**Or inside the app**, the way a report checks before it selects. An +`AUTHORITY-CHECK` in the `check_on_navigated( )` branch, and a message instead +of a view when it fails. The two combine: the handler decides who may start +what, the app decides what they may do inside. + +What the framework itself brings to the table is the part a web application +needs and a report never did. A CSRF token on every POST, on by default. A +Content Security Policy on the first page, restricting what the browser will +load, tightened in the user exit when an old UI5 release no longer has to be +served. Error details off in production, as [#20](/advanced/insights/20-message-still-works) +described. And the business logic never leaves the server: the browser gets a +view and the data bound to it, and nothing else. + +**One node, one handler, one AUTHORITY-CHECK. The rest is PFCG.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/23-100-rows-four-hours-one-request.md b/docs/advanced/insights/23-100-rows-four-hours-one-request.md new file mode 100644 index 00000000..9df8a320 --- /dev/null +++ b/docs/advanced/insights/23-100-rows-four-hours-one-request.md @@ -0,0 +1,49 @@ +# #23 100 Rows, Four Hours, One Request + +Three numbers decide whether an abap2UI5 app feels fast, and none of them is +in the app's code. They are worth knowing before the first table is bound. + +**100 rows.** Every UI5 JSON model exposes at most 100 items to a list binding +by default. Bind a table with 300 entries and the first 100 render — the rest +are silently dropped, with no error anywhere. It is a UI5 setting, not a +framework one, and it is raised per view from ABAP: + +```abap + client->follow_up_action( val = client->cs_event-set_size_limit + t_arg = VALUE #( ( `1000` ) + ( client->cs_view-main ) ) ). +``` + +Raising it is the fix for a table of a thousand rows. It is not the fix for a +table of fifty thousand: every bound row travels to the browser with the +response, and back to the database with the draft. A screen that needs that +many pages in ABAP — a `SELECT` with `UP TO`, a growing table, a filter bar — +the way a selection screen always did. + +**Four hours.** The serialized instance from [#9](/advanced/insights/09-a-new-instance-on-every-request) +lives in `z2ui5_t_01` until it expires, four hours by default. A user who comes +back after lunch continues where they were; one who comes back tomorrow starts +fresh. The number is `draft_exp_time_in_hours` in the user exit, and the table +is the one place in the framework that grows — it is cleaned up on its own +schedule, and a system with many users and long-running screens sizes it like +any other table. + +**One request.** Every click is one HTTP roundtrip plus one read and one write +of the draft. The cost of a click is therefore the size of the instance and the +size of the model, and the two are the same attributes. An app that carries a +catalogue of ten thousand rows in an attribute pays for it on every click, in +either section. An app that re-reads what it needs pays a `SELECT`, which the +database was built for. + +![What a click costs: the instance to the database, the model to the browser.](/insights/23-what-a-click-costs.svg) + +*What a click costs: the instance to the database, the model to the browser.* + +The rule that follows is the same as in a dynpro program with global variables: +keep in the instance what the screen shows, and nothing that can be read +again. + +**Small instance, bounded model, one request per click. Everything else the +system already does well.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/24-abap-unit-for-a-screen.md b/docs/advanced/insights/24-abap-unit-for-a-screen.md new file mode 100644 index 00000000..9a8bdd3d --- /dev/null +++ b/docs/advanced/insights/24-abap-unit-for-a-screen.md @@ -0,0 +1,154 @@ +# #24 ABAP Unit for a Screen + +An abap2UI5 app is a global class, and a global class has a *Test Classes* +include. That is the whole story of testing here, and the reason it stays short +is a decision in the app rather than in the framework: **the logic does not +touch `client`.** + +`main( )` dispatches. The methods it dispatches to read data, decide, and +change attributes. Only `view_display( )` and the message calls need the +client, so a test calls the other methods directly and looks at the attributes +afterwards: + +```abap +CLASS zcl_app_overdue DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + TYPES: + BEGIN OF ty_s_invoice, + invoice TYPE string, + due TYPE d, + open TYPE abap_bool, + END OF ty_s_invoice. + TYPES ty_t_invoice TYPE STANDARD TABLE OF ty_s_invoice WITH EMPTY KEY. + + DATA t_invoices TYPE ty_t_invoice. + DATA t_overdue TYPE ty_t_invoice. + DATA key_date TYPE d. + + METHODS data_read. + METHODS overdue_calc. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS zcl_app_overdue IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + + CASE abap_true. + WHEN client->check_on_init( ). + key_date = sy-datum. + data_read( ). + overdue_calc( ). + view_display( ). + WHEN client->check_on_navigated( ). + view_display( ). + WHEN client->check_on_event( `RECALC` ). + overdue_calc( ). + ENDCASE. + + ENDMETHOD. + + METHOD data_read. + " in your system: SELECT FROM the invoice table + t_invoices = VALUE #( ( invoice = `4711` due = `20260801` open = abap_true ) + ( invoice = `4712` due = `20260930` open = abap_true ) + ( invoice = `4713` due = `20260701` open = abap_false ) ). + ENDMETHOD. + + METHOD overdue_calc. + t_overdue = VALUE #( FOR inv IN t_invoices + WHERE ( open = abap_true AND due < key_date ) + ( inv ) ). + ENDMETHOD. + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Page` + )->a( n = `title` v = `Overdue` + + )->tag( `DatePicker` + )->a( n = `value` v = client->_bind( key_date ) + )->a( n = `valueFormat` v = `yyyyMMdd` + )->a( n = `displayFormat` v = `medium` + )->a( n = `change` v = client->_event( `RECALC` ) + + )->ele( `List` + )->a( n = `items` v = client->_bind( t_overdue ) + )->ele( `items` + )->tag( `StandardListItem` + )->a( n = `title` v = `{INVOICE}` + )->a( n = `info` v = `{DUE}` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. +``` + +The test lives in the include, and nothing in it is abap2UI5: + +```abap +CLASS ltcl_overdue DEFINITION FINAL + FOR TESTING RISK LEVEL HARMLESS DURATION SHORT. + + PRIVATE SECTION. + METHODS only_open_and_past_due FOR TESTING. + +ENDCLASS. + + +CLASS ltcl_overdue IMPLEMENTATION. + + METHOD only_open_and_past_due. + + DATA(cut) = NEW zcl_app_overdue( ). + cut->key_date = `20260901`. + cut->data_read( ). + + cut->overdue_calc( ). + + cl_abap_unit_assert=>assert_equals( + act = lines( cut->t_overdue ) + exp = 1 + msg = `4711 is the only open invoice past the key date` ). + + ENDMETHOD. + +ENDCLASS. +``` + +No client, no HTTP, no browser. The test sets attributes, calls a method, +reads attributes — the same test it would be for a class with no screen at all, +because up to `view_display( )` it *is* a class with no screen at all. + +Two things are worth keeping that way on purpose. Handler methods take their +input from attributes rather than from `client->get( )`, so a test can set the +input. And `data_read( )` is its own method, so a test can either run the real +`SELECT` against test data or fill the table by hand and test the logic alone. + +The view itself has a different kind of check. The [linter](/advanced/linter) +reconstructs the XML the chain builds and holds it against the UI5 metadata — +unknown control, misspelled property, wrong type — without a system. Logic in +ABAP Unit, view in the linter, and the roundtrip in a browser. + +**Keep the client out of the logic, and the logic is testable the way any +ABAP class is.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/24-where-the-line-is.md b/docs/advanced/insights/24-where-the-line-is.md deleted file mode 100644 index cae98231..00000000 --- a/docs/advanced/insights/24-where-the-line-is.md +++ /dev/null @@ -1,39 +0,0 @@ -# #24 What It Does Not Do - -Before choosing anything, the part that is easiest to leave out: what this -does not do. - -**Offline is out.** Every event asks the server what happens next. Take the -server away and there is no app left — not a degraded one, none. An app that has -to work in a warehouse with no signal is a different architecture, and no amount -of tuning here turns into one. - -**Pushdown to HANA is indirect.** A typed OData service can put a CDS view in -front of the database and let the frontend page, filter and fuzzy-search -straight against it. Here the generic service decoupled the UI from all of that -deliberately, and the price is paid exactly there: selecting from the CDS view -in ABAP and sending the result works, but it is written by hand, and a fuzzy -search help rendered on the frontend is not available. - -**Real-time and heavily interactive UIs are not the target.** Collaborative -editing, live cursors, anything where the interesting state lives in the browser -between roundtrips — a request-per-event model is the wrong shape for it. - -**Separate frontend teams lose.** The whole benefit assumes one developer holds -the screen and the logic together. Where a frontend team and a backend team work -independently by design, a contract between them is a feature, and this removes -it. - -**And Fiori Elements is not a worse tool.** A list report or an object page -described by annotations is less work than building the same screen control by -control, it stays consistent by construction, and it keeps getting updates. -Where a floorplan fits, it fits. - -What is left after all of that is still most business software: forms, tables, -dashboards, approvals, admin tools, the small screens nobody funds a project -for. - -**A framework that fits everything fits nothing in particular. This one has a -shape, and the shape has edges.** - -Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/25-when-one-class-is-not-enough.md b/docs/advanced/insights/25-when-one-class-is-not-enough.md new file mode 100644 index 00000000..5c8783d7 --- /dev/null +++ b/docs/advanced/insights/25-when-one-class-is-not-enough.md @@ -0,0 +1,44 @@ +# #25 When One Class Is Not Enough + +Every example in this series is one class, and for the screens it is about +that is the right size. A real application grows past it, and the question is +what the seams are. There are four, and each one is ordinary ABAP. + +**One class per screen.** The unit of an abap2UI5 app is a screen with the +logic behind it. A master list and a detail screen over different data are two +classes, connected by the stack from [#18](/advanced/insights/18-call-screen-leave-screen): +the list calls the detail with `nav_app_call( )`, the detail returns with +`nav_app_leave( )`, and the list reads the result through `get_app_prev( )`. +Each class is small enough to read, and each has a `main( )` of its own. + +**Popups and value helps as classes.** A confirmation dialog, a value help, a +settings popup used from several screens — each is a `z2ui5_if_app` that +displays into the popup slot. Written once, called from anywhere, tested on its +own. + +**The logic in a class that has no screen.** A `zcl_invoice_service` with +`read( )`, `validate( )` and `post( )` is where the business logic goes when a +second screen needs it, or a batch job does, or a test does. The app class +becomes what a dynpro program's PAI modules were: it takes the event, calls the +service, updates the attributes. One thing to know: a reference held in an +attribute is serialized with the instance, so a helper either implements +`if_serializable_object` and stays small, or is created fresh in every +`main( )`, which is usually the better answer anyway. + +**Parts of one screen as nested views.** A master-detail layout, a tab strip, +a side panel — `nest_view_display( )` puts a view built in ABAP into a named +slot of the main view, and rebuilds only that slot later. The page stands, the +part changes. + +![One app, several classes: a screen per class, a service without a screen, a popup on the stack.](/insights/25-more-than-one-class.svg) + +*One app, several classes: a screen per class, a service without a screen, a popup on the stack.* + +What does not appear in that list is a framework concept. No component, no +manifest, no router, no controller hierarchy — the seams are classes, method +calls and one stack, which is exactly what a larger ABAP program has been made +of for thirty years. + +**A larger app is more classes. The unit stayed the same size.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/25-where-the-view-lives.md b/docs/advanced/insights/25-where-the-view-lives.md deleted file mode 100644 index f2fc293b..00000000 --- a/docs/advanced/insights/25-where-the-view-lives.md +++ /dev/null @@ -1,41 +0,0 @@ -# #25 Where the View Lives - -Three ways to put a UI5 screen in front of a user on an ABAP stack. All three -render in the browser, with the same control library, through the same -framework. What differs is one thing: where the view is defined, and therefore -when it is fixed. - -**UI5 Freestyle — build time.** The XML view is a file in a frontend project. -It is written in the IDE, built, and deployed alongside its controller. The -backend serves data through OData or a REST service and has no opinion about -the screen. By the time a user opens the app, the view has been fixed since the -build. - -**RAP with Fiori Elements — activation time.** There is no view file per screen. -The screen is described as UI annotations on CDS entities, and a Fiori Elements -runtime in the browser turns that metadata into controls at load time. The -definition lives in the backend and is fixed when the annotations are activated. - -**abap2UI5 — request time.** The view is an XML string an ABAP class produced -for this request, rendered by a shell app that is the same for every -application. It is fixed when the request is answered, and the next request may -answer differently. - -![One axis: when the definition of the screen stops being changeable.](/insights/22-where-the-view-lives.svg) - -*One axis: when the definition of the screen stops being changeable.* - -Everything else follows from that axis. What has to be deployed per app, how -many artefacts a screen costs, what can still change at runtime, which language -the definition is written in — none of those are independent choices, they are -consequences of where the view sits. - -Which is also why none of this reads as a ranking. A definition fixed early is -easier to standardise and to keep consistent across hundreds of screens. One -fixed late can adapt to things nobody knew at design time. Those are different -properties, not different amounts of the same one. - -**Pick where the view lives, and most of the other decisions have already been -made for you.** - -Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/26-a-tile-like-any-other.md b/docs/advanced/insights/26-a-tile-like-any-other.md new file mode 100644 index 00000000..34170a0b --- /dev/null +++ b/docs/advanced/insights/26-a-tile-like-any-other.md @@ -0,0 +1,51 @@ +# #26 A Tile Like Any Other + +An app that is reached by pasting a URL is a demo. It becomes an application +when it has a tile — and in the launchpad an abap2UI5 app is indistinguishable +from the RAP and freestyle tiles beside it. + +**What is installed once.** The launchpad loads a UI5 app from the system's +UI5 repository, so the abap2UI5 shell has to exist there as one: the +[launchpad connector](https://github.com/abap2UI5-addons) ships it through +abapGit, as app `z2ui5`. That is one deployment for the whole system, not one +per app — every abap2UI5 tile points at the same shell. + +**What is configured per app.** A target mapping, with the app class as the +parameter: + +| | | +|---|---| +| Semantic Object | `Z2UI5_CL_MY_APP` | +| Action | `display` | +| URL | `/sap/bc/ui5_ui5/sap/z2ui5` | +| Parameter | `app_start` / `Z2UI5_CL_MY_APP` | + +A tile, a catalogue, a role — the Fiori administration the system already does, +with nothing abap2UI5-specific in it except the parameter. + +**What the app can do inside.** It knows where it is: + +```abap + IF client->get( )-check_launchpad_active = abap_true. + client->follow_up_action( val = client->cs_event-set_title_launchpad + t_arg = VALUE #( ( `Job Monitor` ) ) ). + ENDIF. +``` + +Startup parameters from the target mapping arrive in +`client->get( )-t_comp_params`, and navigation to another *Fiori* app goes +through the launchpad's own cross-app navigation, so the shell's back button +keeps working. + +**What to do when the tile is blank.** The one recurring trouble is not the +app but the UI5 app index after an abapGit import: run +`/UI5/APP_INDEX_CALCULATE`, clear the HTTP cache in `SMICM`, hard-reload. The +[launchpad page](/configuration/launchpad) walks through it with screenshots. + +Public Cloud is a different door: there the launchpad is Build Work Zone, and +the [ABAP Cloud pages](/configuration/btp) describe the setup. + +**One shell in the UI5 repository, one parameter per tile. No user can tell +the difference.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/17-one-codebase-702-to-abap-cloud.md b/docs/advanced/insights/27-one-codebase-702-to-abap-cloud.md similarity index 95% rename from docs/advanced/insights/17-one-codebase-702-to-abap-cloud.md rename to docs/advanced/insights/27-one-codebase-702-to-abap-cloud.md index 6004bd8d..f2dc33cc 100644 --- a/docs/advanced/insights/17-one-codebase-702-to-abap-cloud.md +++ b/docs/advanced/insights/27-one-codebase-702-to-abap-cloud.md @@ -1,4 +1,4 @@ -# #17 One Codebase, 7.02 to ABAP Cloud +# #27 One Codebase, 7.02 to ABAP Cloud Most SAP landscapes are currently two landscapes. Something old that runs the business, something new that is being built beside it, and a migration between @@ -24,7 +24,7 @@ And the frontend does not age with the backend. UI5 is bootstrapped from a CDN, so the UI5 version is a configuration decision rather than a property of the SAP release. A current UI5 control works on a system that has never heard of it. -![Where UI5 comes from decides whether its version follows the SAP release.](/insights/17-bootstrap.svg) +![Where UI5 comes from decides whether its version follows the SAP release.](/insights/27-bootstrap.svg) *Where UI5 comes from decides whether its version follows the SAP release.* diff --git a/docs/advanced/insights/18-cloud-ready-is-a-property-of-your-app.md b/docs/advanced/insights/28-cloud-ready-is-a-property-of-your-app.md similarity index 96% rename from docs/advanced/insights/18-cloud-ready-is-a-property-of-your-app.md rename to docs/advanced/insights/28-cloud-ready-is-a-property-of-your-app.md index e9ecbcee..f25f7f9a 100644 --- a/docs/advanced/insights/18-cloud-ready-is-a-property-of-your-app.md +++ b/docs/advanced/insights/28-cloud-ready-is-a-property-of-your-app.md @@ -1,4 +1,4 @@ -# #18 Cloud-Ready Is a Property of Your App +# #28 Cloud-Ready Is a Property of Your App The framework is ABAP Cloud compliant. It is written in the ABAP for Cloud language version, it calls released APIs only, it modifies no standard SAP @@ -12,7 +12,7 @@ Virtual Data Model — the CDS views SAP publishes and maintains as an API — i the way in, and that is exactly what keeps an app upgrade-stable when the tables beneath it change. -![The released view is the shape SAP keeps stable across the upgrade.](/insights/20-vdm.svg) +![The released view is the shape SAP keeps stable across the upgrade.](/insights/28-vdm.svg) *The released view is the shape SAP keeps stable across the upgrade.* diff --git a/docs/advanced/insights/19-when-the-api-is-not-released.md b/docs/advanced/insights/29-when-the-api-is-not-released.md similarity index 98% rename from docs/advanced/insights/19-when-the-api-is-not-released.md rename to docs/advanced/insights/29-when-the-api-is-not-released.md index c1e747b0..8a2359f3 100644 --- a/docs/advanced/insights/19-when-the-api-is-not-released.md +++ b/docs/advanced/insights/29-when-the-api-is-not-released.md @@ -1,4 +1,4 @@ -# #19 When the API Is Not Released +# #29 When the API Is Not Released SAP grades an extension, and since August 2025 it does so on four **clean core levels**: diff --git a/docs/advanced/insights/20-on-stack-or-side-by-side.md b/docs/advanced/insights/30-on-stack-or-side-by-side.md similarity index 98% rename from docs/advanced/insights/20-on-stack-or-side-by-side.md rename to docs/advanced/insights/30-on-stack-or-side-by-side.md index bd748cf8..b4a4613c 100644 --- a/docs/advanced/insights/20-on-stack-or-side-by-side.md +++ b/docs/advanced/insights/30-on-stack-or-side-by-side.md @@ -1,4 +1,4 @@ -# #20 On-Stack or Side-by-Side +# #30 On-Stack or Side-by-Side Two places to run an abap2UI5 app, and the choice is about lifecycles rather than about code. diff --git a/docs/advanced/insights/21-one-app-many-systems.md b/docs/advanced/insights/31-one-app-many-systems.md similarity index 98% rename from docs/advanced/insights/21-one-app-many-systems.md rename to docs/advanced/insights/31-one-app-many-systems.md index a98e94c9..6dff9083 100644 --- a/docs/advanced/insights/21-one-app-many-systems.md +++ b/docs/advanced/insights/31-one-app-many-systems.md @@ -1,4 +1,4 @@ -# #21 One App, Many Systems +# #31 One App, Many Systems A side-by-side app already runs outside the system it serves. Which raises a question with a more interesting answer than it looks: how many systems can it diff --git a/docs/advanced/insights/23-abap-on-the-web.md b/docs/advanced/insights/32-from-its-to-abap2ui5.md similarity index 69% rename from docs/advanced/insights/23-abap-on-the-web.md rename to docs/advanced/insights/32-from-its-to-abap2ui5.md index 5f29f42c..b0d9569b 100644 --- a/docs/advanced/insights/23-abap-on-the-web.md +++ b/docs/advanced/insights/32-from-its-to-abap2ui5.md @@ -1,20 +1,20 @@ -# #23 From ITS to abap2UI5 +# #32 From ITS to abap2UI5 Worth laying out end to end, because the shape of it is not what most people remember. -**ITS, 2000.** Dynpro screens rendered as HTML by the server. Every interaction +**ITS, 1996.** Dynpro screens rendered as HTML by the server. Every interaction a full page from the server. Still in warehouses today, on scanners. **BSP, 2001.** HTML and JavaScript written inside the ABAP stack, with ABAP embedded for logic. Server-side rendering again, with real control over the markup. -**Web Dynpro, 2003.** Views, context and controllers defined in ABAP. A +**Web Dynpro ABAP, 2006.** Views, context and controllers defined in ABAP. A component model rather than a page model — and still the server building the HTML. -**UI5 Freestyle, 2010.** The rendering moves. JavaScript, XML views and UI5 +**UI5 Freestyle, 2012.** The rendering moves. JavaScript, XML views and UI5 controls run in the browser; the backend delivers data through OData and stops having an opinion about the screen. @@ -24,12 +24,12 @@ annotations, and a runtime in the browser turns them into the app. **abap2UI5, 2023.** The backend sends the XML view itself, and a static UI5 app renders it. -![Twenty-three years, and the one move that did not reverse.](/insights/21-timeline.svg) +![Twenty-seven years, and the one move that did not reverse.](/insights/32-timeline.svg) -*Twenty-three years, and the one move that did not reverse.* +*Twenty-seven years, and the one move that did not reverse.* -Read as a line, it is not a march away from the server. For the first ten years -ABAP built the HTML. Then rendering left for the browser, where it stays — +Read as a line, it is not a march away from the server. For the first fifteen +years ABAP built the HTML. Then rendering left for the browser, where it stays — because that is where a modern UI belongs, and nobody is proposing otherwise. What has been moving back ever since is not the rendering but the **definition** of the screen: first as annotations, then as the view itself. @@ -38,7 +38,4 @@ Which makes Over-the-Wire less of a new idea than a returning one, arriving with the part the old server-rendered world never had — a real control library in the browser. -**The screen went to the browser and stayed. The decision about the screen came -home.** - Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/26-rap-or-abap2ui5.md b/docs/advanced/insights/33-rap-or-abap2ui5.md similarity index 98% rename from docs/advanced/insights/26-rap-or-abap2ui5.md rename to docs/advanced/insights/33-rap-or-abap2ui5.md index 669118c7..a34d6886 100644 --- a/docs/advanced/insights/26-rap-or-abap2ui5.md +++ b/docs/advanced/insights/33-rap-or-abap2ui5.md @@ -1,4 +1,4 @@ -# #26 RAP or abap2UI5 — When to Use Which +# #33 RAP or abap2UI5? The honest answer is that most systems end up with both, and the question is never "which framework" but "which one for this screen". diff --git a/docs/advanced/insights/27-freestyle-or-abap2ui5.md b/docs/advanced/insights/34-freestyle-or-abap2ui5.md similarity index 55% rename from docs/advanced/insights/27-freestyle-or-abap2ui5.md rename to docs/advanced/insights/34-freestyle-or-abap2ui5.md index 3f180ca4..1d314ac9 100644 --- a/docs/advanced/insights/27-freestyle-or-abap2ui5.md +++ b/docs/advanced/insights/34-freestyle-or-abap2ui5.md @@ -1,4 +1,4 @@ -# #27 UI5 Freestyle or abap2UI5 — When to Use Which +# #34 UI5 Freestyle or abap2UI5? Both give full access to the UI5 control library. Neither limits what a screen can contain. So the choice is not about what is possible — it is about where the @@ -14,14 +14,24 @@ work happens and who does it. | **Tooling** | Business Application Studio, a frontend build | any ABAP IDE, no extra toolchain | | **Deployment** | build and deploy the frontend, plus the backend | activating the class | -**Reach for UI5 Freestyle when the browser has to be smart.** Anything that must -keep working offline, anything where interesting state lives on the client -between roundtrips, anything genuinely interactive — drag and drop across a -board, a canvas, live collaboration. A request per event is the wrong shape for -those, and no amount of tuning changes it. The same goes for an app that has to -ship as its own deployable artefact, and for a dedicated frontend team with a -release cycle of its own: there the contract between frontend and backend is a -feature, and giving it up costs more than it saves. +**Reach for UI5 Freestyle when the browser has to be smart.** This is where the +edges of abap2UI5 are, so they are worth listing plainly: + +- **Offline is out.** Every event asks the server what happens next. Take the + server away and there is no app left — not a degraded one, none. An app for + a warehouse with no signal is a different architecture. +- **Real-time and heavily interactive UIs are the wrong shape.** Drag and drop + across a board, a canvas, live collaboration, anything where the interesting + state lives in the browser between roundtrips. A request per event does not + turn into that with tuning. +- **Pushdown to HANA is indirect.** A typed OData service can put a CDS view in + front of the database and let the frontend page, filter and fuzzy-search + straight against it. Here the app selects in ABAP and sends the result, and + a fuzzy search help rendered on the frontend is not available. +- **Separate frontend teams lose.** The whole benefit assumes one developer + holds the screen and the logic together. Where a frontend team ships on its + own release cycle by design, the contract between the two is a feature, and + giving it up costs more than it saves. **Reach for abap2UI5 when the backend already knows everything.** The data, the rules and the decisions are in ABAP, the screen mostly shows them and sends @@ -34,6 +44,10 @@ Change a class, activate, refresh — no build, no cache, no deployment — is w a great deal for internal applications, and worth nothing for an app that has to work on a tablet with no signal. +What is left after the edges is still most business software: forms, tables, +dashboards, approvals, admin tools, the small screens nobody funds a project +for. + **Nothing about picking one rules out the other later. The view is a string either way, and the controls are the same controls.** diff --git a/docs/advanced/insights/28-low-code-or-abap2ui5.md b/docs/advanced/insights/35-low-code-or-abap2ui5.md similarity index 98% rename from docs/advanced/insights/28-low-code-or-abap2ui5.md rename to docs/advanced/insights/35-low-code-or-abap2ui5.md index 74d5d04a..d4b5d6fc 100644 --- a/docs/advanced/insights/28-low-code-or-abap2ui5.md +++ b/docs/advanced/insights/35-low-code-or-abap2ui5.md @@ -1,4 +1,4 @@ -# #28 Low-Code or abap2UI5 — When to Use Which +# #35 Low-Code or abap2UI5? A commercial low-code platform and abap2UI5 answer the same question: modern UIs for SAP systems without a full frontend stack per app. The models could diff --git a/docs/advanced/insights/36-written-for-agents.md b/docs/advanced/insights/36-written-for-agents.md new file mode 100644 index 00000000..ff43f77c --- /dev/null +++ b/docs/advanced/insights/36-written-for-agents.md @@ -0,0 +1,44 @@ +# #36 Written for Agents + +The property this series kept returning to — an app is one class, and the class +is text — has a consequence that was not on anybody's mind when the framework +was designed. It is the shape an AI coding agent is best at. + +An agent writing a Fiori Elements app has to keep a CDS view, its annotations, +a behavior definition, a service binding and a generated frontend in step, and +can verify none of it without a system. An agent writing a freestyle app has +to hold an ABAP backend and a JavaScript frontend in its head at once and keep +the contract between them true. + +An agent writing an abap2UI5 app writes one file, in one language, and the +thing it writes is the thing that runs. + +Three things around the framework turn that into a working setup. + +**A map for the reader with no checkout.** The site publishes +[`llms.txt`](https://abap2ui5.github.io/docs/llms.txt), every page with one +line of what it covers, and the framework repository publishes the same for its +interfaces. An agent that has read either one works from what abap2UI5 is +today instead of from what it recalls — where the view builder still has a +method per control. + +**A check that needs no system.** The [linter](/advanced/linter) reconstructs +the UI5 view out of the ABAP that builds it and reports what UI5 does not have. +An agent that can verify its own work stops handing over apps that do not +render, and the same linter gates the sample repositories. + +**Several hundred worked examples.** The sample catalogues hold a complete, +tested app per pattern — value help, tree, navigation, upload — so *has +somebody already built this?* has an answer. Several hundred of those samples +were themselves ported by agents that way: write the class, lint the view, run +it, read the screenshot, and CI keeps them true. + +None of it is a feature of abap2UI5. It is what falls out when an application +is a file somebody can read — and an agent is only the newest somebody. + +The setup, from *paste this* to an MCP server, is on +[Developing with AI](/get_started/ai). + +**Code-first was the design. Agent-friendly was the consequence.** + +Happy ABAPing! 🦖🦕🦣 diff --git a/docs/advanced/insights/index.md b/docs/advanced/insights/index.md new file mode 100644 index 00000000..198442aa --- /dev/null +++ b/docs/advanced/insights/index.md @@ -0,0 +1,91 @@ +# Technical Insights + +The knowledge behind abap2UI5, cut into pieces that each fit one coffee. Every +article makes one claim, shows it in code where code can show it, and can be +read on its own — but read in order, the four blocks add up to the whole +picture: why the framework exists, how it works under the app, what a working +day with it looks like, and where it belongs next to what SAP ships. + +The axis the whole series turns on is this one: + +![One axis: when the definition of the screen stops being changeable.](/insights/00-where-the-view-lives.svg) + +*One axis: when the definition of the screen stops being changeable.* + +Three ways to put a UI5 screen in front of a user on an ABAP stack, all three +rendering in the browser with the same control library. **UI5 Freestyle** fixes +the view at build time, as a file in a frontend project. **RAP with Fiori +Elements** fixes it at activation time, as annotations on CDS. **abap2UI5** +fixes it when the request is answered, as a string an ABAP class produced for +this request — and the next request may answer differently. Everything else +follows from where the view sits: what is deployed per app, what a screen +costs in artefacts, what can still change at runtime, and which language it is +all written in. + +## Why + +What the framework is for, before how it works. + +| | | +|---|---| +| [#1 Somewhere on the Way to UI5, We Lost RTTS](/advanced/insights/01-somewhere-on-the-way-to-ui5) | a table whose structure is only known at runtime, drawn in UI5 | +| [#2 abap2UI5 Is Not a Programming Model](/advanced/insights/02-not-a-programming-model) | one interface, one method, and no opinion about what is behind the screen | +| [#3 The Cost of a Screen](/advanced/insights/03-the-cost-of-a-screen) | why the thirty-line screen never gets built, and a whole one as a class | +| [#4 No Annotation in Between](/advanced/insights/04-no-annotation-in-between) | the vocabulary is the UI5 control library, all of it | + +## How It Works + +The mechanism under the app, one piece per article. + +| | | +|---|---| +| [#5 UI5 Over-the-Wire](/advanced/insights/05-ui5-over-the-wire) | every request answered with two strings | +| [#6 The Frontend Knows Nothing](/advanced/insights/06-the-frontend-knows-nothing) | one shell for every app, and why it never needs a redeploy | +| [#7 One Handler for Every App](/advanced/insights/07-one-handler-for-every-app) | no service per screen, and how small the handler is | +| [#8 Only the Changed Part](/advanced/insights/08-only-the-changed-part) | the view stays, the model updates | +| [#9 A New Instance on Every Request](/advanced/insights/09-a-new-instance-on-every-request) | serialized state, and what that asks of the class | +| [#10 Swapping the View at Runtime](/advanced/insights/10-swapping-the-view-at-runtime) | a different control, chosen in an IF | +| [#11 No Build, No Deploy, No Cache](/advanced/insights/11-no-build-no-deploy-no-cache) | change, activate, refresh | +| [#12 Where Your Own JavaScript Goes](/advanced/insights/12-where-your-own-javascript-goes) | three declared seams, no plugin system | +| [#13 Four Verbs, Every Control](/advanced/insights/13-four-verbs-every-control) | why the view builder knows no controls | +| [#14 A Classrun for the Browser](/advanced/insights/14-a-classrun-for-the-browser) | the shape it copied | +| [#15 Where the Selection Screen Went](/advanced/insights/15-where-the-selection-screen-went) | the variable and the field are one declaration again | + +## A Working Day + +What an ABAP developer meets in the first week, and how each habit carries over. + +| | | +|---|---| +| [#16 One Click, One Request](/advanced/insights/16-one-click-one-request) | PBO, PAI, and the dispatch in `main( )` | +| [#17 What the Client Can Do](/advanced/insights/17-what-the-client-can-do) | the whole API surface, by shape | +| [#18 CALL SCREEN, LEAVE SCREEN](/advanced/insights/18-call-screen-leave-screen) | the app stack, sub-apps and popups | +| [#19 Where F4 Went](/advanced/insights/19-where-f4-went) | value help in three sizes | +| [#20 MESSAGE Still Works](/advanced/insights/20-message-still-works) | messages, exceptions, translation | +| [#21 The Lock Is Gone by the Next Click](/advanced/insights/21-the-lock-is-gone-by-the-next-click) | enqueue in a stateless world | +| [#22 Who May Start Which App](/advanced/insights/22-who-may-start-which-app) | one ICF node, authorization, CSRF | +| [#23 100 Rows, Four Hours, One Request](/advanced/insights/23-100-rows-four-hours-one-request) | the three numbers behind performance | +| [#24 ABAP Unit for a Screen](/advanced/insights/24-abap-unit-for-a-screen) | testing the class, linting the view | +| [#25 When One Class Is Not Enough](/advanced/insights/25-when-one-class-is-not-enough) | the seams of a larger app | +| [#26 A Tile Like Any Other](/advanced/insights/26-a-tile-like-any-other) | into the Fiori Launchpad | + +## Where It Belongs + +Releases, clean core, deployment shapes, and the choice against the alternatives. + +| | | +|---|---| +| [#27 One Codebase, 7.02 to ABAP Cloud](/advanced/insights/27-one-codebase-702-to-abap-cloud) | why the release barely matters | +| [#28 Cloud-Ready Is a Property of Your App](/advanced/insights/28-cloud-ready-is-a-property-of-your-app) | decided in the SELECT | +| [#29 When the API Is Not Released](/advanced/insights/29-when-the-api-is-not-released) | the clean core levels, and the wrapper | +| [#30 On-Stack or Side-by-Side](/advanced/insights/30-on-stack-or-side-by-side) | two places to run, one class | +| [#31 One App, Many Systems](/advanced/insights/31-one-app-many-systems) | a SaaS shape in ABAP | +| [#32 From ITS to abap2UI5](/advanced/insights/32-from-its-to-abap2ui5) | twenty-seven years in one line | +| [#33 RAP or abap2UI5?](/advanced/insights/33-rap-or-abap2ui5) | behaviour against screen | +| [#34 UI5 Freestyle or abap2UI5?](/advanced/insights/34-freestyle-or-abap2ui5) | where the browser has to be smart | +| [#35 Low-Code or abap2UI5?](/advanced/insights/35-low-code-or-abap2ui5) | designer against code | +| [#36 Written for Agents](/advanced/insights/36-written-for-agents) | the consequence nobody designed | + +*The articles on how it works grew out of +[Under the Hood of abap2UI5](https://community.sap.com/t5/technology-blog-posts-by-members/abap2ui5-7-technical-background-under-the-hood-of-abap2ui5/ba-p/13566459) +on the SAP Community, the long version in one piece.* diff --git a/docs/get_started/about.md b/docs/get_started/about.md index f15713f1..7a6e5d8e 100644 --- a/docs/get_started/about.md +++ b/docs/get_started/about.md @@ -95,7 +95,7 @@ It is deliberately not built for everything. Three cases are a poor fit, and kno - **Offline use and complex client-side behaviour.** The browser renders; it does not hold the application. - **Teams that work strictly separately.** The app is one ABAP class — that is the point, and it means there is no frontend project for a frontend team to own. -→ *See [On-Stack or Side-by-Side](/advanced/insights/20-on-stack-or-side-by-side) and [One App, Many Systems](/advanced/insights/21-one-app-many-systems) for the deployment scenarios* +→ *See [On-Stack or Side-by-Side](/advanced/insights/30-on-stack-or-side-by-side) and [One App, Many Systems](/advanced/insights/31-one-app-many-systems) for the deployment scenarios* ## Compatibility
diff --git a/docs/public/insights/22-where-the-view-lives.svg b/docs/public/insights/00-where-the-view-lives.svg similarity index 100% rename from docs/public/insights/22-where-the-view-lives.svg rename to docs/public/insights/00-where-the-view-lives.svg diff --git a/docs/public/insights/01-rtti-header.png b/docs/public/insights/01-rtti-header.png deleted file mode 100644 index a3942a65..00000000 Binary files a/docs/public/insights/01-rtti-header.png and /dev/null differ diff --git a/docs/public/insights/01-runtime-model.svg b/docs/public/insights/01-runtime-model.svg new file mode 100644 index 00000000..91a1158e --- /dev/null +++ b/docs/public/insights/01-runtime-model.svg @@ -0,0 +1 @@ +Model at design timeentity typedeclared in CDSOData metadatafixed for every clientdatain that shapeModel at runtimeany internal tablea SELECT, a BAPI, an EML readRTTSreads the componentscolumns + bindingsbuilt for this table \ No newline at end of file diff --git a/docs/public/insights/05-two-strings.svg b/docs/public/insights/05-two-strings.svg index eb3e68a4..d2448bf1 100644 --- a/docs/public/insights/05-two-strings.svg +++ b/docs/public/insights/05-two-strings.svg @@ -1 +1 @@ -What the backend answers withABAP classbuilds bothXML viewJSON modelUI5renders the HTMLClassic split - the backend sends only the data, the view was deployed with the appABAP servicedataUI5 + view file \ No newline at end of file +One roundtrip - what travels each wayABAP classbuilds bothXML viewJSON modelevent + changed valuesUI5renders the HTMLClassic split - the backend sends only the data, the view was deployed with the appABAP servicedataUI5 + view file \ No newline at end of file diff --git a/docs/public/insights/12-three-seams.svg b/docs/public/insights/12-three-seams.svg new file mode 100644 index 00000000..1e8e0810 --- /dev/null +++ b/docs/public/insights/12-three-seams.svg @@ -0,0 +1 @@ +Where an app reaches past the XML viewa method by control idfollow_up_action( cs_event-control_by_id )no JavaScript at allan expression in the view{= ${STATUS} === 'E' ? 'Error' : 'None' }evaluated in the browsera custom controlz2ui5_ccc - a BSP the UI5 loader resolvesa module, not a stringEverything else - JavaScript for the first page - is one setting in the user exit, for the whole system \ No newline at end of file diff --git a/docs/public/insights/15-parameters-bind.svg b/docs/public/insights/15-parameters-bind.svg new file mode 100644 index 00000000..463de5dd --- /dev/null +++ b/docs/public/insights/15-parameters-bind.svg @@ -0,0 +1 @@ +PARAMETERSpa_arbgbvariable = fieldone declarationODatavariablein ABAPservicefieldin the viewtwo artefacts, kept in step by a contract_bindclient->_bind( pa_arbgb )the variable, by referencethe field is the variable again \ No newline at end of file diff --git a/docs/public/insights/16-dispatch.svg b/docs/public/insights/16-dispatch.svg new file mode 100644 index 00000000..b49252af --- /dev/null +++ b/docs/public/insights/16-dispatch.svg @@ -0,0 +1 @@ +main( ) asks three questions, in this ordercheck_on_init( )once - the instance has never runread data, show the viewcheck_on_navigated( )back from a sub-app or popupshow the view againcheck_on_event( )the user did somethingchange attributes - the view standsA first call raises init AND navigated - so init stands first, or it never runs \ No newline at end of file diff --git a/docs/public/insights/18-footprint.svg b/docs/public/insights/18-footprint.svg deleted file mode 100644 index ef7c4a68..00000000 --- a/docs/public/insights/18-footprint.svg +++ /dev/null @@ -1 +0,0 @@ -Everything in the request path1HTTP handler class2interfaces1database tableOriginally about 2,300 lines of ABAP. Views, program flow and controls live in the apps, not in here. \ No newline at end of file diff --git a/docs/public/insights/21-lock-at-save.svg b/docs/public/insights/21-lock-at-save.svg new file mode 100644 index 00000000..66f31081 --- /dev/null +++ b/docs/public/insights/21-lock-at-save.svg @@ -0,0 +1 @@ +Where the lock livesGUI dialogENQUEUE held for the whole sessionabap2UI5openeditsavelockminutes - nobody holds anythingthe timestamp read at open is compared at saveThe lock exists for milliseconds, inside one roundtrip; the optimistic check guards the rest \ No newline at end of file diff --git a/docs/public/insights/23-what-a-click-costs.svg b/docs/public/insights/23-what-a-click-costs.svg new file mode 100644 index 00000000..8b7605f7 --- /dev/null +++ b/docs/public/insights/23-what-a-click-costs.svg @@ -0,0 +1 @@ +What one click movesbrowserevent + changesPOSTapp instanceruns main( )modelbrowserrendersthe whole instancez2ui5_t_01The model is the bound attributes; the instance is all of them. Both are paid on every click. \ No newline at end of file diff --git a/docs/public/insights/25-more-than-one-class.svg b/docs/public/insights/25-more-than-one-class.svg new file mode 100644 index 00000000..3b76802f --- /dev/null +++ b/docs/public/insights/25-more-than-one-class.svg @@ -0,0 +1 @@ +One app, several classeszcl_app_lista screennav_app_callzcl_app_detaila screenzcl_app_confirma popup on the stacknest_view_displaya part of the screencallszcl_invoice_serviceno screenClasses, method calls and one stack - no component, no manifest, no router \ No newline at end of file diff --git a/docs/public/insights/17-bootstrap.svg b/docs/public/insights/27-bootstrap.svg similarity index 100% rename from docs/public/insights/17-bootstrap.svg rename to docs/public/insights/27-bootstrap.svg diff --git a/docs/public/insights/20-vdm.svg b/docs/public/insights/28-vdm.svg similarity index 100% rename from docs/public/insights/20-vdm.svg rename to docs/public/insights/28-vdm.svg diff --git a/docs/public/insights/21-timeline.svg b/docs/public/insights/32-timeline.svg similarity index 96% rename from docs/public/insights/21-timeline.svg rename to docs/public/insights/32-timeline.svg index 83f5fb95..1c195408 100644 --- a/docs/public/insights/21-timeline.svg +++ b/docs/public/insights/32-timeline.svg @@ -1 +1 @@ -Where the HTML is builtITS2000BSP2001Web Dynpro2003UI5 Freestyle2010RAP / FE2019abap2UI52023built onthe serverbuilt inthe browserThe rendering moved once and stayed. What has been coming back since is the definition of the screen. \ No newline at end of file +Where the HTML is builtITS1996BSP2001Web Dynpro2006UI5 Freestyle2012RAP / FE2019abap2UI52023built onthe serverbuilt inthe browserThe rendering moved once and stayed. What has been coming back since is the definition of the screen. \ No newline at end of file