Skip to content

Project created using SAP CAP technology, creating a local database for the back-end and an extended Fiori Elements to create a bulk CSV upload feature.

Notifications You must be signed in to change notification settings

GonzaloMB/Upload-CSV_SAP-CAP

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SAP CAP | Extending Fiori Elements-Upload CSV

Project made it with SAP CAP and Fiori Elements

Starting 🚀

Project created using SAP CAP technology, creating a local database for the back-end and an extended Fiori Elements to create a bulk CSV upload feature.

Pre-requirements 📋

The project contains these folders and files :

File or Folder Purpose
db/ your domain models and data go here
srv/ your service models and code go here
app/ content for UI frontends goes here
package.json project metadata and configuration

Tools you need to be able to develop this application

  • SAP BAS

Practical case ⚙️

In this application we are going to develop both the back-end and the front-end part

Back-end 🔩

1. DB/

This folder contains the scripts for creating the application's database. This is where data models are defined and relationships between tables are specified.

namespace my.bookshop;

entity Books {
  key ID    : UUID    @Common.Label : 'ID' @ObjectModel.generator.UUID;
      title : String  @Common.Label : 'Title';
      stock : Integer @Common.Label : 'Stock';
}

This code defines an entity called "Books" in the namespace "my.bookshop". The entity has three properties: "ID" of type UUID, which is also the primary key for the entity, "title" of type String, and "stock" of type Integer.

The properties are annotated with the @Common.Label annotation, which provides a label for the properties that can be used in user interfaces. The ID property is also annotated with the @ObjectModel.generator.UUID annotation, which indicates that the UUID values for the primary key will be generated by the Object Model Generator.

2. SRV/

This is the folder that contains the backend service of the application. This is where the application's business logic, data models, and APIs are defined.

using my.bookshop as my from '../db/data-model';

service CatalogService {
    entity Books as projection on my.Books;
}

This code defines a service called "CatalogService". It imports the "my.bookshop" namespace using the "my" alias and imports the data model from the "../db/data-model" file.

The service then defines an entity called "Books" using a projection on the "my.Books" entity. This means that the "Books" entity inherits all the properties and annotations of the "my.Books" entity.

The service can then expose APIs that allow clients to perform CRUD (Create, Read, Update, Delete) operations on the "Books" entity.

Front-End ⌨️

2. APP/

This folder contains the front-end web application. This is where the user interface of the application is defined.

This application is a Fiori Element, specifically a List Report. To perform the functionality of bulk record upload, we have followed the following steps.

  1. Upload-CSV_SAP-CAP/app/book_stock_control/annotations.cds
using CatalogService as service from '../../srv/cat-service';

@odata.draft.enabled

annotate service.Books with @(
    UI.SelectionFields : [
        title,
        stock
    ],
    UI.LineItem        : [
        {
            $Type : 'UI.DataField',
            Value : title,
        },
        {
            $Type                     : 'UI.DataField',
            Value                     : stock,
            CriticalityRepresentation : #WithoutIcon,
            Criticality               : {$edmJson : {$If : [
                {$Le : [
                    {$Path : 'stock'},
                    100
                ]},
                1,
                {$If : [
                    {$Ge : [
                        {$Path : 'stock'},
                        500
                    ]},
                    3,
                    2
                ]}
            ]}}
        }
    ]
);

annotate service.Books with @(
    UI.FieldGroup #GeneratedGroup1 : {
        $Type : 'UI.FieldGroupType',
        Data  : [
            {
                $Type : 'UI.DataField',
                Value : title,
            },
            {
                $Type : 'UI.DataField',
                Value : stock,
            },
        ],
    },
    UI.Facets                      : [{
        $Type  : 'UI.ReferenceFacet',
        ID     : 'GeneratedFacet1',
        Label  : 'General Information',
        Target : '@UI.FieldGroup#GeneratedGroup1',
    }, ]
) {
    ID         @(UI : {Hidden : true, });
};

This code imports a service called "CatalogService" from the '../../srv/cat-service' file. It also enables OData draft mode using the "@odata.draft.enabled" annotation.

