Delayed display of a UI control after execution of ReactiveCommand - system.reactive

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

Related

How to drag and drop one div onto another in Cypress test using cypress-drag-drop package?

I am trying to write a Cypress test that drags & drops column A onto column B on this webpage - https://the-internet.herokuapp.com/drag_and_drop
I installed the #4tw/cypress-drag-drop package & added the following to my support/commands.js:
require("#4tw/cypress-drag-drop");
Here is my Cypress code:
cy.get("#column-a").drag("#column-b", { force: true });
The test passes, but the columns aren't behaving the same way visually as it does when I manually drag column A onto column B.
Instead, this is what appears on the browser in Cypress Explorer:
As you can see, column A is greyed out, as if it were dragged, but not dropped
Can someone please point out what I'm doing incorrectly?
You cannot, the library does not support the correct events for this page.
But you can do it using Cypress commands.
These are the events used on the page
col.addEventListener("dragstart", handleDragStart, false);
col.addEventListener("dragenter", handleDragEnter, false);
col.addEventListener("dragover", handleDragOver, false);
col.addEventListener("dragleave", handleDragLeave, false);
col.addEventListener("drop", handleDrop, false);
col.addEventListener("dragend", handleDragEnd, false);
This is the test that passes
// check initial order
cy.get('div.column')
.then($cols => [...$cols].map(col => col.innerText.trim()))
.should('deep.eq', ['A', 'B'])
const dataTransfer = new DataTransfer;
cy.get("#column-a")
.trigger('dragstart', {dataTransfer})
cy.get("#column-b")
.trigger('dragenter')
.trigger('dragover', {dataTransfer})
.trigger('drop', {dataTransfer})
cy.get("#column-a")
.trigger('dragend')
// check new order
cy.get('div.column')
.then($cols => [...$cols].map(col => col.innerText.trim()))
.should('deep.eq', ['B', 'A'])
// check drag opacity reverted back
cy.get("#column-a").should('have.css', 'opacity', '1')
cy.get("#column-b").should('have.css', 'opacity', '1')

Scala swing wait for a button press after pressing a button

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.

When to set the initial value of the form?

I have these codes, if the user opens the form dialog for the first time, it works well.
function PostFormDialog({ id }) {
const queryClient = useQueryClient()
const post = useQuery(['post', id], () => fetchPost(id))
const update = useMutation(() => updatePost(formValue), {
onSuccess: () => {
queryClient.invalidateQueries(['post', id])
},
})
if (post.isLoading) {
return 'loading...'
}
return (
<Dialog {...dialogProps}>
<Form initialValue={post} onSubmit={update.mutate} />
</Dialog>
)
}
But when I submit the form once, I quickly open the dialog box again, and it will display the last data. The data is being retrieved at this time, but isLoading is false.
I want:
After opening the form dialog box, if the data is out of date, wait for the data to be loaded and display loading...
If you are editing the form, switching tabs may cause data to be retrieved, but loading... is not displayed at this time
This is hard for me. I can avoid it by using optimistic updates, but is there a better way?
Try playing around with react query options. For example:
useQuery(
["post", id],
() => fetchPost(id),
{ refetchOnMount: true }
);
After opening the form dialog box, if the data is out of date, wait for the data to be loaded and display loading...
if your PostFormDialog unmounts after it is closed, you can set cacheTime for your query to a "smaller time". It defaults to 5 minutes so that data can be re-used. If you set cacheTime: 0, the cached data will be removed immediately when you unmount the component (= when you close the dialog).
Every new open of the dialog will result in a hard loading state.
If you are editing the form, switching tabs may cause data to be retrieved, but loading... is not displayed at this time
loading... is not displayed because the query is no longer in loading state. The extra isFetching boolean will be true though, which can be used for background updates.
However, when populating forms with "initial data", background updates don't much sense, do they? What if the user has changed data already?
You can:
turn off background refetches with staleTime: Infinity to just load initial data once.
Keep the background refetches, and then maybe display a "data has changed" notification to the user?
Do not initialize the form with initialValue, but keep it undefined and fall back to the server value during rendering. I've written about this approach here

React-Bootstap-Typeahead: Manually set custom display value in onChange() upon menu selection

In the onChange of React-Bootstrap-Typeahead, I need to manually set a custom display value. My first thought was to use a ref and do something similar to the .clear() in this example.
But although .clear() works, inputNode.value = 'abc' does not work, and I'm left with the old selected value from the menu.
onChange={option => {
typeaheadRef.current.blur(); // This works
typeaheadRef.current.inputNode.value = 'abc'; // This does not work (old value is retained)
}}
I also tried directly accessing the DOM input element, whose ID I know, and doing
var inputElement = document.querySelector('input[id=myTypeahead]');
inputElement.value = 'abc';
But that didn't work either. For a brief second, right after my changed value = , I do see the new display label, but then it's quickly lost. I think the component saves or retains the menu-selected value.
Note: I cannot use selected, I use defaultSelected. I have some Formik-related behavior that I've introduced, and it didn't work with selected, so I'm stuck with defaultSelected.
The only workaround I found is to re-render the Typeahead component (hide and re-show, from a blank state) with a new defaultSelected="abc" which is a one-time Mount-time value specification for the control.
I couldn't get selected=.. to work, I have a wrapper around the component which makes it fit into Formik with custom onChange and onInputChange and selected wasn't working with that.
So the simple workaround that works is, if the visibility of the Typeahead depends on some condition (otherwise it won't be rendered), use that to momentarily hide and re-show the component (a brand new repaint) with a new defaultSelected, e.g.
/* Conditions controlling the visibility of the Typeahead */
!isEmptyObject(values) &&
(values.approverId === null || (values.approverId !== null && detailedApproverUserInfo)
)
&&
<AsyncTypehead defaultSelected={{...whatever is needed to build the string, or the literal string itself...}}
..
// Given the above visibility condition, we'll hide/re-show the component
// The below will first hide the control in React's renders
setFieldValue("approver", someId);
setDetailedUserInfo(null);
// The below will re-show the control in React's renders, after a small delay (a fetch)
setDetailedUserInfo(fetchDetailedUserInfo());

ag-grid column menu reset columns event?

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