I am trying to use expand option in my XML view but it's resulting no data.
Data are coming from backend as I can see in debugging under 'Network' option but there seems to be some binding issue in XML view.
Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/Device",
"sem/stock_app/model/models"
], function(UIComponent, Device, models) {
"use strict";
return UIComponent.extend("sem.stock_app.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() {
var url = "/sap/opu/odata/sap/ZMATLIST_SRV_02";
var odata = new sap.ui.model.odata.ODataModel(url, {
json: true
});
this.setModel(odata);
UIComponent.prototype.init.apply(this, arguments);
this.getRouter().initialize();
this.setModel(models.createDeviceModel(), "device");
}
});
});
manifest.json
{
"_version": "1.7.0",
"sap.app": {
"id": "sem.stock_app",
"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
},
"supportedThemes": [
"sap_hcb",
"sap_belize"
]
},
"sap.ui5": {
"rootView": {
"viewName": "sem.stock_app.view.mat",
"type": "XML"
},
"dependencies": {
"minUI5Version": "1.30.0",
"libs": {
"sap.ui.core": {},
"sap.m": {}
}
},
"contentDensities": {
"compact": true,
"cozy": true
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "sem.stock_app.i18n.i18n"
}
}
},
"resources": {
"css": [{
"uri": "css/style.css"
}]
},
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sem.stock_app.view",
"controlId": "idAppControl",
"controlAggregation": "pages"
},
"routes": [{
"name": "r1",
"pattern": "",
"target": "t1"
},
{
"name": "r2",
"pattern": "page2/{noti}",
"target": "t2"
}],
"targets": {
"t1": {
"viewName": "mat",
"viewId": "idmat",
"controlAggregation": "pages",
"viewLevel": 1
},
"t2": {
"viewName": "table",
"viewId": "idtable",
"controlAggregation": "pages",
"viewLevel": 2
}
}
}
}
}
Root View
<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
xmlns:core="sap.ui.core"
controllerName="sem.stock_app.controller.mat"
displayBlock="true"
>
<App id="idAppControl">
<Page title="Material Input">
<Label text="Material Selection"/>
<Input id="materialInput"
type="Text"
width="50%"
placeholder="Enter Material"
showSuggestion="true"
showValueHelp="false"
valueHelpRequest="handleValueHelp"
submit="onSubmit"
suggestionItems="{/matlistSet}"
>
<suggestionItems>
<core:Item text="{Matid}"/>
</suggestionItems>
</Input>
<Button text="Get Details" enabled="true" press="myPress"/>
</Page>
</App>
</mvc:View>
Table View
<mvc:View
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
controllerName="sem.stock_app.controller.table"
>
<App id="tableApp">
<Page
title="Table"
showNavButton="true"
navButtonPress="onNavBack"
>
<Table
growing="true"
items="{
path: 'odata>/matlistSet',
parameters: {
expand: 'NP_ON_MATID'
}
}"
itemPress="onListItemPressed"
width="100%"
mode="MultiSelect"
>
<columns>
<Column>
<Text text="Material ID"/>
</Column>
<Column>
<Text text="Category"/>
</Column>
<Column>
<Text text="Material Desc"/>
</Column>
<Column>
<Text text="Plant"/>
</Column>
</columns>
<items>
<ColumnListItem type="Active">
<Text text="{odata>NP_ON_MATID/Matid}"/>
<Text text="{odata>NP_ON_MATID/Category}"/>
<Text text="{odata>NP_ON_MATID/Matdesc}"/>
<Text text="{odata>NP_ON_MATID/Plant}"/>
</ColumnListItem>
</items>
</Table>
</Page>
</App>
</mvc:View>
Table Controller
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/routing/History"
], function(Controller, History) {
"use strict";
return Controller.extend("sem.stock_app.controller.table", {
onInit: function() {this.getOwnerComponent().getRouter().getRoute("r2").attachPatternMatched(this.mynav, this);
this.getOwnerComponent().getRouter().setView("table");
},
mynav: function(oeve) {
var data = oeve.getParameters().arguments.noti;
var params = "('" + data + "')?$expand=NP_ON_MATID";
var path = "/matlistSet" + params + "";
this.getView().bindElement(path);
},
onNavBack: function(window) {
var oHistory = History.getInstance();
var sPreviousHash = oHistory.getPreviousHash();
if (sPreviousHash !== undefined) {
window.history.go(-1);
} else {
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.navTo("overview", {}, true);
}
},
});
});
metadata.xml
<?xml version="1.0" encoding="utf-8" ?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:sap="http://www.sap.com/Protocols/SAPData">
<edmx:DataServices m:DataServiceVersion="2.0">
<Schema Namespace="ZMATLIST_SRV_02" xml:lang="en" sap:schema-version="1" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
<EntityType Name="matlist" sap:content-version="1">
<Key>
<PropertyRef Name="Matid" />
</Key>
<Property Name="Matid" Type="Edm.String" Nullable="false" MaxLength="18" sap:unicode="false" sap:label="Material" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false" />
<Property Name="Category" Type="Edm.String" Nullable="false" MaxLength="20" sap:unicode="false" sap:label="Char20" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false" />
<Property Name="Matdesc" Type="Edm.String" Nullable="false" MaxLength="40" sap:unicode="false" sap:label="Char" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false" />
<Property Name="Plant" Type="Edm.String" Nullable="false" MaxLength="4" sap:unicode="false" sap:label="Plant" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false" />
<Property Name="Status" Type="Edm.String" Nullable="false" MaxLength="20" sap:unicode="false" sap:label="Char20" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false" />
<Property Name="Qty" Type="Edm.Decimal" Nullable="false" Precision="13" Scale="3" sap:unicode="false" sap:label="Quantity" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false" />
<NavigationProperty Name="NP_ON_MATID" Relationship="ZMATLIST_SRV_02.MATASSOCIATION" FromRole="FromRole_MATASSOCIATION" ToRole="ToRole_MATASSOCIATION" />
</EntityType>
<EntityType Name="matdetails" sap:content-version="1">
<Key>
<PropertyRef Name="Matid" />
</Key>
<Property Name="Matid" Type="Edm.String" Nullable="false" MaxLength="18" sap:unicode="false" sap:label="Material" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false" />
<Property Name="Matitno" Type="Edm.Int32" Nullable="false" sap:unicode="false" sap:label="Number" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false" />
<Property Name="Category" Type="Edm.String" Nullable="false" MaxLength="20" sap:unicode="false" sap:label="Char20" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false" />
<Property Name="Matdesc" Type="Edm.String" Nullable="false" MaxLength="40" sap:unicode="false" sap:label="Char" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false" />
<Property Name="Plant" Type="Edm.String" Nullable="false" MaxLength="4" sap:unicode="false" sap:label="Plant" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false" />
</EntityType>
<Association Name="MATASSOCIATION" sap:content-version="1">
<End Type="ZMATLIST_SRV_02.matlist" Multiplicity="1" Role="FromRole_MATASSOCIATION" />
<End Type="ZMATLIST_SRV_02.matdetails" Multiplicity="*" Role="ToRole_MATASSOCIATION" />
<ReferentialConstraint>
<Principal Role="FromRole_MATASSOCIATION">
<PropertyRef Name="Matid" />
</Principal>
<Dependent Role="ToRole_MATASSOCIATION">
<PropertyRef Name="Matid" />
</Dependent>
</ReferentialConstraint>
</Association>
<EntityContainer Name="ZMATLIST_SRV_02_Entities" m:IsDefaultEntityContainer="true" sap:supported-formats="atom json xlsx">
<EntitySet Name="matlistSet" EntityType="ZMATLIST_SRV_02.matlist" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:pageable="false" sap:content-version="1" />
<EntitySet Name="matdetailsSet" EntityType="ZMATLIST_SRV_02.matdetails" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:pageable="false" sap:content-version="1" />
<AssociationSet Name="MATASSOCIATIONSet" Association="ZMATLIST_SRV_02.MATASSOCIATION" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:content-version="1">
<End EntitySet="matlistSet" Role="FromRole_MATASSOCIATION" />
<End EntitySet="matdetailsSet" Role="ToRole_MATASSOCIATION" />
</AssociationSet>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
Issues to fix
Backend
According to your service metadata, the foreign key inside matdetails is also the only primary key at the same time. I.e. the Matids in matdetails cannot be unique if one matlist is supposed be associated with multiple matdetails entities.
→ Make sure that primary keys (or them combined) are all unique in the entire entity set.
According to your screenshot, the response body of your entity set doesn't contain the property "results" which is not OData V2 compliant (Same as this issue. See also difference between OData V1 and OData V2). The "response" property needs to be there in each collection so that UI5 can display the items correctly.
→ Make sure that the response body is OData V2 compliant.
Frontend
Model
There was no model name passed when calling setModel.
→ Either pass the model name according to your view: this.setModel(odata, "odata")
→ Or remove the model name from everywhere. E.g.: <Text text="{Matid}"/>
Also worth reading: Stop Using sap.ui.model.odata.ODataModel!
UI
The root view and its controller are instantiating twice. In order to avoid that, please read:
→ Why is my Main Controller being called twice?
→ Discussion in https://github.com/SAP/openui5/issues/1746
The application contains <App> multiple times. A typical UI5 application should contain only a single root element. If not, you might end up having this kind of problems.
→ Remove all the <App> from views except from the root view.
Since you want to display matdetails entities of selected matlist entity in the table, binding items there with the absolute path /matlistSet doesn't make sense.
→ Replace ...
items="{
path: 'odata>/matlistSet',
parameters: {
expand: 'NP_ON_MATID'
}
}
... with items="{ path: 'odata>NP_ON_MATID', templateShareable: false }". This binding is now relative. The navigation property NP_ON_MATID will be resolved once the selected context is given in the controller. See also Binding Path from the documentation.
Controller
On patternMatched, the selected Matid value is given. So far so good but there are two anti-patterns in the following lines:
var params = "('" + data + "')?$expand=NP_ON_MATID";
var path = "/matlistSet" + params + "";
→ Avoid creating entity key(s) manually.
→ Avoid appending ?$expand manually as it won't expand the entity. Pass the navigation property to the parameters/expand of bindElement/bindObject instead.
Off-topic but if you want to navigate back: Remove window parameter from onNavBack: function(window) { ... }. Otherwise, the window in window.history.go is not the global window object but the press event from the back button.
Related
I'm facing a problem with sap.ui.table.TreeTable.
My goal is to develop an SAP Fiori App that displays Tree Data which it receives via an OData V2 Service. For this project I am using the SAP Business Application Studio.
Here's the example I worked with and how I want my app to look like: https://sapui5.hana.ondemand.com/#/entity/sap.ui.table.TreeTable/sample/sap.ui.table.sample.TreeTable.BasicODataTreeBinding
This is what my App looks like right now...
As you can see the TreeTable is filled with the information it gets from my OData service. The problem I'm facing is that it just prints the elements without putting them into hierarchical order.
The "Einzelrollen" element is supposed to be a child of the "Rollen" element, as you can see when looking at the hierarchy-level / nodeid / parentNodeId.
The weird thing about this is, that the Tree-Annotation-Binding seemed to work, since the drillState element functions the way it should be --> the elements below the "Langtext"-element don't have an "expand" option, since they are Leafs.
So why does it not work with the hierarchical annotation elements?
Really hope somebody can help me out, looking forward to your guys suggestions! Thanks in advance!
Code below:
View.xml
<mvc:View
controllerName="treetable.controller.tableView"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true"
xmlns="sap.ui.table"
xmlns:m="sap.m">
<TreeTable
id="treeTable"
selectionMode="Single"
enableColumnReordering="false"
rows="{
path : '/NodeSet',
parameters : {
treeAnnotationProperties : {
hierarchyLevelFor : 'HierarchyLevel',
hierarchyNodeFor : 'NodeId',
hierarchyParentNodeFor : 'ParentNodeID',
hierarchyDrillStateFor : 'DrillState'
}
}
}">
<columns>
<Column label="Description">
<template>
<m:Text text="{Description}" wrapping="false" />
</template>
</Column>
<Column label="HierarchyLevel">
<template>
<m:Text text="{HierarchyLevel}" wrapping="false" />
</template>
</Column>
<Column label="NodeId">
<template>
<m:Text text="{NodeId}" wrapping="false" />
</template>
</Column>
<Column label="ParentNodeID">
<template>
<m:Text text="{ParentNodeID}" wrapping="false" />
</template>
</Column>
</columns>
</TreeTable> </mvc:View>
metadata.xml
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:sap="http://www.sap.com/Protocols/SAPData">
<edmx:DataServices m:DataServiceVersion="2.0">
<Schema Namespace="Z_ODATA_EXAMPLE_SRV" xml:lang="en" sap:schema-version="1" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
<EntityType Name="Node" sap:content-version="1">
<Key>
<PropertyRef Name="NodeId"/>
</Key>
<Property Name="NodeId" Type="Edm.Int32" Nullable="false" sap:unicode="false" sap:label="ID" sap:hierarchy-node-for="NodeId"/>
<Property Name="HierarchyLevel" Type="Edm.Int32" Nullable="false" sap:unicode="false" sap:hierarchy-level-for="NodeId"/>
<Property Name="ParentNodeID" Type="Edm.Int32" sap:unicode="false" sap:hierarchy-parent-node-for="NodeId"/>
<Property Name="DrillState" Type="Edm.String" Nullable="false" MaxLength="8" sap:unicode="false" sap:hierarchy-drill-state-for="NodeId"/>
<Property Name="Description" Type="Edm.String" Nullable="false" MaxLength="80" sap:unicode="false"/>
</EntityType>
<EntityContainer Name="Z_ODATA_EXAMPLE_SRV_Entities" m:IsDefaultEntityContainer="true" sap:supported-formats="atom json xlsx">
<EntitySet Name="NodeSet" EntityType="Z_ODATA_EXAMPLE_SRV.Node" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:pageable="false" sap:content-version="1"/>
</EntityContainer>
<atom:link rel="self" href="https://dae:1234/sap/opu/odata/sap/Z_ODATA_EXAMPLE_SRV/$metadata" xmlns:atom="http://www.w3.org/2005/Atom"/>
<atom:link rel="latest-version" href="https://dae:1234/sap/opu/odata/sap/Z_ODATA_EXAMPLE_SRV/$metadata" xmlns:atom="http://www.w3.org/2005/Atom"/>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
manifest.json
{
"_version": "1.32.0",
"sap.app": {
"id": "treetable",
"type": "application",
"i18n": "i18n/i18n.properties",
"applicationVersion": {
"version": "1.0.0"
},
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"resources": "resources.json",
"ach": "ach",
"dataSources": {
"mainService": {
"uri": "/sap/opu/odata/sap/Z_ODATA_EXAMPLE_SRV",
"type": "OData",
"settings": {
"odataVersion": "2.0",
"localUri": "localService/metadata.xml"
}
}
}
},
"sap.ui": {
"technology": "UI5",
"icons": {
"icon": "sap-icon://task",
"favIcon": "",
"phone": "",
"phone#2": "",
"tablet": "",
"tablet#2": ""
},
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
}
},
"sap.ui5": {
"flexEnabled": false,
"rootView": {
"viewName": "treetable.view.tableView",
"type": "XML",
"async": true,
"id": "tableView"
},
"dependencies": {
"minUI5Version": "1.66.0",
"libs": {
"sap.ui.core": {},
"sap.m": {},
"sap.ui.layout": {},
"sap.ui.table": {}
}
},
"contentDensities": {
"compact": true,
"cozy": true
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "treetable.i18n.i18n"
}
},
"testmodel": {
"type": "sap.ui.model.odata.v2.ODataModel",
"settings": {
"defaultOperationMode": "Server",
"defaultBindingMode": "OneWay",
"defaultCountMode": "Request"
},
"dataSource": "mainService",
"preload": true
}
},
"resources": {
"css": [
{
"uri": "css/style.css"
}
]
},
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"async": true,
"viewPath": "treetable.view",
"controlAggregation": "pages",
"controlId": "app",
"clearControlAggregation": false
},
"routes": [
{
"name": "RoutetableView",
"pattern": "RoutetableView",
"target": ["TargettableView"]
}
],
"targets": {
"TargettableView": {
"viewType": "XML",
"transition": "slide",
"clearControlAggregation": false,
"viewId": "tableView",
"viewName": "tableView"
}
}
}
}
}
view.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/UIComponent",
"sap/ui/model/odata/v2/ODataModel",
],
/**
* #param {typeof sap.ui.core.mvc.Controller} Controller
*/
function (Controller) {
"use strict";
return Controller.extend("treetable.controller.tableView", {
onInit: function () {
var oModel = this.getOwnerComponent().getModel("testmodel");
this.getView().setModel(oModel);
var oTreeTable = this.getView().byId("treeTable").setModel(oModel);
oTreeTable.bindRows({
path: "/NodeSet",
parameters : {
treeAnnotationProperties : {
hierarchyLevelFor : 'HierarchyLevel',
hierarchyNodeFor : 'NodeId',
hierarchyParentNodeFor : 'ParentNodeID',
hierarchyDrillStateFor : 'DrillState'} }
});
}
});
});
Your example should work as you expect, i converted it into a running example.
The only thing i change was NodeId to NodeID.
But i advice you to remove all this lines. Models which are define in the manifest are propagated automatically:
var oModel = this.getOwnerComponent().getModel("testmodel");
this.getView().setModel(oModel);
var oTreeTable = this.getView().byId("treeTable").setModel(oModel);
Also in the manifest.xml write "" instead of "testmodel". It is best practise to use the unnamed model for the odata-model.
Bindings are usually created, once the rounter is matching something. Doing it in onInit is also not best practise. see _onRouteMatched
sap.ui.require([
"sap/ui/core/util/MockServer"
], function(MockServer) {
var oMockServer = new MockServer({
rootUri: "/"
});
// simulate against the metadata and mock data
oMockServer.simulate(`<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:sap="http://www.sap.com/Protocols/SAPData">
<edmx:DataServices m:DataServiceVersion="2.0">
<Schema Namespace="Z_ODATA_EXAMPLE_SRV" xml:lang="en" sap:schema-version="1" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
<EntityType Name="Node" sap:content-version="1">
<Key>
<PropertyRef Name="NodeID"/>
</Key>
<Property Name="NodeID" Type="Edm.Int32" Nullable="false" sap:unicode="false" sap:label="ID" sap:hierarchy-node-for="NodeId"/>
<Property Name="HierarchyLevel" Type="Edm.Int32" Nullable="false" sap:unicode="false" sap:hierarchy-level-for="NodeId"/>
<Property Name="ParentNodeID" Type="Edm.Int32" sap:unicode="false" sap:hierarchy-parent-node-for="NodeId"/>
<Property Name="DrillState" Type="Edm.String" Nullable="false" MaxLength="8" sap:unicode="false" sap:hierarchy-drill-state-for="NodeId"/>
<Property Name="Description" Type="Edm.String" Nullable="false" MaxLength="80" sap:unicode="false"/>
</EntityType>
<EntityContainer Name="Z_ODATA_EXAMPLE_SRV_Entities" m:IsDefaultEntityContainer="true" sap:supported-formats="atom json xlsx">
<EntitySet Name="NodeSet" EntityType="Z_ODATA_EXAMPLE_SRV.Node" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:pageable="false" sap:content-version="1"/>
</EntityContainer>
<atom:link rel="self" href="https://dae:1234/sap/opu/odata/sap/Z_ODATA_EXAMPLE_SRV/$metadata" xmlns:atom="http://www.w3.org/2005/Atom"/>
<atom:link rel="latest-version" href="https://dae:1234/sap/opu/odata/sap/Z_ODATA_EXAMPLE_SRV/$metadata" xmlns:atom="http://www.w3.org/2005/Atom"/>
</Schema>
</edmx:DataServices>
</edmx:Edmx>`, {
bGenerateMissingMockData: true
});
oMockServer.setEntitySetData("NodeSet", [{
"__metadata": {
"uri": "NodeSet('1')"
},
"NodeID": 1,
"HierarchyLevel": 0,
"Description": "1",
"ParentNodeID": null,
"DrillState": "expanded"
},
{
"__metadata": {
"uri": "NodeSet('2')"
},
"NodeID": 2,
"HierarchyLevel": 0,
"Description": "2",
"ParentNodeID": null,
"DrillState": "expanded"
},
{
"__metadata": {
"uri": "NodeSet('3')"
},
"NodeID": 3,
"HierarchyLevel": 0,
"Description": "3",
"ParentNodeID": null,
"DrillState": "expanded"
},
{
"__metadata": {
"uri": "NodeSet('4')"
},
"NodeID": 4,
"HierarchyLevel": 1,
"Description": "1.1",
"ParentNodeID": 1,
"DrillState": "leaf"
},
{
"__metadata": {
"uri": "NodeSet('5')"
},
"NodeID": 5,
"HierarchyLevel": 1,
"Description": "1.2",
"ParentNodeID": 1,
"DrillState": "expanded"
}
])
// start
oMockServer.start();
const oModel = new sap.ui.model.odata.v2.ODataModel("/");
sap.ui.controller("view1.initial", {
onInit: function(oEvent) {
this.getView().setModel(oModel);
}
});
sap.ui.xmlview("main", {
viewContent: jQuery("#view1").html()
}).placeAt("uiArea");
});
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js" data-sap-ui-theme="sap_bluecrystal" data-sap-ui-xx-bindingSyntax="complex" data-sap-ui-compatVersion="edge" data-sap-ui-debug="false" data-sap-ui-libs="sap.m"></script>
<div id="uiArea"></div>
<script id="view1" type="ui5/xmlview">
<mvc:View controllerName="view1.initial" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.ui.table" xmlns:m="sap.m">
<TreeTable id="treeTable" selectionMode="Single" enableColumnReordering="false" rows="{
path : '/NodeSet',
parameters : {
treeAnnotationProperties : {
hierarchyLevelFor : 'HierarchyLevel',
hierarchyNodeFor : 'NodeID',
hierarchyParentNodeFor : 'ParentNodeID',
hierarchyDrillStateFor : 'DrillState'
}
}
}">
<columns>
<Column label="Description">
<template>
<m:Text text="{Description}" wrapping="false" />
</template>
</Column>
<Column label="HierarchyLevel">
<template>
<m:Text text="{HierarchyLevel}" wrapping="false" />
</template>
</Column>
<Column label="NodeId">
<template>
<m:Text text="{NodeID}" wrapping="false" />
</template>
</Column>
<Column label="ParentNodeID">
<template>
<m:Text text="{ParentNodeID}" wrapping="false" />
</template>
</Column>
</columns>
</TreeTable>
</mvc:View>
</script>
I have converted an Angular project into a hybrid app following this guide:
https://medium.com/#christof.thalmann/convert-angular-project-to-android-apk-in-10-steps-c49e2fddd29
For Android I did not run into many issues and the app is working as expected on that platform.
On IOS I ran into multiple difficulties. First of all in order contents display I needed to change the Angular LocationStrategy to HashLocation as describe in this SO topic:
Why Angular Router 8 router ...
Although I now do get content to render I still have trouble getting the dynamic content (i.e. the content requiring a call to a web server before rendering) to render properly.
My app has got a classic NavBar to switch from one component to another. If I activate one component by clicking on a Nav Button the static content gets rendered ok. However, the dynamic content is not there. I can verify this by looking at the source code: The holding the dynamic content is empty. If I click on the same Nav Button again the dynamic content is added. I get the same effect on an iPhone simulator and on a real device.
This is the html of one of the components
<div class="card bg-opaque border-light">
<div class="card-body">
<div>
<h2 class="text-light text-opaque">Einsparungen von {{ user }}</h2>
</div>
<div *ngIf="monthScore" class="card border-primary bg-opaque-light"> <!-- THIS BLOCK NOT VISIBLE -->
<div class="card-body text-center">
<h3 class="card-title">Current Month</h3>
<ul class="list-unstyled">
<li>
<strong>Savings:</strong> {{ monthScore.savings | number: '1.1-2' }} kg
</li>
<li>
<strong>Position:</strong> {{ monthScore.rank }}
<span *ngIf="!monthScore.rank">???</span>
<span *ngIf="monthScore.rank == 1">
<mat-icon class="text-warning">emoji_events</mat-icon>
</span>
</li>
<li>
<strong>Captured:</strong> {{ monthScore.captured | number: '1.1-2' }} kg
</li>
</ul>
<div *ngIf="showMonthButton" >
<button class="btn btn-outline-primary" (click)="toggleMonthGraph()">
<mat-icon>bar_chart</mat-icon>
</button>
</div>
<div *ngIf="!showMonthButton" (click)="toggleMonthGraph()">
<canvas
baseChart
[chartType]="'bar'"
[datasets]="monthChartData"
[labels]="monthChartLabels"
[options]="chartOptions"
[legend]="false">
</canvas>
</div>
</div>
</div>
<div *ngIf="yearScore" class="card border-primary bg-opaque-light"> <!-- THIS BLOCK NOT VISIBLE --> <div class="card-body text-center">
<h3 class="card-title">Current year</h3>
<ul class="list-unstyled">
<li>
<strong>Savings:</strong> {{ yearScore.savings | number: '1.1-2' }} kg
</li>
<li>
<strong>Position:</strong> {{ yearScore.rank }}
<span *ngIf="!yearScore.rank">???</span>
<span *ngIf="yearScore.rank == 1">
<mat-icon class="text-warning">emoji_events</mat-icon>
</span>
</li>
<li>
<strong>Captured:</strong> {{ yearScore.captured | number: '1.1-2' }} kg
</li>
</ul>
<div *ngIf="showYearButton" >
<button class="btn btn-outline-primary" (click)="toggleYearGraph()">
<mat-icon>bar_chart</mat-icon>
</button>
</div>
<div *ngIf="!showYearButton" (click)="toggleYearGraph()">
<canvas
baseChart
[chartType]="'bar'"
[datasets]="yearChartData"
[labels]="yearChartLabels"
[options]="chartOptions"
[legend]="false">
</canvas>
</div>
</div>
</div>
</div>
</div>
<app-inpage></app-inpage>
The .ts file:
import { Component, OnInit } from '#angular/core';
import { SummaryService} from '../summary.service';
import { AuthService } from '../../auth/auth.service';
import { Score } from '../summary';
import { MAT_RIPPLE_GLOBAL_OPTIONS } from '#angular/material/core';
#Component({
selector: 'app-score',
templateUrl: './score.component.html',
styleUrls: ['./score.component.scss']
})
export class ScoreComponent implements OnInit {
monthScore: Score;
yearScore: Score;
user: string;
// Histogramm per Consumer
chartOptions = {
responsive: true,
scales: {
xAxes: [{
gridLines: {
drawOnChartArea: false
}
}],
yAxes: [{
gridLines: {
drawOnChartArea: false
}
}]
}
};
yearChartData = [];
yearChartLabels = [];
yearChartTitle: string;
showYearChart: boolean = false;
showYearButton: boolean = true;
monthChartData = [];
monthChartLabels = [];
monthChartTitle: string;
showMonthChart: boolean = false;
showMonthButton: boolean = true;
constructor(private service: SummaryService, private authService: AuthService) { }
ngOnInit(): void {
this.user = this.authService.user
this.getMonthScore();
this.getYearScore();
}
getMonthScore(): void {
this.service.getScore('month').subscribe(score => {
this.monthScore = score;
this.createMonthGraph();
})
}
getYearScore(): void {
console.log('GETTING SCORE')
this.service.getScore('year').subscribe(score => {
this.yearScore = score;
this.createYearGraph();
})
}
private createYearGraph(): void {
this.service.getTimeline('year').subscribe(timelines => {
let data: number[] = [];
let label: string[] = [];
for (let i = 0; i < timelines.length; i++){
data.push(timelines[i].user_savings);
label.push(timelines[i].period.toString());
}
this.yearChartData = [{data: data, label: 'Savings', barThickness: 2, backgroundColor: 'rgba(0, 0, 0, 0.5' }]
this.yearChartLabels = label
})
}
private createMonthGraph(): void {
this.service.getTimeline('month').subscribe(timelines => {
let data: number[] = [];
let label: string[] = [];
for (let i = 0; i < timelines.length; i++){
data.push(timelines[i].user_savings);
label.push(timelines[i].period.toString());
}
this.monthChartData = [{data: data, label: 'Savings', barThickness: 2, backgroundColor: 'rgba(0, 0, 0, 0.5' }]
this.monthChartLabels = label
})
}
toggleYearGraph(): void {
this.showYearChart = !this.showYearChart;
this.showYearButton = !this.showYearButton;
}
toggleMonthGraph(): void {
this.showMonthButton = !this.showMonthButton;
}
}
My config.xml
<?xml version='1.0' encoding='utf-8'?>
<widget id="com.ticumwelt.co2" version="0.2.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>Tracker</name>
<description>
An app to track your savings.
</description>
<author email="mymail#example.com" href="https://example.com">
Developer Team
</author>
<content src="index.html" />
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
<allow-intent href="tel:*" />
<allow-intent href="sms:*" />
<allow-intent href="mailto:*" />
<allow-intent href="geo:*" />
<platform name="android">
<allow-intent href="market:*" />
</platform>
<platform name="ios">
<allow-intent href="itms:*" />
<allow-intent href="itms-apps:*" />
<!-- iOS 8.0+ -->
<!-- iPhone 6 Plus -->
<icon src="res/ios/icons/icon-60#3x.png" width="180" height="180" />
<!-- iOS 7.0+ -->
<!-- iPhone / iPod Touch -->
<icon src="res/ios/icons/icon-60.png" width="60" height="60" />
<icon src="res/ios/icons/icon-60#2x.png" width="120" height="120" />
<!-- iPad -->
<icon src="res/ios/icons/icon-76.png" width="76" height="76" />
<icon src="res/ios/icons/icon-76#2x.png" width="152" height="152" />
<!-- Spotlight Icon -->
<icon src="res/ios/icons/icon-40.png" width="40" height="40" />
<icon src="res/ios/icons/icon-40#2x.png" width="80" height="80" />
<!-- iOS 6.1 -->
<!-- iPhone / iPod Touch -->
<icon src="res/ios/icons/icon.png" width="57" height="57" />
<icon src="res/ios/icons/icon#2x.png" width="114" height="114" />
<!-- iPad -->
<icon src="res/ios/icons/icon-72.png" width="72" height="72" />
<icon src="res/ios/icons/icon-72#2x.png" width="144" height="144" />
<!-- iPad Pro -->
<icon src="res/ios/icons/icon-167.png" width="167" height="167" />
<!-- iPhone Spotlight and Settings Icon -->
<icon src="res/ios/icons/icon-small.png" width="29" height="29" />
<icon src="res/ios/icons/icon-small#2x.png" width="58" height="58" />
<icon src="res/ios/icons/icon-small#3x.png" width="87" height="87" />
<!-- iPad Spotlight and Settings Icon -->
<icon src="res/ios/icons/icon-50.png" width="50" height="50" />
<icon src="res/ios/icons/icon-50#2x.png" width="100" height="100" />
<!-- iPad Pro -->
<icon src="res/ios/icons/icon-83.5#2x.png" width="167" height="167" />
<splash src="res/ios/screen/default#2x~universal~anyany.png" />
<preference name="WKWebViewOnly" value="true" />
<feature name="CDVWKWebViewEngine">
<param name="ios-package" value="CDVWKWebViewEngine" />
</feature>
<preference name="CordovaWebViewEngine" value="CDVWKWebViewEngine" />
<preference name="WKSuspendInBackground" value="false" />
</platform>
<edit-config target="NSLocationWhenInUseUsageDescription" file="*-Info.plist" mode="merge">
<string>need location access to find things nearby</string>
</edit-config>
</widget>
And my package.json
{
"name": "com.example.tracker",
"displayName": "Tracker",
"version": "0.2.1",
"description": "An app to track your savings",
"main": "index.js",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"keywords": [
"ecosystem:cordova"
],
"author": "My Name",
"license": "Apache-2.0",
"private": true,
"dependencies": {
"#angular-material-components/datetime-picker": "^2.0.4",
"#angular/animations": "~9.0.3",
"#angular/cdk": "^9.2.4",
"#angular/common": "~9.0.3",
"#angular/compiler": "~9.0.3",
"#angular/core": "~9.0.3",
"#angular/forms": "~9.0.3",
"#angular/localize": "~9.0.3",
"#angular/material": "^9.2.4",
"#angular/platform-browser": "~9.0.3",
"#angular/platform-browser-dynamic": "~9.0.3",
"#angular/router": "~9.0.3",
"#ionic-native/background-geolocation": "^5.29.0",
"#ionic-native/core": "^5.29.0",
"#ionic/angular": "^5.4.1",
"#ng-bootstrap/ng-bootstrap": "^6.2.0",
"bootstrap": "^4.4.0",
"chart.js": "^2.9.4",
"cordova-plugin-splashscreen": "6.0.0",
"material-design-icons-iconfont": "^6.1.0",
"ng-connection-service": "^1.0.4",
"ng2-charts": "^2.4.2",
"ngx-cookie-service": "^10.1.1",
"rxjs": "~6.5.4",
"tslib": "^1.10.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
"#angular-devkit/build-angular": "^0.1002.0",
"#angular/cli": "~9.0.4",
"#angular/compiler-cli": "~9.0.3",
"#angular/language-service": "~9.0.3",
"#globules-io/cordova-plugin-ios-xhr": "^1.2.0",
"#mauron85/cordova-plugin-background-geolocation": "^3.1.0",
"#types/jasmine": "~3.5.0",
"#types/jasminewd2": "~2.0.3",
"#types/node": "^12.11.1",
"codelyzer": "^5.1.2",
"cordova-ios": "^6.1.1",
"cordova-plugin-geolocation": "^4.1.0",
"cordova-plugin-whitelist": "^1.3.4",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~2.1.0",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.2",
"protractor": "~5.4.3",
"ts-node": "~8.3.0",
"tslint": "~5.18.0",
"typescript": "~3.7.5"
},
"cordova": {
"plugins": {
"cordova-plugin-whitelist": {},
"cordova-plugin-geolocation": {
"GPS_REQUIRED": "true"
},
"cordova-plugin-background-geolocation": {
"ALWAYS_USAGE_DESCRIPTION": "This app always requires location tracking",
"MOTION_USAGE_DESCRIPTION": "This app requires motion detection"
},
"#globules-io/cordova-plugin-ios-xhr": {}
},
"platforms": [
"ios"
]
}
}
My suspicion is that it has something to do with the WKWebView of Apple. As it is the first time I am actually developing something in the Apple World I have the feeling that some Apple security feature is blocking something.
Update:
I did 2 additional checks:
In order to check if any styling stuff was causing the issue I removed all the stylings. However, same problem.
In order to check if the dynamic data is actually fetched from the server when initiating the component I added a console.log() to print the data after it is fetched. It is fetched correctly but the screen does not update to display the data.
Update 2:
Updating from Angular 9 to Angular 10 also did not solve the problem.
After a lot of trial and error and searching I found the solution.
I found a hint here:
https://github.com/angular/angular/issues/7381[1]
For a reason I fo not fully understand yet the app seems to switch zones during the async call to the server. Therefore the UI change mechanism is not triggered and the screen is not updated.
By wrapping the changes of variables into NgZone.run()the screen is updated correctly.
The updated .ts file
import { Component, OnInit, NgZone } from '#angular/core';
// ...
constructor(private service: SummaryService, private authService: AuthService, private zone: NgZone) { }
// ...
getMonthScore(): void {
this.service.getScore('month').subscribe(score => {
this.zone.run(() => {
this.monthScore = score;
this.createMonthGraph();
console.log('GOT MONTH SCORE');
console.log(score);
});
})
}
This is required only when building an iOS app with Cordova. When building an Android app or using the browser does not seem necessary.
I have oData by path Northwind Customers('ANATR')/Orders
I can't bind it to table Customer orders
Here I have /Customers('" + oArgs.customerId + "')/Orders like in oData link
OrderList.controller.js
sap.ui.define([
"sap/ui/demo/nav/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sap.ui.demo.nav.controller.customer.OrderList", {
onInit: function () {
var oRouter = this.getRouter();
oRouter.getRoute("customer").attachMatched(this._onRouteMatched, this);
},
_onRouteMatched : function (oEvent) {
var oArgs, oView;
oArgs = oEvent.getParameter("arguments");
oView = this.getView();
oView.bindElement({
path : "/Customers('" + oArgs.customerId + "')/Orders",
events : {
change: this._onBindingChange.bind(this),
dataRequested: function (oEvent) {
oView.setBusy(true);
},
dataReceived: function (oEvent) {
oView.setBusy(false);
}
}
});
}
});
});
And here I link it to bind data {path : '/Orders'} how to insert {CustomerID} parameter?
OrderList.view.xml
<mvc:View
controllerName="sap.ui.demo.nav.controller.customer.OrderList"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:f="sap.ui.layout.form"
xmlns:core="sap.ui.core">
<Page
id="orderListPage"
title="{i18n>OrderList}"
showNavButton="true"
navButtonPress=".onNavBack"
class="sapUiResponsiveContentPadding">
<Panel accessibleRole="Region">
<headerToolbar>
<Toolbar>
<Title text="{i18n>orderListTitle}"/>
<ToolbarSpacer/>
<SearchField
width="50%"
search=".onFilterOrders"/>
</Toolbar>
</headerToolbar>
<Table
id="orderList"
class="sapUiResponsiveMargin"
width="auto"
items="{
path : '/Orders'
}">
<columns>
<Column
hAlign="End"
minScreenWidth="Small"
demandPopin="true"
width="4em">
<Text text="{i18n>columnOrderID}"/>
</Column>
<Column>
<Text text="{i18n>columnCustomerID}"/>
</Column>
<Column
minScreenWidth="Tablet"
demandPopin="false">
<Text text="{i18n>columnOrderDate}"/>
</Column>
<Column
hAlign="End">
<Text text="{i18n>columnPrice}"/>
</Column>
</columns>
<items>
<ColumnListItem
type="Navigation"
press=".onPress">
<cells>
<ObjectNumber number="{OrderID}" emphasized="false"/>
<ObjectIdentifier title="{CustomerID}"/>
<Text text="{OrderDate}"/>
<ObjectNumber
number="{
parts: [{path: 'Freight'}],
type: 'sap.ui.model.type.Currency',
formatOptions: {
showMeasure: false
}
}"/>
</cells>
</ColumnListItem>
</items>
</Table>
</Panel>
</Page>
</mvc:View>
path : '/Orders' is an absolute binding (slash at the beginning). This means UI5 will try to fetch https://services.odata.org/V2/Northwind/Northwind.svc/Orders.
Also you bound your view to a list of things. In general, this is not what you want.
What you want is to bind your view against a single Customer entity
oView.bindElement({
path : "/Customers('" + oArgs.customerId + "')",
events: {
...
}
});
and then use relative binding (no slash at the beginning) for the Orders:
items="{
path : 'Orders'
}"
I am trying to use OData V4 with SAPUI5. Although it seems quite straight forward, I am facing couple of issues.
I am using sap.ui.layout.sample.SimpleFormToolbar sample from Demokit. Implemented a OData V4 service to display the data in the simple form.
Changes i have done in this app:
1. manifest.json and
2. Page.view.xml for Context Binding of form control
3. Removed code reference to mockdata in controller and index.html
However, I am not able to get data with following errors:
2019-07-07 08:58:23.736110 Failed to update path /Suppliers(12345)/Country - **Error: Must not change a property before it has been read**
**Uncaught Error: Must not change a property before it has been read**
Another error is related batch mode. I have not implemented any batch processing in Odata impl.
2019-07-07 08:58:24.279114 **$batch failed** - Error: Network error
2019-07-07 08:58:24.281175 Failed to read path /Suppliers(12345) - Error: **HTTP request was not processed because $batch failed**
The code i am using is:
//manifest.json
{
"_version": "1.12.0",
"sap.app": {
"id": "sap.ui.layout.sample.SimpleFormToolbar",
"applicationVersion": {
"version": "1.0.0"
},
"dataSources": {
"supplierOData": {
"uri": "https://supplierappp1014576trial.hanatrial.ondemand.com/SupplierApp/SupplierService.svc/",
"type": "OData",
"settings": {
"odataVersion": "4.0"
}
}
}
},
"sap.ui5": {
"rootView": {
"viewName": "sap.ui.layout.sample.SimpleFormToolbar.Page",
"type": "XML",
"async": true
},
"dependencies": {
"libs": {
"sap.ui.layout": {}
}
},
"models": {
"": {
"dataSource": "supplierOData",
"settings" : {
"synchronizationMode" : "None"
}
}
},
"config": {
"sample": {
"files": [
"Page.view.xml",
"Page.controller.js",
"manifest.json"
]
}
}
}
}
//Page.view.xml
<mvc:View
controllerName="sap.ui.layout.sample.SimpleFormToolbar.Page"
xmlns:l="sap.ui.layout"
xmlns:f="sap.ui.layout.form"
xmlns:mvc="sap.ui.core.mvc"
xmlns:core="sap.ui.core"
xmlns="sap.m">
<VBox class="sapUiSmallMargin">
<f:SimpleForm id="SimpleFormToolbar"
binding="{/Suppliers(12345)}"
editable="true"
layout="ResponsiveGridLayout"
labelSpanXL="4"
labelSpanL="3"
labelSpanM="4"
labelSpanS="12"
adjustLabelSpan="false"
emptySpanXL="0"
emptySpanL="4"
emptySpanM="0"
emptySpanS="0"
columnsXL="2"
columnsL="1"
columnsM="1"
singleContainerFullSize="false"
ariaLabelledBy="Title1" >
<f:toolbar>
<Toolbar id="TB1">
<Title id="Title1" text="Address" level="H4" titleStyle="H4"/>
<ToolbarSpacer />
<Button icon="sap-icon://settings"/>
<Button icon="sap-icon://drop-down-list" />
</Toolbar>
</f:toolbar>
<f:content>
<Toolbar ariaLabelledBy="Title2">
<Title id="Title2" text="Office" level="H5" titleStyle="H5"/>
<ToolbarSpacer />
<Button icon="sap-icon://settings"/>
</Toolbar>
<Label text="Name" />
<Input value="{SupplierName}" />
<Label text="Street/No." />
<Input value="{Street}">
</Input>
<Input value="{HouseNumber}">
<layoutData>
<l:GridData span="XL2 L1 M3 S4" />
</layoutData>
</Input>
<Label text="ZIP Code/City" />
<Input value="{ZIPCode}">
<layoutData>
<l:GridData span="XL2 L1 M3 S4" />
</layoutData>
</Input>
<Input value="{City}" />
<Label text="Country" />
<Select id="country" selectedKey="{Country}">
<items>
<core:Item text="England" key="England"/>
<core:Item text="Germany" key="Germany"/>
<core:Item text="USA" key="USA"/>
</items>
</Select>
<Toolbar ariaLabelledBy="Title3">
<Title id="Title3" text="Online" level="H5" titleStyle="H5"/>
<ToolbarSpacer />
<Button icon="sap-icon://settings"/>
</Toolbar>
<Label text="Web" />
<Input value="{Url}" type="Url" />
<Label text="Twitter" />
<Input value="{Twitter}" />
</f:content>
</f:SimpleForm>
</VBox>
</mvc:View>
//Page.controller.js
sap.ui.define([
'jquery.sap.global',
'sap/ui/core/mvc/Controller',
'sap/ui/model/json/JSONModel',
'sap/ui/model/odata/v4/ODataModel'
], function(jQuery, Controller, JSONModel, ODataModel) {
"use strict";
var PageController = Controller.extend("sap.ui.layout.sample.SimpleFormToolbar.Page", {
onInit: function (oEvent) {
console.log("hello");
jQuery.get({
url: "/SupplierService/SupplierService.svc/Suppliers(12345)",
success: function(data) {
console.log( "Recieved data: " + data);
},
error: function(error) {
// your error logic
console.log("Error while requesting odata: " + error);
}
});
}
});
return PageController;
});
//index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="expires" content="0">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fullscreen – with toolbar</title>
<script id="sap-ui-bootstrap"
src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-resourceroots='{
"sap.ui.layout.sample.SimpleFormToolbar": "./",
"sap.ui.demo.mock": "mockdata"
}'
data-sap-ui-compatVersion="edge"
data-sap-ui-async="true"
data-sap-ui-preload=""
data-sap-ui-frameOptions="trusted"
data-sap-ui-oninit="module:sap/ui/core/ComponentSupport">
</script>
</head>
<body class="sapUiBody" id="content">
<div data-sap-ui-component
data-name="sap.ui.layout.sample.SimpleFormToolbar"
data-height="100%"
data-id="container"
data-settings='{"id" : "sap.ui.layout.sample.SimpleFormToolbar"}'
style="height: 100%">
</div>
</body>
</html>
I have added jquery call just to see if the odata is working or not.
Command used for CORS issue: "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --disable-web-security --disable-gpu --user-data-dir=~/chromeTemp
I am actually pointing to odata service in the same project. The same is deployed on Sap Cloud: https://supplierappp1014576trial.hanatrial.ondemand.com/SupplierApp/SupplierService.svc/
Thanks for your time.
EDIT:
Added the solution as an answer below.
The error:
"must not change a property before it has been read" is understood to be CORS issue.
Accepting Cross origin requests on server side can be done by configuring web.xml. Note that this should be re-evaluated for production use.
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
The error:
"$batch failed - Error: Network error" is resolved by changing groupId to $direct. This means we are sending individual network calls to odata service.
"models": {
"": {
"dataSource": "supplierOData",
"settings" : {
"synchronizationMode" : "None",
"groupId": "$direct"
}
}
},
This took away those errors and I see the data is rendered into form fields.
Thanks for your attention.
I would like to create a button control that has a Path element as its content--an IconButton if you will.
This button should fulfill two conditions:
1. The Path element's stroke and fill colors should be available for manipulation by the VisualStateManager.
2. The Path element's data string (which defines it's shape) can be set in code or in XAML, so that I can create several such buttons without creating a new custom control for each.
The only way I can see to do it would involve no XAML whatsoever. That is, setting all the visual states and animations in the code behind, plus generating the Geometry objects point by point (and not being able to use the convenient Data string property on the Path element).
Is there a simpler way to do this?
EDIT
So one of the limitations I'm running into is that Silverlight does not support the mini-language for path expressions in PathGeometry, only in Path. I've got some detailed geometry going on in some of these icons, and I really don't want to take the convenient strings I generated with an Illustrator plug-in (pretty sure it was this one) and make them into separate line segments and curves.
Nice one Chris. If you want to store the paths as resources, you can modify things a bit like so:
First change the dependency property to a Path type:
/// <summary>
/// Button that contains an icon, where the icon is drawn from a path.
/// </summary>
public class IconButton : Button
{
/// <summary>
/// The path data that draws the icon.
/// </summary>
public static readonly DependencyProperty IconPathProperty =
DependencyProperty.Register("IconPath", typeof(Path), typeof(IconButton), new PropertyMetadata(default(Path)));
/// <summary>
/// Depencendy property backer.
/// </summary>
public Path IconPath
{
get { return (Path)GetValue(IconPathProperty); }
set { SetValue(IconPathProperty, value); }
}
}
Next, here's the IconButton control template.
<!-- Icon Button Control Template -->
<ControlTemplate x:Key="IconButtonControlTemplate" TargetType="{x:Type usercontrols:IconButton}">
<Grid x:Name="Grid">
<Border SnapsToDevicePixels="True" x:Name="Border" CornerRadius="1" BorderThickness="1" BorderBrush="{StaticResource ButtonBorderBrush}">
<Path x:Name="Icon" Height="16" Width="16" Stretch="Fill"
Data="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IconPath.Data}"
UseLayoutRounding="False" Grid.Column="1" VerticalAlignment="Center" Margin="0">
<Path.Fill>
<SolidColorBrush x:Name="IconColor" Color="{Binding Color, Source={StaticResource ButtonIconBrush}}" />
</Path.Fill>
</Path>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="BorderBrush" Value="{StaticResource ButtonBorderBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="{StaticResource ButtonBackgroundMouseOverBrush}" TargetName="Border"/>
<Setter Property="BorderBrush" Value="{StaticResource ButtonBorderMouseOverBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" Value="{StaticResource ButtonBackgroundPressedBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Fill" Value="{StaticResource DisabledIconBrush}" TargetName="Icon"/>
<Setter Property="Background" Value="{StaticResource ButtonBackgroundDisabledBrush}" TargetName="Border"/>
<Setter Property="BorderBrush" Value="{StaticResource ButtonBorderDisabledBrush}" TargetName="Border"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
... and here's the IconButton style that uses this template, and adds a few defaults:
<!-- Icon Button Style -->
<Style TargetType="{x:Type usercontrols:IconButton}">
<Setter Property="FocusVisualStyle" Value="{DynamicResource SimpleButtonFocusVisual}"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="Height" Value="26"/>
<Setter Property="Width" Value="26"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Content" Value="Button"/>
<Setter Property="Template" Value="{StaticResource IconButtonControlTemplate}"/>
</Style>
Now you can go ahead and create icons and save them in your resource file:
<!-- Undo Icon -->
<Path x:Key="UndoIcon" Stretch="Fill" Data="F1 M 87.7743,80.7396L 87.5215,75.9539L 89.8692,74.2202C 89.9775,75.0329 90.0859,76.586 90.0859,76.5318C 92.9302,73.7417 97.5369,72.9755 100.208,76.2158C 102.019,78.413 102.258,81.2543 99.7657,83.9361C 97.2735,86.6179 92.6142,90.1124 92.6142,90.1124L 90.3748,87.6744L 97.3096,81.769C 97.3096,81.769 99.1516,79.9992 97.8514,78.3558C 96.2374,76.316 94.384,77.2542 92.1447,78.8795C 92.1176,78.9608 93.3998,79.1143 94.3118,79.2768L 92.4336,81.4439L 87.7743,80.7396 Z "/>
<!-- Filter Icon -->
<Path x:Key="FilterIcon" Stretch="Fill" Data="F1 M 6,16L 10,16L 10.0208,10L 16,3L 16,2.86102e-006L 0,9.53674e-007L 0,3L 6,10L 6,16 Z "/>
And finally, create buttons:
<usercontrols:IconButton IconPath="{StaticResource UndoIcon}"></usercontrols:IconButton>
The Path Data property is simply a Geometry. When you say "several such buttons" one assumes that there are a finite number of such buttons which vary with Icon.
Store each "Icon" as a PathGeomerty in a ResourceDictionaryand have that ResourceDictionary referenced as a merged dictionary in the app.xaml.
Now you can simply assign the geomerty into the Path shap Data property.
For the second part of the problem above, here's a kludge which works pretty well for storing string Path data as resources and accessing them from code.
For the first part of the problem, I just created all the animations in code, apply them on mouse events, got rid of the XAML page altogether.
This ship may have long since sailed, but I needed something very similar and was able to roll my own by inheriting from Button:
public class PathButton : Button
{
public static readonly DependencyProperty PathDataProperty =
DependencyProperty.Register("PathData", typeof(Geometry), typeof(PathButton), new PropertyMetadata(default(Geometry)));
public Geometry PathData
{
get { return (Geometry) GetValue(PathDataProperty); }
set { SetValue(PathDataProperty, value); }
}
}
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.1"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0" To="{StaticResource BlueColor}" Storyboard.TargetProperty="Color" Storyboard.TargetName="BackgroundRectangleColor" />
<ColorAnimation Duration="0" To="{StaticResource WhiteColor}" Storyboard.TargetProperty="Color" Storyboard.TargetName="IconColor" />
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed"/>
<VisualState x:Name="Disabled"/>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Unfocused"/>
<VisualState x:Name="Focused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.Projection>
<PlaneProjection/>
</Grid.Projection>
<Rectangle x:Name="BackgroundRectangle" >
<Rectangle.Fill>
<SolidColorBrush x:Name="BackgroundRectangleColor" Color="{StaticResource GrayColor}" />
</Rectangle.Fill>
</Rectangle>
<Path x:Name="Icon" Stretch="Fill"
Data="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PathData}"
UseLayoutRounding="False" Grid.Column="1" Width="24" Height="24" VerticalAlignment="Center" Margin="0">
<Path.Fill>
<SolidColorBrush x:Name="IconColor" Color="{StaticResource LightGrayColor}" />
</Path.Fill>
</Path>
<TextBlock x:Name="Text" HorizontalAlignment="Center" TextWrapping="Wrap" Text="{TemplateBinding Content}" FontSize="9.333" FontFamily="Segoe UI Light" Foreground="{StaticResource LightGray}" Height="11" Margin="0,0,0,3" VerticalAlignment="Bottom"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Example usage:
<Controls:PathButton Content="Options" PathData="F1M50.6954,32.1146C50.7057,31.1041,50.6068,30.1107,50.4583,29.1459L54.5052,25.9193C53.4636,22.0377,51.4583,18.5612,48.7409,15.7604L43.9063,17.5885C42.3776,16.3229,40.6407,15.3099,38.7552,14.5814L37.9857,9.47656C36.1237,8.98181 34.1719,8.68225 32.1511,8.66663 30.1302,8.65625 28.1771,8.92578 26.2995,9.39844L25.4714,14.4948C23.5794,15.2057,21.8229,16.1901,20.2813,17.4401L15.4675,15.5521C12.7201,18.3138,10.6706,21.7631,9.57556,25.6328L13.582,28.9088C13.4193,29.875 13.3164,30.8658 13.3047,31.8776 13.2969,32.8984 13.3945,33.8854 13.5469,34.8588L9.49353,38.0807C10.5417,41.9584,12.5469,45.4401,15.2604,48.2383L20.0938,46.4127C21.6224,47.6744,23.3659,48.6875,25.2513,49.4193L26.0091,54.5234C27.8802,55.0209 29.8333,55.3177 31.8503,55.3334 33.8698,55.3385 35.8243,55.0729 37.6979,54.6041L38.5352,49.5C40.4219,48.7916,42.1784,47.806,43.7253,46.5664L48.53,48.4531C51.2813,45.6836,53.3268,42.233,54.4245,38.3685L50.418,35.0963C50.5833,34.1224,50.6862,33.1354,50.6954,32.1146 M31.9362,41.6615C26.6068,41.6302 22.3073,37.2734 22.3411,31.9375 22.3776,26.6002 26.7266,22.3008 32.0651,22.3359 37.4011,22.3698 41.7005,26.7252 41.6653,32.0625 41.629,37.4023 37.2786,41.6979 31.9362,41.6615"
Style="{StaticResource PathButtonStyle1}" Grid.RowSpan="2" Grid.Column="1" />