Why does the SAPUI5 getBindingContext("modelName") sometimes returns undefined? - sapui5

I have an UI5 component that loads 5 items automatically:
<m:Table id="idOverviewPage"
backgroundDesign="Translucent"
items="{path: 'theModel>/TheODataServiceThatGetsItems',
parameters : { $count : true },
sorter: { path: 'ProcessedDate', descending: true }}"
growing="true"
growingScrollToLoad="true"
growingThreshold="5"
width="auto">
The items in the table are represented with the <m:ColumnListItem > objects. The first 5 ColumnListItems get automatically obtained when the "TheODataServiceThatGetsItems" OData service from the above definition gets called as the page loads.
When the item is clicked on the following code runs:
var bindingContext = oEvent.getSource().getBindingContext("theModel");
This works, but when I navigate back to the Table, the next 5 records get automatically loaded. When any of the next 5 records are clicked on the above code returns undefined and the method fails. The first 5 records continue to work and the above code returns an object
What could be the cause of this ?

Related

Why do my dynamically generated forms not update correctly when I delete one of them using Vue?

I want to populate a form with multiple text input fields. However, when I delete one of these input fields, the UI doesn't update correctly according to the data present.
Condition.vue
<template v-slot:custom-form>
<el-row v-for="(condition, index) of customFormData" type="flex" :key="`${randomSalt}${index}`" class="condition row-bg" :justify="isElse(condition) ? 'start' : 'space-around'">
<el-col class="operator" :class="[ index === 0 ? 'operator-if' : '', isElse(condition) ? 'operator-else' : '']" :span=".5">
{{condition.type.toLowerCase() === 'else' ? 'ELSE' : index > 0 ? 'ELSE IF' : 'IF'}}
</el-col>
<el-col ref="index" v-if="condition.type.toLowerCase() !== 'else'" :span="6">
<editable-value name="inputAction" dataType="property" v-model="condition.inputAction" :schema="condition.schema"></editable-value>
</el-col>
<el-col v-if="condition.type.toLowerCase() !== 'else'" class="operator" :span=".5">
=
</el-col>
<el-col v-if="condition.type.toLowerCase() !== 'else'" :span="6">
<editable-value name="inputActionValue" dataType="value" v-model="condition.inputActionValue" :schema="condition.schema"></editable-value>
</el-col>
<el-col v-if="condition.type.toLowerCase() !== 'else'" class="operator" :span=".5">
THEN
</el-col>
<el-col :span="6">
<editable-value name="ouputAction" dataType="output" v-model="condition.ouputAction" :schema="condition.schema"></editable-value>
</el-col>
<el-col :span=".5">
<el-button icon="el-icon-delete" type="text" plain class="trans scale delete-condition el-col-offset-1 ev-controls" #click="e => { deleteCondition(index) }"></el-button>
</el-col>
</el-row>
</template>
export default {
components: { EditableValue },
data () {
return {
customFormData: [
{
type: 'if',
schema: Schema
}
],
salt: 1043423
}
},
computed: {
randomSalt () {
return this.salt + this.getRnd()
}
},
methods: {
deleteCondition (index) {
this.customFormData.splice(index, 1)
},
getRnd () {
return Math.floor((Math.random() * 1000000 / (Math.random() - 1)) + 1)
},
chainCondition (type) {
this.customFormData.push({ ...this.customFormData[this.customFormData.length], type, schema: this.schema })
}
}
}
Basically, each Editable-Values component represents an input field, customFormData is an array listing each row of input fields belonging to the main form. When deleting a row, we simply splice that row from the array and vice versa for addition.
Main Problem
Everything else works fine except when I delete a row that is neither the first nor last in the array. When such a deletion happens, the data (code) in the app is correct and represents the deletion.
The UI however, seems as if the row below the intended one was deleted. Using pseudocode, the behavior is as follows:
UI
<row 1 data="one"/>
<row 2 data="two" />
<row 3 data="three"/>
Code
data: {
1: 'one',
2: 'two',
3: 'three'
}
// If we delete row 2, the UI becomes as follows:
UI
<row 1 data="one"/>
<row 2 data="two" />
Code
data: {
1: 'one',
3: 'three'
}
What can I do to make sure that each row represents its data correctly.?
I tried forcing a rerender by changing the row key, however, this just ends up displaying empty input fields. I have also tried using an object instead of an array to display the rows, but the results are much worse, data doesn't seem so reactive with objects.
What can I do to fix this?
Your problem most probably comes from using v-for index as a key. key must be unique and stable - index satisfies only 1st condition (when the 2nd row is deleted, 3rd row now gets index 2 instead of 3 and Vue rendering mechanism makes false assumptions about what needs to be re-rendered/updated in the DOM)
Workaround with random key doesn't work either. computed caches the value and changes only when some of the reactive dependencies are changed (this.salt in this case) so you are actually using same key for all the rows. Changing it into method will lead to actually re-rendering everything all the time - this is not something you want either
Only way to solve it correctly is to assign unique ID to each row - it may very well be an index but it must be stored with each row so it does not change every time some row is deleted...

