I want to be able to apply a filter to a nested aggregation binding, but it doesn't seem to work. Here is the XML:
<l:Grid id="test" defaultSpan="L6 M6 S6" content="{path : 'test>/', templateShareable:false}">
<l:content>
<VBox width="100%">
<HBox height="100px" alignItems="Center" justifyContent="Start">
<VBox alignItems="Center" width="25%">
<core:Icon src="{test>icon}" width="100%" />
<Text text="{test>text}" width="100%"/>
</VBox>
<VBox id="test" height="80px" items="{path: 'test>data/', templateShareable:false}">
<Link text="{parts: [{ path: 'test>key'},
{ path: 'test>value' }],
formatter : 'dostuff'}"/>
</VBox>
</HBox>
</VBox>
</l:content>
</l:Grid>
And my JSON data is as follows:
{
"results": [{
"text": "object1",
"icon": "icon1",
"data": [{
"value1": "foo",
"value2": "bar"
}, {
"value1": "john",
"value2": "smith"
}]
},
{
"text": "object2",
"icon": "icon2",
"data": [{
"value1": "adam",
"value2": "bobson"
}, {
"value1": "john",
"value2": "smith"
}, {
"value1": "whatever",
"value2": "work please"
}]
}
]
}
I want to be able to filter on test>/results/[n]/data/[n]/[value1 + value2] and have the gird filtered at that level. Whatever I try, it only filters on the Grid content because I can't seem to get the binding for the in VBox "test".
Cheers,
James
Are you trying to filter the content dynamically from JS? Or statically from the XML directly? I assume that you want to do it from JS.
Your VBox with id test is inside a Grid whose content aggregation you have binded. This means that the whole VBox inside the grid (including the test VBox) is treated as a template for the content aggregation of the grid (i.e. with our JSON, you will end up having two copies of the VBox).
Using this.byId("test") will clearly not work, as it will return a control which is part of the Grid's content aggregation template. So be able to filter the inner VBoxes (because there will be several of them depending on the contents of the model), you have to iterate through the contents of the grid and apply the filtering to each of the target VBoxes. Something along the lines of:
this.byId("grid").getContent().forEach(function(oVbox) {
// need to get the HBox and the second VBox from it.
oVbox.getItems()[0].getItems()[1].getBinding("items").filter(...);
});
Also, both the Grid and the VBox have the same IDs, so your code will fail because of that as well. Generally, you don't need to define an ID for an aggregation template or a child.
Related
<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>
Is there a way to render the sap.f.Card control dynamically with an XML view? With aggregation binding maybe? I only used aggregation binding with lists and tables so far...
I need to render some cards dynamically on a main view but I want to stay with the MVC concept. That's why I don't prefer to render the cards in the controller. My Card is also very complex and has multiple controls in it. (Text, Status Indicator, Progress Indicator, etc.)
Is there a way of implementing that?
In Fiori, Cards are usually rendered within sap.f.GridContainer (In fact, it was initially called sap.f.CardContainer).
Just like with Tables and Lists, you can bind <items> to the GridContainer too with the Card as a template control:
<f:GridContainer xmlns:f="sap.f"
width="100%"
snapToRow="true"
items="{
path: '/myCardItems',
templateShareable: false
}">
<f:layout>
<f:GridContainerSettings rowSize="84px" columnSize="84px" gap="8px" />
</f:layout>
<f:layoutXS>
<f:GridContainerSettings rowSize="70px" columnSize="70px" gap="8px" />
</f:layoutXS>
<f:items>
<f:Card> <!-- template -->
<f:header>
<card:Header xmlns:card="sap.f.cards" title="{title}" />
</f:header>
<f:layoutData>
<f:GridContainerItemLayoutData minRows="..." columns="..." />
</f:layoutData>
</f:Card>
</f:items>
</f:GridContainer>
The above snippet is from the Demo Kit sample that has an aggregation binding.
You can achieve it using the Grid layout withcontent aggregation
view.xml
<core:View
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
xmlns:l="sap.ui.layout"
xmlns:fc="sap.f"
xmlns:card="sap.f.cards"
controllerName="path.Main"
xmlns:html="http://www.w3.org/1999/xhtml">
<Page title="Main" class="sapUiContentPadding">
<content>
<!-- Cards data binding -->
<l:Grid containerQuery="true" id="cardsGrid" defaultSpan="XL2 L4" class="gridProgressIndicator" hSpacing="0" content="{/cards}">
<fc:Card class="sapUiMediumMargin" width="300px">
<fc:header>
<card:Header
title="{title}"
subtitle="{subTitle}"
iconSrc="{icon}"/>
</fc:header>
<fc:content>
<VBox
height="115px"
class="sapUiSmallMargin"
justifyContent="SpaceBetween">
<HBox justifyContent="SpaceBetween">
<ComboBox
width="120px"
placeholder="To City"
items="{items}">
<core:Item key="{key}" text="{text}" />
</ComboBox>
<ComboBox
width="120px"
placeholder="To City"
items="{items}">
<core:Item key="{key}" text="{text}" />
</ComboBox>
</HBox>
<HBox justifyContent="SpaceBetween">
<DatePicker width="186px" placeholder="Choose Date ..."/>
<Button text="Book" press="onBookPress" type="Emphasized" />
</HBox>
</VBox>
</fc:content>
</fc:Card>
</l:Grid>
</content>
</Page>
</core:View>
controller.js
bindCardsGrid: function() {
var rowData = [
{ "title": "Title 1", "subTitle": "SubTitle 1", "icon": "sap-icon://menu2", "items": [{ "key": "CV1", "text": "CV1"}, { "key": "CV2", "text": "CV2"}, { "key": "CV3", "text": "CV3"},{ "key": "CV4", "text": "CV4"}]},
{ "title": "Title 2", "subTitle": "SubTitle 2", "icon": "sap-icon://add-contact", "items": [{ "key": "CV1", "text": "CV1"}, { "key": "CV2", "text": "CV2"}, { "key": "CV3", "text": "CV3"},{ "key": "CV4", "text": "CV4"}]},
{ "title": "Title 3", "subTitle": "SubTitle 3", "icon": "sap-icon://business-objects-experience", "items": [{ "key": "CV1", "text": "CV1"}, { "key": "CV2", "text": "CV2"}, { "key": "CV3", "text": "CV3"},{ "key": "CV4", "text": "CV4"}]},
{ "title": "Title 4", "subTitle": "SubTitle 4", "icon": "sap-icon://process", "items": [{ "key": "CV1", "text": "CV1"}, { "key": "CV2", "text": "CV2"}, { "key": "CV3", "text": "CV3"},{ "key": "CV4", "text": "CV4"}]}
];
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData({ "cards": rowData });
this.getView().setModel(oModel);
},
output
I have a sap.m.P13nItem that I set it in XML view by CustomData like the following:
<core:CustomData key="p13nData"
value='\{"columnKey": "StdNo",
"leadingProperty": "StdNo",
"sortProperty": "StdNo",
"filterProperty": "StdNo",
"columnIndex":"11",
"type": "empty string",
"maxLength": "{ path: '/#MySet/StdNo/#maxLength', formatter:'.formatter.fnStrToInt' }",
"values": ["", "O", "I"]
}'/>
This StdNo is of type Edm.Boolean.
<Property Name="StdNo" Type="Edm.Boolean" sap:unicode="false" sap:label="Standard No." sap:creatable="false"/>
Everything works fine, just as P13nItem only supports text, numeric or date as the type based on this documentation and does not have any type for boolean I want to pass the array of values for that again based on the same documentation.
What I had passed as the values array does not put any effect on my p13n item.
Any suggestion regarding how I can pass this array in XML view!?
You should set the type property to boolean to show a list of Yes/No for boolean attributes.
<core:CustomData key="p13nData"
value='\{"columnKey": "StdNo",
"leadingProperty": "StdNo",
"sortProperty": "StdNo",
"filterProperty": "StdNo",
"type": "boolean",
"values": ["", "O", "I"]
}'/>
Here is a picture from the original values for boolean drop down box
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
I'm trying to mock some data based on existing and working oData models. The Mock Server works, but I am struggling making the $expand to do it's job. I think it's mostly a matter of me not understanding where or how to store the JSON.
The metadata file is copied verbatim from the working service and contains all entities, entitysets, associations etcetera.
Here are some of the relevant bits. From Users entity:
<NavigationProperty Name="Dealers" Relationship="Y_DP_CORE_SRV.User_Dealer" FromRole="FromRole_User_Dealer" ToRole="ToRole_User_Dealer" />
The association:
<Association Name="User_Dealer" sap:content-version="1">
<End Type="Y_DP_CORE_SRV.User" Multiplicity="1" Role="FromRole_User_Dealer" />
<End Type="Y_DP_CORE_SRV.Dealer" Multiplicity="*" Role="ToRole_User_Dealer" />
<ReferentialConstraint>
<Principal Role="FromRole_User_Dealer">
<PropertyRef Name="Id" />
</Principal>
<Dependent Role="ToRole_User_Dealer">
<PropertyRef Name="Id" />
</Dependent>
</ReferentialConstraint>
</Association>
I can get Users('PRX-00015'). I cannot get Users('PRX-00015')/Dealers or Users('PRX-00015')?$expand=Dealers. There are no errors, but also no data.
Here's Users.JSON:
[{
"__metadata": {
"id": "http://localhost/sap/opu/odata/sap/Y_DP_CORE_SRV/Users('PRX-00015')",
"uri": "http://localhost/sap/opu/odata/sap/Y_DP_CORE_SRV/Users('PRX-00015')",
"type": "Y_DP_CORE_SRV.User"
},
"Id": "PRX-00015",
"FullName": "Jorg",
"Email": "",
"Telephone": "",
"InternalUser": false,
"Enabled": true,
"Dealers": {
"results": [{
"__metadata": {
"id": "http://localhost/sap/opu/odata/sap/Y_DP_CORE_SRV/Dealers('AA2002')",
"uri": "http://localhost/sap/opu/odata/sap/Y_DP_CORE_SRV/Dealers('AA2002')",
"type": "Y_DP_CORE_SRV.Dealer"
},
"Id": "AA2002"
}, {
"__metadata": {
"id": "http://localhost/sap/opu/odata/sap/Y_DP_CORE_SRV/Dealers('AA1046')",
"uri": "http://localhost/sap/opu/odata/sap/Y_DP_CORE_SRV/Dealers('AA1046')",
"type": "Y_DP_CORE_SRV.Dealer"
},
"Id": "AA1046"
}]
},
}]
I can also use the unexpanded version of Dealers and move the array into a Dealers.json file of it's own, in which case the line looks like
"Dealers": {
"__deferred": {
"uri": "http://localhost/sap/opu/odata/sap/Y_DP_CORE_SRV/Users('PRX-00015')/Dealers"
}
}
And Dealers.json
[{
"__metadata": {
"id": "http://localhost/sap/opu/odata/sap/Y_DP_CORE_SRV/Dealers('AA2002')",
"uri": "http://localhost/sap/opu/odata/sap/Y_DP_CORE_SRV/Dealers('AA2002')",
"type": "Y_DP_CORE_SRV.Dealer"
},
"Id": "AA2002"
}, {
"__metadata": {
"id": "http://localhost/sap/opu/odata/sap/Y_DP_CORE_SRV/Dealers('AA1046')",
"uri": "http://localhost/sap/opu/odata/sap/Y_DP_CORE_SRV/Dealers('AA1046')",
"type": "Y_DP_CORE_SRV.Dealer"
},
"Id": "AA1046"
}]
All of these result in an empty Dealers array (Dealers.length being 0). Anyone know how this works?
Normally project structure looks like this:
webapp/
- localService/
- mockdata/
- Y_DP_CORE_SRV.User
- Y_DP_CORE_SRV.Dealer.json
- metadata.json
But it doesn't really matter where your files are, it's matter how is your MockServer is implemented. The MockServer should mock real urls to your real server and return fake data from the files provided by you. Have you taken a look at this example already - https://sapui5.hana.ondemand.com/test-resources/sap/ui/templateapps/demokit/master-detail/webapp/localService/mockserver.js ?
Regarding expands. Normally, in xml views you can expand related entities with binding property by defining expand parameter. In javascript it works similar way. I can provide few examples when I get to my computer, but It's not really related to the MockServer since all expand parameters stay the same regardless it's mockdata or real data from the real backend...
UPDATE: here is an example how to expand models from the views and access properties:
<QuickView
binding="{path: 'to_Supplier', parameters: { expand: 'to_Address,to_PrimaryContactPerson' }}">
<QuickViewPage
title="{CompanyName}"
description="{to_PrimaryContactPerson/FormattedContactName}">
<QuickViewGroup>
<QuickViewGroupElement value="{to_Address/FormattedAddress}"/>
</QuickViewGroup>
</QuickViewPage>
</QuickView>
What code have you write for read OData?
OData requires urlParameters at the read operation:
oModel.read("/User", {
urlParameters:{
"$expand" : "Dealers"
},
success: function (oData) {
var oJson = new sap.ui.model.json.JSONModel();
oJson.setData(oData);
oVista.setModel(oJson);
}
});
You have to read Users Set and expand with the navigation property of dealers, as a result you must be get a tree with corresponding users dealers append from Dealers property of Users JSON object similar to:
User: {
property1 : value1,
... : ...,
Dealers : [
{ dealer1Property1 : dealer1Value1,
.... : .... },
{ dealer2Property1 : dealer2Value2,
.... : .... },
... ]
}
After that, the xml code of Skay must be successful.