SAP UI5: error in binding - sapui5

Even though quite experienced in SAP HCM development, I have just started my quest to learn UI5 (using eclipse) so my apologies in advance if my question is a bit basic...
I am trying to create a binding of data (based upon the example of UI5 rockstar
DJ Adams) but for some reason, have no result.
in my controller I have entered the following code (in the onInit function) to create the data and make them available:
onInit: function() {
var cities = [ { id: "A1", name: "Kobe" },
{ id: "A2", name: "Hiroshoma" }
];
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData(cities);
sap.ui.getCore().setModel(oModel);
},
in my view, I try to bind the data using the following code:
</IconTabFilter>
<IconTabFilter
binding="{/cities/0}"
text="{name}"
icon="sap-icon://group"
design="Horizontal">
</IconTabFilter>
<IconTabFilter
binding="{/cities/1}"
text="{name} ({id})"
icon="sap-icon://group"
design="Horizontal">
</IconTabFilter>
</items>
</IconTabBar>
</content>
</Page>
</core:View>
in my output,all the elements display correctly, however I don't get the values that I initialised in my model. However I don't get any errors either
My questions:
1. can you provide some assistance/guidance to see where I made an error?
2. what would be the easiest way to detect where issues are when it comes to databinding (debugger, other tips)?
Many thanks for your guidance,
Tom

The error is indeed in your bindings.
Although you have a variable cities, your JSON context starts with id.
You could update your JSONModel to have root element cities:
.setModel({
cities: [
{ id: "A1", name: "Kobe" },
{ id: "A2", name: "Hiroshoma" }
]
});
As for debugging, I prefer the standard Google Chrome browser tools. It allows for watches, breakpoints, and (small) live code changes.

