Prepare building custom pages by using SAP Fiori development portal (5 of 6)

Estimated read time 21 min read

In our previous posts, we explored individual building blocks, their sophisticated capabilities, and how they come together in standard floorplans. We also learned how to extend these standard floorplans when needed. But what happens when your business requirements demand a layout that doesn’t match any standard floorplan? The SAP Fiori development portal has you covered. In this post, we’ll show you how to build a completely custom page layout while leveraging SAP Fiori elements building blocks to maintain enterprise-grade functionality.

When to Choose Custom Pages

Before we dive into creating custom pages, let’s be clear about the decision path. If your UI requirements fit one of the existing standard floorplans (List Report, Object Page, Analytical List Page, Overview Page, or Worklist), you should absolutely use them. Standard floorplans offer:

Reduced development and maintenance costsAutomatic UI consistency across your application landscapeBuilt-in enterprise features that would take months to develop from scratchFuture-proof compatibility with SAP Fiori elements updates

However, when you need a special layout – like a wizard, shopping cart, or any non-standard UI pattern – you’re not blocked. You can implement your custom layout while using as many SAP Fiori elements building blocks as possible to retain the benefits of the framework.

Exploring Custom Pages in the Portal

The SAP Fiori development portal’s Custom Page  chapter provides comprehensive guidance for building custom layouts. Let’s explore its key sections:

Overview – Shows a simple custom page demonstrating how building blocks integrate into custom layoutsBuilding Blocks Usage – Explains how to use SAP Fiori elements building blocks in existing UI5 freestyle applications, bridging the gap between traditional UI5 development and the SAP Fiori elements approachCustom Root View – Demonstrates how to create and register custom root views when the standard options (FullScreen and Flexible Column Layout) don’t meet your needsDraft Handling – Shows how to leverage the Draft controller extension in your custom pages for seamless draft management

Fiori Development Portal: Custom page sample

These sections provide the foundation for understanding custom page development. Now let’s put this knowledge into practice with a real-world example.

Building a Wizard: A Step-by-Step Journey

Let’s create a wizard for travel request creation – a floorplan currently not available as a standard in SAP Fiori elements. We’ll build this step by step, just as you would when developing your own custom page. The beauty of the portal is that you can experiment with code changes – auto-run is enabled by default, automatically refreshing the preview when you make simple changes. For more complex modifications, you can disable auto-run and manually click the “Run” button to update the sample when you’re ready.

Step 1: Setting Up the Wizard Structure

Navigate to the Custom Pages Overview section in the portal. Click “Show Code” to open the code editor. We’ll transform this simple page into a wizard.

First, let’s add the wizard structure without any content in the steps. Replace the content of the CustomPageSample view with:

<mvc:View
id=”application-product”
height=”100%”
controllerName=”sap.fe.core.fpmExplorer.customPage.CustomPageSample”
xmlns=”sap.m”
xmlns:mvc=”sap.ui.core.mvc”
xmlns:macros=”sap.fe.macros”
>
<macros:Page title=”Create New Travel Request” class=”sapUiContentPadding”>
<Wizard
id=”travelWizard”
enableBranching=”false”
class=”sapUiResponsivePadding–header sapUiResponsivePadding–content”
>
<WizardStep
id=”travelDataStep”
title=”Travel Information”
validated=”true”
>
<!– We’ll add content here –>
</WizardStep>

<WizardStep
id=”approvalDataStep”
title=”Budget and Approval”
validated=”true”
>
<!– We’ll add content here –>
</WizardStep>

<WizardStep
id=”bookingsStep”
title=”Bookings”
validated=”true”
>
<!– We’ll add content here –>
</WizardStep>

<WizardStep
id=”reviewStep”
title=”Submit Travel Request”
validated=”true”
>
<!– We’ll add content here –>
</WizardStep>
</Wizard>
</macros:Page>
</mvc:View>

Now update the controller to support the wizard functionality. In the controller code editor, replace the CustomPageSample controller with:

import PageController from “sap/fe/core/PageController”;
import MessageBox from “sap/m/MessageBox”;
import type Context from “sap/ui/model/odata/v4/Context”;

