diff --git a/.github/badges/abap2ui5.json b/.github/badges/abap2ui5.json index 480ebb40..9ca0ad61 100644 --- a/.github/badges/abap2ui5.json +++ b/.github/badges/abap2ui5.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "label": "abap2UI5", - "message": "159 apps · 183 views · 2,527 controls", + "message": "166 apps · 190 views · 2,608 controls", "color": "007ec6", "labelColor": "555", "cacheSeconds": 3600 diff --git a/.github/workflows/check-docs.yaml b/.github/workflows/check-docs.yaml index 43b38d4e..8edfb8fa 100644 --- a/.github/workflows/check-docs.yaml +++ b/.github/workflows/check-docs.yaml @@ -2,8 +2,10 @@ name: check-docs # Machine checks for the parts of AGENTS.md that describe the repository # rather than advise on it, and that therefore rot silently: the §1 package -# tree against the actual package.devc.xml CTEXTs, and the §2 strip list -# against the downport script that does the stripping. +# tree against the actual package.devc.xml CTEXTs, the §2 strip list against +# the downport script that does the stripping, and - the other direction of +# the same question - that every sample in the tree is somewhere a catalogue +# can reach it. on: pull_request: @@ -38,6 +40,23 @@ jobs: node-version: '22' - run: node scripts/check-strip-lists.mjs + # Every other gate in this repository counts TILES, so a sample the scan + # cannot place is invisible to all of them at once. Fourteen samples sat in + # the src/ root package for two commits - listed in no overview app and in no + # SAMPLES.md row, with thirteen green gates - because the scan skips a class + # directly in src/ (the overview app lives there). This job asks the opposite + # question: not whether every tile is complete, but whether every sample is a + # tile. + orphan_samples: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '22' + - run: node scripts/check-orphan-samples.mjs + # The view-chain layout used to be a job here, running scripts/chain-format.mjs # because no linter rule judged the four-space STEP. `chain-house-layout` does, # and it is stricter than the pair it replaced, so the layout is now gated diff --git a/AGENTS.md b/AGENTS.md index 42c658f4..42fd4663 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -90,8 +90,17 @@ They spent two commits in the `src/` **root package** on the way, which has no catalog at all: `scripts/lib/scan-samples.mjs` skips a class directly in `src/` (`rel.length < 2`), so all fourteen were missing from the overview app and from `SAMPLES.md` while every gate stayed green. **The root package holds -the overview app and nothing else** — a sample parked there is invisible, and -nothing reports it. +the overview app and nothing else** — a sample parked there is invisible to +every catalogue. + +That last part no longer goes unreported: `node scripts/check-orphan-samples.mjs` +(`npm run check:orphans`, and the `orphan_samples` job on every pull request) +refuses a `z2ui5_cl_smp_app_*` class that sits outside a sample area. It is the +one gate here that does **not** count tiles — which is why it is the only one +that could have caught this: the other thirteen all read the same scan, and the +scan never saw these fourteen. A ZZZ helper and a `src/00/98` app get no tile +either and are fine; being skipped by a RULE is not the same as being skipped +by where the file happens to sit. This tree is machine-checked: `node scripts/check-agents-structure.mjs` compares it against the actual `package.devc.xml` `` values and fails on any @@ -795,7 +804,7 @@ newcomer would actually type: | Header | What belongs in it | |--------|--------------------| -| `Basics I` … `VI` | the entry point — first app, lifecycle, the minimum loop, and what you reach for around it (the developer tools, a unit test on the app's own logic). The only numbered series: the Roman numeral orders them as a learning path (rule 5 sorts by `header`), and `header_base( )` still renders them as one block | +| `Basics I` … `VII` | the entry point — first app, lifecycle, the minimum loop, and what you reach for around it (the developer tools, a unit test on the app's own logic, the translation of its texts). The only numbered series: the Roman numeral orders them as a learning path (rule 5 sorts by `header`), and `header_base( )` still renders them as one block | | `Binding` | `_bind( )`, binding syntax, UI5 model types, the model itself | | `Browser` | the browser page and tab: URL, title, favicon, reload, clipboard, storage, logout | | `Control Behaviour` | one UI5 control is the topic — how it *behaves* and how the backend drives it, typically by calling its methods by ID. **Not** a control reference: that is [samples-controls](https://github.com/abap2UI5/samples-controls), and the header says so (§1) | @@ -888,7 +897,10 @@ newline). **Run `abaplint` — 0 issues — before committing.** 2. Regenerate the overview catalog and `SAMPLES.md`: `npm run launchpad` (§4). 3. If a subpackage was added/removed/renamed: update the §1 tree and run `node scripts/check-agents-structure.mjs`. -4. `abaplint` → 0 issues → commit. +4. Check the sample did not land somewhere no catalogue reaches: + `npm run check:orphans` (§1). Rule 2 of §4 says every sample is listed; + this is what enforces it from the tree side. +5. `abaplint` → 0 issues → commit. **Before every commit** @@ -1116,6 +1128,13 @@ Everything else about a script follows from that: in CI. - **One scan, two renderers.** Anything that reads the sample tree goes through `scripts/lib/scan-samples.mjs` (§4). A second scan drifts silently. +- **A scan that cannot place a class says so.** `scanSamples( )` returns + `orphans` next to `areas` and `hidden`, and `check-orphan-samples.mjs` + refuses them (§1). The two skips that produce one — a class in the `src/` + root, a class in a top-level package that is not an area — were bare + `continue`s until 2026-09-14, which is how fourteen samples went missing + from both catalogues at once. **Never add a silent `continue` to that + loop**: a class the scan drops has to come back out of it somewhere. - **A gate that needs the network says so and passes** when it cannot reach it (`check-app-rules`, `check-docs-links`) — see §6 below and the header of `scripts/check-app-rules.mjs`. Prefer a sibling checkout over a fetch, so a diff --git a/README.md b/README.md index 745b32a6..9f0f12a9 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ # abap2UI5 — samples -**Learn the abap2UI5 basics — 113 ready-to-run samples, from a one-class Hello +**Learn the abap2UI5 basics — 120 ready-to-run samples, from a one-class Hello World to complete applications.** Install them, click through, read the source: every sample adds one idea — a diff --git a/SAMPLES.md b/SAMPLES.md index 0f664a79..c106625d 100644 --- a/SAMPLES.md +++ b/SAMPLES.md @@ -3,11 +3,11 @@ # The sample catalogue -Every app in this repository — 161 of them — with what it shows and a link +Every app in this repository — 168 of them — with what it shows and a link to its source. This is the [overview app](src/z2ui5_cl_smp_app_000.clas.abap) as a page you can read here, before installing anything. -**120 of them ship on every branch** — the portable set the README counts: +**127 of them ship on every branch** — the portable set the README counts: `src/01` plus this overview app. The other 41 are under `src/00` and are stripped from `702`. 2 further classes carry the sample name and are data objects the samples share rather than apps; they are listed too, so nothing @@ -16,7 +16,7 @@ in the tree is invisible. **To run one:** install [abap2UI5](https://github.com/abap2UI5/abap2UI5), pull this repository with [abapGit](https://abapgit.org), then open `?app_start=`. Or run -`Z2UI5_CL_SMP_APP_000` and click through the 113 samples below — +`Z2UI5_CL_SMP_APP_000` and click through the 120 samples below — that is the same list, in the app. **To read one:** click the class. Every sample is a single class, so the link @@ -31,10 +31,10 @@ New to abap2UI5? Start at [Basics](#basics), then the ## The learning path — `src/01` -The **113 ready-to-run samples** the README leads with, and the +The **120 ready-to-run samples** the README leads with, and the overview app lists: cloud-ready, downportable, plain OpenUI5 1.71. Each adds one idea. With the 6 helper apps they call and the -overview app itself, that is 120 apps on every branch. +overview app itself, that is 127 apps on every branch. [Basics](#basics) · [Binding](#binding) · [Browser](#browser) · [Control Behaviour](#control-behaviour) · [CSS](#css) · [Device](#device) · [Event](#event) · [File](#file) · [Focus](#focus) · [Formatter](#formatter) · [Grid Table](#grid-table) · [Hash](#hash) · [List](#list) · [Menu](#menu) · [Message](#message) · [Navigation](#navigation) · [Nested View](#nested-view) · [Popover](#popover) · [Popup](#popup) · [Scroll](#scroll) · [Table](#table) · [Templating](#templating) · [Timer](#timer) · [Tree](#tree) @@ -48,6 +48,7 @@ overview app itself, that is 120 apps on every branch. | **Basics IV** — Events, Views and Roundtrips
What one event does to a running app: a second view replaces the first, the state comes back with it, and an uncaught error surfaces where you can see it.
roundtrip restart second view uncaught error controller basics check_on_navigated get
docs: [cookbook/event_navigation/life_cycle](https://abap2ui5.github.io/docs/cookbook/event_navigation/life_cycle), [cookbook/expert_more/snippets](https://abap2ui5.github.io/docs/cookbook/expert_more/snippets), [tutorials/walkthrough/step-3](https://abap2ui5.github.io/docs/tutorials/walkthrough/step-3) | [`Z2UI5_CL_SMP_APP_004`](src/01/z2ui5_cl_smp_app_004.clas.abap) | | **Basics V** — The Developer Tools (Ctrl+F12)
What Ctrl+F12 opens: the request and response payload, the generated XML view, the model, the source and the error log - the first place to look when something does not render.
developer tools devtools ctrl f12 debug inspect payload previous request response view xml view model source code log error adt export
docs: [cookbook/troubleshooting/common_failures](https://abap2ui5.github.io/docs/cookbook/troubleshooting/common_failures) | [`Z2UI5_CL_SMP_APP_496`](src/01/z2ui5_cl_smp_app_496.clas.abap) | | **Basics VI** — Unit Tests for the App Logic
The app logic in a method of its own - no client, no attributes - so the local test class beside the class can assert it with ABAP Unit.
unit test abapunit testclasses assert testable logic method | [`Z2UI5_CL_SMP_APP_503`](src/01/z2ui5_cl_smp_app_503.clas.abap) | +| **Basics VII** — Translatable Texts (Text Elements)
Puts the screen texts in the class's own text elements instead of an i18n file, so SE63 translates them and the app shows them in the logon language.
translation i18n text element text symbol textpool language message class multi language | [`Z2UI5_CL_SMP_APP_519`](src/01/z2ui5_cl_smp_app_519.clas.abap) | ### Binding @@ -70,6 +71,7 @@ overview app itself, that is 120 apps on every branch. | Sample | Class | |---|---| | Copy to Clipboard (A)
Copies text into the browser clipboard from the backend: a follow-up action carries the string, a toast confirms what landed there.
clipboard paste copy text area
docs: [cookbook/browser_interaction/clipboard](https://abap2ui5.github.io/docs/cookbook/browser_interaction/clipboard) | [`Z2UI5_CL_SMP_APP_325`](src/01/z2ui5_cl_smp_app_325.clas.abap) | +| Keyboard Layout of an Input (inputmode) (C)
Sets the HTML inputmode of an Input through the bound inputMode property of z2ui5.cc.InputExt - the keyboard layout is model data, not an action.
inputmode soft keyboard numeric keypad barcode scanner mobile inputext bound property | [`Z2UI5_CL_SMP_APP_516`](src/01/z2ui5_cl_smp_app_516.clas.abap) | | Local and Session Storage (A,C)
Writes to the browser's local and session storage and reads it back, so a value survives a reload without any state in the backend.
localstorage sessionstorage persist store_data offline
docs: [cookbook/browser_interaction/clipboard](https://abap2ui5.github.io/docs/cookbook/browser_interaction/clipboard) | [`Z2UI5_CL_SMP_APP_327`](src/01/z2ui5_cl_smp_app_327.clas.abap) | | Logout from the Client (A)
Ends the session from the client - the logoff an ICF session or a Fiori launchpad needs, triggered as a follow-up action.
logoff signout icf session end fiori launchpad | [`Z2UI5_CL_SMP_APP_361`](src/01/z2ui5_cl_smp_app_361.clas.abap) | | Open a URL in a New Tab (A)
Opens a URL in a new browser tab from an event, leaving the running app where it is.
url window open_new_tab link target
docs: [cookbook/browser_interaction/url_handling](https://abap2ui5.github.io/docs/cookbook/browser_interaction/url_handling) | [`Z2UI5_CL_SMP_APP_073`](src/01/z2ui5_cl_smp_app_073.clas.abap) | @@ -83,10 +85,14 @@ overview app itself, that is 120 apps on every branch. | Sample | Class | |---|---| +| Aggregation Item by Index (A)
Jumps a Carousel to a page that was cloned from a bound template - addressed positionally as id/aggregation/index, the only way to reach a clone.
carousel aggregation item index clone template setactivepage positional control_by_id | [`Z2UI5_CL_SMP_APP_514`](src/01/z2ui5_cl_smp_app_514.clas.abap) | | Expand a Panel by ID (setExpanded) (A)
Expands a Panel by calling setExpanded on it by ID - a whitelisted control call, no roundtrip and no model behind it.
panel collapse expand setexpanded control_by_id whitelisted
docs: [cookbook/event_navigation/frontend](https://abap2ui5.github.io/docs/cookbook/event_navigation/frontend) | [`Z2UI5_CL_SMP_APP_448`](src/01/z2ui5_cl_smp_app_448.clas.abap) | +| Inline CSS on a Control (css) (A)
Writes a whitelisted CSS declaration onto a control's own DOM node with the css control method - for a value the control has no property for.
css inline style background color opacity control_by_id dom node no property | [`Z2UI5_CL_SMP_APP_513`](src/01/z2ui5_cl_smp_app_513.clas.abap) | | MultiInput with Tokens (C)
A MultiInput with tokens and suggestions bound to an internal table - the value help for a field that holds many values at once.
multiinput token tokens suggestion custom control
docs: [cookbook/expert_more/value_help](https://abap2ui5.github.io/docs/cookbook/expert_more/value_help) | [`Z2UI5_CL_SMP_APP_078`](src/01/z2ui5_cl_smp_app_078.clas.abap) | | Open the PDF Viewer by ID (A)
Opens the PDF viewer by calling it by ID, so a document can be shown from an event without rebuilding the view.
pdfviewer pdf document viewer popup control_by_id whitelisted
docs: [cookbook/device_capabilities/pdf](https://abap2ui5.github.io/docs/cookbook/device_capabilities/pdf), [cookbook/event_navigation/frontend](https://abap2ui5.github.io/docs/cookbook/event_navigation/frontend) | [`Z2UI5_CL_SMP_APP_449`](src/01/z2ui5_cl_smp_app_449.clas.abap) | +| Register an Icon Font (A)
Registers the sap.tnt icon collection with IconPool so a sap-icon://SAP-icons-TNT/... URI resolves - without it the icon renders no glyph and logs nothing.
icon font registerfont iconpool tnt collection glyph missing control_global | [`Z2UI5_CL_SMP_APP_518`](src/01/z2ui5_cl_smp_app_518.clas.abap) | | Switch NavContainer Page by ID (A)
Switches the page of a NavContainer and the tab of an IconTabBar by ID, so navigation inside a view costs no roundtrip.
navcontainer icontabbar icontabheader page switch control_by_id whitelisted
docs: [cookbook/event_navigation/frontend](https://abap2ui5.github.io/docs/cookbook/event_navigation/frontend) | [`Z2UI5_CL_SMP_APP_088`](src/01/z2ui5_cl_smp_app_088.clas.abap) | +| The Global Busy Indicator (A)
Shows and hides the global BusyIndicator from ABAP through control_global - the singleton has no id, so a global target is the only wire that reaches it.
busy indicator global control_global show hide blocking wait spinner long running | [`Z2UI5_CL_SMP_APP_515`](src/01/z2ui5_cl_smp_app_515.clas.abap) | | Wizard with Steps (A)
Drives a Wizard from the backend: setting the next step and discarding progress by ID, which is how a branching wizard is steered.
wizard step branching discardprogress setnextstep control_by_id
docs: [cookbook/event_navigation/frontend](https://abap2ui5.github.io/docs/cookbook/event_navigation/frontend) | [`Z2UI5_CL_SMP_APP_202`](src/01/z2ui5_cl_smp_app_202.clas.abap) | ### CSS @@ -124,6 +130,7 @@ overview app itself, that is 120 apps on every branch. |---|---| | Download to the Browser (A)
Sends a file to the browser as a download - an xstring encoded as base64, handed over as an attachment.
export save base64 attachment xstring document
docs: [cookbook/device_capabilities/upload_download](https://abap2ui5.github.io/docs/cookbook/device_capabilities/upload_download) | [`Z2UI5_CL_SMP_APP_186`](src/01/z2ui5_cl_smp_app_186.clas.abap) | | Upload to the Backend (C)
Takes a file from the FileUploader into the backend as base64 - a picture or a document, arriving as an xstring.
fileuploader base64 attachment import picture document
docs: [cookbook/device_capabilities/upload_download](https://abap2ui5.github.io/docs/cookbook/device_capabilities/upload_download) | [`Z2UI5_CL_SMP_APP_074`](src/01/z2ui5_cl_smp_app_074.clas.abap) | +| Upload with an UploadSet (C)
Takes files from a sap.m.upload.UploadSet into the backend as base64 - the invisible UploadSetExt companion reads each one, so no upload endpoint is needed.
uploadset upload drag drop multiple files base64 attachment uploadsetext companion | [`Z2UI5_CL_SMP_APP_517`](src/01/z2ui5_cl_smp_app_517.clas.abap) | ### Focus diff --git a/abap2ui5lint.jsonc b/abap2ui5lint.jsonc index 699b1e4a..81c697e2 100644 --- a/abap2ui5lint.jsonc +++ b/abap2ui5lint.jsonc @@ -56,6 +56,22 @@ // (z2ui5/cc/*.js) are delivered by the framework at runtime, and the // SAPUI5 template namespace (…/core/template/1/repeat.js) is not part of // OpenUI5. Nothing to fix in the app. + // Only two of the companion controls are mirrored into the harness + // (lib/cc-controls.mjs: MultiInputExt, MessageManager), so every OTHER + // one a view names declaratively lands here - InputExt and UploadSetExt + // are the two added with 516/517. Closing that properly means mirroring + // them upstream in @abap2ui5/linter, not changing anything here. + // + // (a2) the same boundary one step further out: the harness reconstructs the + // VIEW and never executes a frontend action, so anything a + // follow_up_action sets up at runtime is missing while it renders. 518 + // registers the SAP-icons-TNT collection through control_global + // ICON_POOL / registerFont in its init branch - the harness cannot run + // that and reports the collection as unregistered. abap2UI5/samples- + // controls carries the identical case as app 350, where its sidecar + // states the same reason; a render error there has no line and no + // stable message, so naming the file is the only instrument either + // repository has. // // (b) what the RECONSTRUCTION or the sample's own intent makes unjudgeable. // Neither of these is a defect — both were on this list as unresolved @@ -161,6 +177,10 @@ // (a) not served by the harness "z2ui5_cl_smp_app_(074|120|143|279|306|327|461|462|463)\\.", "z2ui5_cl_smp_app_(173|176)\\.", + // (a) an unmirrored companion control: InputExt, UploadSetExt + "z2ui5_cl_smp_app_(516|517)\\.", + // (a2) a runtime registration the harness cannot execute + "z2ui5_cl_smp_app_518\\.", // (b) UNRESOLVED — see above "z2ui5_cl_smp_app_(343|421)\\.", // (c) helper-built, not reconstructable diff --git a/catalogue-derived.json b/catalogue-derived.json index 8d7d4d69..10eadc50 100644 --- a/catalogue-derived.json +++ b/catalogue-derived.json @@ -22,6 +22,7 @@ "sap.m.List", "sap.m.StandardListItem", "sap.ui.layout.Grid", + "sap.m.VBox", "sap.m.Column", "sap.m.ColumnListItem", "sap.m.Table", @@ -32,7 +33,6 @@ "sap.ui.core.Item", "sap.m.CustomListItem", "sap.m.RatingIndicator", - "sap.m.VBox", "sap.m.ObjectStatus", "sap.m.TextArea", "sap.m.ScrollContainer", @@ -41,14 +41,17 @@ "sap.uxap.ObjectPageLayout", "sap.uxap.ObjectPageSection", "sap.uxap.ObjectPageSubSection", + "z2ui5.cc.InputExt", "sap.m.Select", "z2ui5.cc.Storage", "sap.ui.layout.VerticalLayout", + "sap.m.Carousel", "sap.m.Panel", "sap.m.MultiInput", "sap.m.Token", "z2ui5.cc.MultiInputExt", "sap.m.PDFViewer", + "sap.ui.core.Icon", "sap.m.IconTabFilter", "sap.m.IconTabHeader", "sap.m.NavContainer", @@ -73,13 +76,15 @@ "sap.ui.table.Column", "sap.ui.table.Table", "z2ui5.cc.FileUploader", + "sap.m.ObjectNumber", + "sap.m.upload.UploadSet", + "z2ui5.cc.UploadSetExt", "sap.m.CheckBox", "sap.m.DatePicker", "sap.m.DateTimePicker", "sap.m.PlanningCalendar", "sap.m.PlanningCalendarRow", "sap.ui.unified.CalendarAppointment", - "sap.m.ObjectNumber", "sap.f.DynamicPage", "sap.f.DynamicPageHeader", "sap.f.DynamicPageTitle", @@ -108,129 +113,135 @@ "sap.ui.core.ListItem", "sap.ui.core.dnd.DragDropInfo", "sap.m.ObjectIdentifier", - "sap.ui.core.Icon", "sap.m.StandardTreeItem", "sap.m.Tree", "z2ui5.cc.Tree", "sap.m.CustomTreeItem" ], "counts": { - "samples": 113, - "controls": 105 + "samples": 120, + "controls": 109 }, "samples": [ {"class":"z2ui5_cl_smp_app_004","minUi5":"1.71","needs":[],"controls":[5,7,0,1,2,4,13,9],"controlCount":21}, - {"class":"z2ui5_cl_smp_app_006","minUi5":"1.71","needs":[],"controls":[5,65,14,15,0,1,27,81,82,2,16,8,3,87,19,4],"controlCount":33}, - {"class":"z2ui5_cl_smp_app_008","minUi5":"1.71","needs":[],"controls":[5,17,0,1,2,4,13,9],"controlCount":10}, - {"class":"z2ui5_cl_smp_app_009","minUi5":"1.71","needs":[],"controls":[5,14,15,55,6,7,0,18,1,2,16,8,19,51,97,4,13,9],"controlCount":53}, - {"class":"z2ui5_cl_smp_app_011","minUi5":"1.71","needs":[],"controls":[5,65,14,15,6,0,18,1,2,16,8,3,19,4],"controlCount":28}, - {"class":"z2ui5_cl_smp_app_012","minUi5":"1.71","needs":[],"controls":[5,55,7,0,1,2,8,24,51,4,13,9],"controlCount":30}, - {"class":"z2ui5_cl_smp_app_019","minUi5":"1.71","needs":[],"controls":[5,14,15,0,18,1,81,82,2,16,8,3,4],"controlCount":35}, + {"class":"z2ui5_cl_smp_app_006","minUi5":"1.71","needs":[],"controls":[5,71,15,16,0,1,27,86,87,2,17,8,3,92,20,4],"controlCount":33}, + {"class":"z2ui5_cl_smp_app_008","minUi5":"1.71","needs":[],"controls":[5,18,0,1,2,4,13,9],"controlCount":10}, + {"class":"z2ui5_cl_smp_app_009","minUi5":"1.71","needs":[],"controls":[5,15,16,58,6,7,0,19,1,2,17,8,20,54,102,4,13,9],"controlCount":53}, + {"class":"z2ui5_cl_smp_app_011","minUi5":"1.71","needs":[],"controls":[5,71,15,16,6,0,19,1,2,17,8,3,20,4],"controlCount":28}, + {"class":"z2ui5_cl_smp_app_012","minUi5":"1.71","needs":[],"controls":[5,58,7,0,1,2,8,14,54,4,13,9],"controlCount":30}, + {"class":"z2ui5_cl_smp_app_019","minUi5":"1.71","needs":[],"controls":[5,15,16,0,19,1,86,87,2,17,8,3,4],"controlCount":35}, {"class":"z2ui5_cl_smp_app_024","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,4,13,9],"controlCount":17}, - {"class":"z2ui5_cl_smp_app_026","minUi5":"1.71","needs":[],"controls":[5,6,7,17,0,18,1,94,81,82,2,8,3,19,51,4,9],"controlCount":26}, - {"class":"z2ui5_cl_smp_app_027","minUi5":"1.71","needs":[],"controls":[5,6,7,17,0,1,2,3,4,9],"controlCount":25}, + {"class":"z2ui5_cl_smp_app_026","minUi5":"1.71","needs":[],"controls":[5,6,7,18,0,19,1,99,86,87,2,8,3,20,54,4,9],"controlCount":26}, + {"class":"z2ui5_cl_smp_app_027","minUi5":"1.71","needs":[],"controls":[5,6,7,18,0,1,2,3,4,9],"controlCount":25}, {"class":"z2ui5_cl_smp_app_028","minUi5":"1.71","needs":[],"controls":[11,0,1,2,12,4],"controlCount":6}, - {"class":"z2ui5_cl_smp_app_045","minUi5":"1.71","needs":[],"controls":[5,65,14,15,6,7,17,0,18,1,27,2,16,8,3,19,4,9],"controlCount":30}, - {"class":"z2ui5_cl_smp_app_047","minUi5":"1.71","needs":[],"controls":[5,14,15,6,7,0,1,27,2,16,8,3,4,9],"controlCount":29}, + {"class":"z2ui5_cl_smp_app_045","minUi5":"1.71","needs":[],"controls":[5,71,15,16,6,7,18,0,19,1,27,2,17,8,3,20,4,9],"controlCount":30}, + {"class":"z2ui5_cl_smp_app_047","minUi5":"1.71","needs":[],"controls":[5,15,16,6,7,0,1,27,2,17,8,3,4,9],"controlCount":29}, {"class":"z2ui5_cl_smp_app_048","minUi5":"1.71","needs":[],"controls":[11,0,1,2,12,4],"controlCount":6}, - {"class":"z2ui5_cl_smp_app_050","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,3,47,4,9],"controlCount":14}, - {"class":"z2ui5_cl_smp_app_052","minUi5":"1.71","needs":[],"controls":[72,5,14,15,7,17,0,18,1,94,2,16,8,19,51,4,9],"controlCount":35}, - {"class":"z2ui5_cl_smp_app_053","minUi5":"1.71","needs":[],"controls":[5,14,15,10,0,1,76,2,16,8,24,4],"controlCount":25}, - {"class":"z2ui5_cl_smp_app_059","minUi5":"1.71","needs":[],"controls":[14,15,0,1,76,2,16,8,24,4],"controlCount":23}, - {"class":"z2ui5_cl_smp_app_061","minUi5":"1.71","needs":[],"controls":[5,14,15,6,0,18,1,2,16,8,3,19,4],"controlCount":19}, - {"class":"z2ui5_cl_smp_app_064","minUi5":"1.71","needs":[],"controls":[5,0,1,75,2,24,4,35],"controlCount":8}, - {"class":"z2ui5_cl_smp_app_065","minUi5":"1.71","needs":[],"controls":[5,6,17,0,1,2,4],"controlCount":14}, - {"class":"z2ui5_cl_smp_app_067","minUi5":"1.71","needs":[],"controls":[5,6,7,17,0,1,2,8,3,4,9],"controlCount":33}, - {"class":"z2ui5_cl_smp_app_070","minUi5":"1.71","needs":[],"controls":[72,73,74,5,65,28,10,6,0,18,1,75,76,2,8,3,24,4,62,77,78,63,79],"controlCount":52}, - {"class":"z2ui5_cl_smp_app_071","minUi5":"1.71","needs":[],"controls":[5,20,6,7,0,1,2,21,4,9],"controlCount":14}, + {"class":"z2ui5_cl_smp_app_050","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,3,50,4,9],"controlCount":14}, + {"class":"z2ui5_cl_smp_app_052","minUi5":"1.71","needs":[],"controls":[77,5,15,16,7,18,0,19,1,99,2,17,8,20,54,4,9],"controlCount":35}, + {"class":"z2ui5_cl_smp_app_053","minUi5":"1.71","needs":[],"controls":[5,15,16,10,0,1,81,2,17,8,14,4],"controlCount":25}, + {"class":"z2ui5_cl_smp_app_059","minUi5":"1.71","needs":[],"controls":[15,16,0,1,81,2,17,8,14,4],"controlCount":23}, + {"class":"z2ui5_cl_smp_app_061","minUi5":"1.71","needs":[],"controls":[5,15,16,6,0,19,1,2,17,8,3,20,4],"controlCount":19}, + {"class":"z2ui5_cl_smp_app_064","minUi5":"1.71","needs":[],"controls":[5,0,1,80,2,14,4,36],"controlCount":8}, + {"class":"z2ui5_cl_smp_app_065","minUi5":"1.71","needs":[],"controls":[5,6,18,0,1,2,4],"controlCount":14}, + {"class":"z2ui5_cl_smp_app_067","minUi5":"1.71","needs":[],"controls":[5,6,7,18,0,1,2,8,3,4,9],"controlCount":33}, + {"class":"z2ui5_cl_smp_app_070","minUi5":"1.71","needs":[],"controls":[77,78,79,5,71,28,10,6,0,19,1,80,81,2,8,3,14,4,65,82,83,66,84],"controlCount":52}, + {"class":"z2ui5_cl_smp_app_071","minUi5":"1.71","needs":[],"controls":[5,21,6,7,0,1,2,22,4,9],"controlCount":14}, {"class":"z2ui5_cl_smp_app_073","minUi5":"1.71","needs":[],"controls":[5,0,1,2,4,9],"controlCount":6}, - {"class":"z2ui5_cl_smp_app_074","minUi5":"1.71","needs":[],"controls":[0,25,18,1,36,2,26,24,4,64],"controlCount":11}, - {"class":"z2ui5_cl_smp_app_078","minUi5":"1.71","needs":[],"controls":[14,15,6,0,37,1,2,16,8,38,4,39],"controlCount":15}, - {"class":"z2ui5_cl_smp_app_081","minUi5":"1.71","needs":[],"controls":[5,6,7,17,11,0,18,1,94,81,82,2,12,8,3,19,51,4,9],"controlCount":28}, - {"class":"z2ui5_cl_smp_app_088","minUi5":"1.71","needs":[],"controls":[41,42,0,43,1,2,4],"controlCount":12}, - {"class":"z2ui5_cl_smp_app_097","minUi5":"1.71","needs":[],"controls":[93,11,0,18,1,2,12,8,3,4,62,77,78,63],"controlCount":23}, - {"class":"z2ui5_cl_smp_app_098","minUi5":"1.71","needs":[],"controls":[93,5,11,0,18,1,2,12,8,3,4,62,77,78,63],"controlCount":27}, - {"class":"z2ui5_cl_smp_app_104","minUi5":"1.71","needs":[],"controls":[93,11,0,1,2,12,4],"controlCount":7}, - {"class":"z2ui5_cl_smp_app_109","minUi5":"1.71","needs":[],"controls":[5,7,0,1,49,95,96,50,81,82,2,3,51,4,9],"controlCount":24}, - {"class":"z2ui5_cl_smp_app_120","minUi5":"1.71","needs":[],"controls":[6,7,0,1,2,4,9,57],"controlCount":18}, + {"class":"z2ui5_cl_smp_app_074","minUi5":"1.71","needs":[],"controls":[0,25,19,1,38,2,26,14,4,67],"controlCount":11}, + {"class":"z2ui5_cl_smp_app_078","minUi5":"1.71","needs":[],"controls":[15,16,6,0,39,1,2,17,8,40,4,41],"controlCount":15}, + {"class":"z2ui5_cl_smp_app_081","minUi5":"1.71","needs":[],"controls":[5,6,7,18,11,0,19,1,99,86,87,2,12,8,3,20,54,4,9],"controlCount":28}, + {"class":"z2ui5_cl_smp_app_088","minUi5":"1.71","needs":[],"controls":[44,45,0,46,1,2,4],"controlCount":12}, + {"class":"z2ui5_cl_smp_app_097","minUi5":"1.71","needs":[],"controls":[98,11,0,19,1,2,12,8,3,4,65,82,83,66],"controlCount":23}, + {"class":"z2ui5_cl_smp_app_098","minUi5":"1.71","needs":[],"controls":[98,5,11,0,19,1,2,12,8,3,4,65,82,83,66],"controlCount":27}, + {"class":"z2ui5_cl_smp_app_104","minUi5":"1.71","needs":[],"controls":[98,11,0,1,2,12,4],"controlCount":7}, + {"class":"z2ui5_cl_smp_app_109","minUi5":"1.71","needs":[],"controls":[5,7,0,1,52,100,101,53,86,87,2,3,54,4,9],"controlCount":24}, + {"class":"z2ui5_cl_smp_app_120","minUi5":"1.71","needs":[],"controls":[6,7,0,1,2,4,9,60],"controlCount":18}, {"class":"z2ui5_cl_smp_app_122","minUi5":"1.71","needs":[],"controls":[6,7,0,1,2,4,9],"controlCount":49}, {"class":"z2ui5_cl_smp_app_125","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,4,9],"controlCount":8}, {"class":"z2ui5_cl_smp_app_133","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,3,4,9],"controlCount":16}, - {"class":"z2ui5_cl_smp_app_143","minUi5":"1.71","needs":[],"controls":[72,74,10,0,1,2,8,3,24,4,62,77,78,63,80],"controlCount":22}, - {"class":"z2ui5_cl_smp_app_144","minUi5":"1.71","needs":[],"controls":[14,15,6,0,18,1,2,16,8,3,4],"controlCount":20}, - {"class":"z2ui5_cl_smp_app_160","minUi5":"1.71","needs":[],"controls":[28,6,17,0,1,2,8,4,62,63],"controlCount":20}, - {"class":"z2ui5_cl_smp_app_161","minUi5":"1.71","needs":[],"controls":[5,55,7,0,1,2,51,4],"controlCount":13}, - {"class":"z2ui5_cl_smp_app_163","minUi5":"1.71","needs":[],"controls":[5,83,85,0,1,2,24,51,4],"controlCount":14}, + {"class":"z2ui5_cl_smp_app_143","minUi5":"1.71","needs":[],"controls":[77,79,10,0,1,2,8,3,14,4,65,82,83,66,85],"controlCount":22}, + {"class":"z2ui5_cl_smp_app_144","minUi5":"1.71","needs":[],"controls":[15,16,6,0,19,1,2,17,8,3,4],"controlCount":20}, + {"class":"z2ui5_cl_smp_app_160","minUi5":"1.71","needs":[],"controls":[28,6,18,0,1,2,8,4,65,66],"controlCount":20}, + {"class":"z2ui5_cl_smp_app_161","minUi5":"1.71","needs":[],"controls":[5,58,7,0,1,2,54,4],"controlCount":13}, + {"class":"z2ui5_cl_smp_app_163","minUi5":"1.71","needs":[],"controls":[5,88,90,0,1,2,14,54,4],"controlCount":14}, {"class":"z2ui5_cl_smp_app_166","minUi5":"1.71","needs":[],"controls":[6,0,1,2,4],"controlCount":13}, - {"class":"z2ui5_cl_smp_app_167","minUi5":"1.71","needs":[],"controls":[5,6,17,0,1,2,4],"controlCount":11}, - {"class":"z2ui5_cl_smp_app_170","minUi5":"1.71","needs":[],"controls":[5,55,56,41,7,0,43,1,2,24,51,4],"controlCount":21}, - {"class":"z2ui5_cl_smp_app_173","minUi5":"1.71","needs":[],"controls":[14,15,7,0,99,1,2,61,16,8,24,100,4],"controlCount":14}, - {"class":"z2ui5_cl_smp_app_176","minUi5":"1.71","needs":[],"controls":[14,15,0,99,1,2,16,4],"controlCount":11}, - {"class":"z2ui5_cl_smp_app_186","minUi5":"1.71","needs":[],"controls":[5,28,6,0,1,2,8,26,24,4],"controlCount":12}, + {"class":"z2ui5_cl_smp_app_167","minUi5":"1.71","needs":[],"controls":[5,6,18,0,1,2,4],"controlCount":11}, + {"class":"z2ui5_cl_smp_app_170","minUi5":"1.71","needs":[],"controls":[5,58,59,44,7,0,46,1,2,14,54,4],"controlCount":21}, + {"class":"z2ui5_cl_smp_app_173","minUi5":"1.71","needs":[],"controls":[15,16,7,0,104,1,2,64,17,8,14,43,4],"controlCount":14}, + {"class":"z2ui5_cl_smp_app_176","minUi5":"1.71","needs":[],"controls":[15,16,0,104,1,2,17,4],"controlCount":11}, + {"class":"z2ui5_cl_smp_app_186","minUi5":"1.71","needs":[],"controls":[5,28,6,0,1,2,8,26,14,4],"controlCount":12}, {"class":"z2ui5_cl_smp_app_189","minUi5":"1.71","needs":[],"controls":[6,7,0,1,2,4,9],"controlCount":11}, - {"class":"z2ui5_cl_smp_app_197","minUi5":"1.71","needs":[],"controls":[14,15,58,59,60,0,1,2,16,8,4],"controlCount":24}, - {"class":"z2ui5_cl_smp_app_202","minUi5":"1.71","needs":[],"controls":[5,0,1,2,44,45,4],"controlCount":17}, - {"class":"z2ui5_cl_smp_app_255","minUi5":"1.71","needs":[],"controls":[5,28,48,17,0,1,36,49,50,2,24,51,47,4],"controlCount":24}, - {"class":"z2ui5_cl_smp_app_279","minUi5":"1.71","needs":[],"controls":[5,55,28,6,0,1,2,8,24,91,51,4,92],"controlCount":15}, - {"class":"z2ui5_cl_smp_app_305","minUi5":"1.71","needs":[],"controls":[14,15,6,0,18,1,2,16,8,3,46,47,4],"controlCount":16}, - {"class":"z2ui5_cl_smp_app_306","minUi5":"1.71","needs":[],"controls":[20,22,10,52,7,11,0,1,2,8,24,21,4,53,54],"controlCount":19}, - {"class":"z2ui5_cl_smp_app_316","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,26,4,35,9],"controlCount":27}, - {"class":"z2ui5_cl_smp_app_325","minUi5":"1.71","needs":[],"controls":[5,28,10,6,0,1,2,26,3,24,4,29,30,31,32],"controlCount":20}, - {"class":"z2ui5_cl_smp_app_327","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,33,2,21,4,9,34],"controlCount":20}, + {"class":"z2ui5_cl_smp_app_197","minUi5":"1.71","needs":[],"controls":[15,16,61,62,63,0,1,2,17,8,4],"controlCount":24}, + {"class":"z2ui5_cl_smp_app_202","minUi5":"1.71","needs":[],"controls":[5,0,1,2,47,48,4],"controlCount":17}, + {"class":"z2ui5_cl_smp_app_255","minUi5":"1.71","needs":[],"controls":[5,28,51,18,0,1,38,52,53,2,14,54,50,4],"controlCount":24}, + {"class":"z2ui5_cl_smp_app_279","minUi5":"1.71","needs":[],"controls":[5,58,28,6,0,1,2,8,14,96,54,4,97],"controlCount":15}, + {"class":"z2ui5_cl_smp_app_305","minUi5":"1.71","needs":[],"controls":[15,16,6,0,19,1,2,17,8,3,49,50,4],"controlCount":16}, + {"class":"z2ui5_cl_smp_app_306","minUi5":"1.71","needs":[],"controls":[21,23,10,55,7,11,0,1,2,8,14,22,4,56,57],"controlCount":19}, + {"class":"z2ui5_cl_smp_app_316","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,26,4,36,9],"controlCount":27}, + {"class":"z2ui5_cl_smp_app_325","minUi5":"1.71","needs":[],"controls":[5,28,10,6,0,1,2,26,3,14,4,29,30,31,32],"controlCount":20}, + {"class":"z2ui5_cl_smp_app_327","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,34,2,22,4,9,35],"controlCount":20}, {"class":"z2ui5_cl_smp_app_352","minUi5":"1.71","needs":[],"controls":[6,7,0,1,2,3,4,9],"controlCount":8}, {"class":"z2ui5_cl_smp_app_361","minUi5":"1.71","needs":[],"controls":[5,0,1,2,4],"controlCount":9}, - {"class":"z2ui5_cl_smp_app_362","minUi5":"1.71","needs":[],"controls":[5,14,15,0,18,1,2,16,8,4],"controlCount":24}, - {"class":"z2ui5_cl_smp_app_363","minUi5":"1.71","needs":[],"controls":[5,6,7,0,18,1,2,8,4,9],"controlCount":20}, - {"class":"z2ui5_cl_smp_app_381","minUi5":"1.71","needs":[],"controls":[5,65,6,7,17,0,1,36,33,2,8,47,21,4,9],"controlCount":52}, - {"class":"z2ui5_cl_smp_app_382","minUi5":"1.71","needs":[],"controls":[5,6,7,17,0,18,1,36,2,8,26,19,4,9],"controlCount":22}, - {"class":"z2ui5_cl_smp_app_421","minUi5":"1.71","needs":[],"controls":[5,65,14,15,6,7,0,18,1,2,16,8,3,19,4],"controlCount":34}, - {"class":"z2ui5_cl_smp_app_445","minUi5":"1.71","needs":[],"controls":[5,55,56,41,0,1,36,2,8,51,4],"controlCount":15}, - {"class":"z2ui5_cl_smp_app_448","minUi5":"1.71","needs":[],"controls":[5,0,1,36,2,8,24,4],"controlCount":8}, - {"class":"z2ui5_cl_smp_app_449","minUi5":"1.71","needs":[],"controls":[5,0,40,1,2,24,4],"controlCount":7}, - {"class":"z2ui5_cl_smp_app_450","minUi5":"1.71","needs":[],"controls":[66,67,7,0,1,2,4,9],"controlCount":11}, - {"class":"z2ui5_cl_smp_app_452","minUi5":"1.71","needs":[],"controls":[5,55,17,88,89,0,90,18,1,2,19,51,4],"controlCount":21}, - {"class":"z2ui5_cl_smp_app_453","minUi5":"1.71","needs":[],"controls":[14,15,0,71,25,1,2,16,8,4],"controlCount":24}, - {"class":"z2ui5_cl_smp_app_454","minUi5":"1.71","needs":[],"controls":[5,10,11,0,1,76,2,12,24,4],"controlCount":11}, - {"class":"z2ui5_cl_smp_app_455","minUi5":"1.71","needs":[],"controls":[11,0,1,76,2,12,24,4],"controlCount":8}, - {"class":"z2ui5_cl_smp_app_456","minUi5":"1.71","needs":[],"controls":[0,1,68,69,2,4,70],"controlCount":7}, - {"class":"z2ui5_cl_smp_app_457","minUi5":"1.71","needs":[],"controls":[66,0,1,2,8,24,4],"controlCount":7}, - {"class":"z2ui5_cl_smp_app_459","minUi5":"1.71","needs":[],"controls":[14,15,0,1,2,16,8,98,4],"controlCount":13}, - {"class":"z2ui5_cl_smp_app_460","minUi5":"1.71","needs":[],"controls":[0,1,2,101,102,4],"controlCount":6}, - {"class":"z2ui5_cl_smp_app_461","minUi5":"1.71","needs":[],"controls":[0,1,2,101,102,98,4,103],"controlCount":8}, - {"class":"z2ui5_cl_smp_app_462","minUi5":"1.71","needs":[],"controls":[5,55,0,1,2,101,102,24,51,4,103],"controlCount":12}, - {"class":"z2ui5_cl_smp_app_463","minUi5":"1.71","needs":[],"controls":[5,104,6,0,1,2,102,24,4,103],"controlCount":10}, - {"class":"z2ui5_cl_smp_app_464","minUi5":"1.71","needs":[],"controls":[5,0,1,2,24,4],"controlCount":7}, - {"class":"z2ui5_cl_smp_app_465","minUi5":"1.71","needs":[],"controls":[5,0,1,94,2,8,24,4],"controlCount":8}, + {"class":"z2ui5_cl_smp_app_362","minUi5":"1.71","needs":[],"controls":[5,15,16,0,19,1,2,17,8,4],"controlCount":24}, + {"class":"z2ui5_cl_smp_app_363","minUi5":"1.71","needs":[],"controls":[5,6,7,0,19,1,2,8,4,9],"controlCount":20}, + {"class":"z2ui5_cl_smp_app_381","minUi5":"1.71","needs":[],"controls":[5,71,6,7,18,0,1,38,34,2,8,50,22,4,9],"controlCount":52}, + {"class":"z2ui5_cl_smp_app_382","minUi5":"1.71","needs":[],"controls":[5,6,7,18,0,19,1,38,2,8,26,20,4,9],"controlCount":22}, + {"class":"z2ui5_cl_smp_app_421","minUi5":"1.71","needs":[],"controls":[5,71,15,16,6,7,0,19,1,2,17,8,3,20,4],"controlCount":34}, + {"class":"z2ui5_cl_smp_app_445","minUi5":"1.71","needs":[],"controls":[5,58,59,44,0,1,38,2,8,54,4],"controlCount":15}, + {"class":"z2ui5_cl_smp_app_448","minUi5":"1.71","needs":[],"controls":[5,0,1,38,2,8,14,4],"controlCount":8}, + {"class":"z2ui5_cl_smp_app_449","minUi5":"1.71","needs":[],"controls":[5,0,42,1,2,14,4],"controlCount":7}, + {"class":"z2ui5_cl_smp_app_450","minUi5":"1.71","needs":[],"controls":[72,73,7,0,1,2,4,9],"controlCount":11}, + {"class":"z2ui5_cl_smp_app_452","minUi5":"1.71","needs":[],"controls":[5,58,18,93,94,0,95,19,1,2,20,54,4],"controlCount":21}, + {"class":"z2ui5_cl_smp_app_453","minUi5":"1.71","needs":[],"controls":[15,16,0,68,25,1,2,17,8,4],"controlCount":24}, + {"class":"z2ui5_cl_smp_app_454","minUi5":"1.71","needs":[],"controls":[5,10,11,0,1,81,2,12,14,4],"controlCount":11}, + {"class":"z2ui5_cl_smp_app_455","minUi5":"1.71","needs":[],"controls":[11,0,1,81,2,12,14,4],"controlCount":8}, + {"class":"z2ui5_cl_smp_app_456","minUi5":"1.71","needs":[],"controls":[0,1,74,75,2,4,76],"controlCount":7}, + {"class":"z2ui5_cl_smp_app_457","minUi5":"1.71","needs":[],"controls":[72,0,1,2,8,14,4],"controlCount":7}, + {"class":"z2ui5_cl_smp_app_459","minUi5":"1.71","needs":[],"controls":[15,16,0,1,2,17,8,103,4],"controlCount":13}, + {"class":"z2ui5_cl_smp_app_460","minUi5":"1.71","needs":[],"controls":[0,1,2,105,106,4],"controlCount":6}, + {"class":"z2ui5_cl_smp_app_461","minUi5":"1.71","needs":[],"controls":[0,1,2,105,106,103,4,107],"controlCount":8}, + {"class":"z2ui5_cl_smp_app_462","minUi5":"1.71","needs":[],"controls":[5,58,0,1,2,105,106,14,54,4,107],"controlCount":12}, + {"class":"z2ui5_cl_smp_app_463","minUi5":"1.71","needs":[],"controls":[5,108,6,0,1,2,106,14,4,107],"controlCount":10}, + {"class":"z2ui5_cl_smp_app_464","minUi5":"1.71","needs":[],"controls":[5,0,1,2,14,4],"controlCount":7}, + {"class":"z2ui5_cl_smp_app_465","minUi5":"1.71","needs":[],"controls":[5,0,1,99,2,8,14,4],"controlCount":8}, {"class":"z2ui5_cl_smp_app_466","minUi5":"1.71","needs":[],"controls":[0,1,2,4],"controlCount":5}, - {"class":"z2ui5_cl_smp_app_467","minUi5":"1.71","needs":[],"controls":[6,7,11,0,1,2,12,24,4,86],"controlCount":12}, - {"class":"z2ui5_cl_smp_app_468","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,61,4,13,9],"controlCount":15}, - {"class":"z2ui5_cl_smp_app_470","minUi5":"1.71","needs":[],"controls":[5,14,15,55,11,0,25,1,2,12,16,8,24,51,4],"controlCount":26}, + {"class":"z2ui5_cl_smp_app_467","minUi5":"1.71","needs":[],"controls":[6,7,11,0,1,2,12,14,4,91],"controlCount":12}, + {"class":"z2ui5_cl_smp_app_468","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,64,4,13,9],"controlCount":15}, + {"class":"z2ui5_cl_smp_app_470","minUi5":"1.71","needs":[],"controls":[5,15,16,58,11,0,25,1,2,12,17,8,14,54,4],"controlCount":26}, {"class":"z2ui5_cl_smp_app_471","minUi5":"1.71","needs":[],"controls":[5,10,11,0,1,2,12,4],"controlCount":11}, - {"class":"z2ui5_cl_smp_app_472","minUi5":"1.71","needs":[],"controls":[7,17,0,1,2,61,8,4,9],"controlCount":11}, - {"class":"z2ui5_cl_smp_app_473","minUi5":"1.71","needs":[],"controls":[10,83,84,85,0,1,2,4],"controlCount":15}, - {"class":"z2ui5_cl_smp_app_474","minUi5":"1.71","needs":[],"controls":[5,10,88,89,0,1,2,4],"controlCount":11}, + {"class":"z2ui5_cl_smp_app_472","minUi5":"1.71","needs":[],"controls":[7,18,0,1,2,64,8,4,9],"controlCount":11}, + {"class":"z2ui5_cl_smp_app_473","minUi5":"1.71","needs":[],"controls":[10,88,89,90,0,1,2,4],"controlCount":15}, + {"class":"z2ui5_cl_smp_app_474","minUi5":"1.71","needs":[],"controls":[5,10,93,94,0,1,2,4],"controlCount":11}, {"class":"z2ui5_cl_smp_app_480","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,4,13,9],"controlCount":13}, {"class":"z2ui5_cl_smp_app_488","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,4,13,9],"controlCount":14}, - {"class":"z2ui5_cl_smp_app_490","minUi5":"1.71","needs":[],"controls":[5,0,25,1,94,2,8,24,51,4],"controlCount":12}, + {"class":"z2ui5_cl_smp_app_490","minUi5":"1.71","needs":[],"controls":[5,0,25,1,99,2,8,14,54,4],"controlCount":12}, {"class":"z2ui5_cl_smp_app_491","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,4,9],"controlCount":8}, {"class":"z2ui5_cl_smp_app_492","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,4,9],"controlCount":10}, {"class":"z2ui5_cl_smp_app_493","minUi5":"1.71","needs":[],"controls":[0,1,2,3,4],"controlCount":5}, {"class":"z2ui5_cl_smp_app_494","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,8,4,9],"controlCount":12}, {"class":"z2ui5_cl_smp_app_495","minUi5":"1.71","needs":[],"controls":[5,10,11,0,1,2,12,4],"controlCount":9}, {"class":"z2ui5_cl_smp_app_496","minUi5":"1.71","needs":[],"controls":[5,6,7,11,0,1,2,12,8,4,9],"controlCount":15}, - {"class":"z2ui5_cl_smp_app_497","minUi5":"1.71","needs":[],"controls":[14,15,0,1,2,16,8,4],"controlCount":9}, - {"class":"z2ui5_cl_smp_app_498","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,61,4,9],"controlCount":16}, - {"class":"z2ui5_cl_smp_app_499","minUi5":"1.71","needs":[],"controls":[5,6,7,0,43,25,1,81,82,2,4,9],"controlCount":19}, - {"class":"z2ui5_cl_smp_app_502","minUi5":"1.71","needs":[],"controls":[5,10,7,0,1,2,8,3,87,4,9],"controlCount":97}, + {"class":"z2ui5_cl_smp_app_497","minUi5":"1.71","needs":[],"controls":[15,16,0,1,2,17,8,4],"controlCount":9}, + {"class":"z2ui5_cl_smp_app_498","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,64,4,9],"controlCount":16}, + {"class":"z2ui5_cl_smp_app_499","minUi5":"1.71","needs":[],"controls":[5,6,7,0,46,25,1,86,87,2,4,9],"controlCount":19}, + {"class":"z2ui5_cl_smp_app_502","minUi5":"1.71","needs":[],"controls":[5,10,7,0,1,2,8,3,92,4,9],"controlCount":97}, {"class":"z2ui5_cl_smp_app_503","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,8,4,9],"controlCount":13}, - {"class":"z2ui5_cl_smp_app_504","minUi5":"1.71","needs":[],"controls":[5,14,15,22,10,6,11,0,18,1,2,16,8,3,19,4],"controlCount":31}, - {"class":"z2ui5_cl_smp_app_505","minUi5":"1.71","needs":[],"controls":[0,18,1,2,8,3,4,62,63],"controlCount":17}, + {"class":"z2ui5_cl_smp_app_504","minUi5":"1.71","needs":[],"controls":[5,15,16,23,10,6,11,0,19,1,2,17,8,3,20,4],"controlCount":31}, + {"class":"z2ui5_cl_smp_app_505","minUi5":"1.71","needs":[],"controls":[0,19,1,2,8,3,4,65,66],"controlCount":17}, {"class":"z2ui5_cl_smp_app_506","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,8,4,9],"controlCount":16}, - {"class":"z2ui5_cl_smp_app_507","minUi5":"1.71","needs":[],"controls":[22,10,7,11,0,1,23,2,8,3,24,4],"controlCount":29}, + {"class":"z2ui5_cl_smp_app_507","minUi5":"1.71","needs":[],"controls":[23,10,7,11,0,1,24,2,8,3,14,4],"controlCount":29}, {"class":"z2ui5_cl_smp_app_508","minUi5":"1.71","needs":[],"controls":[6,7,11,0,25,1,2,12,8,4,9],"controlCount":14}, {"class":"z2ui5_cl_smp_app_509","minUi5":"1.71","needs":[],"controls":[7,11,0,1,2,12,8,26,4,9],"controlCount":14}, - {"class":"z2ui5_cl_smp_app_510","minUi5":"1.71","needs":[],"controls":[5,10,6,0,1,36,2,19,24,4],"controlCount":20}, - {"class":"z2ui5_cl_smp_app_511","minUi5":"1.71","needs":[],"controls":[6,7,0,1,2,8,3,24,4,13],"controlCount":19}, - {"class":"z2ui5_cl_smp_app_512","minUi5":"1.71","needs":[],"controls":[5,10,7,0,1,36,2,8,4,9],"controlCount":26} + {"class":"z2ui5_cl_smp_app_510","minUi5":"1.71","needs":[],"controls":[5,10,6,0,1,38,2,20,14,4],"controlCount":20}, + {"class":"z2ui5_cl_smp_app_511","minUi5":"1.71","needs":[],"controls":[6,7,0,1,2,8,3,14,4,13],"controlCount":19}, + {"class":"z2ui5_cl_smp_app_512","minUi5":"1.71","needs":[],"controls":[5,10,7,0,1,38,2,8,4,9],"controlCount":26}, + {"class":"z2ui5_cl_smp_app_513","minUi5":"1.71","needs":[],"controls":[5,10,0,1,38,2,8,14,4],"controlCount":11}, + {"class":"z2ui5_cl_smp_app_514","minUi5":"1.71","needs":[],"controls":[5,37,10,0,1,2,8,3,14,4],"controlCount":12}, + {"class":"z2ui5_cl_smp_app_515","minUi5":"1.71","needs":[],"controls":[5,0,25,1,2,14,4],"controlCount":7}, + {"class":"z2ui5_cl_smp_app_516","minUi5":"1.71","needs":[],"controls":[5,10,0,25,1,2,14,4,33],"controlCount":13}, + {"class":"z2ui5_cl_smp_app_517","minUi5":"1.71","needs":[],"controls":[5,15,16,0,68,19,1,2,17,8,3,20,69,4,70],"controlCount":21}, + {"class":"z2ui5_cl_smp_app_518","minUi5":"1.71","needs":[],"controls":[0,1,2,3,14,43,4],"controlCount":9}, + {"class":"z2ui5_cl_smp_app_519","minUi5":"1.71","needs":[],"controls":[5,6,7,0,1,2,14,4],"controlCount":8} ] } diff --git a/catalogue.json b/catalogue.json index aa9ca455..70818a48 100644 --- a/catalogue.json +++ b/catalogue.json @@ -62,7 +62,7 @@ } ], "counts": { - "samples": 113, + "samples": 120, "categories": 24, "stages": 6 }, @@ -224,6 +224,30 @@ ], "docs": [] }, + { + "class": "z2ui5_cl_smp_app_519", + "file": "src/01/z2ui5_cl_smp_app_519.clas.abap", + "category": "Basics", + "stage": "start", + "title": "Basics VII", + "description": "Translatable Texts (Text Elements)", + "summary": "Puts the screen texts in the class's own text elements instead of an i18n file, so SE63 translates them and the app shows them in the logon language.", + "keywords": [ + "translation", + "i18n", + "text", + "element", + "text", + "symbol", + "textpool", + "language", + "message", + "class", + "multi", + "language" + ], + "docs": [] + }, { "class": "z2ui5_cl_smp_app_497", "file": "src/01/z2ui5_cl_smp_app_497.clas.abap", @@ -494,6 +518,29 @@ "https://abap2ui5.github.io/docs/cookbook/browser_interaction/clipboard" ] }, + { + "class": "z2ui5_cl_smp_app_516", + "file": "src/01/z2ui5_cl_smp_app_516.clas.abap", + "category": "Browser", + "stage": "beyond", + "title": "Browser", + "description": "Keyboard Layout of an Input (inputmode) (C)", + "summary": "Sets the HTML inputmode of an Input through the bound inputMode property of z2ui5.cc.InputExt - the keyboard layout is model data, not an action.", + "keywords": [ + "inputmode", + "soft", + "keyboard", + "numeric", + "keypad", + "barcode", + "scanner", + "mobile", + "inputext", + "bound", + "property" + ], + "docs": [] + }, { "class": "z2ui5_cl_smp_app_327", "file": "src/01/z2ui5_cl_smp_app_327.clas.abap", @@ -650,6 +697,27 @@ "https://abap2ui5.github.io/docs/cookbook/browser_interaction/soft_keyboard" ] }, + { + "class": "z2ui5_cl_smp_app_514", + "file": "src/01/z2ui5_cl_smp_app_514.clas.abap", + "category": "Control Behaviour", + "stage": "beyond", + "title": "Control Behaviour", + "description": "Aggregation Item by Index (A)", + "summary": "Jumps a Carousel to a page that was cloned from a bound template - addressed positionally as id/aggregation/index, the only way to reach a clone.", + "keywords": [ + "carousel", + "aggregation", + "item", + "index", + "clone", + "template", + "setactivepage", + "positional", + "control_by_id" + ], + "docs": [] + }, { "class": "z2ui5_cl_smp_app_448", "file": "src/01/z2ui5_cl_smp_app_448.clas.abap", @@ -670,6 +738,29 @@ "https://abap2ui5.github.io/docs/cookbook/event_navigation/frontend" ] }, + { + "class": "z2ui5_cl_smp_app_513", + "file": "src/01/z2ui5_cl_smp_app_513.clas.abap", + "category": "Control Behaviour", + "stage": "beyond", + "title": "Control Behaviour", + "description": "Inline CSS on a Control (css) (A)", + "summary": "Writes a whitelisted CSS declaration onto a control's own DOM node with the css control method - for a value the control has no property for.", + "keywords": [ + "css", + "inline", + "style", + "background", + "color", + "opacity", + "control_by_id", + "dom", + "node", + "no", + "property" + ], + "docs": [] + }, { "class": "z2ui5_cl_smp_app_078", "file": "src/01/z2ui5_cl_smp_app_078.clas.abap", @@ -712,6 +803,27 @@ "https://abap2ui5.github.io/docs/cookbook/event_navigation/frontend" ] }, + { + "class": "z2ui5_cl_smp_app_518", + "file": "src/01/z2ui5_cl_smp_app_518.clas.abap", + "category": "Control Behaviour", + "stage": "beyond", + "title": "Control Behaviour", + "description": "Register an Icon Font (A)", + "summary": "Registers the sap.tnt icon collection with IconPool so a sap-icon://SAP-icons-TNT/... URI resolves - without it the icon renders no glyph and logs nothing.", + "keywords": [ + "icon", + "font", + "registerfont", + "iconpool", + "tnt", + "collection", + "glyph", + "missing", + "control_global" + ], + "docs": [] + }, { "class": "z2ui5_cl_smp_app_088", "file": "src/01/z2ui5_cl_smp_app_088.clas.abap", @@ -733,6 +845,29 @@ "https://abap2ui5.github.io/docs/cookbook/event_navigation/frontend" ] }, + { + "class": "z2ui5_cl_smp_app_515", + "file": "src/01/z2ui5_cl_smp_app_515.clas.abap", + "category": "Control Behaviour", + "stage": "beyond", + "title": "Control Behaviour", + "description": "The Global Busy Indicator (A)", + "summary": "Shows and hides the global BusyIndicator from ABAP through control_global - the singleton has no id, so a global target is the only wire that reaches it.", + "keywords": [ + "busy", + "indicator", + "global", + "control_global", + "show", + "hide", + "blocking", + "wait", + "spinner", + "long", + "running" + ], + "docs": [] + }, { "class": "z2ui5_cl_smp_app_202", "file": "src/01/z2ui5_cl_smp_app_202.clas.abap", @@ -1095,6 +1230,28 @@ "https://abap2ui5.github.io/docs/cookbook/device_capabilities/upload_download" ] }, + { + "class": "z2ui5_cl_smp_app_517", + "file": "src/01/z2ui5_cl_smp_app_517.clas.abap", + "category": "File", + "stage": "beyond", + "title": "File", + "description": "Upload with an UploadSet (C)", + "summary": "Takes files from a sap.m.upload.UploadSet into the backend as base64 - the invisible UploadSetExt companion reads each one, so no upload endpoint is needed.", + "keywords": [ + "uploadset", + "upload", + "drag", + "drop", + "multiple", + "files", + "base64", + "attachment", + "uploadsetext", + "companion" + ], + "docs": [] + }, { "class": "z2ui5_cl_smp_app_421", "file": "src/01/z2ui5_cl_smp_app_421.clas.abap", diff --git a/package.json b/package.json index 5406fa06..d92bcfc2 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "fmt:chains": "abap2ui5lint --fix", "check:agents": "node scripts/check-agents-structure.mjs", "check:strip": "node scripts/check-strip-lists.mjs", + "check:orphans": "node scripts/check-orphan-samples.mjs", "check:keywords": "node scripts/check-keywords.mjs", "check:launchpad": "node scripts/generate-launchpad.mjs --check && node scripts/generate-samples-md.mjs --check", "check:catalogue": "node scripts/generate-catalogue.mjs --check && node scripts/check-catalogue-contract.mjs", @@ -25,7 +26,7 @@ "rename": "abaplint .github/abaplint/rename_test.jsonc --rename", "syfixes": "find . -type f -name '*.abap' -exec sed -i -e 's/ RAISE EXCEPTION TYPE cx_sy_itab_line_not_found/ ASSERT 1 = 0/g' {} + ", "downport": "rm -rf src/00 && abaplint --fix .github/abaplint/abap_702.jsonc && npm run syfixes && node scripts/generate-samples-md.mjs", - "check": "npm run check:pin && npm run lint && npm run check:cloud && npm run check:abap2ui5 && npm run check:agents && npm run check:strip && npm run check:keywords && npm run check:launchpad && npm run check:catalogue && npm run check:derived && npm run check:prose && npm run check:docs-links && npm run check:app-rules && npm run rename" + "check": "npm run check:pin && npm run lint && npm run check:cloud && npm run check:abap2ui5 && npm run check:agents && npm run check:strip && npm run check:orphans && npm run check:keywords && npm run check:launchpad && npm run check:catalogue && npm run check:derived && npm run check:prose && npm run check:docs-links && npm run check:app-rules && npm run rename" }, "repository": { "type": "git", diff --git a/scripts/check-orphan-samples.mjs b/scripts/check-orphan-samples.mjs new file mode 100644 index 00000000..f7b10603 --- /dev/null +++ b/scripts/check-orphan-samples.mjs @@ -0,0 +1,75 @@ +#!/usr/bin/env node +/* + * check-orphan-samples — a sample that is in the tree but in no catalogue. + * + * Every other gate here counts TILES: check-keywords holds each one to a + * `@keywords` and a `@summary` line, generate-launchpad refuses a category no + * learning-path stage names, generate-samples-md and generate-catalogue fail + * when their output is stale. All of them read scanSamples( ), and scanSamples + * only ever returns what it could place — so a sample the scan cannot place is + * invisible to the lot of them, and a green `npm run check` says nothing about + * it. + * + * That is not hypothetical. `Z2UI5_CL_SMP_APP_004` and thirteen others were + * moved out of `src/00/97` and landed in the `src/` ROOT package (#843, and the + * two hand-made commits before it). The root package has no catalogue: the + * scan skips a class directly in `src/` because the overview app lives there + * and must not list itself. So for two commits the repository shipped fourteen + * samples that appeared in neither the overview app nor SAMPLES.md — among + * them the only demonstrations of app_state_set_active, hash_back, _bind_path, + * omit_initial, check_arg_literal and t_model_skipped, and Basics IV, which + * left the learning path reading I, II, III, V, VI. Thirteen gates stayed + * green the whole time. + * + * This is the gate for the other direction: not "is every tile complete" but + * "is every sample a tile". A class whose name says sample + * (`z2ui5_cl_smp_app_*`) has to sit in an area scanSamples reads — `src/00` or + * `src/01` — or it is refused here. + * + * WHAT IS DELIBERATELY NOT AN ORPHAN: + * + * - the overview app (`z2ui5_cl_smp_app_000`). It lives in the root package + * on purpose (AGENTS.md section 3) and is an index, not a sample; + * scanSamples drops it by name before either skip, so it never reaches + * the orphan list. + * - a ZZZ helper app. It is in an area, gets no tile by its own rule, and + * scanSamples returns it under `hidden` — accounted for, not lost. + * - a sample in `src/00/98`. No tile either, and that is the package's + * whole point; it is still scanned, still counted, still listed in + * SAMPLES.md's system section. + * + * The distinction this gate draws is therefore not "has a tile" but "was + * SEEN". Being skipped by a rule is fine; being skipped by an accident of + * where the file sits is not. + * + * Run: node scripts/check-orphan-samples.mjs (npm run check:orphans) + */ +import { scanSamples, AREAS } from './lib/scan-samples.mjs'; + +const { areas, hidden, orphans } = scanSamples(); + +if (orphans.length > 0) { + const n = orphans.length; + console.error( + `${n} sample class${n === 1 ? '' : 'es'} in the tree that no catalogue can reach:\n`, + ); + for (const o of orphans) console.error(` ${o.path}\n sits in ${o.why}`); + console.error( + '\nA class named z2ui5_cl_smp_app_* has to live in a sample area ' + + `(${AREAS.map((a) => `src/${a}`).join(', ')}), in a categorised subpackage.` + + '\nWhere it sits now it is in no overview app and in no SAMPLES.md row, ' + + 'and every\nother gate here passes it without looking: they all count ' + + 'tiles, and it is not one.' + + '\n\nMove it into src/01 if it is a finished sample, or into src/00/98 if ' + + 'it is a test\napp (AGENTS.md sections 1 and 2), then run `npm run ' + + 'launchpad` and commit the result.', + ); + process.exit(1); +} + +const tiles = Object.values(areas).flat().length; +console.log( + `check-orphans: ${tiles + hidden.length} sample class(es) scanned in ` + + `${AREAS.map((a) => `src/${a}`).join(' + ')}, every one of them reachable ` + + `by a catalogue (${hidden.length} ZZZ helper(s) accounted for) - OK`, +); diff --git a/scripts/lib/scan-samples.mjs b/scripts/lib/scan-samples.mjs index 60636dde..1c985f58 100644 --- a/scripts/lib/scan-samples.mjs +++ b/scripts/lib/scan-samples.mjs @@ -105,11 +105,21 @@ function groupOf(dir) { * than dropped: the overview must not list them, and a catalogue that claims * to account for the tree has to be able to say they exist. * - * @returns {{areas: Record, hidden: object[]}} + * So are the classes this scan cannot place - `orphans`. A sample carries the + * name of a sample wherever it sits, but only a class inside an AREA becomes a + * tile, and the two skips that decide it (a class in the `src/` root, a class + * in a top-level package that is not an area) used to be silent `continue`s. + * Fourteen samples sat in the root package for two commits because of it: + * absent from the overview app and from SAMPLES.md, and every gate green, + * because nothing that counts tiles can miss what it never scanned. They are + * collected here and `check-orphan-samples.mjs` refuses them. + * + * @returns {{areas: Record, hidden: object[], orphans: object[]}} */ function scanSamples() { const areas = Object.fromEntries(AREAS.map((a) => [a, []])); const hidden = []; + const orphans = []; for (const abap of walk(SRC)) { if (!abap.endsWith('.clas.abap')) continue; @@ -118,12 +128,14 @@ function scanSamples() { if (!cls.startsWith(SAMPLE_PREFIX)) continue; const rel = path.relative(SRC, abap).split(path.sep); // [ area, ...subfolders, file ] - if (rel.length < 2) continue; // a class directly in src/ root is never a tile + const where = path.relative(ROOT, abap).split(path.sep).join('/'); + // a class directly in src/ root is never a tile - and never intended + if (rel.length < 2) { orphans.push({ app: cls, path: where, why: 'the src/ root package' }); continue; } const area = rel[0]; // full subfolder path ("03" or nested "03/01") so nested subpackages form // their own group directly after their parent slot const subnum = rel.slice(1, -1).join('/'); - if (!(area in areas)) continue; + if (!(area in areas)) { orphans.push({ app: cls, path: where, why: `src/${area}, which is not a sample area` }); continue; } const xmlPath = abap.replace(/\.clas\.abap$/, '.clas.xml'); if (!fs.existsSync(xmlPath)) { console.warn(`skipping ${cls}: no .clas.xml`); continue; } @@ -233,7 +245,7 @@ function scanSamples() { seenText.set(key, entry.app); } - return { areas, hidden }; + return { areas, hidden, orphans }; } export { ROOT, SRC, AREAS, DOCS_SITE, scanSamples, headerBase }; diff --git a/src/01/z2ui5_cl_smp_app_513.clas.abap b/src/01/z2ui5_cl_smp_app_513.clas.abap new file mode 100644 index 00000000..da24bd15 --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_513.clas.abap @@ -0,0 +1,149 @@ +" @keywords css inline style background color opacity control_by_id dom node no property +" @summary Writes a whitelisted CSS declaration onto a control's own DOM node with the css control method - for a value the control has no property for. +CLASS z2ui5_cl_smp_app_513 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + " not bound - mirrors what was written last, so the buttons can alternate + DATA highlighted TYPE abap_bool. + + METHODS view_display. + METHODS on_event. + METHODS css_set + IMPORTING + property TYPE string + value TYPE string. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smp_app_513 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_navigated( ). + view_display( ). + ELSE. + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD css_set. + + " t_arg is positional: id, the pseudo-method `css`, the property, the value. + " `css` is not a UI5 method - it is a frontend capability in method form, + " and only ten properties are allowed: width, min-width, max-width, + " height, min-height, max-height, color, background-color, font-size and + " opacity. Anything else is refused and logged, never applied. + client->follow_up_action( val = z2ui5_if_client=>cs_event-control_by_id + t_arg = VALUE #( ( `demoPanel` ) + ( `css` ) + ( property ) + ( value ) ) ). + + ENDMETHOD. + + + METHOD on_event. + + CASE client->get_event( ). + + WHEN `HIGHLIGHT`. + highlighted = xsdbool( highlighted = abap_false ). + " sap.m.Panel carries no background colour and no font size, so there + " is no property to bind and nothing a formatter could reach. The + " declaration lands on the panel's own DOM node. + IF highlighted = abap_true. + css_set( property = `background-color` + value = `#fff4e5` ). + css_set( property = `font-size` + value = `1.25rem` ). + ELSE. + " an empty value removes the declaration again + css_set( property = `background-color` + value = `` ). + css_set( property = `font-size` + value = `` ). + ENDIF. + + WHEN `FADE`. + css_set( property = `opacity` + value = `0.35` ). + + WHEN `RESET`. + " the declaration lives on the element and is gone after a re-render, + " so a fresh view is the other way back to the control's own styling + highlighted = abap_false. + view_display( ). + + ENDCASE. + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ). + + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - Control Behaviour - Inline CSS on a Control (css)` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `The css control method writes ONE whitelisted declaration onto the control's own DOM node ` && + `- for a value the control has no property for. Prefer a bound property wherever one exists; a declaration ` && + `written here is gone after the next re-render.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + page->ele( `VBox` + )->a( n = `class` v = `sapUiSmallMargin` + + )->ele( `HBox` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `HIGHLIGHT` ) + )->a( n = `text` v = `Toggle background and font size` + )->a( n = `icon` v = `sap-icon://palette` + )->a( n = `class` v = `sapUiTinyMarginEnd` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `FADE` ) + )->a( n = `text` v = `Fade` + )->a( n = `icon` v = `sap-icon://hide` + )->a( n = `class` v = `sapUiTinyMarginEnd` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `RESET` ) + )->a( n = `text` v = `Rebuild the view` + )->a( n = `icon` v = `sap-icon://refresh` ). + + page->ele( `Panel` + )->a( n = `width` v = `auto` + )->a( n = `id` v = `demoPanel` + )->a( n = `class` v = `sapUiSmallMargin` + )->a( n = `headerText` v = `A panel with no colour property` + )->tag( `Text` + )->a( n = `text` v = `sap.m.Panel has no background-color, no font-size and no opacity. ` && + `Every one of them is set here from ABAP, client-side after the response renders.` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/01/z2ui5_cl_smp_app_513.clas.xml b/src/01/z2ui5_cl_smp_app_513.clas.xml new file mode 100644 index 00000000..9f493913 --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_513.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMP_APP_513 + E + Control Behaviour - Inline CSS on a Control (css) (A) + 1 + X + X + X + + + + diff --git a/src/01/z2ui5_cl_smp_app_514.clas.abap b/src/01/z2ui5_cl_smp_app_514.clas.abap new file mode 100644 index 00000000..8ae5bfe3 --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_514.clas.abap @@ -0,0 +1,171 @@ +" @keywords carousel aggregation item index clone template setactivepage positional control_by_id +" @summary Jumps a Carousel to a page that was cloned from a bound template - addressed positionally as id/aggregation/index, the only way to reach a clone. +CLASS z2ui5_cl_smp_app_514 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + TYPES: + BEGIN OF ty_s_page, + title TYPE string, + text TYPE string, + END OF ty_s_page. + + DATA t_pages TYPE STANDARD TABLE OF ty_s_page WITH EMPTY KEY. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + " not bound - the index the backend last jumped to, counted from 1 + DATA current TYPE i. + + METHODS view_display. + METHODS model_init. + METHODS on_event. + METHODS page_show + IMPORTING + index TYPE i. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smp_app_514 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + model_init( ). + view_display( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ELSE. + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD model_init. + + t_pages = VALUE #( ( title = `Bicycle` text = `The first page of the carousel.` ) + ( title = `Car` text = `The second page - and the one the buttons below jump to.` ) + ( title = `Train` text = `The third page.` ) + ( title = `Aircraft` text = `The fourth and last page.` ) ). + current = 1. + + ENDMETHOD. + + + METHOD page_show. + + current = index. + " A page of this carousel is a CLONE of the aggregation template, and a + " clone has no id the backend can spell: UI5 mints it from the template + " id, the parent id and the position, and the parent id carries the view + " prefix assigned at runtime. So the item is addressed POSITIONALLY - + " `//`, 0-based - which the frontend + " resolves against the live aggregation. It is the equivalent of the UI5 + " controller idiom oCarousel.setActivePage( oCarousel.getPages()[ i ] ). + client->follow_up_action( val = z2ui5_if_client=>cs_event-control_by_id + t_arg = VALUE #( ( `demoCarousel` ) + ( `setActivePage` ) + ( |demoCarousel/pages/{ index - 1 }| ) ) ). + + ENDMETHOD. + + + METHOD on_event. + + CASE client->get_event( ). + + WHEN `FIRST`. + page_show( 1 ). + + WHEN `NEXT`. + " wrap around at the end - the count is ABAP's, not the carousel's + page_show( COND #( WHEN current >= lines( t_pages ) THEN 1 ELSE current + 1 ) ). + + WHEN `LAST`. + page_show( lines( t_pages ) ). + + ENDCASE. + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ). + + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - Control Behaviour - Address an Aggregation Item by Index` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `The carousel pages are clones of a bound template, so none of them has an id the backend ` && + `could name. Wherever a control call takes a control id it also takes an aggregation item, written ` && + `id/aggregation/index and counted from 0.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + page->ele( `HBox` + )->a( n = `class` v = `sapUiSmallMargin` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `FIRST` ) + )->a( n = `text` v = `First` + )->a( n = `icon` v = `sap-icon://close-command-field` + )->a( n = `class` v = `sapUiTinyMarginEnd` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `NEXT` ) + )->a( n = `text` v = `Next` + )->a( n = `icon` v = `sap-icon://navigation-right-arrow` + )->a( n = `class` v = `sapUiTinyMarginEnd` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `LAST` ) + )->a( n = `text` v = `Last` + )->a( n = `icon` v = `sap-icon://open-command-field` ). + + page->ele( `Carousel` + )->a( n = `id` v = `demoCarousel` + )->a( n = `height` v = `20rem` + )->a( n = `pages` v = client->_bind( t_pages ) + )->a( n = `class` v = `sapUiSmallMargin` + + )->ele( `pages` + )->ele( `VBox` + )->a( n = `justifyContent` v = `Center` + )->a( n = `alignItems` v = `Center` + )->a( n = `height` v = `100%` + + )->tag( `Title` + )->a( n = `text` v = `{TITLE}` + )->a( n = `level` v = `H2` + + )->tag( `Text` + )->a( n = `text` v = `{TEXT}` + )->a( n = `class` v = `sapUiSmallMarginTop` ). + + client->view_display( view->stringify( ) ). + + " The active page is live control state that no binding carries: this + " response destroys the view slot and the frontend rebuilds the control + " tree, so the carousel comes back on its first page while `current` + " survives in ABAP. Re-issuing the call from here - after the view that + " will hold it - is what keeps the two in step across a rebuild. + page_show( current ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/01/z2ui5_cl_smp_app_514.clas.xml b/src/01/z2ui5_cl_smp_app_514.clas.xml new file mode 100644 index 00000000..32ec3eff --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_514.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMP_APP_514 + E + Control Behaviour - Aggregation Item by Index (A) + 1 + X + X + X + + + + diff --git a/src/01/z2ui5_cl_smp_app_515.clas.abap b/src/01/z2ui5_cl_smp_app_515.clas.abap new file mode 100644 index 00000000..711cac53 --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_515.clas.abap @@ -0,0 +1,121 @@ +" @keywords busy indicator global control_global show hide blocking wait spinner long running +" @summary Shows and hides the global BusyIndicator from ABAP through control_global - the singleton has no id, so a global target is the only wire that reaches it. +CLASS z2ui5_cl_smp_app_515 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA status TYPE string. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + METHODS on_event. + METHODS busy_call + IMPORTING + method TYPE string + arg TYPE string OPTIONAL. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smp_app_515 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + status = `Idle - nothing running.`. + view_display( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ELSE. + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD busy_call. + + " sap.ui.core.BusyIndicator renders nothing of its own and has no id, so + " control_by_id cannot address it - control_global is the only wire that + " reaches a singleton like this one. t_arg is positional: the object, the + " method, then its arguments. show( ) takes the delay in milliseconds + " before the spinner appears; hide( ) takes none. + client->follow_up_action( val = z2ui5_if_client=>cs_event-control_global + t_arg = COND #( WHEN arg IS INITIAL + THEN VALUE #( ( `BUSY_INDICATOR` ) ( method ) ) + ELSE VALUE #( ( `BUSY_INDICATOR` ) ( method ) ( arg ) ) ) ). + + ENDMETHOD. + + + METHOD on_event. + + CASE client->get_event( ). + + WHEN `START`. + " The indicator blocks the whole screen, so nothing the user does can + " take it away again - something in this response has to. A client + " timer is what fills the gap here; a real app would hide it in the + " handler of whatever it was waiting for. + status = `Running - the BusyIndicator is up, the timer takes it away.`. + busy_call( method = `show` + arg = `0` ). + client->follow_up_action( val = z2ui5_if_client=>cs_event-start_timer + t_arg = VALUE #( ( `FINISHED` ) ( `2500` ) ) ). + + WHEN `FINISHED`. + status = `Done - hide( ) was called from the timer's handler.`. + busy_call( `hide` ). + + ENDCASE. + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ). + + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - Control Behaviour - The Global Busy Indicator` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `abap2UI5 busies the screen for its own roundtrips already. This is the indicator an app ` && + `drives itself, for a wait the framework knows nothing about - the eight whitelisted global objects are ` && + `reached the same way, by name instead of by id.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + page->ele( `VBox` + )->a( n = `class` v = `sapUiSmallMargin` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `START` ) + )->a( n = `text` v = `Show it for 2.5 seconds` + )->a( n = `icon` v = `sap-icon://busy` + )->a( n = `type` v = `Emphasized` + + )->tag( `ObjectStatus` + )->a( n = `title` v = `Status` + )->a( n = `text` v = client->_bind( status ) + )->a( n = `class` v = `sapUiSmallMarginTop` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/01/z2ui5_cl_smp_app_515.clas.xml b/src/01/z2ui5_cl_smp_app_515.clas.xml new file mode 100644 index 00000000..48e12bc2 --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_515.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMP_APP_515 + E + Control Behaviour - The Global Busy Indicator (A) + 1 + X + X + X + + + + diff --git a/src/01/z2ui5_cl_smp_app_516.clas.abap b/src/01/z2ui5_cl_smp_app_516.clas.abap new file mode 100644 index 00000000..82d733cc --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_516.clas.abap @@ -0,0 +1,129 @@ +" @keywords inputmode soft keyboard numeric keypad barcode scanner mobile inputext bound property +" @summary Sets the HTML inputmode of an Input through the bound inputMode property of z2ui5.cc.InputExt - the keyboard layout is model data, not an action. +CLASS z2ui5_cl_smp_app_516 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA mode TYPE string. + DATA value TYPE string. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + METHODS on_event. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smp_app_516 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + mode = `numeric`. + view_display( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ELSE. + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD on_event. + + " The mode is an ordinary bound attribute, so switching the keyboard is a + " model update and nothing else - no follow-up action travels, and there + " is no ordering to get right between the action and the next render. + CASE client->get_event( ). + WHEN `NUMERIC`. + mode = `numeric`. + WHEN `DECIMAL`. + mode = `decimal`. + WHEN `TEL`. + mode = `tel`. + WHEN `NONE`. + mode = `none`. + WHEN `OFF`. + " empty leaves the field exactly as sap.m.Input rendered it + mode = ``. + ENDCASE. + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + )->a( n = `xmlns:z2ui5` v = `z2ui5.cc` ). + + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - Browser - Keyboard Layout of an Input (inputmode)` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `inputmode asks the on-screen keyboard for a layout without changing what the field IS - ` && + `numeric gives a digit pad on a field that still takes any text, and none keeps the keyboard DOWN while ` && + `the field goes on taking input, which is what a barcode scanner needs. Visible on a phone or tablet.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + page->ele( `VBox` + )->a( n = `class` v = `sapUiSmallMargin` + + )->tag( n = `InputExt` ns = `z2ui5` + )->a( n = `value` v = client->_bind( value ) + )->a( n = `inputMode` v = client->_bind( mode ) + )->a( n = `placeholder` v = `tap here on a touch device` + )->a( n = `width` v = `20rem` + + )->tag( `ObjectStatus` + )->a( n = `title` v = `inputMode` + )->a( n = `text` v = client->_bind( mode ) + )->a( n = `class` v = `sapUiSmallMarginTop sapUiSmallMarginBottom` ). + + page->ele( `HBox` + )->a( n = `class` v = `sapUiSmallMarginBegin` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `NUMERIC` ) + )->a( n = `text` v = `numeric` + )->a( n = `class` v = `sapUiTinyMarginEnd` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `DECIMAL` ) + )->a( n = `text` v = `decimal` + )->a( n = `class` v = `sapUiTinyMarginEnd` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `TEL` ) + )->a( n = `text` v = `tel` + )->a( n = `class` v = `sapUiTinyMarginEnd` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `NONE` ) + )->a( n = `text` v = `none - no keyboard` + )->a( n = `class` v = `sapUiTinyMarginEnd` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `OFF` ) + )->a( n = `text` v = `plain sap.m.Input` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/01/z2ui5_cl_smp_app_516.clas.xml b/src/01/z2ui5_cl_smp_app_516.clas.xml new file mode 100644 index 00000000..5409f774 --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_516.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMP_APP_516 + E + Browser - Keyboard Layout of an Input (inputmode) (C) + 1 + X + X + X + + + + diff --git a/src/01/z2ui5_cl_smp_app_517.clas.abap b/src/01/z2ui5_cl_smp_app_517.clas.abap new file mode 100644 index 00000000..b1d9dfbb --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_517.clas.abap @@ -0,0 +1,164 @@ +" @keywords uploadset upload drag drop multiple files base64 attachment uploadsetext companion +" @summary Takes files from a sap.m.upload.UploadSet into the backend as base64 - the invisible UploadSetExt companion reads each one, so no upload endpoint is needed. +CLASS z2ui5_cl_smp_app_517 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + TYPES: + BEGIN OF ty_s_file, + name TYPE string, + type TYPE string, + size TYPE string, + bytes TYPE i, + END OF ty_s_file. + + " the companion writes into these four, one file at a time + DATA file_name TYPE string. + DATA file_data TYPE string. + DATA file_type TYPE string. + DATA file_size TYPE string. + DATA removed TYPE string. + DATA t_received TYPE STANDARD TABLE OF ty_s_file WITH EMPTY KEY. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + METHODS on_event. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smp_app_517 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_navigated( ). + view_display( ). + ELSE. + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD on_event. + + CASE client->get_event( ). + + WHEN `FILE_ADDED`. + " The companion hands over ONE file per roundtrip - several files + " picked at once arrive one after the other, each waiting for the + " previous response. fileData is a base64 DATA URL, so the payload + " starts after the comma. + IF file_name IS NOT INITIAL. + DATA(payload) = substring_after( val = file_data + sub = `,` ). + INSERT VALUE #( name = file_name + type = file_type + size = file_size + " the decoded length, so the sample shows the bytes + " ABAP actually received rather than the base64 text + bytes = strlen( payload ) * 3 / 4 ) INTO TABLE t_received. + ENDIF. + + WHEN `FILE_REMOVED`. + DELETE t_received WHERE name = removed. + + WHEN `CLEAR`. + CLEAR t_received. + + ENDCASE. + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + )->a( n = `xmlns:upload` v = `sap.m.upload` + )->a( n = `xmlns:z2ui5` v = `z2ui5.cc` ). + + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - File - Upload with an UploadSet` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `sap.m.upload.UploadSet posts to an upload endpoint, which an abap2UI5 app does not have. ` && + `The invisible UploadSetExt companion reads every added file into bound properties instead, so the content ` && + `arrives in the same roundtrip as the event - drag files onto the list or use its uploader button.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + " The companion renders nothing and is wired to the UploadSet by id. + page->tag( n = `UploadSetExt` ns = `z2ui5` + )->a( n = `uploadSetId` v = `demoUploadSet` + )->a( n = `fileName` v = client->_bind( file_name ) + )->a( n = `fileData` v = client->_bind( file_data ) + )->a( n = `mediaType` v = client->_bind( file_type ) + )->a( n = `fileSize` v = client->_bind( file_size ) + )->a( n = `removedFileName` v = client->_bind( removed ) + )->a( n = `change` v = client->_event( `FILE_ADDED` ) ). + + page->ele( n = `UploadSet` ns = `upload` + )->a( n = `id` v = `demoUploadSet` + )->a( n = `instantUpload` b = abap_false + )->a( n = `uploadEnabled` b = abap_false + )->a( n = `class` v = `sapUiSmallMargin` ). + + DATA(table) = page->ele( `Table` + )->a( n = `items` v = client->_bind( t_received ) + )->a( n = `class` v = `sapUiSmallMargin` + )->a( n = `noDataText` v = `No file has reached the backend yet.` ). + + table->ele( `headerToolbar` + )->ele( `OverflowToolbar` + )->tag( `Title` + )->a( n = `text` v = `Received in the backend` + + )->tag( `ToolbarSpacer` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `CLEAR` ) + )->a( n = `text` v = `Clear` + )->a( n = `icon` v = `sap-icon://delete` ). + + table->ele( `columns` + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `File` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Media type` + )->end( + )->ele( `Column` + )->tag( `Text` + )->a( n = `text` v = `Bytes` ). + + table->ele( `items` + )->ele( `ColumnListItem` + )->ele( `cells` + )->tag( `Text` + )->a( n = `text` v = `{NAME}` + )->tag( `Text` + )->a( n = `text` v = `{TYPE}` + )->tag( `ObjectNumber` + )->a( n = `number` v = `{BYTES}` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/01/z2ui5_cl_smp_app_517.clas.xml b/src/01/z2ui5_cl_smp_app_517.clas.xml new file mode 100644 index 00000000..e785ab2d --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_517.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMP_APP_517 + E + File - Upload with an UploadSet (C) + 1 + X + X + X + + + + diff --git a/src/01/z2ui5_cl_smp_app_518.clas.abap b/src/01/z2ui5_cl_smp_app_518.clas.abap new file mode 100644 index 00000000..6c80632f --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_518.clas.abap @@ -0,0 +1,101 @@ +" @keywords icon font registerfont iconpool tnt collection glyph missing control_global +" @summary Registers the sap.tnt icon collection with IconPool so a sap-icon://SAP-icons-TNT/... URI resolves - without it the icon renders no glyph and logs nothing. +CLASS z2ui5_cl_smp_app_518 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + METHODS font_register. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smp_app_518 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + font_register( ). + view_display( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ENDIF. + + ENDMETHOD. + + + METHOD font_register. + + " A normal UI5 app does this in its Component's init. An abap2UI5 app has + " no Component of its own, and IconPool is a module SINGLETON rather than + " a control, so no other wire reaches it. t_arg is positional: the font + " family and the font URI - a module path in every real use, resolved + " through sap.ui.require.toUrl, so the registration survives a different + " mount point. + " + " Issue it from the init branch: the collection is registered once per + " session, so a repeat call costs nothing but says the wrong thing. + client->follow_up_action( val = z2ui5_if_client=>cs_event-control_global + t_arg = VALUE #( ( `ICON_POOL` ) + ( `registerFont` ) + ( `SAP-icons-TNT` ) + ( `sap/tnt/themes/base/fonts/` ) ) ). + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + )->a( n = `xmlns:core` v = `sap.ui.core` ). + + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - Control Behaviour - Register an Icon Font (registerFont)` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `Only the default SAP-icons font is registered out of the box. A URI naming another ` && + `collection renders NO GLYPH and logs nothing at all - an empty space where an icon should be is the ` && + `whole symptom, which is what makes this one worth knowing.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + page->ele( `VBox` + )->a( n = `class` v = `sapUiSmallMargin` + + )->tag( `Title` + )->a( n = `text` v = `From the default collection` + )->a( n = `level` v = `H3` + + )->tag( n = `Icon` ns = `core` + )->a( n = `src` v = `sap-icon://sap-ui5` + )->a( n = `size` v = `2.5rem` + )->a( n = `class` v = `sapUiSmallMarginBottom` + + )->tag( `Title` + )->a( n = `text` v = `From SAP-icons-TNT, registered above` + )->a( n = `level` v = `H3` + + )->tag( n = `Icon` ns = `core` + )->a( n = `src` v = `sap-icon://SAP-icons-TNT/application-service` + )->a( n = `size` v = `2.5rem` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/01/z2ui5_cl_smp_app_518.clas.xml b/src/01/z2ui5_cl_smp_app_518.clas.xml new file mode 100644 index 00000000..e6c3157d --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_518.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_SMP_APP_518 + E + Control Behaviour - Register an Icon Font (A) + 1 + X + X + X + + + + diff --git a/src/01/z2ui5_cl_smp_app_519.clas.abap b/src/01/z2ui5_cl_smp_app_519.clas.abap new file mode 100644 index 00000000..ca0e36e4 --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_519.clas.abap @@ -0,0 +1,95 @@ +" @keywords translation i18n text element text symbol textpool language message class multi language +" @summary Puts the screen texts in the class's own text elements instead of an i18n file, so SE63 translates them and the app shows them in the logon language. +CLASS z2ui5_cl_smp_app_519 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA name TYPE string. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + METHODS on_event. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_smp_app_519 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_navigated( ). + view_display( ). + ELSE. + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD on_event. + + IF client->get_event( ) = `GREET`. + " The text element is translated, the value is not - so the two are + " concatenated here rather than written as one literal. A text element + " with a placeholder would be a message class instead. + client->message_box_display( |{ 'Hello'(004) } { name }| ). + ENDIF. + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `displayBlock` v = `true` + )->a( n = `height` v = `100%` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ). + + DATA(page) = view->ele( `Shell` + )->ele( `Page` + )->a( n = `title` v = `abap2UI5 - Basics VII - Translatable Texts` + )->a( n = `showNavButton` b = client->check_app_prev_stack( ) + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ). + + page->tag( `MessageStrip` + )->a( n = `text` v = `A UI5 app keeps its translations in i18n files. An abap2UI5 app has no frontend ` && + `artefacts to put them in - and does not need any: the view is built in ABAP, so ABAP's own ` && + `translation carries it. Text elements here, a message class where a text takes placeholders.` + )->a( n = `type` v = `Information` + )->a( n = `showIcon` b = abap_true + )->a( n = `class` v = `sapUiSmallMargin` ). + + " Every text below comes out of the class's text pool (Goto > Text + " Elements in SE24/ADT). The literal in the source is the fallback and the + " maintenance text; what renders is the entry for the logon language. + page->ele( `VBox` + )->a( n = `class` v = `sapUiSmallMargin` + + )->tag( `Label` + )->a( n = `text` v = 'Your name'(001) + )->a( n = `labelFor` v = `nameInput` + + )->tag( `Input` + )->a( n = `id` v = `nameInput` + )->a( n = `value` v = client->_bind( name ) + )->a( n = `placeholder` v = 'Type a name here'(002) + )->a( n = `width` v = `20rem` + + )->tag( `Button` + )->a( n = `press` v = client->_event( `GREET` ) + )->a( n = `text` v = 'Greet'(003) + )->a( n = `type` v = `Emphasized` + )->a( n = `class` v = `sapUiSmallMarginTop` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/01/z2ui5_cl_smp_app_519.clas.xml b/src/01/z2ui5_cl_smp_app_519.clas.xml new file mode 100644 index 00000000..a92328f0 --- /dev/null +++ b/src/01/z2ui5_cl_smp_app_519.clas.xml @@ -0,0 +1,42 @@ + + + + + + Z2UI5_CL_SMP_APP_519 + E + Basics VII - Translatable Texts (Text Elements) + 1 + X + X + X + + + + I + 001 + Your name + 9 + + + I + 002 + Type a name here + 16 + + + I + 003 + Greet + 5 + + + I + 004 + Hello + 5 + + + + + diff --git a/src/z2ui5_cl_smp_app_000.clas.abap b/src/z2ui5_cl_smp_app_000.clas.abap index 5bbf68b7..87b13e49 100644 --- a/src/z2ui5_cl_smp_app_000.clas.abap +++ b/src/z2ui5_cl_smp_app_000.clas.abap @@ -1,5 +1,5 @@ " @keywords overview launchpad catalogue index all samples search start tiles -" @summary Every sample in this repository as a searchable list, grouped along the learning path - the app the other 113 are reached from. +" @summary Every sample in this repository as a searchable list, grouped along the learning path - the app the other 120 are reached from. CLASS z2ui5_cl_smp_app_000 DEFINITION PUBLIC. PUBLIC SECTION. @@ -730,6 +730,7 @@ CLASS z2ui5_cl_smp_app_000 IMPLEMENTATION. keywords = `developer tools devtools ctrl f12 debug inspect payload previous request response view xml view model source code log error adt export` path = `src/01` app = `z2ui5_cl_smp_app_496` ) ( group = `Start here` header = `Basics VI` sub = `Unit Tests for the App Logic` keywords = `unit test abapunit testclasses assert testable logic method` path = `src/01` app = `z2ui5_cl_smp_app_503` ) + ( group = `Start here` header = `Basics VII` sub = `Translatable Texts (Text Elements)` keywords = `translation i18n text element text symbol textpool language message class multi language` path = `src/01` app = `z2ui5_cl_smp_app_519` ) ( group = `Get your data on screen` header = `Binding` sub = `A View Built From RTTI, No Field Named` keywords = `rtti generic view runtime columns get_components describe_by_data no field name itab structure column cell binding` @@ -862,6 +863,10 @@ CLASS z2ui5_cl_smp_app_000 IMPLEMENTATION. keywords = `clipboard paste copy text area` intro = `Where an app stops being only a view: the browser it runs in, the device it runs on, files in and out, custom CSS, and driving a control from the backend by its id.` path = `src/01` app = `z2ui5_cl_smp_app_325` ) + ( group = `Reach outside the view` header = `Browser` + sub = `Keyboard Layout of an Input (inputmode) (C)` + keywords = `inputmode soft keyboard numeric keypad barcode scanner mobile inputext bound property` + path = `src/01` app = `z2ui5_cl_smp_app_516` ) ( group = `Reach outside the view` header = `Browser` sub = `Local and Session Storage (A,C)` keywords = `localstorage sessionstorage persist store_data offline` path = `src/01` app = `z2ui5_cl_smp_app_327` ) ( group = `Reach outside the view` header = `Browser` sub = `Logout from the Client (A)` keywords = `logoff signout icf session end fiori launchpad` path = `src/01` app = `z2ui5_cl_smp_app_361` ) ( group = `Reach outside the view` header = `Browser` sub = `Open a URL in a New Tab (A)` keywords = `url window open_new_tab link target` path = `src/01` app = `z2ui5_cl_smp_app_073` ) @@ -870,10 +875,14 @@ CLASS z2ui5_cl_smp_app_000 IMPLEMENTATION. ( group = `Reach outside the view` header = `Browser` sub = `Set the Tab Favicon (A)` keywords = `favicon icon tab image data uri` path = `src/01` app = `z2ui5_cl_smp_app_491` ) ( group = `Reach outside the view` header = `Browser` sub = `Set the Tab Title (A)` keywords = `document.title tab caption headline set_title` path = `src/01` app = `z2ui5_cl_smp_app_125` ) ( group = `Reach outside the view` header = `Browser` sub = `Soft Keyboard Mode on Mobile (A)` keywords = `mobile numeric keypad keyboard_set_mode phone input` path = `src/01` app = `z2ui5_cl_smp_app_352` ) + ( group = `Reach outside the view` header = `Control Behaviour` sub = `Aggregation Item by Index (A)` keywords = `carousel aggregation item index clone template setactivepage positional control_by_id` path = `src/01` app = `z2ui5_cl_smp_app_514` ) ( group = `Reach outside the view` header = `Control Behaviour` sub = `Expand a Panel by ID (setExpanded) (A)` keywords = `panel collapse expand setexpanded control_by_id whitelisted` path = `src/01` app = `z2ui5_cl_smp_app_448` ) + ( group = `Reach outside the view` header = `Control Behaviour` sub = `Inline CSS on a Control (css) (A)` keywords = `css inline style background color opacity control_by_id dom node no property` path = `src/01` app = `z2ui5_cl_smp_app_513` ) ( group = `Reach outside the view` header = `Control Behaviour` sub = `MultiInput with Tokens (C)` keywords = `multiinput token tokens suggestion custom control` path = `src/01` app = `z2ui5_cl_smp_app_078` ) ( group = `Reach outside the view` header = `Control Behaviour` sub = `Open the PDF Viewer by ID (A)` keywords = `pdfviewer pdf document viewer popup control_by_id whitelisted` path = `src/01` app = `z2ui5_cl_smp_app_449` ) + ( group = `Reach outside the view` header = `Control Behaviour` sub = `Register an Icon Font (A)` keywords = `icon font registerfont iconpool tnt collection glyph missing control_global` path = `src/01` app = `z2ui5_cl_smp_app_518` ) ( group = `Reach outside the view` header = `Control Behaviour` sub = `Switch NavContainer Page by ID (A)` keywords = `navcontainer icontabbar icontabheader page switch control_by_id whitelisted` path = `src/01` app = `z2ui5_cl_smp_app_088` ) + ( group = `Reach outside the view` header = `Control Behaviour` sub = `The Global Busy Indicator (A)` keywords = `busy indicator global control_global show hide blocking wait spinner long running` path = `src/01` app = `z2ui5_cl_smp_app_515` ) ( group = `Reach outside the view` header = `Control Behaviour` sub = `Wizard with Steps (A)` keywords = `wizard step branching discardprogress setnextstep control_by_id` path = `src/01` app = `z2ui5_cl_smp_app_202` ) ( group = `Reach outside the view` header = `CSS` sub = `Color Table Cells from the Backend` keywords = `color background conditional formatting style data attribute` path = `src/01` app = `z2ui5_cl_smp_app_305` ) ( group = `Reach outside the view` header = `CSS` sub = `FlexBox Layouts with Custom Classes` keywords = `flexbox layout responsive navigation tile panel` path = `src/01` app = `z2ui5_cl_smp_app_255` ) @@ -886,7 +895,8 @@ CLASS z2ui5_cl_smp_app_000 IMPLEMENTATION. path = `src/01` app = `z2ui5_cl_smp_app_122` ) ( group = `Reach outside the view` header = `Device` sub = `Geolocation from the Browser (C)` keywords = `gps position latitude longitude altitude location` path = `src/01` app = `z2ui5_cl_smp_app_120` ) ( group = `Reach outside the view` header = `File` sub = `Download to the Browser (A)` keywords = `export save base64 attachment xstring document` path = `src/01` app = `z2ui5_cl_smp_app_186` ) - ( group = `Reach outside the view` header = `File` sub = `Upload to the Backend (C)` keywords = `fileuploader base64 attachment import picture document` path = `src/01` app = `z2ui5_cl_smp_app_074` ) ). + ( group = `Reach outside the view` header = `File` sub = `Upload to the Backend (C)` keywords = `fileuploader base64 attachment import picture document` path = `src/01` app = `z2ui5_cl_smp_app_074` ) + ( group = `Reach outside the view` header = `File` sub = `Upload with an UploadSet (C)` keywords = `uploadset upload drag drop multiple files base64 attachment uploadsetext companion` path = `src/01` app = `z2ui5_cl_smp_app_517` ) ). ENDMETHOD.