Missing table template on back and forth navigation - sapui5

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>

Related

Add button in sap.m.list row

I want to add buttons to each row in my sap.m.List. On that button I want to open a popup to display further details without navigating to another page.
Any code snippet or examples out there how I can add buttons to each row and bind them to fetch data from another model.
Instead of the StandardListItem you need a CustomListItem. There you can add any control you like:
<List headerText="Custom Content" items="{path: '/ProductSet'}" >
<CustomListItem>
<HBox>
<Label text="{ProductName}"/>
<Button text="More Infos" click="onPressMoreInfos" />
</HBox>
</CustomListItem>
</List>
I think the tricky part here is the binding. One CustomListItem is bound to a single entity of your set. If you add a Button to your CustomListItem (or any other control) they are also automatically bound to the specific entity.
So in your click handler you can do the following:
onPressMoreInfos: function(oEvent) {
var oButton = oEvent.getSource();
// if your model has a name, don't forget to pass it as a parameter
var oContext = oButton.getBindingContext();
// create the popover, either here or in a new method
var oPopover = this.getTheInfoPopover();
// if your model has a name, don't forget to pass it as the second parameter
oPopover.setBindingContext(oContext);
}
Then your Popover has the same binding information as the list item and you can access every property of the specific entity.
Try below code to add a button to each row, in your XML view:
<columns>
<Column id="userNameColumn">
<Text text="userNameLabelText" />
</Column>
<Column id="buttonColumn">
<Text text="Button" />
</Column>
</columns>
<items>
<ColumnListItem>
<cells>
<Input value="{UserName}"/>
</cells>
<Button id="buttonId" icon="sap-icon://add" press="handleResponsivePopoverPress"></Button>
</ColumnListItem>
</items>
Controller to handle button press, see example here
https://sapui5.hana.ondemand.com/#/sample/sap.m.sample.ResponsivePopover/preview
You can use sap.m.CustomListItem as a template for items aggregation. There is a sample here. You can add any control to the item.

How can I bind a single property of my OData service to a Form?