/**
* Controller for the custom travel request wizard page.
*/
export default class CustomPageSampleController extends PageController {
/**
* Creates a new travel request draft context and binds it to the view before rendering.
*/
public onBeforeRendering(): void {
const view = this.getView();

try {
const model = view.getModel();

// Create list binding and create a draft context
const listBinding = model.bindList(“/Travel”);
const wizardContext = listBinding.create({
CurrencyCode_code: “EUR”,
BookingFee: 0,
TotalPrice: 0,
TravelStatus_code: “O”
});

// Bind the view to the created context
view.setBindingContext(wizardContext);
// Set the UI model property to make the forms and table editable
view.getModel(“ui”).setProperty(“/isEditable”, true);
} catch (error) {
MessageBox.error(“Failed to create travel request: ” + (error as Error).message);
}
}

/**
* Handler called when the submit button is pressed.
*/
public async onSubmitTravelRequest(): Promise<void> {
// Navigate to the Object Page
const routing = this.getExtensionAPI().getRouting();
routing.navigate(this.getView().getBindingContext() as Context);
}
}

You now have a working wizard structure with four steps! Before rendering the view, the controller’s onBeforeRendering() method creates a new draft travel request and binds it to the view, so all our building blocks automatically work with this draft data. The onSubmitTravelRequest() method will be used later to finalize the wizard. For now, notice how the wizard steps are empty but navigable.

Preview wizard without step content

Step 2: Adding the First Form

Now let’s add content to our first wizard step. This is where the portal becomes invaluable. You can either navigate to the Form Building Block in the same tab (the portal keeps your changes in memory as long as you don’t refresh the browser), or better yet, open the portal in a second browser tab so you can reference the Form documentation while working on your wizard.

Open the Form page in the portal. You’ll see it demonstrates the UI.FieldGroup#ApprovalData annotation. But we want to use a different field group for our first step. To see what’s available, click “Show Code” next to the annotations example and look at the annotations.cds file in the code editor. You’ll find several field groups, including TravelData, which is perfect for our first step.

Back in your wizard code, update the first WizardStep to include the form:

<WizardStep
id=”travelDataStep”
title=”Travel Information”
validated=”true”
>
<MessageStrip
text=”Please provide the basic information about your travel request.”
type=”Information”
class=”sapUiSmallMarginBottom”
/>
<macros:Form
id=”travelDataForm”
metaPath=”@UI.FieldGroup#TravelData”
/>
</WizardStep>

Run the sample and navigate to the first step. You’ll see the form with the travel data fields. 

Preview of first wizard step

Notice that the TravelID field appears as the first field. However, since travel IDs are calculated in the backend, it doesn’t make sense to show this field here.

Let’s remove it from the field group. Go back to the annotations.cds code editor and locate the TravelData field group. Remove the TravelID field:

FieldGroup #TravelData : {
{ Value : BeginDate },
{ Value : EndDate },
{ Value : AgencyID },
{ Value : CustomerID }
}

Click the “Run” button to refresh the preview with your changes – the TravelID field disappears! This demonstrates the power of annotation-driven UIs: simple metadata changes directly affect the user interface without touching the view code.

Preview without travel ID

Step 3: Adding the Budget & Approval Form

Now let’s add the second form. Update the second WizardStep:

<WizardStep
id=”approvalDataStep”
title=”Budget and Approval”
validated=”true”
>
<MessageStrip
text=”Provide budget information for approval.”
type=”Information”
class=”sapUiSmallMarginBottom”
/>
<macros:Form
id=”approvalDataForm”
metaPath=”@UI.FieldGroup#ApprovalData”
/>
</WizardStep>

This step uses the ApprovalData field group, which contains financial information like booking fee and total price. The same building block, configured differently through the metaPath property, delivers completely different content.

Preview second wizard step

Step 4: Adding the Bookings Table

The third step needs a table for managing flight bookings. Navigate to the Table Building Blocks in the portal. Study how the table building block works, paying special attention to the metaPath property.

For our wizard, we want to display bookings that are associated with the travel request. Since our view is bound to a Travel context, we need to set the metaPath to the bookings navigation. Update the third WizardStep:
 

