Confirm angular modal closing on dirty form - javascript

I have an Angular-UI modal with a form in it. When the user triggers the dismiss event I want to implement a confirmation based on $dirty. I have searched through numerous sources to find notions on Promise and can succesfully get e.g. an alert during the closing event. However, I can't find anywhere how to actually stop the modal from closing.
EDIT:
With the current code the confirmation alert often (surprisingly not always) pops up after the modal has already been dismissed.
var editResourceModalController = function($scope, $uibModalInstance) {
$uibModalInstance.result.catch(function() {
if ($scope.editForm.$dirty) {
window.confirm("close modal?");
}
$uibModalInstance.dismiss('cancel');
});
}
var uibModalInstance;
$scope.openEditModal = function() {
uibModalInstance = $uibModal.open({
animation: true,
templateUrl: "edit.html",
controller: editResourceModalController
});
}

Add the $scope.ok method and hook it to the editForm's submit button's ng-click
var editResourceModalController = function($scope, editItem, hierarchy, selectedFolder) {
$scope.form = {};
$scope.editItem = editItem;
$scope.editListItems = [];
$scope.listItems = 0;
$scope.getNumber = function(n) {
return new Array(n);
}
$scope.hierarchy = hierarchy;
$scope.selectedFolder = selectedFolder;
$scope.editModel = {
name: $scope.editItem.name,
description: $scope.editItem.description,
hierarchyId: $scope.selectedFolder
}
$scope.ok = function () {
editItem.close($scope.editForm.$dirty);
};
}
Inject the $scope.edeitForm.$dirty as isDirty and use the injected value as you like
$scope.openEditModal = function(editItem, hierarchy, selectedFolder) {
$scope.modalInstance = $uibModal.open({
animation: true,
templateUrl: "edit.html",
controller: ["$scope", "editItem", "hierarchy", "selectedFolder", editResourceModalController],
resolve: {
editItem: function() {
return editItem;
},
hierarchy: function() {
return hierarchy;
},
selectedFolder: function() {
return selectedFolder;
}
}
});
$scope.modalInstance.result.catch(function(isDirty) {
if (isDirty) {
// confirmation code here
}else{
// other logic
}
// dismiss the modal
editItem.dismiss('cancel');
});
}
Hope this helped you :D

I fixed it using $scope.$on, extensive example here
var editResourceModalController = function($scope, $uibModalInstance) {
$scope.close = function() {
$uibModalInstance.close();
}
$scope.$on('modal.closing', function(event) {
if ($scope.editForm.$dirty) {
if (!confirm("U sure bwah?")) {
event.preventDefault();
}
}
});
}
var uibModalInstance;
$scope.openEditModal = function(editItem, hierarchy, selectedFolder) {
uibModalInstance = $uibModal.open({
animation: true,
templateUrl: "edit.html",
controller: editResourceModalController
});
}

This solution works for me.
Esc, X button on top and Close button at the bottom.
function cancel() {
if (vm.modalForm.$dirty) {
var response = DevExpress.ui.dialog.confirm("You have unsaved changes. Would you like to discard them?");
response.done(function (result) {
if (result)
vm.dismiss({ $value: 'cancel' });
});
}
else
vm.dismiss({ $value: 'cancel' });
}
$scope.$on('modal.closing', function (event, reason) {
if (reason === 'escape key press') {
var message;
if (vm.modalForm.$dirty) {
message = "You have unsaved changes. Would you like to discard them?";
if (!confirm(message)) {
event.preventDefault();
}
}
}
});

Related

Using resolve to wait for RESTful results in angularjs $modal

