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: 
MustafaBensan
Active Contributor

Introduction

In this edition of the Design Studio Innovation Series, I'm going to describe an approach for implementing a hierarchy filter based on standard and SDK Development Community components, to take advantage of complementary features of both.  With the standard functionality, we can implement cascading filters with the Dropdown Box and List Box components but neither of these cater for hierarchy structures, which is the requirement I will address here.

The Result

The solution allows compact, level-by-level hierarchical drill-down, with the option to drill back up level-by-level or go straight to the beginning.  Before going into the details, here's the end result in the form of an animated gif (Click on the image if it doesn't start playing immediately.  It may take a little bit of time to load).  In this example, the hierarchy filter is driving a chart:

For those of you who want to get straight to it, a sample BIAPP is attached at the end.  For those interested in the details, read on!...

Background

This post was inspired by the Cascade Filter for Hierarchy Dimension question from pouser.  Since the requirement involved a fixed level hierarchy structure, I was able to propose a solution that relied only on standard functionality based on the Crosstab component.  A further thought then occurred to me about how could the solution be extended to achieve dynamic filtering for drilling down and up a hierarchy structure with many levels of varying depth, as shown below?:

We could certainly use a Crosstab component as-is, as shown above but this can become unwieldy as nodes are expanded and collapsed, with the added need for scrolling when the structure is large.  Similarly, use of the standard Dimension Filter also requires node manipulation and scrolling, with several clicks to find the required item and then back out to apply, as shown below:

The Approach


The solution is centred around a standard Crosstab component because unlike the Dropdown Box and List Box, it supports display of a hierarchy structure and selection of nodes.  Drilling down a hierarchy one level at a time by applying the setFilter() method on the data source assigned to a Crosstab is relatively straight forward.  However, the problem arises when we need to implement drill-up functionality to move back in the hierarchy.  The problem here is we need to somehow "remember" the drill path and parent-child relationships.  As far as I could see, there was no easy way to achieve this with standard scripting alone.  We need to store the drill path in some kind of structure that we can manipulate and look up, so I took a look at the Design Studio SDK: Collection Utility Component from the SDK Development Community as a possible candidate and it did the trick!

Note:  The Collection Utility component currently doesn't seem to allow deletion of specific entries so all drill paths that the user traverses are stored the first time the user goes down a particular path.  I think a great enhancement to the Design Studio SDK: Collection Utility Component would be support for stack functionality with Push and Pop methods.  This would allow a more efficient mechanism for recording and traversing drill paths and only the current drill path would need to be tracked.

So now let's go through a step-by-step breakdown.

Data Source

In order to allow level-by-level drill-down, the Initial View of the main data source is configured as shown below, expanded to level 2:

Components

My aim was to implement a modular, reusable solution, so on the UI side I have effectively created a composite component by combining a Home Button, Back Button and Crosstab within a Panel component:



In the outline panel it looks like this:


To track the drill path, I have I have used two collections implemented with the Design Studio SDK: Collection Utility Component:

The Hierarchy collection keeps track of the drill path and the Variable collection tracks the previous node to allow reversal back up the drill path.  I probably could have used a Global Script Variable to perform the same function as the Variable collection but thought it would help make the solution a little more modular.

For demo purposes I've also included the SDK Development Community Fiori App Header component.

CSS

I have adapted haripriya very useful method for Default Selection in a Crosstab to implement the following functionality:


  1. Highlight the first row as the default selection on startup
  2. Highlight the first row as the default selection when drilling back to the previous level


The CSS file for the application has been named defaultselection.css and consists of the following code:



.highlightrow .sapzencrosstab-RowHeaderArea tr:nth-child(1).sapzencrosstab-HeaderRow td:nth-child(1)
{
background-color: #7bc3ef !important;
}






The CSS file should be assigned to the Application Properties as shown below:


The CSS Class highlightrow should then be assigned to the Crosstab component as shown below:

Global Script Variables

I have defined a set of constants as Global Script Variables so they can be maintained centrally, as shown below:

