I am trying to show Angular Material Dialog Box (Popup window), when User hits the Chrome Window Close button (upper right). The Dialog modal should hold prompt the user, if they want to save changes, or cancel,
However it only shows the modal for quick second, then closes without waiting for user.
Using code reference below. How can it be fixed ?
How can we detect when user closes browser?
#HostListener('window:beforeunload', ['$event'])
beforeunloadHandler(event) {
this.openDocumentSaveDialog();
}
public openDocumentSaveDialog(): void {
const documentSaveDialogRef = this.documentSaveDialog.open(DocumentSaveDialogComponent, {
width: '600px',
height: '200px',
disableClose: true,
autoFocus: false,
data: null
});
documentSaveDialogRef.afterClosed().subscribe(result => {
this.closeMenu.emit()
});
}
Note: We do Not want to display Native chrome browser popup, but a custom popup .
Angular Material Dialog Box:
https://material.angular.io/components/dialog
The beforeunload event doesn't support a callback function that returns a promise so you can't show the popup and return value as it isn't a sync operation.
what you can do instead is just returning false always or call
event.preventDefault()
and if the user decided to leave the page you can call
window.close(....)
if not you already have cancelled the event.
so your code should look something like this
#HostListener('window:beforeunload', ['$event'])
beforeunloadHandler(event) {
this.openDocumentSaveDialog();
event.preventDefault();
event.returnValue = '';
return false;
}
public openDocumentSaveDialog(): void {
const documentSaveDialogRef =
this.documentSaveDialog.open(DocumentSaveDialogComponent, {
width: '600px',
height: '200px',
disableClose: true,
autoFocus: false,
data: null
});
documentSaveDialogRef.afterClosed().subscribe(result => {
if(!result)
window.close()
this.closeMenu.emit()
});
}
I am afraid that browser security won't allow you to prevent the user from closing the window. In my opinion this is not possible, you can only show the native window that warns the user about losing the data if closing the browser window.
This works for me. But you have no control over the display!
#HostListener('window:beforeunload', ['$event'])
showAlertMessageWhenClosingTab($event) {
$event.returnValue = 'Your data will be lost!';
}
Related
I'm writing my first electron app, so please be lenient :)
When the user presses a button on the main Window, there should open a new window which shows some json string.
This event gets cought by ipcMain:
ipcMain.on("JSON:ShowPage", function(e, item) {
createJSONWindow(item);
})
This is the function where I create the new window:
function createJSONWindow(item) {
let jsonWin = new BrowserWindow({
width: 600,
height: 800,
center: true,
resizable: true,
webPreferences:{
nodeIntegration: true,
show: false
}
});
jsonWin.loadFile("jsonView.html");
ipcMain.on('JSON_PAGE:Ready', function(event, arg) {
jsonWin.webContents.send('JSON:Display', item);
})
jsonWin.once('ready-to-show',()=>{
jsonWin.show()
});
jsonWin.on('closed',()=>{
jsonWin = null;
});
}
Now to my question, when I have multiple JSONWindows open, every single one of them gets the JSON:Display Message and updates it's content. Shouldn't they work independently from each other? The jsonWin is always a new BrowserWindow, isn't it?
Thanks in advance.
The problem is this code:
ipcMain.on('JSON_PAGE:Ready', function(event, arg) {
jsonWin.webContents.send('JSON:Display', item);
})
Every time you create a new window, you are having ipcMain subscribe to the same message. This means that when ipcMain gets the 'JSON_PAGE:Ready' message, it calls every single callback it has registered and sends a message to every single window.
The simplest solution in this case is to use the event that's passed to the ipcMain handler to send the message to the renderer that sent it to main. Second, subscribe a single time outside of createJSONWindow:
ipcMain.on('JSON_PAGE:Ready', function(event, arg) {
e.sender.send('JSON:Display', item);
});
function createJSONWindow() { ... }
However, is 'JSON:Display' simply sent when the page has loaded? If so, you can subscribe the window's webContents to the did-finish-load event which fires when the page has loaded.
jsonWin.webContents.on("did-finish-load", () => {
jsonWin.webContents.send(...);
});
I have a custom popup functionality. What I want is for the browser back button to close this popup.
My ideal scenario would be to NOT show a hashtag in the URL bar.
I have tried putting window.history.pushState('forward', null, ''); in my showPopup() function and then doing the following:
$(window).on('popstate', function () {
closePopup();
});
This does work but the problem is when I manually close the popup I have to press the back button twice to navigate back to the previous page (obviously because a browser history entry was added when the popup was opened).
What is the best way of doing this? Can it be done without adding a browser history entry? Essentially what I am trying to do is replicate the behaviour of a mobile app. Press the back button in a mobile app will usually dismiss any open modals or context menus.
$('.popup-link').click(function() {
showPopup();
});
$('.popup-close').click(function() {
hidePopup();
});
function showPopup() {
$('.popup').addClass('active');
}
function hidePopup() {
$('.popup').removeClass('active');
}
.popup {
background-color: #ccc;
width: 300px;
height: 300px;
display: none;
}
.popup.active {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="popup-link">Click</button>
<div class="popup">
<button class="popup-close">x</button>
<!-- popup content here -->
</div>
It is not possible to do it without adding browser history entries since you cannot override the back button behaviour, see Intercepting call to the back button in my AJAX application: I don't want it to do anything
Sujumayas answer is a good option, you should introduce some additional variable though to avoid problems with the history when opening multiple popups (e.g. when clicking the button multiple times)
Here is some possible sample code:
let popupOpen = false;
$(".popup-link").click(function() {
showPopup();
});
$(".popup-close").click(function() {
window.history.back();
});
function showPopup() {
if (popupOpen) {
window.history.back();
}
popupOpen = true;
window.history.pushState("forward", null, "");
$(".popup").addClass("active");
}
function hidePopup() {
popupOpen = false;
$(".popup").removeClass("active");
}
$(window).on("popstate", function() {
hidePopup();
});
Additionally please note that you might have problems with Opera Mini: https://caniuse.com/#search=history
Altho I don't recommend to override regular browser history managment (back button) to use it as you please....
I think that the only thing you missed in your example is that the close button should not close the modal by itself, but instead just execute a backbutton event (which will eventually close the modal).
That simple fix and it will work as you wanted.
I am doing already something like this, and it works nicely with the browser back-button and by pushing the android back-button as well. I am also not showing a hashtag in the URL bar.
Here is the stub (I just tried to apply that to Your scenario):
function freezeHistory() {
window.history.pushState({}, window.document.title, window.location.href);
}
function goBack() {
/*
Custom history back actions: close panel, close popup, close drop-down menu
*/
var popupOpen = $(".popup.active").length > 0;
if(popupOpen) {
hidePopup();
return false;
}
window.history.back();
return true;
}
function showPopup() {
$('.popup').addClass('active');
freezeHistory();
}
function hidePopup() {
$('.popup').removeClass('active');
}
$(window).on("popstate", function(e) {
/*
Browsers tend to handle the popstate event differently on page load.
Chrome (prior to v34) and Safari always emit a popstate event on page load,
but Firefox doesn’t.
*/
goBack();
})
If this won't work for You out-of-the box, it is because IMHO You may need to clarify a little bit how do You expect to manage the page history. Feel free to add more detail to Your question if this isn't working as You'd expect now, but anyway, I strongly believe You got the idea and You are able to apply it inside the scenario of Your web-app.
Open popup and try going back and forth with the browser history buttons
$(document).ready(function () {
// manage popup state
var poped = false;
$('.popup-link').click(function () {
// prevent unwanted state changtes
if(!poped){
showPopup();
}
});
$('.popup-close').click(function () {
// prevent unwanted state changtes
if(poped){
hidePopup();
}
});
function showPopup() {
poped = true;
$('.popup').addClass('active');
// push a new state. Also note that this does not trigger onpopstate
window.history.pushState({'poped': poped}, null, '');
}
function hidePopup() {
poped = false;
// go back to previous state. Also note that this does not trigger onpopstate
history.back();
$('.popup').removeClass('active');
}
});
// triggers when browser history is changed via browser
window.onpopstate = function(event) {
// show/hide popup based on poped state
if(event.state && event.state.poped){
$('.popup').addClass('active');
} else {
$('.popup').removeClass('active');
}
};
.popup {
background-color: #ccc;
width: 300px;
height: 300px;
display: none;
}
.popup.active {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="popup-link">Click</button>
<div class="popup">
<button class="popup-close">x</button>
<!-- popup content here -->
</div>
You could add window.history.go(-2) to your popstate. That should take you back twice, which would be your original page before the modal as pushState added an entry to your history object.
Conversely, you could use history.back(2)
Use window.location.href to go 2 pages back and reload
Just run window.history.back(); when closing the popup.
$('.popup-close').click(function() {
hidePopup();
window.history.back();
});
You would have two options to implement this:
Option 1: Using the window.beforeunload event. reference
$('.popup-link').click(function() {
showPopup();
$(window).on("beforeunload", hidePopup);
});
$('.popup-close').click(hidePopup);
function hidePopup() {
$(window).off("beforeunload", hidePopup);
$('.popup').removeClass('active');
}
Demo
Option 2: Using the HTML5 History API. reference
$('.popup-link').click(function() {
showPopup();
window.history.pushState('popup-open', null, '');
$(window).on('popstate', hidePopup);
});
$('.popup-close').click(function() {
if(history.state == 'popup-open') {
window.history.back();
}
hidePopup();
});
function hidePopup() {
$(window).off('popstate', hidePopup);
$('.popup').removeClass('active');
}
Demo
Edit: sujumayas's idea is also pretty good one.
Demo
Further, I'ld recommend to register the popstate / beforeunload events only when necessary and unregister them, when you no longer need 'em in order to avoid overhead.
if anyone use bootstrap with any version you can use this
let popupOpen = false;
//show
$(".popup-link").on('click',(function() {
showPopup();
})
);
// hide
$(".popup-close").on('click',(function() {
window.history.back();
})
);
// on click back button
$(window).on("popstate", function() {
if (popupOpen) {
hidePopup();
}
});
// for bootstrap, if clicked outside the modal or close somehow
$(window).on('hidden.bs.modal', function(e) {
// Make sure is open and the same modal
if (e.target.id=='exampleModal' && popupOpen) {
popupOpen = false;
window.history.back();
}
});
function showPopup() {
// if open back
if (popupOpen) {
window.history.back();
}
popupOpen = true;
// push the browser history
window.history.pushState("forward", null, "#popup");
// you have to use the id
$("#exampleModal").modal('show');
}
function hidePopup() {
popupOpen = false
// you have to use the id to close the modal
$("#exampleModal").modal('hide');
}
I am using the Angular-ui/bootstrap modal in my project.
Here is my modal:
$scope.toggleModal = function () {
$scope.theModal = $modal.open({
animation: true,
templateUrl: 'pages/templates/modal.html',
size: "sm",
scope: $scope
});
}
One is able to close the modal by clicking the ESC button or clicking outside the modal area. Is there a way to run a function when this happens? I am not quite sure how to catch the sort of closing.
I know that I can manually dismiss a modal by having a ng-click="closeModal()" like this:
$scope.closeModal = function () {
$scope.theModal.dismiss('cancel');
};
Yes you can. It causes a dismiss event and the promise is rejected in that case. Also, note that the $modal.open() method returns an object that has a result property that is a promise.
With the promise you can...
//This will run when modal dismisses itself when clicked outside or
//when you explicitly dismiss the modal using .dismiss function.
$scope.theModal.result.catch(function(){
//Do stuff with respect to dismissal
});
//Runs when modal is closed without being dismissed, i.e when you close it
//via $scope.theModal.close(...);
$scope.theModal.result.then(function(datapassedinwhileclosing){
//Do stuff with respect to closure
});
as a shortcut you could write:
$scope.theModal.result.then(doClosureFn, doDismissFn);
See ref
The open method returns a modal instance, an object with the following properties:
close(result) - a method that can be used to close a modal, passing a result
dismiss(reason) - a method that can be used to dismiss a modal, passing a reason
result - a promise that is resolved when a modal is closed and rejected when a modal is dismissed
opened - a promise that is resolved when a modal gets opened after downloading content's template and resolving all variables
'rendered' - a promise that is resolved when a modal is rendered.
Old question, but if you want to add confirmation dialogs on various close actions, add this to your modal instance controller:
$scope.$on('modal.closing', function(event, reason, closed) {
console.log('modal.closing: ' + (closed ? 'close' : 'dismiss') + '(' + reason + ')');
var message = "You are about to leave the edit view. Uncaught reason. Are you sure?";
switch (reason){
// clicked outside
case "backdrop click":
message = "Any changes will be lost, are you sure?";
break;
// cancel button
case "cancel":
message = "Any changes will be lost, are you sure?";
break;
// escape key
case "escape key press":
message = "Any changes will be lost, are you sure?";
break;
}
if (!confirm(message)) {
event.preventDefault();
}
});
I have a close button on the top right of mine, which triggers the "cancel" action. Clicking on the backdrop (if enabled), triggers the cancel action. You can use that to use different messages for various close events. Thought I'd share in case it's helpful for others.
You can use the "result" promise returned by $modal.open() method. As bellow:
$scope.toggleModal = function () {
$scope.theModal = $modal.open({
animation: true,
templateUrl: 'pages/templates/modal.html',
size: "sm",
scope: $scope
});
$scope.theModal.result.then(function(){
console.log("Modal Closed!!!");
}, function(){
console.log("Modal Dismissed!!!");
});
}
Also you can use "finally" callback of "result" promise as below:
$scope.theModal.result.finally(function(){
console.log("Modal Closed!!!");
});
In my case, when clicking off the modal, we wanted to display a prompt warning the user that doing so would discard all unsaved data in the modal form. To do this, set the following options on the modal:
var myModal = $uibModal.open({
controller: 'MyModalController',
controllerAs: 'modal',
templateUrl: 'views/myModal.html',
backdrop: 'static',
keyboard: false,
scope: modalScope,
bindToController: true,
});
This prevents the modal from closing when clicking off:
backdrop: 'static'
And this prevents the modal from closing when hitting 'esc':
keyboard: false
Then in the modal controller, add a custom "cancel" function - in my case a sweet alert pops up asking if the user wishes to close the modal:
modal.cancel = function () {
$timeout(function () {
swal({
title: 'Attention',
text: 'Do you wish to discard this data?',
type: 'warning',
confirmButtonText: 'Yes',
cancelButtonText: 'No',
showCancelButton: true,
}).then(function (confirm) {
if (confirm) {
$uibModalInstance.dismiss('cancel');
}
});
})
};
And lastly, inside the modal controller, add the following event listeners:
var myModal = document.getElementsByClassName('modal');
var myModalDialog = document.getElementsByClassName('modal-dialog');
$timeout(function () {
myModal[0].addEventListener("click", function () {
console.log('clicked')
modal.cancel();
})
myModalDialog[0].addEventListener("click", function (e) {
console.log('dialog clicked')
e.stopPropagation();
})
}, 100);
"myModal" is the element you want to call the modal.cancel() callback function on.
"myModalDialog" is the modal content window - we stop the event propagation for this element so it won't bubble up to "myModal".
This only works for clicking off the modal (in other words clicking the backdrop). Hitting 'esc' will not trigger this callback.
Instead of ng-click="closeModal()" you can try ng-click="$dismiss()"
<button ng-click="$dismiss()">Close</button>
We can call jquery 'On' event as well in the controller like this. here "viewImageModal" is the id of modal popup.
constructor($scope: AuditAppExtension.IActionPlanScope, dataSvc: ActionPlanService, Upload, $timeout, $mdToast: any) {
$('#viewImageModal').on('shown.bs.modal', function (e) {
console.log("shown", e);
$scope.paused = false;
$modal.find('.carousel').carousel('cycle');
});
$('#viewImageModal').on('hide.bs.modal', function (e) {
console.log("hide", e);
return true;
});
}
I am developing a firefox extension with the Mozilla SDK. The situation is:
I want a toggle/action button to show/hide the extension's panel.
My code:
// the panel
let panel = require("sdk/panel").Panel({
// ...
onHide: handleHide
});
// the button
let button = ToggleButton({
// ...
// will be executed, when user clicks the button
onChange: handleChange
});
// event handlers
function handleChange(state) {
// state.checked is always true
if (???) {
panel.show();
}
}
function handleHide() {
// un-check the button
button.state('window', {checked: false});
}
The problem is, that inside of handleChange, where the toggling logic should be, i can't tell if the panel is supposed to show or hide. In the docs theres an example which uses state.checked, but since this code is run while i am clicking on the button, the state.checked is always true. In the end, the toggling does not work this way, because the panel never gets hidden when clicking the "toggle"-button.
help much appreaciated, i already tried so many things.. mothings works.
thanks in advance!
simply use console.log to see what's inside the state:
{
"disabled": false,
"checked": false,
"label": "the default label",
"icon": "./icon.png",
"id": "show-panel"
}
so we can go on like this:
if(state.checked)
{
myExt.show();
return false;
} else {
myExt.hide();
return false;
}
in older versions you need to return false.
So, a few users are experiencing their browser window minimizing when they click save on this modal window. For those users, it's consistent, but it's just a small number of users. Most people are using IE9, and everyone that has this problem is using IE9. It happens on the .dialog('close'); call, and it minimizes before it reaches the close function. Does anyone have any ideas?
$("#new-specified").dialog($.extend({}, ns.modalOptions, {
open: function () {
if (ns.disabled) {
$(this).dialog("close");
}
$(this).dialog("option", "title", app.viewModels.MissionViewModel.EditingSpecified() ? "EDIT SPECIFIED TASK" : "NEW SPECIFIED TASK");
$(this).parent().find("button:contains('Cancel')").removeClass().addClass("cancel-button");
$(this).parent().find("button:contains('SAVE')").removeClass().addClass("save-button");
app.viewModels.MissionViewModel.CurrentSpecified().TempDescription(app.functions.htmlUnescape(app.viewModels.MissionViewModel.CurrentSpecified().Description()));
if (app.viewModels.MissionViewModel.SpecifiedTasks().length === 0) {
app.viewModels.MissionViewModel.CurrentSpecified().IsMainEffort(true);
}
},
buttons: {
"Cancel": function() {
$(this).dialog("close");
},
"SAVE": function () {
var newSpecified = app.viewModels.MissionViewModel.CurrentSpecified();
newSpecified.Description(app.functions.htmlEscape(newSpecified.TempDescription()));
newSpecified.Validate();
if (newSpecified.IsInError()) {
return;
}
if (!app.viewModels.MissionViewModel.EditingSpecified()) {
app.viewModels.MissionViewModel.SpecifiedTasks.push(newSpecified);
} else {
app.viewModels.MissionViewModel.OldSpecified().CopyFrom(newSpecified);
newSpecified = app.viewModels.MissionViewModel.OldSpecified();
}
app.viewModels.MissionViewModel.CurrentSpecified(new app.models.SpecifiedTaskViewModel());
var isMainEffort = newSpecified.IsMainEffort();
var index = isMainEffort ? app.viewModels.MissionViewModel.SpecifiedTasks().indexOf(newSpecified) : -1;
app.viewModels.MissionViewModel.VerifyMainEffort(index);
ns.setupSpecifiedModal();
//VV This line below minimizes
$(this).dialog("close");
ns.setupDroppable();
}
},
close: function() {
// We don't reach here before the window minimizes
app.viewModels.MissionViewModel.CurrentSpecified(new app.models.SpecifiedTaskViewModel());
app.viewModels.MissionViewModel.EditingSpecified(false);
app.viewModels.MissionViewModel.VerifyMainEffort(-1);
ns.saveMissionToServer();
}
}));
This question might have the solution: Dynamically loading jQuery mobile causes IE to minimize
Seems like calling blur() forces the IE window to the back of the stack. If you're using jQuery Mobile, here is the related issue: https://github.com/jquery/jquery-mobile/issues/2057
If you're not using jQuery Mobile, look for calls to blur() in your code.
This was also found in jquery-UI 1.10.0: http://bugs.jqueryui.com/ticket/9420.
As first comment suggest, the cause is that blur() is called on document.body.
As a workaround you can override body.blur(), look at How do I disable body.blur() in IE8?