Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
VitorSeifert
Advisor
Advisor

Introduction


After we have adapted an SAP Fiori elements application in my last blog post, today we are going to make changes to a freestyle SAPUI5 application. Before you proceed, please make sure you understand the different capabilities of SAPUI5 Flexibility by reading this blog.

We will start with a purchasing app and add a custom column to an existing table of products. This column will contain a link to a Google search for the corresponding product so that the end user can quickly access more information about it. Let’s briefly review how to create an Adaptation Project in SAP Business Application Studio and then implement the extension using Visual Editor.

Target Audience: Developers who want to compatibly adapt and extend existing freestyle applications by changing their appearance and/or functionality.

Creating the Adaptation Project and Starting the Visual Editor


SAP Business Application Studio is an SAP Business Technology Platform (BTP) service that offers a development environment optimized for business applications. Please refer to the sections Creating the Adaptation Project in SAP Business Application Studio and Working with the SAPUI5 Visual Editor in my last blog post and follow the steps described there. I named the project for this example OrderProducts.adapt. Once you are in the editor, you can proceed to the next section. The app we will be adapting originally looks like this:


Application before adaptation



Adding the XML Fragments


In this application, we are dealing with an sap.m.Table (Responsive Table). To learn about the structure of this table (or any other standard control) and to help you decide how to make your changes, you can check the SAPUI5 Samples and API Reference.

From the documentation, we learn that the column header (title) should be an extension of the columns aggregation on the table while the link itself should be added to each line of the table, using aggregation cells – which are bound to the table items (products). This way the column header will always be present and static while the link for each item will be dynamic (triggering the search for the corresponding product name).

Both the column and the cell will be new XML fragments on the application. To add a fragment, we need to make a change to an existing control. Let’s start with the column: In Edit mode, on the table and choose Add Fragment.


Add fragment to table


In the pop-up we need to select the Target Aggregation and the index (position in the aggregation) for the new fragment. In our example we use the Columns aggregation and the index 10 – so the column will be added at the end. We only see six columns in the screenshot – that’s because some columns are hidden. Click on Create New and choose a name for our fragment. Let’s call it MoreInformationColumn. This will create a new change on the /webapp/changes folder and a new file called MoreInformationColumn.fragment.xml in the /webapp/changes/fragments folder. Before we start modifying the XML, notice the comment on the first line: Use stable and unique IDs! When working with SAPUI5 Flexibility, it is very important to understand and use stable IDs. More details on the topic can be found here.

We already learned about internationalization and resource bundles in my previous blog post, so let’s create the XML fragment already with translatable texts. The MoreInformationColumn.fragment.xml file should look like this – the column properties were copied from the other columns in the table, so they all have the same appearance:
<core:FragmentDefinition xmlns:core='sap.ui.core' xmlns='sap.m'>
<Column id="moreInformationColumn"
width="12em"
hAlign="Right"
styleClass="sapRTSTOrdProdColumnProductTable"
vAlign="Middle">
<Text id="moreInformationColumnHeaderText" text="{i18n>MORE_INFORMATION_COLUMN_HEADER}" />
</Column>
</core:FragmentDefinition>

Add the following lines to the /i18n/i18.properties file:
# More Information Column
MORE_INFORMATION_COLUMN_HEADER=More Info

Once the fragment is finalized, restart the Visual Editor to see the new column header. However, the content is still missing. Let’s take care of that next.


Application with new column


If we click on any of the line items on the table, all the items are selected. This is because we are selecting an aggregation binding – meaning the items are generated based on the underlying data. As our link should also be displayed on each line and refer to the item on that line, our next fragment should be added to this aggregation. Right-click on any line and select Add Fragment:


Add fragment to items


The proposed aggregation – cells – is the one we are looking for and the index should be the same which was chosen for the table (in our example, 10). Click on Create new – let’s call it SearchLink. This fragment is very simple – it only contains a link with a text.
<core:FragmentDefinition xmlns:core='sap.ui.core' xmlns='sap.m'>
<Link id="searchLink" text="{i18n>SEARCH_LINK}"/>
</core:FragmentDefinition>

Add the following lines to the /i18n/i18.properties file:
# More Information Column
SEARCH_LINK=Search in Google

If we restart the Visual Editor, the line items will now contain links. If we click on them, nothing happens – because we still need to define their behavior. This is done using a Controller Extension.


Column with link



Add the Custom Controller Extension


On the editor, right-click on the view and select Extend with Controller. We enter a name for our controller and choose Extend.


Adding a controller extension


For this example, let’s use SearchLinkImplementation. The file will be created in the /webapp/changes/coding/ folder. The editor will open it automatically. The controller extension is created with guidelines to support with the implementation. For the sake of brevity, these comments have been removed in the example below, leaving only the code necessary for our functionality:
sap.ui.define([
'sap/ui/core/mvc/ControllerExtension',
'sap/m/library'
],
function (
ControllerExtension,
mobileLibrary
) {
"use strict";
return ControllerExtension.extend("OrderProducts.adapt.SearchLinkImplementation", {
handleLinkPress: function(oEvent) {
var oClickedLink = oEvent.oSource;
var oBindingContext = oClickedLink.getBindingContext();
var sProductName = oBindingContext.getObject().ProductName;
var sTargetURL = "https://www.google.com/search?q=" + sProductName;
var oURLHelper = mobileLibrary.URLHelper;
oURLHelper.redirect(sTargetURL, /*bNewWindow =*/ true);
}
});
});

We want the link to trigger a custom function that opens a search link with the product name in a new tab. From the sap.m.Link API documentation, we find that the event for clicking on the link is called “press”. We can declare an event handler for it in the controller extension. The event handler receives the event as a parameter and we can use it to retrieve the exact control that was clicked (oClickedLink).

Now we want to retrieve the product name, which is coming from the data bound to the items. As the link is part of a binding template, we can use getBindingContext() to retrieve information about it. With the binding context, call getObject() to get the actual data item related to the link and finally read the ProductName property.

Now that we have the product name, we can focus on triggering the search. To handle the opening of the Google search link in another tab, we will use the URLHelper control (API reference) from the sap.m library. The target URL is built using the product name previously retrieved and we can finally call the redirect method to open the URL. If the second parameter is “true”, it opens in a new window.