<WizardStep
id=”bookingsStep”
title=”Bookings”
validated=”true”
>
<VBox>
<MessageStrip
text=”Add flight bookings for your travel. You can add multiple bookings.”
type=”Information”
class=”sapUiSmallMarginBottom”
/>

<macros:Table
id=”bookingsTable”
metaPath=”/Travel/to_Booking/@UI.LineItem”
personalization=”Sort,Column”
header=”Flight Bookings”
creationMode=”NewPage”
/>
</VBox>
</WizardStep>

Run the sample and navigate to the Bookings step. Click the “Create” button in the table toolbar. You can now create bookings! The table building block automatically provides creation, editing, and deletion capabilities based on the entity’s capabilities defined in the service metadata. This is enterprise-grade functionality delivered through a single building block with minimal configuration.

Preview third wizard step

Step 5: Submitting the Travel Request

The final step allows users to review and submit their travel request. Update the fourth WizardStep:

<WizardStep
id=”reviewStep”
title=”Submit Travel Request”
validated=”true”
>
<VBox class=”sapUiContentPadding”>
<Text
text=”You have successfully completed all steps. Your travel request is ready to be submitted.”
class=”sapUiMediumMarginBottom”
/>

<HBox justifyContent=”End” class=”sapUiMediumMarginTop”>
<Button
text=”Submit”
type=”Emphasized”
press=”onSubmitTravelRequest”
icon=”sap-icon://paper-plane”
/>
</HBox>
</VBox>
</WizardStep>

 Notice that we’re not saving the draft directly using the EditFlow controller extension. Instead, when users click “Submit”, the onSubmitTravelRequest method in our controller uses the routing API to navigate to the travel request’s Object Page:

public async onSubmitTravelRequest(): Promise<void> {
// Navigate to the Object Page
const routing = this.getExtensionAPI().getRouting();
routing.navigate(this.getView().getBindingContext() as Context);
}

With this approach, users can review their complete travel request in the standard Object Page layout, make any final changes, and then save it using the familiar Object Page header actions. This demonstrates an important principle: you can mix custom pages with standard floorplans in the same application, using each where it fits best.

The Power of Building Blocks in Custom Pages

What we’ve built demonstrates the core value proposition of SAP Fiori elements building blocks in custom pages:

Rapid Development – We created a sophisticated multi-step wizard with forms, tables, and draft handling in minutes, not daysEnterprise Features – Each building block brings personalization, validation, error handling, and accessibility automaticallyAnnotation-Driven – Business logic changes (like removing the TravelID field) require only metadata updates, not code changesConsistency – The forms and table in our wizard look and behave exactly like those in standard floorplans, ensuring UI consistencyFlexibility – We combined building blocks with standard UI5 controls (Wizard, MessageStrip, Button) to create a layout not available in standard floorplans

Key Takeaways

When building custom pages with SAP Fiori elements:

Choose Wisely – Use standard floorplans whenever possible. Resort to custom pages only when your layout requirements genuinely don’t fit standard patternsMaximize Building Block Usage – The more you use SAP Fiori elements building blocks in your custom pages, the more enterprise features you get for freeLeverage the Portal – Use the SAP Fiori development portal to understand building block capabilities, experiment with configurations, and see immediate resultsMix and Match – Combine custom pages with standard floorplans in the same application, using each where appropriateStay Annotation-Driven – Even in custom pages, let annotations drive as much behavior as possible to minimize code and maximize maintainability

What’s Next

In our final post, “Bringing the SAP Fiori development portal and its use to the next level (6 of 6),” we’ll explore options to advance portal features and how to best include all project team members for maximizing productivity.

 

To get the full overview about the SAP Fiori development portal, check out the following blog posts of the mini-series:

Introducing the SAP Fiori Development Portal: Your Gateway to Rapid SAP Fiori App Creation (1 of 6)Get familiar with SAP Fiori development portal and the power of the building blocks (2 of 6)Explore the potential of SAP Fiori development portal using complex building blocks (3 of 6)Use SAP Fiori development portal to understand extension options for standard floorplans (4 of 6)Prepare building custom pages by using SAP Fiori development portal (5 of 6)Bringing the SAP Fiori development portal and its use to the next level (6 of 6) 

