Flexible Programming Model enables you to extend Fiori elements applications based on OData V4, as well as create freestyle applications from scratch using building blocks. An overview of Flexible Programming Model and what it provides is explained in the following blog post.
Leverage the flexible programming model to extend your SAP Fiori elements apps for OData V4
In this article, I am going to create a freestyle (custom page) application with create function. In Business Application Studio there is “Form Entry Object Page” template. This creates an application that directly opens an Object Page where you can create a new record. What I am going to do is to achieve a similar functionality with a freestyle application using “Custom Page” template.
The source code is available on GitHub in the blog-custom-form branch.
Form entry object page
Before we start off, let’s see how “Form Entry Object Page” looks like.
- When you start the application, an object page opens.
- When you press “Create”, the screen turns to display mode and a message toast is shown.
- When you press “Discard Draft”, draft data is discarded and the screen turns to display mode.
Create a custom form entry page
Prerequisites
To create an Fiori application using Flexible Programming Model, your OData service must fulfill the following requirements.
- OData V4
- Draft enabled (in case of create & edit scenarios)
Steps
- Create an app with “Custom Page” template
- Create a form page
The base CAP project is on GitHub.
1. Create an app with “Custom Page” template
1.1. Generate an app
Select “Custom Page” from the templates.
Select local CAP project as a data source.
An app with the following structure will be generated. Note that the main view and controller files are located in the “ext/main” folder.
1.2. Trigger the creation of an entity
Add the following code to Main.contrller.js to trigger the creation of an entity.
sap.ui.define(
[
'sap/fe/core/PageController'
],
function(PageController) {
'use strict';
return PageController.extend('flex.customformentry.ext.main.Main', {
onInit: function() {
PageController.prototype.onInit.apply(this);
const router = this.getAppComponent().getRouter();
router.getRoute("OrdersMain").attachPatternMatched(this._onObjectMatched, this);
},
_onObjectMatched: function() {
if(this._createDone) {
if (sap.ushell && sap.ushell.Container && sap.ushell.Container.getService) {
var oCrossAppNav = sap.ushell.Container.getService("CrossApplicationNavigation");
oCrossAppNav.toExternal({
target: {
shellHash: "#"
}
});
}
} else {
this._createDone = true;
const listBinding = this.getAppComponent().getModel().bindList("/Orders");
this.editFlow.createDocument(listBinding, {
creationMode: "NewPage"
});
}
}
});
}
);
EditFlow provides several methods to interact with a document. Inside a controller, you can access those methods simply by this.editflow.<functionName>
.
createDocument is used here to create a new entity. The parameter “creationMode” accepts one of the following values.
- NewPage: the created document is shown in a new page
- Inline: the creation is done inline in a table
- External: the creation is done in a different application specified via the parameter ‘outbound’
In the case of “NewPage”, the URL pattern changes to /<EntityName>(<key>)
so a route matching this pattern has to exist (we will be creating this route in the next step).
Important:
- When you implement “onInit” method, don’t forget to call
PageController.prototype.onInit.apply(this)
. Omitting this line will cause issues in Flexible Programming Model. - Calling createDocument directly inside onInit method seemed to be too early and I faced an error ‘TypeError: Cannot read properties of undefined (reading ‘getRouterProxy’) ‘. So I moved it inside the callback of attachPatternMatched event.
What is “if -else” block inside _onObjectMached method?
This is a workaround. When you cancel editing the form, the URL pattern changes from #<SemanticObject>-<Action>&/<Entity>(<key>)
to #<SemanticObject>-<Action>
, triggering navigation back to the main page. As I did not want to repeat creating new documents, I decided to navigate back to the launchpad if the same route is accessed a second time.
2. Create a form page
The main page simply triggers the creation of an entity, and the data input will take place on another page, what I call the form page.
2.1. Generate a new page
Place the cursor on “webapp” folder and press “Show Page Map”.
Press “+” icon on the Custom Page to add a new page.
Enter page details as below.
The view and controller files are generated under “ext/view” folder.
As a result, a new a route named “OrdersForm” is added to manifest.json.
"routing": {
"config": {},
"routes": [
{
"name": "OrdersMain",
"pattern": ":?query:",
"target": "OrdersMain"
},
{
"name": "OrdersForm",
"pattern": "Orders({OrdersKey}):?query:",
"target": "OrdersForm"
}
],
"targets": {
"OrdersMain": {
"type": "Component",
"id": "OrdersMain",
"name": "sap.fe.core.fpm",
"options": {
"settings": {
"viewName": "flex.customformentry.ext.main.Main",
"entitySet": "Orders",
"navigation": {
"Orders": {
"detail": {
"route": "OrdersForm"
}
}
}
}
}
},
"OrdersForm": {
"type": "Component",
"id": "OrdersForm",
"name": "sap.fe.core.fpm",
"options": {
"settings": {
"viewName": "flex.customformentry.ext.view.Form",
"entitySet": "Orders",
"navigation": {}
}
}
}
}
}
}
2.2. Form.view.xml
In the view, the following building blocks are used.
- Form : renders a form based on UI.FieldGroup, UI.ReferenceFacet, or UI.CollectionFacet annotations.
- Table: renders a table based on UI.LineItem or UI.PresentationVariant annotations.
<mvc:View xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:macros="sap.fe.macros"
xmlns:html="http://www.w3.org/1999/xhtml" controllerName="flex.customformentry.ext.view.Form">
<Page id="Form" title="Form">
<content>
<Panel headerText="Order information" >
<macros:Form metaPath="@com.sap.vocabularies.UI.v1.FieldGroup#main" id="main"/>
</Panel>
<Panel headerText="Order items" >
<macros:Table metaPath="to_Items/@com.sap.vocabularies.UI.v1.LineItem" id="items"
/>
</Panel>
</content>
<footer>
<OverflowToolbar>
<ToolbarSpacer />
<Button text="Create" press="saveDocument" type="Emphasized"
visible="{viewModel>/editable}" />
<Button id="cancelButton" text="Cancel" press="cancelDocument"
visible="{viewModel>/editable}"/>
</OverflowToolbar>
</footer>
</Page>
</mvc:View>
2.3. Form.controller.js
Implement the code controller as below. As the context creation is already done in the main view, all you need to do is to handle save and cancel actions. For this, EditFlow methods saveDodument and cancelDocument are used.
sap.ui.define(
[
'sap/fe/core/PageController',
'sap/ui/model/json/JSONModel',
],
function(PageController, JSONModel,) {
'use strict';
return PageController.extend('flex.customformentry.ext.view.Form', {
onInit: function() {
PageController.prototype.onInit.apply(this);
let model = {
editable : true
};
this.getView().setModel(new JSONModel(model), "viewModel");
},
saveDocument: function () {
var that = this;
this.editFlow.saveDocument(this.getView().getBindingContext()).then(function(){
that.getView().getModel("viewModel").setProperty("/editable", false);
})
},
cancelDocument: function () {
var that = this;
this.editFlow.cancelDocument(this.getView().getBindingContext(), {
control: this.byId("cancelButton")
}).then(function(){
that.getView().getModel("viewModel").setProperty("/editable", false);
})
}
});
}
);
Application Behavior
The image below shows the resulting application behavior. To test locally, I utilized cds-launchpad-plugin, as introduced in the following blog post by Geert-Jan Klaps. This allows you to open UI applications from a local launchpad.
A Fiori Launchpad Sandbox for all your CAP-based projects – Overview
Click on the tile.
The form opens.
When you press the “Cancel” button, you will be redirected to the launchpad.
Lessons learned
With Flexible Programming Model, you can create CRUD-enabled applications with very few lines of code. You don’t have to deal with odata models, or care about the screen mode (edit or display). All of these are taken care by the framework.
One thing to note is that the URL patterns are determined by the framework and you cannot seem to influence it. So you have to model your application routes accordingly. For example, when you create a new document, the pattern will be &/<EntityName>(<key>)
, and if you discard the document, this part will be removed from the pattern.
Final words
When I stared exploring Flexible Programming Model, there were few working examples apart from those mentioned in the “Learning materials” section below. Although Flexible Programming Model Explorer provides some sample code, they do not look like real-life examples to me. Especially, how to apply those parts in free style applications were hard to imagine. I hope this blog post helps you get started with Flexible Programming Model and more people share their knowledge within SAP Community.
Learning materials
- Document: Flexible Programming Model Explorer
- Youtube video series: SAP Fiori elements Flexible Programming
- TechEd 2022 hands-on: DT181 – Boost Your Productivity in Developing SAP Fiori Apps