Skip to content

Ui5Card

Introduction

The Ui5Card is a UI5 artifact that wraps a sap.ui.integration Integration Card — a compact, embeddable element such as a KPI, list, or object preview. Each card is a small PHP class declaring its identity and wiring a data provider, backed by a Blade-rendered manifest that lives in the card's own self-contained folder (alongside an optional i18n bundle).

A card is read-only — it visualizes data, never mutates state — and it can be consumed two ways: served standalone at its own endpoint, or embedded in a Ui5Dashboard. Both paths render the same manifest.

Conceptual Overview

The two render paths

A single card class serves both of these without change:

  • StandaloneGET card/{ns}@{ver}/manifest.json returns the Blade-rendered manifest (the provider's array is available to the template as $data). The UI5 Integration Card framework fetches and renders it.
  • Embedded in a dashboard — the dashboard's emitter builds the same manifest URL (with bound slot values appended as query params) and hands it to the card widget via getCard(). The widget fetches the manifest from the standalone endpoint above.

Structure — a self-contained card folder

Each card owns a self-contained folder — a canonical sap.ui.integration card package:

resources/ui5/cards/{slug}/
├── manifest.json.blade.php   # the manifest, Blade-rendered to JSON
└── i18n/                      # optional translation bundle
    ├── i18n.properties
    └── i18n_de.properties     # locale variants, as needed

The .blade.php suffix keeps editors treating the template as Blade; the URL-served resource is still manifest.json. Cards are sandboxed: a card resolves its {i18n>…} bindings against its own bundle (served card-relative at card/{ns}@{ver}/i18n/), not the host app's i18n model — so each card carries its translations with it.

How to Generate

bash
php artisan ui5:card Finance/Revenue \
  --title="Revenue Overview" \
  --description="Displays key revenue metrics for the current quarter"

This creates the card class, its provider, and the self-contained card folder (manifest + i18n stub).

Options

OptionDefaultDescription
name (argument)(required)Format: {App}/{Card}, e.g. Sales/Overviewwithout the Card suffix
--titleCard TitleCard title (an i18n key in the manifest)
--descriptionCard DescriptionCard description
--seed[=<name>](bare)Scaffold the manifest from a named seed (see Seeding)

The PHP and JS namespaces are derived from the target app's module — there are no namespace-prefix options.

⚠️ Pass the bare name — the generator adds the Card suffix.ui5:card Finance/Revenue produces the class RevenueCard, the provider RevenueProvider, and the folder slug cards/revenue/ (the name in snake_case). Passing Finance/RevenueCard is legal but gives you RevenueCardCard and cards/revenue_card/, which is almost never what you meant.

Output

Given php artisan ui5:card Finance/Revenue:

ui5/
└── Finance/
    ├── src/
    │   └── Cards/
    │       ├── RevenueCard.php
    │       └── Provider/
    │           └── RevenueProvider.php
    └── resources/
        └── ui5/
            └── cards/
                └── revenue/
                    ├── manifest.json.blade.php
                    └── i18n/
                        └── i18n.properties

On success the command prints the exact registration directives — the module method (getCards()) and the owning Dashboard Group's getChildNamespaces(), each with the literal line to add.

Seeding

By default the card's manifest.json.blade.php is a bare, header-only Object card ("content": { "groups": [] }) and the Provider returns no data — a valid card that boots but shows nothing.

To scaffold a populated starting point:

bash
php artisan ui5:card Finance/Kpi --seed=kpi
php artisan ui5:card Finance/Kpi --seed     # pick from the catalog

A card seed is different from a tile/chart seed: it swaps only the manifest.json, populating it with static visualization data that deliberately does not correspond to the generated DataProvider. Its job is "see a populated shape instantly" — live data-binding to the provider is your act to build; the scaffolded provider stays bare on purpose. An unknown seed name fails with the list of available seeds. The card seed catalog ships kpi, top-deals and account-snapshot; it is open content.

Artifact Roles

RevenueCard.php

Implements Ui5CardInterface. Identity is declared as class constants — the getters are inherited from AbstractUi5Card:

php
class RevenueCard extends AbstractUi5Card
{
    public const string NAMESPACE   = 'com.acme.finance.cards.revenue';
    public const string VERSION     = '1.0.0';
    public const string TITLE       = 'Revenue Overview';
    public const string DESCRIPTION = 'Displays key revenue metrics for the current quarter';

    public function getProvider(): string
    {
        return RevenueProvider::class;
    }
}

getProvider() declares the provider by class-string; the framework resolves it through the container, so the provider's constructor dependencies are autowired (see the provider below). Never write return app(...) or return new ... here — a declaration method declares, it does not build. Override getCard() only to set dashboard-grid properties (see Dashboard embedding).

RevenueProvider.php

Supplies the data the manifest template renders: provide() returns an array, and the Blade template receives it as $data. It implements DataProviderInterface. Follow the injection recipe: services go in the constructor (autowired), the context arrives as a provide() parameter. Slot values do not: the card path runs no slot pipeline.

php
class RevenueProvider extends AbstractConfigurable implements DataProviderInterface
{
    public function __construct(private readonly RevenueRepository $revenue) {}

    public function provide(): array
    {
        return ['title' => 'Revenue', 'value' => $this->revenue->thisQuarter(), 'unit' => 'EUR'];
    }
}

manifest.json.blade.php

The Integration Card manifest, rendered to JSON per request. Use Blade freely; the output must be valid JSON. Bind user-visible strings to the card's own i18n bundle with {i18n>KEY}never the double-brace form, which collides with Blade's echo syntax. Provider data is available as $data.

blade
{
  "_version": "1.15.0",
  "sap.app": {
    "id": "{{ $card->getNamespace() }}",
    "type": "card",
    "applicationVersion": { "version": "{{ $card->getVersion() }}" }
  },
  "sap.card": {
    "type": "Object",
    "header": { "title": "{i18n>title}", "subTitle": "{i18n>subTitle}" },
    "content": { "item": { "title": "{title}", "number": "{value}", "unit": "{unit}" } }
  }
}

i18n/i18n.properties

The card's translation bundle. Add locale variants alongside it (i18n_de.properties, i18n_en_US.properties); Core derives sap.app/i18n.supportedLocales from whichever files are present and serves them card-relative.

properties
title=Revenue
subTitle=Current quarter

Module Integration

Register cards in your module's getCards(), passing the module instance to each:

php
public function getCards(): array
{
    return [
        new Cards\RevenueCard($this),
    ];
}

This enables discovery, routing, and (when embedded) dashboard composition. The manifest folder is resolved from the card's slug.

Dashboard embedding

To embed a card in a Ui5Dashboard, reference it from a group's getChildNamespaces() by its ::NAMESPACE constant. Override getCard() to set grid placement (and any other sap.ui.integration.widgets.Card property exceptmanifest, which the framework injects):

php
public function getCard(): Card
{
    return new Card(
        layoutData: new GridContainerItemLayoutData(columns: 3, rows: 5),
    );
}

Best Practices

  • Keep heavy data logic in the provider; inject services via its constructor.
  • Translate every user-visible string with {i18n>KEY} — keep the keys in the card's own i18n/ bundle.
  • Prefer standard UI5 card types (Object, List, Table, Analytical).
  • A card is read-only — for state changes, use a Ui5Action.