SAPUI5 XML View Binding with parameters from same model - sapui5

I have the following Problem:
I want to develop a shopping cart and have problems with the counter of the product card and I have problems to show the data in the summary view.
For this project I use XML views and I've already readed a lot about binding. When I want to bind a static path I have no problems. The data comes from a JSON model named "cartData".
Example (from the goToCart Button)
...
text="{cartData>/currentUser}";
...
Everything shows correctly (in the example), but for my project I need to bind a main binding (for counter of the cart) and this path need a parameter for the user. Which is saved at the path like in the example.
I've already tried a lot of combinations to accomplish this bug, but now I have no more ideas :-(
A example of my tried combinations:
text="{ ${cartData>/cartOfUser/} + {cartData>/currentUser} + '/roles/counter'}"
EDIT:
Some dummy parts of my code:
My Button (doen't work yet how I need...):
<m:Button
id="details.Btn.ShowCart"
text="{ parts: [
{path: 'cartProducts>/cartEntries/'},
{path: 'cartProducts>/currentChoice/'},
{path: '/addedRoles/counter'}
]}"
type="Emphasized"
icon="sap-icon://cart-3"
iconFirst="true"
width="auto"
enabled="true"
visible="true"
iconDensityAware="false"
press="showCart"/>
How my JSON Model in LocalStorage look like:
{
"cartEntries": {
"counter": 2,
"UserId12": {
"UserId": "UserId12",
"Email": "Email12",
"dateCreated": "2017-07-14T13:18:13.632Z",
"dateUpdated": "2017-07-14T13:18:13.632Z",
"addedRoles": {
"counter": 0
},
"existingRoles": {
"counter": 0
}
},
"UserId14": {
"UserId": "UserId14",
"Email": "Email14",
"dateCreated": "2017-07-14T13:18:30.415Z",
"dateUpdated": "2017-07-14T13:18:30.415Z",
"addedRoles": {
"counter": 0
},
"existingRoles": {
"counter": 0
}
}
},
"currentChoice": "UserId14"
}
My JSON Data with comment:
I need to grab the value from "currentChoice", to search with this information in cartEntries for the right counter
How the Button look now:
It show the data not in the correct way. Please ignore the zero at first...
The goal is to take the value of "currentChoice" and use it as a 'parameter' to call the information for the right user..
What I also tried:
text="{= ${= 'cartProducts>/cartEntries/' + ${cartProducts>/currentChoice/} + '/addedRoles/counter' } }"
What works, but I need it more "dynamic" is:
text="{cartProducts>/cartEntries/UserId14/addedRoles/counter}"
I hope you guy's now know what I mean... :-/
Best regards
The Solution
How I solve the problem:
Add a formatter to the button:
/',
formatter: '.formatter._getCartInt'
}"
type="Emphasized"
icon="sap-icon://cart-3"
iconFirst="true"
width="auto"
enabled="true"
visible="true"
iconDensityAware="false"
press="showCart"/>
Implement the formatter in my formatter.js file:
_getCartInt: function (sP1) {
var sCurrent = sP1.currentChoice;
var sFinalString = "cartProducts>/cartEntries/" + sCurrent + "/addedRoles/counter";
this.getView().byId("btn.ShowCart").bindProperty("text",{path: sFinalString, type: new sap.ui.model.type.Integer()}); }

Try to use the following approach:
in i18n file:
cartInfoTitle=User: {0} has: {1} items in the cart
in XML view:
<Text text="{
parts: [
{path: 'i18n>cartInfoTitle'},
{path: 'modelName>/property1'},
{path: 'modelName>/property2'}
],
formatter: 'jQuery.sap.formatMessage'
}" />
So you declare the i18n entry and then use the predefined formatter to replace the placeholders with the values from the "parts" array (Documentation article).

Ok so to answer : you cannot use expression in a binding (same applies for classes). So to have the output you want you will indeed need a formatter + include the needed top level elements of your JSON model in the binding parts (so that it updates properly).
XML (I assume your model is called 'cartData')
<Text text="{
parts: [
'cartData>/cartEntries',
'cartData>/currentChoice'
],
formatter: '.myFormatter'
}" />
JS Controller
controller.prototype.myFormatter = function (cartEntries, currentChoice) {
if (cartEntries && cartEntries[currentChoice]) {
return cartEntries[currentChoice].addedRoles.counter;
}
}
[code not tested]

Related

UI5 i18n support for the static model data