Global Script Functions

To keep the code modular, several script functions have been created under the HIERARCHY_FILTER global scripts object as shown below:

Each of these functions are self-contained in that all necessary external data are passed as input parameters.  A great feature of global script functions is that you can even pass in components and data sources as parameters and then perform actions on these such as setting properties or filtering, so you can apply the same standard process to multiple components / data sources if needed.

I'll now describe each of the functions in logical order.

initialize:

This function is called from the "On Startup" event script of the application.  It performs the following main tasks:

  • Verifies that the dimension we want to drill on has an active hierarchy assigned to it
  • Initializes the collections for the hierarchy structure and previous node
  • Sets the default text of the filter value
  • Disables navigation, sorting and column resizing on the filter crosstab
  • Disables the back button

It requires the following parameters:

Input ParameterTypeDescription
pCrosstabCrosstabCrosstab for hierarchy filter
pHierarchyDimensionStringDimension for hierarchy filter
pHierarchyRootStringRoot of hierarchy
pHierarchyRootDescriptionStringHierarchy root description
pHierarchyPrefixStringHierarchy prefix
pDataSourceDataSourceAliasData Source for hierarchy filter
pHierarchyCollectionorg.scn.community.utils.CollectionCollection for hierarchy
pVariableCollectionorg.scn.community.utils.CollectionVariable collection for hierarchy
pBackButtonButtonBack Button
pTitleTextTextSelected Item Text
pPreviousNodeIDStringPrevious Node Identifier key used in variable collection lookup

The script is coded as follows:


//
// Initialise Hierarchy Filter
//
// Verify active hierarchies exist
if (!pDataSource.isHierarchyActive(pHierarchyDimension))
{
  APPLICATION.createErrorMessage("No active hierarchy for data source " + pDataSource.getInfo().dataSourceName);
return false;
}
// Initialize Collections
var hierarchyRoot = pHierarchyPrefix + pHierarchyRoot;
pHierarchyCollection.addItem(hierarchyRoot, '', 0.0, pHierarchyRootDescription); // Initialize first index item with hierarchy root node
pVariableCollection.addItem(pPreviousNodeID, hierarchyRoot, 0.0, pHierarchyRootDescription); // Initialize previously selected node
// Set root node description as default text
pTitleText.setText(pHierarchyCollection.getEntryByKey(hierarchyRoot).prop1);
// Initialise crosstab settings
pCrosstab.setHierarchyNavigationEnabled(false); // Disable navigation
pCrosstab.setSortingEnabled(false); // Disable sorting
pCrosstab.setColumnResizingEnabled(false); // Disable column re-sizing
pBackButton.setEnabled(false); // Disable Back button
return true;






drillDown:

This function is called from the "On Select" event script of the filter crosstab.  It is the engine of overall functionality and performs the following main tasks:


  • Initializes variables
  • Records the drill path
  • Drills down to the next level
  • Applies a filter to the target data source


It requires the following parameters:


Input ParameterTypeDescription
pCrosstabCrosstabCrosstab for hierarchy filter
pHierarchyDimensionStringDimension for hierarchy filter
pDataSourceDataSourceAliasHierarchy data source
pTargetDataSourceDataSourceAliasTarget data source for filtering
pHierarchyCollectionorg.scn.community.utils.CollectionCollection for hierarchy
pVariableCollectionorg.scn.community.utils.CollectionVariable collection for hierarchy
pBackButtonButtonBack Button
pHierarchyNodePrefixStringHierarchy node prefix
pPreviousNodeIDStringPrevious node ID for variable collection lookup
pNotApplicableStringNot Applicable constant value
pAllMembersStringAll members filter value

The script is coded as follows:



