Live Update the Number of Items - sapui5

I have a requirement where I need to live update the number of list items to Page's sub-header. I want use sap.ui.base.EventProvider, aggregation binding, or expression binding. Please walk me through as I have never used it before.
If I delete a list item, the number of list item should live update.

Client-side Models
If a client-side model such as JSONModel is used (i.e. assuming all the data are already available on the client) and if the target collection is an array, a simple expression binding is sufficient:
title="{= ${myJSONModel>/myProducts}.length}"
Sample 1 (ClientModel): Updating count after deleting items from sap.m.Table
Sample 2 (ClientModel): Updating count after filtering sap.ui.table.Table
As you can see in the above samples, when the number of items changes, the framework notifies the Expression Binding which eventually updates the property value automatically.
Server-side Models (e.g. OData)
OData V2
Using updateFinished event from sap.m.ListBaseapi
Especially if the growing feature is enabled, this event comes in handy to get always the new count value which the framework assigns to the event parameter total.
[The parameter total] can be used if the growing property is set to true.
Sample 3 (v2.ODataModel): Updating count after filtering sap.m.ListBase
<List
growing="true"
items="{/Products}"
updateFinished=".onUpdateFinished"
>
onUpdateFinished: function(event) {
const reason = event.getParameter("reason"); // "Filter", "Sort", "Refresh", "Growing", ..
const count = event.getParameter("total"); // Do something with this $count value
// ...
},
The updateFinished event is fired after items binding is updated and processed by the control. The event parameter "total" provides the value of $count that has been requested according to the operation such as filtering, sorting, etc..
Using change event from sap.ui.model.Bindingapi
This event can be applied to any bindings which comes in handy especially if the control doesn't support the updateFinished event.
someAggregation="{
path: '/Products',
events: {
change: '.onChange'
}
}"
onChange: function(event) {
const reason = event.getParameter("reason"); // See: sap.ui.model.ChangeReason
const count = event.getSource().getLength();
// ...
},
event.getSource() returns the corresponding (List)Binding object which has the result of $count (or $inlinecount) stored internally. We can get that count result by calling the public API getLength().
One downside is that there is no "growing" reason included in sap.ui.model.ChangeReason. But if the control can grow, it's probably derived from the ListBase anyway which supports the updateFinished event.
Manual trigger (Only in V2)
If there is no list binding at all but the count value is still required, we can always send a request manually to get the count value. For this, append the system query $count to the path in the read method:
myV2ODataModel.read("/Products/$count", {
filters: [/*...*/],
success: function(data) {
const count = +data; // "+" parses the string to number.
// ...
}.bind(this),
})
OData V4
Please, take a look at the documentation topic Binding Collection Inline Count.

I will guess that "MyListModel" is your model name and inside it you have something like this:
[
{objectName: "object1"},
{objectName: "object2"},
{objectName: "object3"}
]
Then try:
<Page title="{= ${myListModel>/}.length}">

Related

How Do I Generate RowId For Intermediate Group Rows?

I am working on implementing grouping w/ the Server Side Row Model. I need to generate an appropriate ID for the intermediate group rows. For example, if I group by Status then I would have intermediate rows representing each Status (NEW, IN PROGRESS, COMPLETE, etc). I need to come up with a unique ID for these rows (but preferable something deterministic if they need to be accessed/updated later).
The getRowId function is passed an object that contains things like the row's data, the previous parent group values, a reference to the api, etc.
What I would ideally like to know is the current list of group fields... I have all of the values readily accessible, but I don't know what field the current row is being grouped by - else I could just go grab that field from the row's data to use as part of the row id...
Is there any good way to acquire this information?
The columnApi exposes the 'getRowGroupColumns' function from which the field property can be deduced:
getRowId: ({ columnApi, data, level, parentKeys = [] }) => {
const groupColumns = columnApi.getRowGroupColumns();
if (groupColumns.length > level) {
const field = groupColumns[level].getColDef().field;
return [...parentKeys, data[field]].join('-');
}
return [...parentKeys, data.athlete, data.year];
},

Search for child items matching certain name values

