VizFrame doesn't update when the bound model changes - sapui5

I have a sap.viz.ui5.controls.VizFrame with its data property bound to a two-way JSON model in my view. Additionally, I have a Button and a Text element in the same view. The Text element displays the values of the model, which is bound against the VizFrame, and the Button has a press event handler, which increases the value of one property of the model.
So, when I press the button, the Text updates automatically, unlike the VizFrame. Do you have any suggestions on how to fix this? I saw this vizUpdate method, but it looked very complex and is probably overkill. However, changing the whole model also changes the displayed data of the VizFrame.
Code Example
Main.view.xml
<mvc:View controllerName="demo.chart.controller.Main" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m"
xmlns:viz="sap.viz.ui5.controls" xmlns:viz.data="sap.viz.ui5.data" xmlns:viz.feeds="sap.viz.ui5.controls.common.feeds">
<Shell id="shell">
<App id="app">
<pages>
<Page id="page" title="{i18n>title}">
<content>
<VBox>
<viz:VizFrame id="vizFrame" uiConfig="{applicationSet:'fiori'}" vizType='donut'>
<viz:dataset>
<viz.data:FlattenedDataset data="{data>/}">
<viz.data:dimensions>
<viz.data:DimensionDefinition name="Type" value="{manipulatedData>type}"/>
</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="Type"/>
<viz.feeds:FeedItem uid="size" type="Measure" values="Amount"/>
</viz:feeds>
</viz:VizFrame>
<Button text="+1 Foo" press="onButtonPress"/>
<Text text="Foo: {data>/0/amount} | Bar: {data>/1/amount}"/>
</VBox>
</content>
</Page>
</pages>
</App>
</Shell>
</mvc:View>
Main.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel"
], function (Controller, JSONModel) {
"use strict";
return Controller.extend("demo.chart.controller.Main", {
onInit: function () {
this.mData = new JSONModel([{
type: "foo",
amount: 23
}, {
type: "bar",
amount: 20
}]).setDefaultBindingMode("TwoWay");
this.getView().setModel(this.mData, "data");
},
onButtonPress() {
this.mData.setProperty("/0/amount", this.mData.getProperty("/0/amount") + 1);
}
});
});

I guess I figured it out. You have to rebind the data to the dataset aggregation after you changed the model:
Working Example
Main.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel"
], function (Controller, JSONModel) {
"use strict";
return Controller.extend("demo.chart.controller.Main", {
onInit: function () {
this.mData = new JSONModel([{
type: "foo",
amount: 23
}, {
type: "bar",
amount: 20
}]).setDefaultBindingMode("TwoWay");
this.getView().setModel(this.mData, "data");
},
onButtonPress() {
this.mData.setProperty("/0/amount", this.mData.getProperty("/0/amount") + 1);
this.byId("vizFrame").getDataset().bindData({
path: "data>/"
});
}
});
});

Related

How to get $count into a tile

