getItems() method for table returns up to 20 rows - sapui5

I have a report table that is populated in three different ways, depending on selection filters. At the end of the called report I use the "updateFinished" event to loop through the table entries, paint them zebra style and paint last summary line pink.
SAPUI5 Application version is 1.44.12
this.oStsTable = this.getView().byId("statusReportTable");
this.oStsTable.attachEventOnce("updateFinished", function(oEv) {
var aItems = oEv.getSource().getItems();
if (aItems && aItems.length > 0) {
for (var i = 0; i < aItems.length; i++) {
if (i === aItems.length - 1) {
aItems[i].addStyleClass("pinkBackground");
} else {
var even = i % 2;
if (even !== 0 && i < aItems.length) {
aItems[i].addStyleClass("cyanBackground");
}
}
}
}
});
this.byId("statusReportTable").getBinding("items").filter(oTableSearchState);
With the data available, I get back 3 datasets. 7 for case 1, 7 for case 2 and 23 for case 3. With the first two cases everything is OK. Problem is with case 3 (23 records). Debugging on the back-end, I see 23. When table appears, I see again 23. But the getItems() method within "updateFinished" event sees only 20 (!). As result of it (yes, guessed right), line 20 goes pink and the remaining lines without color as loop exits.
Rings any bells?

If you are only trying to add a zebra style with a different color in the last row, I recommend doing that only with CSS in your app like below.
table tr:nth-child(even) {
background-color: green;
}
table tr:nth-child(odd) {
background-color: red;
}
table tr:last-of-type {
background-color: yellow;
}

If you only need to alternate the row colors on your sap.m.Table then I would recommend you to set the property alternateRowColors to true.
<Table id="idProductsTable"
alternateRowColors="true"
items="{ path: '/ProductCollection' }">
<headerToolbar>
<Toolbar>
<Title text="Products"/>
</Toolbar>
</headerToolbar>
<columns>
<Column>
<Text text="Product" />
</Column>
<Column>
<Text text="Supplier" />
</Column>
</columns>
<items>
<ColumnListItem>
<cells>
<ObjectIdentifier title="{Name}" text="{ProductId}"/>
<Text text="{SupplierName}" />
</cells>
</ColumnListItem>
</items>
</Table>
Note: This property can only be used with the Belize and Belize Deep themes. Alternate row coloring is not available for the High Contrast Black/White themes.
Find the properties for sap.m.Table here.

Related

Missing table template on back and forth navigation

I am trying to build an application that has table#1 with clickable items and display data in another view with table#2 depending on which item you clicked in the first table.
This is some weird behaviour and I'll try to explain the problem the best I can:
The application works fine the first time you use it - I click an Item in table#1 -> it navigates to the view with table#2 and displays the dependent data. Everything works perfectly fine.
Now if i navigate back to table#1 and click another item, it loads table#2 but without any data.
The third time (and every time after that) I navigate back and click an item in table #1 and table#2 is supposed to load I get the error message :
"Error: Missing template or factory function for aggregation items of Element sap.m.Table"
I have tried reloading the item aggregation, destroying the template on navigation, reloading model data and setting the templateSharable flag to true / false but nothing helped in any way.
I'm posting the relevant code for the view with table #2 below
My XML Code:
<f:SimpleForm id="simpleform" editable="true" layout="ResponsiveGridLayout">
<f:content>
<semantic:SemanticPage id="page" headerPinnable="false" toggleHeaderOnTitleClick="false" >
<semantic:content id="posContent">
<Table
id="rkDetailTable"
noDataText="{i18n>tableNoDataText}"
>
<columns>
<Column>
<Text text="Pos ID"/>
</Column>
<Column>
<Text text="{i18n>Art_RkDetail}" />
</Column>
<Column>
<Text text="{i18n>Betrag_RkDetail}"/>
</Column>
</columns>
<items>
<ColumnListItem id="rkDetailTableTemplate" type="Navigation" press="onDetail">
<cells>
<ObjectIdentifier title="{RkposId}" />
<Text text="{RksatzId}" />
<ObjectNumber number="{RkposBetrag}" unit="EUR" />
</cells>
</ColumnListItem>
</items>
</Table>
</semantic:content>
</semantic:SemanticPage>
</f:content>
</f:SimpleForm>
The corresponding controller:
onInit: function () {
this.getRouter().getRoute("RkItemDetail").attachMatched(this.onRouteMatched, this);
},
onRouteMatched: function (oEvent) {
var oArguments = oEvent.getParameter("arguments");
var RkId = oArguments.RkId;
var sBindingPathAbrechnung = "/AbrechnungSet(" + RkId + (")");
this.getView().byId("simpleform").bindElement(sBindingPathAbrechnung);
var sBindingPathPosition = "/AbrechnungSet(" + RkId + (")/ToPos");
this.byId("rkDetailTable").bindItems({
path: sBindingPathPosition,
template: this.byId("rkDetailTableTemplate"),
templateShareable: true
});
}
Disregard the element binding, it is used for something else in that view.
Thank you!
In you table change the <items> aggregation to <dependents>.
<Table
id="rkDetailTable"
noDataText="{i18n>tableNoDataText}"
>
<columns>
...
</columns>
<dependents>
<ColumnListItem id="rkDetailTableTemplate" type="Navigation" press="onDetail">
...
</ColumnListItem>
</dependents>
</Table>