Please try to use template, instead of binding individual elements you can use the template to bind the array to individual elements.
Example:
<List
items="{/ProductCollection}"
headerText="Products">
<items>
<ObjectListItem
title="{Name}"
type="Active"
press="onListItemPress"
number="{Price}"
numberUnit="{CurrencyCode}">
<firstStatus>
<ObjectStatus
text="Overweight"
state="Error" />
</firstStatus>
<secondStatus>
<ObjectStatus
text="In Stock"
state="Success" />
</secondStatus>
<attributes>
<ObjectAttribute text="{WeightMeasure} {WeightUnit}" />
<ObjectAttribute text="{Width} x {Depth} x {Height} {DimUnit}" />
</attributes>
</ObjectListItem>
</items>
[Live example: https://sapui5.hana.ondemand.com/sdk/explored.html#/sample/sap.m.sample.ObjectListItem/code]
If you want to have finer control on the data then you can set multiple model with key value pair.
Example:
sap.ui.getCore().setModel("key",oModel);
and to get the value:
sap.ui.getCore().getModel("key");

Your approach of creating model is not quiet right. Variable cities is not object but an array. Either you can set data to model:
onInit: function() {
var cities ={ "cities": [{ "id": "A1", "name": "Kobe" },
{ "id": "A2", "name": "Hiroshoma" }
]}
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData(cities);
sap.ui.getCore().setModel(oModel);
},
or you can set property:
var cities = [ { id: "A1", name: "Kobe" },
{ id: "A2", name: "Hiroshoma" }
];
var oModel = new sap.ui.model.json.JSONModel();
oModel.setProperty("/cities", cities);
sap.ui.getCore().setModel(oModel);
and you also have to bind your xml correctly. You can follow the template of list items from SapUi5 explored or developers guide

Related

SAPUI5 sap.m.list in a popover is emtpy when populated using bindElement

I have a table (sap.m.table) with different columns. One columns contains a link and when the user clicks on the link a popover fragment opens and shows some details in a list (sap.m.list). The data is coming from an oData Service. One Entity feeds the table and through a navigation property the data for the popover is fetched.
I have a working example for this scenario where I create the the list template in the controller. But I believe that this should also be possible just by xml and a bindElement in the controller. What is the mistake in my second scenario?
First Scenario (which is working fine):
Popover XML:
<Popover
showHeader="false"
contentWidth="320px"
contentHeight="300px"
placement="Bottom"
ariaLabelledBy="master-title">
<Page
id="master"
class="sapUiResponsivePadding--header"
title="Aktionen">
<List
id="AktionList">
</List>
</Page>
</Popover>
Calling the Popover in the controller file (sPath is /TableEntity('123456')/AktionSet):
if (!this._oPopover) {
Fragment.load({
id: "popoverNavCon",
name: "bernmobil.ZPM_STOERUNG_FDA.view.AktionPopover",
controller: this
}).then(function(oPopover){
this._oPopover = oPopover;
this.getView().addDependent(this._oPopover);
var oList = Fragment.byId("popoverNavCon", "AktionList");
var oItemTemplate = this._BuildItemTemplate();
oList.bindAggregation("items", sPath, oItemTemplate);
this._oPopover.openBy(oControl);
}.bind(this));
} else {
var oList = Fragment.byId("popoverNavCon", "AktionList");
var oItemTemplate = this._BuildItemTemplate();
oList.bindAggregation("items", sPath, oItemTemplate);
this._oPopover.openBy(oControl);
}
_BuildItemTemplate: function(){
var oItemTemplate = new sap.m.ObjectListItem({
title:"{AktionsBez}",
type: "Inactive"
});
oItemTemplate.addAttribute(new sap.m.ObjectAttribute({
text : "{Aktionstext}"
}));
oItemTemplate.addAttribute(new sap.m.ObjectAttribute({
text : "{path: 'ChangedAt', type: 'sap.ui.model.type.DateTime'}"
}));
return oItemTemplate;
}
And this is the idea of the second scenario which is calling the oDataService but not displaying any data:
Instead of having only the List definition in XML also the ObjectListItem is defined in the XML:
<List
id="AktionList"
items="{AktionSet}">
<ObjectListItem
title="{AktionsBez}"
type="Active">
<ObjectAttribute text="{Aktionstext}" />
<ObjectAttribute text="{ChangedAt}" />
</ObjectListItem>
</List>
And in the controller instead of building the template and doing the bindAggretation there is just:
var oList = Fragment.byId("popoverNavCon", "AktionList");
oList.bindElement(sPath);
How do I get the second scenario displaying data in the list?
I would suggest a third solution:
When pressing on your table row, get the binding context of the current row using oContext = oClickedRow.getBindingContext() or something similar. This context should point to /TableEntity('123456').
Apply this context to your Popover using oPopover.setBindingContext(oContext).
Now your Popover has the same context as your table row. The XML should look like in your second scenario.
Using the path is an extra step imo. I think it also makes more sense to bind the complete Popover and not just the List. Has the neat side effect that you can use a property of your TableEntity as the title for the Popover. It also completely avoids working with controls directly (which is one of the seven deadly sins) and you should be able to remove all those IDs.
Here is how the relevant part of the view should look:
<List
id="AktionList"
items="{AktionSet}">
<dependents>
<ObjectListItem
id="oObjectListItem"
title="{AktionsBez}"
type="Active">
<ObjectAttribute text="{Aktionstext}" />
<ObjectAttribute text="{ChangedAt}" />
</ObjectListItem>
</dependents>
</List>
And in the controller:
var oList = sap.ui.core.Fragment.byId(this.getView().createId("popoverNavCon"), "AktionList");
oList.bindItems({
path: sPath,
template: sap.ui.core.Fragment.byId(this.getView().createId("popoverNavCon"), "oObjectListItem")
});
Check dependents in sap.m.List aggregation section for details.

Button Filter to Table

Trying to add a filter that takes a table that uses xsodata and have a dropdown that applies a filter
current code:
var testButton = new sap.m.Button('filterTable', {
text: "Filter",
tooltip: "Filter table to selection",
icon: sap.ui.core.IconPool.getIconURI("filter"),
press: new sap.ui.model.Filter(testTable['testColumn'], sap.ui.model.FilterOperator.EQ, "testValue")
});
I think that this example in the SDK is what you are looking for. Take a look at how the "availability" column is declared in the XML view and at the implementation of the toggleAvailabilityFilter function in the controller.
EDIT: Here the basic code, as example.
How the column should be declared in the XML View:
<Column
id="columnId"
filterProperty="Available"
showFilterMenuEntry="false"
defaultFilterOperator="EQ"
filterType="sap.ui.model.type.Boolean">
<m:Label text="Status" />
<template>
...template...
</template>
</Column>
Example of button callback:
toggleAvailabilityFilter : function(oEvent) {
this.byId("columnId").filter(oEvent.getParameter("pressed") ? "X" : "");
},

How to properly use JSONModel and setModel?

I'm trying to create an example screen using SAP Web IDE where clicking different buttons changes different texts around the screen.
I have a few functions at the App.controller.js and the code is this (All the functions do the same for now but affect different text areas):
onPressButton2: function () {
var oData = {
text: {
line1: "line1",
line2: "line2",
line3: "line3",
line4: "line4"
}
};
var oModel = new JSONModel(oData);
this.getView().setModel(oModel);
},
And this is corresponding part at the XML:
<items>
<Text xmlns="sap.m" text="{/text/line1}" id="text1"/>
<Text xmlns="sap.m" text="{/text/line2}" id="text2"/>
<Text xmlns="sap.m" text="{/text/line3}" id="text3"/>
<Text xmlns="sap.m" text="{/text/line4}" id="text4"/>
</items>
This works, but when I try and change different areas of the screen, the previous changes I made by clicking the buttons disappear. I assume this is because I use setModel anew every time which overwrites it but I cannot find the proper usage.
Should I create a different js file for every section in the screen?
Is there a way to update the model instead of overwriting it all?
Try to declare your JSONModel outside of the onPressButton function. You can declare it in the manifest to be visible for the entire application (controllers and views):
"sap.ui5": {
"_version": "1.1.0",
...
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"uri": "i18n/i18n.properties"
},
"MyModel" : {
"type" : "sap.ui.model.json.JSONModel"
}
Once the model is available you can set the data to it outside of the onPressButton2 function:
this.getOwnerComponent().getModel("MyModel").setData(oData)
Now, in the onPressButton2 function, you can just update the model's data using the setProperty method:
this.getOwnerComponent().getModel("MyModel").setProperty("/text/line1", "NewValue");
I think what you are searching are named models. with named models you are able create different models without overwriting them, if you want to additionally add a new model.
var oModel = new JSONModel(oData);
this.getView().setModel(oModel, "model1");
have a look at the second parameter in the setmodel method. now you can access them in the view with
<items>
<Text xmlns="sap.m" text="{model1>/text/line1}" id="text1"/>
<Text xmlns="sap.m" text="{model1>/text/line2}" id="text2"/>
<Text xmlns="sap.m" text="{model1>/text/line3}" id="text3"/>
<Text xmlns="sap.m" text="{model1>/text/line4}" id="text4"/>
</items>
You should create your model during the intilisation phase of the page lifecycle.
So, in your instance, create the model and the intial values in the onInit function of the relevant view/page:
onInit: function () {
var oData = {
text: {
line1: "line1",
line2: "line2",
line3: "line3",
line4: "line4"
}
};
var oModel = new JSONModel(oData);
this.getView().setModel(oModel);
Then, when you need to assign different values to that model for the existing values you would simply set the relevant property in the model as follows:
this.getView().getModel().setProperty("/text/line1", "<new value>");
if you wish to add an additional line you could simply get the existing model values and add the new value:
var mydata = this.getView().getModel().getProperty("/");
mydata.text["line5"] = "line5";
this.getView().setProperty("/", mydata);
Hope that helps.
I trust you are aware of the differences between the un-named model you were using and the concept of a named model.

SAPUI5 Binding Context for array hierarchy

I have a questions object which contains an answers array
var oModel = new JSONModel({
questions: [{
order: 1,
title: "",
answers: [{
sequence: 1,
label: "Yes",
}, {
sequence: 3,
label: "N/A",
}]
}]
});
And the context set as follows
this.getView().setModel(oModel, "viewmodel");
var oContext = oModel.createBindingContext("/questions/0/");
this.getView().setBindingContext(oContext, "viewmodel");
In my view, I can bind questions fine
<Input value="{viewmodel>title}"/>
However, updating the answers binding updates ALL the answer arrays in EVERY questions context!, e.g. below i'm binding to my current context (questions/0/) but update the label will update the answers for all questions....
<f:Form id="formCustomRadio" editable="true" visible="true" formContainers="{viewmodel>answers}">
<f:layout>
<f:ResponsiveGridLayout/>
</f:layout>
<f:formContainers>
<f:FormContainer title="Answer {viewmodel>sequence}">
<f:formElements>
<f:FormElement label=" {i18n>radioLabel}">
<f:fields>
<Input value="{viewmodel>label}"/>
</f:fields>
Any ideas?
The issue was related to the the array push when creating new questions...
questionData.questions.push({
order: questionData.questions.length + 1,
title: questionData.questions[questionData.questions.length - 1].title,
answers: questionData.questions[questionData.questions.length - 1].answers
});
As answers is an array of objects , you can't simply push to it as this creates duplicate references. That's the reason answers where duplicated after each update. Solution was to push each answer separately in a for loop.
for (var i = 0; i < answerArray.length; i++) {
questionData.questions[newIndex].answers.push({
sequence: answerArray[i].sequence,
label: answerArray[i].label,
points: answerArray[i].points,
imageFlag: answerArray[i].imageFlag,
image: answerArray[i].image,
mandatoryComments: answerArray[i].mandatoryComments,
additionalQuestion: answerArray[i].additionalQuestion
});
}

Passing Data from Master to Detail Page

I watched some tutorials about navigation + passing data between views, but it doesn't work in my case.
My goal is to achieve the follwing:
On the MainPage the user can see a table with products (JSON file). (Works fine!)
After pressing the "Details" button, the Details Page ("Form") is shown with all information about the selection.
The navigation works perfectly and the Detail page is showing up, however the data binding doesnt seem to work (no data is displayed)
My idea is to pass the JSON String to the Detail Page. How can I achieve that? Or is there a more elegant way?
Here is the code so far:
MainView Controller
sap.ui.controller("my.zodb_demo.MainView", {
onInit: function() {
var oModel = new sap.ui.model.json.JSONModel("zodb_demo/model/products.json");
var mainTable = this.getView().byId("productsTable");
this.getView().setModel(oModel);
mainTable.setModel(oModel);
mainTable.bindItems("/ProductCollection", new sap.m.ColumnListItem({
cells: [new sap.m.Text({
text: "{Name}"
}), new sap.m.Text({
text: "{SupplierName}"
}), new sap.m.Text({
text: "{Price}"
})]
}));
},
onDetailsPressed: function(oEvent) {
var oTable = this.getView().byId("productsTable");
var contexts = oTable.getSelectedContexts();
var items = contexts.map(function(c) {
return c.getObject();
});
var app = sap.ui.getCore().byId("mainApp");
var page = app.getPage("DetailsForm");
//Just to check if the selected JSON String is correct
alert(JSON.stringify(items));
//Navigation to the Detail Form
app.to(page, "flip");
}
});
Detail Form View:
<mvc:View xmlns:core="sap.ui.core" xmlns="sap.m" xmlns:f="sap.ui.layout.form" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mvc="sap.ui.core.mvc" controllerName="my.zodb_demo.DetailsForm">
<Page title="Details" showNavButton="true" navButtonPress="goBack">
<content>
<f:Form id="FormMain" minWidth="1024" maxContainerCols="2" editable="false" class="isReadonly">
<f:title>
<core:Title text="Information" />
</f:title>
<f:layout>
<f:ResponsiveGridLayout labelSpanL="3" labelSpanM="3" emptySpanL="4" emptySpanM="4" columnsL="1" columnsM="1" />
</f:layout>
<f:formContainers>
<f:FormContainer>
<f:formElements>
<f:FormElement label="Supplier Name">
<f:fields>
<Text text="{SupplierName}" id="nameText" />
</f:fields>
</f:FormElement>
<f:FormElement label="Product">
<f:fields>
<Text text="{Name}" />
</f:fields>
</f:FormElement>
</f:formElements>
</f:FormContainer>
</f:formContainers>
</f:Form>
</content>
</Page>
</mvc:View>
Detail Form Controller:
sap.ui.controller("my.zodb_demo.DetailsForm", {
goBack: function() {
var app = sap.ui.getCore().byId("mainApp");
app.back();
}
});
The recommended way to pass data between controllers is to use the EventBus
sap.ui.getCore().getEventBus();
You define a channel and event between the controllers. On your DetailController you subscribe to an event like this:
onInit : function() {
var eventBus = sap.ui.getCore().getEventBus();
// 1. ChannelName, 2. EventName, 3. Function to be executed, 4. Listener
eventBus.subscribe("MainDetailChannel", "onNavigateEvent", this.onDataReceived, this);)
},
onDataReceived : function(channel, event, data) {
// do something with the data (bind to model)
console.log(JSON.stringify(data));
}
And on your MainController you publish the Event:
...
//Navigation to the Detail Form
app.to(page,"flip");
var eventBus = sap.ui.getCore().getEventBus();
// 1. ChannelName, 2. EventName, 3. the data
eventBus.publish("MainDetailChannel", "onNavigateEvent", { foo : "bar" });
...
See the documentation here: https://openui5.hana.ondemand.com/docs/api/symbols/sap.ui.core.EventBus.html#subscribe
And a more detailed example:
http://scn.sap.com/community/developer-center/front-end/blog/2015/10/25/openui5-sapui5-communication-between-controllers--using-publish-and-subscribe-from-eventbus
Even though this question is old, the scenario is still valid today (it's a typical master-detail / n-to-1 scenario). On the other hand, the currently accepted solution is not only outdated but also a result of an xy-problem.
is there a more elegant way?
Absolutely. Take a look at this Flexible Column Layout tutorial: https://sdk.openui5.org/topic/c4de2df385174e58a689d9847c7553bd
No matter what control is used (App, SplitApp, or FlexibleColumnLayout), the concept is the same:
User clicks on an item from the master.
Get the binding context from the selected item by getBindingContext(/*modelName*/).
Pass only key(s) to the navTo parameters (no need to pass the whole item context).
In the "Detail" view:
Attach a handler to the patternMatched event of the navigated route in the Detail controller's onInit.
In the handler, create the corresponding key, by which the target entry can be uniquely identified, by accessing the event parameter arguments in which the passed key(s) are stored. In case of OData, use the API createKey.
With the created key, call bindObject with the path to the unique entry in order to propagate its context to the detail view.
The relative binding paths in the detail view can be then resolved every time when the detail page is viewed. As a bonus, this also enables deep link navigation or bookmark capability.
You can also set local json model to store your data, and use it in the corresponding views. But be sure to initialize or clear it in the right time.