I'm exploring the sap.f.ProductSwitch controller on a sample project sap.f.sample.ShellBarProductSwitch.
Everything is clear besides one thing, what should be the approach if I want to provide an i18n support for a list of products (model/data.json)?
E.g. additionally to the hardcoded English list of products:
{
"items": [
{
"src": "sap-icon://home",
"title": "Home"
}
]
}
I want to provide a Frech one:
{
"items": [
{
"src": "sap-icon://home",
"title": "Maison"
}
]
}
With a basic dialogues I can rely on the built-in i18n UI5-engine, but here I don't know how to enable i18n in an XML-template:
<f:ProductSwitchItem
src = "{src}"
title = "{title}" />
A home-made solution.
XML-template:
<f:ProductSwitchItem
src = "{src}"
title = "{titleI18NKey}" />
Controller:
const resourceBundle = this.getView().getModel("i18n").getResourceBundle();
const productSwitcherModelData = this.getView().getModel("productSwitcher")?.getData();
productSwitcherModelData.items.forEach((item) => {
item.titleI18NKey = resourceBundle.getText(item.titleI18NKey);
});
this.productSwitcher.setModel(this.getView().getModel("productSwitcher"));
In product switcher model instead of real text I store a key-value pair:
titleI18NKey: i18n_dialogue_key
which is later replaced by the end-text from the i18n-model and set to the productSwitcher model.
P.S. Please, let me know, if there is more elegant implementation based on UI5 functionality.
If you like the data binding approach better, you can use a formatter with the title property binding to retrieve the text from the resource model.
The key to the text of the resource bundle must be defined in your data.json for every item:
The JSON data model:
{
"items": [
{
"src": "sap-icon://home",
"titleI18NKey": "myResourceBundleKeyToThisItem"
}
]
}
The XML-template:
<ProductSwitch
change = "onClickProductSwitcherItem"
items = "{
path: '/items'
}">
<items>
<ProductSwitchItem
src = "{src}"
title = "{
path: 'titleI18NKey',
formatter: '.getText'
}" />
</items>
</ProductSwitch>
The formatter getText needs to be defined in your controller:
getText(i18nKey) {
const dialogue = this.getView().getModel("i18n").getProperty(i18nKey);
return dialogue;
}
If you only have static values I would rather not use list binding but create the product switch items individually:
<ProductSwitch change="fnChange" >
<items>
<ProductSwitchItem src="sap-icon://retail-store" title="{i18n>Title_1}" />
<ProductSwitchItem src="sap-icon://family-care" title="{i18n>Title_2}" />
</items>
</ProductSwitch >
In doing so you can use the resource model for the titles as usual and don't need any further code.

Calling formatter with parts and static value doesn't work

