Global Dialogs
A dialog in LaravelUi5 is not a fragment you keep in a controller. It is an artifact: a declared, named, authorized thing that the shell opens by namespace — from your own app, from another app, or from the command palette, without either side knowing the other's views.
LaravelUi5.dispatchIntent({
name: 'ui5-artifact.open',
parameters: { namespace: 'com.acme.sales.dialogs.create_customer' },
});That is the whole call site. No fragment path, no Fragment.load, no lifecycle bookkeeping — the shell creates the view, checks the actor's permission on the server first, opens it, and destroys it after close.
What you write
Four files: a PHP artifact, a line in your module, and the view and controller pair.
The artifact
namespace App\Sales\Dialogs;
use App\Sales\SalesRole;
use LaravelUi5\Sdk\Security\Attributes\Access;
use LaravelUi5\Sdk\Ui5\AbstractUi5Dialog;
#[Access(ability: 'createCustomerDialog', role: SalesRole::Sales, note: 'Open the create-customer dialog.')]
class CreateCustomerDialog extends AbstractUi5Dialog
{
public const NAMESPACE = 'com.acme.sales.dialogs.create_customer';
public const VERSION = '1.0.0';
public const TITLE = 'Create Customer';
public const DESCRIPTION = 'Creates a customer record.';
public const VIEW = 'com.acme.sales.view.dialog.CreateCustomer';
}Five constants and nothing else. AbstractUi5Dialog has no abstract method to implement: it answers getType() with ArtifactType::Dialog and getViewName() with your VIEW, and the identity getters read the constants. A missing constant is a runtime Error on first use, not a compile error — so write all five.
VIEW is the UI5 view name in dotted notation, the same string you would hand XMLView.create.
Registering it
Dialogs are not discovered automatically. Declare them on your module:
public function getDialogs(): array
{
return [
new Dialogs\CreateCustomerDialog($this),
];
}There is no ui5:dialog generator. Write the four files by hand — a dialog is small enough that a scaffold would not save much, and dialogs share that with value helps: neither has a stub.
The view
The view's single root control must be a sap.m.Dialog. The shell enforces that at open time and throws otherwise, and it also throws if the view has more than one root control.
<mvc:View
controllerName="com.acme.sales.controller.dialog.CreateCustomer"
xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" xmlns:f="sap.ui.layout.form">
<Dialog title="{i18n>createCustomer.title}">
<f:Form>
<!-- … -->
</f:Form>
<beginButton>
<Button text="{i18n>save}" type="Emphasized" press=".onSave"/>
</beginButton>
<endButton>
<Button text="{i18n>cancel}" press=".onCancel"/>
</endButton>
</Dialog>
</mvc:View>The controller comes from controllerName — the artifact names only the view.
The controller, and the one hook
Your controller is an ordinary one. If it defines a method named initDialog(dialog), the shell calls it once, on afterOpen, with the dialog instance. That is the place to reset a form model or focus a field.
initDialog(oDialog) {
this.getView().setModel(new JSONModel({ name: '' }), 'form');
}Closing destroys both the dialog and its view. Every open starts from a clean instance, so you never have to reset state on the way in — but also cannot keep it between opens.
Who may open it
#[Access] on the artifact class is the gate, and it is checked on the server before the dialog's view is ever created — the open intent's authorizer resolves the ability and asks the actor's grants.
| Situation | Result |
|---|---|
the dialog declares no #[Access] | anyone in the shell may open it |
| the actor holds the ability | the dialog opens |
| the actor does not hold it | a deny result; no view is built |
the ability exists in code but ui5:sync has not run | refused — an unresolved ability is treated as no |
The ability is catalogued under the module's namespace, like every other ability, and its role must be declared with #[Role] on the module (Permission Levels). Run ui5:sync after adding the attribute, then grant the role.
A dialog is a gate you can actually use
Unlike an app, a dialog is a small, sharply-scoped thing to put an ability on: may this actor open the create-customer form. The write behind Save carries its own #[Act], so the two decisions stay separate — hiding the door and locking the safe.
What happens on open
- Your app dispatches
ui5-artifact.openwith the dialog's namespace. - The server authorizes, resolves the artifact, and answers with a
dialogresult carrying the owning app's namespace and the view name. - The shell decides where to host it:
- your own app — the view is created in your component's context, so your models, OData bindings and i18n are all there;
- another app's dialog — the shell loads that app as a hidden component, opens the dialog from it, and destroys the component after close. The dialog is rendered with the owning app's models, which is the point: the dialog belongs to the module that knows the business object.
Opening a dialog from a foreign app needs nothing from you as the caller. You name a namespace.
If your app is the one being hosted
When another app opens your dialog, your component starts with componentData.mode === 'dialog'. Your Component.init must honour that and skip getRouter().initialize() — a router that starts in a hosted component navigates and tears the just-opened dialog down. The base component passes the flag but does not act on it; that check belongs to your app.
Saving
A dialog's Save is a mutation, and mutations are actions — OData stays read-only:
await LaravelUi5.call('com.acme.sales.actions.create_customer', {}, formData);The action carries its own #[Act] ability, its own FormRequest and its own transaction. See Mutating Actions.
What it doesn't do
- No URL. A dialog is not routable; there is no deep link to an open dialog. If a user must be able to link to it, it is a route in an app, not a dialog.
- No shell without the SDK. On Core alone
dispatchIntentdoes nothing, silently. Dialogs are a LeanShell capability. - No return value. A dialog is fire-and-forget. A dialog that hands a selection back to its caller is a value help — a different artifact type, with a promise.
- No generator, no auto-discovery, as above.
See also
- Value Helps: the dialog's sibling, with a return channel
- Intent Dispatch: the open path in the wider catalogue
- Mutating Actions: the write behind Save
- Permission Levels: where the ability comes from