Select Hidden List Items with Growing=True

I've a sap.m.Table with growing(true) and growingThreshold set to 5 and MultiSelect mode. Say, at any moment, the number of records in my JSON model is more than 5 (say 20). Hence, it will always do lazy loading.
I've a button which needs to select all the elements in my model i.e, 20 items. So, on click handler of a button i say : - table.selectAll().
Now, I since only 5 items are shown in UI ( and in DOM), the UI5 library only selects which ever elements are shown.
console.log('How many items selected ?', oTable.getSelectedContexts(true).length ) // says 5. But it needs to be 20.
Question is - How do I select all items without loading them in UI ? Showing all elements to just select them does not make sense to me.
Now,
User will not click load more so many times to load all elements. There could be 100s of elements in real scenario. How to select all n elements in model ?
I do want to keep additional property in my model just to mark a item is selected or not ? Any suggestions are welcome.
User can select-all initially and then de-select few items which he doesnt mean. So, 20 items selected and say, removed first 2, so total no of selections should be 18. Any suggestions are welcome.
Code:
<ToggleButton text='Select All' press='onSelectAll'/>
<Button text='Submit' press='onSubmit' />
<Table noDataText="No Data" id="table0" items="{/}" mode='MultiSelect' growing='true' growingThreshold='5'>
<items>
<ColumnListItem type="Active">
<cells>
<Text text="{id}"/>
<Text text="{name}"/>
</cells>
</ColumnListItem>
</items>
<columns>
<Column id="column0">
<header>
<Label text="ID" id="label0"/>
</header>
</Column>
<Column id="column1">
<header>
<Label text="NAME" id="label1"/>
</header>
</Column>
</columns>
</Table>
Controller :
onInit: function () {
var aData = [];
for(var i =0;i< 20; i++) {
var element = {
id : 'I00' + i,
name: 'Clone-' + i
};
aData.push(element);
}
var oModel = new sap.ui.model.json.JSONModel(aData);
this.getView().setModel(oModel);
},
onSelectAll: function (oEvent) {
var oSource = oEvent.getSource();
var oTable = this.byId('table0');
if (oSource.getPressed()) {
oTable.selectAll();
} else {
oTable.removeSelections(true);
}
},
onSubmit: function() {
var oTable = this.byId('table0');
console.log('How many items selected ?', oTable.getSelectedContexts(true).length );
}

How to Remove Row from Table

