I'm trying to set up the update loop of a simple game, built with observables in mind. The top-level components are a model, which takes input commands, and produces updates; and a view, which displays the received updates, and produces input. In isolation, both work fine, the problematic part is putting the two together, since both depend on the other.
With the components being simplified to the following:
var view = function (updates) {
return Rx.Observable.fromArray([1,2,3]);
};
var model = function (inputs) {
return inputs.map(function (i) { return i * 10; });
};
The way I've hooked things together is this:
var inputBuffer = new Rx.Subject();
var updates = model(inputBuffer);
var inputs = view(updates);
updates.subscribe(
function (i) { console.log(i); },
function (e) { console.log("Error: " + e); },
function () { console.log("Completed"); }
);
inputs.subscribe(inputBuffer);
That is, I add a subject as a placeholder for the input stream, and attach the model to that. Then, after the view is constructed, I pass on the actual inputs to the placeholder subject, thus closing the loop.
I can't help but feel this is not the proper way to do things, however. Using a subject for this seems to be overkill. Is there a way to do the same thing with publish() or defer() or something along those lines?
UPDATE: Here's a less abstract example to illustrate what I'm having problems with. Below you see the code for a simple "game", where the player needs to click on a target to hit it. The target can either appear on the left or on the right, and whenever it is hit, it switches to the other side. Seems simple enough, but I still have the feeling I'm missing something...
//-- Helper methods and whatnot
// Variables to easily represent the two states of the target
var left = 'left';
var right = 'right';
// Transition from one side to the other
var flip = function (side) {
if (side === left) {
return right;
} else {
return left;
}
};
// Creates a predicate used for hit testing in the view
var nearby = function (target, radius) {
return function (position) {
var min = target - radius;
var max = target + radius;
return position >= min && position <= max;
};
};
// Same as Observable.prototype.scan, but it also yields the initial value immediately.
var initScan = function (values, init, updater) {
var initValue = Rx.Observable.return(init);
var restValues = values.scan(init, updater);
return initValue.concat(restValues);
};
//-- Part 1: From input to state --
var process = function (inputs) {
// Determine new state based on current state and input
var update = function(current, input) {
// Input value ignored here because there's only one possible state transition
return flip(current);
};
return initScan(inputs, left, update);
};
//-- Part 2: From display to inputs --
var display = function (states) {
// Simulate clicks from the user at various positions (only one dimension, for simplicity)
var clicks = Rx.Observable.interval(800)
.map(function (v) {return (v * 5) % 30; })
.do(function (v) { console.log("Shooting at: " + v)})
.publish();
clicks.connect();
// Display position of target depending on the model
var targetPos = states.map(function (state) {
return state === left ? 5 : 25;
});
// Determine which clicks are hits based on displayed position
return targetPos.flatMapLatest(function (target) {
return clicks
.filter(nearby(target, 10))
.map(function (pos) { return "HIT! (# "+ pos +")"; })
.do(console.log);
});
};
//-- Part 3: Putting the loop together
/**
* Creates the following feedback loop:
* - Commands are passed to the process function to generate updates.
* - Updates are passed to the display function to generates further commands.
* - (this closes the loop)
*/
var feedback = function (process, display) {
var inputBuffer = new Rx.Subject(),
updates = process(inputBuffer),
inputs = display(updates);
inputs.subscribe(inputBuffer);
};
feedback(process, display);
I think I understand what you are trying to achieve here:
How can I get a sequence of input events going in one direction that feed into a model
But have a sequence of output events going in the other direction that feed from the model to the view
I believe the answer here is that you probably want to flip your design. Assuming an MVVM style design, instead of having the Model know about the input sequence, it becomes agnostic. This means that you now have a model that has a InputRecieved/OnInput/ExecuteCommand method that the View will call with the input values. This should now be a lot easier for you to deal with a "Commands in one direction" and "Events in the other direction" pattern. A sort of tip-of-the-hat to CQRS here.
We use that style extensively on Views+Models in WPF/Silverlight/JS for the last 4 years.
Maybe something like this;
var model = function()
{
var self = this;
self.output = //Create observable sequence here
self.filter = function(input) {
//peform some command with input here
};
}
var viewModel = function (model) {
var self = this;
self.filterText = ko.observable('');
self.items = ko.observableArray();
self.filterText.subscribe(function(newFilterText) {
model.filter(newFilterText);
});
model.output.subscribe(item=>items.push(item));
};
update
Thanks for posting a full sample. It looks good. I like your new initScan operator, seems an obvious omission from Rx.
I took your code an restructured it the way I probably would have written it. I hope it help. The main things I did was encapsulted the logic into the model (flip, nearby etc) and have the view take the model as a parameter. Then I did also have to add some members to the model instead of it just being an observable sequence. This did however allow me to remove some extra logic from the view and put it in the model too (Hit logic)
//-- Helper methods and whatnot
// Same as Observable.prototype.scan, but it also yields the initial value immediately.
var initScan = function (values, init, updater) {
var initValue = Rx.Observable.return(init);
var restValues = values.scan(init, updater);
return initValue.concat(restValues);
};
//-- Part 1: From input to state --
var process = function () {
var self = this;
var shots = new Rx.Subject();
// Variables to easily represent the two states of the target
var left = 'left';
var right = 'right';
// Transition from one side to the other
var flip = function (side) {
if (side === left) {
return right;
} else {
return left;
}
};
// Determine new state based on current state and input
var update = function(current, input) {
// Input value ignored here because there's only one possible state transition
return flip(current);
};
// Creates a predicate used for hit testing in the view
var isNearby = function (target, radius) {
return function (position) {
var min = target - radius;
var max = target + radius;
return position >= min && position <= max;
};
};
self.shoot = function(input) {
shots.onNext(input);
};
self.positions = initScan(shots, left, update).map(function (state) {
return state === left ? 5 : 25;
});
self.hits = self.positions.flatMapLatest(function (target) {
return shots.filter(isNearby(target, 10));
});
};
//-- Part 2: From display to inputs --
var display = function (model) {
// Simulate clicks from the user at various positions (only one dimension, for simplicity)
var clicks = Rx.Observable.interval(800)
.map(function (v) {return (v * 5) % 30; })
.do(function (v) { console.log("Shooting at: " + v)})
.publish();
clicks.connect();
model.hits.subscribe(function(pos)=>{console.log("HIT! (# "+ pos +")");});
// Determine which clicks are hits based on displayed position
model.positions(function (target) {
return clicks
.subscribe(pos=>{
console.log("Shooting at " + pos + ")");
model.shoot(pos)
});
});
};
//-- Part 3: Putting the loop together
/**
* Creates the following feedback loop:
* - Commands are passed to the process function to generate updates.
* - Updates are passed to the display function to generates further commands.
* - (this closes the loop)
*/
var feedback = function (process, display) {
var model = process();
var view = display(model);
};
feedback(process, display);
I presume that because you do not "assign" the inputs after the model is created, you are aiming for a non-mutative approach to instantiating your model and view. However, your model and your view seem to depend on one another. To resolve this issue, you can use a third party to facilitate the relationship between the two objects. In this case, you can simply use a function for dependency injection...
var log = console.log.bind(console),
logError = console.log.bind(console, 'Error:'),
logCompleted = console.log.bind(console, 'Completed.'),
model(
function (updates) {
return view(updates);
}
)
.subscribe(
log,
logError,
logCompleted
);
By providing the model a factory to create a view, you give the model the ability to fully instantiate itself by instantiating it's view, but without knowing how the view is instantiated.
As per my comment on the question itself, here's the same sort of code you're writing done with a scheduler in Windows. I would expect a similar interface in RxJS.
var scheduler = new EventLoopScheduler();
var subscription = scheduler.Schedule(
new int[] { 1, 2, 3 },
TimeSpan.FromSeconds(1.0),
(xs, a) => a(
xs
.Do(x => Console.WriteLine(x))
.Select(x => x * 10)
.ToArray(),
TimeSpan.FromSeconds(1.0)));
The output I get, with three new numbers every second, is:
1
2
3
10
20
30
100
200
300
1000
2000
3000
10000
20000
30000
Related
This is my first post to Stack, appreciate the work you guys do, amazing.
I have a sap.m.table sap ui5 and i have 4 records
out of 4, 2 are selected by default, i want to disable the preselected once based on condition.
I have tried below code but its not working, any input please?
View
/results' }" **mode="MultiSelect"**
Controller logic
//--->disable the selected department checkboxes
var tbl = that.getView().byId('idImpactTable');
var header = tbl.$().find('thead');
var selectAllCb = header.find('.sapMCb');
selectAllCb.remove();
tbl.getItems().forEach(function(r) {
var obj = r.getBindingContext("impactModel").getObject();
var oStatus = obj.COMPLETED;
var cb = r.$().find('.sapMCb');
var oCb = sap.ui.getCore().byId(cb.attr('id'));
if (oStatus === "X") {
oCb.setSelected(true);
oCb.setEnabled(false);
} else {
oCb.setEnabled(false);
}
});
Multiselect Mode Table - Make selected check box read only
Last time I tried this I found it easiest to use the updateFinished event on the table, and then use an internal property of the column list item, like so:
onTableUpdateFinished: function (oEvent) {
oEvent.getSource().getItems().forEach(function (item) {
var data = item.getBindingContext().getObject();
item._oMultiSelectControl.setEnabled(!data.IsEnabled); //whatever your check is
});
}
You'll have to find a way to keep them disabled though when using the Select All checkbox at the top of the table. I ended up extending sap.m.Table to accomplish that, there might be easier ways...
My extension is like this
sap.ui.define([
"sap/m/Table"
], function(Control) {
return Control.extend("myapp.controls.MyTable", {
updateSelectAllCheckbox: function(oEvent) {
if (this._selectAllCheckBox && this.getMode() === "MultiSelect") {
var aItems = this.getItems();
var iSelectedItemCount = this.getSelectedItems().length;
var iSelectableItemCount = aItems.filter(function(oItem) {
//standard table does not check if the item is enabled
return oItem.getSelected() || oItem._oMultiSelectControl.getEnabled();
}).length;
// set state of the checkbox by comparing item length and selected item length
this._selectAllCheckBox.setSelected(aItems.length > 0 && iSelectedItemCount === iSelectableItemCount);
}
}
});
});
And just the standard renderer
sap.ui.define([
"sap/m/TableRenderer"
], function(Control) {
return Control.extend("myapp.controls.MyTableRenderer", {
});
});
I suppose I could have extended the ColumnListItem but that was more effort than I wanted to put into the table extension
I have managed to find the solution, please find sample code to achieve.
//--->disable the selected department checkboxes
var tbl = that.getView().byId("idImpactTable");
var header = tbl.$().find("thead");
var selectAllCb = header.find(".sapMCb");
selectAllCb.remove();
var aItems = that.byId("idImpactTable").getItems();
//---> Check individual item property value and select the item
aItems.forEach(function(oItem) {
debugger;
//---> If using OData Model items Binding, get the item object
var mObject = oItem.getBindingContext().getObject();
var sPath = oItem.getBindingContextPath();
var completed = oItem.oBindingContexts.impactModel.getProperty("COMPLETED");
//--->get the id of Multi Checkbox
var cb = oItem.$().find(".sapMCb");
var oCb = sap.ui.getCore().byId(cb.attr("id"));
if (completed === "X") {
oCb.setEditable(false);
oItem.setSelected(true);
oItem.getCells()[4].setEnabled(false);
} else {
oItem.setSelected(false);
}
});
Thank you,
Jacob.Kata
//--->disable the selected department checkboxes
var tbl = that.getView().byId('idImpactTable');
tbl.getItems().forEach(function(r) {
// this makes the trick --->
var oMultiSelCtrl = r.getMultiSelectControl();
oMultiSelCtrl.setDisplayOnly( true );
});
We have a target area (target and targetparsys) with component which is a container (parsys) with some components. The problem is that when this container is situated in target area, parsys of container becomes a targetparsys inside of which there isn't a possibility to edit inside components. Does this issue has the solution?
I've tried to overlay the logic of creating the overlay in overlayManager.js. It has solved a little bit the problem, but there isn't a proper work of reordering components inside of it.
I overlayed the following file - /cq/gui/components/authoring/editors/clientlibs/core/js/overlayManager.js by changing and adding the following code:
self.create = function (editable) {
if (!editable.overlay) {
var parent = ns.editables.getParent(editable);
// going up recursively until we reach the root container
if (parent && !parent.overlay) {
self.create(parent);
}
// we check again because a child overlay might also be created by the parent constructor
if (!editable.overlay) {
editable.overlay = new overlayConstructor(editable, parent ? parent.overlay.dom : container);
}
// get children sorted by depth of their paths
var sortedChildren = getSortedChildren(editable, true);
//and going down recursively until we reach the deepest editable
sortedChildren.forEach(function (child) {
self.create(child);
});
}
};
/**
* Returns the sorted {Array} of child {#link Granite.author.Editable}s by depth of their paths for the given {#link Granite.author.Editable}
*
* #param {Granite.author.Editable} editable - {#link Granite.author.Editable} for which to find child editables to be sorted by depth of their paths
* #param {Boolean} all - All the children or only the direct descendant
*
* WARNING! Not OTB function! See the description on {self.create = function(editable)}
*/
function getSortedChildren(editable, all){
var children = ns.editables.getChildren(editable, all);
//key - editable, value - number of slashes
var childrenMap = new Map();
//going through all children
for (let i = 0; i < children.length; i++){
var path = children[i].path;
var numberOfSlashes = 1;
var isFirstSlashChecked = false;
//searching slashes
for (let j = 0; j < path.length; j++){
var letter = path[j];
var SLASH = '/';
if (letter == SLASH){
if (isFirstSlashChecked){
childrenMap.set(children[i], ++numberOfSlashes);
} else {
childrenMap.set(children[i], numberOfSlashes);
isFirstSlashChecked = true;
}
}
}
//if there are not slashes in editable path
if (!isFirstSlashChecked){
childrenMap.set(children[i], 0);
}
}
//sort map by depth (number of slashes)
var sortedChildrenMapByPaths = new Map([...childrenMap.entries()].sort((a, b) => a[1] - b[1]));
//return sorted editables
return Array.from(sortedChildrenMapByPaths.keys());
}
And also added checking null statement here:
function repositionOverlay(editable) {
var parent;
// if there is no overlay in place, don't bother we ignore it (most likely timing issue)
if (editable.overlay) {
parent = ns.editables.getParent(editable);
//the place which was overlaid
// don't rely on order of editables in the store...
if (parent && parent.overlay != null && !parent.overlay.currentPos) {
repositionOverlay(parent);
}
editable.overlay.position(editable, parent);
}
}
I just starting to learn about Leaflet.js for my upcoming project.
What i am trying to accomplish:
I need to make a list of marker which displayed on the map, and when the list item is being hovered (or mouseover) it will show where the position on the map (for single marker, it should change its color. For Clustered marker, it should display Coverage Line like how it behave when we hover it.. and perhaps change its color too if possible).
The map should not be changed as well as the zoom level, to put it simply, i need to highlight the marker/ Cluster on the map.
What i have accomplished now : I am able to do it on Single Marker.
what i super frustrated about : I failed to find a way to make it happen on Clustered Marker.
I use global var object to store any created marker.
function updateMapMarkerResult(data) {
markers.clearLayers();
for (var i = 0; i < data.length; i++) {
var a = data[i];
var myIcon = L.divIcon({
className: 'prop-div-icon',
html: a.Description
});
var marker = L.marker(new L.LatLng(a.Latitude, a.Longitude), {
icon: myIcon
}, {
title: a.Name
});
marker.bindPopup('<div><div class="row"><h5>Name : ' + a.Name + '</h5></div><div class="row">Lat : ' + a.Latitude + '</div><div class="row">Lng : ' + a.Longitude + '</div>' + '</div>');
marker.on('mouseover', function(e) {
if (this._icon != null) {
this._icon.classList.remove("prop-div-icon");
this._icon.classList.add("prop-div-icon-shadow");
}
});
marker.on('mouseout', function(e) {
if (this._icon != null) {
this._icon.classList.remove("prop-div-icon-shadow");
this._icon.classList.add("prop-div-icon");
}
});
markersRef[a.LocId] = marker; // <-- Store Reference
markers.addLayer(marker);
updateMapListResult(a, i + 1);
}
map.addLayer(markers);
}
But i don't know which object or property to get the Clustered Marker reference.
And i trigger the marker event by my global variable (which only works on single marker).
...
li.addEventListener("mouseover", function(e) {
jQuery(this).addClass("btn-info");
markersRef[this.getAttribute('marker')].fire('mouseover'); // --> Trigger Marker Event "mouseover"
// TODO : Trigger ClusteredMarker Event "mouseover"
});
...
This is my current https://jsfiddle.net/oryza_anggara/2gze75L6/, any lead could be a very big help. Thank you.
Note: the only js lib i'm familiar is JQuery, i have no knowledge for others such as Angular.js
You are probably looking for markers.getVisibleParent(marker) method, to retrieve the containing cluster in case your marker is clustered.
Unfortunately, it is then not enough to fire your event on that cluster. The coverage display functionality is set on the Cluster Group, not on its individual clusters. Therefore you need to fire your event on that group:
function _fireEventOnMarkerOrVisibleParentCluster(marker, eventName) {
var visibleLayer = markers.getVisibleParent(marker);
if (visibleLayer instanceof L.MarkerCluster) {
// In case the marker is hidden in a cluster, have the clusterGroup
// show the regular coverage polygon.
markers.fire(eventName, {
layer: visibleLayer
});
} else {
marker.fire(eventName);
}
}
var marker = markersRef[this.getAttribute('marker')];
_fireEventOnMarkerOrVisibleParentCluster(marker, 'mouseover');
Updated JSFiddle: https://jsfiddle.net/2gze75L6/5/
That being said, I think another interesting UI, instead of showing the regular coverage polygon that you get when "manually" hovering a cluster, would be to spiderfy the cluster and highlight your marker. Not very easy to implement, but the result seems nice to me. Here is a quick try, it would probably need more work to make it bullet proof:
Demo: https://jsfiddle.net/2gze75L6/6/
function _fireEventOnMarkerOrVisibleParentCluster(marker, eventName) {
if (eventName === 'mouseover') {
var visibleLayer = markers.getVisibleParent(marker);
if (visibleLayer instanceof L.MarkerCluster) {
// We want to show a marker that is currently hidden in a cluster.
// Make sure it will get highlighted once revealed.
markers.once('spiderfied', function() {
marker.fire(eventName);
});
// Now spiderfy its containing cluster to reveal it.
// This will automatically unspiderfy other clusters.
visibleLayer.spiderfy();
} else {
// The marker is already visible, unspiderfy other clusters if
// they do not contain the marker.
_unspiderfyPreviousClusterIfNotParentOf(marker);
marker.fire(eventName);
}
} else {
// For mouseout, marker should be unclustered already, unless
// the next mouseover happened before?
marker.fire(eventName);
}
}
function _unspiderfyPreviousClusterIfNotParentOf(marker) {
// Check if there is a currently spiderfied cluster.
// If so and it does not contain the marker, unspiderfy it.
var spiderfiedCluster = markers._spiderfied;
if (
spiderfiedCluster
&& !_clusterContainsMarker(spiderfiedCluster, marker)
) {
spiderfiedCluster.unspiderfy();
}
}
function _clusterContainsMarker(cluster, marker) {
var currentLayer = marker;
while (currentLayer && currentLayer !== cluster) {
currentLayer = currentLayer.__parent;
}
// Say if we found a cluster or nothing.
return !!currentLayer;
}
When I hover on pie chart, the values are displayed in tooltip. However, I want to display values outside of pie chart. I want to make chart like this image:
How to do this?
I was able to get something similar working using chart.js v2.3.0 using both the plugin API and extending chart types API. You should be able to take this as a starting point and tweak it to your needs.
Here is how it looks after being rendered.
Note, this requires digging deep into chart.js internals and could break if they change the way tooltips are positioned or rendered in the future. I also added a new configuration option called showAllTooltips to enable selectively using the plugin on certain charts. This should work for all chart types, but I am currently only using it for pie, doughnut, bar, and line charts so far.
With that said, here is a working solution for the image above.
Chart.plugins.register({
beforeRender: function (chart) {
if (chart.config.options.showAllTooltips) {
// create a namespace to persist plugin state (which unfortunately we have to do)
if (!chart.showAllTooltipsPlugin) {
chart.showAllTooltipsPlugin = {};
}
// turn off normal tooltips in case it was also enabled (which is the global default)
chart.options.tooltips.enabled = false;
// we can't use the chart tooltip because there is only one tooltip per chart which gets
// re-positioned via animation steps.....so let's create a place to hold our tooltips
chart.showAllTooltipsPlugin.tooltipsCollection = [];
// create a tooltip for each plot on the chart
chart.config.data.datasets.forEach(function (dataset, i) {
chart.getDatasetMeta(i).data.forEach(function (sector, j) {
// but only create one for pie and doughnut charts if the plot is large enough to even see
if (!_.contains(['doughnut', 'pie'], sector._chart.config.type) || sector._model.circumference > 0.1) {
var tooltip;
// create a new tooltip based upon configuration
if (chart.config.options.showAllTooltips.extendOut) {
// this tooltip reverses the location of the carets from the default
tooltip = new Chart.TooltipReversed({
_chart: chart.chart,
_chartInstance: chart,
_data: chart.data,
_options: chart.options.tooltips,
_active: [sector]
}, chart);
} else {
tooltip = new Chart.Tooltip({
_chart: chart.chart,
_chartInstance: chart,
_data: chart.data,
_options: chart.options.tooltips,
_active: [sector]
}, chart);
}
// might as well initialize this now...it would be a waste to do it once we are looping over our tooltips
tooltip.initialize();
// save the tooltips so they can be rendered later
chart.showAllTooltipsPlugin.tooltipsCollection.push(tooltip);
}
});
});
}
},
afterDraw: function (chart, easing) {
if (chart.config.options.showAllTooltips) {
// we want to wait until everything on the chart has been rendered before showing the
// tooltips for the first time...otherwise it looks weird
if (!chart.showAllTooltipsPlugin.initialRenderComplete) {
// still animating until easing === 1
if (easing !== 1) {
return;
}
// animation is complete, let's remember that fact
chart.showAllTooltipsPlugin.initialRenderComplete = true;
}
// at this point the chart has been fully rendered for the first time so start rendering tooltips
Chart.helpers.each(chart.showAllTooltipsPlugin.tooltipsCollection, function (tooltip) {
// create a namespace to persist plugin state within this tooltip (which unfortunately we have to do)
if (!tooltip.showAllTooltipsPlugin) {
tooltip.showAllTooltipsPlugin = {};
}
// re-enable this tooltip otherise it won't be drawn (remember we disabled all tooltips in beforeRender)
tooltip._options.enabled = true;
// perform standard tooltip setup (which determines it's alignment and x, y coordinates)
tooltip.update(); // determines alignment/position and stores in _view
tooltip.pivot(); // we don't actually need this since we are not animating tooltips, but let's be consistent
tooltip.transition(easing).draw(); // render and animate the tooltip
// disable this tooltip in case something else tries to do something with it later
tooltip._options.enabled = false;
});
}
},
});
// A 'reversed' tooltip places the caret on the opposite side from the current default.
// In order to do this we just need to change the 'alignment' logic
Chart.TooltipReversed = Chart.Tooltip.extend({
// Note: tooltipSize is the size of the box (not including the caret)
determineAlignment: function(tooltipSize) {
var me = this;
var model = me._model;
var chart = me._chart;
var chartArea = me._chartInstance.chartArea;
// set caret position to top or bottom if tooltip y position will extend outsite the chart top/bottom
if (model.y < tooltipSize.height) {
model.yAlign = 'top';
} else if (model.y > (chart.height - tooltipSize.height)) {
model.yAlign = 'bottom';
}
var leftAlign, rightAlign; // functions to determine left, right alignment
var overflowLeft, overflowRight; // functions to determine if left/right alignment causes tooltip to go outside chart
var yAlign; // function to get the y alignment if the tooltip goes outside of the left or right edges
var midX = (chartArea.left + chartArea.right) / 2;
var midY = (chartArea.top + chartArea.bottom) / 2;
if (model.yAlign === 'center') {
leftAlign = function(x) {
return x >= midX;
};
rightAlign = function(x) {
return x < midX;
};
} else {
leftAlign = function(x) {
return x <= (tooltipSize.width / 2);
};
rightAlign = function(x) {
return x >= (chart.width - (tooltipSize.width / 2));
};
}
overflowLeft = function(x) {
return x - tooltipSize.width < 0;
};
overflowRight = function(x) {
return x + tooltipSize.width > chart.width;
};
yAlign = function(y) {
return y <= midY ? 'bottom' : 'top';
};
if (leftAlign(model.x)) {
model.xAlign = 'left';
// Is tooltip too wide and goes over the right side of the chart.?
if (overflowLeft(model.x)) {
model.xAlign = 'center';
model.yAlign = yAlign(model.y);
}
} else if (rightAlign(model.x)) {
model.xAlign = 'right';
// Is tooltip too wide and goes outside left edge of canvas?
if (overflowRight(model.x)) {
model.xAlign = 'center';
model.yAlign = yAlign(model.y);
}
}
}
});
As the title says, how to make the sap.m.ProgressIndicator not animated when changing the percent value of it?
I cannot find a method for it, and extending would probably be the way to go, but maybe somebody has already figured it out and done it?
My Google search was not successful though.
interesting question, below is the sap.m.ProgressIndication.prototype.setPercentValue function, you can see when the percent value changes the bars values is changed via an linear animation
My suggestion, the easiest way to change this behavior is to extend the control to your own control and to redefine the setPercentValue, either remove the animate function on the bar or set time to null so there is no animation
sap.m.ProgressIndicator.prototype.setPercentValue = function(fPercentValue) {
var that = this;
...
if (that.getPercentValue() != fPercentValue) {
// animation without rerendering
this.$().addClass("sapMPIAnimate");
var time = Math.abs(that.getPercentValue() - fPercentValue) * 20;
this.setProperty("percentValue", fPercentValue, true);
var $Bar = this.$("bar");
$Bar.animate({
width : fPercentValue + "%"
}, time, "linear", function() {
that._setText.apply(that);
that.$().removeClass("sapMPIAnimate");
});
}
something like
jQuery.sap.declare("my.ProgressIndicator");
jQuery.sap.require("sap.m.ProgressIndicator");
sap.m.ProgressIndicator.extend("my.ProgressIndicator", {
renderer: {}
});
my.ProgressIndicator.prototype.setPercentValue = function(fPercentValue) {
var that = this;
// validation of fPercentValue
if (typeof (fPercentValue) == "number") {
if (that.getPercentValue() != fPercentValue) {
// animation without rerendering
this.$().addClass("sapMPIAnimate");
//var time = Math.abs(that.getPercentValue() - fPercentValue) * 20;
var time = 0;
this.setProperty("percentValue", fPercentValue, true);
var $Bar = this.$("bar");
$Bar.animate({
width : fPercentValue + "%"
}, time, "linear", function() {
that._setText.apply(that);
that.$().removeClass("sapMPIAnimate");
});
}
return this;
};
There is no convenient method to suppress this behavior.
You can only extend the control and overwrite the method setPercentValue to you desired behavior.
As of UI5 1.73, the animation on percantageValue-change can be turned off by setting the property displayAnimation to false.
Determines whether a percentage change is displayed with animation.
Since: 1.73.
<ProgressIndicator displayAnimation="false" />