I am an ABAP developer who is just trying to take the first steps in SAPUI5/Fiori.
So my very simple task is to create a fiori app with a single tile to show the number of EDIDC records.
View1.controller.js:
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("QuickStartApplication.controller.View1", {
onGetValue: function ( ) {
var oTileValue = this.getView().byId("tile1");
this.getModel().read("/EDIDCSet/$count", {
success: $.proxy(function (oEvent, oResponse) {
// var count = Number(oResponse.body);
var count = "1337";
oTileValue.setValue(count);
}, this)
});
}
});
});
View1.view.xml:
<mvc:View xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m"
controllerName="QuickStartApplication.controller.View1">
<App id="idAppControl">
<pages>
<Page xmlns="sap.m" id="pageId" title="TileTest" floatingFooter="true">
<content>
<Button xmlns="sap.m" text="Button" id="button0" press=".onGetValue"/>
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="Country-Specific Profit Margin" subheader="Expenses"
press="press" id="tile1">
<TileContent unit="EUR" footer="Current Quarter">
<NumericContent scale="M" value="1000" valueColor="Error" indicator="Up" withMargin="false"/>
</TileContent>
</GenericTile>
</content>
</Page>
</pages>
</App>
</mvc:View>
As you can see, I can't even pass the value "1337" that I hardcoded.
​My first mistake was to adress the tile and not the numeric content of the file. So first, I gave the ID to the numeric content:
​
<NumericContent scale="M" value="" valueColor="Error" indicator="Up" withMargin="false" id="num1"/>
​
​Then, I've adjusted the line, to access the object:
var oTileValue = this.getView().byId("num1");
​The last thing I learned was, that the model is connected to the view. So to access this, I changed the line to:
​this.getView().getModel().read("/EDIDCSet/$count", {
​Now I can call my function onGetValue and it's working.

Table grouping resets selected values when adding new item

Here we have a very basic table with an Add button and grouping activated. When I select values for the existing items and then press the button to add a new row, my selected values are getting reset, but without grouping it works fine. Am I doing something wrong or is this a bug?
sap.ui.getCore().attachInit(() => sap.ui.require([
"sap/ui/core/mvc/XMLView",
"sap/ui/model/json/JSONModel" // sample model. Can be also an ODataModel.
], async (XMLView, JSONModel) => {
"use strict";
const control = await XMLView.create({
definition: `<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">
<Table items="{
path: '/tableItems',
sorter: { path : 'group', group: true }
}">
<headerToolbar>
<OverflowToolbar>
<Title text="Test Table" />
<ToolbarSpacer/>
<Button id="addButton" text="Add" type="Emphasized" />
</OverflowToolbar>
</headerToolbar>
<columns>
<Column width="20rem">
<Label text="col0"/>
</Column>
<Column width="20rem">
<Label text="col1"/>
</Column>
</columns>
<ColumnListItem vAlign="Middle">
<Text text="{text}" />
<Select forceSelection="false" width="100%" xmlns:core="sap.ui.core">
<core:Item text="test1" key="test1" />
<core:Item text="test2" key="test2" />
<core:Item text="test3" key="test3" />
<core:Item text="test4" key="test4" />
</Select>
</ColumnListItem>
</Table>
</mvc:View>`,
afterInit: function() {
this.byId("addButton").attachPress(onPressAdd, this);
function onPressAdd() {
const oModel = this.getModel();
const aItems = oModel.getProperty("/tableItems");
const newItems = aItems.concat({
group: "3",
text: "3-1",
key: "3-1",
});
oModel.setProperty("/tableItems", newItems);
}
},
models: new JSONModel({
number: 0,
tableItems: [
{
group: "1",
text: "1-1",
key: "1-1",
},
{
group: "1",
text: "1-2",
key: "1-2",
},
{
group: "2",
text: "2-1",
key: "2-1",
},
{
group: "2",
text: "2-2",
key: "2-2",
},
],
}),
});
control.placeAt("content");
}));
<script id="sap-ui-bootstrap"
src="https://openui5.hana.ondemand.com/1.82.2/resources/sap-ui-core.js"
data-sap-ui-libs="sap.ui.core,sap.m"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-compatversion="edge"
data-sap-ui-async="true"
data-sap-ui-excludejquerycompat="true"
data-sap-ui-xx-waitfortheme="init"
></script>
<body id="content" class="sapUiBody sapUiSizeCompact"></body>
Set the growing="true" to the sap.m.Table.
This enables GrowingEnablement internally which prevents rerendering the entire ListBase (Table) when the target gets invalidated. Only the necessary item will be then appended to the table.
Generally, in order to optimize the rendering behavior, it's always a good practice to..:
Enable the growing property if the table / list is editable, shrinkable, or expandable.
Add key: <propertyName with unique values> to the ListBinding info object to benefit from the Extended Change Detection if the model is a client-side model such as JSONModel. With an ODataModel, the key is automatically added by the framework.
<Table xmlns="sap.m"
growing="true"
items="{
path: 'myModel>/myCollection',
key: <propertyName>, (if 'myModel' is not an ODataModel)
sorter: ...
}"
>

Add a New Item to a Table / List

I have a fragment / view written in XML which contains a simple table with some columns and one ColumnListItem:
<m:Table id="CorrectiveActionsTable">
<m:columns>
<m:Column>
<m:Text text="Lfd. Nr"/>
</m:Column>
<m:Column width="30%">
<m:Text text=""/>
</m:Column>
<m:Column>
<m:Text text="gefordert von"/>
</m:Column>
<m:Column>
<m:Text text="Durchführungsverantwortung"/>
</m:Column>
<m:Column>
<m:Text text="Planungstermin"/>
</m:Column>
<m:Column>
<m:Text text="IST-Termin"/>
</m:Column>
</m:columns>
<m:ColumnListItem id="ListItem_00">
<m:Text text="1"/>
<m:TextArea
value="senf"
rows="6"
width="100%"
/>
<m:Input placeholder="bla"/>
<m:Input placeholder="bla2"/>
<m:DatePicker placeholder="bla3"/>
<m:DatePicker placeholder="bla4"/>
</m:ColumnListItem>
</m:Table>
<m:HBox>
<m:Button
text="Add Button"
visible="true"
press="onAddButton"
icon="sap-icon://add"
/>
</m:HBox>
The Button should be used to add a new ColumnListItem to the Table.
I think I should write the onAddButton function in the controller but I don't know where to start.
For now, my controller looks like this:
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/ColumnListItem",
"sap/m/Text",
"sap/m/TextArea",
"sap/m/Input",
"sap/m/DatePicker"
], function(Controller, ColumnListItem, Text, TextArea, Input, DatePicker) {
"use strict";
return Controller.extend("...", {
onAddButton: function(oEvent) {
var columnListItemNewLine = new ColumnListItem({
cells: [
new Text({
text: "1"
}),
new TextArea({
value: "senf",
rows: "6",
width: "30%"
}),
new Input({
type: "text",
placeholder: "bla"
}),
new Input({
type: "text",
placeholder: "bla2"
}),
new DatePicker({
placeholder: "bla3"
}),
new Datepicker({
placeholder: "bla4"
})
]
});
this._oTable.addItem(columnListItemNewLine);
}
});
});
And I'm pretty sure there is a better way to do this than my approach.
Resolution
Bind the collection of data to the aggregation of table (e.g. <items>).
Add a new entry via the model (instead of to the UI directly) when the user clicks on Add.
Thanks to the aggregation binding, UI5 will create a new sap.m.ColumnListItem for you and you did not break the MVC pattern. Here are some examples, using..:
v2.ODataModel
Call createEntry and later submitChanges to send it to backend.
Demo: plnkr.co/F3t6gI8TPUZwCOnA (Click on the Add button to create a new "Flight").
Documentation: OData V2 - Creating Entities
v4.ODataModel
See the documentation topic OData V4 - Creating an Entity.
JSONModel
globalThis.onUI5Init = () => sap.ui.require([
"sap/ui/core/mvc/XMLView",
"sap/ui/model/json/JSONModel",
], async (XMLView, JSONModel) => {
"use strict";
const control = await XMLView.create({
definition: document.getElementById("myxmlview").textContent,
models: new JSONModel({
myArray: [],
}),
});
control.placeAt("content");
});
function onAddItemPress(event) {
const model = event.getSource().getModel();
const newArray = model.getProperty("/myArray").concat({
id: globalThis.crypto.randomUUID(),
text: "My New Item",
});
model.setProperty("/myArray", newArray, null, true);
}
html, body { height: 100%; }
body { margin: 0; }
<script defer id="sap-ui-bootstrap"
src="https://sdk.openui5.org/resources/sap-ui-core.js"
data-sap-ui-oninit="onUI5Init"
data-sap-ui-libs="sap.ui.core,sap.m"
data-sap-ui-theme="sap_horizon"
data-sap-ui-async="true"
data-sap-ui-compatversion="edge"
data-sap-ui-excludejquerycompat="true"
data-sap-ui-xx-waitfortheme="init"
></script>
<script id="myxmlview" type="text/xml">
<mvc:View xmlns:mvc="sap.ui.core.mvc" height="100%" displayBlock="true">
<Page xmlns="sap.m" title="My Items ({= ${/myArray}.length})">
<headerContent>
<Button text="Add" type="Emphasized" press="onAddItemPress" />
</headerContent>
<Table xmlns="sap.m"
growing="true"
items="{
path: '/myArray',
templateShareable: false,
key: 'id'
}">
<columns>
<Column>
<Text text="UUID" />
</Column>
<Column>
<Text text="Text" />
</Column>
</columns>
<ColumnListItem>
<Text text="{id}" />
<Text text="{text}" />
</ColumnListItem>
</Table>
</Page>
</mvc:View>
</script>
<body id="content" class="sapUiBody sapUiSizeCompact">
</body>
For client-side models such as JSONModel, calling setProperty is sufficient. DO NOT use push or modify the internal model reference directly.
⚠️ Note
Do not modify the control aggregation manually, e.g. via myListControl.addItem, after binding the aggregation (items). Instead, apply the changes from the model as described above.
Try with this code below
onAddButton: function(oEvent){
var columnListItemNewLine = new sap.m.ColumnListItem({
cells:[
new sap.m.Text({text: "1"}),
new sap.m.TextArea({value: "senf", rows: "6", width:
"30%"}),
new sap.m.Input({type: "text", placeholder: "bla"}),
new sap.m.Input({type: "text", placeholder: "bla2"}),
new sap.m.DatePicker({placeholder: "bla3"}),
new sap.m.Datepicker({placeholder: "bla4"})
]
});
this._oTable.removeAllItems();
this._oTable.addItem(columnListItemNewLine);
}