The code then uses two annotations to modify the "Books" entity of the "CatalogService". The first annotation defines the selection fields and line items for the "Books" entity. It specifies that the "title" and "stock" fields should be used for selection and creates a line item with the title and stock fields.

The criticality of the stock field is also defined using an expression that sets a value of 1 if the stock is less than or equal to 100, 3 if the stock is greater than or equal to 500, and 2 otherwise.

The second annotation defines a field group and a reference facet for the "Books" entity. The field group contains the title and stock fields, and the reference facet is linked to the field group. The "ID" property is also defined using the "@UI" annotation to hide the ID field.

Overall, the annotations are used to modify the default behavior and appearance of the "Books" entity in the "CatalogService".

  1. Upload-CSV_SAP-CAP/app/book_stock_control/webapp/ext/view/PersonalizationDialog.fragment.xml

Within the webapp folder, we create the ext (extension) folder where we will create the necessary folders and files to extend the standard of a Fiori Element. Inside the ext folder, we create the view folder and the PersonalizationDialog.fragment.xml file.

This will be the popup from which we can download a CSV template to fill in with new records and then upload it to our database and display them in our table.

<core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core" xmlns:u="sap.ui.unified" xmlns:smartTable="sap.ui.comp.smarttable">
    <Dialog title="{i18n>titleDialogCSV}" titleAlignment="Center">
        <endButton>
            <Button text="{i18n>btnClose}" type="Negative" press=".onCloseDialog" />
        </endButton>
        <beginButton>
            <Button type="Success" icon="sap-icon://upload" text="{i18n>btnUpload}" press=".onUploadData" />
        </beginButton>
        <VBox justifyContent="Center" alignContent="Center" alignItems="Center">
            <HBox alignContent="Center" alignItems="Center" class="sapUiSmallMarginTop">
                <u:FileUploader icon="sap-icon://browse-folder" change="handleFiles" buttonText="{i18n>btnBrowse}" fileType="CSV" placeholder="{i18n>msgNonFileSelect}" />
            </HBox>
            <HBox alignContent="Center" alignItems="Center" class="sapUiSmallMarginTop sapUiSmallMarginBottom">
                <Button type="Neutral" icon="sap-icon://download" text="{i18n>btnDowloadTmpl}" press=".onDownloadTemplate" />
            </HBox>
            <HBox>
                <MessageStrip id="messageStripId" visible="false" text="{i18n>msgStrip}" type="Success" showIcon="true" class="sapUiSmallMarginBottom" />
            </HBox>
        </VBox>
    </Dialog>
</core:FragmentDefinition>

This is an XML code defining a dialog fragment in a SAPUI5 application. The fragment contains a dialog box with a title, two buttons (one for closing the dialog and one for uploading data), a file uploader control, and a message strip control. The dialog also includes several layout containers such as VBox and HBox, which are used to organize the controls in a vertical and horizontal layout, respectively. The control properties are set using data binding to internationalization resources specified in the application's i18n file. The code also includes event handlers for the button presses and file upload/change events.

  1. Upload-CSV_SAP-CAP/app/book_stock_control/webapp/ext/controller/ListReportExt.controller.js

Once we have the controller created, we reference it in the manifest.

   "extends": {
          "extensions": {
              "sap.ui.controllerExtensions": {
                  "sap.fe.templates.ListReport.ListReportController": {
                      "controllerName": "fiorielements.bookstockcontrol.ext.controller.ListReportExt"
                  }
              }
          }
      }

We create the custom action, which will be the button that opens the popup to download or upload the CSV to our table.

 "actions": {
                                      "customAction": {
                                          "id": "idUpladCSV",
                                          "text": "Upload CSV",
                                          "press": ".extension.fiorielements.bookstockcontrol.ext.controller.ListReportExt.onUploadCSV",
                                          "requiresSelection": false
                                      }
                                  }

Finally, we create the logic in our controller to provide the functionality to open the popup with the custom action, to be able to upload and download the CSV.