The last step is to connect the link control to this event handler. This is done on the XML fragment. The fragment needs to know where to look for the function, so we must make sure that the path is prefixed with “.extension.” and the namespace of the controller (as described in the documentation😞
<core:FragmentDefinition xmlns:core='sap.ui.core' xmlns='sap.m'>
<Link id="searchLink" text="{i18n>SEARCH_LINK}" press=".extension.OriginalOrderProducts.adapt.SearchLinkImplementation.handleLinkPress"/>
</core:FragmentDefinition>

And we are done! If we reload the application and click on a link, a new tab will open with a Google search for the product name. If you have difficulties, you can find more information about testing applications and debugging inside the Visual Editor in my previous blog post.

Wrap Up and Further Steps


In this article we went through another simple example of an extension done using Adaptation Project, this time for a freestyle application. Don’t forget to check the other available tutorials and courses provided by SAP and keep an eye on this blog as we will be updating with further resources as soon as they are available.

I would like to thank storkd, hristotsolev, mikhail.benderskiy and oelchen for helping prepare and review the content of this post.

We would love to hear from you! Please leave your feedback below – and let us know which topic you’d like to see next.
19 Comments
bhavikmeh22
Discoverer
0 Kudos
Hi Vitor,

Really good blog on Adaptation Project for Free Style Apps. I had few questions though.

  1. Previously when we used Webide we had extension points which were provided by SAP and we use to use the same for adding new fields using fragments. In above e.g. you just directly added new column without referring to extension point does it mean we can add any UI at any given point.

  2. Also previously we had options to replace View and controller in Webide, where we can hack and reimplement SAP provided Functions and UI controls. Is that possible with adaptation project.


Thanks,

Bhavik
VictorHo
Participant
It's good to refresh my knowledge with your blog. Thanks vitoreduardo.seifertbazzo .
hristotsolev
Advisor
Advisor
Hi Bhavik,

 

1: Yes, you can now add fragments to both extension points (where available) and control aggregations. You can check the official documentation for that here: https://help.sap.com/docs/SAP%20Business%20Application%20Studio/584e0bcbfd4a4aff91c815cefa0bce2d/81d...

 

2. You can add controller extension, but you cannot 'replace' the original controller or replace a View with another one. Though you should be able technically to remove everything that you do not need from a certain view and add the all the needed fragments as mentioned in point 1.

 

Regards,

Hristo
fenik84
Explorer

Hi Vitor,

Congratulations for the blog, it's really good.

I had a important question. How is it possible to preview the application to use the browser's developer tools? (I am not referring to the preview of the manifest.appdescr.variant).

If this possibility does not exist, I see it very complicated to add the code of the new extended controllers.

Regards.

Fernando.

VitorSeifert
Advisor
Advisor
0 Kudos

Hi Fernando,

Thank you for the positive feedback!

Regarding your question, please take a look at the "Debugging" section on my previous blog post: https://blogs.sap.com/2022/01/26/sap-fiori-elements-adaptation-project-adding-a-custom-filter-to-the...

If you select the correct context in the browser's developer tools when debugging the Visual Editor preview, it is easy to find the relevant files and evaluate the execution of your controller extension code.

Best regards,

Vitor

sri129225
Participant
0 Kudos
Hi Vitor,

I tried replicating this in my sap.m.Table. I was able to create the column but the link does not appear in the line items after choosing the target aggregation as "items".

I then tried adding the link to the page which was possible and the link appeared. But the controller function does not trigger. Please help me as to where I could have gone wrong.

Regards,

Srinivasan Seetharaman

 
VitorSeifert
Advisor
Advisor
0 Kudos
Hi Srinivasan,

Without seeing your project it is challenging to guess where the issues could be, but based on your description I have a couple of guesses:

  1. You said you used the target aggregation "items" for the link, but in the post I use "columns" to add the new column and "cells" to add the content to it (= the link itself).

  2. It can be tricky to choose the right namespace for the controller function. Please double-check the path to your function on the "press" property of the control.


I hope my answer will be helpful! Let me know if you make progress.

Best regards,

Vitor
Hi Vitor,

It's very helpful blog! Thanks for sharing.

I was having a requirement to extend the Manage Commodity Code standard application (F2516). BY following your blog, i can able to add the Column in the Table. But to add aggregation, 'items' is not working. In your example, you have used 'cells' as the aggregation. But i am not getting the option -


No CELLS aggregation


 

FYI, i am using Business Application Studio.

Can you please help here ?

 

Thanks,

Srinath
VitorSeifert
Advisor
Advisor
Hi Srinath,

Thank you for the positive feedback!

Regarding your issue, I noticed that you selected sap.m.Table to add the Fragment, but we actually want to change the table lines - so you need to select and right-click the lines of the table and then add the fragment there. Notice that for this to work your table needs to have at least one visible line - otherwise you won't be able to click the aggregation. So make sure that the table has some data.

I hope this helps!

Best regards,

Vitor
VitorSeifert
Advisor
Advisor
0 Kudos

Hi Fernando,

I'm happy to inform you that it is now possible to preview the application on a separate tab/window - making it easier to test and debug your adaptations. You just need to right-click the manifest.appdescr_variant file and choose "Open Preview". If you make further changes on the application, you can simply reload the preview on the other tab/window to see the updates.

Best regards,

Vitor

fenik84
Explorer
0 Kudos
Great news!

However, I am trying to test with my trial account and I am having problems accessing the applications (I have verified my services in the sicf of the backend and I have not made changes in my cloud connector):


The destination accesses the oData services of the backend:


Is there an open issue?

Thank you very much for everything.

Best regards

 

 

 
VitorSeifert
Advisor
Advisor
Hi Fernando,

I am not aware of any open issues. If you are still having trouble with the connection, I recommend opening a support ticket.

Best regards,

Vitor
fenik84
Explorer
0 Kudos

Hello again Victor,

Is it possible to run an adaptation project stand alone?

In the base application, accessing to index.hmtl through the SICF service is enough. But in these applications there is not index.hmtl. Is it possible to run the application without launchpad?

Can you please help?

Thanks,

VitorSeifert
Advisor
Advisor
0 Kudos

Hi Fernando,

Why do you need to run the application without launchpad? Could you please provide more details about your use case?

Thank you and best regards.

fenik84
Explorer
0 Kudos
Hi Victor,

The base application is embedded in an external system (they use the SICF service url to perform this action). We would need to adapt the base application to each of the client's needs and update/maintain the base application independently

Thanks 🙂

Hi Fernando,

an app variant will only run properly if the base app is in the same ABAP system. Only then the runtime can bring all components together.
If this is fulfilled it would be possible to deploy an html5 repository which holds an html page that launches the app variant standalone. The html needs to do what the launchpad would do: Read the UI5 component configuration for it and then feed this into the UI5 component factory.

A simple script to do this looks like this:

sap.ui.require([
"sap/ui/thirdparty/jquery",
"sap/m/Shell",
"sap/ui/core/ComponentContainer"
], function(
jQuery,
Shell,
ComponentContainer
) {
sap.ui.getCore().attachInit(function() {
var oData = jQuery.ajax("/sap/bc/ui2/app_index/ui5_app_info_json?id=<App Variant ID>", {async:false}).responseJSON;
var oComponent = sap.ui.component(oData["<App Variant ID>"]);

new Shell({
app: new ComponentContainer({
height : "100%",
component : oComponent
})
}).placeAt("content");
});
});

Best regards
Matthias

fenik84
Explorer

Thank you very much Matías and thank you very much Viktor,

Thanks to your responses I am making a lot of progress.

I have uploaded an "empty" UI5 application where the index.html contains the following code:

<script id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-libs="sap.m, sap.f"
data-sap-ui-theme="sap_belize"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
data-sap-ui-resourceroots='{"aa.aaa": "."}'
data-sap-ui-frameOptions="trusted">
</script>

<script>
sap.ui.define(
[
"sap/ui/thirdparty/jquery",
"sap/m/Shell",
"sap/ui/core/ComponentContainer"
],
function(jQuery, Shell, ComponentContainer) {
sap.ui.getCore().attachInit(function() {
var oData = jQuery.ajax("/sap/bc/ui2/app_index/ui5_app_info_json?id=[ID VARIANT]",
{async:false}).responseJSON;
var oComponent = sap.ui.component(oData["customer.geasingleinvoive"]);
new Shell({
app: new ComponentContainer({
height : "100%",
component : oComponent
})
}).placeAt("content");
});
});
</script>
</head>

<!-- UI Content -->
<body class="sapUiBody" id="content">
</body>
</html>

 

The app loads and executes controller code! However, it is unable to insert it into the component container and can't display.

The browser gives me this error:

Error from browser

Again, thank you so much.

Sorry for the inconvenience.

Greetings.

 

Fernando.

Hi Fernando,

that's strange. The code looks fine.

Could you provide the error stacktrace? Best would be to open the page with sap-ui-debug=true to have readable source code.
Which UI5 version do you use?

Regards Matthias
fenik84
Explorer
Hello Matthias,

the error was mine, I have embedded the adaptation project inside a component container as you described in the previous answer. The code is the following:
<!DOCTYPE html>
<html>
<head><meta name="sap-client" content="100"><meta name="sap-ui-fesr" content="/sap/bc/ui2/flp;sap-fesr-only"><meta name="sap.whitelistService" content="/sap/public/bc/uics/whitelist/service">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>aa</title>

<!-- Bootstrapping UI5 -->
<script id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-libs="sap.m, sap.f"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-compatVersion="edge">
</script>

<script>
sap.ui.require(
[
"sap/ui/thirdparty/jquery",
"sap/m/Shell",
"sap/ui/core/ComponentContainer"
],
function(jQuery, Shell, ComponentContainer) {
sap.ui.getCore().attachInit(function() {
var oData = jQuery.ajax("/sap/bc/ui2/app_index/ui5_app_info_json? id=[ID VARIANT APP]", {async:false}).responseJSON;
var oComponent = sap.ui.component(oData["ID VARIANT APP"]).then(function(result){
new Shell({
app: new ComponentContainer({
height : "100%",
component : result,
manifest: true,
name: "[IDBASEAPP]"
})
}).placeAt("container");
});
});
});
</script>

</head>

<!-- UI Content -->
<body class="sapUiBody" >
<div id="container"
data-sap-ui-component
data-name="appsingleinvoice.appsingleinvoice"
data-id="container"
data-settings='{"id" : "appsingleinvoice"}'
></div>
</body>
</html>

Thank you all very much for the help.

Best regards.

Fernando