Object Page with blocks

I've got this ObjectPageLayout:
request.view.xml
<ObjectPageLayout>
<headerTitle>
...
</headerTitle>
<headerContent>
...
</headerContent>
<sections>
<ObjectPageSection
mode="Collapsed">
<subSections>
<ObjectPageSubSection title="fooBlock">
<blocks>
<blockdetail:FormBlock columnLayout="auto" /> <!-- MY BLOCK -->
</blocks>
</ObjectPageSubSection>
</subSections>
</ObjectPageSection>
</sections>
</ObjectPageLayout>
FormBlockCollapsed.view.xml (MY BLOCK)
<mvc:View xmlns:f="sap.ui.layout.form" xmlns:mvc="sap.ui.core.mvc"
xmlns:core="sap.ui.core" xmlns:l="sap.ui.layout" xmlns="sap.m"
controllerName="NAMESPACE.blocks.DetailsBlockCommon">
<FlexBox>
<HBox>
<VBox>
<f:SimpleForm >
<f:content>
<CheckBox class="sapUiSmallMarginBegin sapUiSmallMarginTop" id="myCheckbox" />
</f:content>
</f:SimpleForm>
</VBox>
</HBox>
</FlexBox>
...
</mvc:View>
So far, everything is fine. My Object page looks good a the checkbox is shown.
In my Controller request.controller.js i want to validate the checkbox in FormBlockCollapsed.view.xml
validateBlockForm: function(format){
console.log( oView.byId("myCheckbox").checked() ); //oView.byId("myCheckbox") is undefined
}
But i've no access to my checkbox in the block.
Cannot read property 'checked' of undefined
Further infos
FormBlock.js
sap.ui.define(['sap/uxap/BlockBase'], function (BlockBase) {
"use strict";
var MultiViewBlock = BlockBase.extend("NAMESPACE.blocks.FormBlock", {
metadata: {
views: {
Collapsed: {
viewName: "NAMESPACE.blocks.FormBlockCollapsed",
type: "XML"
}
}
}
});
return MultiViewBlock;
}, true);
DetailBlockCommon.js
sap.ui.define([
"NAMESPACE/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("NAMESPACE.blocks.DetailsBlockCommon", {
});
});
You can access the elements in a section by having the block's id and element's id.
First we have to define an id for the block in the xml view:
<blocks>
<blockdetail:FormBlock id="formBlock" columnLayout="auto" /> <!-- MY BLOCK -->
</blocks>
Then we generate the id of the elements like the following in the view controller. The state of the art here is to add -Collapsed-- as part of the id that adds by SAPUI5 to sections elements.
var sFieldId = this.getView().createId("formBlock") +
"-Collapsed--myCheckbox";
var oCheckbox = sap.ui.getCore().byId(sFieldId);
Check if oView is defined.
you can easily get it by this.getView()

