Skip to Content
Author's profile photo Mike Doyle

Simple Mapping of UI5 Tree Table to JSON Model

I love trees

In my experience it’s very common to need hierarchies in apps, and a tree is a great way to represent them.  Of course, we need to be careful where we use them, especially when it comes to mobile devices with small screens.  We should always have a good user experience in mind.  That said, I’m sure trees will find a place in many UI5 apps.

The problem

You can see the UI5 Treetable control in the Explored section of the UI5 SDK (sap.ui.table.TreeTable).  The sample app maps the control to a JSON model, and the JSON file begins like this

  1. {
  2.   “catalog”: {
  3.   “clothing”: {
  4.   “categories”: [
  5.   {“name”: “Women”, “categories”: [
  6.   {“name”:“Clothing”, “categories”: [
  7.   {“name”: “Dresses”, “categories”: [
  8.   {“name”: “Casual Red Dress”, “amount”: 16.99, “currency”: “EUR”, “size”: “S”},
  9.   {“name”: “Short Black Dress”, “amount”: 47.99, “currency”: “EUR”, “size”: “M”},
  10.   {“name”: “Long Blue Dinner Dress”, “amount”: 103.99, “currency”: “USD”, “size”: “L”}
  11.   ]},
  12.   {“name”: “Tops”, “categories”: [
  13.   {“name”: “Printed Shirt”, “amount”: 24.99, “currency”: “USD”, “size”: “M”},
  14.   {“name”: “Tank Top”, “amount”: 14.99, “currency”: “USD”, “size”: “S”}
  15.   ]},

This is what you might think of as a recursive JavaScript data format.  Each node in the structure can have an attribute called categories which contains an array of child node objects.  Each child node can similarly contain an array of its own children.

This kind of structure is common in the JavaScript world, but not so common in the ABAP world.  If I expand an HR org. structure with function module RH_GET_STRUC (or a PM functional location tree with PM_HIERARCHY_CALL_REMOTE) I will get the data in a flat format.  There will be one table row per node, with each row having an ID, a level, a parent ID and a text.

How can we convert that kind of data to the JSON format you see above?

The solution

Tomasz Mackowski has explained here TreeTable Odata binding how you can map the TreeTable to an OData service.  Today I’d like to show you another approach, which might be simple for you to implement.  It maps the control to a JSON model, just like the sample used in the SDK.  If your hierarchy is small, and you don’t need lazy-loading (i.e. you prefer eager-loading), it might be a good way to go.  Those SAP-standard function modules are quite efficient at returning a few levels of a tree in one go.  A large number of tiny, unbatched OData calls (a consequence of lazy-loading) can be disproportionately ‘expensive’. You may be able to make an asynchronous call to get the tree data before the user reaches the point where the tree is shown, and hence they won’t need to wait before using the tree.

With this approach the OData service can be very simple, returning the tree data in a flat structure of one entity per node.  As you can see, the JavaScript is very simple too.  This function does all the transformation we need, in about 25 lines.

    transformTreeData: function(nodesIn) {

         var nodes = [];          //'deep' object structure
         var nodeMap = {};      //'map', each node is an attribute

         if (nodesIn) {
              var nodeOut;
              var parentId;

              for (var i = 0; i < nodesIn.length; i++) {
                   var nodeIn = nodesIn[i]
                   nodeOut = {      id: nodeIn.ID,
                                            text: nodeIn.Text,
                                            type: nodeIn.Type,
                                            children: [] };


                   parentId = nodeIn.ParentID;

                   if (parentId && parentId.length > 0) {
                        //we have a parent, add the node there
                        //NB because object references are used, changing the node
                        //in the nodeMap changes it in the nodes array too
                        //(we rely on parents always appearing before their children)
                        var parent = nodeMap[nodeIn.ParentID];

                        if (parent) {
                             parent.children.push(nodeOut);
                        }

                   } else {
                        //there is no parent, must be top level
                        nodes.push(nodeOut);
                   }

                   //add the node to the node map, which is a simple 1-level list of all nodes
                   nodeMap[nodeOut.id] = nodeOut;

              }

         }

         return nodes;

    }

The only explanation required (I hope) is around the use of the nodeMap.  Each node must be added to the children array of its parent.  This could be tricky due to the recursive nature of the structure (how do we find the parent?).  To make it simple we create a ‘map’ (in fact just an object) which contains an entry for each node, keyed on the nodeId.  This map contains a reference to the node, not the node itself.  Therefore if we add a node to the parent in the map it is also added in the main structure (nodes).

The function transforms this (an array of simple objects)….

Before.jpg

..to this (a complex, recursive object), which matches the example from the SDK shown above

After.jpg

If we store this object in a JSON model we can map our TreeTable directly to it, and the result looks like this:

 

Result.jpg

Options are good

In many cases you will want your tree to lazy-load and it will be best to map the TreeTable control directly to an OData model.  I hope that this example will prove helpful for the ‘other’ times.  Perhaps the core transformation function will come in handy for whenever you want to use a deep, recursive JavaScript object, even without the TreeTable control.

Appendix: Build the demo app

If you would like to build a demo app, which you can then extend to meet your own requirements then then follow these steps.  To keep things simple we will use a local JSON-format file rather than an external datasource.

  • Log into Web-IDE.  Create a new project from the SAPUI5 Application template.  I used the name TreeTableDemo and the namespace com.scn.demo.treetable.  I left the view details at the default of type=XML and name = View1
  • Add the new functions readFile, transformTreeData and setModelData to Component.js (see example code below)NB Be careful with your commas, you must have one after every function except the last.  Add the following 3 lines to the end of the Init function:
var flatData = this.readFile();
var deepData = this.transformTreeData(flatData);
this.setModelData(deepData);
  • Copy the code sample below into your View1.view.xml. NB You must be sure to include the extra xmlns entries, but don’t alter your controllerName.
  • Create a new file, FlatData.json, in the model folder.  Copy the contents from the sample below.
  • Add this line to your i18n.properties file: treetitle=My Team
  • Right-click on your project and choose Run->Run as->Web Application to launch a preview
  • Change the code, try new things, change the data in the JSON file, add an external datasource……..

 

Component.js

sap.ui.define([
	"sap/ui/core/UIComponent",
		"sap/ui/Device",
		"com/scn/demo/treetable/model/models"
],             function(UIComponent, Device, models) {
		"use strict";

	return UIComponent.extend("com.scn.demo.treetable.Component", {

		metadata: {
			manifest: "json"
		},

		/**
		 * The component is initialized by UI5 automatically during the startup of the app and calls the init method once.

		 * @public
		 * @override
		 */

		init: function() {
			// call the base component's init function
			UIComponent.prototype.init.apply(this, arguments);

			// set the device model
			this.setModel(models.createDeviceModel(), "device");

			var flatData = this.readFile();
			var deepData = this.transformTreeData(flatData);
			this.setModelData(deepData);
		},

		readFile: function() {

			var flatData = null;
			//load the data from the JSON file
			//NB same format as gateway service could be
			var inModel = new sap.ui.model.json.JSONModel();
            		inModel.loadData("/webapp/model/FlatData.json", "", false);
 			
			var data = inModel.getData();

            		if (data) {
				flatData = data.nodes;
                        }

			return flatData; 
		},

		transformTreeData: function(nodesIn) {

			var nodes = [];		//'deep' object structure
            		var nodeMap = {};	//'map', each node is an attribute

			if (nodesIn) {

            			var nodeOut;
            			var parentId;

		            	for (var i = 0; i < nodesIn.length; i++) {
            				var nodeIn = nodesIn[i];
            				nodeOut = { id: 	nodeIn.ID,
            		            		    text:	nodeIn.Text,
           		            		    type:	nodeIn.Type,
            		            		    children: [] };
 	            
			parentId = nodeIn.ParentID;


            		
			if (parentId && parentId.length > 0) {
           					//we have a parent, add the node there
            					//NB because object references are used, changing the node
            					//in the nodeMap changes it in the nodes array too
            					//(we rely on parents always appearing before their children)
            					var parent = nodeMap[nodeIn.ParentID];

            					if (parent) {
							parent.children.push(nodeOut);
            					}
            				} else {
						//there is no parent, must be top level
           					nodes.push(nodeOut);
            				}

			//add the node to the node map, which is a simple 1-level list of all nodes

            				nodeMap[nodeOut.id] = nodeOut;

            			}

            		}
      
        		return nodes;
		},
            
		setModelData: function (nodes) {
        		//store the nodes in the JSON model, so the view can access them
		        var nodesModel = new sap.ui.model.json.JSONModel();
            		nodesModel.setData({nodeRoot: { children: nodes }});
                        this.setModel(nodesModel, "nodeModel");
		}
	});
});

 

View1.view.xml    

<mvc:View xmlns:html="http://www.w3.org/1999/xhtml"
		  xmlns="sap.m" xmlns:table="sap.ui.table" xmlns:mvc="sap.ui.core.mvc"
		  controllerName="com.scn.demo.treetable.controller.View1">
	<App>
		<pages>
			<Page title="{i18n>title}" class="sapUiSizeCompact">
				<content>
					<table:TreeTable	id="TreeTable"
			                    		rows="{path:'nodeModel>/nodeRoot', parameters: {arrayNames:['children']}}"
			                    		enableSelectAll="false"
			                    		expandFirstLevel="true">	
				    	<table:columns>
		                    <table:Column width="13rem">
		                        <Label text="{i18n>treetitle}"/>
		                        <table:template>
		                            <Text text="{nodeModel>text}" />
		                        </table:template>
		                    </table:Column>
	                	</table:columns>
            		</table:TreeTable>
				</content>
			</Page>
		</pages>
	</App>
</mvc:View>

 

FlatData.json    

{ "nodes": [
		{ "ID": "O100",		"Text": "Software Development",	"ParentID": "", 		"Type": "O" },
		{ "ID": "O110",		"Text": "Team A",				"ParentID": "O100", 	"Type": "O" },
		{ "ID": "S111",		"Text": "Product Owner",			"ParentID": "O110", 	"Type": "S" },
		{ "ID": "S112",		"Text": "Scrum Master",			"ParentID": "O110", 	"Type": "S" },
		{ "ID": "S113",		"Text": "Team Member",			"ParentID": "O110", 	"Type": "S" },
		{ "ID": "S114",		"Text": "Team Member",			"ParentID": "O110", 	"Type": "S" },
		{ "ID": "S115",		"Text": "Team Member",			"ParentID": "O110", 	"Type": "S" },
		{ "ID": "O120",		"Text": "Team B",				"ParentID": "O100", 	"Type": "O" },
		{ "ID": "S121",		"Text": "Product Owner",			"ParentID": "O120", 	"Type": "S" },
		{ "ID": "S122",		"Text": "Scrum Master",			"ParentID": "O120", 	"Type": "S" },
		{ "ID": "S123",		"Text": "Team Member",			"ParentID": "O120", 	"Type": "S" },
		{ "ID": "S124",		"Text": "Team Member",			"ParentID": "O120", 	"Type": "S" },
		{ "ID": "S125",		"Text": "Team Member",			"ParentID": "O120", 	"Type": "S" },
		{ "ID": "S126",		"Text": "Team Member",			"ParentID": "O120", 	"Type": "S" },
		{ "ID": "S127",		"Text": "Team Member",			"ParentID": "O120", 	"Type": "S" },
		{ "ID": "O130",		"Text": "Support",				"ParentID": "O100", 	"Type": "O" },
		{ "ID": "S131",		"Text": "Team Lead",			        "ParentID": "O130", 	"Type": "S" },
		{ "ID": "S132",		"Text": "Support Analyst",	                "ParentID": "O130", 	"Type": "S" },
		{ "ID": "S133",		"Text": "Support Analyst",	                "ParentID": "O130", 	"Type": "S" },
		{ "ID": "S134",		"Text": "Support Analyst",		        "ParentID": "O130", 	"Type": "S" },
		{ "ID": "S135",		"Text": "Support Analyst",			"ParentID": "O130", 	"Type": "S" }
	]
}

Assigned tags

      31 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Kashif Bashir
      Kashif Bashir

      super! 

      very nice article.

      🙂

      Author's profile photo Frederico Rocha
      Frederico Rocha

      Good article! Thanks for sharing

      Author's profile photo Former Member
      Former Member

      Could I add tree table in master view of splitapp & click on each names to navigate data in detail view using local Json

      Author's profile photo Mike Doyle
      Mike Doyle
      Blog Post Author

      Yes, but you would need to have some clickable content in your tree table.  A table row (sap.ui.table.Row) isn't selectable in the same way that a list item (see sap.m.ListItemBase) is.  For example, the tree table column could contain a Link control.

      I'm not sure if we will ever see a TreeList control.

      Author's profile photo Mike Doyle
      Mike Doyle
      Blog Post Author

      There is now a 'TreeList' control, Kaushik.  It's called sap.m.Tree and it's available from UI5 version 1.42.

      Author's profile photo Viswanath Golakoti
      Viswanath Golakoti

      Hi Mike,

      Good article, Thanks for sharing.

      I am unable to find the attachments of code  you mentioned in code. Could you please share them.

      Thanks,

      Best Regards,

      Viswanath

       

      Author's profile photo Mike Doyle
      Mike Doyle
      Blog Post Author

      Thanks Viswanath for pointing that out.  I don’t think the new platform supports attachments and they haven’t been migrated.  For that reason I have added the sample code at the end of the blog.

       

      Author's profile photo Viswanath Golakoti
      Viswanath Golakoti

      Hi Mike,

      Thanks for Sharing

      Regards,

      Viswanath

      Author's profile photo komal Mohite
      komal Mohite

      Very Informative.
      Thanks Mike.

      Author's profile photo Aditya Adhauliya
      Aditya Adhauliya

      Hey Mike , I have read your article thoroughly and I really appreciate how informative you have made it . It’s very helpful.
      I have tried to use your example to create a “Tree” not a tree table .
      I need a little more help.
      Can you clarify if the transformtreedata function will work also if you have data coming in a random order and not in a hierachial order (i.e. Parents may not appear before the children)as opposed to your FlatData.json file .

      e.g. The data could be coming in like this ..
      Countries and Cities
      City 1 in US
      City 2 in US
      City 3 in US
      City 1 in India
      City 2 in India
      City 1 in UK
      City 1 in China 

      US 

      INDIA

      UK

      CHINA

      etc

      And we have to display it like this(Nodes Not Expanded)
      Countries
           US
           UK
           India
           China

       

      Nodes Expanded

      Countries
           US
              City 1
              City 2
              City 3 
           UK
           China                         //Non-expanded nodes(UK & China)
           India 
              City 1
              City 2

      Thanks in Advance ,
      Cheers! ?

      Author's profile photo Mike Doyle
      Mike Doyle
      Blog Post Author

      Hi Aditya, actually this simple version does rely on parents appearing before the children.  That's because the node is added to it's parent's children array and if the parent is missing then, well, we don't have anywhere to add it.

      Normally the kind of standard function modules I'm targeting here give a level for each node in the hierarchy.  In your example the countries would be level 1 and the cities level 2.  As long as you can sort ascending by the level then that's enough to guarantee that each parent comes before it's children.  I would do the sorting in the back-end if I were you.

      This method should work fine with the new sap.m.Tree control.  Judging by the example in the explored app you can replace the property name "children" with "nodes" and omit the arrayNames parameter in the binding.  Use the network tab in the browser dev tools to view the data file Tree.json.

       

      Author's profile photo SAP Seeker
      SAP Seeker

      Excellent blog Mike. Will be definitely helpful for those who try to implement treetable.

      I am trying to implement one and facing issues with events, where no events of sap.m.tree is working. We are on 1.44.11 library. Have you tried to implement event like selectionChange ? Since sap.ui.commons library is depreciated, we will need to go with sap.m library.

      Appreciate your views on this.

      Thanks.

      Author's profile photo Mike Doyle
      Mike Doyle
      Blog Post Author

      Hi there SAP Seeker, I'm glad you found the blog useful.

      I haven't had any problems with the events myself.  First of all I would double check your code where you register your event handlers.  Second I would try to debug the standard code to see where the events are raised.

      I assume you aren't using your tree with an aggregation binding (i.e. having many trees)?  That can sometimes cause issues with events.

       

      Author's profile photo Former Member
      Former Member

      Hello, Thank you for sharing this solution. I have a question about the tree table(in a gantt) using your transformation routine. I am unable to set a filter...it's like my path for a field of the table wasn't good. Can you tell me how I sould implement a filter for a field of the tree structure. I'm really stuck with this thing, and I don't understand why.

      Thank you again.

      Laurent

      Author's profile photo Mike Doyle
      Mike Doyle
      Blog Post Author

      Hi Laurent, I haven't tried a filter on a treetable.  I'm sure you know that a filter is applied to the context.  The context sits between the model and the UI control.

      There is a filter method which you can see here:

      https://sapui5.hana.ondemand.com/#/api/sap.ui.table.Table/methods/filter

      If you're still having trouble I suggest you raise a question on SAP community with some details of exactly what you are doing.

       

       

      Author's profile photo raju m
      raju m

      Hi  Mike,

      can it work for below scenario like

      Europe
      Austria
      Belgium
      Germany
      France
      Greece
      Italy
      Luxembourg
      Netherlands
      United Kingdom
      Europe South
      Italy

      Here Italy is present in 2 parent nodes Europe as well as Europe South.

      Regards,

      Raju.

       

      Author's profile photo Mike Doyle
      Mike Doyle
      Blog Post Author

      Hi Raju, the nodes have to be unique, so your two Italy nodes would have to have different IDs.

      Author's profile photo Deep Desai
      Deep Desai

      Hi Mike,

      I am working on a tree table app and able to display the tree table and do operations on top of the data. Basically the tree table is a budgetary table and end users would change the budget values for the table which should then be saved on the backend.

      How should i save the tree structure data back to the ABAP system. I will need to convert the tree data back to flat structure ? If yes, how should i do it?

      Regards

      Deep

       

      Author's profile photo Mike Doyle
      Mike Doyle
      Blog Post Author

      Hi Deep,

      You should be able to use the nodeMap for this.  Remember that the nodeMap has a reference to each of the nodes in the tree.  When the node values are updated (if the input fields are bound to the JSON model) then the new value will be available via the nodeMap too.  You can loop through the properties of the nodeMap object to prepare your updates.

      Mike

       

       

      Author's profile photo Poojith M V
      Poojith M V

      Hi Mike,

      Thanks for the wonderful article. I am able to display the content as a tree table from a flat file as a JSON output in view. However, my requirement now is to allow users to add / remove entries within the tree table.

      Please could you let me know how to achieve this functionality?

      Regards,

      Poojith

      Author's profile photo Mike Doyle
      Mike Doyle
      Blog Post Author

      Hi Poojith,

      Glad you found the blog useful.

      You should try setting the mode property of the Tree to Delete.  As with a regular list, a delete icon appears on each line.  The delete event on the Tree will be raised when they click the icon.

      If you want some kind of add child button on each line item you could use the sap.m.CustomTreeItem control instead of sap.m.StandardTreeItem.

      If you change the JSON model data the tree will update, although you may need to trigger an explicit update of the binding.

      Mike

       

       

       

      Author's profile photo Poojith M V
      Poojith M V

      Hi Mike,

      Thanks for the response. I will try the approach and let you know the outcome.

      Regards,

      Poojith

      Author's profile photo Poojith M V
      Poojith M V

      Hi Mike,

      I tried performing some changes and was able to delete records from the treetable successfully. However, adding new rows is still a problem:(

      Please could you help on this? Is there a way wherein I can loop thru the JSON model bound to the treetable and add a new record?

      Regards,

      Poojith M V

      Author's profile photo Mike Doyle
      Mike Doyle
      Blog Post Author

      Hi Poojith, why not post a new question? Add some more details and post a link to it here....

       

      Author's profile photo Poojith M V
      Poojith M V

      Hi Mike,
      I have created a question with the required details. I have managed to achieve 1 part of the scenario.
      However, there is a new requirement which I am trying to realize which has some errors. I have attached my code which I am using in the reply.

      Please could you help with the corrections?

      Regards,
      Poojith

      Author's profile photo Poojith M V
      Poojith M V

      Hi Mike,

      Thanks for the reply. I have created a new question in SDN https://answers.sap.com/questions/740941/add-new-rows-in-sap-ui5-treetable.html.

      Regards,

      Poojith

      Author's profile photo Manish Gupta
      Manish Gupta

      Hi Mike,

      My oData returns me the below output. I tried your code for converting this to tree table format but it's not working. Can you please help me?

      oData%20Result

      Regards

      Manish Gupta

       

      Author's profile photo Mike Doyle
      Mike Doyle
      Blog Post Author

      Hi Manish, do you have a field which has the ID of the parent node?  It would be easier to see if you paste the output as JSON, rather than a screenshot.

      Author's profile photo Manish Gupta
      Manish Gupta

      Dear Mike,

      Greetings.

      Thanks for your reply. I checked the result but i don't think there is any such parent node. Below is he output of oData in JSON format.

      Looking forward to hear from you. Thanks.

       

      {
      "d": {
      "results": [{
      "__metadata": {
      "id": "https://URL,
      "uri": "https://URL",
      "type": "{SericeName}.{Entity Set}"
      },
      "ResulltCol1": "10650625_FLM_0004",
      "User": "SUDESH JAITIYA",
      "ID": " 1"
      }, {
      "__metadata": {
      "id": "https://URL,
      "uri": "https://URL",
      "type": "{SericeName}.{Entity Set}"
      },
      "ResulltCol1": "10650625_FLM_0004",
      "User": "GOVIND KUMAR",
      "ID": " 2"
      }, {
      "__metadata": {
      "id": "https://URL,
      "uri": "https://URL",
      "type": "{SericeName}.{Entity Set}"
      },
      "ResulltCol1": "10650625_FLM_0004",
      "User": "SUDESH JAITIYA",
      "ID": " 3"
      }, {
      "__metadata": {
      "id": "https://URL,
      "uri": "https://URL",
      "type": "{SericeName}.{Entity Set}"
      },
      "ResulltCol1": "10650625_FLM_0004",
      "User": "SRIRAM SHARMA",
      "ID": " 4"
      }, {
      "__metadata": {
      "id": "https://URL,
      "uri": "https://URL",
      "type": "{SericeName}.{Entity Set}"
      },
      "ResulltCol1": "10650625_FLM_0004",
      "User": "SUDESH JAITIYA",
      "ID": " 5"
      }]
      }
      }

      Regards

      Manish

      Author's profile photo Mike Doyle
      Mike Doyle
      Blog Post Author

      To use my approach you need another property which contains (if applicable) the ID of the parent node.  The distinguishing feature of a tree is that all nodes below the very top level have a parent. If you want instead to lazy load your tree (making a call each time a node is expanded) then the approach I describe here isn't suitable.

      Author's profile photo Khouloud Daghsni
      Khouloud Daghsni

      Thanks a lot for your solution.