Will you please tell me how to refer to the first tabular (and single) record?
There is a table with only one record:
<Table items="{WaybillsPlaces}" mode="SingleSelectMaster">
<columns>
<Column hAlign="Center">
<header>
<Text text="Number" />
</header>
</Column>
</columns>
<items>
<ColumnListItem>
<cells>
<Text text="{CoNumber}" />
</cells>
</ColumnListItem>
</items>
</Table>
How can I read the field of the first record without a table?
<Text text="{WaybillsPlaces[0]/>CoNumber}"/>
I get a table reply, but I do not want to display it in the table - I want to display it in the form in text boxes, so there will always be one line in the response.
<entry>
<id>
http://xxxxx.xxx.local:8000/sap/opu/odata/sap/LOGISTICS_SRV/WaybillsPlaces('4610103052')
</id>
<title type="text">WaybillsPlaces('4610103052')</title>
<updated></updated>
<content type="application/xml">
<m:properties xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">
<d:CoNumber>46101030520001</d:CoNumber>
</m:properties>
</content>
</entry>
EDIT (As #Denis description in the comments):
I have an OData giving me the following entry
<entry>
<id>
http://xxxxx.xxx.local:8000/sap/opu/odata/sap/LOGISTICS_SRV/WaybillsPlaces('4610103052')
</id>
<title type="text">WaybillsPlaces('4610103052')</title>
<updated></updated>
<content type="application/xml">
<m:properties xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">
<d:CoNumber>46101030520001</d:CoNumber>
</m:properties>
</content>
</entry>
How can I bind the CoNumber property with a Input within a From ??
give and ID to your table, get the table in your controller and call its 'getItems()' method. Then get the first object in the returned array:
In your view
<Table id="myTable" items="{WaybillsPlaces}" mode="SingleSelectMaster">
<columns>
<Column hAlign="Center">
<header>
<Text text="Number" />
</header>
</Column>
</columns>
<items>
<ColumnListItem>
<cells>
<Text text="{CoNumber}" />
</cells>
</ColumnListItem>
</items>
</Table>
In your controller
onCertainEvent(evt){
var oTable = this.getView().byId('myTable');
var oFirstItem = oTable.getItems()[0]; // This is your ColumnListItem object in the first row
var oFirstCellItem = oFirstItem.getCells()[0]; // This is your Text object in the first cell of the first row
var sCellText = oFirstCellItem.getText(); // This is the text string in your first cell of the first row
}
EDIT: bind only the first item in your model
In this case you can do aggregation binding. For aggregation binding you need multiplicity greater than 1. So do property binding in yout ColumnListItem with WaybillsPlaces/0/CoNumber
<Table id="myTable" mode="SingleSelectMaster">
<columns>
<Column hAlign="Center">
<header>
<Text text="Number" />
</header>
</Column>
</columns>
<items>
<ColumnListItem>
<cells>
<Text text="{WaybillsPlaces/0/CoNumber}" />
</cells>
</ColumnListItem>
</items>
</Table>
EDIT: Data Binding options in certain events
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta charset="utf-8">
<title>MVC with XmlView</title>
<!-- Load UI5, select "blue crystal" theme and the "sap.m" control library -->
<script id='sap-ui-bootstrap'
src='https://sapui5.hana.ondemand.com/resources/sap-ui-core.js'
data-sap-ui-theme='sap_bluecrystal'
data-sap-ui-libs='sap.m'
data-sap-ui-xx-bindingSyntax='complex'></script>
<!-- DEFINE RE-USE COMPONENTS - NORMALLY DONE IN SEPARATE FILES -->
<!-- define a new (simple) View type as an XmlView
- using data binding for the Button text
- binding a controller method to the Button's "press" event
- also mixing in some plain HTML
note: typically this would be a standalone file -->
<script id="view1" type="sapui5/xmlview">
<mvc:View
controllerName="my.own.controller"
xmlns:l="sap.ui.layout"
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc"
xmlns:f="sap.ui.layout.form"
xmlns="sap.m">
<Panel headerText="Table Panel">
<Table id="myTable" items="{/WaybillsPlaces}" mode="SingleSelectMaster" select="onRowPressed" updateFinished="onUpdateFinished">
<columns>
<Column hAlign="Center">
<header>
<Text text="Number" />
</header>
</Column>
</columns>
<items>
<ColumnListItem>
<cells>
<Text text="{CoNumber}" />
</cells>
</ColumnListItem>
</items>
</Table>
</Panel>
<Panel id="Panel1" headerText="onAfterRendering Data Binding">
<Label text="First Item - Property binding:"></Label>
<Input id="Input1"></Input>
<Label text="First Item - Element binding:"></Label>
<Input value="{CoNumber}"></Input>
</Panel>
<Panel id="Panel2" headerText="onUpdateFinished Data Binding">
<Label text="First Item - Property binding:"></Label>
<Input id="Input2"></Input>
<Label text="First Item - Element binding:"></Label>
<Input value="{CoNumber}"></Input>
</Panel>
<Panel id="Panel3" headerText="onRowPressed Data Binding">
<Label text="Selected Item - Property binding:"></Label>
<Input id="Input3"></Input>
<Label text="Selected Item - Element binding:"></Label>
<Input value="{CoNumber}"></Input>
</Panel>
</mvc:View>
</script>
<script>
// define a new (simple) Controller type
sap.ui.controller("my.own.controller", {
onAfterRendering: function(){
var oTable = this.getView().byId('myTable');
var oFirstItem = oTable.getItems()[0]; // This is your ColumnListItem object in the first row
var oFirstCellItem = oFirstItem.getCells()[0]; // This is your Text object in the first cell of the first row
var sFirstItemDataPath = oFirstCellItem.getBindingContext().getPath();
this.getView().byId("Input1").bindProperty("value", sFirstItemDataPath + "/CoNumber");
this.getView().byId("Panel1").bindElement(sFirstItemDataPath);
},
onUpdateFinished: function(oEvent){
var oTable = oEvent.getSource();
var oFirstItem = oTable.getItems()[0]; // This is your ColumnListItem object in the first row
var oFirstCellItem = oFirstItem.getCells()[0]; // This is your Text object in the first cell of the first row
var sFirstItemDataPath = oFirstCellItem.getBindingContext().getPath();
this.getView().byId("Input2").bindProperty("value", sFirstItemDataPath + "/CoNumber");
this.getView().byId("Panel2").bindElement(sFirstItemDataPath);
},
onRowPressed: function(oEvent){
var oFirstItem = oEvent.getParameter("listItem"); // This is the pressed ColumnListItem object
var oFirstCellItem = oFirstItem.getCells()[0]; // This is your Text object in the first cell of the pressed row
var sFirstItemDataPath = oFirstCellItem.getBindingContext().getPath();
this.getView().byId("Input3").bindProperty("value", sFirstItemDataPath + "/CoNumber");
this.getView().byId("Panel3").bindElement(sFirstItemDataPath);
}
});
// instantiate the View
var myView = sap.ui.xmlview({viewContent:jQuery('#view1').html()}); // accessing the HTML inside the script tag above
// create some dummy JSON data
var data = {
WaybillsPlaces: [{
CoNumber: "Item 1",
},{
CoNumber: "Item 2",
},{
CoNumber: "Item 3",
}]
};
// create a Model and assign it to the View
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData(data);
myView.setModel(oModel);
// put the View onto the screen
myView.placeAt('content');
</script>
</head>
<body id='content' class='sapUiBody'>
</body>
</html>
EDIT2: Bind a form input to a certain property in a OData Model
Here I'm gonna use Northwind OData service, for ease of use, but you just need to use your own model. I used the "Customers" entitySet (a.k.a "Customers" DB table), and the "CustomerName" property in this entitySet.
In your case (as per the given OData in the description, you should use the 'WaybillsPlaces' entity and your 'CoNumber' property. If you want to access one specific entry, provide the Key between parenthesis in the binding URL (in your case: WaybillsPlaces('4610103052')).
I highly recommend you to read more about OData binding in UI5 (here and here). In a productive application, please set your OData model in the manifest.json file, to be sure that the $metadata call is done when the application starts, avoiding this way performance and many other issues.
HERE THE SNIPPET

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
}

Getting the bound data object of pressed table row - getBindingContext() returns undefeind

In SAP UI5, I try to get the data object (in my controller) that is bound to a table row when the user presses it. My view is defined in XML, and my controller is in JS of course.
I have checked How to get content of a row in sap.m.table already but it doesn't work for me, or something is missing.
My view (the relevant part):
<Panel>
<Table id="lineItemList" items="{
path: 'statusJobs>/jobs',
sorter: {
path: 'start',
descending: true
}
}">
<headerToolbar>
<!-- ... -->
</headerToolbar>
<columns>
<Column hAlign="Left" vAlign="Middle">
<Label text="Job" />
</Column>
<Column hAlign="Center" vAlign="Middle">
<Label text="Start" />
</Column>
<Column hAlign="Center" vAlign="Middle">
<Label text="End" />
</Column>
<Column hAlign="Right" vAlign="Middle">
<Label text="Success" />
</Column>
</columns>
<ColumnListItem
type="Navigation"
press=".handleLineItemPress"
>
<Text text="{statusJobs>job}" />
<Text text="{
path: 'statusJobs>start',
formatter:'util.Formatter.Date'}"
/>
<Text text="{
path: 'statusJobs>end',
formatter: 'util.Formatter.Date'}"
/>
<Text text="{statusJobs>status}"/>
</ColumnListItem>
</Table>
The relevant part here is obviously:
<ColumnListItem
type="Navigation"
press=".handleLineItemPress"
>
And in my controller, I have this:
handleLineItemPress: function(evt) {
console.log('evt.getSource: ' + evt.getSource());
console.log('evt.getBindingContext: ' + evt.getSource().getBindingContext());
}
which logs as follows:
evt.getSource: Element sap.m.ColumnListItem#__item11-StatusJobs--lineItemList-0
evt.getBindingContext: undefined
evt.getSource returns the ColumnListItem, so of course from there, I could use the object hierarchy and fetch the text of a cell, like:
evt.getSource().getCells()[0].getText();
But this does not seem to be the right way and especially does not give the the entire object nor its unique ID, which I happen to not display in the table.
I am somehow missing the connection back to my data model, that I had bound earlier in the code, in the <Table> item, as follows:
items="{
path: 'statusJobs>/jobs',
sorter: {
path: 'start',
descending: true
}
}"
I hate to say it, but I have had the same issue, and it took me quite some time to find the cause...
It's all related to the use of named models, in your case statusJobs.
If you want to retrieve the binding context for items bound to a named attribute, for some reason (honestly, I can't think of any) you also have to specify the named model:
evt.getSource().getBindingContext("statusJobs");
will return the correct binding context.
Thus, to retrieve the actual object bound to the pressed line item, you could then use:
var obj = evt.getSource().getBindingContext("statusJobs").getObject();
Since a context can't be bound to more than one model (to my knowledge) I really don't understand why you need to specifically give the named model name as a parameter, but for now I guess we have to live with this behavior