Pass data from one view to another view, using target

I am new to sapui5, i am trying to make an app with two views.one view with multicomboBox and another with dynamic table , after selection of items from multicomboBox i have to click submit button. on submit button click i have to navigate to another view and create a table with column name, column name will be selected values of multicomboBox.
i am using target to navigate and used xml view.
The problem is i am getting the selected items, how to pass the selected data to another view and create the table is the issue i am facing.
i am posting the views and controllers below. The below code is taken from sapui5 Explored, i have combined multicomboBox and Target Program. Thanks in advance
View1.xml
<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
xmlns:l="sap.ui.layout"
xmlns:html="http://www.w3.org/1999/xhtml"
controllerName="sap.ui.core.sample.trial.targetsApp.controller.View1"
xmlns:form="sap.ui.layout.form">
<App>
<Page title="Example 1">
<MultiComboBox selectionChange="handleSelectionChange" selectionFinish="handleSelectionFinish" width="500px"
items="{
path: '/Collection',
sorter: { path: 'Name' }
}">
<core:Item key="{ProductId}" text="{Name}" />
</MultiComboBox>
<footer>
<Bar>
<contentRight>
<Button text="Submit" press="onToView2" />
</contentRight>
</Bar>
</footer>
</Page>
</App>
</mvc:View>
View2.xml
<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
xmlns:l="sap.ui.layout"
xmlns:html="http://www.w3.org/1999/xhtml"
controllerName="sap.ui.core.sample.trial.targetsApp.controller.View2"
xmlns:form="sap.ui.layout.form">
<App>
<Page title="TableView"
showNavButton="true"
navButtonPress="onBack">
<footer>
<Bar>
<contentRight>
<Button text="Submit" press="onToView2" />
</contentRight>
</Bar>
</footer>
</Page>
</App>
</mvc:View>
controller for view1
sap.ui.define([
'jquery.sap.global',
'sap/m/MessageToast',
'sap/ui/core/mvc/Controller',
'sap/ui/model/json/JSONModel'
], function(jQuery, MessageToast, Controller, JSONModel) {
"use strict";
var PageController = Controller.extend("sap.ui.core.sample.trial.targetsApp.controller.View1", {
onInit: function () {
// set explored app's demo model on this sample
var oModel = new JSONModel(jQuery.sap.getModulePath("sap.ui.demo.mock", "/products.json"));
this.getView().setModel(oModel);
},
onToView2 : function () {
this.getOwnerComponent().getTargets().display("page2");
},
handleSelectionChange: function(oEvent) {
var changedItem = oEvent.getParameter("changedItem");
var isSelected = oEvent.getParameter("selected");
var state = "Selected";
if (!isSelected) {
state = "Deselected"
}
MessageToast.show("Event 'selectionChange': " + state + " '" + changedItem.getText() + "'", {
width: "auto"
});
},
handleSelectionFinish: function(oEvent) {
var selectedItems = oEvent.getParameter("selectedItems");
var messageText = "Event 'selectionFinished': [";
for (var i = 0; i < selectedItems.length; i++) {
messageText += "'" + selectedItems[i].getText() + "'";
if (i != selectedItems.length-1) {
messageText += ",";
}
}
messageText += "]";
MessageToast.show(messageText, {
width: "auto"
});
},
handleNav: function(evt){
this.getOwnerComponent().getTargets().display("page2");
}
});
return PageController;
}, true);
controller for view2
sap.ui.define( ["sap/ui/core/mvc/Controller"], function (Controller) {
"use strict";
return Controller.extend("sap.ui.core.sample.trial.targetsApp.controller.View2", {
onBack : function () {
this.getOwnerComponent().getTargets().display("page1");
}
});
},true);
Edited controller2.js
sap.ui.define( ["sap/ui/core/mvc/Controller"], function (Controller) {
"use strict";
return Controller.extend("sap.ui.core.sample.trial.targetsApp.controller.View2", {
onInit:function(){
//console.log(sap.ui.getCore().AppContext.selectedArray);
//console.log(sap.ui.getCore().AppContext.selectedArray.length);
var oTable = new sap.m.Table();
for(var i = 0;i < sap.ui.getCore().AppContext.selectedArray.length;i++){
oTable.addColumn(new sap.m.Column({
demandPopin : true,
header:new sap.m.Text({text:sap.ui.getCore().AppContext.selectedArray[i]})
}));
}
//not working i want to place it in view2.xml
//oTable.placeAt("content");
},
displayPress : function(){
console.log(sap.ui.getCore().AppContext.selectedArray);
console.log(sap.ui.getCore().AppContext.selectedArray.length);
console.log(sap.ui.getCore().AppContext.selectedArray[0]);
console.log(sap.ui.getCore().AppContext.selectedArray[1]);
},
onBack : function () {
this.getOwnerComponent().getTargets().display("page1");
}
});
}, true);
edited view2.xml
<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
xmlns:l="sap.ui.layout"
xmlns:html="http://www.w3.org/1999/xhtml"
controllerName="sap.ui.core.sample.trial.targetsApp.controller.View2"
xmlns:form="sap.ui.layout.form">
<App>
<Page title="TableView"
showNavButton="true"
navButtonPress="onBack">
<l:HorizontalLayout id="tableid">
</l:HorizontalLayout>
<footer>
<Bar>
<contentRight>
<Button text="Add New Rows" press="displayPress" />
<Button text="display" press="displayPress" />
</contentRight>
</Bar>
</footer>
</Page>
</App>
</mvc:View>
Use in handleNav function:
//yourData can be any variable or object
this.getOwnerComponent().getTargets().display("page2", yourData);
Read more here:
display(vTargets, vData?): sap.ui.core.routing.Targets