How to combine multiple formatters? - sapui5

I made a generic function that generates some form fields, because I need them at many different places in my app.
It has an optional parameter for setting the visibility of the fields.
But now I need to add additional visibility rules for specific fields.
These additional rules should stay the same for all function calls, so theoretically there's no need to set them through a new parameter.
Unfortunately these specific rules are overwritten if the visibility parameter is set.
My goal is to extend these rules with the visibility parameter.
Here's a minimal example:
The specific visibility rule is that an input's value has to be greater than 0.
On one function call another visibility rule is that the value of FlagSet need to be equal to "yes".
let data = {
"FlagSet": "yes",
"Data": [{
"Size": 1
},
{
"Size": 0
}
]
};
let oModel = new sap.ui.model.json.JSONModel();
oModel.setData(data);
let oPage = new sap.m.VBox();
oPage.setModel(oModel);
let additionalVisibilityRules = [
false,
{
path: "/FlagSet",
formatter: (flagSet) => (flagSet === "yes")
}
];
for (let i = 0, l = data.Data.length; i < l; i++)
oPage.addItem(getFields(i, additionalVisibilityRules[i]));
function getFields(index, oVisibility) {
let oCtrl = new sap.m.Input({
value: `{/Data/${index}/Size}`
}).bindProperty("visible", {
path: `/Data/${index}/Size`,
formatter: (size) => (size > 0)
});
if (oVisibility) {
oCtrl.bindProperty("visible", oVisibility);
}
return oCtrl;
}
oPage.placeAt("content");
<script id='sap-ui-bootstrap' type='text/javascript' src='https://sapui5.hana.ondemand.com/resources/sap-ui-core.js' data-sap-ui-libs="sap.m,sap.ui.commons,sap.ui.table" data-sap-ui-theme="sap_bluecrystal">
</script>
<body class='sapUiBody'>
<div id='content'></div>
</body>
How can I combine it so that the second input field is only visible if both its value is above 0 AND FlagSet is equal to "yes"?

If you need multiple model properties, you could try using Composite Binding
Here is an example from the documentation:
<TextField value="{
parts: [
{path:'birthday/day'},
{path:'birthday/month'},
{path:'birthday/year'}
],
formatter:'my.globalFormatter'}"/>
For more information see:
https://ui5.sap.com/#/topic/e2e6f4127fe4450ab3cf1339c42ee832

Related

How to prevent Google Charts from changing x-axis order?

I'm trying to draw a Google Chart whose x-axis represents the week numbers. As we're crossing a new year, the axis goes 50, 51, 52, 1, 2, 3, ....
I'm properly ordering my data, but Google Charts insists on reordering my x-axis, and I end up with a weird graph:
var chartData = [
["Week","Revenue"],
[40,227],
[41,317],
[42,320],
[43,482],
[44,418],
[45,345],
[46,313],
[47,316],
[48,380],
[49,467],
[50,349],
[51,256],
[52,393],
[1,276],
[2,349],
[3,312]
];
google.load("visualization", "1", {
packages:["corechart"],
callback: function() {
var div = document.getElementById('chart');
var chartDataTable = google.visualization.arrayToDataTable(chartData);
var chart = new google.visualization['LineChart'](div);
chart.draw(chartDataTable);
}});
<div id="chart" style="height: 400px;">test</div>
<script src="//www.google.com/jsapi"></script>
How can I prevent it from reordering my data?
google's object notation allows you to provide a value (v:) and a formatted value (f:)
thus, you can use a value of 1 with a format of '40'
e.g. --> {v: 1, f: '40'}
in a row --> [{v: 1, f: '40'},227]
the following working snippet uses object notation to re-format the values for the x-axis,
and re-use those values for the x-axis labels (hAxis.ticks)
var chartData = [
["Week","Revenue"],
[40,227],
[41,317],
[42,320],
[43,482],
[44,418],
[45,345],
[46,313],
[47,316],
[48,380],
[49,467],
[50,349],
[51,256],
[52,393],
[1,276],
[2,349],
[3,312]
];
var hAxisTicks = [];
chartData.forEach(function (row, index) {
if (index === 0) {
return;
}
row[0] = {
v: index,
f: row[0].toString()
};
hAxisTicks.push(row[0]);
});
google.charts.load('current', {
callback: function () {
var div = document.getElementById('chart');
var chartDataTable = google.visualization.arrayToDataTable(chartData);
var chart = new google.visualization['LineChart'](div);
chart.draw(chartDataTable, {
hAxis: {
ticks: hAxisTicks
}
});
},
packages:['corechart']
});
<div id="chart"></div>
<script src="https://www.gstatic.com/charts/loader.js"></script>
note:
recommend using loader.js to load the the library, instead of jsapi
according to the release notes...
The version of Google Charts that remains available via the jsapi loader is no longer being updated consistently. Please use the new gstatic loader from now on.
this only changes the load statement, see snippet above...
EDIT:
there are more options available for continuous axis
which must be sorted, or in reverse sort order ('number', 'date' values)
but the chart will respect the original sort order for a discrete axis ('string' values)
see following snippet for 'string' values
and discrete vs. continuous for more...
var chartData = [
["Week","Revenue"],
[40,227],
[41,317],
[42,320],
[43,482],
[44,418],
[45,345],
[46,313],
[47,316],
[48,380],
[49,467],
[50,349],
[51,256],
[52,393],
[1,276],
[2,349],
[3,312]
];
chartData.forEach(function (row, index) {
if (index === 0) {
return;
}
row[0] = row[0].toString();
});
google.charts.load('current', {
callback: function () {
var div = document.getElementById('chart');
var chartDataTable = google.visualization.arrayToDataTable(chartData);
var chart = new google.visualization['LineChart'](div);
chart.draw(chartDataTable);
},
packages:['corechart']
});
<div id="chart"></div>
<script src="https://www.gstatic.com/charts/loader.js"></script>

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);
}