This page describes how to retrieve an item, (immediate) child items and searching in Sitecore9 using the RESTful API (via PostMan).
What it doesn't appear to say is how to combine those queries.
I would like to search the children of an item which is specified by path. So, currently, I have this returning an item:
GET https://{{sitecorehost}}/sitecore/api/ssc/aggregate/content/Items('{{sitecorehome}}/banners-tests')?sc_apikey={{sitecore-master-apikey}}
I also have this returning the child items of that item:
GET https://{{sitecorehost}}/sitecore/api/ssc/aggregate/content/Items('{{sitecorehome}}/banners-tests/Subcontent/Image and Texts')/Children?sc_apikey={{sitecore-master-apikey}}
However, because the children are not immediate children - they are two levels down at /Subcontent/Image and Texts - I cannot request them. Yes, I could search for them, but then any items would come back with the matching criteria and I only want to search items under that particular path.
I would like something which, I imagine, would look something like this:
GET https://{{sitecorehost}}/sitecore/api/ssc/aggregate/content/Items?sc_apikey={{sitecore-master-apikey}}&$filter=Name eq 'banner' and Path eq 'banners-tests'
Or perhaps this:
GET https://{{sitecorehost}}/sitecore/api/ssc/aggregate/content/Items('{{sitecorehome}}/banners-tests')/Children?sc_apikey={{sitecore-master-apikey}}&$filter=Name eq 'banner'
But these do not work.
#Matt We can do filtering based on the item path. For example, considering item path as :
'sitecore/content/home/tenant1/Subcontent/Image and Texts/neededitem' - the one needed
'sitecore/content/home/tenant1/Subcontent/Image and
Texts/item1/neededitem/notneededitem' - the one we need to exclude
since '/' is not a valid character in the Sitecore item name and indicates the children of the needed item.
Hence, it can be used as a filter in javascript.
So we can split by 'Image and Texts' and then find the items.
For example, consider an array of results and let us say object with a collection of items is items and item path of each item is denoted by Path(let's say, this can be some other property as well) property
let items = [{
Path: 'sitecore/content/home/tenant1/Subcontent/Image and Texts/neededitem',
anotherProperty: 'text-val1'
}, {
Path: 'sitecore/content/home/tenant1/Subcontent/Image and Texts/item1/neededitem/notneededitem',
anotherProperty: 'text-val2'
}];
const results = items.filter(item => {
const splittedPath = item.Path.split('Image and Texts');
if (splittedPath[1].split("/").length <= 2) {
return item;
}
});
console.log(results);
In case your SSC controller (C#) is custom one and having access to Sitecore Context object or Sitecore APIs then the GetChildren() method of Item class will bring children of first level only.
I hope this helps.

graphql - Combining results from multiple resolvers into one

I have a set of functions at the server side which each return a list of objects of the same type based on the passed parameters to the resolvers in the GraphQL query-
query {
objListQuery {
objResolver1(params) {
obj-id
}
objResolver2(different params) {
obj-id
}
...
}
}
Here, objResolver1 and objResolver2 send back a list of obj objects.
Server side -
function objResolver1(params) -> returns list of obj
function objResolver2(different params) -> returns list of obj
...
I want to perform a logical AND between the results of the resolvers that is, find out the common objects in the results of the different resolvers.
Instead of getting the individual lists, I only want the combined list.
One way is to aggregate the results at the client side but this will increase the amount of duplicated data sent by the server.
What is the best way to achieve this at the server side? What changes are required in the schema?
--------------------EDIT--------------------
The data source is a JSON array of obj objects which is obtained from an external service at the server. Data source is not a database.
Parameters in each resolver can be one or many. It is used for filtering the objects. For example, the data store will have the structure as:
[
{"dateCreated":"2011-08-12T20:17:46.384Z",
"type":"customer",
....
},
{"dateCreated":"2011-08-14T20:17:46.384Z",
"type":"test",
....
}
]
resolvers will be of the form:
dateResolver(String startDate, String endDate) -> returns list of obj whose dateCreated is within the range
typeResolver(String[] type) -> returns list of obj whose type is anyone of the values passed in the array.
Assumed you're using a database you're somehow asking how to shift constraints from database- or repository-layer on controller-level.
While this has some weakness on model-level perhaps, it might depend on the class-implementation if you can easily change the objResolver in the kind that you just build one that allows more parameters like this:
query {
objListQuery {
objResolver(params1, params2, constraint) {
...
}
}
}
Like this you could create a database-query that is directly fetching the right result or you can perform several queries and resolve them inside the objResolver. If the constraint is always AND you could leave the parameter away, but perhaps you like to offer the possibility to use also OR, XOR, or others.
If the amount of parameter-sets is always 2, then it's simple like my code above, also considering the optional constraint. If the amount of parameter-sets might be variable, i.e. 4 or 5, then it's getting complicated if you still want to offer the constraint-parameter(s). Without constraint-parameter(s) it's simple, you just could note the function without parameters but check for the amount of parameters in the caller and handle them accordingly, in the caller you just use so many parameters as required.
query {
objListQuery {
objResolver() {
paramArray = getArguments();
}
}
}
Like written above it's getting hard, if you still want to offer constraint-parameters here, but I'd suggest that would be material for another question.
You can implement a Connection interface, with a single resolver to allow a one-step querying mechanism. You can reduce query endpoints using this technique.
E.g, an example query would look like:
allObjects(start: "01-01-2019", end: "04-29-2019", types:["test", "sales"]){
nodes {
id,
dateCreated,
type
}
}
In the resolver, you can use this criteria to prepare and return the data.
Benefits:
Less query endpoints.
Filtering and pagination.
Your filter interface can be quite fancy:
allObjects(
dateCreated: {
between:{
start,
end
},
skipWeekends: true
},
types: {
include:[],
exclude: []
}
)
Add new criteria as your needs grow. Start with what you want and take it from there.

Meteor: return subset of attributes from Mongo

Im querying Mongo to get the user item, but I only want to pass through a subset of the info to the template. My current solution is this:
var returnUsers = [];
var users = Meteor.users.find().fetch();
for (var i = 0; i < users.length; i++) {
returnUsers.push(users[i].profile);
}
console.log(returnUsers);
return returnUsers;
But I'm losing the iterator. Ideally I want to just return the profile object of each user. How do you do that?
There is little point in doing this on the client. Returning a cursor with fields you don't end up using from minimongo is normally just as fast or faster than filtering fields out in javascript.
Especially for the Users collection you want to filter out the extra fields in your publication from the server. For example:
Meteor.publish('allUsers',function(){
return Meteor.users.find({},{ fields: { profile: 1 }});
});
This will publish the profile data and the _id for each user. Then when you do
Meteor.users.find({});
on the client you will only get the profile data and _id without any need to do extra filtering.
Note that the fields option only allows you to define a set of fields to include or exclude together. You cannot mix include and exclude:
{ fields: { key1: 0, key2: 1 }}
will fail.
There is no security benefit to filtering fields on the client either. The user has full access to the published collection from the console.
Seeing as you want to keep cursor as per comment in previous answer remove the fetch as this turns it into an array not a cursor and add fields like below
return Meteor.users.find({},{fields:{profile:1}});
This won't give you only profile but will also return the id as this is always sent regardless of the fields specified to return.
use `map`
var profiles=Meteor.users.find().map(function(a){return a.profile})

Composite views in couchbase

I'm new to Couchbase and am struggling to get a composite index to do what I want it to. The use-case is this:
I have a set of "Enumerations" being stored as documents
Each has a "last_updated" field which -- as you may have guessed -- stores the last time that the field was updated
I want to be able to show only those enumerations which have been updated since some given date but still sort the list by the name of the enumeration
I've created a Couchbase View like this:
function (doc, meta) {
var time_array;
if (doc.doc_type === "enum") {
if (doc.last_updated) {
time_array = doc.last_updated.split(/[- :]/);
} else {
time_array = [0,0,0,0,0,0];
}
for(var i=0; i<time_array.length; i++) { time_array[i] = parseInt(time_array[i], 10); }
time_array.unshift(meta.id);
emit(time_array, null);
}
}
I have one record that doesn't have the last_updated field set and therefore has it's time fields are all set to zero. I thought as a first test I could filter out that result and I put in the following:
startkey = ["a",2012,0,0,0,0,0]
endkey = ["Z",2014,0,0,0,0,0]
While the list is sorted by the 'id' it isn't filtering anything! Can anyone tell me what I'm doing wrong? Is there a better composite view to achieve these results?
In couchbase when you query view by startkey - endkey you're unable to filter results by 2 or more properties. Couchbase has only one index, so it will filter your results only by first param. So your query will be identical to query with:
startkey = ["a"]
endkey = ["Z"]
Here is a link to complete answer by Filipe Manana why it can't be filtered by those dates.
Here is a quote from it:
For composite keys (arrays), elements are compared from left to right and comparison finishes as soon as a element is different from the corresponding element in the other key (same as what happens when comparing strings à la memcmp() or strcmp()).
So if you want to have a view that filters by date, date array should go first in composite key.