​ In our previous posts, we explored individual building blocks, their sophisticated capabilities, and how they come together in standard floorplans. We also learned how to extend these standard floorplans when needed. But what happens when your business requirements demand a layout that doesn’t match any standard floorplan? The SAP Fiori development portal has you covered. In this post, we’ll show you how to build a completely custom page layout while leveraging SAP Fiori elements building blocks to maintain enterprise-grade functionality.When to Choose Custom PagesBefore we dive into creating custom pages, let’s be clear about the decision path. If your UI requirements fit one of the existing standard floorplans (List Report, Object Page, Analytical List Page, Overview Page, or Worklist), you should absolutely use them. Standard floorplans offer:Reduced development and maintenance costsAutomatic UI consistency across your application landscapeBuilt-in enterprise features that would take months to develop from scratchFuture-proof compatibility with SAP Fiori elements updatesHowever, when you need a special layout – like a wizard, shopping cart, or any non-standard UI pattern – you’re not blocked. You can implement your custom layout while using as many SAP Fiori elements building blocks as possible to retain the benefits of the framework.Exploring Custom Pages in the PortalThe SAP Fiori development portal’s Custom Page  chapter provides comprehensive guidance for building custom layouts. Let’s explore its key sections:Overview – Shows a simple custom page demonstrating how building blocks integrate into custom layoutsBuilding Blocks Usage – Explains how to use SAP Fiori elements building blocks in existing UI5 freestyle applications, bridging the gap between traditional UI5 development and the SAP Fiori elements approachCustom Root View – Demonstrates how to create and register custom root views when the standard options (FullScreen and Flexible Column Layout) don’t meet your needsDraft Handling – Shows how to leverage the Draft controller extension in your custom pages for seamless draft managementFiori Development Portal: Custom page sampleThese sections provide the foundation for understanding custom page development. Now let’s put this knowledge into practice with a real-world example.Building a Wizard: A Step-by-Step JourneyLet’s create a wizard for travel request creation – a floorplan currently not available as a standard in SAP Fiori elements. We’ll build this step by step, just as you would when developing your own custom page. The beauty of the portal is that you can experiment with code changes – auto-run is enabled by default, automatically refreshing the preview when you make simple changes. For more complex modifications, you can disable auto-run and manually click the “Run” button to update the sample when you’re ready.Step 1: Setting Up the Wizard StructureNavigate to the Custom Pages Overview section in the portal. Click “Show Code” to open the code editor. We’ll transform this simple page into a wizard.First, let’s add the wizard structure without any content in the steps. Replace the content of the CustomPageSample view with:<mvc:View
id=”application-product”
height=”100%”
controllerName=”sap.fe.core.fpmExplorer.customPage.CustomPageSample”
xmlns=”sap.m”
xmlns:mvc=”sap.ui.core.mvc”
xmlns:macros=”sap.fe.macros”
>
<macros:Page title=”Create New Travel Request” class=”sapUiContentPadding”>
<Wizard
id=”travelWizard”
enableBranching=”false”
class=”sapUiResponsivePadding–header sapUiResponsivePadding–content”
>
<WizardStep
id=”travelDataStep”
title=”Travel Information”
validated=”true”
>
<!– We’ll add content here –>
</WizardStep>

<WizardStep
id=”approvalDataStep”
title=”Budget and Approval”
validated=”true”
>
<!– We’ll add content here –>
</WizardStep>

<WizardStep
id=”bookingsStep”
title=”Bookings”
validated=”true”
>
<!– We’ll add content here –>
</WizardStep>

<WizardStep
id=”reviewStep”
title=”Submit Travel Request”
validated=”true”
>
<!– We’ll add content here –>
</WizardStep>
</Wizard>
</macros:Page>
</mvc:View>Now update the controller to support the wizard functionality. In the controller code editor, replace the CustomPageSample controller with:import PageController from “sap/fe/core/PageController”;
import MessageBox from “sap/m/MessageBox”;
import type Context from “sap/ui/model/odata/v4/Context”;