NavigationListItem & Sort

I tried to sort a NavigationListItem on a field which is different from the keyfield within the onInit method of the view controller w/o any success.
Note, there is no orderby implementation on the server side.
My xml view which doesn't implement any sorter since orderby isn't implemented: https://openui5.hana.ondemand.com/#/api/sap.tnt.NavigationListItem
<tnt:NavigationList id="navigationList">
<tnt:NavigationListItem icon="sap-icon://"
expanded="{expanded}"
key="{MY_KEY}"
items="{/MY_PATH}">
<tnt:NavigationListItem text="{MY_CONTENT}" key="{MY_KEY}"></tnt:NavigationListItem>
</tnt:NavigationListItem>
</tnt:NavigationList>
Here is an attempt to add a sorter that presently doesn't work. First, because return items correspond to the first level of NavigationListItem and secondly probably because of a wrong approach.
var oNavigationList = this.getView().byId('navigationList');
var aSorter = [];
aSorter[0] = new sap.ui.model.Sorter('MY_FIELD', true);
var oItems = oNavigationList.getItems();
oItems.sort("items", {path: "/MY_PATH", sorter: aSorter});
Could you please advice on the appropriate approach?
Since your List is bound to ODATA Model, all the sorting will happen at server side. Every time you say, sort, a new request is sent to server. Now, as you said, the server is not supporting orderby, I suggest the following approach :
Do oDataModel.read and store the data in a JSON Model. oData Read API
Apply the sorting in XML using below code:
<tnt:NavigationList id="navigationList">
<tnt:NavigationListItem icon="sap-icon://"
expanded="{expanded}" key="{MY_KEY}"
items="{
path : '/MY_PATH',
sorter: {
path: 'MY_CONTENT',
descending: false,
group: false
},
}">
<tnt:NavigationListItem text="{MY_CONTENT}"
key="{MY_KEY}"></tnt:NavigationListItem>
</tnt:NavigationListItem>
</tnt:NavigationList>
Let me know if you need more information.

Non reactive helper and template #each crashing

