How to Select All Rows and Execute Custom Actions on a List Report-like Screen

Estimated read time 16 min read

Introduction

In the previous blog, we explored how to call RAP actions with table types from a List Report. While this approach works effectively for scenarios involving a few selected rows, the “Select All” functionality only selects the rows currently visible on the screen. This constraint makes it unsuitable for use cases requiring bulk selection of large datasets.

To address this limitation, this blog focuses on how to implement “Select All”-like functionality on a List Report-like screen to execute custom actions. By leveraging the Flexible Programming Model and creating a Custom Page, we can achieve bulk selection and execute actions on all rows, whether visible or not.

 

Implementation Approach

To achieve “Select All”-like functionality and execute actions on all rows, the following approach is used:

Retrieve Filter Conditions: Instead of selected rows, the filter conditions from the filter bar are passed to the action, allowing it to operate on all matching rows, even if they are not visible.Customize the Filter Bar: The filter bar displays only fields relevant to the action, and the “Adapt Filter” button is hidden to prevent users from adding additional fields to the filter bar.

This application is not intended to be a generic List Report but is specifically for scenarios requiring bulk actions on all rows.

Note: This approach was implemented as part of a proof of concept (PoC) and has not been used in any productive applications.

 

Scenario Overview

In this blog, we will create an application similar to the one in the previous example, focusing on calculating stock values. The application allows users to specify a Plant (single value) and Location (multiple values) in the filter bar. When the “Get Total Stock Value” action is executed, the total stock value is calculated and displayed in a popup dialog.

 

Development Environment

Backend: BTP ABAP Environment (trial)Frontend: SAP Business Application Studio

 

Development Steps

Backend

Table DefinitionDefining an Action to Handle Deep ParametersImplementing Behavior LogicFrontendCreating a Custom Page ApplicationAdjusting Filter FieldsImplementing the ViewImplementing the Controller

 

Backend

The backend implementation for this application is similar to the one described in the previous blog. As such, the explanation has been omitted here, and only the code is provided for reference.

1. Table Definition

 

@EndUserText.label : ‘Product Stock’
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zyasu_stock {

key client : abap.clnt not null;
key id : sysuuid_x16 not null;
product_id : abap.char(20);
plant : abap.char(4);
location : abap.char(4);
price : abap.int2;
stock : abap.int2;
created_by : abp_creation_user;
created_at : abp_creation_tstmpl;
last_changed_by : abp_locinst_lastchange_user;
last_changed_at : abp_locinst_lastchange_tstmpl;

}

 

 

2. Defining an Action to Handle Deep Parameters

2.1. Behavior Definition

 

static action calcStockAmountAll deep parameter ZA_PLANT result [1] ZA_AMOUNT;

 

2.2. Root Abstract Entity

 

@EndUserText.label: ‘Plant’
define root abstract entity ZA_PLANT
{
plant : abap.char(4);
_location : composition [0..*] of ZA_LOCATION;
}

 

2.3. Child Abstract Entity

 

@EndUserText.label: ‘Location’
define abstract entity ZA_LOCATION
{
location : abap.char(4);
_plant : association to parent ZA_PLANT;

}

 

2.4. Abstract Behavior Definition

 

abstract;
strict ( 2 );
with hierarchy;

define behavior for ZA_PLANT alias Plant
{
association _location;
}

define behavior for ZA_LOCATION alias Location
{
}

 

 

3. Implement Behavior Logic

 

METHOD calcStockAmountAll.
DATA plant TYPE zr_yasu_stock-Plant.
DATA r_location TYPE RANGE OF zr_yasu_stock-location.
DATA amount TYPE i.

“get plant and storage locations
LOOP AT keys INTO DATA(key).
plant = key-%param-plant.
r_location = VALUE #( FOR loc IN key-%param-_location (
sign = ‘I’
option = ‘EQ’
low = loc-location ) ).
EXIT. “get the first record of the keys
ENDLOOP.

“select from cds view (not including draft data)
SELECT price,
stock
FROM zr_yasu_stock
WHERE plant =
AND location IN @r_location
INTO TABLE (stock_t).

” calculate stock amount
LOOP AT stock_t INTO DATA(stock).
amount = amount + stock-Price * stock-Stock.
ENDLOOP.

” return result
result = VALUE #( FOR key1 IN keys (
%cid = key1-%cid
%param = VALUE #( amount = amount )
) ).
ENDMETHOD.

 

 

