How to get the aggregated values of an OData (V2) property? - sapui5

I want to display a chart (sap.viz.ui5.controls.VizFrame) that visualizes data from an OData Service. However, the data of the service has to be manipulated. See the example below:
Main.view.xml
<mvc:View controllerName="demo.chart.controller.Main" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m">
<Shell id="shell">
<App id="app">
<pages>
<Page id="page" title="Chart Demo">
<content></content>
</Page>
</pages>
</App>
</Shell>
</mvc:View>
Chart.fragment.xml
<c:FragmentDefinition xmlns:c="sap.ui.core" xmlns:viz="sap.viz.ui5.controls" xmlns:viz.data="sap.viz.ui5.data"
xmlns:viz.feeds="sap.viz.ui5.controls.common.feeds">
<viz:VizFrame uiConfig="{applicationSet:'fiori'}" vizType='donut'>
<viz:dataset>
<viz.data:FlattenedDataset data="{manipulatedData>/}">
<viz.data:dimensions>
<viz.data:DimensionDefinition name="Gender" value="{manipulatedData>gender}"/>
</viz.data:dimensions>
<viz.data:measures>
<viz.data:MeasureDefinition name="Amount" value="{manipulatedData>amount}"/>
</viz.data:measures>
</viz.data:FlattenedDataset>
</viz:dataset>
<viz:feeds>
<viz.feeds:FeedItem uid="color" type="Dimension" values="Gender"/>
<viz.feeds:FeedItem uid="size" type="Measure" values="Amount"/>
</viz:feeds>
</viz:VizFrame>
</c:FragmentDefinition>
Main.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/ui/core/Fragment"
], function (Controller, JSONModel, Fragment) {
"use strict";
return Controller.extend("demo.chart.controller.Main", {
onInit: function () {
const mDefault = this.getOwnerComponent().getModel();
const mManipulatedData = new JSONModel([{
gender: "F",
amount: 0
}, {
gender: "M",
amount: 0
}, {
gender: "X",
amount: 12
}]);
this.getView().setModel(mManipulatedData, "manipulatedData");
console.log(this.getView().getModel("manipulatedData"));
mDefault.read("/ContactSet", {
success: oData => {
const aManipulatedData = mManipulatedData.getData();
oData.results.forEach(entry => {
aManipulatedData.forEach(type => {
if (entry.Sex === type.gender) {
type.amount++
}
})
});
Fragment.load({
name: "demo.chart.view.Chart",
controller: this
}).then(oFragment => {
this.getView().addDependent(oFragment);
this.byId("page").addContent(oFragment);
});
}
})
}
});
});
Structure of the service (GWSAMPLE_BASIC/ContactSet)
{
"d": {
"results": [
{
"__metadata": {
"id": "https://sapes5.sapdevcenter.com/sap/opu/odata/IWBEP/GWSAMPLE_BASIC/ContactSet(guid'0050568c-901d-1eda-bcae-e8394de7e116')",
"uri": "https://sapes5.sapdevcenter.com/sap/opu/odata/IWBEP/GWSAMPLE_BASIC/ContactSet(guid'0050568c-901d-1eda-bcae-e8394de7e116')",
"type": "GWSAMPLE_BASIC.Contact"
},
"Address": {
"__metadata": {
"type": "GWSAMPLE_BASIC.CT_Address"
},
"City": "Walldorf",
"PostalCode": "69190",
"Street": "Robert-Koch-Straße",
"Building": "1",
"Country": "DE",
"AddressType": "02"
},
"ContactGuid": "0050568c-901d-1eda-bcae-e8394de7e116",
"BusinessPartnerID": "0100000000",
"Title": "",
"FirstName": "Karl",
"MiddleName": "",
"LastName": "Müller",
"Nickname": "",
"Initials": "",
"Sex": "M",
"PhoneNumber": "0622734567",
"FaxNumber": "0622734004",
"EmailAddress": "do.not.reply#sap.com",
"Language": "EN",
"DateOfBirth": null,
"ToBusinessPartner": {
"__deferred": {
"uri": "https://sapes5.sapdevcenter.com/sap/opu/odata/IWBEP/GWSAMPLE_BASIC/ContactSet(guid'0050568c-901d-1eda-bcae-e8394de7e116')/ToBusinessPartner"
}
}
}
]
}
}
So, as you can see, I'm only interested in the aggregated values of the Sex property. My current solution is to loop through all the entries of the entity set and increase the property in a custom JSON model. This is not only very inperformant because you do the heavy-lifting on the client side, it also requires you to know all the possible values for the data displayed in the chart right away. Is there a way to do this with OData queries or do I have to create a new entity set with the comulated data in my SAP system?

