I've used the below code to bind the odata services to sapui5 table
I was not able to get the data from the oData service to the SAPui5 table, odata is stored in separate vpn client. I'm using reverse proxy server to retrieve the data
Error in the console is shown in the below link
Code:
//Creating the instance of oData model
var oModel = new sap.ui.model.odata.v2.ODataModel("http://admin-think:88/sap/...",{useBatch : true});
sap.ui.getCore().setModel(oModel,"model1");
console.log(oModel);
// Create instance of table
var oTable = new sap.ui.table.Table({
visibleRowCount : 6,
selectionMode: sap.ui.table.SelectionMode.Single,
navigationMode: sap.ui.table.NavigationMode.scrollbar,
selectionBehavior: sap.ui.table.SelectionBehavior.RowOnly
});
// First column "Application"
oTable.addColumn(new sap.ui.table.Column({
label : new sap.ui.commons.Label({
text : "APPLICATION",
textAlign : "Center",
}),
template : new sap.ui.commons.TextView({
textAlign:"Center"}).bindProperty("text","model1>Applno"),
}));
// Bind model to table control
oTable.bindRows("model1>/");
Your service end-point is probably incorrect in this line: var oModel = new sap.ui.model.odata.v2.ODataModel("http://admin- think:88/sap/...",{useBatch : true});.
In the error output you sent, you can see that the application is trying to reach URL http://.../ZTEST1_SRV/CoreOpenAppSet()/$metadata. This results in a 404 status, meaning that that service is not available on that URL. The right URL for the app to download metadata from should probably be http://.../ZTEST1_SRV/$metadata instead.
To solve this, you should remove the CoreOpenAppSet() portion of the variable you're passing to the ODataModel constructor. Instead you should call this 'Function Import' using the callFunction of your ODataModel, i.e.: oModel.callFunction().
When the call of the function import completes and the returned promise is resolved, you can bind the result of the call to your UI using setBindingContext:
var oPromise = oModel.callFunction("/CoreOpenAppSet");
oPromise.contextCreated().then(function(oContext) {
oView.setBindingContext(oContext);
});
Also, it's a good practice to specify your model and endpoints in the manifest so that they're separated from your code. You can learn more about that here: https://sapui5.netweaver.ondemand.com/docs/guide/8f93bf2b2b13402e9f035128ce8b495f.html
Related
I am working on a Master-Detail app. I have a view with list control and I am binding the same with the data from an entityset called "entityset1".
Odata -> data from the entityset1
<serialno>122333</serialno>
I do have another entityset called entityset2 in the same service.
Odata -> data from the entityset2
<hdata>is Active</hdata>
Data from above entityset2 will only be retrieved with the filter (/sap/opu/odata/sap/My_SRV/entityset2?$filter=(serialno=122333)
I am now trying to retrieve the value from the entityset2 and trying to bind it to one attribute in my list. This list is already binded with the entityset1 data.
Myview.xml.
<List id="list" select="_handleSelect">
<ObjectListItem id="MAIN_LIST_ITEM" press="_handleItemPress" title="{Name}">
<attributes>
<ObjectAttribute id="ATTR1" text="{serialno}" />
<ObjectAttribute id="ATTR2" text="{entityset2/hdata}" />
</attributes>
</ObjectListItem>
</List>
Controller.js (binding using the below lines)
this.oList.bindAggregation("items", {
path: '/entityset1',
template: this.oListItem,
filters: this.searchFilters
});
var oserialnum = this.getView().getBindingContext().getObject().serialno;
var oHdata = new sap.ui.model.Filter("serialno", "EQ",oserialnum);
this.searchFilters = new sap.ui.model.Filter([oserialnum],true);
this.oList.bindAggregation("items",{
path : "/entityset2",
filters :this.searchFilters
});
However I am getting an error "Cannot read property 'getObject' of undefined" on this line "this.getView().getBindingContext().getObject().serialno".
Can someone kindly advise how to retrive the data from the entity2 and binding it to the list, ?
You cannot get BindingContext using the view. Read more about binding Context - it's a pointer to an object in Model data.
Also, serialNo(the parameter you are trying to retrieve from the Model is also contextual i.e. it differs with each row item).
One way to do this would be:
onListeItemPress Event of the List
<ObjectListItem ... ... press="onListItemPress" >
In the corresponding Controller
`onListItemPress : function(oEvent){
var oserialnum = Event.getSource().getBindingContext("mainODataModel")..getProperty("serialNo")`
Let me know if this helps.
If I understand you correctly what you need is associations.
They will allow the OData Service to deliver the needed Data from entityset2 directly with the entityset1 through "associating" entityset2 with your serial number.
If you are using a SAP Backend and SEGW this Blog might help you:
https://blogs.sap.com/2014/09/24/lets-code-associationnavigation-and-data-provider-expand-in-odata-service/
I was faced with a similar issue whilst creating a Master-Detail App, but found out from the SAP Forums that this is not possible, which makes sense and ended up creating a separate entityset in the Backend having a link to the other set
I have an OData connection using which I read an entityset with conditions that returns me data in the following structure. The data being in the root node itself rather than in an object array-
oData Structure
I am then setting this data to a JSON Model and binding this model to my oTable.
The code in controller looks something like below-
//Get data for the table
var oTabData = sap.ui.getCore().byId("MyTable");
var sServiceUrl = "http://example.com/sap/opu/odata/SAP/ZXX_SRV";
var oModel2 = new sap.ui.model.odata.ODataModel(sServiceUrl,true);
var oJsonModel = new sap.ui.model.json.JSONModel();
oModel2.read("/MyEntitySet(Myvar1='000001',Myvar2='abc')?
$format=json",null,null,true,function(oData,response){
oJsonModel.setData(oData);
});
oTabData.setModel(oJsonModel);
The view contains the table and the binding is as below. Have ignored rest of table declaration code to keep it short. Table declared as oTable with id MyTable and with all the right columns-
//Template to map the data to the respective column
var template = new sap.m.ColumnListItem({
id:"table_template",
type: "Navigation",
visible: true,
cells:[
new sap.m.Label({
text: "{/Myvar1}"
}),
new sap.m.Label({
text: "{/Myvar2}"
}),
new sap.m.Label({
text: "{/Myvar3}"
}),
new sap.m.Label({
text: "{/Myvar4}"
})
]
});
var oFilters = null;
oTable.bindItems("/",template,null,oFilters);
The issues are-
As I have made a service call with conditions the oData output
received does not output an array of data like it does when we make
a call without conditions
(oModel2.read("/MyEntitySet?$format=json",null,null,true,function(oData,response)).
In the latter case the output comes under results array for which
the binding assignment is /results and that works right as expected.
In current case (service call with conditions), as the columns in
output are available right in the root level, have used binding /
and in template as well using / in front of every element (e.g.
{/Myvar1} as seen above). But using this technique it also creates 3
default blank lines in table initially. When the above controller
code is executed, it fills the table 3 x 4 times i.e. 12 rows for a
single row output.
Due to organization restrictions I cannot put the original code here. But this is how the scenario is. Any help would be appreciated.
Thanks.
PS: Haven't used any loop that would generate the repeated lines.
You can bind your table directly to the ODataModel:
Steps:
Before doing binding, you need to create a template for the table rows, in sap.m.Table it is ColumnListItem control, I would suggest to carry it out to the XML fragment. Try to use XML for markup as much as possible, becasue it much clearer and maintainablier than js coding;
All the controls in "cells" should be bound to the rows' properties using relative paths.
Tell the View about your model by calling "oView.setModel(MODEL_INSTANCE)", by this line you set the default model to the view. From now on all the controls inside are able to see it. The model is default because the name of it was not specified.
oTable.bindItems("/MyEntitySet", { template: oColumnListItem, filters: aFilters}); - this will automatically trigger an OData call to grab all needed data. The table will be updated automatically. You should see the rows.
I'm using OData v4 to load data from my backend to my frontend (developed with SAP UI5) and I am using a form to display a detail page. When I click the "edit" button I'm able to edit the data. My implementation is similar to this example: https://sapui5.hana.ondemand.com/explored.html#/sample/sap.ui.layout.sample.Form354/code/Page.controller.js
When editing something, the data is directly edited at the model and, therefore, updated at the backend. However, I want to be able to choose if I want to save the changes or if I want to cancel the edit before it is updated at the backend.
I read on other questions that one can copy the ODataModel to a JSONModel and use that copy instead, by doing something like:
var oModel = this.getView().getModel();
var oModelJson = new sap.ui.model.json.JSONModel();
oModel.read("/Data", {
success: function(oData, response) {
oModelJson.setData(oData);
sap.ui.getCore().setModel(oModelJson, "oJSONModel");
alert("Success!");
},
error: function(response) {
alert("Error");
}
});
However, the read method seems not to be available for OData v4. the code of my controller where the data is loaded looks like following:
onInit: function() {
this.oModel = new ODataModel({
groupId : "$direct",
synchronizationMode : "None",
serviceUrl : '/odata/'
});
this.getView().setModel(this.oModel, 'oModel');
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.getRoute("details").attachPatternMatched(this._onObjectMatched, this);
this._showFormFragment("display");
},
_onObjectMatched: function (oEvent) {
this.getView().bindElement({
path: "/Data(" + oEvent.getParameter("arguments").dataPath + ")",
model: "oModel"
});
//I want to copy the data from the ODataModel to the JSONModel here
},
What's the best way to accomplish this? And how to do it with OData v4?
I suppose you want to resetChanges in case user cancels the save.
For V2 ODataModel, there is deferedGroup concept which you can use to resetChanges or submitChanges.
I have not much experience with V4. Though from the documentation, it is possible.
Please try to pass a updateGroupId in the constructor. Then you can choose resetChanges or submitBatch by group Id.
mParameters.updateGroupId? The group ID that is used for update requests. If no update group ID is specified, mParameters.groupId is used. Valid update group IDs are undefined, '$auto', '$direct' or an application group ID, which is a non-empty string consisting of alphanumeric characters from the basic Latin alphabet, including the underscore.
Thank you!
I want to use smart Table like in this sapui5 explored sample but the problem is I have a OData model and the example shows only how we can handle the binding with mock data and also I didn't understand the metadata.xml file. I guess oData model has also its own metadata document. Here my codes in controller :
this.DataPath = "QuarterPerformanceSet";
var oModel = new sap.ui.model.odata.ODataModel(model.Config.getServiceUrl(), true, model.user, password);
oModel.setCountSupported(false);
oSmartTable.setModel(oModel);
oSmartTable.setEntitySet(this.DataPath);
but it doesn't work. I got this error :
Component-changes.json could not be loaded from ./Component-changes.json. Check for 'file not found' or parse errors. Reason: Not Found -
getChanges' failed: -
Simply how can I set entitySet using my odata model?
my view:
<smartTable:SmartTable id="idSmartTable" tableType="Table"
useExportToExcel="true" useVariantManagement="false"
useTablePersonalisation="true" header="Line Items" showRowCount="true"
persistencyKey="SmartTableAnalytical_Explored" enableAutoBinding="true"/>
Thank you in advance if someone can help.
UPDATE 2 : I rebind table according to this discussion
this.DataPath = "QuarterPerformanceSet";
var oModel = new sap.ui.model.odata.ODataModel(model.Config.getServiceUrl(), true, model.user, password);
oModel.setCountSupported(false);
var oSmartTable = this.getView().byId("idSmartTable");
oSmartTable.setModel(oModel);
oSmartTable.setEntitySet(this.DataPath);
oSmartTable.rebindTable();
sad to say but still I got same error.
You need to pass the name of entity set, not the model instance. If you have for example an entity set Customers defined you just do:
oSmartTable.setEntitySet("Customers");
or add the attribute entitySet to your table declaration.
<smartTable:SmartTable id="idSmartTable" entitySet="ENTITY_SET" .../>
I have the following Model:
window.MyModel = Backbone.Model.extend({
initialize: function(props){
this.url = props.url;
}
parse: function(){
// #override- parsing data fetched from URL
}
});
// instantiate
var mod = new MyModel({url: 'some/url/here'});
I use this global variable 'mod' to fetch some data into this model from backend.
// fetch
mod.fetch({
success: function(){ ...},
error: ...
});
All above works well....
My Issue: I want to reuse this model by changing resetting the url and call fetch but it does not update the url somehow. I have tried the following:
mod.fetch({
data: {url:'/some/other/url'},
postData: true,
success: function(){ //process data},
error: ...
});
mod.set({url: '/some/other/url'});
// called fetch() without data: and postData: attributes as mentioned in previous
How do I set the url for my model so that I could call fetch() and it fetches data from updated url? Am I missing something. Thanks for any pointers..
UPDATE 1: Basically, I am unable to get updated values if I did
model.set({url: 'new value'});
followed by
model.fetch();
'model' is a global variable. Creating a fresh instance of 'model' works:
model = new Model({url:'/some/other/url'});
model.fetch();
however, works as required. Does this mean that a model instance is permanently attached to a url and it cannot be reset?
ANSWER TO MY QUESTION in UPDATE 1 Model instance is not permanently attached to a url. It can be reset dynamically. Please read through #tkone's thorough explanation and then #fguillens' solution for a better understanding.
After have understood the #tkone 's explanation...
If you still want to have a dynamic Model.url you always can delay its construction to run time, try this:
window.MyModel = Backbone.Model.extend({
url: function(){
return this.instanceUrl;
},
initialize: function(props){
this.instanceUrl = props.url;
}
}
Well the answer here is that you want to do:
mod.url = '/some/other/url'
The URL isn't part of the instance of the model itself, but rather an attribute of the MyModel object that you're creating your model instance from. Therefore, you'd just set it like it was an normal JavaScript object property. set is used only when the data you're setting (or conversely getting with get) is actually an attribute of the data you want to send/receive from the server.
But why you're changing the URL is the question we should be asking. The idea behind Backbone's model/collection system is that you speak to a REST endpoint and each model has a corresponding endpoint.
Like you've got a blog and that blog has an "entry" object which is available at:
/rest/entry/
And you've got a Backbone model for Entry:
Entry = Backbone.Model.extend({urlBase: '/rest/entry'});
Now when you save or fetch Backbone knows how this works.
So like you're making a new model:
e = new Entry();
e.set({title: "my blog rulez", body: "this is the best blog evar!!!!1!!"});
e.save();
This would then make Backbone do an HTTP POST request to /rest/entry with the body:
{
"title": "my blog rulez",
"body": "this is the best blog evar!!!!1!!"
}
(When you do your mod.set({url: '/some/other/url'}); you're actually adding a field called url to the dataset, so the server would send "url": "/some/other/url" as part of that JSON POST body above:
{
"title": "my blog rulez",
"body": "this is the best blog evar!!!!1!!",
"url": "/some/other/url"
}
The server would then respond with an HTTP 200 (or 201) response with the same model, only with, like, say, and ID attached:
{
"id": 1,
"title": "my blog rulez",
"body": "this is the best blog evar!!!!1!!"
}
And that's not what you're looking for, right?)
Now you've got this model and it's got an ID. This means if you change it:
e.set('title', 'my blog is actually just ok');
e.save()
Backbone now makes an HTTP PUT request on /rest/entry/1 to update the resource on the server.
The server sees that you're talking about ID 1 on the /rest/entry/ endpoint, so knows to update an existing record (and send back an HTTP 200).
TL;DR
Don't change the URL, Backbone will. Make a new model for a new piece of data.
model.urlRoot = "/your/url"
OR
model.urlRoot = function(){ return "/your/url"; }
OR
model.url = "/your/url"
OR
model.url = function(){ return "/your/url"; }
Default 'url' property of a Backbone.Model object is as below. Backbone.js doc says:
Default URL for the model's representation on the server -- if you're using Backbone's restful methods, override this to change the endpoint that will be called.
url: function() {
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
},
Clearly, It first gets the value of urlRoot, if not available, it will look at the url of the collection to which model belongs. By defining urlRoot instead of url has an advantage of falling back to collection url in case urlRoot is null.
You can set url as option in fetch function, like this:
var mod = new MyModel();
mod.fetch({
url: '/some/other/url',
data: {}
});