//
// Drilldown Processing
// Called from OnSelect event of Crosstab
//
//Initialise variables
var parentNodeKey = "";
var selectedFilterKey = pCrosstab.getSelectedMember(pDrillDimension).internalKey;
var selectedFilterText = pCrosstab.getSelectedMember(pDrillDimension).text;
var indexCheck = pHierarchyCollection.getIndexByKey(selectedFilterKey) + '';
var nodePrefix = selectedFilterKey.substring(0, pHierarchyNodePrefix.length);
var previousNodeKey = pVariableCollection.getLabelByKey(pPreviousNodeID);
var rootNode = pHierarchyCollection.getKeyByIndex(0);
// Add drill path to collection only if it has not already been added from a previous drilldown
if (indexCheck == pNotApplicable && selectedFilterKey != pAllMembers && nodePrefix == pHierarchyNodePrefix)
{
  parentNodeKey = previousNodeKey;
  pHierarchyCollection.addItem(selectedFilterKey, parentNodeKey, 0.0,selectedFilterText);
}
// Drilldown to the next level only if the selected node is a hierarchy node
// Update previous node
if (nodePrefix == pHierarchyNodePrefix)
{
  pDataSource.setFilter(pDrillDimension, selectedFilterKey);
  pVariableCollection.removeAllItems();
  pVariableCollection.addItem(pPreviousNodeID, selectedFilterKey, 0.0, selectedFilterText);
}
else
{
  pCrosstab.setCSSClass(""); // Clear first row highlighting if selection is leaf node
}
// If the Back button is not enabled then enable it if conditions are met
if (!pBackButton.isEnabled() && selectedFilterKey != pAllMembers && selectedFilterKey != rootNode)
{
  pBackButton.setEnabled(true);
}
// Apply filter to the target if filter is not "all members"
if (selectedFilterKey != pAllMembers)
{
  HIERARCHY_FILTER.filterTarget(pTargetDataSource, pDrillDimension, selectedFilterKey, selectedFilterText, TEXT_CHART_TITLE);
}






filterTarget:

This function is called from the drillDown, back and home functions.  It performs the following main tasks:


  • Filters the target data source
  • Updates the text field to display the new filter value


It requires the following parameters:

Input ParameterTypeDescription
pDataSourceDataSourceAliasTarget datasource for applying filter
pFilterDimensionStringDimension to be filtered in target data source
pFilterKeyStringDimension member key to be filtered in target data source
pFilterTextStringDimension member text
pTextTextText component to display selected dimension member


The script is coded as follows:



//
//  Filter Target
//
pDataSource.setFilter(pFilterDimension, pFilterKey);
pText.setText(pFilterText);  // Update text to display new filter value







back:


This function is called from the "On Click" event script of the Back button.  It performs the following main tasks:


  • Initializes variables
  • Updates the previous hierarchy node
  • Drills back to the previous hierarchy node
  • Applies a filter to the target data source


It requires the following parameters:

Input ParameterTypeDescription
pDataSourceDataSourceAliasHierarchy data source
pTargetDataSourceDataSourceAliasTarget data source for filtering
pHierarchyCollectionorg.scn.community.utils.CollectionCollection for hierarchy
pVariableCollectionorg.scn.community.utils.CollectionVariable collection for hierarchy
pBackButtonButtonBack Button
pTextTextText component to display selected dimension member
pHierarchyDimensionStringDimension for Hierarchy Filter
pPreviousNodeIDStringPrevious node ID for variable collection lookup
pCrossTabCrosstabCrosstab for Hierarchy Filter
pRowSelectionClassStringCSS Class to highlight first row of crosstab


The script is coded as follows:



//
// Back button (drillup) Processing
//
// Initialise Variables
var previousNode = pVariableCollection.getLabelByKey(pPreviousNodeID);
var parentNode = pHierarchyCollection.getLabelByKey(previousNode);
var parentNodeText = pHierarchyCollection.getEntryByKey(parentNode).prop1;
var rootNode = pHierarchyCollection.getKeyByIndex(0);
previousNode = parentNode;
var previousNodeText = parentNodeText;
// Update Previous Node
pVariableCollection.removeAllItems();
pVariableCollection.addItem(pPreviousNodeID, previousNode, 0.0, previousNodeText);
// Set filter to drill back to previous node (parent node)
pDataSource.setFilter(pHierarchyDimension, previousNode);
// Apply highlight to first row of crosstab
pCrossTab.removeSelection();
pCrossTab.setCSSClass(pRowSelectionClass);
// Disable Back button if top of hierarchy has been reached
if (previousNode == rootNode)
{
  pBackButton.setEnabled(false);
}
// Apply filter to target data source
HIERARCHY_FILTER.filterTarget(pTargetDataSource, pHierarchyDimension, previousNode, previousNodeText, pText);







