Skip to Content
Technical Articles

A General UI5 Application for Reviewing All Sets inside an OData service

Introduction:

Have you ever tried to review data represented by a list of sets in an OData service? Assume we want to create an application to only review data in all 26 sets inside the Northwind OData service. If we want to follow the normal way of doing this we need 26 different tables that each of them must be tailor made for an specific set. But how about an application that we only need to set its main OData service and then it will do the rest for us?

In the following, I will introduce an application that has been developed for these kinds of general ways of reviewing the data. This application is freely accessible in GitHub.

The goal of this article is not to explaining the application itself but to explain the programming techniques that we used in this app. By reading this article you will learn:

1. How to extract useful data from an OData metadata object in run time.

2. How to generate Smart Tables in run time inside the controller.

Main Part:

This app was generated based on SAPUI5 master-details template. In the master part it shows a list of all possible sets existing inside the OData service. And in the detail part it shows a smart table that lists the data of the selected set. As we used smart table, it has the possibility to sort, filter, group or modify the columns. Let us take a look at a video that shows how does it work before going deeply through the code.

The state-of-the-art in this application collected in few functions.

1. In the following function we showed how can we collect a list of all possible sets in an OData service.

initDataModel: function () {
	var oModel = this.getModel(),
		fnAfterMetadataLoaded = function () {
			var aSets = [];
			var oMetaData = oModel.getServiceMetadata();
			for (var j = 0; j < oMetaData.dataServices.schema.length; j++) {
				var oEntityContainer = oMetaData.dataServices.schema[j].entityContainer;
				if (oEntityContainer) {
					var aEntityContainer = oEntityContainer[0];
					if (aEntityContainer && aEntityContainer.entitySet.length > 0) {
						for (var i = 0; i < aEntityContainer.entitySet.length; i++) {
							aSets.push({
								SetName: aEntityContainer.entitySet[i].name,
								EntityName: aEntityContainer.entitySet[i].entityType
							});
						}
						var oData = {
							DataSet: aSets
						};
						//Creation of the JSON Model for the Test Daya
						var oDataModel = new sap.ui.model.json.JSONModel(oData);
						this.setModel(oDataModel, "DataModel");
						return;
					}
				}
			}
		}.bind(this);
	oModel.metadataLoaded().then(fnAfterMetadataLoaded);
},

The function `initDataModel` tries to extract all the sets inside the main service. This function contains an interior function `fnAfterMetadataLoaded`, which is called as soon as the metadata is loaded. Every OData model has a metadata object.

2. In the following function we showed how we can generate a smart table in run time (not statically), again based on loaded metadata for a selected set. As soon as user press on one item in the left hand side list in the application (master list), we will pass the selected set name to the detail view. In the detail controller we collect the parameters from the url and read the data from OData service. The collected data pushed in a JSON model and bind to the view and then we call `createSmartTable` function to generate the smart table in run time.

_onObjectMatched: function (oEvent) {
	var sSetName = oEvent.getParameter("arguments").SetName,
		sEntityName = oEvent.getParameter("arguments").EntityName;
	this.getModel("appView").setProperty("/layout", "TwoColumnsMidExpanded");
	var oModel = this.getModel();
	oModel.metadataLoaded().then(function () {
		oModel = this.getModel();
		oModel.setUseBatch(false);
		oModel.read("/" + sSetName, {
			success: function (oData, oResponse) {
				var oItems = {
					DataSet: oData.results
				};
				this.getModel("DataModel").setData(oItems);
				this.createSmartTable(sEntityName, sSetName);
			}.bind(this)
		});
	}.bind(this));
},
_extractEntity: function (sEntityName) {
	var aRes = sEntityName.split("."),
		sNamespace = aRes[0],
		sEntity = aRes[1],
		oEntity,
		oModel = this.getModel(),
		oMetaData = oModel.getServiceMetadata();
	for (var j = 0; j < oMetaData.dataServices.schema.length; j++) {
		if (oMetaData.dataServices.schema[j].namespace === sNamespace) {
			var aEntities = oMetaData.dataServices.schema[j].entityType;
			for (var i = 0; i < aEntities.length; i++) {
				if (aEntities[i].name === sEntity) {
					oEntity = aEntities[i];
					break;
				}
			}
			break;
		}
	}
	return oEntity;
},
createSmartTable: function (sEntityName, sSetName) {
	var oEntity = this._extractEntity(sEntityName);
	if (oEntity) {
		var aCells = [];
		for (var i = 0; i < oEntity.property.length; i++) {
			var oCol = oEntity.property[i];
			aCells.push(oCol.name);
		}
		if (aCells.length > 0) {
			var sFields = "",
				sCols = "",
				sCels = "";
			for (i = 0; i < aCells.length; i++) {
				sFields += aCells[i];
				if (i < aCells.length - 1) {
					sFields += ",";
				}
				sCols += "" +
					"	        <m:Column visible='true'> \n" +
					"		       <m:customData> \n" +
					"			      <core:CustomData key='p13nData' \n" +
					"				     value='\\{\"sortProperty\":\"" + aCells[i] + "\",\"filterProperty\":\"" + aCells[i] + "\",\"columnKey\":\"" +
					aCells[i] + "\",\"leadingProperty\":\"" + aCells[i] + "\",\"columnIndex\":\"" + i + "\"}'/> \n" +
					"		       </m:customData> \n" +
					"		       <m:Text text='" + aCells[i] + "'/> \n" +
					"	        </m:Column> \n";
				sCels += "" +
					"			   <m:Text text=\"{path:'DataModel>" + aCells[i] + "'}\"/> \n";
			}
			var oViewModel = this.getModel("detailView");
			oViewModel.setProperty("/setItems", sSetName);
			var xmlStr = "" +
				"<core:FragmentDefinition xmlns:m='sap.m' xmlns:smartTable='sap.ui.comp.smarttable' xmlns:core='sap.ui.core'> \n" +
				"    <smartTable:SmartTable id='__smartTable' entitySet='DataSet' tableBindingPath='DataModel>/DataSet' header='{detailView>/setItems}' \n" +
				"     showRowCount='true' tableType='ResponsiveTable' showFullScreenButton='false' useVariantManagement='false' enableAutoBinding='true' \n" +
				"     requestAtLeastFields='" + sFields + "' \n" +
				"     useExportToExcel='false' ignoredFields='' beforeRebindTable='handleBeforeRebindTable'> \n" +
				"     <m:Table mode='MultiSelect' busy='{detailView>/tableSectionTypeBusy}'> \n" +
				"        <m:columns> \n" +
				sCols +
				"        </m:columns> \n" +
				"      <m:items> \n" +
				"	     <m:ColumnListItem type='Inactive' press='onTableItemPress'> \n" +
				"		    <m:cells> \n" +
				sCels +
				"		    </m:cells> \n" +
				"	     </m:ColumnListItem> \n" +
				"      </m:items> \n" +
				"    </m:Table> \n" +
				"   </smartTable:SmartTable> \n" +
				"</core:FragmentDefinition> \n";
			var oLayout = this.getView().byId("myLayout");
			var aControls = oLayout.removeAllContent();
			for (var j = 0; j < aControls.length; j++) {
				var oControl = aControls[j];
				if (typeof oControl.destroy === "function") {
					oControl.destroy();
				}
			}
			sap.ui.core.Fragment.load({
				type: "XML",
				id: sSetName,
				definition: xmlStr,
				controller: this
			}).then(function (oControll) {
				oLayout.addContent(oControll);
			});
		}
	}
},

