This issue is a follow up from the discussion at
https://github.com/SAP/openui5/issues/2288#issuecomment-452228266.
to dig deeper into the usage of the odata v4 model API from Javascript. Thanks for your feedback Thomas. Ok I think I need to give you a deeper look into what I am trying to implement and how the data in the backend look like. I will show 2 examples, one for a direct context binding which is used in 2 different views, and another one for the list binding which is used in 3 different views. I assume this covers most of the story.
For both examples, let's say I have a database with 3 tables.
Table 1 named "Profile" with columns:
- ProfileID as primary key
- BodyHeight as float
Table 2 named "ConfigOptions" with columns:
- fkProfileID as a primary key and foreign key linked to "Profile.ProfileID"
- MUnitLength as enum('cm','in')
- MUnitWeight as enum('kg','lb')
Table 3 named "BodyWeight" with columns:
- idBodyWeight as a primary key
- fkProfile as a foreign key linked to "Profile.ProfileID"
- WeightMeasurementDateTime as DateTime
- Weight as float
- The first example is a direct context binding.
View 1 called ConfgView provides configuration options for ConfigOptions(ProfileID=x). The ConfigOptions context for the user is directly bound to the layout which holds all controls. The view looks as follows:
View 2 called ProfileView is bound to Profile(ProfileID=x) and for this example shall display the BodyHeight of the user in the correctly computed weight unit. The view looks as follows
As you can see from the image, for the BodyHeight Input control I need information from 2 tables. Profile.BodyHeight for control.value and ConfigOptions.MUnitLength for control.description. Furthermore, to recompute the value for the selected unit I have written 2 customTypes, one for cm and one for inches. As I can only bind one path to the Input control at a time if I want to be able to read and write on the input control, I decided to use the ConfigOptions structure in javascript to manually update the Input control when needed. Therefore in the "onRoutePatternMatched" function I check if MUnitLength has changed in comparison to the last set configuration of the BodyHeight control. In case it changed I update the control. Code looks as follows:
// update input field for body height
gConfOptsContextBinding.getBoundContext().requestObject("MUnitLength")
.then(function (strLengthUnit) {
if (oBodyHeightInput.getSelectedKey() !== strLengthUnit) {
// we use the selectedKey attribute to hold current control state
oBodyHeightInput.setSelectedKey(strLengthUnit);
if (strLengthUnit === "cm") {
// display adequate length units
oBodyHeightInput.setDescription(this.translateText("strCm"));
oBodyHeightInput.bindProperty("value", {
path: "BodyHeight",
type: "mnd.model.MUnitLengthTypeCM"
});
// if control is not configured for selected unit type, do it
oBodyHeightInput.setType("Number");
} else {
// display adequate length units
oBodyHeightInput.setDescription(this.translateText("strFt_In_WideFormat"));
oBodyHeightInput.bindProperty("value", {
path: "BodyHeight",
type: "mnd.model.MUnitLengthTypeIN"
});
}
}
}.bind(this), function (oResult) {
console.error("Profile error:" + oResult);
});
I have tried to use the same gConfOptsContextBinding object which I use in ProfileView in Javascript, to bind it to the layout of the Controls in the ConfigView, i.e.
oVertLayout.setBindingContext(gConfOptsContextBinding.getBoundContext());
However, this does not work. When I try to change one of the values I get the following error:
2019-01-08 16:41:36.976899 Failed to update path /ConfigOptions(38)/MUnitLength - Error: Cannot set value on this binding
at http://localhost:9001/resources/sap/ui/core/library-preload.js:3610:496
at http://localhost:9001/resources/sap/ui/core/library-preload.js:252:173
at c (http://localhost:9001/resources/sap/ui/core/library-preload.js:247:99)
at new S (http://localhost:9001/resources/sap/ui/core/library-preload.js:249:773)
at S.then (http://localhost:9001/resources/sap/ui/core/library-preload.js:252:151)
at constructor.O.setValue (http://localhost:9001/resources/sap/ui/core/library-preload.js:3610:471)
at constructor.P._setBoundValue (http://localhost:9001/resources/sap-ui-core.js:1655:219)
at constructor.P.setExternalValue (http://localhost:9001/resources/sap-ui-core.js:1661:268)
at f.j.updateModelProperty (http://localhost:9001/resources/sap-ui-core.js:385:229)
at f.j.setProperty (http://localhost:9001/resources/sap-ui-core.js:341:269) sap.ui.model.odata.v4.ODataPropertyBinding
I thought that might be as I cannot reuse the same binding context and resulted in my approach to use
oVertLayout.bindElement("/ConfigOptions(" + gProfileID + ")");
and do a
gConfOptsContextBinding.refresh();
when a config option was changed, to push the change into the gConfOptsContextBinding binding context. So Thomas from your reply, it looks like this context sharing should work? Maybe I found a bug in OpenUI5 then?
The second example is related to the required use of a list binding from Javascript. Here I have three views. Again the ProfileView from the last example, which shows body weight in lb or kg. Again implemented with two customTypes one for lb and one for kg. Here is the view.
The table for the Body Weight just shows a single cell with an Input Control in a CustomListItem and should always show the most recent measurement. Hence it needs to get updated once a new measurement was added. The binding is given as
items="{path: '/BodyWeight', length:1, sorter: { path: 'WeightMeasurementDateTime', descending: true}}"
My choice here for a table instead of a single input control to show the most recent body weight measurement was more or less thought as a workaround, as I do not know how I could update the Input field, once BodyWeight measurements were added in a different view. My hope was that I could use the same ListBinding on different tables. This reflects probably your thinking if I correctly interpret your answer.
The second view is a dialog which opens once you press the + Button next to the Weight Measurement input field. Here one can add a new record. The Dialog looks as follows and again does Input Validation and formating with customTypes for the Input control.
The third view is now the BodyWeightView which shows a table with all body weight records and later also should show graphs with the data. It also allows to delete records.
Regarding your answer and the idea with getContexts(0, Infinity) . I do not like to load all data. There could be several hundred Weight measurements in the database, which I do not want to get loaded upfront. Only on request. A single List would get to slow if it shows all measurements, as the app should run on a mobile using Cordova, and I do not want to create more traffic than is required.
So for me now three questions are arising:
-
In respect to topic 1. Should sharing of the BindingContext in different views work for read and write operations on the data? In that case I would need to check in how far I can debug the issue. Is such an approach working for you? I use the mobile OpenUI5 package 1.61.1.
-
Is there an option to reload gConfOptsContextBinding from cached data rather than using refresh with a http request, to pull in the changes from the second binding to the same path? This would be my current approach but somewhat more efficient.
-
Regarding topic 2. What approach for datasync between the 3 views would you consider best and could you give me a code sniped to show what I need to do with the ListBinding? Maybe just read the values I like to display getContexts(0, 10) sorted by date and registering a change handler to be informed when data arive? Would I than use the same list binding on all three views, or different bindings?
regarding your comments:
-control. getBindingContext().submitBatch("auto")...
true, forgotten the getModel() when writing the issue text. It is there in code :)
-And then I thought "auto" looks a bit misleading. There is "$auto" which means you need not call submitBatch(),
I tried to call submitBatch("$auto") which gave an error and I was thinking I do have to call submitBatch without dollar. Well it does what it needs, but just by luck. You are right, I unintentionally created a batch group with it.
So what I have to ensure with my current solution is, that I do not call request object on my second binding before the data on the auto group were send. Is there a way that I can get informed when changes on the auto batch where commited to the server?
Thanks a lot!! The topic is not that easy for me :)
consulting