OData should be able to count and group, as described in this quesiton: OData v4 groupby with $count
But I doubt your SAP OData Service will, most of the time, those services do not implement the full OData specification.
You can improve your js by not using two nested for loops but something like this:
mGender = {};
oData.results.forEach(entry => {
if (!mGender[entry.Sex])
{mGender[entry.Sex] = 0
}
mGender[entry.Sex]++
});

Related

How to change aggregation binding path dynamically when pressing a button

<mvc:View xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
xmlns:core="sap.ui.core"
controllerName="z.controller.Main">
<Page id="page" title="ComboBox" >
<content>
<ComboBox name="Drop-down List" id="box0" items="{/country}">
<items >
<core:Item key="{Key}" text="{Name}" id="item0"/>
</items>
</ComboBox>
</content>
<Button text="Change" press="onChange"/>
</Page>
</mvc:View>
model1.json
{
"country": [{
"Key": "IN",
"Name": "India"
},
{
"Key": "US",
"Name": "USA"
},{
"Key": "UK",
"Name": "United Kingdom"
}]
}
model2.json
{
"country2": [{
"Key": "BD",
"Name": "Bangladesh"
},
{
"Key": "CA",
"Name": "Canada"
},{
"Key": "FR",
"Name": "France"
}]
}
Main.controller.js
sap.ui.define([
'sap/ui/core/mvc/Controller',
"sap/ui/model/json/JSONModel",
"z/models/model"
], function(Controller,JSONModel, localModel) {
'use strict';
return Controller.extend("z.controller.Main",{
onInit: function(){
var oModel = localModel.createJSONModel("models/data/model1.json");
sap.ui.getCore().setModel(oModel);
var oModel2 = localModel.createJSONModel("models/data/model2.json");
sap.ui.getCore().setModel(oModel2, "model2");
},
onChange: function(){
}
})
});
controller.js
sap.ui.define([
'sap/ui/core/mvc/Controller',
"sap/ui/model/json/JSONModel"
], function(Controller,JSONModel) {
'use strict';
return {
createJSONModel: function(filePath){
var oModel = new JSONModel();
oModel.loadData(filePath);
return oModel;
}
}
});
I have two JSON models and building a combo list using aggregation binding(items="{/country}") by reading data from model1. I am trying to fill the same Combolist by reading the data from the model2 on a button press event.
You can switch the model by using it as a prefix model2> (as you have defined in the Main.controller.js in the onInit method) in the binding path:
onChange: function() {
var oComboBox = this.getView().byId("box0");
oComboBox.bindItems("model2>/country2", {
template: this.getView().byId("item0")
});
}
And change the aggregation from items to dependents.
<ComboBox name="Drop-down List" id="box0" items="{/country}">
<dependents>
<core:Item key="{Key}" text="{Name}" id="item0"/>
</dependents>
</ComboBox>

How to summarize in the Ui5 / fiori table JSONModel

