Prompt with callback when user tries to exit - javascript

I have a page which in it there is a Webrtc call between users.
And i want that when a user tries to go to another page/reload page/exit browser, there will be a prompt asking him if he's sure that he wants to leave the page, and if the user presses "yes", i want there to be a callback.
In the callback(not sure if relevant) i would close the peers and disconnect everything properly so that the call will get disconnected and the user won't hear audio anymore and the other side will know the user got disconnected also.
To do this on refresh i saw the window.onunloaded function
Is it even possible on browser exit?
I saw the Prompt option to do this on route change but i didn't see how to insert a callback on confirmation there.
I will be happy to get one working example as it feels to complicated for such a little thing which is weird..
Thanks.

Handling React's navigation events
I think what you're looking for is the getUserConfirmation prop of BrowserRouter component.
The function passed to the prop might look like (TypeScript example):
const getUserConfirmation = (
message: string,
callback: (ok: boolean) => void
): void => {
const answer = window.confirm("Do you want to navigate away?");
// do something depending on which button was pressed
callback(answer); // allow or disallow React's navigation
};
In the place where you define your routes:
<BrowserRouter getUserConfirmation={getUserConfirmation}>
... routes go here ...
</BrowserRouter>
You'll still need to use <Prompt>, though:
<Prompt
when={boolean}
message="This string will be supplied in the confirmation function above as second parameter"
/>
Handling browser refresh
You might want to add a callback like this
window.onbeforeunload = () => {
// some non-blocking logic
return true;
};
After you no longer need it, you can cleanup:
window.onbeforeunload = null;
Looking at this SO question, it doesn't seem to be possible out of the box to capture the user's response to the native confirm window in onbeforeunload.
Probably window.onunload would be the right place to clean up your stuff as it most probably means the user has opted to leave the page.

Related

Assigning onclick on button that is outside of current react app (php) gives unexpected behaviour

We are builidng react components for a php app, it doesn't really have much intergration but there's a php save button that is should listen for and save onclick. But there's a catch, for some reason most of functions i'm trying to use are just not getting ran. I'm working with react-form-hook and trying to run the onsubmit() with my function inside.
handleSubmit((data) => {
handleSaveOrderProducts(data, selectedProductsRef.current);
});
To assign this function to the button i have to listen for the button clicks with id, i tried different approaches but none seems to fix the problem. Ways to listen i tried:
Using https://www.npmjs.com/package/react-detect-click-outside#features lib
const productInformationContainerRef = useDetectClickOutside({
onTriggered(event) {
// #ts-ignore
if (event.target?.id === "save-order-button") {
console.log("save order button");
handleSubmit((data) => {
handleSaveOrderProducts(data, selectedProductsRef.current);
});
}
},
});
What i get in result? The callback itself runs, but the function (handlesubmit) is not. Even if i put a console.log in callback with data there's just nothing displayed.
Different approaches from this stackoverflow thread, mostly everything related to functional react Detect click outside React component .
If you had experience in something like this or have any theoretical knowledge please comment :)strong text

How do I wait for users to click on certain buttons in component, then return a value from that in a function depending on which button was pressed?

I have an overlay component that appears when a user clicks on certain things in my page, and in this overlay it gives a warning and 2 buttons, one for yes and the other for no. What I want is to create a function that'll serve this component, and then it will wait for the user to respond, and subsequently return true or false based on what button was pressed. This boolean result can then be used to further progress to other code.
This is what I have tried already. It uses promises rather than rxjs observables.
A component will call this function to bring the overlay from the service, eg this.service.promptUser().then(res => if (res === true) { doSomething() }).
In the service:
didContinue: boolean = null;
async promptUser() {
this.showOverlay.next(true) //BehaviourSubject when true brings the popup
await waitForUser();
const decision = this.didContinue;
this.closeOverlay(); //sets didContinue back to null
return decision
}
The didContinue is a property inside of the service to indicate whether they have clicked yes or no using a boolean. Otherwise it will remain null. The click events from the overlay component will set the property didContinue to true or false.
The waitForUser function to wait for the user's input:
async waitForUser() {
while (this.didContinue === null) {setTimeout(() => {}, 50};
}
Currently it'll get stuck at the waitForUser() function but the popup will have not rendered at that stage, so the user can't input anything, the didContinue property will never change, and the application will freeze.
Please do send it forward if you know of an existing solution, I miss a lot of things with my google-foo. I am currently still new to Angular.
Create a service that will handle the result
inject service in your component where you want to use it like this
constructor(public popService: popService) { } // create functions accordingly in service
Call service method with the help of constructor on click() event like this
<button (click)="popService.success()">Done
<button (click)="popService.cancel()">Done
i hope this will help, let me know if you need further help :-)

Angular4 - how to ensure ngOnDestroy finishes before navigating away