Frontend

1. Creating a Custom Page Application

In SAP Business Application Studio, select the OData service created in the previous step and use the “Custom Page” template to create the application.

Since we want to implement the logic using TypeScript, select the “Enable TypeScript” option in the “Project Attributes” during the project setup process.

The structure of the generated project is as follows:

2. Adjusting Filter Fields

Set the filter fields in the Page Map so that only Plant and Location are displayed in the filter bar.

3. Implementing the View

Implement the ext/main/Main.view.xml file as follows. Similar to the List Report, it displays a filter bar, a table, and variant management.

 

<mvc:View xmlns:core=”sap.ui.core” xmlns:mvc=”sap.ui.core.mvc” xmlns=”sap.m” xmlns:macros=”sap.fe.macros”
xmlns:f=”sap.f” xmlns:macrosTable=”sap.fe.macros.table” xmlns:v=”sap.ui.fl.variants”
xmlns:html=”http://www.w3.org/1999/xhtml” controllerName=”miyasuta.selectallaction.ext.main.Main”>
<f:DynamicPage id=”FilterBarDefault” class=”sapUiResponsiveContentPadding”>
<f:title>
<f:DynamicPageTitle id=”_IDGenDynamicPageTitle1″>
<f:heading>
<v:VariantManagement id=”vm” for=”FilterBar” showSetAsDefault=”true” headerLevel=”H2″ />
</f:heading>
<f:snappedContent>
<Text id=”_IDGenText” text=”{fbConditions>/filtersTextInfo}” />
</f:snappedContent>
</f:DynamicPageTitle>
</f:title>
<f:header>
<f:DynamicPageHeader id=”_IDGenDynamicPageHeader1″ pinnable=”true”>
<VBox id=”_IDGenVBox1″>
<macros:FilterBar
metaPath=”/Stock/@com.sap.vocabularies.UI.v1.SelectionFields”
id=”FilterBar”
filterChanged=”.onFiltersChanged”
/>
</VBox>
</f:DynamicPageHeader>
</f:header>
<f:content>
<macros:Table metaPath=”@com.sap.vocabularies.UI.v1.LineItem” readOnly=”true” id=”LineItemTable” filterBar=”FilterBar”>
<macros:actions>
<macrosTable:Action id=”_IDGenAction1″
key=”getTotalStockValue”
text=”Get Total Stock Value”
press=”.onGetTotalStockValue”
requiresSelection=”false”
/>
</macros:actions>
</macros:Table>
</f:content>
</f:DynamicPage>
</mvc:View>

 

 

4. Implementing the Controller

The complete code is available here.

4.1. Hiding the “Adapt Filter” Button

To hide the “Adapt Filter” button, set the visible property of the button to false in the onBeforeRendring method. While this approach may not be officially recommended, I found it in the following Q&A:

CAP: Disable visibility of Adapt Filters button in List Page

 

public onBeforeRendering(): void {
//disable the adapt filter button
const adaptFilter = this.getView()?.byId(“miyasuta.selectallaction::StockMain–FilterBar-content-btnAdapt”) as Button;
adaptFilter.setVisible(false);
}

 

The button’s ID can be identified using tools like the UI5 Inspector.

4.2. Calling the Action

First, retrieve the selected values from the filter bar. If the required fields are not specified, return an error.
Next, set the retrieved values as parameters and call the action. Finally, display the result in a popup dialog.

public onGetTotalStockValue(): void {
//get filter conditions
const { plant, locations } = this.extractFilterConditions();

// validate required parameters
if (!plant) {
MessageBox.error(“Select a plant in the filter”);
return;
}

// invoke action
const model = this.getModel();
const operation = model?.bindContext(“/Stock/” + namespace + “calcStockAmountAll(…)”) as ODataContextBinding;
operation.setParameter(“plant”, plant);
operation.setParameter(“_location”, locations);

const fnSuccess = () => {
// show total
const result = operation.getBoundContext().getObject() as Result;
const amount = result.amount;
const numberFormat = NumberFormat.getIntegerInstance({
groupingEnabled: true
});
const formattedAmount = numberFormat.format(amount);
MessageBox.show(`${formattedAmount} JPY`, {
title: “Stock Amount”
});
};

const fnError = (error: Error) => {
MessageBox.error(error.message);
};

operation.invoke().then(fnSuccess, fnError);
}