/**
* Controller for the custom travel request wizard page.
*/
export default class CustomPageSampleController extends PageController {
/**
* Creates a new travel request draft context and binds it to the view before rendering.
*/
public onBeforeRendering(): void {
const view = this.getView();

try {
const model = view.getModel();

// Create list binding and create a draft context
const listBinding = model.bindList(“/Travel”);
const wizardContext = listBinding.create({
CurrencyCode_code: “EUR”,
BookingFee: 0,
TotalPrice: 0,
TravelStatus_code: “O”
});

// Bind the view to the created context
view.setBindingContext(wizardContext);
// Set the UI model property to make the forms and table editable
view.getModel(“ui”).setProperty(“/isEditable”, true);
} catch (error) {
MessageBox.error(“Failed to create travel request: ” + (error as Error).message);
}
}

/**
* Handler called when the submit button is pressed.
*/
public async onSubmitTravelRequest(): Promise<void> {
// Navigate to the Object Page
const routing = this.getExtensionAPI().getRouting();
routing.navigate(this.getView().getBindingContext() as Context);
}
}You now have a working wizard structure with four steps! Before rendering the view, the controller’s onBeforeRendering() method creates a new draft travel request and binds it to the view, so all our building blocks automatically work with this draft data. The onSubmitTravelRequest() method will be used later to finalize the wizard. For now, notice how the wizard steps are empty but navigable.Preview wizard without step contentStep 2: Adding the First FormNow let’s add content to our first wizard step. This is where the portal becomes invaluable. You can either navigate to the Form Building Block in the same tab (the portal keeps your changes in memory as long as you don’t refresh the browser), or better yet, open the portal in a second browser tab so you can reference the Form documentation while working on your wizard.Open the Form page in the portal. You’ll see it demonstrates the UI.FieldGroup#ApprovalData annotation. But we want to use a different field group for our first step. To see what’s available, click “Show Code” next to the annotations example and look at the annotations.cds file in the code editor. You’ll find several field groups, including TravelData, which is perfect for our first step.Back in your wizard code, update the first WizardStep to include the form:<WizardStep
id=”travelDataStep”
title=”Travel Information”
validated=”true”
>
<MessageStrip
text=”Please provide the basic information about your travel request.”
type=”Information”
class=”sapUiSmallMarginBottom”
/>
<macros:Form
id=”travelDataForm”
metaPath=”@UI.FieldGroup#TravelData”
/>
</WizardStep>Run the sample and navigate to the first step. You’ll see the form with the travel data fields. Preview of first wizard stepNotice that the TravelID field appears as the first field. However, since travel IDs are calculated in the backend, it doesn’t make sense to show this field here.Let’s remove it from the field group. Go back to the annotations.cds code editor and locate the TravelData field group. Remove the TravelID field:FieldGroup #TravelData : {
{ Value : BeginDate },
{ Value : EndDate },
{ Value : AgencyID },
{ Value : CustomerID }
}Click the “Run” button to refresh the preview with your changes – the TravelID field disappears! This demonstrates the power of annotation-driven UIs: simple metadata changes directly affect the user interface without touching the view code.Preview without travel IDStep 3: Adding the Budget & Approval FormNow let’s add the second form. Update the second WizardStep:<WizardStep
id=”approvalDataStep”
title=”Budget and Approval”
validated=”true”
>
<MessageStrip
text=”Provide budget information for approval.”
type=”Information”
class=”sapUiSmallMarginBottom”
/>
<macros:Form
id=”approvalDataForm”
metaPath=”@UI.FieldGroup#ApprovalData”
/>
</WizardStep>This step uses the ApprovalData field group, which contains financial information like booking fee and total price. The same building block, configured differently through the metaPath property, delivers completely different content.Preview second wizard stepStep 4: Adding the Bookings TableThe third step needs a table for managing flight bookings. Navigate to the Table Building Blocks in the portal. Study how the table building block works, paying special attention to the metaPath property.For our wizard, we want to display bookings that are associated with the travel request. Since our view is bound to a Travel context, we need to set the metaPath to the bookings navigation. Update the third WizardStep: <WizardStep
id=”bookingsStep”
title=”Bookings”
validated=”true”
>
<VBox>
<MessageStrip
text=”Add flight bookings for your travel. You can add multiple bookings.”
type=”Information”
class=”sapUiSmallMarginBottom”
/>