home:


This function is called from the "On Click" event script of the Home button.  It performs the following main tasks:


  • Gets the root node of the hierarchy
  • Resets the previous hierarchy node
  • Removes the hierarchy filter
  • Filters the target data source based on the root node


It requires the following parameters:

Input ParameterTypeDescription
pDataSourceDataSourceAliasHierarchy data source
pTargetDataSourceDataSourceAliasTarget data source for filtering
pHierarchyCollectionorg.scn.community.utils.CollectionCollection for hierarchy
pVariableCollectionorg.scn.community.utils.CollectionVariable collection for hierarchy
pBackButtonButtonBack Button
pHierarchyDimensionStringDimension for Hierarchy Filter
pPreviousNodeIDStringPrevious node ID for variable collection lookup
pCrossTabCrosstabCrosstab for Hierarchy Filter
pRowSelectionClassStringCSS Class to highlight first row of crosstab


The script is coded as follows:



//
// Home Button Processing
//
// Get root node information
var rootNodeKey = pHierarchyCollection.getKeyByIndex(0);
var rootNodeText = pHierarchyCollection.getEntryByKey(rootNodeKey).prop1;
// Reset previous node pointer
pVariableCollection.removeAllItems();
pVariableCollection.addItem(pPreviousNodeID, rootNodeKey, 0.0);
// Remove filter to reset hierarchy to top level
pDataSource.clearFilter(pHierarchyDimension);
// Remove previous row selection and highlight first row as default
pCrossTab.removeSelection();
pCrossTab.setCSSClass(pRowSelectionClass);
// Disable Back button
pBackButton.setEnabled(false);
// Filter target data source based on root node
HIERARCHY_FILTER.filterTarget(pTargetDataSource, pHierarchyDimension, rootNodeKey, rootNodeText, TEXT_CHART_TITLE);







Startup Script:


HIERARCHY_FILTER.initialize(CROSSTAB_FILTER, cDrillDimension, cHierarchyRootNode, cHierarchyRootNodeDesc, cHierarchyPrefix, DS_1, HIERARCHY_COLLECTION, VARIABLE_COLLECTION, BACK_BUTTON,TEXT_CHART_TITLE, cPreviousNode);







Component Scripts:


Crosstab:


HIERARCHY_FILTER.drillDown(CROSSTAB_FILTER, cDrillDimension, DS_1, DS_2, HIERARCHY_COLLECTION, VARIABLE_COLLECTION, BACK_BUTTON, cHierarchyNodePrefix,cPreviousNode,cNotApplicable ,cAllMembers);







Home Button:


HIERARCHY_FILTER.back(DS_1, DS_2,HIERARCHY_COLLECTION, VARIABLE_COLLECTION, BACK_BUTTON, TEXT_CHART_TITLE, cDrillDimension, cPre






Back Button:


HIERARCHY_FILTER.back(DS_1, DS_2,HIERARCHY_COLLECTION, VARIABLE_COLLECTION, BACK_BUTTON, TEXT_CHART_TITLE, cDrillDimension, cPrviousNode, CROSSTAB_FILTER, cRowSelectionClass);






Conclusion


I hope the hierarchy filter approach described here is useful for those of you who have such a requirement.  The BIAPP in the animated demo above is attached.

Comments and questions are welcome as always.

Enjoy.

Blog Series Index:  Design Studio Innovation Series - Welcome



9 Comments
Labels in this area