I'm working on some legacy code that uses angularjs 1.x for a web frontend. I need to create a modal dialog that will make a RESTful call to the backend when the modal is opened and wait for the data to be returned before rendering the view.
I was able to figure out most of what I needed to do, but there is one thing I still can't wrap my head around. My understanding was that I needed to use 'resolve' to define a function that would return a $promise to the controller. When I put a breakpoint inside my controller though, the parameter is an object containing the promise, the resolution status, and finally my actual data.
I can pull the data I need out of this object, but it feels like I shouldn't have to do that. My controller doesn't care about the promise itself; just the data that got returned. Is there some way to structure this so only the data gets sent to the controller or is this just how angular modals are expected to behave?
A sample of my code:
$scope.openTerritorySelect = function () {
var modalInstance = $modal.open({
animation: true,
templateUrl: 'prospect/detail/selectTerritoriesModal.tpl.html',
controller: function($scope, $modalInstance, availableReps){
$scope.reps = availableReps;
$scope.ok=function()
{
$modalInstance.close();
};
$scope.cancel=function()
{
$modalInstance.dismiss('cancel');
};
},
resolve: {
availableReps: function () {
return Prospect.getRelatedReps({}, function (data, header) {
$scope.busy = false;
return data.result;
}, function (response) {
$scope.busy = false;
if (response.status === 404) {
$rootScope.navError = "Could not get reps";
$location.path("/naverror");
}
}).$promise;
}
}
});
modalInstance.result.then(function (selectedReps) {
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
};
The 'Prospect' service class:
angular.module('customer.prospect', [ "ngResource" ]).factory('Prospect', [ 'contextRoute', '$resource', function(contextRoute, $resource) {
return {
getRelatedReps : function(args, success, fail) {
return this.payload.getRelatedReps(args, success, fail);
},
payload : $resource(contextRoute + '/api/v1/prospects/:id', {
}, {
'getRelatedReps' : {
url : contextRoute + '/api/v1/prospects/territories/reps',
method : 'GET',
isArray : false
}
})
};
} ]);
You could simplify things a great deal by making the REST request before you even open the modal. Would you even want to open the modal if the request were to fail?
$scope.openTerritorySelect = function () {
Prospect.getRelatedReps({}, function (data, header) {
$scope.busy = false;
var modalInstance = $modal.open({
animation: true,
templateUrl: 'prospect/detail/selectTerritoriesModal.tpl.html',
controller: function($scope, $modalInstance, availableReps){
$scope.reps = availableReps;
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
},
resolve: {
availableReps: function () {
return data.result;
}
});
modalInstance.result.then(function (selectedReps) {},
function () {
console.log('Modal dismissed at: ' + new Date());
});
}, function (response) {
$scope.busy = false;
if (response.status === 404) {
$rootScope.navError = "Could not get reps";
$location.path("/naverror");
}
});
};

bootstrap modal not close angular js

I am using UI bootstrap modal dialog box with angular js. Modal is successfully loaded. But when I click YES/NO Button, issued occurred & modal did not close.
Error said, ' $uibModal.close is not a function'.
.directive('confirm', function(ConfirmService) {
return {
restrict: 'A',
scope: {
eventHandler: '&ngClick'
},
link: function(scope, element, attrs){
element.unbind("click");
element.bind("click", function(e) {
ConfirmService.open(attrs.confirm, scope.eventHandler);
});
}
}
})
This is my service
.service('ConfirmService', function($uibModal) {
var service = {};
service.open = function (text, onOk) {
var modalInstance = $uibModal.open({
templateUrl: 'modules/confirmation-box/confirmation-box.html',
controller: 'userListCtrl',
resolve: {
text: function () {
return text;
}
}
});
modalInstance.result.then(function (selectedItem) {
onOk();
}, function () {
});
};
return service;
})
This is my controller file. I am trying to yes/no button inside the controller
.controller('userListCtrl',
['$scope','$http','appConfig','$uibModalInstance', '$uibModal','$log','alertService',
function ($scope,$http, appConfig,$uibModalInstance, $uibModal,$log,alertService) {
$scope.ok = function () {
$uibModalInstance.close();
};
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
};
}]);
You're attempting to use two usage methods at once time. There are two (probably more) that you can use the $uibModal, but here are the two that I believe you're intermixing:
1) Service controls the modal and returns a promise, I believe this is what I think you're doing. You do not need to call close/dismiss manually in this instance. You can make the following changes:
service.open = function(text, onOK) {
var modalInstance = $uibModal.open({
templateUrl: 'modules/confirmation-box/confirmation-box.html',
controller: 'userListCtrl',
resolve: {
text: function () {
return text;
}
}
});
// Return so you can chain .then just in case. Generally we don't even
// do this, we just return the instance itself and allow the controller to
// decide how to handle results/rejections
return modalInstance.result;
}
In your template file you'd have something like:
<button type="button" ng-click="$close(selectedItem)"></button>
<button type="button" ng-click="$dismiss(readon)"></button>
2) If you want to use the close method directly, then you only need to change the service to:
...
return $uibModal.open({});
then in your controller:
var modal = service.open('confirm');
modal.result.then(...)
modal.close()
Edit - updated with change to op to remove the antipattern as per georgeawg suggestion.