<macros:Table
id=”bookingsTable”
metaPath=”/Travel/to_Booking/@UI.LineItem”
personalization=”Sort,Column”
header=”Flight Bookings”
creationMode=”NewPage”
/>
</VBox>
</WizardStep>Run the sample and navigate to the Bookings step. Click the “Create” button in the table toolbar. You can now create bookings! The table building block automatically provides creation, editing, and deletion capabilities based on the entity’s capabilities defined in the service metadata. This is enterprise-grade functionality delivered through a single building block with minimal configuration.Preview third wizard stepStep 5: Submitting the Travel RequestThe final step allows users to review and submit their travel request. Update the fourth WizardStep:<WizardStep
id=”reviewStep”
title=”Submit Travel Request”
validated=”true”
>
<VBox class=”sapUiContentPadding”>
<Text
text=”You have successfully completed all steps. Your travel request is ready to be submitted.”
class=”sapUiMediumMarginBottom”
/>

<HBox justifyContent=”End” class=”sapUiMediumMarginTop”>
<Button
text=”Submit”
type=”Emphasized”
press=”onSubmitTravelRequest”
icon=”sap-icon://paper-plane”
/>
</HBox>
</VBox>
</WizardStep> Notice that we’re not saving the draft directly using the EditFlow controller extension. Instead, when users click “Submit”, the onSubmitTravelRequest method in our controller uses the routing API to navigate to the travel request’s Object Page:public async onSubmitTravelRequest(): Promise<void> {
// Navigate to the Object Page
const routing = this.getExtensionAPI().getRouting();
routing.navigate(this.getView().getBindingContext() as Context);
}With this approach, users can review their complete travel request in the standard Object Page layout, make any final changes, and then save it using the familiar Object Page header actions. This demonstrates an important principle: you can mix custom pages with standard floorplans in the same application, using each where it fits best.The Power of Building Blocks in Custom PagesWhat we’ve built demonstrates the core value proposition of SAP Fiori elements building blocks in custom pages:Rapid Development – We created a sophisticated multi-step wizard with forms, tables, and draft handling in minutes, not daysEnterprise Features – Each building block brings personalization, validation, error handling, and accessibility automaticallyAnnotation-Driven – Business logic changes (like removing the TravelID field) require only metadata updates, not code changesConsistency – The forms and table in our wizard look and behave exactly like those in standard floorplans, ensuring UI consistencyFlexibility – We combined building blocks with standard UI5 controls (Wizard, MessageStrip, Button) to create a layout not available in standard floorplansKey TakeawaysWhen building custom pages with SAP Fiori elements:Choose Wisely – Use standard floorplans whenever possible. Resort to custom pages only when your layout requirements genuinely don’t fit standard patternsMaximize Building Block Usage – The more you use SAP Fiori elements building blocks in your custom pages, the more enterprise features you get for freeLeverage the Portal – Use the SAP Fiori development portal to understand building block capabilities, experiment with configurations, and see immediate resultsMix and Match – Combine custom pages with standard floorplans in the same application, using each where appropriateStay Annotation-Driven – Even in custom pages, let annotations drive as much behavior as possible to minimize code and maximize maintainabilityWhat’s NextIn our final post, “Bringing the SAP Fiori development portal and its use to the next level (6 of 6),” we’ll explore options to advance portal features and how to best include all project team members for maximizing productivity. To get the full overview about the SAP Fiori development portal, check out the following blog posts of the mini-series:Introducing the SAP Fiori Development Portal: Your Gateway to Rapid SAP Fiori App Creation (1 of 6)Get familiar with SAP Fiori development portal and the power of the building blocks (2 of 6)Explore the potential of SAP Fiori development portal using complex building blocks (3 of 6)Use SAP Fiori development portal to understand extension options for standard floorplans (4 of 6)Prepare building custom pages by using SAP Fiori development portal (5 of 6)Bringing the SAP Fiori development portal and its use to the next level (6 of 6)   Read More Technology Blog Posts by SAP articles 

#SAP

#SAPTechnologyblog

You May Also Like

More From Author