Is it possible to hide sap.m.input "description" property value

I'm using the description field to hold an value that I don't want to be displayed, is it possible to set this property to visible:false or set to width to 0?
new sap.m.Input("idAltDistInput"+refDocID+sequenceID, {value:"{AltDistrictDesc}",
description: { path : 'AltDistrictID' }
:
visible : false doesn't seem to work.
Yes you can by adding StyleClass.
sap.m.Input("id",{
//Properties
}).addStyleClass("InputDescripTionHidden");
Add following css
.InputDescripTionHidden>span{
display:none
}
Your comment above suggests that you want to store some hidden value, for later use.
Rather than "hijack" (in the nicest sense of the word) another property, you should consider using Custom Data, which is designed for this sort of thing. Here's an example.
new sap.m.List({
mode: "SingleSelectMaster",
items: {
path: "/records",
template: new sap.m.InputListItem({
label: "District",
content: new sap.m.Input({
value: "{AltDistrictDesc}",
customData: [new sap.ui.core.CustomData({
key: "DistrictID",
value: "{AltDistrictID}"
})]
})
})
},
select: function(oEvent) {
var id = oEvent.getParameter("listItem")
.getContent()[0] // the Input control
.getCustomData()[0] // the only Custom Data
.getValue();
alert("Selected District ID : " + id);
}
})
.setModel(new sap.ui.model.json.JSONModel({
records: [{
AltDistrictID: "D1",
AltDistrictDesc: "District 1"
}, {
AltDistrictID: "D2",
AltDistrictDesc: "District 2"
}]
}))
.placeAt("content");
<script src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js" id="sap-ui-bootstrap" data-sap-ui-libs="sap.m" data-sap-ui-theme="sap_bluecrystal"></script>
<div class="sapUiBody" id="content"></div>
(Note that for simplicity, I'm just grabbing the first content and the first custom data within that content in the select listener. You will want to do this slightly more precisely in real code.)

How to display the value (sum) rather than count of markers in a dc.leaflet.js

I want to use crossfilter's reduceSum function dc.leaflet.js, and display the sum instead of the number of clustered markers.
The first example for dc.leaflet.js uses reduceCount. Additionally it doesn't use the reduced value; it just displays the number of markers in the cluster.
I want to use the sum of data using reduceSum.
Here is my data as tsv:
type geo say
wind 38.45330,28.55529 10
wind 38.45330,28.55529 10
solar 39.45330,28.55529 10
Here is my code:
<script type="text/javascript" src="../js/d3.js"></script>
<script type="text/javascript" src="../js/crossfilter.js"></script>
<script type="text/javascript" src="../js/dc.js"></script>
<script type="text/javascript" src="../js/leaflet.js"></script>
<script type="text/javascript" src="../js/leaflet.markercluster.js"></script>
<script type="text/javascript" src="../js/dc.leaflet.js"></script>
<script type="text/javascript">
/* Markers */
d3.csv("demo1.csv", function(data) {
drawMarkerSelect(data);
});
function drawMarkerSelect(data) {
var xf = crossfilter(data);
var facilities = xf.dimension(function(d) { return d.geo; });
var facilitiesGroup = facilities.group().reduceSum(function(d){return d.say});
dc.leafletMarkerChart("#demo1 .map")
.dimension(facilities)
.group(facilitiesGroup)
.width(1100)
.height(600)
.center([39,36])
.zoom(6)
.cluster(true);
var types = xf.dimension(function(d) { return d.type; });
var typesGroup = types.group().reduceSum(function(d){return d.say});
dc.pieChart("#demo1 .pie")
.dimension(types)
.group(typesGroup)
.width(200)
.height(200)
.renderLabel(true)
.renderTitle(true)
.ordering(function (p) {
return -p.value;
});
dc.renderAll();
}
</script>
I have rewritten the question because it was very unclear. I agree with #Kees that the intention was probably to display the sum in a clustered marker chart, rather than "reduceSum doesn't work".
#Kees also pointed out a Leaflet.markercluster issue which gives basic information about how to display a sum inside a marker cluster.
The question becomes, how to apply these customizations to dc.leaflet.js?
First, I've created a version of the example data with another column rnd:
type geo rnd
wind 43.45330,28.55529 1.97191
wind 43.44930,28.54611 3.9155
wind 43.45740,28.54814 3.9922
...
We can use reduceSum like this:
var facilitiesGroup = facilities.group().reduceSum(d => +d.rnd);
And annotate each marker with its value by overriding .marker(), wrapping the default callback:
const old_marker_function = marker.marker();
marker.marker(function(d, map) {
const m = old_marker_function(d, map);
m.value = d.value;
return m;
});
And we can specify a different rendering of the icon using .clusterOptions():
marker.clusterOptions({
iconCreateFunction: function(cluster) {
var children = cluster.getAllChildMarkers();
var sum = 0;
for (var i = 0; i < children.length; i++) {
sum += children[i].value;
}
sum = sum.toFixed(0);
var c = ' marker-cluster-';
if (sum < 10) {
c += 'small';
} else if (sum < 100) {
c += 'medium';
} else {
c += 'large';
}
return new L.DivIcon({ html: '<div><span>' + sum + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
}
});
The example given in the above issue was missing any styling, so I copied the implementation of _defaultIconCreateFunction from the Leaflet.markercluster sources, and modified it.
Demo fiddle
As expected, the numbers are close to 2.5 times the original numbers (since the new column is a random number from 0 to 5).
Putting numbers on the individual markers
marker.icon() allows you change the icon for individual markers, so you can use DivIcon with similar styling to display the numbers:
marker.icon(function(d, map) {
return new L.DivIcon({
html: '<div><span>' + d.value.toFixed(0) + '</span></div>',
className: 'marker-cluster-indiv marker-cluster',
iconSize: new L.Point(40, 40) });
});
This introduces a new class .marker-cluster-indiv to distinguish it from the others; in the new fiddle I've colored them blue with
.marker-cluster-indiv {
background-color: #9ecae1;
}
.marker-cluster-indiv div {
background-color: #6baed6;
}
The interaction is perhaps less clear since clicking blue dots brings up a popup instead of expanding. Perhaps a different icon should be used.
The reduceSum part should work fine, since that is just a different crossfilter function.
Are you sure that your data is getting read correctly? You state that it is a tsv file, and show code that looks like it is tab-separated, but then you use d3.csv to load it, which would have pretty bad effects considering there is a comma in the middle of the second field.
Please try console.log(data) after your data is loaded, and verify that it is loading correctly.
Also, you do not state what problem you encounter. "It doesn't work" does not help us help you.

sapui5 :- text not shown on ui5 in Dropdown Box

The values are loaded from the data source but on ui no text is shown.
var r0c1 = new sap.ui.commons.DropdownBox("r0c1");
var oItemTemplate1 = new sap.ui.core.ListItem();
property binding is done:
oItemTemplate1.bindProperty("text", "{ZtmDockid}");
bind the items:
r0c1.bindItems("/d/results", oItemTemplate1);
Data is properly coming, but on UI its not showing the text.
there are two ways to bind data to a control.
First way using bindProperty:
var oItemTemplate1 = new sap.ui.core.ListItem();
oItemTemplate1.bindProperty("text", "value");
(notice: usage of { })
or binding the values when creating the control:
var oItemTemplate1 = new sap.ui.core.ListItem({
text: "{value}"
});
(you need to use { } to indicate dynamic values)
Herrlock is correct, but I wanted to draw out the subtlety - explicit binding with the bind* functions requires no curly braces ... these are only needed for embedded, or implicit binding.
Here's your code with the braces removed from your bindProperty's second parameter, as a runnable snippet.
// Code from question
var r0c1 = new sap.ui.commons.DropdownBox("r0c1");
var oItemTemplate1 = new sap.ui.core.ListItem();
oItemTemplate1.bindProperty("text", "ZtmDockid");
r0c1.bindItems("/d/results", oItemTemplate1);
// Extra code
r0c1
.setModel(new sap.ui.model.json.JSONModel({
d : {
results : [
{ ZtmDockid : "1" },
{ ZtmDockid : "2" },
{ ZtmDockid : "3" }
]
}
}))
.placeAt('content');
<script src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.m,sap.ui.commons"
data-sap-ui-theme="sap_bluecrystal"></script>
<div id="content"></div>