Get path from property in m.table - sapui5

I'm looking for a convenient method to get the path from a table cell.
Background: It is required to implement a search field allowing to filter on all columns of responsive table. Here, the path is needed as parameter for the filter object.
XML Code
<Table items="{path: 'modelName>pathPart1/pathPart2'}">
<headerToolbar>
<Toolbar>
<Title text="titleText"/>
<SearchField search="searchInTable"/>
</Toolbar>
</headerToolbar>
<columns>
<Column>
<Text text="column1"/>
</Column>
<Column>
<Text text="column2"/>
</Column>
</columns>
<ColumnListItem>
<Text text="{modelName>cellName1}"/>
<Text text="{modelName>cellName2}"/>
</ColumnListItem>
</Table>
Controller Logic
searchInTable: function(event) {
var table = event.getSource().getParent().getParent();
var query = event.getParameters("query");
table.getBinding("items").filter(this.getFilters(table, query));
},
getFilters: function(table, query) {
var aFilters = [];
var items = table.getItems();
// Loop through items aggregation and populate filter object
jQuery.each(items, function(i, oItem) {
// Get path from cells (e.g. cellName1)
var sPath = oItem.mAggregations.cells[i].mBindingInfos.text.binding.sPath;
var sOperator = FilterOperator.EQ;
var sValue1 = query;
var oFilter = new Filter(sPath, sOperator, sValue1);
aFilters.push(oFilter);
});
return aFilters;
},
Can we replace this part by a more convenient and robust method?
var sPath = oItem.mAggregations.cells[i].mBindingInfos.text.binding.sPath;
As you notice, I'm trying to receive the sPath going through the whole object. However, its not working in all cases as the structure of the object may change. I bet there is an better approach available. However, I struggling a bit here.
Any ideas?
Edit: I do like to get the path pointing to the text property in the table. In this samplle it would be: cellName2

I'm on the phone right now, so I can't test it, but it is something like this
oItem.getCells()[i].getBindingContext().getPath()
getCells() comes from the ColumnListItem API if I am not wrong.
The other two from the ODataListBinding API or something like that...
If you dive a bit in the API you will find it
EDIT: I think you should provide the model name when getting the context. But I don't remember well...
oItem.getCells()[i].getBindingContext("modelName").getPath()
Try both, with and without it...
EDIT2: Here you have the snippet http://jsbin.com/votaxiyedi/edit?html,output
And this what you need:
oItem.getBindingContext("odata").getPath() + "/" + oItem.getCells()[0].getBinding("text").getPath();

Related

SAPUI5 How to display only the first element of an expanded entitySet in table

I am currently using an entityset(i.e, "SolutioningVersions") in my smarttable which i have expanded in controler using
var mBindingParams = oEvent.getParameter("bindingParams");
mBindingParams.parameters["expand"] = "TEAMID/TEAMDETAILS,SOLREQ,SOLESTIMATE";
I have bound it in my table using
<VBox items="{ path: 'SOLESTIMATE', templateShareable:false }">
<Text text="{WBSVARIANT}"/>
</VBox>
But "SOLESTIMATE" is having array of objects & i want to show only "WBSVARIANT" from it 1st object in array.
Currently it shows me like this in single cell of table
enter image description here
I want to show only 1st element of this array. Also, i dont want to filter array here as objects are not unique.
A formatter can help you:
<Text text="{path: 'WBSVARIANT', formatter: '.formatText'}"/>
function formatText(items){
return items[0];
}

How to properly use JSONModel and setModel?