angular multiple $mdDialog

I work with modal tabs and I have notification pop-up window which is always shown to user when he logs into my application. It contains all events which happends when user was offline. Problem is when i click on any objects from list it close my pop-up window and display new modal tab.
I want to archieve this functionality. When user log in, notification pop-up window will be shown to user and if he clicks on any object it will open another window without closing my notification pop-up window(New events). I want something like that on picture below which I made.
I checked angular material documentation, but there is no demo at all and not even well explained how to work with multiple: true option and I dont know exactly how to make it work like I want.
https://material.angularjs.org/latest/api/service/$mdDialog
This is my code for displaying notification pop-up window.
//show new notifications when user log in
NotificationService.getUnreadedNotifications(function (data) {
//initialization
$scope.notification = [];
$scope.OverAllCount = 0;
$scope.messageNotification = [];
$scope.OverAllMessageCount = 0;
if (data.ProjectNotifications != null) {
angular.forEach(data.ProjectNotifications, function (key, value) {
$scope.notification.push(key);
$scope.OverAllCount = $scope.OverAllCount + 1;
});
}
if (data.TasksNotifications != null) {
angular.forEach(data.TasksNotifications, function (key, value) {
$scope.notification.push(key);
$scope.OverAllCount = $scope.OverAllCount + 1;
});
}
if (data.MessageNotifications != null) {
angular.forEach(data.MessageNotifications, function (key, value) {
$scope.OverAllMessageCount = $scope.OverAllMessageCount + 1;
$scope.messageNotification.push(key);
});
}
popUpNotification();
$scope.hide = function () {
$mdDialog.hide();
};
$scope.cancel = function () {
$mdDialog.cancel();
};
$scope.answer = function (answer) {
$mdDialog.hide(answer);
};
//mark notifications as readed when user click on notification
function popUpNotification() {
$mdDialog.show({
controller: NotificationController,
templateUrl: 'app/components/templates/PopUpNotification.html',
parent: angular.element(document.body),
//targetEvent: ev,
clickOutsideToClose: true,
fullscreen: false,
scope: $scope,
multiple:true,
preserveScope: true,
onComplete: function () {
$scope.notificationPopUp = $scope.notification;
}
})
.then(function () {
}, function () {
//fail
});
}
});
This is code for displaying details of object on which user clicked in new overlaying modal tab
//mark notifications as readed when user click on notification
$scope.popUpDetail = function (notification, index, ev) {
$mdDialog.show({
controller: NotificationController,
templateUrl: 'app/components/templates/TaskDetailsDialog.html',
parent: angular.element(document.body),
targetEvent: ev,
clickOutsideToClose: true,
fullscreen: false,
scope: $scope,
multiple: true,
preserveScope: true,
onComplete: function () {
//was readed => update database
NotificationResourceService.update({ id: notification.Id }, notification);
$scope.OverAllCount -= 1;
$scope.notification.splice(index, 1);
TaskService.get({ id: notification.EntityId })
.$promise.then(function (task) {
$scope.task = task;
});
}
})
.then(function () {
}, function () {
//fail
});
}
Somehow i found working solution for my problem. It might help somebody in future.
Working code:
function popUpNotification() {
$mdDialog.show({
templateUrl: 'app/components/templates/PopUpNotification.html',
clickOutsideToClose: true,
bindToController: true,
scope: $scope,
preserveScope: true,
controller: function ($scope, $mdDialog) {
$scope.notificationPopUp = $scope.notification;
$scope.popUpDetail = function (notification, index, ev) {
$mdDialog.show({
controller: function ($mdDialog) {
this.click = function () {
$mdDialog.hide();
}
},
targetEvent: ev,
clickOutsideToClose: true,
preserveScope: true,
autoWrap: true,
skipHide: true,
scope: $scope,
preserveScope: true,
templateUrl: 'app/components/templates/TaskDetailsDialog.html',
onComplete: function () {
TaskService.get({ id: notification.EntityId })
.$promise.then(function (task) {
$scope.task = task;
});
}
})
}
},
autoWrap: false,
})
}
});
Add 'multiple: true' as a parameter:
// From plain options
$mdDialog.show({
multiple: true
});
// From a dialog preset
$mdDialog.show(
$mdDialog
.alert()
.multiple(true)
);
From documentation: https://material.angularjs.org/latest/api/service/$mdDialog
The key is using skipHide: true as parameter in the object we pass into $mdDialog.show(). I tried without multiple: true and it still works. This parameter has to be passed into the second (or nth) dialog. So it will look somethink like this:
// second dialog
$mdDialog.show({
// some fields
skipHide: true,
//some fields
});

