<!-- https://abap2ui5.github.io/docs/advanced/insights/12-where-your-own-javascript-goes -->

# #12 Where Your Own JavaScript Goes

Sooner or later an app needs something the XML view cannot express. A chart
library. A control nobody has wrapped. A UI5 method that exists only as a
method — `sap.m.Carousel` moves through `setActivePage( )` and through nothing
else, and no property will do it for you.

This is the moment where frameworks usually grow a plugin system, and then a
plugin registry, and then a page of documentation about lifecycle hooks.
abap2UI5 has none of that, deliberately. It has three seams, each one a
declared place rather than an escape hatch.

![Three seams, from the cheapest to the widest: a method call by control id, an expression in the view, a custom control in its own BSP.](/insights/12-three-seams.svg)

*Three seams, from the cheapest to the widest: a method call by control id, an expression in the view, a custom control in its own BSP.*

**An imperative method needs no JavaScript at all.** The client can call one on
a control by id:

```abap
    " t_arg is positional: control id, method, parameters
    client->follow_up_action( val   = z2ui5_if_client=>cs_event-control_by_id
                              t_arg = VALUE #( ( `myCarousel` )
                                               ( `setActivePage` )
                                               ( `page2` ) ) ).
```

The whitelist decides what is reachable. Check it before writing anything —
a method it already declares costs one call, and an argument it does not
declare is dropped in silence — a debugging session you can skip by reading a
list first.

**A custom control lives in its own BSP.** The frontend resolves two reserved
resource roots — `z2ui5_cci` for the custom-controls addon, `z2ui5_ccc` for a
customer's own extension — so a control loads through the UI5 loader like any
other module, not as a string smuggled through a view.

**A view attribute can compute.** UI5 expression binding is available in an
app view — `{= ${STATUS} === 'E' ? 'Error' : 'None' }` — evaluated in the
browser against the model that just arrived, with no roundtrip and no module to
load. Write it as a backtick literal rather than a string template: a template
has to escape every brace, and one missed escape is a parser error on the whole
statement instead of a wrong string. Ask how we know.

**Everything else is a system decision, not an app decision.** Extra JavaScript
for the initial page is `custom_js` in the HTTP GET configuration, set in the
[user exit](/advanced/extensibility/user_exits) through `z2ui5_if_ui5_exit` —
one place, reviewable, and the same for every app in the system.

None of them lets an app change the framework, and none requires the framework
to change for an app. No plugin registry to learn, and no pull request to wait
for either.

Extensibility is not the absence of a boundary. It is knowing exactly where
the boundary is.