Basically I want to use a formatter function to fill the 3 properties of an sap.m.ObjectStatus (text, state, icon) depending on some static value.
<m:ObjectStatus
text="{
parts: [
{ path: 'currentRoles>STATE_TEXT' },
{ path: 'currentRoles>STATE' },
{ path: 'currentRoles>REFERENCED_ENTRY/SV_RH_ROLE_ACTIVE' },
{ path: 'currentRoles>invalid' },
{ value: 'text' }
],
formatter: '.formatter.Formatter.convertRoleStatus'
}"
...
/>
The strange thing is; if I omit the value part in the XML, the function is called. If it's included, the function gets never called in the first place.
As of the one of the answers to the post Pass Static Value to Formatter Parameters in XML View, passing parameters with value should work if the UI5 version is higher than 1.61. We use 1.71.2.
At other places in code, this works.
How to fix this issue?
Update: The issue is fixed now with commit: 4a9cf89 which will be available in 1.80+.
Now static bindings can be used even without any workarounds like the one mentioned below.
Original answer (workaround):
The issue is now reported in https://github.com/SAP/openui5/issues/2916. Thanks for making us aware of it!
A quick "fix" (I'd say a monkey patch) is to add model: 'myExistingModel' to the static binding info:
parts: [
{ path: 'currentRoles>STATE_TEXT' },
{ path: 'currentRoles>STATE' },
{ path: 'currentRoles>REFERENCED_ENTRY/SV_RH_ROLE_ACTIVE' },
{ path: 'currentRoles>invalid' },
{ value: 'text', model: 'currentRoles' }
],
This fix actually doesn't make sense since static bindings don't have any models, but can be used until the official fix arrives without the need to refactor a lot.
I suspect there is a limitation (possibly a bug):
If you do not use a named model this works for me:
...
??="{ parts : [ {path: 'a'}, {path: 'b'}, {path: 'c'}, {path: 'd'}, {value: 23} ], formatter: '.myFormatter'}"
...
let model = new JSONModel(this.getData());
this.getView().setModel(model);
...
myFormatter: function (a, b, c, d, v) {
console.log(a, b, c, d, v);
},
getData: function(){
return {"testdata": [
{ a: 1, b: "stringB", c: "stringC", d: "stringD"},
]};
}
console output: 1 "stringB" "stringC" "stringD" 23
The moment I name my model this stops working.
For now, if possible, use the default model for your data - not ideal?!
Try (you may have to do some model name trading?!) after assigning the named model as the default (un-named) model:
parts: [
{path: 'STATE_TEXT'},
{path: 'STATE'},
{path: 'REFERENCED_ENTRY/SV_RH_ROLE_ACTIVE'},
{path: 'invalid'},
{value: 'text'}
],
while this gets it to work you may want to raise this with the UI5 Team?
As for now, since changing the binding to a default one like Bernard propsed was not possible without heavy refactoring, I changed my formatter logic a bit in a way such as to create 3 seperat functions (with 4 parameters) that call the orginal convertRoleStatus function, each with different inputs for the fifth parameter, which is mode.
I will report the problem with SAP to hopfully resolve it someday.

SAPUI 5 - Aggregated control with two data models

I have a generic tile control that the tile get created base on number of entity available (e.g.: SCARRSet), which is normal. Then in the nested Tile content i need to show the airline logo where the image path is stored in a JSON file. My idea is to use the key in SCARRSet/Carrid to lookup the JSON file to find the image path. So the right image will be displayed for the airline.
Previously i put the image path in Url field which was fine, but that field was meant for something else. Now i want to do it properly.
<l:HorizontalLayout id="hLayout1" allowWrapping="true" content="{flight>/SCARRSet}">
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout"
header="{flight>Carrname}"
subheader="{flight>Carrid}"
press="onTilePressed"
backgroundImage="">
<TileContent unit="{flight>Currcode}" footer="">
<ImageContent src="{flight>Url}" />
</TileContent>
</GenericTile>
</l:HorizontalLayout>
The JSON file looks like following. Is there a way to iterate through each tile to lookup Key = SCARRSet/Carrid then populate imageContent src=(e.g.:"/image/AA.gif")?
{
"icon": [
{
"Key" : "AA",
"Path" : "/image/AA.gif"
},
...
]
}
I would use a formatter function to do the lookup:
src="{formatter: '.formatter.getIconUrl', path: 'flight>Carrid'}"
In the formatter getIconUrl you get the Carrid as input parameter.
For performance reasons I would also suggest to reformat the JSON once to have a hash access to the url: jsonData[carrid] returns the url.

How to delete pending changes in ui5?

I get three pending changes from oModel.getPendingChanges(), oModel is sap.ui.model.odata.v2.ODataModel
{
{
ASet('id1') : {id: 1}
},
{
BSet('id1') : {id: 1}
},
{
CSet('id1') : {id: 1}
}
}
I just want submit ASet. B and C changes is from ComboBox select. I have three ComboBoxes that are related to each other. I used binding to solve that.
<ComboBox
id="theSecondSelect"
selectionChange="onChange"
enabled="false"
showSecondaryValues="true"
value="{
path: 'propertySetId',
type: '.Utils.mandatoryValueType'}">
</ComboBox>
<items> is dynamically binding in controller.
I've even tried
for(var sBindingPath in oChanges) {
if(sBindingPath.indexOf("ASet") === -1) {
delete oModel.mChangedEntities[sBindingPath];
}
}
console.log(oModel.getPendingChanges());
I can see that pending changes has already been deleted, but the three requests still sent.
Any suggestion?
Update:
Remove pending changes by
_resetSelectChanges: function() {
var oChanges = this._oModel.getPendingChanges();
for (var sBindingPath in oChanges) {
if (sBindingPath.indexOf("ControlIoTImplementationSet") === -1) {
this._oModel.resetChanges(["/" + sBindingPath]);
}
}
}
But it will appear again after I close the form dialog.
See the API description of the resetChanges method here.
The method takes an array of strings as its parameter. Each string is a path of an entity which should be reset.
Sample call:
oModel.resetChanges(["/BSet('id1')", "/CSet('id1')"]);
This resets the changes of the two given entities. Thus only the changes to your ASet('id1') entity should be submitted.

mongodb query field in object that is being edited

How can I query a field in an object? My html retrieves all the objects in array called 'postcards'
Meteor.user.profile.postcards [
{
_id: 84fh83f,
field_one: "a name",
field_two: " winter whether",
field_three: " lost more writing"
},
{
_id: 6jsf58s,
field_one: "another name",
field_two: " topical issues ",
field_three: " lost more writing"
}
]
Note: I used random.Id() so each object in the array can be uniquely identified.
Setting a session value to this._id when the user is focused on the input field will retrieve this unique id, however, I would like to query the actual field in focus. The value in these fields are projected within the text input area by using the spacebars syntax within the html.
Can I somehow assign the name within the curly braces of the value attribute to a variable? Then query?
Is there a whole new way to achieve this?
I want to update that specific field in this object instead updating the entire object.
HTML:
{{#with currentUser.profile}}
{{#each postcards}}
<input id="one" value="{{field_one}}" type="text">
<input id="two" value="{{field_two}}" type="text">
<input id="three" value="{{field_three}}" type="text">
{{/each}}
{{/with}}
client.js
Within events, I would like to update the field on focus upon keyup.
Templates.myTemplate.events({
'keyup input[type=text]': _.throttle(function(event) {
Meteor.users.update(this._id, {$set: {**fieldbeingedited**: event.target.value}});
}, 500);
});
What you want to have is an ES6 capability named 'Computed property names'.
This is what is looks like :
var x = 'hello',
obj = {
[x]: 'world'
};
console.log(obj); // Object {hello: "world"}
You have two options :
- you use the meteor harmony package to transpile your es6 to es5 (https://github.com/mquandalle/meteor-harmony)
- you build your object first
To build you object first :
var obj = {};
obj[ this.targetField ] = event.target.value
Meteor.users.update(this._id, {$set: obj});