How could I create an IObservable<bool> "IsReseting" which is true as soon as ShouldReset fires for the IReactiveDerivedList and back to false once the derived list is recomputed (and may not have any items)?
I'd like to display some activity indicator while filters apply.
I've done the following:
_isReseting= MyDerivedList
.ShouldReset
.Select(_ => true)
.Merge(MyDerivedList
.Changed
.Select(_ => false))
.ToProperty(this, x => x.IsReseting);
Related
So What I want to do is to press a button and inside the ButtonClicked Event I want to wait completing the event, until I press a specific button/one of the specified buttons.
I also know that there are some similar questions to this topic but I wasnt able to find a fix out of the answers
So basically this:
reactions += {
case event.ButtonClicked(`rollDice`) =>
some code ...
wait for one of the specified buttons to be pressed and then continue
some code ...
Is there an easy way to solve this problem without the use of threads?
There are certainly some abstractions you could set up around the event layer, but you asked for "easy", so my suggestion is to set a flag/state when the dice are rolled, and then check for that flag in the other buttons' event handler.
private var postDiceRollState: Option[InfoRelatedToRollDice] = None
reactions += {
case event.ButtonClicked(`rollDice`) =>
// capture whatever info you need to pass along to the later button handler
val relevantInfo = /* some code... */
// store that info in the "state"
postDiceRollState = Some(relevantInfo)
case event.ButtonClicked(other) if isPostDiceRollButton(other) =>
// when the other button gets clicked, pull the relevant info from your "state"
postDiceRollState match {
case Some(relevantInfo) =>
postDiceRollState = None // clear state
doInterestingStuff(relevantInfo) // "some code..."
case None =>
// nothing happens if you didn't roll the dice first
}
}
Note: I represented the "flag" as an Option, under the assumption that you might have some information you want to capture about the rollDice event. If you don't actually have anything to put in there, you could represent your state as private var didRollDice: Boolean = false and set/clear would be setting it to true/false respectively.
I'm implementing a click-and-drag type operation for panning a camera in a graphical application.
I would like to keep track of whether or not we are panning in another stream, that can be checked by other operations for filtering purposes (e.g. we shouldn't allow rubber band selection if we are panning).
The code looks something like this:
MouseClicked
.Where(startClick => startClick.Action == MouseAction.LeftDown)
.SelectMany(_ =>
MouseMoved
.Select(endMove => (_.startClick, endMove))
.TakeUntil(MouseReleased))
.Subscribe(_ => PanCamera(_.startClick, _.endMove));
My solution is to add the following two lines of code
MouseClicked
.Where(startClick => startClick.Action == MouseAction.LeftDown)
.Do(_ => _isPanning.OnNext(true)) // Add this
.SelectMany(_ =>
MouseMoved
.Select(endMove => (_.startClick, endMove))
.TakeUntil(MouseReleased)
.Finally(() => _isPanning.OnNext(false)) // Add this
)
.Subscribe(_ => PanCamera(_.startClick, _.endMove));
Where _isPanning is a Subject. This works great, but I was wondering if there was a better approach, without having to use a Subject.
Instead of using a Subject you could fold the panning state into a single observable with multiple subscribers. For example:
public IDisposable PanningBehaviour()
{
var observable = MouseClicked
.Where(startClick => startClick.Action == MouseAction.LeftDown)
.SelectMany(start =>
MouseMoved
.Select(current => (Start: start.Position, Current: current.Position, Panning: PanningState.Panning))
.TakeUntil(MouseReleased)
.Concat(Observable.Return((Start: Point.Empty, Current: Point.Empty, Panning: PanningState.NotPanning))))
.Publish();
var cameraSubscription = observable
.Where(tuple => tuple.Panning == PanningState.Panning)
.Subscribe(tuple => PanCamera(tuple.Start, tuple.Current));
var notPanningSubscription = observable
.Select(tuple => tuple.Panning == PanningState.NotPanning)
.Subscribe(allowOperations => { /* Allow / disallow actions */ });
return new CompositeDisposable(
notPanningSubscription,
cameraSubscription,
observable.Connect()
);
}
In the /* Allow / disallow actions */ you can put whatever you like. In an MVVM app, this approach would work particularly well with an ICommand implementation that allows external code to control it's "CanExecute" status.
FWIW, my MVx.Observable package features an Observable.Command which could directly subscribe to the "notPanning" status as shown below:
private MVx.Observable.Command _allowSelection = new MVx.Observable.Command();
public IDisposable PanningBehaviour()
{
var observable = MouseClicked
.Where(startClick => startClick.Action == MouseAction.LeftDown)
.SelectMany(start =>
MouseMoved
.Select(current => (Start: start.Position, Current: current.Position, Panning: PanningState.Panning))
.TakeUntil(MouseReleased)
.Concat(Observable.Return((Start: Point.Empty, Current: Point.Empty, Panning: PanningState.NotPanning))))
.Publish();
var cameraSubscription = observable
.Where(tuple => tuple.Panning == PanningState.Panning)
.Subscribe(tuple => PanCamera(tuple.Start, tuple.Current));
var notPanningSubscription = observable
.Select(tuple => tuple.Panning == PanningState.NotPanning)
.Subscribe(_allowSelection);
return new CompositeDisposable(
notPanningSubscription,
cameraSubscription,
observable.Connect()
);
}
public ICommand AllowSelection => _allowSelection;
Hope it helps.
Does ag-grid have a grid event that corresponds to to the "Reset Columns" item that is at the bottom of each column menu?
I need to do some special processing on "Reset Columns", and different handling of column "move", "resize", (etc.). I setup an event handler for the "columnEverythingChanged" event and a different event handler for "columnMoved" (etc.). I found that:
1) When no changes have been made to any column and I press "Reset Columns", "columnEverythingChanged" gets called. Fine.
2) When one or more columns have been changed and I press "Reset Columns", both "columnEverythingChanged" AND "columnMoved" (or other) get called.
My problem: in case (2), my "columnMoved" logic should not run.
A secondary problem: "columnEverythingChanged" also gets called at application startup. Not a big deal, but I had to hack around it.
This was so long ago, I don't remember the details. But in my code, I see that I use the columnMoved, columnResized, columnVisible, filterChanged, and sortChanged events. I run the same function on all of these events. That function debounces the event before doing my special processing.
you can use GlobalListener to work with multiple events without issue
gridOption.api.addGlobalListener(GlobalListenerSaveColumnState);
function GlobalListenerSaveColumnState(type, event) {
if (type == "columnVisible" || type == "columnResized"|| type == "columnMoved"|| type == "columnPinned" || type == "dragStopped") {
UpdateGridDefaultsIntoDb(event);
}
}
I solved this by 'overriding' the default getMainMenuItems function, which basically creates that "hamburger" icon, and reveals the options inside the Main Menu. When overriden, you can supply the list of items you want to include, and you can give it your own function you want to run when "Reset Columns" is clicked.
this.gridOptions = {
viewportDatasource: {
init: this.init,
setViewportRange: this.setViewportRange,
},
rowModelType: 'viewport',
sideBar: localStorage.getItem('test_pivot') == 'true' ? 'columns' : '',
suppressRowClickSelection: false,
suppressMultiSort: true,
rowMultiSelectWithClick: true,
rowSelection: 'multiple',
getMainMenuItems: (params) => this.getMainMenuItems(params), // <- This piece right here
getContextMenuItems: this.getContextMenuItems,
};
Later you define that getMainMenuItems()
getMainMenuItems(params) {
params.defaultItems[params.defaultItems.length - 1] = {
name: this.translate.instant('i18n.reset_columns'),
action: () => {
this.onResetColumnStateClick(null); // <- The custom functionality you want to execute when Reset Columns is clicked
},
};
return params.defaultItems;
}
I believe their documentation has another example containing the getMainMenuItems function
https://ag-grid.com/javascript-data-grid/column-menu/
https://plnkr.co/edit/?open=main.js&preview
I have a reactive command readCommand that I execute based on a signal.
IObservable<Unit> readSignal = ...
readSignal.InvokeCommand(readCommand);
The result of the command is shown in a user control, let's say a TextBox.
I would like to place a refresh button next to the TextBox which when clicked invokes the readCommand. This button should not be visible when the the command is executing and then made visible after 5 seconds of execution of the command.
My attempt to display/hide the refresh button follows. IsRefreshable is linked to the Visibility property of the refresh button.
readCommand
.IsExecuting
.SelectMany(x => (x ? Observable.Timer(TimeSpan.FromMilliseconds(0)) : Observable.Timer(refreshTimeout)).Select(_ => !x))
.ToPropertyEx(this, vm => vm.IsRefreshable, false, false, RxApp.MainThreadScheduler);
I think it is working fine, when the rate of emission of readSignal is slower than the rate of refresh (refreshTimeout). But clearly does not work if the readSignal rate is faster than refreshTimeout.
With the SelectMany you are turning an IObservable<IObservable<T>> into an IObservable<T> by merging all of the inner observables - which means all of the the timers will fire leading to unwanted behaviour.
A SelectMany is effectively the same as a Select/Merge combo.
What you need is to just produce values from the latest inner observable produced. For that you need a Select/Switch combo.
Try this:
readCommand
.IsExecuting
.Select(x => x
? Observable.Timer(TimeSpan.FromMilliseconds(0))
: Observable.Timer(refreshTimeout).Select(_ => !x))
.Switch()
.ToPropertyEx(this, vm => vm.IsRefreshable, false, false, RxApp.MainThreadScheduler);
I am still trying to wrap my head around rxjs and observables and BehaviorSubject. What I would like to do is, combine BehaviorSubject and LocalStorage so that all components get notified when a particular LocalStorage variable changes.
For example, consider the following scenario.
There are two components Component1 and Component2.
Both these components look for a variable in LocalStorage called Component1 and Component2, which contain a color and they display a square of that color.
Component1 needs to subscribe to "Component1Color" key in LocalStorage and Component2 needs to subscribe to "Component2Color" in LocalStorage.
One way to do this is have a BehaviorSubject that maintains the state of LocalStorage and anytime a change is made to any variable, broadcast this message to all the components that have subscribed to the BehaviorSubject.
The problem with this approach is that when Component2Color is updated, component1 gets notified as well, and will do nothing about it.
What would be nice is, Component1 gets notified only when Component1Color is updated, and Component2 gets notified only when Component2Color is updated. Is there a way this can be done using a single BehaviorSubject?
You can listen to the storage event via the StorageEvent
const storage = Rx.Observable.fromEvent(window, 'storage').groupBy(ev => ev.key);
Then you can access each stream by doing:
let component1Stream = storage.filter(x => x.key === 'Component1Color').merge();
//Or using flatMap
let component2Stream = storage.flatMap(x =>
x.key === 'Component2Color' ? x : Rx.Observable.empty()
);
Edit
As was pointed out in the comments, the storage event is only useful for cross page updates it won't update if you are on the same page. Fortunately, the solution remains pretty much the same, you just need to provide a decorator object for the localStorage object so that it can emit events.
class RxLocalStorage {
private _subject: Rx.Subject<any>;
private _updates: Observable<any>;
constructor() {
this._subject = new Rx.Subject();
this._updates = this._subject.groupBy(ev => ev.key)
.share();
}
getItem(key) { return this._updates.filter(x => x.key === key).merge(); }
setItem(key, newValue) {
const oldValue = localStorage.getItem(key);
const event = {key, newValue, oldValue};
localStorage.setItem(newValue);
this._subject.next(event);
}
}
let myLocalStorage = new RxLocalStorage();
myLocalStorage.getItem('Component1Color')
.subscribe(x => console.log('Component1 Color changed'));
myLocalStorage.setItem('Component1Color', 'blue');
//-> Component1 Color changed
Note the above assumes that all of your subscriptions are made before you begin making changes.