This question is a follow up to this: Button to add new row in SAPUI5 table
In my new scenario, I have added a "remove" button in the first column of the table. Again, the JSON file looks like this:
{
"Invoices": [
{
"ProductName": "Pineapple",
"Quantity": 21,
"ExtendedPrice": 87.2000,
"ShipperName": "Fun Inc.",
"ShippedDate": "2015-04-01T00:00:00",
"Status": "A"
},
...
]
}
My view now looks like this:
<mvc:View controllerName="stepbystep.demo.wt.controller.App" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:core="sap.ui.core"
xmlns:html="http://www.w3.org/1999/xhtml" displayBlock="true">
<Table id="ins" items="{ path : 'invoice>/Invoices', sorter : { path : 'ProductName' } }">
<headerToolbar>
<Toolbar>
<Button icon="sap-icon://add" text="Row" press="addRow"/>
<Button icon="sap-icon://display" text="Row" press="fetchRecords"/>
</Toolbar>
</headerToolbar>
<columns>
<Column width="50px"/>
<Column hAlign="Right" minScreenWidth="Small" demandPopin="true" width="4em">
<Text text="{i18n>columnQuantity}"/>
</Column>
<Column>
<Text text="{i18n>columnName}"/>
</Column>
<Column minScreenWidth="Small" demandPopin="true">
<Text text="{i18n>columnStatus}"/>
</Column>
<Column minScreenWidth="Tablet" demandPopin="false">
<Text text="{i18n>columnSupplier}"/>
</Column>
<Column hAlign="Right">
<Text text="{i18n>columnPrice}"/>
</Column>
</columns>
<items>
<ColumnListItem type="Navigation" press="onPress">
<cells>
<Button icon="sap-icon://delete" press="deleteRow" type="Reject"/>
<ObjectNumber number="{invoice>Quantity}" emphasized="false"/>
<ObjectIdentifier title="{invoice>ProductName}"/>
<Text text="{ path: 'invoice>Status', formatter: '.formatter.statusText' }"/>
<Text text="{invoice>ShipperName}"/>
<ObjectNumber
number="{ parts: [{path: 'invoice>ExtendedPrice'}, {path: 'view>/currency'}], type: 'sap.ui.model.type.Currency', formatOptions: { showMeasure: false } }"
unit="{view>/currency}" state="{= ${invoice>ExtendedPrice} > 50 ? 'Error' : 'Success' }"/>
</cells>
</ColumnListItem>
</items>
</Table>
And I have a function for adding a row (this one works fine):
addRow: function() {
this.getView().getModel("invoice").create("/Invoices", {
ProductName: "",
Quantity: "",
ShippedDate: "",
Status: ""
});
}
And I am trying to build one for deleting. This is what I have so far:
deleteRow: function(oArg) {
// var array = oArg.oSource.sId.split("-");
// var index = array[array.length - 1];
var path = oArg.oSource.oPropagatedProperties.oBindingContexts.invoice.sPath;
delete this.getView().getModel("invoice").oData[path.substr(1)];
this.getView().getModel("invoice").refresh(true);
}
But the row gets emptied and then returns again (like getting fetched from the mock server again). I am trying to completely remove the row (not just its contents), and the data to be removed.
I have seen plenty of examples online, but none cover my use case.
If you already use create for create new row then I think the best is to be consistent and use remove in order to remove it.
so in your case I think your code should look something like this:
this.getView().getModel("invoice").remove(path);
This line will do both:
Execute DELETE request to delete the object on the server (if you are using oDataModel)
Will delete the row from the table and refresh it because the table is bounded to this model
If you can please always use binding in your code. Using binding is much more efficient and easy to maintain because you don't need to deal with any DOM objects. The only thing that you need to do in code is creating/deleting/updating your model objects and UI5 runtime will do the rest for you.

How to implement multiple level merge in sap.m.Table?