This is the part of the code that extracts filter conditions from the filter bar, processes them, and returns the extracted plant and location(s).

private extractFilterConditions(): { plant: string | undefined; locations: Location[] } {
//get filter conditions
const allFilters = this.getAllFilters();

let plantRef = { plant: undefined as string | undefined };
let locations: Array<Location> = [];

allFilters.forEach(filter => {
if (filter.getPath()) {
this.processFilter(filter, plantRef, locations);
} else {
filter.getFilters()?.forEach(innerFilter => this.processFilter(innerFilter, plantRef, locations));
}
});

return { plant: plantRef.plant, locations };
}

private getAllFilters(): Filter[] {
const filterBar = this.getView()?.byId(“FilterBar”) as FilterBar;

// Get the filters from the FilterBar; the structure of filters[0]
// can either be an object (if a single filter value is set)
// or an array (if multiple filter values are set).
const filters = (filterBar.getFilters() as FilterObject).filters[0];

// If filters[0] contains an array of filters, return it directly
if (Array.isArray(filters?.getFilters())) {
return filters.getFilters() as Filter[];

// If filters[0] is an object, wrap it in an array and return
} else if (filters) {
return [filters];
}
// If no filters are set, return an empty array
return [];
}

private processFilter(filter: Filter, plantRef: { plant: string | undefined }, locations: Location[]): void {
if (filter.getPath() === “Plant”) {
plantRef.plant = filter.getValue1();
} else if (filter.getPath() === “Location”) {
locations.push({ location: filter.getValue1() });
}
}

 

Conclusion

This blog addressed the challenge of being unable to select all rows and execute an action in a List Report. To overcome this, values specified in the filter bar were passed as parameters to the action, achieving functionality similar to “Select All.”

While I considered implementing a similar approach by extending the List Report, I found it to be less feasible for two reasons: the difficulty of retrieving values from the filter bar, and the fact that using the internal controls of the filter bar itself is not appropriate. This made the approach an unsuitable solution.

The Flexible Programming Model is well-suited for creating applications with a List Report-like appearance while enabling more flexible extensions.

If you have solved a similar challenge using a different approach, I would appreciate it if you could share your solution in the comments section.

 

​ IntroductionIn the previous blog, we explored how to call RAP actions with table types from a List Report. While this approach works effectively for scenarios involving a few selected rows, the “Select All” functionality only selects the rows currently visible on the screen. This constraint makes it unsuitable for use cases requiring bulk selection of large datasets.To address this limitation, this blog focuses on how to implement “Select All”-like functionality on a List Report-like screen to execute custom actions. By leveraging the Flexible Programming Model and creating a Custom Page, we can achieve bulk selection and execute actions on all rows, whether visible or not. Implementation ApproachTo achieve “Select All”-like functionality and execute actions on all rows, the following approach is used:Retrieve Filter Conditions: Instead of selected rows, the filter conditions from the filter bar are passed to the action, allowing it to operate on all matching rows, even if they are not visible.Customize the Filter Bar: The filter bar displays only fields relevant to the action, and the “Adapt Filter” button is hidden to prevent users from adding additional fields to the filter bar.This application is not intended to be a generic List Report but is specifically for scenarios requiring bulk actions on all rows.Note: This approach was implemented as part of a proof of concept (PoC) and has not been used in any productive applications. Scenario OverviewIn this blog, we will create an application similar to the one in the previous example, focusing on calculating stock values. The application allows users to specify a Plant (single value) and Location (multiple values) in the filter bar. When the “Get Total Stock Value” action is executed, the total stock value is calculated and displayed in a popup dialog. Development EnvironmentBackend: BTP ABAP Environment (trial)Frontend: SAP Business Application Studio Development StepsBackendTable DefinitionDefining an Action to Handle Deep ParametersImplementing Behavior LogicFrontendCreating a Custom Page ApplicationAdjusting Filter FieldsImplementing the ViewImplementing the Controller BackendThe backend implementation for this application is similar to the one described in the previous blog. As such, the explanation has been omitted here, and only the code is provided for reference.1. Table Definition @EndUserText.label : ‘Product Stock’
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zyasu_stock {

key client : abap.clnt not null;
key id : sysuuid_x16 not null;
product_id : abap.char(20);
plant : abap.char(4);
location : abap.char(4);
price : abap.int2;
stock : abap.int2;
created_by : abp_creation_user;
created_at : abp_creation_tstmpl;
last_changed_by : abp_locinst_lastchange_user;
last_changed_at : abp_locinst_lastchange_tstmpl;

}  2. Defining an Action to Handle Deep Parameters2.1. Behavior Definition static action calcStockAmountAll deep parameter ZA_PLANT result [1] ZA_AMOUNT; 2.2. Root Abstract Entity @EndUserText.label: ‘Plant’
define root abstract entity ZA_PLANT
{
plant : abap.char(4);
_location : composition [0..*] of ZA_LOCATION;
} 2.3. Child Abstract Entity @EndUserText.label: ‘Location’
define abstract entity ZA_LOCATION
{
location : abap.char(4);
_plant : association to parent ZA_PLANT;

} 2.4. Abstract Behavior Definition abstract;
strict ( 2 );
with hierarchy;