As you see in the above code, the best practice way that we found for creating a smart table in run time is by using fragments. However in this case we cannot use static fragment files, and we will generate the xml string of the smart table in the controller and pass this string instead the file name.

Maybe you asked why we didn’t generated the smart table by using its JS class. The problem is that we couldn’t find a way to pass the interior table to the sap.ui.comp.smarttable.SmartTable class. if anyone can suggest a solution to pass a table to smart table it will be worthwhile. For example in the following code I showed how we can generate this interior table by JS code, but it is not clear that how we have to pass this table to smart table in JS. Please write me in comments if you have any idea.

createTable: function (sEntityName) {
	var oTable = new sap.m.Table(),
		oEntity = this._extractEntity(sEntityName);
	if (oEntity) {
		oTable.removeAllColumns();
		oTable.unbindItems();
		var aCells = [];
		for (var i = 0; i < oEntity.property.length; i++) {
			var oCol = oEntity.property[i];
			var oColumn = new sap.m.Column({
				header: new sap.m.Label({
					text: oCol.name
				})
			});
			oColumn.addCustomData(
				new sap.ui.core.CustomData({
					key: "p13nData",
					value: "\{'sortProperty': '" + oCol.name + "', 'filterProperty': '" + oCol.name + "', 'columnKey': '" + oCol.name +
						"', 'leadingProperty': '" + oCol.name + "', 'columnIndex':'" + i + "'}"
				}));
			aCells.push(new sap.m.Text({
				text: "{DataModel>" + oCol.name + "}"
			}));
			oTable.addColumn(oColumn);
		}

		oTable.bindItems({
			path: "DataModel>/DataSet",
			template: new sap.m.ColumnListItem({
				cells: aCells
			})
		});
	}
	return oTable;
},

 

How to run this app?

If you like to run the app in you own WEBIDE environment you have to do the following steps:

  1. Clone or download the from GitHub.
  2. Import the app in your WEBIDE.
  3. Add northwind OData service to your WEBIDE based on this manual.
  4. (optional) if you select a different name for your Northwind connector please add that as your main service.
  5. Run the app.

 

How can we use this app for reviewing other OData Services except Northwind?

It is very simple. The app has been written very general. It does not depend on Northwind. You only need to substitute main service with your desired service. That’s it!

 

Conclusion:

We tried to show how can we develop some applications that work around metadata and generate their view automatically.

In the next port we will try to extend this app to generate the modification dialog for each set automatically.

 

 

 

 

 

 

1 Comment
You must be Logged on to comment or reply to a post.
  • Regarding the question that has been asked in the context.

    Maybe you asked why we didn’t generated the smart table by using its JS class. The problem is that we couldn’t find a way to pass the interior table to the sap.ui.comp.smarttable.SmartTable class. if anyone can suggest a solution to pass a table to smart table it will be worthwhile.

    We can pass the interior table in JS like the following:

    var oSmartTable = new sap.ui.comp.smarttable.SmartTable({
    	id: "__mySmartTable",
    	entitySet: "BusinessPartnerSet",
    	tableBindingPath: "/BusinessPartnerSet",
    	header: "Business Partners",
    	showRowCount: true,
    	tableType: "ResponsiveTable",
    	showFullScreenButton: true,
    	useVariantManagement: false,
    	enableAutoBinding: true,
    	ignoredFields: "",
    	beforeRebindTable: "handleBeforeRebindTable",
    	dataReceived: "onDataReceived",
    	dataRequested: "onDataRequested",
    	initiallyVisibleFields: "Partner,Displayname,TypeName,TitleName,BpkindName,Centralarchivingflag",
    	items: [
    		new sap.m.Table("myInteriorTable")
    	]
    });
    this.byId("myContainer").addContent(oSmartTable);

    The reason is that each `sap.ui.comp.smarttable.SmartTable` class is an extension of `sap.m.VBox` class. Thus we can use the `items` aggregation for passing the interior table to the smart table in Java Script code.