I am writing a simple application which aims to display the local data of a json file.
How can I sum the values ​​from a column?
Expected effect:
enter image description here
Below are the files to recreate the case
V_Root.view.xml
<mvc:View controllerName="Nawigacja.ZNavigation.controller.V_Root" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m">
<App id="V_Main">
<pages>
<Page title="Root View">
<content></content>
</Page>
</pages>
</App>
</mvc:View>
V_Source.view.xml
<mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" controllerName="Nawigacja.ZNavigation.controller.V_Source"
xmlns:html="http://www.w3.org/1999/xhtml">
<App>
<pages>
<Page title="Soruce View">
<content>
<VBox width="100%" direction="Column" id="__vbox0">
<items>
<Button text="Button 1" width="100px" id="__button2" press="GoToTarget_1"/>
<Button text="Button 2" width="100px" id="__button3" press="GoToTarget_2"/>
</items>
</VBox>
</content>
</Page>
</pages>
</App>
</mvc:View>
V_Target_1.view.xml
<mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" controllerName="Nawigacja.ZNavigation.controller.V_Target_1"
xmlns:html="http://www.w3.org/1999/xhtml">
<App>
<pages>
<Page title="Target One" showNavButton="true" navButtonPress="GoOneScreenBack">
<content>
<ScrollContainer vertical="true" focusable="true" height="95%">
<Table id="namiot" items="{path: '/namiot'}">
<columns>
<Column width="12rem">
<Label text="Najem"/>
</Column>
<Column minScreenWidth="tablet" demandPopin="true">
<Label text="Price"/>
</Column>
<Column width="12rem">
<Label text="Total Cost"/>
<footer>
<Text text="Sum:"/>
</footer>
</Column>
</columns>
<ColumnListItem>
<Text text="{ProductType}"></Text>
<Text text="{ProductPrice}"></Text>
<Text text="{ProductTotal}"></Text>
</ColumnListItem>
</Table>
</ScrollContainer>
</content>
</Page>
</pages>
</App>
</mvc:View>
V_Root.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("Nawigacja.ZNavigation.controller.V_Root", {
onInit: function () {
}
});
});
V_Source.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("Nawigacja.ZNavigation.controller.V_Source", {
/**
* Called when a controller is instantiated and its View controls (if available) are already created.
* Can be used to modify the View before it is displayed, to bind event handlers and do other one-time initialization.
* #memberOf Nawigacja.ZNavigation.view.V_Source
*/
onInit: function () {
},
GoToTarget_1: function () {
// Now Get the Router Info
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
// Tell the Router to Navigate To Route_Tar_1
oRouter.navTo("Route_Tar_1", {});
},
GoToTarget_2: function () {
// Now Get the Router Info
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
// Tell the Router to Navigate To Route_Tar_2
oRouter.navTo("Route_Tar_2", {});
}
/**
* Similar to onAfterRendering, but this hook is invoked before the controller's View is re-rendered
* (NOT before the first rendering! onInit() is used for that one!).
* #memberOf Nawigacja.ZNavigation.view.V_Source
*/
// onBeforeRendering: function() {
//
// },
/**
* Called when the View has been rendered (so its HTML is part of the document). Post-rendering manipulations of the HTML could be done here.
* This hook is the same one that SAPUI5 controls get after being rendered.
* #memberOf Nawigacja.ZNavigation.view.V_Source
*/
// onAfterRendering: function() {
//
// },
/**
* Called when the Controller is destroyed. Use this one to free resources and finalize activities.
* #memberOf Nawigacja.ZNavigation.view.V_Source
*/
// onExit: function() {
//
// }
});
});
V_Target_1.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/routing/History",
"Nawigacja/ZNavigation/formatter/Formatter"
], function (Controller, History,formatter) {
"use strict";
return Controller.extend("Nawigacja.ZNavigation.controller.V_Target_1", {
/**
* Called when a controller is instantiated and its View controls (if available) are already created.
* Can be used to modify the View before it is displayed, to bind event handlers and do other one-time initialization.
* #memberOf Nawigacja.ZNavigation.view.V_Target_1
*/
onInit: function () {
var oTable = this.getView().byId("namiot");
var oModel = new sap.ui.model.json.JSONModel();
oModel.loadData("model/namiot.json");
oTable.setModel(oModel);
},
GoOneScreenBack: function (Evt) {
var oHistory = History.getInstance();
var sPreviousHash = oHistory.getPreviousHash();
// Go one screen back if you find a Hash
if (sPreviousHash !== undefined) {
window.history.go(-1);
}
// If you do not find a correct Hash, go to the Source screen using default router;
else {
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.navTo("", true);
}
},
formatter: formatter
/**
* Similar to onAfterRendering, but this hook is invoked before the controller's View is re-rendered
* (NOT before the first rendering! onInit() is used for that one!).
* #memberOf Nawigacja.ZNavigation.view.V_Target_1
*/
// onBeforeRendering: function() {
//
// },
/**
* Called when the View has been rendered (so its HTML is part of the document). Post-rendering manipulations of the HTML could be done here.
* This hook is the same one that SAPUI5 controls get after being rendered.
* #memberOf Nawigacja.ZNavigation.view.V_Target_1
*/
// onAfterRendering: function() {
//
// },
/**
* Called when the Controller is destroyed. Use this one to free resources and finalize activities.
* #memberOf Nawigacja.ZNavigation.view.V_Target_1
*/
// onExit: function() {
//
// }
});
});
models.js
sap.ui.define([
"sap/ui/model/json/JSONModel",
"sap/ui/Device"
], function (JSONModel, Device) {
"use strict";
return {
createDeviceModel: function () {
var oModel = new JSONModel(Device);
oModel.setDefaultBindingMode("OneWay");
return oModel;
}
};
});
manifest.json
{
"_version": "1.12.0",
"sap.app": {
"id": "Nawigacja.ZNavigation",
"type": "application",
"i18n": "i18n/i18n.properties",
"applicationVersion": {
"version": "1.0.0"
},
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"sourceTemplate": {
"id": "ui5template.basicSAPUI5ApplicationProject",
"version": "1.40.12"
}
},
"sap.ui": {
"technology": "UI5",
"icons": {
"icon": "",
"favIcon": "",
"phone": "",
"phone#2": "",
"tablet": "",
"tablet#2": ""
},
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
}
},
"sap.ui5": {
"flexEnabled": false,
"rootView": {
"viewName": "Nawigacja.ZNavigation.view.V_Root",
"type": "XML",
"async": true,
"id": "V_Root"
},
"dependencies": {
"minUI5Version": "1.65.6",
"libs": {
"sap.ui.layout": {},
"sap.ui.core": {},
"sap.m": {}
}
},
"contentDensities": {
"compact": true,
"cozy": true
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "Nawigacja.ZNavigation.i18n.i18n"
}
}
},
"resources": {
"css": [{
"uri": "css/style.css"
}]
},
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"async": true,
"viewPath": "Nawigacja.ZNavigation.view",
"controlAggregation": "pages",
"controlId": "V_Main",
"clearControlAggregation": false,
"viewLevel": 1
},
"routes": [{
"name": "Route_Source",
"pattern": "",
"target": ["Target_Source"],
"titleTarget": ""
}, {
"name": "Route_Tar_1",
"pattern": "V_Target_1",
"titleTarget": "",
"greedy": false,
"target": ["Target_1"]
}, {
"name": "Route_Tar_2",
"pattern": "V_Target_2",
"titleTarget": "",
"greedy": false,
"target": ["Target_2"]
}],
"targets": {
"Target_1": {
"viewType": "XML",
"transition": "slide",
"clearAggregation": true,
"viewName": "V_Target_1",
"viewLevel": 2
},
"Target_2": {
"viewType": "XML",
"transition": "slide",
"clearAggregation":"true",
"viewName": "V_Target_2",
"viewLevel": 2
},
"Target_Source": {
"viewType": "XML",
"clearAggregation":"true",
"viewName": "V_Source",
"viewLevel": 1
}
}
}
}
}
namiot.json
{
"namiot":[
{
"ProductName": "Product1",
"ProductType": "Type1",
"ProductPrice": "25",
"ProductTotal": "5000",
"Productadd": "",
"ProductaddPr":""
},
{
"ProductName": "Product2",
"ProductType": "Type2",
"ProductPrice": "25",
"ProductTotal": "5000",
"Productadd": "",
"ProductaddPr":""
}
]
}
there are probably x options to realize this.
You could use the method attachUpdateFinished which will be called when the Tablebinding get's updated (the table receives the Items). Then you can summarize the values and store it in an JSONModel and bind it to the footertext. When the binding (the items) change the sum would be updated.
oTable.attachUpdateFinished(function (oEvt) {
var iSum = 0;
var aItems = oEvt.getSource().getItems();
for (var i = 0; i < aItems.length; i++) {
var oItem = aItems[i].getBindingContext().getObject();
iSum = iSum + parseFloat(oItem.ProductTotal);
}
//Do whatever you want with the iSum
});
Here's an example how it could be realized.
Code Sample

