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: 
12,007
Dimension members are one of the basic concepts in any analytics application. Every analytical app, dashboard or self-service tool contains member lists: inside of a Crosstab, on a chart axis or legends, on a filter control and so forth.

Therefore one of the very first features in Design Studio was an API to retrieve a member list and display it in a listbox, combobox etc. The API getMemberList is in the meantime replaced by getMembers. Let's assume that our data source DS_1 contains a dimension "customer_id" and we have a listbox LISTBOX_1 that we want to fill with the members from dimension "customer_id". This is easy and cab be done with the following script:
var members = DS_1.getMembers("customer_id", 100);
members.forEach(function(member) {
LISTBOX_1.addItem(member.internalKey, member.text);
});

What is interesting with this script?

  1. The functions has an argument to limit the number of members to be fetched. Dimensions can have a huge number of members and retrieving them can be very expensive.

  2. getMembers returns a array of quite interesting objects. They contains quite interesting fields and one method


The fields of a Member objects contain so-called presentations. Each member could have several, including several key types and texts with different length. Which text is returned by the "text" field could be e.g. configured in BEx Query Designer.

Very powerful is the function getAttributeMember. It allows you to access content from a so-called Display Attribute for each member. Let's extend our sample to also show the birthdates of our customers:
var members = DS_1.getMembers("customer_id", 100, ["birthdate"]);
members.forEach(function(member) {
var birthdayAttributeMember = member.getAttributeMember("birthdate");
LISTBOX_1.addItem(member.internalKey, member.text + " (" + birthdayAttributeMember.text + ")");
});

 

You see that I have added "birthdate" twice - ones as an array at the optional last parameter of getMembers. And as argument of getAttribributeMember. If I would remove the third parameter of getMembers, the script would still work, but would run much much slower! If you pass the display attributes that you need to getMembers, they will be retrieved with as part of the single roundtrip from BW or HANA. If you try to get them later, you might end up with an additional roundtrip per customer!

Also attribute members have multiple presentations, including keys and texts, therefore you have the same fields available. But and attribute member can't reference another attribute member.

 

Filter Behavior


Let's now see how our member list reacts on active filters. If I modify my script like this, what happens? Will getMembers return only one entry?
DS_1.setFilter("customer_id", "3872");

var members = DS_1.getMembers("customer_id", 100);
members.forEach(function(member) {
LISTBOX_1.addItem(member.internalKey, member.text );
});

 

No! The result is completely unchanged. Strange, isn't it? But if I have a Crosstab or Chart in my app, I'll see that the filter is work. It should only one customer.

Let's try something else:
DS_1.setFilter("city", "Albany");

var members = DS_1.getMembers("customer_id", 100);
members.forEach(function(member) {
LISTBOX_1.addItem(member.internalKey, member.text );
});

 

Now we might get a different result - or even the same. It depends on the "Member Access Mode" or "Read Mode of Members for Filtering". You can configure it per dimension, e.g. in Initial View Editor:



 

You see that I have configured "Values in Master Data". This means that filters have no influence on member lists at all, they contain always all members from master data.

The available modes depend on the data source type. The picture shows a HANA view. BW queries and Lumira Offline Datasets have different modes.

In Lumira 2.1 you can also set the Member Access Mode also with scripting:
DS_1.setMemberAccessMode("customer_id", "MODE_POSTED_VALUES");
DS_1.setFilter("city", "Albany");

var members = DS_1.getMembers("customer_id", 100);
members.forEach(function(member) {
LISTBOX_1.addItem(member.internalKey, member.text );
});

Now my listbox will only contain customers from Albany. Obviously the result of getMembers depends on filters of other dimensions but not on filters of the current dimension itself.

Members for Filtering


This behavior is by intention. APIs like getMembers are intended to provide some UIs for choosing the members for filter operations. If the memberlist itself would be filtered, the UI would be unusable - after you have filtered for a member you can never come back to another filter state.

