4

Creating a custom form entry page with Flexible Programming Model

 1 year ago
source link: https://blogs.sap.com/2023/01/18/creating-a-custom-form-entry-page-with-flexible-programming-model/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
January 18, 2023 7 minute read

Creating a custom form entry page with Flexible Programming Model

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

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.
    Create screen
  • When you press “Create”, the screen turns to display mode and a message toast is shown.
    Press Create button
  • When you press “Discard Draft”, draft data is discarded and the screen turns to display mode.
    Press Discard button

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

Template selection

Select local CAP project as a data source.

Data source selection

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%20structure

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

Show Page Map

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

Add a new page

Add a new page

Enter page details as below.

Page details

Page details

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

Generated view and controller

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

Local launchpad

Local launchpad

The form opens.

Form page

Form page

Save the document.
Saved 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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK