bindProperty to Single OData Entity - odata

In UI5, is it possible to bind a single attribute of a single entity to a control property if your model is an OData? Binding works ok if you bind an aggregation to an entity set but does not seem to work with properties to entities. Say I have an entity set called TestSet. Each "Test" has attribute Key and Name. I'd like to bind the title of a table to the Name of one of the entities on that set.
What's wrong with the following code?
createContent: function(oController) {
jQuery.sap.require("sap.ui.table.Table");
var oTable = new sap.ui.table.Table({title: "{/TestSet('01')/Name}"});
oTable.setModel(new sap.ui.model.odata.ODataModel("/path/to/root/of/odata/"));
oTable.bindProperty("title", "/TestSet('01')/Name");
return oTable;
},
OData works ok when tested in isolation. /TestSet returns set of Test entities and /TestSet('01') returns one of those entities.
I've tested binding to /Name, /TestSet('01')/Name, etc. Nothing seems to work.

You can effect a property binding like this by binding the control to the specific element (hierarchy, as it were, is aggregation->element->property). So taking your example, you could do this:
var oTable = new sap.ui.table.Table({
title : "{Name}"
});
and then when you do this:
oTable.bindElement("/Products(0)");
the HTTP call is made by the OData model mechanism and the value appears in the table's title property.
Here's a running example, using Northwind.

According to the developer guide ...
Requests to the back end are triggered by list bindings, element bindings, and CRUD functions provided by the ODataModel. Property bindings do not trigger requests.
Thus, instead of trying to bind data directly on the properties of the target control with an absolute path, leverage ContextBinding (aka. "Element Binding") on the parent control or on the target control itself, and then bind the data on the properties of the target control or even further on child controls with a relative path (> instead of >/ in the path).
We can bind a single entity either in JS (e.g. if entity keys are required) or in XML views.
In JS
Instead of hardcoding the key predicate of the entity type, make sure to create the binding path together with key(s) via v2.ODataModel.createKey() dynamically after $metadata is loaded.
const oTable = new Table({ // Required from "sap/ui/table/Table"
title: "{Name}", // No absolute but relative binding path (no '/')
});
const myODataModel = /*...*/;
await myODataModel.metadataLoaded();
const bindingPath = myODataModel.createKey("/TestSet", { // See stackoverflow/a/47016070
key1: "...",
key2: 1234,
}); // Returns e.g. "/TestSet(key1='test1',key2='1234l')"
oTable.bindElement({ // Triggers a request and resolves the relative {Name} property.
path: bindingPath,
parameters: {
expand: "...",
select: "...",
// ... for more options see sap/ui/model/odata/v2/ODataContextBinding
},
events: { // Event handlers can be also assigned
dataRequested: e => { /*...*/ },
dataReceived: e => { /*...*/ },
change: e => { /*...*/ },
},
});
Note: bindElement and bindObject are same APIs just with different names. See also GitHub issue#3000.
In XML views
In XML views, single object / entity can be bound via binding in any controls. But creating keys dynamically (with createKey as shown above) is not possible. Hence, use binding only in combination with a <NavigationProperty> name, where keys are not required to be defined.
<table:Table xmlns:table="sap.ui.table"
binding="{
path: 'ToThatSingleEntity',
parameters: {
expand: '...',
select: '...'
},
events: {
dataRequested: '.onDataRequested',
dataReceived: '.onDataReceived',
change: '.onDataFromModelChanged'
}
}"
title="{FromThatSingleEntity}">
binding="{/TestSet(<keys>)}" is also possible but, again, the keys need to be then hardcoded.

Related

Would it be possible to do lazy loading in Relay?

I have a Parent component which contains many Child components as an array. Each child component contains a huge amount of data. So, I decided to not load all of them when Parent get loaded. The data fetched from Parent container is as following:
{
...
childs: [childId1, childId2, ...] // Array of child id
...
}
Then, I would like to send one request per child by passing the child's id to back-end apis. Each child will be show up on the UI whenever its data get back, otherwise, a spinner icon is displayed for indicating the loading data.
Would it be possible to achieve this in Relay?
UPDATED:
Here is an example of the option 1:
Child container:
export default Relay.createContainer(Child, {
initialVariables: {
id: null,
hasQuery: false
},
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
child(id: $id) #include(if: $hasQuery) {
...
}
}
`,
},
});
Child component:
const Child = React.createClass({
componentWillMount() {
ChildContainer.setVariables({ id: this.props.childId, hasQuery: true });
}
});
Parent container:
export default Relay.createContainer(Parent, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
childIds // Return an array of child's id
Child.getFragment('viewer')
}
`,
},
});
Parent component:
const Parent = React.createClass({
render() {
this.props.viewer.childIds.map(childId => {
<Child childId={childId} />
});
}
});
The problem is that when each Child got rendered, it fetched its data and replaced the last Child data with its own data. For example, if childIds = [1, 2, 3], it displayed data of 3 three times on the screen; 3 3 3
There are two typical patterns for delayed data fetching in open-source Relay:
Use #include or #skip directives where the condition is initially set to false. After the UI loads, or in response to user action, set the condition to true (e.g. with setVariables).
Use nested <Relay.Renderer> components. The top-level <RelayRenderer> would fetch the minimum "required" data in one round trip and then display it, which would render additional <RelayRenderer>s that would fetch more data.
The second option seems best-suited to your use case: the top-level renderer would fetch the list of IDs only. Then it would render a list of UI components, each of which fetched more data about its ID. List items would render as their data resolves.
One potential downside of this approach is that the data for all the items will be fetched in parallel; the first item in the list won't necessarily be the first one to get its data and render. To mitigate this an application would have to maintain greater control of the ordering of fetches; Relay accommodates this via an injectable network layer. For example, you could batch requests to the server and/or ensure ordering (for example by intentionally delaying resolving responses of "later" requests until previous queries have completed). You might check out the community-driven react-relay-network-layer which implements some of these ideas and supports pluggable middleware to help achieve the rest.

Bonita 7 : Custom Widget property type to support JSON data

I am trying to create a new custom widget in Bonita 7 and I need a Property that can accept JSON data (not simple array collection) i.e., I want to assign below data
[
{
'header1': 'data1-1',
'header2': 'data1-2',
'header3': 'data1-3'
},
{
'header1': 'data2-1',
'header2': 'data2-2',
'header3': 'data2-3'
}
]
I tried all the available property types (text,choice ,html ,integer,boolean ,collection property) & I guess nothing works.
Could someone help me to achieve this.
I am trying to create a Smart-Table custom widget (https://github.com/lorenzofox3/Smart-Table) & I want the table data(rows) to be populated using a variable that is of JSON type.
Is there any other way of achieving this.
Thank you very much for your time
I am very sorry, collection property type actually works
This is what I did:
In the CUSTOM WIDGET EDITOR
1A) created a new property :
name : tableDataCollection
Label: TABLE_DATA
Type: collection (text type also works)
Default value: (trying to assign JSON data here didn't work for me)
1B) In the Controller box, use the property tableDataCollection defined in step 1A:
$scope.data = $scope.properties.tableDataCollection; (inside function ($scope) {})
1C) In the Template box, use the variable data defined in step 1B:
example : <tr ng-repeat="row in data">
2) In the PAGE EDITOR (FORM EDITOR), if you are using the custom widget created in step 1, you will see a new fields on the right related to your custom widget. In my case it is TABLE_DATA
2A) Now, create a new form variable of type JSON.
example : data2Var =
[{
"header1": "data1-1",
"header2": "data1-2",
"header3": "data1-3"
},
{
"header1": "data2-1",
"header2": "data2-2",
"header3": "data2-3"
}]
2B) Now, Assign the variable created in step 2A to your custom widget field.
in my case, I assigned data2Var to TABLE_DATA

How to create an OData service for SAPUI5 TreeTable?

Does someone know if there is a way to create a OData Service that is bindable to an SAPUI5 TreeTable element?
According to the example from here:
https://sapui5.netweaver.ondemand.com/sdk/#test-resources/sap/ui/table/demokit/TreeTable.html
The recursive structure of the data is a list. For example:
//Define some sample data
var oData = {
root:{
name: "root",
description: "root description",
checked: false,
0: {
name: "item1",
description: "item1 description",
checked: true,
...
I expected to solve that problem with an navigation at the service side. Like
Element with attributes and one attribute points to a list of Elements.
But this would not be the same.
I also detected this thread, but the answer is one year old:
http://scn.sap.com/thread/3389546
All in all, is the answer still true? Or is there a solution, so that the data binding works?
You can use treebinding from ODataModel. First of all, prepare backend. You need to cycle navigation in your entity set. For example, you have entity set ItemCollection and you add navigation property ItemNavigation which refers to ItemCollection. After just bind tree to your TreeTable:
var oTreeTable = this.getView().byId("myTreeTable");
oTreeTable.bindRows({
path : '/ItemCollection',
properties : {
navigation : {
'ItemCollection' : 'ItemNagigation'
}
}
});
Should work fine but be aware to use it with Tree control. TreeTable loads navigation item by click on its parent and Tree loads the whole tree at once.

Breeze navigation property is not observable

I don't understand something about collection navigation properties.
First, I went through metadata debug, all checks out. As per documentation, I expected the navigation collection property to be observable, e.g. I can get the collection via
Foo.Bar() // bar is KO observable array
However, I can't get it working like that.
My query:
new breeze.EntityQuery()
.from('Classes')
.where('ClassId', '==', id)
.expand('LessonOfClasses')
Results to:
[
{
"$id":"1",
"$type":"Model.Class, Model",
"ClassId":24,
"LessonOfClasses":[
{
"$id":"2",
"$type":"Model.LessonOfClass, Model",
"class_lesson_id":30,
"class_id":24,
"lesson_id":40,
"Class":{
"$ref":"1"
},
"Lesson":null
},
// other instances of LessonOfClass...
]
// other properties of Class...
}
[
Which looks fine to me. However, when I run the query the result is:
var classObject = data.results[0];
classObject.LessonOfClasses // returns normal array
classObject.LessonOfClasses() // error: not a function
Figured out when knockout isn't available as a dependency, Breeze results to using plain JSON objects.
The problem is I'm using knockout as a require.js dependency and the global ko reference wasn't set.
requirejs(['knockout'], function(ko) {
window.ko = ko;
})
Breeze is looking strictly for ko, as opposed to durandal which I think looks for knockout.

How do I bind the column with the property?

I'm trying connect slickgrid and breeze.js, but I got a problem.
breeze generates the js model for you, and the object properties has get and set methods like:
var p1 = myobj.property1();
var p2 = myobj.property2();
myobj.property1("Test");
But in the slickgrid columns model, how do I bind the column with the property?
columns : [
{
id: "id",
name: "ID",
field: "property1" //this way I only see for every row on the page the text "function..."
},
{
id: "prop2", name: "prop2", field: "property2"
}
]
Try using the breeze "backingStore" adapter instead of the default "ko" (knockout) adapter.
This requires just a single line of Breeze configuration near the top of the file
breeze.config.initializeAdapterInstance("modelLibrary", "backingStore", true);
The backingStore adapter creates ES5 props for your model instead of "knockout" properties. I guessing that these will be easier for slickgrid to bind to.

Resources