You can nicely try it out using several list boxes to simulate a Facet Filter component. Each listbox is filled with members from a different dimension. Clicking some entries would include the member into the filter. You could achieve this with scripting. Much easier however is to use Property Binding. Internally Property Binding uses exactly the same functionality as getMembers.

Create a listbox and drag-and-drop the data source component to it. You will be prompted for a dimension. Repeat the step for all other dimensions. Configure all dimension using "Only values with Posted Data". In Initial View editor you might need to but them to the "Background Filter" basket.

As a result you have an app with cascading facet filters.



 

Members from Resulset


We learned that getMembers works, but it is intended to create UIs  that configure filters. Therefore in many cases it might get the wrong result and it is unnecessarily time consuming.

If you want to get exactly the members that you see in a Crosstab or Chart, you should retrieve the members from the resultset. This has two advantages:

  • All filters are applied. You get exactly the members that you see in the Crosstab.

  • It is much faster, as the resultset is usually anyway available.


Use the following script. Details about the resultset and the API getDataSelections can be found in this blog.
var selections = DS_1.getDataSelections({
"customer_id": "?"
});

selections.forEach(function(selection) {
var member = DS_1.getMember("customer_id", selection);
LISTBOX_1.addItem(member.internalKey, member.text);
});

 

With "member" you can do the same things as show above, e.g. retrieving other presentations and display attributes. However you should avoid using display attributes that are currently not shown in your resultset, as this could cause extra round trip to BW or HANA.

The members are retrieved as if you would search in a Crosstab in English reading order: first left-to-right then top-to-bottom. If the dimension is not the first on a its axis, the order might be a bit unexpected. The duplicate members that could also appear in such a case are automatically removed. The following picture shows this case: The "customer_id" dimension is placed as second dimension in rows axis after "city" using Key and Text presentations.


Getting a Single Member


Sometimes you have a member key and you need some presentation (e.g. the text) or a Display Attribute. The following snippet uses a constant key, but you can also use a variable.
var member = DS_1.getMember("customer_id", {
"customer_id": "3872"
});
APPLICATION.alert(member.text + " " + member.getAttributeMember("birthdate").text);

 

Again the warning: getAttributeMember can make the app very slow if used to frequently on data that is not yet cached.

 

Conclusion


It is important to understand that there are two ways how you can retrieve members:

  1. for filter UIs using getMembers

  2. from resulset using getDataSelections and getMember


I have explained the differences, advantages and disadvantages. I'm curious if there are other use cases that require new features or APIs for coming release. I'm looking forward to your comments.

 
19 Comments
Pierre
Explorer
0 Kudos
Thanks Reiner.

Again a nice post explaining important APIs introduced in Lumira 2.1.
Please keep up the good work on both, giving us insights on such topics and bringing Lumira 2.x even further with powerful features.

 
MustafaBensan
SAP Champion
SAP Champion
0 Kudos
Hi Reiner,

Once again a very informative post about great new features in Lumira 2.1.  I have the following feedback:

Firstly, I would like to thank the SAP Lumira Designer Team for introducing the very useful setMemberAccessMode() API and Alex Bruland for registering this as a feature request during the Lumira 2.0 EAC Program.  It's great to learn this feature request has been implemented so soon.  At the time, I had encountered an issue whereby cascading filters could not be applied to dynamically selected offline data sources because the read mode was defaulted to something other than "posted values" and it was not possible to change this default via scripting.  So now this issue is solved with setMemberAccessMode(), with the added bonus that it is useful for other scenarios as well, such as the examples you have provided.

Secondly, in response to your comment "I’m curious if there are other use cases that require new features or APIs for coming release", I have the following suggestions for new/enhanced features:

i)  As pointed out in this blog, a getMembers() call can be very expense from a performance perspective, especially if the second parameter for limiting the number of members to be fetched is set to a value significantly higher then the recommended 100.  However, in real-world scenarios, it is quite common to require the retrieval of a large number of dimension members for the purposes of building a custom filtering UI.  Therefore, to support such scenarios while not impacting performance, it would be very helpful to introduce a new "fetch" parameter in getMembers() whereby each call to getMembers() for a particular dimension would fetch the next n members based on the fetch parameter;