sap.ui.define(
    [
        "sap/ui/core/mvc/ControllerExtension",
        "sap/ui/core/Fragment",
        "sap/m/MessageToast",
        "sap/ui/model/json/JSONModel",
    ],
    function (
        ControllerExtension,
        Fragment,
        MessageToast,
        JSONModel

    ) {
        "use strict";
        return ControllerExtension.extend(
            "fiorielements.bookstockcontrol.ext.controller.ListReportExt",
            {
                onUploadCSV: function () {
                    // If the fragment is not loaded it generates it
                    Fragment.load({
                        name: "fiorielements.bookstockcontrol.ext.view.PersonalizationDialog", // Path of the fragment
                        controller: this, // Select this controller for the Dialog
                    }).then(
                        function (oDialog) {
                            this.pDialog = oDialog;
                            this.getView().addDependent(this.pDialog);
                            this.pDialog.open();
                        }.bind(this)
                    );
                },
                // Function to close the Dialog
                onCloseDialog: function () {
                    // Close dialog en clear and reset the compent inside it
                    this.pDialog.close();
                    this.pDialog.destroy(true);
                    this.pDialog = null;
                    this.oFileData = undefined;
                },

                // Function to read CSV file and prepare data for upload once done show a success msg
                handleFiles: function (oEvent) {
                    var oModelContentCsv = new JSONModel();
                    var oFileToRead = oEvent.getParameters().files["0"];
                    var reader = new FileReader();
                    // Save the register of the CSV inside an object
                    var loadHandler = function (oEvent) {
                        var csv = oEvent.target.result,
                            allTextLines = csv.split(/\r\n|\n/),
                            lines = [];
                        for (var i = 0; i < allTextLines.length; i++) {
                            var data = allTextLines[i].split(";"),
                                tarr = [];
                            for (var j = 0; j < data.length; j++) {
                                tarr.push(data[j]);
                            }
                            lines.push(tarr);
                        }
                        lines.splice(-1);
                        oModelContentCsv.setData(lines);
                        this.oFileData = oModelContentCsv.oData;
                        sap.ui.getCore().byId("messageStripId").setVisible(true);
                    }.bind(this);

                    // Error during the reading csv
                    var errorHandler = function (evt) {
                        if (evt.target.error.name == "NotReadableError") {
                            // Show message error read
                            var msgErrorRead = this.getView()
                                .getModel("i18n")
                                .getResourceBundle()
                                .getText("msgErrorRead");
                            MessageToast.show(msgErrorRead);
                        }
                    }.bind(this);

                    // Read file into memory as UTF-8
                    reader.readAsText(oFileToRead);

                    // Handle errors load
                    reader.onload = loadHandler;
                    reader.onerror = errorHandler;
                },

                // This function uploads a CSV file with book data to a table in a Fiori Elements application.
                onUploadData: function () {
                    // If the object with the load records is not undefined, the object is read and the entries are created
                    if (this.oFileData != undefined) {
                        // Get binding of the inner table
                        var oBinding = this.getView()
                            .byId("fiorielements.bookstockcontrol::BooksList--fe::table::Books::LineItem-innerTable")
                            .getBinding();
                        var aEntries = [];
                        // Format and sort the CSV data for each entry
                        for (var i in this.oFileData) {
                            var oEntry = {};
                            for (var z in this.oFileData[i]) {
                                oEntry[this.oFileData[0][z]] = this.oFileData[i][z];
                            }
                            oEntry.IsActiveEntity = true;
                            aEntries.push(oEntry);
                        }

                        // Remove header row
                        aEntries.shift();

                        // Convert stock value to integer
                        for (let i = 0; i < aEntries.length; i++) {
                            aEntries[i].stock = parseInt(aEntries[i].stock);
                        }

                        // Create every entry
                        for (var x in aEntries) {
                            var oContext = oBinding.create(aEntries[x]);
                        }

                        // Activate each draft entry
                        oContext.created()
                            .then(function () {
                                var oBinding = this.getView()
                                    .byId("fiorielements.bookstockcontrol::BooksList--fe::table::Books::LineItem-innerTable")
                                    .getBinding();

                                this.iCompleted = 0;
                                this.iItems = 0;

                                // Loop through the table's contexts to find draft entries and activate them
                                oBinding.aContexts.forEach(function (oRecord) {
                                    if (oRecord.sPath.indexOf("IsActiveEntity=false") !== -1) {
                                        var draftActivateUrl = this.getView().getModel().sServiceUrl +
                                            oRecord.sPath +
                                            "/CatalogService.draftActivate?$select=HasActiveEntity,HasDraftEntity,ID,IsActiveEntity,stock,title&$expand=DraftAdministrativeData($select=DraftIsCreatedByMe,DraftUUID,InProcessByUser)";

                                        // Make an AJAX request to activate the draft entry
                                        $.ajax({
                                            headers: {
                                                Accept: "application/json;odata.metadata=minimal;IEEE754Compatible=true",
                                                "Accept-Language": "en-US",
                                                Prefer: "handling=strict",
                                                "Content-Type": "application/json;charset=UTF-8;IEEE754Compatible=true",
                                            },
                                            url: draftActivateUrl,
                                            type: "POST",
                                            success: function () {
                                                this.iCompleted++;
                                            }.bind(this),
                                            error: function (error) {
                                                console.log(`Error ${error}`);
                                            }.bind(this),
                                        });
                                        this.iItems++;
                                    }
                                }.bind(this));
                                this.checkCompleted();
                            }.bind(this))
                            .catch(function () {
                                // Handle rejection of entity creation
                                var msgLoadError = this.getView()
                                    .getModel("i18n")
                                    .getResourceBundle()
                                    .getText("msgLoadError");

                                // Show message Load with error
                                MessageToast.show(msgLoadError);
                                this.onCloseDialog();
                            }.bind(this));
                    } else {
                        // If the object is undefined show msg
                        var msgNonFileSelect = this.getView()
                            .getModel("i18n")
                            .getResourceBundle()
                            .getText("msgNonFileSelect");

                        // Show message non file selected
                        MessageToast.show(msgNonFileSelect);
                    }
                },

                checkCompleted: function () {
                    if (this.iCompleted === this.iItems) {
                        this.iCompleted = 0;
                        // Entry successfully created and Show message Load success
                        var msgLoadSuccess = this.getView()
                            .getModel("i18n")
                            .getResourceBundle()
                            .getText("msgLoadSuccess");
                        MessageToast.show(msgLoadSuccess);
                        this.onCloseDialog();
                        setTimeout(
                            function () {
                                var goBtn = this.getView().byId(
                                    "fiorielements.bookstockcontrol::BooksList--fe::FilterBar::Books-btnSearch"
                                );
                                goBtn.firePress(true);
                            }.bind(this),
                            500
                        );
                    } else {
                        setTimeout(
                            function () {
                                this.checkCompleted();
                            }.bind(this),
                            500
                        );
                    }
                },

                // Function to download template
                onDownloadTemplate: function () {
                    // define the heading for each row of the data
                    var csv = "title;stock";
                    var hiddenElement = document.createElement("a");
                    hiddenElement.href = "data:text/csv;charset=utf-8," + encodeURI(csv);
                    // provide the name for the CSV file to be downloaded
                    hiddenElement.download = "TemplateCsv.csv";
                    hiddenElement.click();
                },
            }
        );
    }
);

Testing the print pdf feature 👨‍💻

UploadCSV.mp4

Acknowledgement 📚

  • CAP CDS
  • Javascript / UI5
  • Fiori Elements

Built with 🛠️

Back-end:

  • CAP CDS

Gateway:

  • oData

Front-End:

  • Javascript / UI5
  • Fiori Elements

Next Steps

  • Open a new terminal and run cds watch
  • (in VS Code simply choose Terminal > Run Task > cds watch)
  • Start adding content, for example, a db/schema.cds.

Learn More

Learn more at https://cap.cloud.sap/docs/get-started/.


⌨️ with ❤️ love GonzaloMB 😊

About

Project created using SAP CAP technology, creating a local database for the back-end and an extended Fiori Elements to create a bulk CSV upload feature.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published