define behavior for ZA_PLANT alias Plant
{
association _location;
}

define behavior for ZA_LOCATION alias Location
{
}  3. Implement Behavior Logic  METHOD calcStockAmountAll.
DATA plant TYPE zr_yasu_stock-Plant.
DATA r_location TYPE RANGE OF zr_yasu_stock-location.
DATA amount TYPE i.

“get plant and storage locations
LOOP AT keys INTO DATA(key).
plant = key-%param-plant.
r_location = VALUE #( FOR loc IN key-%param-_location (
sign = ‘I’
option = ‘EQ’
low = loc-location ) ).
EXIT. “get the first record of the keys
ENDLOOP.

“select from cds view (not including draft data)
SELECT price,
stock
FROM zr_yasu_stock
WHERE plant =
AND location IN @r_location
INTO TABLE (stock_t).

” calculate stock amount
LOOP AT stock_t INTO DATA(stock).
amount = amount + stock-Price * stock-Stock.
ENDLOOP.

” return result
result = VALUE #( FOR key1 IN keys (
%cid = key1-%cid
%param = VALUE #( amount = amount )
) ).
ENDMETHOD.  Frontend1. Creating a Custom Page ApplicationIn SAP Business Application Studio, select the OData service created in the previous step and use the “Custom Page” template to create the application. Since we want to implement the logic using TypeScript, select the “Enable TypeScript” option in the “Project Attributes” during the project setup process.The structure of the generated project is as follows:2. Adjusting Filter FieldsSet the filter fields in the Page Map so that only Plant and Location are displayed in the filter bar.3. Implementing the ViewImplement the ext/main/Main.view.xml file as follows. Similar to the List Report, it displays a filter bar, a table, and variant management. <mvc:View xmlns:core=”sap.ui.core” xmlns:mvc=”sap.ui.core.mvc” xmlns=”sap.m” xmlns:macros=”sap.fe.macros”
xmlns:f=”sap.f” xmlns:macrosTable=”sap.fe.macros.table” xmlns:v=”sap.ui.fl.variants”
xmlns:html=”http://www.w3.org/1999/xhtml” controllerName=”miyasuta.selectallaction.ext.main.Main”>
<f:DynamicPage id=”FilterBarDefault” class=”sapUiResponsiveContentPadding”>
<f:title>
<f:DynamicPageTitle id=”_IDGenDynamicPageTitle1″>
<f:heading>
<v:VariantManagement id=”vm” for=”FilterBar” showSetAsDefault=”true” headerLevel=”H2″ />
</f:heading>
<f:snappedContent>
<Text id=”_IDGenText” text=”{fbConditions>/filtersTextInfo}” />
</f:snappedContent>
</f:DynamicPageTitle>
</f:title>
<f:header>
<f:DynamicPageHeader id=”_IDGenDynamicPageHeader1″ pinnable=”true”>
<VBox id=”_IDGenVBox1″>
<macros:FilterBar
metaPath=”/Stock/@com.sap.vocabularies.UI.v1.SelectionFields”
id=”FilterBar”
filterChanged=”.onFiltersChanged”
/>
</VBox>
</f:DynamicPageHeader>
</f:header>
<f:content>
<macros:Table metaPath=”@com.sap.vocabularies.UI.v1.LineItem” readOnly=”true” id=”LineItemTable” filterBar=”FilterBar”>
<macros:actions>
<macrosTable:Action id=”_IDGenAction1″
key=”getTotalStockValue”
text=”Get Total Stock Value”
press=”.onGetTotalStockValue”
requiresSelection=”false”
/>
</macros:actions>
</macros:Table>
</f:content>
</f:DynamicPage>
</mvc:View>  4. Implementing the ControllerThe complete code is available here.4.1. Hiding the “Adapt Filter” ButtonTo hide the “Adapt Filter” button, set the visible property of the button to false in the onBeforeRendring method. While this approach may not be officially recommended, I found it in the following Q&A:CAP: Disable visibility of Adapt Filters button in List Page  public onBeforeRendering(): void {
//disable the adapt filter button
const adaptFilter = this.getView()?.byId(“miyasuta.selectallaction::StockMain–FilterBar-content-btnAdapt”) as Button;
adaptFilter.setVisible(false);
} The button’s ID can be identified using tools like the UI5 Inspector.4.2. Calling the ActionFirst, retrieve the selected values from the filter bar. If the required fields are not specified, return an error.Next, set the retrieved values as parameters and call the action. Finally, display the result in a popup dialog. public onGetTotalStockValue(): void {
//get filter conditions
const { plant, locations } = this.extractFilterConditions();

// validate required parameters
if (!plant) {
MessageBox.error(“Select a plant in the filter”);
return;
}

// invoke action
const model = this.getModel();
const operation = model?.bindContext(“/Stock/” + namespace + “calcStockAmountAll(…)”) as ODataContextBinding;
operation.setParameter(“plant”, plant);
operation.setParameter(“_location”, locations);

const fnSuccess = () => {
// show total
const result = operation.getBoundContext().getObject() as Result;
const amount = result.amount;
const numberFormat = NumberFormat.getIntegerInstance({
groupingEnabled: true
});
const formattedAmount = numberFormat.format(amount);
MessageBox.show(`${formattedAmount} JPY`, {
title: “Stock Amount”
});
};

const fnError = (error: Error) => {
MessageBox.error(error.message);
};

operation.invoke().then(fnSuccess, fnError);
}This is the part of the code that extracts filter conditions from the filter bar, processes them, and returns the extracted plant and location(s). private extractFilterConditions(): { plant: string | undefined; locations: Location[] } {
//get filter conditions
const allFilters = this.getAllFilters();

let plantRef = { plant: undefined as string | undefined };
let locations: Array<Location> = [];

allFilters.forEach(filter => {
if (filter.getPath()) {
this.processFilter(filter, plantRef, locations);
} else {
filter.getFilters()?.forEach(innerFilter => this.processFilter(innerFilter, plantRef, locations));
}
});

return { plant: plantRef.plant, locations };
}

