Eleven ready-to-use custom controls for abap2UI5 — signature pad, charts, barcodes, Excel export, form validation, product tours, Font Awesome, animations, clickable image maps, Markdown and a code editor.
They ship in their own BSP (Z2UI5_CCI), not inside the framework. Install this
repository and the controls are there; nothing in abap2UI5 or in the frontend BSP
has to change, and no pull request against the framework is needed to add one.
- Install this repository with abapGit. It brings the ABAP classes, the BSP
application
Z2UI5_CCIand the two ICF nodes it is served from. - Start
?app_start=z2ui5_cl_cci_sample_00— the overview app lists every control and opens its sample.
Requires abap2UI5 with the reserved resourceRoot z2ui5_cci in the frontend
manifest (see Troubleshooting if a control stays blank).
On a system whose browsers have no internet access, install the local branch
instead — see Branches.
Every control has a builder class with one render( ) method. Declare the
namespace once per view, then add controls like any other:
DATA(view) = z2ui5_cl_ui5_view_builder=>factory( ).
DATA(root) = view->ele( n = `View`
ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ).
z2ui5_cl_cci=>xmlns( root ). " declares xmlns:z2ui5_cci - once per view
DATA(page) = root->ele( `Page`
)->a( n = `title` v = `Signature` ).
z2ui5_cl_cci_signature_pad=>render(
view = page
value = client->_bind( signature ) " the base64 PNG arrives here
height = `200px`
change = client->_event( `SIGNED` ) ).
client->view_display( view->stringify( ) ).That is all — properties bind, values are written back into your ABAP variables
and events arrive in on_event like for any built-in control.
| Control | What it does | Builder | Sample |
|---|---|---|---|
| SignaturePad | sign with mouse, finger or stylus → base64 PNG | z2ui5_cl_cci_signature_pad |
..._sample_01 |
| ExportSpreadsheet | export a table's rows as .xlsx, in the browser |
z2ui5_cl_cci_spreadsheet |
..._sample_02 |
| Validator | check a form against ABAP rules without a roundtrip | z2ui5_cl_cci_validator |
..._sample_03 |
| ChartJs | Chart.js charts from a bound structure | z2ui5_cl_cci_chartjs |
..._sample_04 |
| Barcode | barcodes and QR codes with bwip-js | z2ui5_cl_cci_barcode |
..._sample_05 |
| DriverJs | product tours and spotlights with driver.js | z2ui5_cl_cci_driverjs |
..._sample_06 |
| FontAwesome | Font Awesome as UI5 icons and CSS classes | z2ui5_cl_cci_font_awesome |
..._sample_07 |
| AnimateCss | animate.css class names on any control | z2ui5_cl_cci_animate_css |
..._sample_08 |
| ImageMapster | clickable, highlighting regions on an image | z2ui5_cl_cci_imagemapster |
..._sample_09 |
| Markdown | Markdown from ABAP as HTML, with marked | z2ui5_cl_cci_markdown |
..._sample_10 |
| CodeEditor | UI5's own sap.ui.codeeditor, reachable from a view |
z2ui5_cl_cci_code_editor |
inside ..._sample_10 |
Sample classes are z2ui5_cl_cci_sample_NN — start any of them directly with
?app_start=…, or browse them from z2ui5_cl_cci_sample_00, which lists one
row per demo along with the third-party library each control needs. CodeEditor
has no row there because it has no demo of its own: it is the editor on the
left of the Markdown demo.
value (base64 PNG, bind two-way), width, height, linewidth, linecolor,
editable · event change.
The value is published on pointer-up, not per mouse move. To clear the pad, set the bound variable to empty.
tableid, columns (a ty_t_column table), filename, sheetname, text,
icon, type, tooltip, enabled, status · event exported.
Reads the bound table in the browser, so the data makes no second trip to the
backend. Needs SAPUI5 — under OpenUI5 sap.ui.export is missing and the button
renders disabled.
rules (a ty_t_rule table), trigger, valid, errors · event validated.
Rule fields: field, type (number/integer), format (email/date),
pattern, required, minlength, maxlength, minimum, maximum,
message. No external library — works without internet access.
config (a ty_chart structure), width, height, plugins, liburl ·
event elementpress.
config is the Chart.js configuration verbatim, so anything the Chart.js docs
describe works from ABAP. Change the structure and the chart updates in
place — the framework pushes the changed model on its own. plugins takes the names from
z2ui5_cl_cci_chartjs=>cs_plugin (datalabels, autocolors, deferred,
annotation, venn, wordcloud).
bcid, text, alttext, scale, height, includetext, textalign,
rotate, backgroundcolor, options, renderas (canvas/svg), liburl ·
event error.
z2ui5_cl_cci_barcode=>get_types( ) returns a handful of symbologies with
values that encode cleanly. Leave height empty for 2D codes — a fixed height
squashes a QR code.
config (a ty_s_config structure), highlight, mode, trigger,
customcss, liburl, cssurl · events highlighted, done.
A step's element is the control id from your view, not a CSS selector; it
is resolved in the frontend, so a step may point into a nested view or a dialog.
fonturi, collections, cssurl.
One element in the view and Font Awesome is available two ways: as UI5 icons
(sap-icon://fa-solid/heart in any icon property) and as its own CSS classes
(class="fa-brands fa-github"). fonturi points at a directory holding the font
and the metadata JSON the UI5 IconPool needs.
duration, delay, repeat, cssurl.
Put animate__animated animate__bounce into a control's class attribute and it
animates. The class names are constants on z2ui5_cl_cci_animate_css
(cs_base, cs_attention-*, cs_entrance-*, cs_exit-*, cs_modifier-*).
duration/delay/repeat retune every animation on the page at once.
src, areas (a ty_t_area table), config, selectedkeys (bind two-way),
width, height · event areapress.
Turns a floor plan or a machine drawing into an input control: regions highlight,
the selection is written back into the model and a click raises a backend event
carrying the region key. Colours are hex without a leading #.
The regions are an SVG overlay whose viewBox is the image's natural pixel
space — the same space the HTML image map coords are written in — so the
browser scales them with the image. No library, nothing to resize, and every
region is a real focusable element, so the map is keyboard reachable.
It used to be the jquery.imagemapster plugin drawing on a canvas, which was the
only reason anything in this repository needed jQuery. Three parameters survive
that change as no-ops, kept so callers that pass them still compile:
liburl and autoresize on render( ), and scale_map in ty_s_config.
value (the Markdown source), sanitize, breaks, gfm, width, height,
liburl, purifyurl · event linkpress.
UI5 has no Markdown control. sap.m.FormattedText takes HTML and whitelists
only a few tags, so anything with a heading, a table and a code block has to be
assembled as HTML in ABAP — bind a Markdown string instead. Useful for long
texts, release notes, help pages and LLM answers, which come out of every model
as Markdown already.
Bind the same attribute to a TextArea and to this control and the preview
follows every keystroke without a roundtrip.
The rendered HTML goes through DOMPurify
unless sanitize is set to false. Leave it on for anything a user typed or a
model produced: Markdown carries raw HTML through, and unsanitized that HTML
runs with the user's session. A link whose href starts with # does not
navigate — it raises linkpress with the href, which is how a help text links
into the app it documents. Every other link opens in a new tab.
Five controls take a whole configuration as one property (Chart.js config, driver.js steps, ImageMapster options, workbook columns, validation rules). Bind those with this library's JSON filter — and with the camelCase mapper where the target library expects camelCase names:
config = client->_bind(
val = ms_chart
custom_filter = NEW z2ui5_cl_cci_json_filter( )
custom_mapper = z2ui5_cl_ajson_mapping=>create_camel_case(
iv_first_json_upper = abap_false ) )ABAP has no "unset" for a structure component: fields you never touched still
serialize as "", 0 or false, and borderWidth: 0 draws no border. The
filter drops initial values so the library's own defaults survive. The flip side:
a value that is meaningfully zero, empty or false cannot be sent this way.
Six controls wrap a library that is not part of UI5 — Chart.js 4, bwip-js 4, driver.js 1, Font Awesome 6, animate.css 4, and marked 12 together with DOMPurify 3. Each control loads its library on first use, cached per URL, so ten charts on a page fetch Chart.js once.
The other five need nothing: SignaturePad and Validator are self-contained, ExportSpreadsheet and CodeEditor use libraries the UI5 distribution already carries, and ImageMapster draws its own SVG overlay.
Where it loads it from is what separates the two branches: on main from
jsDelivr, on local from this BSP. Nothing in the repository spells a URL out
— tools/libs.json names the npm package and tools/vendor.mjs generates
app/webapp/cc/LibUrls.js from it and from the version in package.json, in
one shape or the other. So the version a browser downloads is the version this
repository was tested against, and a version bump is an edit to package.json
plus npm run vendor, never a hand-written URL.
A single library can still be redirected per control, on either branch, with the
liburl / cssurl property:
z2ui5_cl_cci_chartjs=>render( view = page config = … liburl = `/sap/bc/ui5_ui5/sap/z2ui5_cci/lib/chart.umd.js` ).main is where development happens; local is generated from it on every
push and force-pushed by CI. Never develop on it — a commit made there is gone
with the next run. Install it with abapGit exactly like main.
| Branch | What it is | Install it when |
|---|---|---|
main |
the sources, ABAP ≥ 7.50, libraries from jsDelivr | the default |
local |
the same, with every library vendored into the BSP | the browsers have no internet access |
npm run build:local copies every library out of node_modules into
app/webapp/lib/ and regenerates the BSP, which grows to about 3.5 MB. The
files are the upstream ones byte-for-byte, with two exceptions. The first is
that lines are wrapped. A BSP page is stored as 255-character lines, so a minified bundle
would be chopped at character 256 — in the middle of an identifier as often as
not — and the file the system serves back would no longer be the file that went
in. tools/wrap-lines.mjs inserts newlines only where the JavaScript and CSS
grammars treat them as whitespace, and npm test proves it by re-parsing every
vendored library and comparing its syntax tree against the original's.
The second is that the trailing //# sourceMappingURL= comment is removed. The
.map files are developer tooling and are not vendored, so the pointer would
only make a browser with devtools open request a page the BSP does not have —
and a 404 next to a custom control is exactly the symptom someone loses an
afternoon to. Nothing else in the branch reaches outside the SAP system: the
libraries carry no absolute URL they load from (the http links in them are
banner comments and marked's autolink prefix), the stylesheets have no
@import and no url() other than the inlined fonts, and none of them opens
an XHR, a fetch or a script tag of its own.
UI5 itself is a separate question and lives outside this repository.
abap2UI5 bootstraps from https://sdk.openui5.org/... unless told otherwise,
so an offline system also has to point cs_config-src at a local
distribution — see z2ui5_cl_ui5_user_exit in the framework. That is what serves
sap.ui.export and sap.ui.codeeditor too.
Two consequences worth knowing before installing it:
- Font Awesome's CSS classes work, its UI5 IconPool collections do not. The
stylesheet carries the webfonts inline as base64, so
class="fa-solid fa-heart"renders offline.sap-icon://fa-solid/heartneeds the fonts as real files in a directory, plus the metadata JSON that maps icon names to code points — and a BSP page is a text object, so neither can ship here. Put that bundle in a MIME repository or a BSP of your own and pass the directory asfonturito switch the IconPool half back on. - ExportSpreadsheet and CodeEditor were never affected, and still are not: both use libraries out of the UI5 distribution rather than a CDN.
| Symptom | Cause |
|---|---|
ICF Node NOT found! |
the SICF nodes were not activated — activate z2ui5_cci in transaction SICF |
control stays blank, 404 on cc/<Name>.js |
check /sap/bc/ui5_ui5/sap/z2ui5_cci/cc/SignaturePad.js returns JavaScript |
control stays blank, request goes to resources/… |
your abap2UI5 frontend predates the reserved resourceRoot z2ui5_cci; update it |
In the browser console, sap.ui.require.toUrl("z2ui5_cci/cc/SignaturePad.js")
must return the BSP path. That separates a BSP problem from a frontend problem,
which look identical from inside the app.
- write
app/webapp/cc/<Name>.js, extendingsap.ui.core.Controlunderz2ui5_cci.cc.<Name>, with no dependency onz2ui5/…modules - wraps a third-party library? add it to
package.jsonwith an exact version and totools/libs.json, thennpm run vendor— the control reads its URL fromz2ui5_cci/cc/LibUrls, never from a literal, so it works onmainand onlocalwithout a second code path - run
npm run app2bsp— regenerates the BSP artefacts undersrc/01 - add a builder class
z2ui5_cl_cci_<name>next to the others - add a sample and a row in
z2ui5_cl_cci_sample_00=>model_init( )
File names under app/webapp become BSP page names and SAP validates them: at
most one directory level, and letters, digits, _ and . only. app2bsp
refuses anything else — otherwise you find out on import, as
CREATE_NEW_PAGE sy-subrc=2 (invalid_name).
| Path | What it is |
|---|---|
app/webapp/cc/*.js |
the controls — plain UI5, the single source of truth |
app/webapp/cc/MapShapes.js |
the image-map geometry, split out so it can be unit-tested |
app/webapp/cc/LibUrls.js |
generated: where each control loads its library from |
app/webapp/lib/ |
generated, local branch only: the vendored libraries |
tools/libs.json |
the third-party libraries — npm package, file, CDN URL |
tools/vendor.mjs |
writes LibUrls.js, and app/webapp/lib/ with --local |
tools/wrap-lines.mjs |
breaks a library into lines a BSP page can carry |
tools/*.test.mjs |
unit tests — here, not under app/, where they would become BSP pages |
tools/app2bsp.mjs |
generates the abapGit BSP artefacts from app/webapp |
src/z2ui5_cl_cci*.clas.abap |
the library and one view builder per control |
src/00/ |
the overview app and the samples |
src/01/ |
generated: the Z2UI5_CCI BSP and its ICF nodes |
Every ABAP object of this repository lives in the z2ui5_xx_cci namespace
(z2ui5_cl_cci, z2ui5_cl_cci_<control>, z2ui5_cl_cci_sample_NN) — the same
one-token repository prefix the samples repository uses with z2ui5_xx_smp.
The frontend carries the same token: z2ui5_cci is the resourceRoot the
abap2UI5 frontend reserves in its manifest.json, the BSP Z2UI5_CCI and the
UI5 module namespace z2ui5_cci.cc. It needs an abap2UI5 that reserves that
root; frontends predating the rename reserve z2ui5ccc (and older ones
z2ui5cc) and cannot resolve the controls.
CI runs abaplint against the abap2UI5 framework in both syntax versions,
syntax-checks every control, runs the line-wrapper tests, builds the local
variant, and fails if any generated artefact — the BSP under src/01 or
LibUrls.js — has drifted from its source.
This repository is for controls worth sharing. For a customer's own
frontend artefacts — an in-house reuse library, a corporate icon font, company
CSS — use
abap2UI5/customer-frontend-extension.
It is the same mechanism under a second reserved resourceRoot (z2ui5ext
instead of z2ui5_cci), so the two can be installed side by side and neither
needs a change to abap2UI5.
These controls replace the
abap2UI5-addons/js-libraries-obsolet
and
abap2UI5-addons/custom-controls-obsolet
repositories, which shipped the same JavaScript as ABAP string literals injected
into every view. Each control's JS file names the class it came from and lists
what changed. Favicon and MessageManager were not ported — abap2UI5 carries them
itself (z2ui5_cl_xml_view_cc=>favicon( ) / =>message_manager( )).