How can I resolve a modal inside another modal using Angular-ui?

I'm using the angular-ui modal directive http://angular-ui.github.io/bootstrap/
I want to call a different modal 'openEditModal' from my 'openAsideModal', how can I achieve this?
$scope.openAsideModal = function(){
if($scope.asideInstance){
$scope.asideInstance.close();
delete $scope.asideInstance;
}else{
$scope.asideInstance = $aside.open({
templateUrl: asideTemplateUrl,
backdrop: false,
controller: function($scope, $modalInstance, userEvents, openEditModal) {
$scope.events = userEvents;
$scope.openEditModal = function(e) {
console.log(e);
};
$scope.ok = function(e) {
$modalInstance.close();
e.stopPropagation();
};
$scope.cancel = function(e) {
$modalInstance.dismiss();
e.stopPropagation();
};
},
placement: 'right',
size: 'sm',
resolve:{
userEvents: function() {
// Return current user tasks
return $filter('filter')($scope.events, {resourceId: currentUserId});
},
openEditModal: function() {
return $scope.openEditModal;
},
}
});
}

how to enable ionic multi-touch events

I 'm developing a simple ionic app, and part of the app requires you to press two buttons at once. I've built this logic like so:
<!--yT stands for yourThumb, pT stands for partnersThumb -->
<a class="icon ion-qr-scanner lg-txt" on-hold="Global.thumbHoldManager('yT',true)" on-release="Global.thumbHoldManager('yT',false, true)"></a>
<a class="icon ion-qr-scanner lg-txt" on-hold="Global.thumbHoldManager('pT',true)" on-release="Global.thumbHoldManager('pT',false, true)"></a>
I have a method on my controller which handles this event using a service I 've created
var globalCtrl = function (clickHandler, $timeout) {
var self = this;
this.clickHandler = clickHandler;
this.timeout = $timeout;
this.readyState = clickHandler.ready;
this.showInstruction = false;
clickHandler.watchForReady();
};
globalCtrl.prototype.thumbHoldManager = function(which, what, up) {
this.clickHandler.setClickState(which, what);
var self = this;
if (up) {
this.clickHandler.stopWatching();
}
if (!this.readyState) {
this.instruction = "Hold both thumbs in place to scan"
if (!this.showInstruction) {
this.showInstruction = true;
self.timeout(function() {
self.showInstruction = false;
}, 5000)
}
}
};
globalCtrl.$inject = ['clickHandler', '$timeout'];
The service clickHandler exposes an api to a private object whose job it is to track when a button is pressed, and when both buttons are pressed to navigate to a new url.
.factory('clickHandler', [
'$interval',
'$rootScope',
'$location',
function($interval, $rootScope, $location) {
// Service logic
// ...
var clickState = {
yT: false,
pT: false,
ready: false,
watching: false,
watcher: false
};
// Public API here
return {
setClickState: function(which, what) {
clickState[which] = what;
},
getClickState: function(which) {
return clickState[which]
},
getReadyState: function() {
return ((clickState.yT) && (clickState.pT));
},
watchForReady: function() {
var self = this;
clickState.watching = $interval(function() {
clickState.ready = self.getReadyState();
},50);
clickState.watcher = $rootScope.$watch(function() {
return clickState.ready
}, function redirect(newValue) {
if (newValue) {
self.stopWatching();
$location.path('/scan');
}
})
},
stopWatching: function() {
if (clickState.watching) {
$interval.cancel(clickState.watching);
clickState.watcher();
clickState.watching = false;
clickState.watcher = false;
}
}
};
}
])
I don't get any errors with this code, everything works as it should, the watcher gets registered on the hold event and unregistered on the release event. But no matter what I do, I cannot seem to get my phone to detect a press on both buttons. It's always one or the other and I don't know why. I can't test this in the browser or the emulator since multi-touch is not supported and I don't have a multi-touch trackpad if it were.
Here's how I implemented my own directive and service to do this:
.factory('clickHandler', ['$interval', '$rootScope', '$location', '$document', function ($interval, $rootScope, $location, $document) {
// Service logic
// ...
$document = $document[0];
var
touchStart,
touchEnd;
touchStart = ('ontouchstart' in $document.documentElement) ? 'touchstart' : 'mousedown';
touchEnd = ('ontouchend' in $document.documentElement) ? 'touchend' : 'mouseup';
var clickState = {
yT: false,
pT: false,
ready: false,
watching: false,
watcher: false,
startEvent: touchStart,
endEvent: touchEnd
};
// Public API here
return {
setClickState: function (which, what) {
clickState[which] = what;
},
getClickState: function (which) {
return clickState[which]
},
getReadyState: function () {
return ( (clickState.yT) && (clickState.pT) );
},
watchForReady: function () {
var self = this;
//prevent multiple redundant watchers
if (clickState.watching) {
return;
}
clickState.watching = $interval(function () {
clickState.ready = self.getReadyState();
}, 50);
clickState.watcher = $rootScope.$watch(function () {
return clickState.ready
}, function redirect(newValue) {
if (newValue) {
self.stopWatching();
$location.path('/scan');
}
})
},
stopWatching: function () {
if (clickState.watching) {
$interval.cancel(clickState.watching);
clickState.watcher();
clickState.watching = false;
clickState.watcher = false;
}
},
getTouchEvents: function () {
return {
start: clickState.startEvent,
end: clickState.endEvent
}
}
};
}])
.directive('simultaneousTouch', ['clickHandler', '$document', function (clickHandler) {
return {
restrict: 'A',
link: function (scope, elem, attr) {
var touchEvents = clickHandler.getTouchEvents();
elem.on(touchEvents.start, function () {
clickHandler.watchForReady();
clickHandler.setClickState(attr.simultaneousTouch, true);
});
elem.on(touchEvents.end, function () {
clickHandler.stopWatching();
clickHandler.setClickState(attr.simultaneousTouch, false);
})
}
}
}]);
Crossposting stankugo's answer from the ionic forums for the sake of reference. The simple solution below is entirely his idea, I've just done a little cleanup.
angular.module('xxxx').directive('multitouch', function () {
return function(scope, element, attr) {
element.on('touchstart', function() {
scope.$apply(function() {
scope.$eval(attr.multitouch);
});
});
};
});
Use like:
<div multitouch="handler()"></div>

Categories

Resources