ii)  As an enhancement to the above request, it would also be useful to add a "search" parameter to getMembers() to provide a mechanism to further reduce the number of returned dimension members by searching for members that contain the specified search string;

iii)  A current limitation of getMembers() is that if there is an active hierarchy on the dimension specified, getMembers() ignores this and still returns individual member values rather than the hierarchy node values.  It would certainly enhance the use cases of getMembers() if any active hierarchy was taken into consideration when returning the member list.  This does not mean that a fully expanded hierarchy node list should be returned but at least to the hierarchy expansion level defined in the settings.

Thanks,

Mustafa.
0 Kudos
Hello Mustafa,

I understand that "wildcards" and "paging" would be useful for app or SDK components that want to replace the existing filter components. However Designer contains already quite some filter components that can be used out of the box and that have such features built-in.

For normal script I don't see too much value in such feature. Especially "paging" looks complex to be used in normal app: How would an app look like that calls getMembers with a start index and a length? Would you place little paging buttons beside your list box? Most likely not.

More sense I see is integrating such a paging feature into the existing property binding. On the other hand since 2.0 the Dimension Filter component has several visualization modes that replace the need to use a listbox and property binding.

For wildcard searches there might be some use cases in typical apps, e.g. the app could use a wildcard e.g. based on some user setting or role.

An API to get hierarchy nodes might make sense, but again I would assume that using a standard component is the better approach.

Anyway, you can contact PM or PO and see if those features make it on the backlog.

 

Thanks and best regards,

Reiner.
MustafaBensan
SAP Champion
SAP Champion
0 Kudos
Hi Reiner,

Thanks for your detailed feedback.  You are right, the "wildcard" and "paging" suggestions were more aimed at supporting the development of SDK filter components rather than for normal scripting.  While I agree that there are now a variety of standard filter components available with these features built in, I think there is a case for providing such flexibility in SDK components to allow purpose-built custom filters to be developed with specialised UIs.

I will pursue with PM/PO as you've suggested.

Regards,

Mustafa.
former_member234401
Active Participant
0 Kudos
Hi Reiner

Thanks for your great post -it's very useful

One question - the getDataSelections is hard coded .. is there any option to make this dynamic .. It assumes that the reporting dimension is constant.

var selections = DS_1.getDataSelections({"customer_id": "?"})

Can I in some way replace the dimension with a variable (local or Global) or something else - or do I have to anticipate the dimension the users selects (if dimension swap is enabled)

 

Regards,

René
0 Kudos
Hello René,

you are right: The JSON syntax of our data selection expressions does not allow a dynamic dimension name.

Therefore in 2.2 I have added two things:

  • Convert.stringToDataSelection – which like a JSON.parse will allow you to parse a string into a data select JSON.

  • With an existing data selection JSON you can also add entries with


var dataSelection = Convert.stringToDataSelection(); // creates an emty JSON
dataSelection["myDim"] = ["myMember1"];

If you need the feature urgently in 2.1, you could create an SDK component with an ZTL API like
ResultSetSelectionByString stringToDataSelection(String s) {*
return JSON.parse(s);
*}

 

Regards,

Reiner.

 
former_member234401
Active Participant
0 Kudos
Hi Reiner

Thanks for your reply and the future development in 2.2. Currently I will stick with the standard functions in Lumira designer 2.1.

If it was possible to change formatter function in a binding via a script .. that would also be great.
0 Kudos
Hello René,

as I have explained in  "Dynamic Apps in Lumira 2.1 – Property Binding" you can modify a property binding afterwards. But you will end up with the same issue, because the setDataSelection requires also such a JSON.

 

Regards,

Reiner.
0 Kudos
 

hello reiner

many thanks for this very helpful post

in the lumira 2.0, thw function getDataSelections seems to be not replace by getData.

can you please share how the present in a listbox data coming from a filtered data sources with the new lumira version ?

