TreeTable Odata binding
I was trying to use TreeTable control with Odata model binding. Documentation in this area is far from perfect and searching in SDN and internet returned more questions around this topic than answers.
After analyzing source code, unit tests and some intensive debugging sessions I managed to bind TreeTable to Odata model. In this blog I’d like to describe the steps required to achieve it in two different ways: using navigation properties and OData annotations.
Expected result of this example is to have an OData service returning hierarchy data without limitation of depth and simple UI5 application to display the data in TreeTable.
1. Hierarchy using navigation properties
Odata Service
Create new OData service
Create Entity: Node
Add Properties:
Id (key) |
|
Name |
|
ParentId |
|
Create Entity Set: Nodes
Create Association: NodeToParent
Create Navigation Property: ChildNodes
All Artifacts:
Implement NODES_GET_ENTITYSET in DPC_EXT class
METHOD nodeset_get_entityset.
DATA: ls_entity LIKE LINE OF et_entityset,
ls_key LIKE LINE OF it_key_tab.
CLEAR es_response_context–inlinecount.
* no navigation path – top level call
IF lines( it_navigation_path ) = 0.
DO 3 TIMES.
ls_entity–id = sy–index.
CONDENSE ls_entity–id.
ls_entity–name = |Node { ls_entity–id }|.
APPEND ls_entity TO et_entityset.
ENDDO.
es_response_context–inlinecount = lines( et_entityset ).
RETURN.
ENDIF.
READ TABLE it_key_tab INTO ls_key INDEX 1.
IF strlen( ls_key–value ) > 5. “stop here
RETURN.
ENDIF.
* this is a call using navigation from parent
* Read key of parent node and return child nodes
IF lines( it_navigation_path ) > 0.
DO 5 TIMES.
ls_entity–parentid = ls_key–value.
ls_entity–id = |{ ls_entity–parentid }.{ sy–index }|.
CONDENSE ls_entity–id.
ls_entity–name = |Node { ls_entity–id }|.
APPEND ls_entity TO et_entityset.
ENDDO.
es_response_context–inlinecount = lines( et_entityset ).
RETURN.
ENDIF.
ENDMETHOD.
UI5 Application
Create UI5 application – index.html
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv='Content-Type' content='text/html;charset=UTF-8' />
<script src="resources/sap-ui-core.js" id="sap-ui-bootstrap"
data-sap-ui-libs="sap.ui.commons,sap.ui.table"
data-sap-ui-theme="sap_bluecrystal">
</script>
<script>
//Create an instance of the table control
var oTable = new sap.ui.table.TreeTable({
columns : [ new sap.ui.table.Column({
label : "Id",
template : "Id"
}), new sap.ui.table.Column({
label : "Name",
template : "Name"
}) ],
selectionMode : sap.ui.table.SelectionMode.Single,
enableColumnReordering : true,
});
//Create a model and bind the table rows to this model
//navigation service
var sServiceUrl = "/sap/opu/odata/sap/ZT38MP_TM_HIER_SRV/";
var oModel = new sap.ui.model.odata.v2.ODataModel(sServiceUrl, { useBatch : true });
oTable.setModel(oModel);
//navigation service binding
oTable.bindRows({
path : "/Nodes",
parameters : {
expand : "ChildNodes",
navigation : {
'Nodes' : 'ChildNodes'
}
}
});
//Bring the table onto the UI
oTable.placeAt("content");
</script>
</head>
<body class="sapUiBody" role="application">
<div id="content"></div>
</body>
</html>
Result:
2. Hierarchy using annotations
We will use following annotations: hierarchy-node-for, hierarchy-level-for, hierarchy-parent-node-for, hierarchy-drill-state-for
List of all SAP annotations for OData Version 2.0 http://scn.sap.com/docs/DOC-44986
OData service
Create new OData service
Create Entity: Node
Add Properties:
Id (key) |
|
Name |
|
ParentId |
|
Level |
|
DrilldownState |
|
Create Entity Set: Nodes
All Artifacts:
Go to Model class (MPC_EXT) and redefine DEFINE method to add annotations:
METHOD define.
super->define( ).
DATA:
lo_annotation TYPE REF TO /iwbep/if_mgw_odata_annotation, “#EC NEEDED
lo_entity_type TYPE REF TO /iwbep/if_mgw_odata_entity_typ, “#EC NEEDED
lo_complex_type TYPE REF TO /iwbep/if_mgw_odata_cmplx_type, “#EC NEEDED
lo_property TYPE REF TO /iwbep/if_mgw_odata_property, “#EC NEEDED
lo_entity_set TYPE REF TO /iwbep/if_mgw_odata_entity_set. “#EC NEEDED************************************************************************************************************************************ ENTITY – Node***********************************************************************************************************************************
lo_entity_type = model->get_entity_type( iv_entity_name = ‘Node’ ).
************************************************************************************************************************************Properties***********************************************************************************************************************************
lo_property = lo_entity_type->get_property( iv_property_name = ‘Id’ ).
lo_annotation = lo_property->/iwbep/if_mgw_odata_annotatabl~create_annotation( iv_annotation_namespace = /iwbep/if_mgw_med_odata_types=>gc_sap_namespace ).
lo_annotation->add(
iv_key = /iwbep/if_ana_odata_types=>gcs_ana_odata_annotation_key–hierarchy_node_for
iv_value = ‘Id’ ).
lo_property = lo_entity_type->get_property( iv_property_name = ‘Level’ ).
lo_annotation = lo_property->/iwbep/if_mgw_odata_annotatabl~create_annotation( iv_annotation_namespace = /iwbep/if_mgw_med_odata_types=>gc_sap_namespace ).
lo_annotation->add(
iv_key = /iwbep/if_ana_odata_types=>gcs_ana_odata_annotation_key–hierarchy_level_for
iv_value = ‘Id’ ).
lo_property = lo_entity_type->get_property( iv_property_name = ‘ParentId’ ).
lo_annotation = lo_property->/iwbep/if_mgw_odata_annotatabl~create_annotation( iv_annotation_namespace = /iwbep/if_mgw_med_odata_types=>gc_sap_namespace ).
lo_annotation->add(
iv_key = /iwbep/if_ana_odata_types=>gcs_ana_odata_annotation_key–hierarchy_parent_node_for
iv_value = ‘Id’ ).
lo_property = lo_entity_type->get_property( iv_property_name = ‘DrilldownState’ ).
lo_annotation = lo_property->/iwbep/if_mgw_odata_annotatabl~create_annotation( iv_annotation_namespace = /iwbep/if_mgw_med_odata_types=>gc_sap_namespace ).
lo_annotation->add(
iv_key = /iwbep/if_ana_odata_types=>gcs_ana_odata_annotation_key–hierarchy_drill_state_for
iv_value = ‘Id’ ).
ENDMETHOD.
Implement NODES_GET_ENTITYSET
METHOD nodeset_get_entityset.
DATA: ls_filter LIKE LINE OF it_filter_select_options,
ls_filter_prop LIKE LINE OF ls_filter–select_options.
DATA: ls_entity LIKE LINE OF et_entityset,
lv LIKE sy–index.
CLEAR es_response_context–inlinecount.
READ TABLE it_filter_select_options INTO ls_filter WITH KEY property = ‘ParentId’.
IF sy–subrc = 0.
READ TABLE ls_filter–select_options INTO ls_filter_prop INDEX 1.
DO 5 TIMES.
ls_entity–parentid = ls_filter_prop–low.
ls_entity–id = |{ ls_entity–parentid }.{ sy–index }|.
CONDENSE ls_entity–id.
ls_entity–name = |Node { ls_entity–id }|.
IF strlen( ls_entity–id ) > 5. “stop here
ls_entity–drilldownstate = ‘leaf’.
ENDIF.
APPEND ls_entity TO et_entityset.
ENDDO.
ENDIF.
READ TABLE it_filter_select_options INTO ls_filter WITH KEY property = ‘Level’.
IF sy–subrc = 0.
READ TABLE ls_filter–select_options INTO ls_filter_prop INDEX 1.
IF ls_filter_prop–low = 0.
DO 3 TIMES.
ls_entity–id = sy–index.
CONDENSE ls_entity–id.
ls_entity–name = |Node { ls_entity–id }|.
APPEND ls_entity TO et_entityset.
ENDDO.
es_response_context–inlinecount = lines( et_entityset ).
RETURN.
ENDIF.
ENDIF.
ENDMETHOD.
UI5 Application
Create UI5 application – index.html
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv='Content-Type' content='text/html;charset=UTF-8' />
<script src="resources/sap-ui-core.js" id="sap-ui-bootstrap"
data-sap-ui-libs="sap.ui.commons,sap.ui.table"
data-sap-ui-theme="sap_bluecrystal">
</script>
<script>
//Create an instance of the table control
var oTable = new sap.ui.table.TreeTable({
columns : [ new sap.ui.table.Column({
label : "Id",
template : "Id"
}), new sap.ui.table.Column({
label : "Name",
template : "Name"
}) ],
selectionMode : sap.ui.table.SelectionMode.Single,
enableColumnReordering : true,
});
//Create a model and bind the table rows to this model
//annotation service
var sServiceUrl = "/sap/opu/odata/sap/ZT38MP_TM_HIER2_SRV_01/";
var oModel = new sap.ui.model.odata.v2.ODataModel(sServiceUrl, { useBatch : true });
oTable.setModel(oModel);
//annotation service binding
oTable.bindRows({
path : "/Nodes",
parameters : {
countMode: "Inline",
numberOfExpandedLevels : 2
}
});
//Bring the table onto the UI
oTable.placeAt("content");
</script>
</head>
<body class="sapUiBody" role="application">
<div id="content"></div>
</body>
</html>
Result:
Very useful. Especially using annotations. There seems to be another way usigng sap.ui.model.odata.v2.ODataTreeBinding
Hi Krishna,
Actually sap.ui.model.odata.v2.ODataTreeBinding is used internally by TreeTable in both my examples - annotations and navigation properties.
Ok. Thanks Tomasz.I have a question.
As I understand every level is fetched by an OData call here. That is whenever I expand a node, a Odata call gets triggered. Can I send/fetch multiple level data in one go? I need to reduce the number of server round trips.
Hi,
You can use numberOfExpandedLevels parameter of bindRows to prefetch data.
Combining it with useBatch : true will ensure it goes in one http request.
Hello,
I could successfully implement the odata service and use it in the ui5 application. I need to implement a search function in the ui5 app which should search the entire tree including the child nodes. But I don’t see the child nodes data stored in any model all I can see is the uri for getting the children nodes.Can anyone help me solve this problem.
Thanks
Rohan Poludasu.
Hi Tomasz,
We have a similar scenario ,where we are binding a Tree table control to a XSODATA collection ,which is giving heirarchical data .But the issue is ,its even displaying the Level 2 and level 3 data as root node and we certailnly dont want that .The tree control as a property - 'rootLevel ' but it says its only applies for Annotation.Have you come across scenario on how to set root level similarly with using oData .
Appreciate any pointer on this...
Hi everyone,
how can I pass additional parameters using $batch requests?
It is:
GET NodeSet?$filter=ParentId%20eq%20%271.1.2%27&$skip=0&$top=110&$inlinecount=allpages?Currency=EUR
where currency would be read from other dropdown in this view.
using filters parameter of rows binding
Note: It works in versions >1.30
ODataTreeBinding: Filters have no effect | SCN
https://sapui5.hana.ondemand.com/sdk/#releasenotes.html
1.30
Thanks, filtering works for me.
But I have another issue with this element. How to achieve dynamic height of TreeTable control? I can't find any event which is called after earch expand / collapse and before rendering (to change VisibleRowCount). What's more no matter which VisibleRowCountMode I use, after expansion scrollbar does not appear.
Hi,
can anyone explain me the values for drill-down-state? What's the purpose of this attribute?
Regards
Tobias
Hi,
It's described here: SAP Annotations for OData Version 2.0
Look for description of 'hierarchy-drill-state-for'.
I do have a none recursive navigation structure which i can use with the navigation parameter like this:
navigation : {
'NodeLevel1Set' : 'toNodeLevel2Set',
'NodeLevel2Set' : 'toNodeLevel3Set'
// and so on
}
Does anyone know if it's possible to lazy load the sub-nodes after expanding the parent node?
BTW:
I don't use an OData service from SEGW therefore I had to extend my $metadata with the annotation tag sap:schema-version="1" in the Schema and in the EntityType as well.
Of course it's also necessary to add the namespace xmlns:sap="http://www.sap.com/Protocols/SAPData" to the service description.
PS: Thank you very much for this article Tomasz Mackowski. It really helped me a lot.
Hi Tomasz,
I tried the first method, it is working fine.
But output I get does not have the expand sign on load, once I click, then it appears.
I tried the second Method, but I do not get any data in the app.
Dev Tools shows error "A navigation paths parameter object has to be defined - "
Please guide.
--
Regards
Saurabha J
Hi Tomasz!
Could you please show the metadata file with hierarchy annotations?
Regards,
Eugeny
Thanks for sharing 🙂 !
Helpful Post, Thanks 🙂
It's very helpful post. Thank you so much.
I have a question, in annotation part, why we need to define MPC_EXT?
Thanks in advance.
Hi Scott,
This question is very unspecific. First of all you need to put your code to create new annotations in MPC_EXT because the MPC class is generated and should not be changed. If you trigger a new generation in the Gateway Builder (SEGW) it would be overwritten and all your changes would be lost. The annotations mentioned in this article are necessary to tell the SAPUU5 TreeTable how the data is structured. The data in the OData Collection binded to your tree has a flat structure. The annotations are interpreted by the SAPUI5 TreeTable and the tree makes a hierarchy out of it.
Best regards
Tobias
Hi Tobias,
Thank you so much for your kindly feedback. I really appreciated it even though I don't quite understant. Because I am the newbie in the OData, I believe I must asked so many silly questions,haha.so I still need to learn a lot.
Best regards
Scott Zheng
Hello Tomasz Mackowski,
many thanks for all your research and sharing this in this excellent blog!
Best regards,
Peter
Hi,
it seems, that this function pumps our system. which means it sucks all ressources.
Is there a way to make one single call to the System and get all entities? so it does not need to make additional calls?
Hi Michael,
that's actually possible. You can simply make one OData request to the backend (before you even instansiate the Tree) and get all the data you want in JSON format. Just make sure your JSON looks like Tomasz explained in his second solution. Just bind this JSON to the tree after it's instansiated. This way you could even think about getting the data in packages from the backend (e.g. 500 entries per call). This approach is surely more development effort but it gives you much for flexability.
Regards
Tobias
hmmm.. for now, i tried to get all entities in one call inclusive the annotation, but the control does show the rows flat and not hierarchycal... my next idea is to set a batch-group... perhaps it makes just one call then? or does the gateway in the back makes an rfc-call for each hierarchylevel??
Hard to say why it's showing the data flat instead of hierarchical. I guess there is an issue with the annotations. This should be easy to find when you just debug the tree code in the standard library and see why it's not creating any parent/child relations. Never used batch-groups. Hard to say if this is applicable for your needs.
The Problem is, that (also in Tomasz example) there is a call for each parent to the system. It seems that the control can't render a complete loaded Entityset with Annotations. I think i have to create an OSS, because debugging the TreeTable-Classes in SAP UI5 didn't help... it just shows, that he goes trough all items of the collection. it checks the drill-donw-state, but not it it has really children and it also does not hide "non-visible"-Items
I am just one step further... i inserted the Annotation "hierarchy-node-descendant-count-for" and it now tries to render the tree.. not right until now, but it looks better
Hi, I am also doing the Same. My treetable is in XML view and I have assigned all treeAnnotationProperties including "descedentcountfor", for which, I am using same property as hierarchicalLevel but its not rendering properly. Do you know what kind of data we need for "hierarchy-node-descendant-count-for" ?
Hi Patel,
it's an annotation that needs to hold the information which property in your odata model holds the information for how many childs a single node actually has. The actual property should be a number of course since it's a count.
Regards
Tobias
Hi, i did something different... i decided to change the operationmode to Client. So i can return the complete hierarchy which is perfect in combination with hana, because of performance.
therefor i used:
Hi Roger,
that works well for "small" data sets. So you do not use lazy load option that are very useful in some scenarios. For huge amount of data you need to transfer all to the client and javascript resp. SAPUI5 controls sometimes behave very slow for huge data sets.
This consumes a lot of front end memory.
Regards
Tobias
i have a tree with 5000 entries and it is much faster for complete extend... so i just can prefer this.
best regards!
I didn't want to criticize your approach. It can be totally valid. Earlier this year I tried the very same with over 10000 records and had some memory issues. The browser was lagging very much. Really bad user experience.
do you know how to configure the binding to get the "hierlevel-call" because i got it for a short time and after that it just uses the "node-calling"
Hi, let me get this straight, so if I have following treetable:
For this treetable, descendant could be like following?
A – 5
B – 2
C – 0
D – 0
E – 1
F – 0
G – 4
H – 0
I – 2
J – 0
K – 0
Is that what you are saying ?
My oData doesn’t have any property that has count like this ?
Hi Tomasz,
Thank you so much for your example. It really helpful.
For the sample of using annotation, I found the height cannot be adjusted dynamically. if the item height exceed the total height, the items cannot display completed, and there is even no scrollbar appeared.
Dose anyone has the same problem?
Thanks for your suggestion.
Thanks.
Hi Tomasz,
Have you ever come across an error in the OData call ?
This call to bind rows gives an error
because the URL in the query string gets automatically appended with parameters such as top with a long number
$expand=ChildNodes&$skip=0&$top=1.7976931348623157e+308&$inlinecount=allpages
Thanks !
P
Hi all,
I have the same number error at $top. First call works fine but the second has a wrong number. Why?
Any news about that?
Thanks
Beat
My problem is quite similar, but instead of display the hierarchy of nodes, we intend to display the hierarchy of its child nodes with one specific node .
So within one specific node's object page, we want a tree table to display the hierarchy of its child nods.
So I wrote like below, but did not work( the table has navigation property "to_Child" and "to_Parent" ).
How to achieve that ? Thanks!
Hi, Tomasz!
Very good article.
I guess there's a little mistake on second example (annotations) on NODES_GET_ENTITYSET method:
What do you think?
Thanks for your elightening article!
Rafael M.
wow! thank you thank you!!!