Skip to Content
Author's profile photo Tomasz Mackowski

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)

  1. Edm.String

Name

  1. Edm.String

ParentId

  1. Edm.String

Create Entity Set: Nodes

Create Association: NodeToParent

/wp-content/uploads/2015/10/1_816589.png

/wp-content/uploads/2015/10/2_816605.png

Create Navigation Property: ChildNodes

/wp-content/uploads/2015/10/3_816606.png

All Artifacts:

/wp-content/uploads/2015/10/4_816607.png

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_contextinlinecount.

* no navigation path – top level call
   
IF lines( it_navigation_path ) = 0.
     
DO 3 TIMES.
        ls_entity
id = syindex.
       
CONDENSE ls_entityid.
        ls_entity
name = |Node { ls_entityid }|.
       
APPEND ls_entity TO et_entityset.
     
ENDDO.

      es_response_contextinlinecount = lines( et_entityset ).
     
RETURN.
   
ENDIF.

    READ TABLE it_key_tab INTO ls_key INDEX 1.
   
IF strlen( ls_keyvalue ) > 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_entityparentid
= ls_keyvalue.
        ls_entity
id = |{ ls_entityparentid }.{ syindex }|.
       
CONDENSE ls_entityid.
        ls_entity
name = |Node { ls_entityid }|.
       
APPEND ls_entity TO et_entityset.
     
ENDDO.
      es_response_contextinlinecount
= 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:

/wp-content/uploads/2015/10/5_816612.png

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)

  1. Edm.String

Name

  1. Edm.String

ParentId

  1. Edm.String

Level

  1. Edm.Int32

DrilldownState

  1. Edm.String

Create Entity Set: Nodes

All Artifacts:

/wp-content/uploads/2015/10/6_816613.png

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_keyhierarchy_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_keyhierarchy_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_keyhierarchy_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_keyhierarchy_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_filterselect_options.
   
DATA: ls_entity LIKE LINE OF et_entityset,
          lv       
LIKE syindex.

    CLEAR es_response_contextinlinecount.
   
READ TABLE it_filter_select_options INTO ls_filter WITH KEY property = ‘ParentId’.
   
IF sysubrc = 0.
     
READ TABLE ls_filterselect_options INTO ls_filter_prop INDEX 1.
     
DO 5 TIMES.
        ls_entityparentid
= ls_filter_proplow.
        ls_entity
id = |{ ls_entityparentid }.{ syindex }|.
       
CONDENSE ls_entityid.
        ls_entity
name = |Node { ls_entityid }|.
       
IF strlen( ls_entityid ) > 5. stop here
          ls_entitydrilldownstate
= ‘leaf’.
       
ENDIF.
       
APPEND ls_entity TO et_entityset.
     
ENDDO.
   
ENDIF.

    READ TABLE it_filter_select_options INTO ls_filter WITH KEY property = ‘Level’.

    IF sysubrc = 0.
     
READ TABLE ls_filterselect_options INTO ls_filter_prop INDEX 1.
     
IF ls_filter_proplow = 0.

        DO 3 TIMES.
          ls_entity
id = syindex.
         
CONDENSE ls_entityid.
          ls_entity
name = |Node { ls_entityid }|.
         
APPEND ls_entity TO et_entityset.
       
ENDDO.

        es_response_contextinlinecount = 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:

/wp-content/uploads/2015/10/7_816614.png