thanks

 
0 Kudos
Your mean something like
var selections = DS_1.getDataSelections({
"product_id": "*",
"(MEASURES_DIMENSION)":"store_sales"
});

selections.forEach(function(selection, index) {
var cell = DS_1.getData("", selection);
LISTBOX_1.addItem(index + "", cell.formattedValue);
});

You find more sample with getDataSelections and getMember in this blog.
mweil-valantic
Explorer
0 Kudos
Hi Reiner,

great post!

I'm looking for a way to figure out what rows are are shown/hidden in a crosstab that has member hierarchy enables.

The crosstab component in itself only allows to retrieve selected rows, but there's no "setSelection" method that would allow for a complete selection of a column by coding.

Using "getMembers" method on the DataSource gets me all rows but the result is not influenced by any crosstab collapsing/expanding action. I guess that's by design anyway because the query data is only passed to the crosstab component which then does any manipulation its "own data container".

Any way you see given the currently available functionality to achieve this? Maybe with 2.2?

 

best regards,

Matthias

 
0 Kudos
Hello Matthias,

getDataSelections give you exactly the resultset that is shown in the Crosstab. There is no feature to detect if a member is a node and if its is expanded or collapsed. But you would see the difference in you getDataSelections iteration, because for collapsed nodes you would not see any child members.

 

Regards,

Reiner.
mweil-valantic
Explorer
0 Kudos
Thanks Reiner! I'll give it a try.

 

best regards,

Matthias
Former Member
0 Kudos

Is there any possibility to select the dimension  filtered by other dimension?

 

var member = DS_1.getMember(‘customer_Name’ , {“customer_id” : “153”});

and display selected cusomer Name  :  member.text. 

Any idea ??

 

0 Kudos
Hello Ola,

first of all: in multi-dimension analytics dimensions are usually independent - therefore they are called "dimensions". But "customer_Name" and "customer_id" don't sound independent. If they would be selecting one customer ID would not necessarily result in one customer_Name - it could be anything. In OLAP you would model "customer_Name" as an attribute of Customer.

Anyway, if you know that you have dependent dimension, you can achieve what you want:

 
var selection = DS_1.getDataSelections({"customer_id": "153", "customer_Name": "*"});
selection.forEach(function(selection) {
var member = DS_1.getMember("customer_Name", selection);
APPLICATION.alert(member.text);
});

0 Kudos
Hi Mustafa,

Could you kindly tell me the value list of MemberAccessMode for BW Query? I would like to set to "posted values", and I could NOT find out any document about that, thank you very much~

My codes: DS_1.setMemberAccessMode("0BATCH", MemberAccessMode memberAccessMode);
roemers
Discoverer
0 Kudos
Thank you for this great post.

I need the MemberAccessMode for Master Data Value. Unfortunately it is undocumented see API Reference MemberAccessModi

Could you please tell my all possible values for MemberAccessMode?

I would like to add a context menu to switch the AccessMode in Lumira Designer.

My coding:

var dsname = CONTEXT_MENU.getDataSource();

var dims = dsname.getDimensions();
dims.forEach(function(dim, index){

DS_1.setMemberAccessMode(dim, [unknown for Master Data Value]);

});

My next issue is that the DataSource is unassigned. The BW query ist dynamically assigned via URL parameter. I want to retrieve all dimensions from the dynamical data source and use the coding above to switch the MemberAccessMode. Is this possible?


Error Message

0 Kudos
Hello Stephan,

best you create a little helper app that uses the same datasource, but statically without "Load in script". Then you can easily crete a dummy script to use content-assists (Ctrl+Space) to see all available modes. Those you can copy into your real app as string.

Regards,

Reiner.
0 Kudos

Hi @reiner_hille-doering ,

I have a scenario here in spreadsheet component . The component is out come after clicking a stacked bar chart and it should show the value count and it is showing correct count but It is also displaying 0 in the measure which are not counted and I have enable suppress 0's option every time I am looking into the spreadsheet. Please could you help me how the 0 suppression without manually suppressing the o's. 

Thanks,