Designing List Report and Object Page Apps with UI Annotations
Learn how to configure List Report and Object Page floorplans in detail using @UI annotations, headers, facets, tables, and actions wired to RAP behavior, including runtime binding to OData V4.
Explanation
Once the architectural model is understood, the practical skill of building a production-quality Fiori Elements app comes down to mastering the @UI annotation vocabulary and knowing how each annotation maps to a specific rendered control. This lesson focuses on the List Report Object Page (LROP) pattern, which is the default and most common Fiori Elements template used with RAP-managed and unmanaged business objects exposed via OData V4. The List Report page is built from three annotation families. @UI.selectionField controls which fields appear in the smart filter bar at the top of the list, along with their position and whether they are mandatory. @UI.lineItem defines the columns of the results table, including position, importance (#HIGH/#MEDIUM/#LOW, which drives responsive column hiding on smaller screens), and optionally a criticality annotation (@UI.criticality) that colors values (e.g., red/yellow/green status indicators) without any custom code. @UI.presentationVariant can define default sort order, grouping, and which variant is shown initially. The Object Page, reached by navigating into a list row, is built from @UI.facet (defining the visual sections/tabs of the page: general information, related items tables, sub-object sections) and @UI.fieldGroup or @UI.identification (defining which fields appear within each facet, and in what order/grouping). Facets can be of type #IDENTIFICATION_REFERENCE (a form section), #COLLECTION (grouping several sub-facets), or #LINEITEM_REFERENCE (a table facet showing, for example, booking line items under a travel header via a composition association). This is where RAP's composition child entities become visible as embedded tables directly on the Object Page with zero additional coding - the framework auto-generates create/edit/delete row actions if the underlying behavior definition supports them. Actions (RAP behavior actions, both instance-bound and non-instance-bound/static/factory actions) are exposed to the UI via @UI.lineItem or @UI.identification with a semanticObject-style linking, or more directly using the OData action metadata combined with @UI annotations that mark a button. Draft-enabled business objects (see the dedicated draft handling topic) automatically get Save/Edit/Cancel/Discard buttons injected by the framework - these are not manually annotated but derived from the presence of a draft behavior definition. Field-level behavior such as read-only, mandatory, or hidden states can be controlled dynamically at runtime through RAP's field control mechanisms (e.g., determinations setting a field control value) rather than static annotations, which is essential for business-rule-driven UIs (for example, a status field becoming read-only once a travel is 'Booked'). A key runtime consideration: all of this is rendered client-side by the SAPUI5 Fiori Elements library at app load time by reading $metadata and annotation XML, meaning changes to CDS annotations require regenerating and republishing the OData service (and often clearing the browser/metadata cache) before they are visible - a frequent source of 'my change isn't showing up' support tickets. Also, in S/4HANA Cloud (public cloud) versus on-premise/private cloud, key user adaptation (allowing business users to add fields, rearrange columns) is exposed differently and is subject to extensibility scope restrictions in the public cloud, whereas on-premise/private cloud allows deeper technical extension via the ABAP development tools.
Code example
// Object Page facets and field groups for a Travel Object Pageannotate view ZC_Travel_FE with{ @UI.facet: [ { id: 'GeneralInfo', purpose: #STANDARD, type: #IDENTIFICATION_REFERENCE, label: 'General Information', position: 10 }, { id: 'Bookings', purpose: #STANDARD, type: #LINEITEM_REFERENCE, label: 'Bookings', targetElement: '_Booking', position: 20 } ] @UI.fieldGroup: [ { qualifier: 'Header', position: 10 } ] @UI.identification: [ { position: 10, fieldGroup: 'Header' } ] TravelId; @UI.fieldGroup: [ { qualifier: 'Header', position: 20 } ] @UI.identification: [ { position: 20, fieldGroup: 'Header' } ] @UI.criticality: #OverallStatusCriticality OverallStatus; @Semantics.amount.currencyCode: 'CurrencyCode' @UI.lineItem: [ { position: 30, importance: #HIGH } ] @UI.fieldGroup: [ { qualifier: 'Header', position: 30 } ] TotalPrice;} // Criticality mapping element (referenced above)define transient view entity ZC_Travel_Criticality_Helper as select from ZR_Travel{ key TravelUUID as TravelUuid, case OverallStatus when 'A' then 3 /* accepted - green */ when 'X' then 1 /* rejected - red */ else 2 /* open - yellow */ end as OverallStatusCriticality}Real project scenario
A procurement team's Manage Purchase Requisitions app needs a status column colored red/yellow/green, a filter bar limited to plant, requisition type, and date range, and an Object Page showing header data plus an embedded editable table of requisition line items. All of this is delivered purely through @UI.lineItem, @UI.selectionField, @UI.facet, and @UI.criticality annotations on the projection view and its composition child, with the development team spending review time only on the underlying RAP behavior (validations, determinations) rather than any UI5 view code.
Common mistakes
โข Overloading the List Report filter bar with too many @UI.selectionField entries, degrading usability and performance because the smart filter bar issues a combined OData query with excessive parameters. โข Forgetting that @UI.lineItem importance levels only affect responsive behavior, not column order - developers sometimes mistakenly rely on importance to reorder columns. โข Hardcoding criticality values instead of deriving them from a calculated/transient element, making the annotation brittle when status codes change. โข Adding facets referencing associations that are not properly exposed or authorized in the projection view, resulting in an empty or erroring Object Page section at runtime. โข Not testing annotation changes with a full service metadata refresh, leading to false 'it doesn't work' bug reports that are actually caching issues.
Best practices
โข Keep the filter bar lean - only include selectionFields that map to real, indexed, commonly-used search criteria. โข Derive criticality and status-driven UI behavior from calculated CDS elements or RAP determinations rather than embedding logic in the UI layer. โข Use @UI.lineItem importance intentionally for responsive design, and control column order via explicit position values. โข Structure Object Page facets to mirror the business object's composition hierarchy so navigation matches the user's mental model of the document. โข Always verify annotation changes against the live service metadata preview before handing off to functional testers, to eliminate caching-related false defects.
Interview angle
Expect scenario questions like 'how would you show a colored status badge in a Fiori list without custom JavaScript?' The expected answer references @UI.criticality bound to a calculated CDS element, demonstrating the annotation-first mindset. Interviewers also probe understanding of facet types (#IDENTIFICATION_REFERENCE vs #LINEITEM_REFERENCE) to assess real hands-on experience versus theoretical knowledge.