This is about a problem with a helper containing a dynamic query that involves reactive vars and the $where operator that does not rerun when the reactive vars values are changed. Then about how a try to solve it lead to a strange system behavior and crash.
I have a template in which we want to show a list of found documents inserted within an #each loop:
<template name="todo">
{{#each pendingMTs}}
<button class="showPendMT">
<h4> - <strong> {{name}}</strong> </h4>
</button>
{{/each}}
</template>
The pendingMTs helper searches for the documents in the Tasks collection with a dynamic and somehow elaborate -here simplified- query that involves using the $where operator:
pendingMTs() {
return Tasks.find(
{ $and: [ { owner: Meteor.userId() },
{ time: { $gt: 0 } },
{ $where: function()
{ return moment(this.createdAt).add( (((this.timeuts == 'Days') ? 1 : ((this.timeuts=='Meses') ? 30 : 365)) * this.time), 'days').diff(moment([currYear.get(), currMonth.get()])) < 0; }}
]
});
}
The two reactive vars involved in the search are defined at the creation of the template:
Template.todo.onCreated(function() {
var year = moment().format("YYYY");
var month = moment().format("M");
month = month - 1; //to values from 0 to 11
currYear = new ReactiveVar(year);
currMonth = new ReactiveVar(month);
});
Then in an event handler we modify the reactive vars upon a 'select' change, for instance for the currMonth:
'change #monthForecast' (event) {
event.preventDefault();
const target = event.target;
currMonth.set( target.value );
},
The first issue is that the helper is not rerun despite we modify through the event handler the value of the reactive vars: WHY??
Thinking that this might be due to the fact that the reactive vars are inside the $where function, I added a simple line at the beginning of the helper in order simply to create awareness for them in the helper:
var rerun = currYear.get(); rerun = currMonth.get();
Now certainly that made the helper to rerun any time any of the 2 reactive var was changed. But unfortunately this lead to a very strange behavior:
If the reactive vars were modified, but did not affect the documents retrieved by the helper, system was running fine, but:
When the modified reactive vars caused the helper to retrieve one more document than the number of documents retrieved the first time, the system crashed (and therefore the new document was not shown in the #each template):
Exception from Tracker recompute function: meteor.js:930:11
Error: Bad index in range.getMember: 3
While looking for the cause I found out that the bad index number given, 3 in this case, is always equal to the number of documents retrieved the first time the helper was executed. In this case, after modifying the value of one of the reactive vars, a 4th document had to be shown when system crashed.
I found some maybe related issues on https://github.com/GroundMeteor/db/issues/184
Could anyone point out how this dynamic query involving $where with reactive vars could be done, maintaining its natural reactivity?
Looks like you're not getting and setting your reactive-vars properly. You can't just call myReactiveVar.get() - they are (usually) attached to the template instance so your code should be:
In the pendingMTs helper, you access the instance with const instance = Template.instance(); and then in your $where function you would use instance.currentMonth.get() and instance.currentYear.get()
In the event, the second argument of the event is the template instance, so you would have:
'change #monthForecast' (event, templateInstance) {
event.preventDefault();
const target = event.target;
templateInstance.currMonth.set( target.value );
},
So you're not too far off!
https://docs.meteor.com/api/reactive-var.html
https://themeteorchef.com/tutorials/reactive-dict-reactive-vars-and-session-variables

What is use of 'parameters' option in binding object?

In the code below, what is the significance of parameters option and select option within it?
I have looked into API but to avail nothing.
<List id="list"
items="{
path: '/PurchaseOrders',
sorter: [{
path: 'ChangedAt',
descending: true
}, {
path: 'POId',
descending: false
}],
parameters: {
select: 'POId,OrderedByName,SupplierName,GrossAmount,CurrencyCode,ChangedAt,ItemCount'
}
}">
...
</List>
With the select property you can tell the server to return just a subset of entity properties. This is useful if you have large entities with a lot of properties, but want to display only some of them, e.g. in a table. This works only if you are using an ODataModel and thus an ODataListBinding.
In general the parameters argument is used to pass implementation specific parameters to the binding while having a common API for all model implementations, i.e. all implementations of a ListBinding have the following signature:
oModel (the model which holds the data)
sPath (the binding path)
oContext (the binding context)
aSorters (sorters to be applied)
aFilters (filters to be applied)
mParameters (additional parameters which depend on the model implementation and are not necessarily supported by all of them)

Meteor .find() from Collection returns [object Object]

Running on Ubuntu
Data.js
//Collections
Database = new Meteor.Collection('data');
if (Meteor.isClient) {
Template.main.data = function () {
var c = Database.find();
return c;
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
data.html
<head>
<title>data</title>
</head>
<body>
{{> main}}
</body>
<template name="main">
{{data}}
</template>
I inserted into the db using mongo:
> db.Database.insert({title: 'ShouldWork'});
> db.Database.find();
{ "_id" : ObjectId("5296403855ee6e1350b35afb"), "title" : "ShouldWork" }
Yet when I run the site it just returns [object Object]..
There should be autopublish on and insecure,
This has become quite the roadblock for me to clear in learning the framework.
This is expected. This is because the results of .find() are always a cursor and have multiple objects.
You have to decide which one you want or if you want to loop through each one.
1) You want to use one result:
var c = Database.findOne();
or 2) You want to iterate through each one:
{{#each data}}
{{title}}
{{/each}}
Additionally be sure to use the property of {{data}} because {{data}}, even with findOne is still an [Object object]. You should use something like {{data.title}} instead depending on the property you want to use.
How to access data in Mongo DB from html ?
First of all you need to have the Mongo DB instance present in global variable i:e it must be declared in any .js file as below. It is not part of client or server code
Say we create a Collection of Events in one of the js files.
EventList = new Mongo.Collection('Events');
Now, in order to access all the objects from HTML, we would need a Helper function inside client in our .js file. Below is Helper used:-
Template.viewEvent.helpers ({
//NOTE : 'event' is the name of variable from html template
'event' : function () {
//returns list of Objects for all Events
return EventList.find().fetch();
}
'users' : function () {
//returns reference to Document for all users
return UserList.find().fetch();
}
});
Just left to Display content on .html:-
Say EventList Collection has fields Event_Name, Event_Date. Below would be the html template code
<template name="viewEvent">
<h2>View Event</h2>
<!-- Iterate on all Objects fetched by the Helper Class-->
{{#each event}}
{{Event_Name}} : {{Event_Date}}
{{/each}}