private getAllFilters(): Filter[] {
const filterBar = this.getView()?.byId(“FilterBar”) as FilterBar;

// Get the filters from the FilterBar; the structure of filters[0]
// can either be an object (if a single filter value is set)
// or an array (if multiple filter values are set).
const filters = (filterBar.getFilters() as FilterObject).filters[0];

// If filters[0] contains an array of filters, return it directly
if (Array.isArray(filters?.getFilters())) {
return filters.getFilters() as Filter[];

// If filters[0] is an object, wrap it in an array and return
} else if (filters) {
return [filters];
}
// If no filters are set, return an empty array
return [];
}

private processFilter(filter: Filter, plantRef: { plant: string | undefined }, locations: Location[]): void {
if (filter.getPath() === “Plant”) {
plantRef.plant = filter.getValue1();
} else if (filter.getPath() === “Location”) {
locations.push({ location: filter.getValue1() });
}
} ConclusionThis blog addressed the challenge of being unable to select all rows and execute an action in a List Report. To overcome this, values specified in the filter bar were passed as parameters to the action, achieving functionality similar to “Select All.”While I considered implementing a similar approach by extending the List Report, I found it to be less feasible for two reasons: the difficulty of retrieving values from the filter bar, and the fact that using the internal controls of the filter bar itself is not appropriate. This made the approach an unsuitable solution.The Flexible Programming Model is well-suited for creating applications with a List Report-like appearance while enabling more flexible extensions.If you have solved a similar challenge using a different approach, I would appreciate it if you could share your solution in the comments section.   Read More Technology Blogs by Members articles 

#SAP

#SAPTechnologyblog

You May Also Like

More From Author