Skip to Content

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

To report this post you need to login first.

38 Comments

You must be Logged on to comment or reply to a post.

    1. Tomasz Mackowski Post author

      Hi Krishna,

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

      (0) 
      1. 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.

        (0) 
        1. Tomasz Mackowski 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.

          (0) 
          1. rohan poludasu

            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.

             

            (0) 
      2. 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…

        (0) 
  1. 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.

    (0) 
      1. 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.

        (0) 
  2. Tim Malich

    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/SAPDatato the service description.

    PS: Thank you very much for this article Tomasz Mackowski. It really helped me a lot.

    (0) 
  3. Saurabha Joglekar

    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

    (0) 
  4. Scott Zheng

    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.

    (0) 
    1. 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

      (0) 
      1. Scott Zheng

        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

        (0) 
  5. 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?

    (0) 
    1. 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

      (0) 
      1. 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??

        (0) 
        1. 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.

          (0) 
          1. 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

            (0) 
            1. 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

              (0) 
              1. 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” ?

                (0) 
                1. 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

                  (0) 
                2. 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’ }}”

                  (0) 
                  1. 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

                    (0) 
                      1. 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.

                        (0) 
                        1. 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”

                          (0) 
                3. 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 ?

                  (0) 
  6. Scott Zheng

    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.

    (0) 
  7. Prem M

    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

    (0) 
    1. 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

      (1) 
  8. 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!

    (0) 

Leave a Reply