I have a list of objects. The user can click on one, which then loads a child component to edit that component.
The problem I have is that when the user goes back to the list component, the child component has to do some cleanup in the ngOnDestroy method - which requires making a call to the server to do a final 'patch' of the object. Sometimes this processing can be a bit slow.
Of course what happens is the user arrives back on the list, and that api call completes before the database transaction from the ngOnDestroy completes, and thus the user sees stale data.
ngOnDestroy(){
this.destroy$.next();
this.template.template_items.forEach((item, index) => {
// mark uncompleted items for deletion
if (!item.is_completed) {
this.template.template_items[index]['_destroy'] = true;
};
});
// NOTE
// We don't care about result, this is a 'silent' save to remove empty items,
// but also to ensure the final sorted order is saved to the server
this._templateService.patchTemplate(this.template).subscribe();
this._templateService.selectedTemplate = null;
}
I understand that doing synchronous calls is not recommended as it blocks the UI/whole browser, which is not great.
I am sure there are multiple ways to solve this but really don't know which is the best (especially since Angular does not support sync requests so I would have to fall back to standard ajax to do that).
One idea I did think of was that the ngOnDestroy could pass a 'marker' to the API, and it could then mark that object as 'processing'. When the list component does its call, it could inspect each object to see if it has that marker and show a 'refresh stale data' button for any object in that state (which 99% of the time would only be a single item anyway, the most recent one the user edited). Seems a bit of a crap workaround and requires a ton of extra code compared to just changing an async call to a sync call.
Others must have encountered similar issues, but I cannot seem to find any clear examples except this sync one.
EDIT
Note that this child component already has a CanDeactive guard on it. It asks the user to confirm (ie. discard changes). So if they click to confirm, then this cleanup code in ngOnDestroy is executed. But note this is not a typical angular form where the user is really 'discarding' changes. Essentially before leaving this page the server has to do some processing on the final set of data. So ideally I don't want the user to leave until ngOnDestroy has finished - how can I force it to wait until that api call is done?
My CanDeactive guard is implemented almost the same as in the official docs for the Hero app, hooking into a general purpose dialog service that prompts the user whether they wish to stay on the page or proceed away. Here it is:
canDeactivate(): Observable<boolean> | boolean {
console.log('deactivating');
if (this.template.template_items.filter((obj) => { return !obj.is_completed}).length < 2)
return true;
// Otherwise ask the user with the dialog service and return its
// observable which resolves to true or false when the user decides
return this._dialogService.confirm('You have some empty items. Is it OK if I delete them?');
}
The docs do not make it clear for my situation though - even if I move my cleanup code from ngOnDestroy to a "YES" method handler to the dialog, it STILL has to call the api, so the YES handler would still complete before the API did and I'm back with the same problem.
UPDATE
After reading all the comments I am guessing the solution is something like this. Change the guard from:
return this._dialogService.confirm('You have some empty items.
Is it OK if I delete them?');
to
return this._dialogService.confirm('You have some empty items.
Is it OK if I delete them?').subscribe(result => {
...if yes then call my api and return true...
...if no return false...
});
As you said, there are many ways and they depend on other details how your whole app, data-flow and ux-flow is setup but it feels like you might want to take a look at CanDeactivate guard method which ensures user cannot leave route until your Observable<boolean>|Promise<boolean> are resolved to true.
So, its a way for async waiting until your service confirms things are changed on server.
[UPDATE]
it depends on your user confirmation implementation but something along these lines...
waitForServiceToConfirmWhatever(): Observable<boolean> {
return yourService.call(); //this should return Observable<boolean> with true emitted when your server work is done
}
canDeactivate(): Observable<boolean> {
if(confirm('do you want to leave?') == true)
return this.waitForServiceToConfirmWhatever();
else
Observable.of(false)
}
One "workaround" I can think of is to have your list based in client. You have the list as a JS array or object and show the UI based on that. After editing in the details screen, have a stale flag on the item which the service called on ngOnDestroy clears while updating the other related data.

Perform action if window history for a site is empty

I have a Component that works as follows.
export default class BackButton extends Component {
back() {
window.history.back()
}
render() {
return (
<Button
icon={<ArrowLeft/>}
onTouchTap={this.back.bind(this)}
/>
)
}
}
This works perfectly well, but there is an "edge case". If you pop from the stack enough, it might lead to being stuck at a certain state without being able to go back because the stack is empty. When that happens, I want to be able to perform some additional functionality and disable the back button.
<Button
icon={<ArrowLeft/>}
disabled={!hasMoreHistory}
onTouchTap={this.back.bind(this)}
onHistoryEmpty={() => callback ? callback() : Router.go('/')}
/>
Is there a way to do this with javascript?
Unfortunately as other SO question(s) point it out, there is no reliable way of telling whether you can go back in your browser history or not.
window.history.length
does not contain a standardized value when you open up a fresh page(in chrome: 1, in IE: 0)
might be > 0 in the case when you may go forward
window.history.previous - in most cases is undefined
See:
How to check if the user can go back in browser history or not

How can I ask users to confirm before leaving the page on the Aurelia.js

I have an long operation on the page. How can I said user wait a bit when he make the navigation change?
The idea is to show the promt dialog. But the trouble prevent default Aurelia router behavior.
How make it works in Aurelia.js?
You could use the canDeactivate hook of your view-model. Like this:
canDeactivate() {
return confirm('Are you sure you want to leave this page?');
}
canDeactivate() - Implement this hook if you want to control whether or not the router can navigate away from your view-model when moving to a new route. Return a boolean value, a promise for a boolean value, or a navigation command.
More information at http://aurelia.io/docs.html#/aurelia/framework/1.0.0-beta.1.2.2/doc/article/cheat-sheet/7

Categories

Resources