Skip to content

The LaravelUi5 facade

LaravelUi5 is the single object your UI5 code talks to. It calls actions, reads endpoints, answers questions about the current context, and — on an SDK host — reaches the shell. One singleton, shared across the app, so nothing has to be passed around.

The name is borrowed from Laravel's Facade pattern for the same reason: a static entry point in front of machinery you would otherwise have to wire by hand — the component, the base URL, the CSRF token, the manifest lookups.

It ships in the com.laravelui5.core UI5 library, which Core bundles into its own resources/. There is nothing to install in your front end.

Initialization

Initialize the facade once, in your Component.ts:

ts
import UIComponent from "sap/ui/core/UIComponent";
import LaravelUi5 from "com/laravelui5/core/LaravelUi5";

export default class Component extends UIComponent {

    public static metadata = {
        manifest: "json",
        interfaces: ["sap.ui.core.IAsyncContentCreation"]
    };

    public init(): void {
        super.init();

        // …your own setup…

        // Arm the facade (connection, CSRF, session-expiry guard, route and
        // settings models) BEFORE routing starts: the first view may bind
        // immediately, and a binding that fires against an unarmed facade has
        // no base URL and no token.
        LaravelUi5.init(this).then(() => {
            this.getRouter().initialize();
        }).catch((error: unknown) => {
            console.error(error);
        });
    }
}

That is the whole integration, and the order in it is the point: arm, then route. The facade must never be instantiated anywhere else — it is a singleton, and a second init() would replace the connection the running views are already bound to.

init() reads the app's manifest.json: the OData service URI becomes the connection's base URL, the laravel.ui5 block supplies the action and resource addresses and the app's declared settings, and the routes and meta models are attached to the component. It also arms the session guard, so an expired session surfaces as a re-authentication bounce instead of a parse error inside a data binding.

On an SDK host the LeanShell replaces this initialization and hands the facade a live shell. Your Component.ts does not change.

Types come from npm, the runtime does not

The facade is authored in JavaScript with hand-written declaration files — see TypeScript Strategy in the library's README for why. Install the package to get IntelliSense and type checking:

bash
npm install @laravelui5/core --save

At runtime the library comes from Core's own bundle, served under the app's resource roots. The npm package is for your editor and your build, not for the browser — nothing from node_modules is shipped.

Anything that runs after initialization can wait for it:

ts
await LaravelUi5.ready();

In Core that resolves immediately. On an SDK host it resolves when the shell has finished booting, so it is the safe place to read the actor or a setting.

One exception: assembled, self-contained apps

An app produced by ui5:assemble loads the facade through sap.ui.require() inside init() rather than importing it at the top. That shape belongs to the self-contained bundle, not to a normal app — the generated Component.js carries it, and the reasoning is on that page.

Calling an action

Every write goes through an action, and actions are addressed by their full namespace — the key under which the manifest lists them:

js
await LaravelUi5.call("com.acme.invoicing.actions.approve", { invoice: 42 });

The second argument fills the route parameters the action declares: each {name} placeholder in the registered URL is replaced, and a missing one is an error rather than a malformed request. The third argument is the request body, the fourth an optional form model whose bindings light up when the backend answers 422.

A short name does not work — call("approve", …) throws Unknown action 'approve'. The namespace is the identifier; that is what makes an action addressable across modules.

Reaching endpoints

For everything that is not an action, the facade wraps the HTTP layer with the CSRF token and the base URL already applied:

js
const data = await LaravelUi5.get("/ui5/resource/com/acme/invoicing/resources/[email protected]");

get, post, put, patch and delete take a path; fetchXml, fetchHtml and fetchEntitySet return the shapes their names suggest. The addresses of your app's resources are in the manifest's laravel.ui5/resources map — see Manifest Fragments.

getMainServiceUri() and getBaseUrl() give you the OData service and the host root when you need to build something yourself.

What the facade answers, and what it needs a shell for

This is the part worth reading before you design around it. The facade has one surface, but some of it is brokered by the shell — and a shell is an SDK capability. Core answers those calls with a defined fallback rather than an error, so the same app runs on both, and so a Core-only host degrades predictably:

MethodOn CoreOn an SDK host
can(ability)true — Core is auth-blindthe actor's real grant
getActor() · getPrincipal()nullthe signed-in actor / the principal behind an impersonation
getSetting(key)the app's declared default, from the manifestthe stored, scope-resolved value
getClient()nullthe tenant client
dispatchIntent(intent)resolves, does nothingrouted by the shell
openValueHelp(options)rejects — there is no return channelopens the value help and resolves with the pick
showHelp(uuid)no-opopens the help viewer
getWeave()[]the current app's outbound doorways
attach / detachno-opshell events, e.g. context:changed
log(event, payload, level)console.logthe shell's log channel
exportTable(config)404 — Core serves no export routestreams the table as CSV
call, get/post/…, ready, initfull functionsame

can() is not a gate

On Core it answers true for every ability, because Core knows nothing about users. Use it to hide a button, never to protect an operation. The server decides: an action is gated by #[Act], an artifact by #[Access], and both refuse regardless of what the client believed.

openValueHelp() is the one call that fails loudly instead of degrading, and deliberately: a value help that silently returns nothing would look like a user cancelling. If your app needs one, it needs an SDK host — as does the other end of that conversation, confirmValueHelp() and cancelValueHelp(), which the picking app calls to hand the selection back.

exportTable() is the second SDK-only call, and it fails differently: the request simply finds no route on a Core host. The endpoint belongs to the SDK (Export).

Messages and side effects

call() carries three channels on the response body, all inert against a backend that sends none of them: validation errors from a 422 become binding paths on the form model you passed, so bound controls show their own error state; business messages land in the global message model; and the OData paths an action reports as changed trigger a refresh of the main model, so live reads reconcile without you wiring anything.

createMessagePopover() gives you the control that displays the collected messages.

  • Manifest Fragments — the laravel.ui5 block the facade reads: actions, resources, settings, routes.
  • Actions — the other end of call().
  • <lux:Chart> — the control for a Chart artifact.
  • Architecture — why the front end sits inside the module.