DEMO
I want to implement multiple level merge in sap.m.Table. I tried to add mergeDuplicates="true" in the second column, but it look like this:
Pretty strange. I added data of the first column to the second column: <Text text="{name} {amount} "/>, the split looks like want I want, but how to hide {name} data?
Did some research in doc of sap.m.Column, find mergeFunctionName
You can pass one string parameter to given function after "#" sign. e.g. "data#myparameter":
Can I pass both {name} and {amount} to myparameter function?
In order to create a property where we can add a property to hold such primary key that can automatically be accessed by a single getter function, we extend the Text control as shown below.
sap.ui.define(
['sap/m/Text'],
function(Text) {
return Text.extend("namespace.controls.MergableText", {
metadata: {
properties: {
mergeDependent: {
type: "string",
defaultValue: ""
}
}
},
renderer: function(oRm, oControl) {
sap.m.TextRenderer.render(oRm, oControl); //use supercass renderer routine
}
});
}
);
The above is a file called MergableText.js which can be stored in the controls folder in the webapp. Now this can be used by defining in namespace:
<mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:custom="namespace.controls"
controllerName="namespace.controller.PlannedOT">
<Table>
<columns>....</columns>
<items>
<ColumnListItem>
....
<custom:MergableText text="{>amount}" mergeDependent="{name}"> </custom:MergableText>
.....
<ColumnListItem>
</items>
</Table>
</mvc:View>
In this manner the amount can be merged only based on its dependency on the name.
the root cause is that you should define your sorter properly for your binding.
items="{
path: '/',
sorter: [{
path: 'name',
descending: false,
group:true
}, {
path: 'amount',
descending: false,
group:true
}]
}"
Using mergeNameFunction is the good approach :) You can use any binding internal method that will force the comparison.
E.g.
<Column mergeDuplicates="true" mergeFunctionName="getBindingContext">
<Text text="column2"/>
</Column>
will work with no other modifications
Late reply but hopefully still useful. I developed my own solution based on this.
Good news: What you want to achieve is possible without extending controls or grouping the table.
Add sap.ui.core library:
xmlns:core="sap.ui.core"
Add custom data to your cells like this:
<customData>
<core:CustomData key="mergeKey" value="{name}" />
</customData>
Use this custom data in your merge function like this:
mergeFunctionName="data#mergeKey"
The magic happens when you use the binding from column1 and column2 combined as a value for the custom data in column 2. That stops the whole column2 from becoming one cell and you get the desired divider between bus and truck.
Final solution:
<mvc:View
controllerName="demo.FirstPage"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:semantic="sap.f.semantic"
xmlns:core="sap.ui.core">
<Table
id="table"
mode="MultiSelect"
width="auto"
items="{
path: '/'
}"
growing="true"
growingScrollToLoad="true">
<columns>
<Column
mergeDuplicates="true"
mergeFunctionName="data#mergeKey">
<Text text="column1"/>
</Column>
<Column
mergeDuplicates="true"
mergeFunctionName="data#mergeKey">
<Text text="column2"/>
</Column>
<Column>
<Text text="column3"/>
</Column>
</columns>
<items>
<ColumnListItem id="cli" type="Detail">
<cells>
<Text text="{name}">
<customData>
<core:CustomData key="mergeKey" value="{name}" />
</customData>
</Text>
<Text text="{amount}">
<customData>
<core:CustomData key="mergeKey" value="{name}{amount}" />
</customData>
</Text>
<Text text="{currency}, {size}"/>
</cells>
</ColumnListItem>
</items>
</Table>
</mvc:View>
Screenshot of final result

How can I get row value for each sap.m.select element

<m:Table id="tableId"
inset="false"
mode="MultiSelect"
width = "100%"
fixedLayout="false"
border-collapse="collapse"
items="{
path: 'jsonViewModel>/results',
sorter: {
path: 'ProductId'
}
}">
<columns>
<Column
minScreenWidth="Desktop"
demandPopin="true">
<Text text="Product No" />
</Column>
<Column
minScreenWidth="Desktop"
demandPopin="true"
hAlign="Left">
<Text text="Model" />
</Column>...
</columns>
<items>
<ColumnListItem>
<cells>
<ObjectIdentifier
title="{jsonViewModel>ProductId}"/>
<Select id="selectId"
items="{
path: '/ModelList',
sorter: { path: 'Name' }
}">
<core:Item key="{modelId}" text="{Name}" />
</Select>...
</cells>
</ColumnListItem>
</items>
</Table>
First I have a jsonViewModel which is holding Products JSON array and also there is a ModelList service which gives me the list of models. So I should be able to fill some inputs(I didn't show other inputs cause I can retrive their values) and select model of products. But if I have 5 products I also have 5 select elements and I can't retrieve the select item for each rows(for each products). For example I can't retrieve the value with these codes in controller:
var oSelect = this.getView().byId("selectId");
var selectedItemObject = oSelect.getSelectedItem().getBindingContext().getObject();
var selectedModelName = selectedItemObject.Name;
Cause I have 5 select elements indeed and with these codes I can't retrieve every selected item object. Any help would be appreciated.
Cant we go through every row and then fetch the select control and then fetch the selectedItem? I mean,
var aItems = this.getView().byId("tableId").getItems();
for(var i =0;i<aItems.length;i++){
var aCells = aItems[i].getCells();
// I know select is at 0th cell, so I can use aCells[0].
var rowSelectedKey = aCells[0].getSelectedItem().getKey();
// once you have the selcetedKey, you can save them in a local array or // model and after the loop, you can work with them
}