I'm trying to create an example screen using SAP Web IDE where clicking different buttons changes different texts around the screen.
I have a few functions at the App.controller.js and the code is this (All the functions do the same for now but affect different text areas):
onPressButton2: function () {
var oData = {
text: {
line1: "line1",
line2: "line2",
line3: "line3",
line4: "line4"
}
};
var oModel = new JSONModel(oData);
this.getView().setModel(oModel);
},
And this is corresponding part at the XML:
<items>
<Text xmlns="sap.m" text="{/text/line1}" id="text1"/>
<Text xmlns="sap.m" text="{/text/line2}" id="text2"/>
<Text xmlns="sap.m" text="{/text/line3}" id="text3"/>
<Text xmlns="sap.m" text="{/text/line4}" id="text4"/>
</items>
This works, but when I try and change different areas of the screen, the previous changes I made by clicking the buttons disappear. I assume this is because I use setModel anew every time which overwrites it but I cannot find the proper usage.
Should I create a different js file for every section in the screen?
Is there a way to update the model instead of overwriting it all?
Try to declare your JSONModel outside of the onPressButton function. You can declare it in the manifest to be visible for the entire application (controllers and views):
"sap.ui5": {
"_version": "1.1.0",
...
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"uri": "i18n/i18n.properties"
},
"MyModel" : {
"type" : "sap.ui.model.json.JSONModel"
}
Once the model is available you can set the data to it outside of the onPressButton2 function:
this.getOwnerComponent().getModel("MyModel").setData(oData)
Now, in the onPressButton2 function, you can just update the model's data using the setProperty method:
this.getOwnerComponent().getModel("MyModel").setProperty("/text/line1", "NewValue");
I think what you are searching are named models. with named models you are able create different models without overwriting them, if you want to additionally add a new model.
var oModel = new JSONModel(oData);
this.getView().setModel(oModel, "model1");
have a look at the second parameter in the setmodel method. now you can access them in the view with
<items>
<Text xmlns="sap.m" text="{model1>/text/line1}" id="text1"/>
<Text xmlns="sap.m" text="{model1>/text/line2}" id="text2"/>
<Text xmlns="sap.m" text="{model1>/text/line3}" id="text3"/>
<Text xmlns="sap.m" text="{model1>/text/line4}" id="text4"/>
</items>
You should create your model during the intilisation phase of the page lifecycle.
So, in your instance, create the model and the intial values in the onInit function of the relevant view/page:
onInit: function () {
var oData = {
text: {
line1: "line1",
line2: "line2",
line3: "line3",
line4: "line4"
}
};
var oModel = new JSONModel(oData);
this.getView().setModel(oModel);
Then, when you need to assign different values to that model for the existing values you would simply set the relevant property in the model as follows:
this.getView().getModel().setProperty("/text/line1", "<new value>");
if you wish to add an additional line you could simply get the existing model values and add the new value:
var mydata = this.getView().getModel().getProperty("/");
mydata.text["line5"] = "line5";
this.getView().setProperty("/", mydata);
Hope that helps.
I trust you are aware of the differences between the un-named model you were using and the concept of a named model.

How to bind a calendar and two time pickers properly to a sap.m.table?

I am learning SAPUI5 at the moment and created a little application for myself to play around and learn by practice. Before I get to my question, I will provide some short information about my app, so you know what I am trying to do and what should be the expected result.
What does the app?
The only thing this application does is providing a calendar and a table for the user. If the user clicks on a date, then the table should be filled with this date and two time pickers to give a start and end time for this selected day. For every date a new row should be created.
The actual problem:
The problem is a design problem I guess. The table gets filled with the dates and the time pickers, but my way of doing this is, is bad practice I guess. I store my selected dates in two models, one for the calendar, one copy to bind it to the table. That works and dates are saved and I can access the data via model. When a new dates get added to the table, the two timepickers get added too because they are provided in the ColumnListItem in the XML view, but no model is bound to them nor do I have access to the values of the timepickers e.g. via ID. And here is the problem, until now I found no proper way of how to them bind to a model or another clean way nor to access the values of the created timepickers.
The question:
How would you implement this in a clean way, so that the time pickers are bound in a right way maybe to a model or models? and you can access their data? I would be thankful if you could give me an advice or hint how I should implement this in a clean way since I want to learn from this and don't want to start hacking around with bad practices just to achieve the goal in shorter time.
The relevant sourcecode:
Controller:
var CalendarController = Controller.extend("sap.ui.unified.sample.CalendarMultipleDaySelection.CalendarMultipleDaySelection", {
oFormatYyyymmdd: null,
oModel: null,
onInit: function(oEvt) {
this.oFormatYyyymmdd = sap.ui.core.format.DateFormat.getInstance({
pattern: "dd.MM.yyyy",
calendarType: sap.ui.core.CalendarType.Gregorian
});
this.oModel = new JSONModel({
selectedDates: []
});
this.oCopyModel = new JSONModel({
selectedDates: []
});
var oCalendar = this.getView().byId("calendar");
oCalendar.setModel(this.oModel);
},
handleCalendarSelect: function(oEvt) {
var oCalendar = oEvt.oSource;
var aSelectedDates = oCalendar.getSelectedDates();
console.log(aSelectedDates);
var oDate;
var oData = {
selectedDates: []
};
var oTable = this.getView().byId("dateTable");
if (aSelectedDates.length > 0) {
for (var i = 0; i < aSelectedDates.length; i++) {
oDate = aSelectedDates[i].getStartDate();
oData.selectedDates.push({
Date: this.oFormatYyyymmdd.format(oDate)
});
}
this.oModel.setData(oData);
if (this.oCopyModel.getProperty("/selectedDates/length") >= 0) {
this.oCopyModel.setData(oData);
oTable.setModel(this.oCopyModel);
}
} else {
this._clearModel();
}
},
return CalendarController;
View:
<content>
<unified:Calendar id="calendar" select="handleCalendarSelect" intervalSelection="false" singleSelection="false"/>
<Table id="dateTable" items="{path: '/selectedDates', sorter: {path: 'Date', comparator: '.dateComperator'}}"mode="None" fixedLayout="true">
<columns>
<Column>
<header>
<Text text="Date"/>
</header>
</Column>
<Column>
<header>
<Text text="Beginning"/>
</header>
</Column>
<Column>
<header>
<Text text="End"/>
</header>
</Column>
</columns>
<ColumnListItem>
<Text text="{Date}"/>
<TimePicker value="10:00" valueFormat="HH:mm" displayFormat="HH:mm" change="handleChange"/>
<TimePicker value="11:00" valueFormat="HH:mm" displayFormat="HH:mm" change="handleChange"/>
</ColumnListItem>
</Table>
Kind regards
Maximilian
I created a small example:
https://next.plnkr.co/edit/OGmJimjF2YZ46mv6DsF2?preview
A few points:
I simply added a few properties (startTime and endTime) to a selected date. You can now modify the time with the timepicker, the changes are stored in the model.
The data binding of the calender seems broken. I also had to use getSelectedDates. This may be due to singleSelection="false". When using single selection you can access the selected date (or interval) via data binding.
Never access internal properties (oEvt.oSource). There are accessors for this (oEvt.getSource()).

SAPUI5 Table cell valuestate disappear

I am adding controls to SAPUI5 table column items via the controller using factory function so that I can apply cross-field validation along with standard control validations. Now the validation is working fine it shows the error message with the red coloured border, but when I move to next control, validation state of control disappear. I noticed that it is happening due to some internal functionality of SAPUI5 where it re-render the table body element of the table in the HTML dom explorer which also get rid of the error classes applied to control. It occurs for the first time, but when I try to change the value again of the same controller with an invalid data, it displays the error and keeps the value state with the red border.
my table XML view
<Table id="todosTable" growing="true" items="{
path: 'TripService>/todos', factory: '.populateItems'
}">
<columns>
<Column id="id">
<Text text="Id"/>
</Column>
<Column id="title">
<Text text="Title"/>
</Column>
<Column id="url">
<Text text="Url"/>
</Column>
<Column id="thumbnailUrl">
<Text text="Thumbnail Url"/>
</Column>
</columns>
</Table>
My Controller code to apply the columns item
function populateItems(sId: any, oContext: any) {
const idInput = new Input({
value: "{TripService>id}",
id: `id_${sId}`,
liveChange: onIdChange.bind(this)
});
const titleInput = new Input({
value: "{TripService>title}",
id: `title_${sId}`,
liveChange: onTitleChange.bind(this)
});
const urlInput = new Input({
value: "{TripService>url}"
});
const tumbnailInput = new Input({
value: "{TripService>thumbnailUrl}"
});
var row = new ColumnListItem(sId, {
cells: [idInput, titleInput, urlInput, tumbnailInput]
});
return row;
}
function onIdChange(oEvent: any) {
oEvent.oSource.setValueState(sap.ui.core.ValueState.Error);
}
function onTitleChange(oEvent: any) {
oEvent.oSource.setValueState(sap.ui.core.ValueState.Error);
}
Some images with valid error state and then with buggy error state
As you can see in above two images the error is gone in second image though I expect it to be there.

highlight a changed property on model load

I have a table that where the data is periodically updated by a javascript interval function in my controller:
var model = this.getview().getModel();
var updateModel = setInterval(function(){
model.loadData('path/to/my/data.json');
}, 30000)
This will basically be static display on a public monitor showing a summary of data.
I want to be able to highlight when a property has changed, so I've been trying to add a class to the control when it changes. The class will then highlight this in some way with CSS.
<Table items="{items}">
<columns>
<Column/>
<Column/>
</columns>
<items>
<ColumnListItem>
<cells>
<Text
text="{name}" />
<ObjectStatus
text="{value}"
state="{
path: 'value',
formatter: '.formatter.redOrGreen'
}"/>
</cells>
</ColumnListItem>
</items>
</Table>
So the model updates every 30 seconds. If the {value} field changes, I want to add a class to ObjectStatus control.
At the moment I'm just using a JSON model for local development to see if this is possible, but in production it will be an oData service.
Thanks for the answers, I managed to solve this, but my method wasn't quite covered by the answers on here. This is how I did it:
The requirements for this changed slightly since I posted the question. I'll need to indicate if something has changed, but also if the value has gone up or down. I'll also need to indicate if something goes above or below a certain value. I also wanted to make a solution that could be easily adapted if there are any other future requirements. This will also need to be easily adapted for oData when the backend service is up and running.
First of all (and key to this) is setting up a duplicate model, so this goes into my component.js file .I'm just duplicating the model here so that the values old and new values are unchanged, to make the formatter functions work on the first page load:
var oModel = new JSONModel('/path/to/data.js');
this.setModel(oModel, 'model');
this.setModel(oModel, 'oldModel');
In the controller for my view, I then take a copy of the old data, which goes into the old model that I've attached to the view, the new model is then updated. I do this in the after rendering hook to optimize the initial page load.
onAfterRendering: function(){
var thisView = this.getView();
var updateModel = function(){
var oldData = thisView.getModel('model').getData();
var oldModel = new JSONModel(oldWharehousesData);
thisView.setModel(ollModel, 'oldModel');
//update model
var newModel = thisView.getModel('model');
model.loadData('/path/to/data.js');
};
window.refershInterval = setInterval(updateModel, 30000);
}
I'm then able to input the new and old values to a formatter in my XML view and output a couple of custom data attribute:
<core:CustomData
key="alert-status"
value="{
parts: [
'model>Path/To/My/Property',
'oldModel>Path/To/My/Property'
],
formatter: '.formatter.alertStatus'
}"
writeToDom="true"/>
</customData>
My formatter.js :
alertStatus: function(newValue, oldValue){
var alertNum = 25;
if(newValue < alertNum && oldValue >= alertNum) {
return 'red';
} else if (newValue >= alertNum && oldValue < alertNum) {
return 'green';
} else {
return 'none';
}
}
I can then have as many custom data attributes as I like, run them through their own formatter function, which can be styled to my heart's content, e.g:
compareValues: function(newValue, oldValue) {
if (newValue > oldValue) {
return 'higher';
} else if (newValue < oldValue){
return 'lower';
} else {
return 'false';
}
}
I have build an example on JSBin.
First you have to get the received data. You can use the
Model.attachRequestCompleted event for that:
this.model = new sap.ui.model.json.JSONModel();
this.model.attachRequestCompleted(this.onDataLoaded, this);
In the event handler onDataLoaded you can retrieve the JavaScript object and compare it to a saved copy. You have to write the flags that indicate changes to the array item itself. (Storing it in a separate model as Marc suggested in his comment would not work because in your aggregation binding you only have the one context to your array item.)
At last you have to save the newData object as this.oldData for the next request.
onDataLoaded:function(){
var newData = this.model.getProperty("/");
if (this.oldData){
//diff. You should customize this to your needs.
for(var i = 0, length = Math.min(newData.items.length, this.oldData.items.length); i< length; i++){
newData.items[i].valueChanged = newData.items[i].value !== this.oldData.items[i].value;
newData.items[i].nameChanged = newData.items[i].name !== this.oldData.items[i].name;
}
}
this.oldData = newData;
this.getView().getModel().setProperty("/",newData);
},
You can then bind the ObjectState state property to the flag(s):
<ObjectStatus
text="{value}"
state="{= ${valueChanged} ? 'Warning':'None' }"/>
If you want to change the background color of the whole row or something like that you can apply Bernard's answer and use the flag(s) in a customData attribute.
You can use the <customData> tag
This allows the insertion of a custom attribute into the HTML produced by the XML to HTML conversion process
In the example below for example I add a custom attribute (my own) - this code generates the following attribute data-colour in a relevant HTML element (a <SPAN> tag) - inspect the relevant element using, say, Chrome.
<customData>
<core:CustomData writeToDom="true" key="colour" value="{vproducts>ListCostColour}" />
</customData>
You are then able to create a style for this attribute in your own style sheet as follows (and reference this in your manifest.json)
[data-colour="red"] {
background-color: #ffd1cc;
}
[data-colour="orange"] {
background-color: rgba(255, 243, 184, 0.64);
}
[data-colour="green"] {`enter code here`
background-color: rgba(204, 255, 198, 0.97);
}