Assigned Tags

      40 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Krishna Kishor Kammaje
      Krishna Kishor Kammaje

      Very useful. Especially using annotations. There seems to be another way usigng sap.ui.model.odata.v2.ODataTreeBinding

      Author's profile photo Tomasz Mackowski
      Tomasz Mackowski
      Blog Post Author

      Hi Krishna,

      Actually sap.ui.model.odata.v2.ODataTreeBinding is used internally by TreeTable in both my examples - annotations and navigation properties.

      Author's profile photo Krishna Kishor Kammaje
      Krishna Kishor Kammaje

      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.

      Author's profile photo Tomasz Mackowski
      Tomasz Mackowski
      Blog Post Author

      Hi,

      You can use numberOfExpandedLevels parameter of bindRows to prefetch data.

      Combining it with useBatch : true will ensure it goes in one http request.

      Author's profile photo Former Member
      Former Member

      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.

       

      Author's profile photo Bishnu Priya Sahoo
      Bishnu Priya Sahoo

      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...

      Author's profile photo Jakub Plocki
      Jakub Plocki

      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.

      Author's profile photo Tomasz Mackowski
      Tomasz Mackowski
      Blog Post Author

      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

      • [FIX] TreeTable/ODataTreeBinding: Respect application filter
      Author's profile photo Jakub Plocki
      Jakub Plocki

      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.

      Author's profile photo Tobias Schnur
      Tobias Schnur

      Hi,

      can anyone explain me the values for drill-down-state? What's the purpose of this attribute?

      Regards

      Tobias

      Author's profile photo Tomasz Mackowski
      Tomasz Mackowski
      Blog Post Author

      Hi,

      It's described here: SAP Annotations for OData Version 2.0

      Look for description of 'hierarchy-drill-state-for'.

      Author's profile photo Former Member
      Former Member

      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.

      Author's profile photo Former Member
      Former Member

      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

      Author's profile photo Former Member
      Former Member

      Hi Tomasz!


      Could you please show the metadata file with hierarchy annotations?


      Regards,

      Eugeny

      Author's profile photo Former Member
      Former Member

      Thanks for sharing 🙂 !

      Author's profile photo Former Member
      Former Member

      Helpful Post, Thanks 🙂

      Author's profile photo Former Member
      Former Member

      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.

      Author's profile photo Tobias Schnur
      Tobias Schnur

      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

      Author's profile photo Former Member
      Former Member

      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

      Author's profile photo Peter Gilberg
      Peter Gilberg

      Hello Tomasz Mackowski,

      many thanks for all your research and sharing this in this excellent blog!

      Best regards,

      Peter

      Author's profile photo Michael Roger
      Michael Roger

      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?

      Author's profile photo Tobias Schnur
      Tobias Schnur

      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

      Author's profile photo Michael Roger
      Michael Roger

      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??

      Author's profile photo Tobias Schnur
      Tobias Schnur

      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.

      Author's profile photo Michael Roger
      Michael Roger

      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

      Author's profile photo Michael Roger
      Michael Roger

      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

      Author's profile photo Priteshkumar Patel
      Priteshkumar Patel

      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" ?

      Author's profile photo Tobias Schnur
      Tobias Schnur

      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

      Author's profile photo Michael Roger
      Michael Roger

      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:

      { path : '/HierarchyCollection',  parameters: { useServersideApplicationFilters: false , operationMode:'Client', autoExpandMode:'Bundled' }}"

      Author's profile photo Tobias Schnur
      Tobias Schnur

      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

      Author's profile photo Michael Roger
      Michael Roger

      i have a tree with 5000 entries and it is much faster for complete extend... so i just can prefer this.

      best regards!

      Author's profile photo Tobias Schnur
      Tobias Schnur

      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.

      Author's profile photo Michael Roger
      Michael Roger

      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"

      Author's profile photo Priteshkumar Patel
      Priteshkumar Patel

      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 ?

      Author's profile photo Former Member
      Former Member

      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.

      Author's profile photo Former Member
      Former Member

      Hi Tomasz,

      Have you ever come across an error in the OData call ?

      			 oTable.bindRows({
      				 path : "/ETS_NODES",
      				 parameters : {
      				 expand : "ChildNodes",
      				 navigation : {
      				 'ETS_NODES' : 'ChildNodes'
      				 }
      				 }
      				 });

      This call to bind rows gives an error

      Invalid system query options value"

      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

      Author's profile photo Beat Birrer
      Beat Birrer

      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

      Author's profile photo Frank Liu
      Frank Liu

      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" ).

      rows="{ path: 'to_ChildNodes',
              parameters: { expand : 'to_ChildNodes', 
                            navigation : { 'ParentNodes' : 'to_ChildNodes' }
                           }
             }"

      How to achieve that ? Thanks!

      Author's profile photo Rafael Silva de Menezes
      Rafael Silva de Menezes

      Hi, Tomasz!

      Very good article.

      I guess there's a little mistake on second example (annotations) on NODES_GET_ENTITYSET method:

      "...
      
          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.
      
            "**** SUGGESTED CORRECTION - BEGIN
            es_response_context-inlinecount = lines( et_entityset )."I think you should add this line here
            "**** SUGGESTED CORRECTION - END
      
          ENDIF.
      
      "...

       

      What do you think?

      Thanks for your elightening article!

      Rafael M.

      Author's profile photo Ioan Radulescu
      Ioan Radulescu

      wow! thank you thank you!!!