Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
MioYasutake
Active Contributor

Introduction


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.


Templates


 

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



  1. Create an app with "Custom Page" template

  2. 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.


Template selection


Select local CAP project as a data source.


Data source selection


An app with the following structure will be generated. Note that the main view and controller files are located in the "ext/main" folder.


Project structure



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".


Show Page Map


Press "+" icon on the Custom Page to add a new page.


Add a new page


Enter page details as below.


Page details


The view and controller files are generated under "ext/view" folder.


Generated view and controller


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 8d8214c7f9734f45be69f95cc0d5aeee. 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.


Local launchpad


The form opens.

Form page



Save the document.


Saved document


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


6 Comments
Labels in this area