When will Component.js be loaded?

I'm trying to run a SAPUI5 application. I created a view with a view piecharts and add them in index.html to the body by referring to its ID content
<script>
var app = new sap.m.App();
var page = sap.ui.view({
viewName: "...",
type: sap.ui.core.mvc.ViewType.XML
});
app.addPage(page);
app.placeAt("content");
</script>
My problem now is that the Component.js isn't called. So I have no chance of using Routing since the Router isn't initialized.
I tried some different ways, like this:
<script>
sap.ui.getCore().attachInit(function() {
new sap.m.Shell({
app: new sap.ui.core.ComponentContainer({
height: "100%",
name: "vizFrame.gettingStarted.demoVizFrame"
})
}).placeAt("content");
});
</script>
After this approach, the Component.js got triggered, but the page is empty. The content of my root view isn't displayed.
manifest.json
{
"_version": "1.12.0",
"sap.app": {
"id": "vizFrame.gettingStarted.demoVizFrame",
"type": "application",
"i18n": "i18n/i18n.properties",
"applicationVersion": {
"version": "1.0.0"
},
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"sourceTemplate": {
"id": "ui5template.basicSAPUI5ApplicationProject",
"version": "1.40.12"
}
},
"sap.ui": {
"technology": "UI5",
"icons": {
"icon": "",
"favIcon": "",
"phone": "",
"phone#2": "",
"tablet": "",
"tablet#2": ""
},
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
}
},
"sap.ui5": {
"flexEnabled": false,
"rootView": {
"viewName": "vizFrame.gettingStarted.demoVizFrame.view.VizChart",
"type": "XML"
},
"dependencies": {
"minUI5Version": "1.60.1",
"libs": {
"sap.ui.layout": {},
"sap.ui.core": {},
"sap.m": {}
}
},
"contentDensities": {
"compact": true,
"cozy": true
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "vizFrame.gettingStarted.demoVizFrame.i18n.i18n"
}
}
},
"resources": {
"css": [
{
"uri": "css/style.css"
}
]
},
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"async": true,
"viewPath": "vizFrame.gettingStarted.demoVizFrame.view",
"controlAggregation": "pages",
"controlId": "app",
"clearControlAggregation": false
},
"routes": [
{
"name": "RouteVizChart",
"pattern": "RouteVizChart",
"target": ["TargetVizChart"]
},
{
"pattern": "detail",
"name": "detail",
"target": "detail"
}
],
"targets": {
"TargetVizChart": {
"viewType": "XML",
"transition": "slide",
"clearControlAggregation": false,
"viewId": "VizChart",
"viewName": "VizChart"
},
"testView": {
"viewType": "XML",
"viewName": "testView",
"viewId": "testView",
"viewLevel": 2
},
"detail": {
"viewType": "XML",
"viewName": "detailView"
}
}
}
}
}
Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/Device",
"vizFrame/gettingStarted/demoVizFrame/model/models"
], function (UIComponent, Device, models) {
"use strict";
return UIComponent.extend("vizFrame.gettingStarted.demoVizFrame.Component", {
metadata: {
manifest: "json"
},
init: function () {
UIComponent.prototype.init.apply(this, arguments);
this.getRouter().initialize();
this.setModel(models.createDeviceModel(), "device");
}
});
});
VizChart.view.xml
This is the root view where only the page title gets displayed:
<mvc:View controllerName="vizFrame.gettingStarted.demoVizFrame.controller.VizChart"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
xmlns:viz="sap.viz.ui5.controls"
xmlns:chart="sap.suite.ui.commons"
xmlns:l="sap.ui.layout"
>
<Page title="Übersicht">
<l:VerticalLayout
width="100%"
class="gridWrapper">
<l:Grid containerQuery="true">
<viz:VizFrame xmlns="sap.viz" id="l100" width="100%" selectData="onDataClick" />
<viz:VizFrame xmlns="sap.viz" id="l300" width="100%" selectData="onDataClick" />
<viz:VizFrame xmlns="sap.viz" id="l400" width="100%" selectData="onDataClick" />
<viz:VizFrame xmlns="sap.viz" id="l430" width="100%" selectData="onDataClick" />
<viz:VizFrame xmlns="sap.viz" id="l499" width="100%" selectData="onDataClick" />
</l:Grid>
</l:VerticalLayout>
</Page>
</mvc:View>
The topic App Initialization: What Happens When an App Is Started? explains which files are loaded in which order generally. In most cases, a Component is loaded with its descriptor (manifest.json) once the ComponentContainer is instantiated.
About the issue with the page being empty, see my other answer in stackoverflow.com/a/50951902.
In your case, the root view is missing a root control (e.g. sap.m.App). So it should be:
<!-- Root view -->
<mvc:View xmlns:mvc="sap.ui.core.mvc">
<App id="app" xmlns="sap.m">
<!-- Leave this block empty if Router should be used. Otherwise, continue.. -->
<Page title="Übersicht">
<!-- content -->
</Page>
</App>
</mvc:View>
In your 1st snippet, you could see the page content because there was already sap.m.App.
ComponentContainer loads component.js file from index.html
component.js loads component descriptor(manifest.json - application descriptor file to declare dependencies) which is referenced in the file.
component.js create root view, default and resource(i18n) model.
index.html
sap.ui.getCore().attachInit(function () {
new sap.ui.core.ComponentContainer({
name: "ABC.AppName"
}).placeAt("content");
});
component.js
sap.ui.core.UIComponent.extend("ABC.AppName.Component", {
metadata : {
includes: [jQuery.device.is.phone ? "assets/css/mStyle.css" : "assets/css/dStyle.css"]
},
createContent: function () {
// create root view
var oView = sap.ui.view({
id: "app",
viewName: "ABC.AppName.ViewFolder.App",
type: "JS",
viewData: { component: this },
});
i18nGlobal = new sap.ui.model.resource.ResourceModel({
bundleUrl: "i18n/i18n.properties",
bundleLocale: "de"
});
oView.setModel(i18nGlobal, "i18n");
sap.ui.getCore().setModel(i18nGlobal, "i18n");
return oView;
}
});

