How to properly use JSONModel and setModel? - sapui5

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.

Related

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 bind a calendar and two time pickers properly to a sap.m.table?

I am learning SAPUI5 at the moment and created a little application for myself to play around and learn by practice. Before I get to my question, I will provide some short information about my app, so you know what I am trying to do and what should be the expected result.
What does the app?
The only thing this application does is providing a calendar and a table for the user. If the user clicks on a date, then the table should be filled with this date and two time pickers to give a start and end time for this selected day. For every date a new row should be created.
The actual problem:
The problem is a design problem I guess. The table gets filled with the dates and the time pickers, but my way of doing this is, is bad practice I guess. I store my selected dates in two models, one for the calendar, one copy to bind it to the table. That works and dates are saved and I can access the data via model. When a new dates get added to the table, the two timepickers get added too because they are provided in the ColumnListItem in the XML view, but no model is bound to them nor do I have access to the values of the timepickers e.g. via ID. And here is the problem, until now I found no proper way of how to them bind to a model or another clean way nor to access the values of the created timepickers.
The question:
How would you implement this in a clean way, so that the time pickers are bound in a right way maybe to a model or models? and you can access their data? I would be thankful if you could give me an advice or hint how I should implement this in a clean way since I want to learn from this and don't want to start hacking around with bad practices just to achieve the goal in shorter time.
The relevant sourcecode:
Controller:
var CalendarController = Controller.extend("sap.ui.unified.sample.CalendarMultipleDaySelection.CalendarMultipleDaySelection", {
oFormatYyyymmdd: null,
oModel: null,
onInit: function(oEvt) {
this.oFormatYyyymmdd = sap.ui.core.format.DateFormat.getInstance({
pattern: "dd.MM.yyyy",
calendarType: sap.ui.core.CalendarType.Gregorian
});
this.oModel = new JSONModel({
selectedDates: []
});
this.oCopyModel = new JSONModel({
selectedDates: []
});
var oCalendar = this.getView().byId("calendar");
oCalendar.setModel(this.oModel);
},
handleCalendarSelect: function(oEvt) {
var oCalendar = oEvt.oSource;
var aSelectedDates = oCalendar.getSelectedDates();
console.log(aSelectedDates);
var oDate;
var oData = {
selectedDates: []
};
var oTable = this.getView().byId("dateTable");
if (aSelectedDates.length > 0) {
for (var i = 0; i < aSelectedDates.length; i++) {
oDate = aSelectedDates[i].getStartDate();
oData.selectedDates.push({
Date: this.oFormatYyyymmdd.format(oDate)
});
}
this.oModel.setData(oData);
if (this.oCopyModel.getProperty("/selectedDates/length") >= 0) {
this.oCopyModel.setData(oData);
oTable.setModel(this.oCopyModel);
}
} else {
this._clearModel();
}
},
return CalendarController;
View:
<content>
<unified:Calendar id="calendar" select="handleCalendarSelect" intervalSelection="false" singleSelection="false"/>
<Table id="dateTable" items="{path: '/selectedDates', sorter: {path: 'Date', comparator: '.dateComperator'}}"mode="None" fixedLayout="true">
<columns>
<Column>
<header>
<Text text="Date"/>
</header>
</Column>
<Column>
<header>
<Text text="Beginning"/>
</header>
</Column>
<Column>
<header>
<Text text="End"/>
</header>
</Column>
</columns>
<ColumnListItem>
<Text text="{Date}"/>
<TimePicker value="10:00" valueFormat="HH:mm" displayFormat="HH:mm" change="handleChange"/>
<TimePicker value="11:00" valueFormat="HH:mm" displayFormat="HH:mm" change="handleChange"/>
</ColumnListItem>
</Table>
Kind regards
Maximilian
I created a small example:
https://next.plnkr.co/edit/OGmJimjF2YZ46mv6DsF2?preview
A few points:
I simply added a few properties (startTime and endTime) to a selected date. You can now modify the time with the timepicker, the changes are stored in the model.
The data binding of the calender seems broken. I also had to use getSelectedDates. This may be due to singleSelection="false". When using single selection you can access the selected date (or interval) via data binding.
Never access internal properties (oEvt.oSource). There are accessors for this (oEvt.getSource()).

Binding OData Service to view

I want to bind oData services to SAPUI5 view but not bind. How to fix this problem?
tes.view.xml
<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m"
controllerName="tes.tes">
<Page title="Title">
<content>
<Label text="Hai dunia!"></Label>
<List
headerText="Products"
items="{
path: '/DATA'
}" >
<StandardListItem
title="{NAME}"
counter="{DESC}"/>
</List>
</content>
</Page>
</mvc:View>
tes.controller.js
sap.ui.define([
'jquery.sap.global',
'sap/m/MessageToast',
'sap/ui/core/Fragment',
'sap/ui/core/mvc/Controller',
'sap/ui/model/json/JSONModel'
], function(jQuery, MessageToast, Fragment, Controller, JSONModel) {
"use strict";
var CController = Controller.extend("tes.tes", {
onInit : function () {
var model = new sap.ui.model.odata.ODataModel("http://192.168.78.23:8000/Tes/WebContent/tes/data.xsodata", false);
console.log(model);
var model2 = new JSONModel(model);
this.getView().setModel(model2);
}
});
return CController;
});
data.xsodata
service namespace "tes" {
"HANATES"."USER" as "DATA";
}
project structure:
data:
Data not bind to view SAPUI5.
Thanks.
Bobby
The problem lies with your models:
var model = new sap.ui.model.odata.ODataModel("http://192.168.78.23:8000/Tes/WebContent/tes/data.xsodata", false); - This step is right. You are creating a oData Model.
var model2 = new JSONModel(model); - This here is a problem. JSONModel constructor will accept either the URL where to load the JSON from or a JS object but you are passing an OData Model instance. This step will not fetch the data from oDataModel.
this.getView().setModel(model2);' - Change this to this.getView().setModel(model);` - Make ODataModel as your default model to view ( Since you have done the binding - /data in your view.)
NOTE: If you want to bind a JSONModel to your view, then :
Call ODataModel.read method to fetch data from Server.
in Sucess handler of oDataModel.read, copy the data to JSON Model.
Bind the respective JSON Model to view.
LINK: for OdataModel read method https://openui5.hana.ondemand.com/#docs/api/symbols/sap.ui.model.odata.ODataModel.html#read
Let me know if you need more info.

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.

SAP UI5: error in binding

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