Aggregation Binding: How to Put Different Control with Different Aggregation Binding inside the Control which Already Has Aggregation Binding

I have the following data in my model:
{
vertexTable: [
{
"NAME": "Tethys",
"TYPE":"titan",
"RESIDENCE":"Othrys"
},
{
"NAME": "Oceanus",
"TYPE": "titan",
"RESIDENCE": "Othrys"
}
],
vertexAttributes: [
{
"COLUMN_NAME": "NAME",
"DATA_TYPE_NAME": "VARCHAR"
},
{
"COLUMN_NAME": "TYPE",
"DATA_TYPE_NAME": "VARCHAR"
},
{
"COLUMN_NAME": "RESIDENCE",
"DATA_TYPE_NAME": "VARCHAR"
}
]
}
I want to display this data in my view such that there is a label coming from vertexAttributes/COLUMN_NAME and each label has an associated dropdown / select box coming from ..
vertexTable/NAME for the NAME label,
vertexTable/TYPE for the TYPE label, and
vertexTable/RESIDENCE for the RESIDENCE label.
I thought about using a Form control which has aggregation binding to formElements="{/vertexattributes}" and then each Form Element bound to "{COLUMN_NAME}" from vertexAttributes. But now, I want to put the Select control which has an aggregation binding to vertexTable and then the items bound to NAME, TYPE, and RESIDENCE.
Below is the code I tried in my view:
<f:form>
<f:FormContainer formElements="{/vertexAttributes}">
<f:FormElement label="{COLUMN_NAME}">
<List id="values" items="{path: 'VALUES', templateShareable: false}">
<StandardListItem title="{value}"></StandardListItem>
</List>
</f:FormElement>
</f:FormContainer>
</f:form>
I had to use templateShareable because I was getting an error. Not completely sure what it is.
Also, instead of select / dropdown, I am using a List to show the complete values.
Is this the right way to do it or is there a better way?
I came across factory function but I am unable to implement it.
Here is a working example:
sap.ui.getCore().attachInit(() => sap.ui.require([
"sap/ui/model/json/JSONModel",
"sap/ui/layout/form/FormElement",
"sap/m/Label",
"sap/m/Select",
"sap/ui/core/Item",
], (JSONModel, FormElement, Label, Select, Item) => sap.ui.xmlview({
viewContent: `<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns:form="sap.ui.layout.form"
xmlns="sap.m"
controllerName="demo.MyController"
>
<form:Form editable="true">
<form:layout>
<form:ResponsiveGridLayout/>
</form:layout>
<form:FormContainer
formElements="{
path: '/vertexAttributes',
factory: '.createFormElement'
}"
/>
</form:Form>
</mvc:View>`,
controller: sap.ui.controller("demo.MyController", {
createFormElement: function(id, context) {
const columnName = context.getProperty("COLUMN_NAME");
return new FormElement(id).setLabel(new Label({
text: columnName,
})).addField(new Select().bindItems({
path: "/vertexTable",
template: new Item().bindProperty("text", columnName),
}));
},
}),
}).setModel(new JSONModel({
vertexTable: [{
"NAME": "Tethys",
"TYPE": "Titan1",
"RESIDENCE": "Othrys1"
},
{
"NAME": "Oceanus",
"TYPE": "Titan2",
"RESIDENCE": "Othrys2"
},
],
vertexAttributes: [{
"COLUMN_NAME": "NAME",
"DATA_TYPE_NAME": "VARCHAR"
},
{
"COLUMN_NAME": "TYPE",
"DATA_TYPE_NAME": "VARCHAR"
},
{
"COLUMN_NAME": "RESIDENCE",
"DATA_TYPE_NAME": "VARCHAR"
},
]
})).placeAt("content")));
<script src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js" id="sap-ui-bootstrap"
data-sap-ui-libs="sap.ui.layout, sap.m, sap.ui.core"
data-sap-ui-preload="async"
data-sap-ui-theme="sap_belize"
data-sap-ui-compatVersion="edge"
data-sap-ui-resourceRoots='{"demo": "./"}'
data-sap-ui-xx-waitForTheme="true"></script>
<body id="content" class="sapUiBody sapUiSizeCompact"></body>
The design of your data structure is somewhat questionable. The fact that the value (of vertexAttributes/COLUMN_NAME) is the key (of vertexTable/*) in other objects makes the implementation complicated. Nevertheless, as you've already tried before, displaying the appropriate data is still possible with a factory function.
The example above creates a Select control in each iteration of the createFormElement call. Then, the select item can bind its text with the current columnName as a path.
About the templateShareable: https://stackoverflow.com/a/47734086/5846045

How to concatenate multiple property bindings

How can we concatenate data in binding
JSON
{
"firstName": "John",
"lastName": "Doe",
"birthday": {
"day": "01",
"month": "05",
"year": "1982"
},
"address": [
{
"city": "Heidelberg"
}
],
"enabled": "true"
}
I am able to bind single property to label
txt.bindText("data>/birthday/year/");
But I have do display date for that I am trying to concatenate
var dData = "data>/birthday/day/" + " : "+"data>/birthday/month/" + " : " + "data>/birthday/year/";
It is not working. What is proper way to write?
Define a formatter function. Please check and run the code snippet.
<script src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js" id="sap-ui-bootstrap" data-sap-ui-theme="sap_bluecrystal" data-sap-ui-libs="sap.m,sap.ui.commons"></script>
<script id="view1" type="sapui5/xmlview">
<mvc:View xmlns:core="sap.ui.core" xmlns:layout="sap.ui.commons.layout" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.ui.commons" controllerName="my.own.controller" xmlns:html="http://www.w3.org/1999/xhtml">
<layout:VerticalLayout id="vl">
</layout:VerticalLayout>
</mvc:View>
</script>
<script>
sap.ui.controller("my.own.controller", {
onInit: function() {
var data = {
"firstName": "John",
"lastName": "Doe",
"birthday": {
"day": "01",
"month": "05",
"year": "1982"
},
"address": [{
"city": "Heidelberg"
}],
"enabled": "true"
};
var oText = new sap.m.Text();
oText.bindProperty("text", {
parts: [
"data>/birthday/year/",
"data>/birthday/day/",
"data>/birthday/month/"
],
formatter: function(year, day, month) {
return day + ":" + month + ":" + year;
}
});
var oVl = this.getView().byId("vl");
oVl.addContent(oText);
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData(data);
sap.ui.getCore().setModel(oModel, "data");
},
});
var myView = sap.ui.xmlview("myView", {
viewContent: jQuery('#view1').html()
}); //
myView.placeAt('content');
</script>
</head>
<body class='sapUiBody'>
<div id='content'></div>
</body>
In XMLViews you don't even need a formatter. It's as easy as:
<m:Text text="{data>/birthday/day} : {data>/birthday/month} : {data>/birthday/year}" />
Not quite sure if there is a short syntax like this for JSViews as well.
Furthermore you could make use of the UI5 Type System to get your date properly formatted for every locale.