Showing multiple modals in a loop - javascript

I'm using Angular UI Modals (http://angular-ui.github.io/bootstrap/#modal)
I have to show the same modal, multiple times in a loop.
For example I have a team, and I need to show the edit team member modal for each person in succession (basically it's a wizard)
The problem I have is that it opens ALL the modals, stacked on top of each other, and I'd like to get them to open one at a time.
for (var i = 0; i < team.SizeLimit; i++) {
var participant = { }; // assume this hold the correct participant
var openModal = $modal.open({
templateUrl: '/Modals/Participant/Edit.html',
backdrop: true,
windowClass: 'modal',
controller: 'ModalParticipantController',
resolve: {
title: function () { return 'Edit Participant'; },
participant: function () { return participant; }
}
});
openModal.result.then(function (data) {
alert('success!');
});
}
How can I get it to open these in succession, as the modals are closed, one at a time?

I'm not an angularJs user, but i had this same problem a while back.
instead of creating the modals in a loop, you create a loop with the modals itself where you check the next participant when you close the current modal. (i'm guessing openModal.result.then() is the callback for that)
var fnOpenModal = function(i) {
if (i >= team.sizeLimit)
return;
var participant = team[i],
openModal = $modal.open({
....
});
openModal.result.then(function (data) {
alert('success!');
fnOpenModal(i + 1);
});
}
fnOpenModal(0);
it's definetly not the best solution, but it's a quick one that works :-)

Related

Angular modal close function not executing

The loading modal is created correctly, but when the finally block is hit it does not close it. Is there any known reason for this? The loading time is minimal but I still need it for cases where there is a delay. I am testing with a device and in Chrome - The issue only arises when it is being run in Chrome.
$scope.init = function() {
var dialog = Modals.openLoadingModal();
OfflineManager.getTemplates().then(function(templates) {
$scope.templates = templates.map(function(e) {
// get e
return e;
});
OfflineManager.getInspections().then(function(inspections) {
$scope.inspections = inspections.map(function(e) {
// get e
return e;
});
}).finally(function() {
dialog.close();
});
});
};
The modal view:
<div class="loadingModal">
<data-spinner data-ng-init="config={color:'#fff', lines:8}" data-config="config"></spinner>
</div>
The modal service:
this.openLoadingModal = function(callback) {
var opts = {
backdrop: true,
backdropClick: false,
keyboard: false,
templateUrl: 'views/modals/loading.html'
};
return this.open(opts, callback, null);
};
this.open = function(opts, closeHandler, dismissHandler, model) {
opts.resolve = { modalModel:function() { return model; }};
opts.controller = opts.controller || 'ModalController';
$('div, input, textarea, select, button').attr('tabindex', -1);
var modalInstance = $modal.open(opts);
modalInstance.result.then(function(result) {
$('div, input, textarea, select, button').removeAttr('tabindex');
if (closeHandler) {
closeHandler(result);
}
}, function(result) {
$('div, input, textarea, select, button').removeAttr('tabindex');
if (dismissHandler) {
dismissHandler(result);
}
});
return modalInstance;
};
After some searching I found the following solution which waits until the modal has finished opening before executing:
.finally(function() {
dialog.opened.then(function() {
dialog.close();
});
});
Source:
Call function after modal loads angularjs ui bootstrap
Per the ui.bootstrap docs - http://angular-ui.github.io/bootstrap/versioned-docs/0.13.3/#/modal
result - a promise that is resolved when a modal is closed and rejected when a modal is dismissed
It looks like you're trying to use the wrong promise to execute your logic. result gets triggered as a product of calling $modalInstance.close or $modalInstance.dismiss. If you're trying to close your modal programmatically (as opposed to closing via ng-click within the modal template/controller) you need to call $modalInstance.close or $modalInstance.dismiss directly, then your result.then will execute.

angular watch causing looping Iligal invocation error

I have a modal dialog where the user can select files to be uploaded. The actual file select/upload is handled by ng-file-upload. When the user selects one or more file, they are added to a list in the dialog, showing progress, completion and failure statuses for each element. The list of items are handled inside a custom directive, since it's used other places as well.
I need to prevent the user from dismissing the dialog while files are still uploading, and that's a challenge for me, cause the button for closing the dialog is in one controller, while the list of uploads is in another (the directive controller). I have solved that by giving and empty list to the directive like this:
//extract from directive:
var directive = {
...
scope: {
'files': '='
}
}
//extract from usage
<uploadFiles files="files" />
Now the outer controller and the inner controller shares the list of files uploading.
So when the user tries to dismiss the dialog by clicking the Close button, I first check if the list contains files still uploading, and if so, I disable the button and display a spinner and a 'please wait'-text.
//from the outer controller
function onModalOk() {
if (uploadInProgress()) {
waitForCompletionBeforeClosingDialog();
} else {
closeDialog();
}
}
the waitForCompletionBeforeClosingDialog() is implemented by setting up a deep watch on the files array. Each time the watch is triggered, I loop through to see if every file has completed. If so, I delete the watch and dismiss the dialog.
function waitForCompletionBeforeClosingDialog() {
$scope.showWaitText = true;
var unregisterWatchForCompletion = $scope.$watch('files', function(files) {
if (allCompleted(files)) {
unregisterWatchForCompletion();
closeDialog();
}
}, true);
}
Everything is working ok, except for one little thing...
In the console, I get this error:
TypeError: Illegal invocation
at equals (angular.js:931)
at equals (angular.js:916)
at Scope.$digest (angular.js:14302)
at Scope.$apply (angular.js:14571)
at angular.js:16308
at completeOutstandingRequest (angular.js:4924)
at angular.js:5312
and it's fired in a tight loop.
I have tried debugging this error, but with no luck..
Do anyone have any ideas?
Is there better ways of doing this all together?
What about using an $httpInterceptor to keep count of the amount of active requests?
something like:
angular.module('someModule').provider('httpStatus', ['$httpProvider', function ($httpProvider) {
var currentRequestCount = 0;
var interceptor = ['$q', function ($q) {
return {
request: function (config) {
currentRequestCount++;
return config;
},
response: function (response) {
currentRequestCount--;
return response;
},
responseError: function (rejection) {
currentRequestCount--;
return $q.reject(rejection);
}
}
}];
$httpProvider.interceptors.push(interceptor);
this.$get = function () {
return {
isWaiting: function () {
return currentRequestLength > 0;
}
}
};
}]);
You could inject the httpStatus service into your dialog and use it to disable the buttons if there are any active requests. May need to add the requestError handler also.
https://docs.angularjs.org/api/ng/service/$http

Angularjs Bootstrap modal closing call when clicking outside/esc

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

Angular JS dismissing $modalInstance: undefined is not a function

Writing a test to test my delete functionality for my app. I created a mock delete $modal to simulate cancelling/confirming deletion.
var modalInstanceMock=
{
result: {
then: function(confirmCallback, cancelCallback) {
//Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog
this.confirmCallBack = confirmCallback;
this.cancelCallback = cancelCallback;
}
},
confirmCallBack: function(item){
return true;
},
cancelCallback: function(type){
return false;
},
close: function( item ) {
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
this.result.confirmCallBack( item );
},
dismiss: function( type ) {
//The user clicked cancel on the modal dialog, call the stored cancel callback
this.result.cancelCallback( type );
}
};
I do this before each test:
beforeEach(inject(function($modal) {
spyOn($modal, 'open').andReturn(modalInstanceMock);
}));
This works perfectly:
var newRes = scope.deleteCar(car);
scope.modalInstance.close("ok");
However when I try this:
var newRes = scope.deleteCar(car);
scope.modalInstance.dismiss("ok");
I get a Type:error undefined is not a function at Object.modalInstanceMock.dismiss.
Can't understand what is going wrong when close works fine.
Initilize in beforeEach,
modalInstance = {
close: jasmine.createSpy('modalInstance.close'),
dismiss: jasmine.createSpy('modalInstance.dismiss')
},
and then expect.

Implementing Amazon Wishlist Like Undo in AngularJS

I'm thinking of implementing an "Amazon Wishlist-ish" undo functionality to my app.
I mean...
then click 'Delete Item'
by clicking 'Undo', the deletion appears to be canceled
My list controller is currently looking like this,
function ListsController($scope, List) {↲
List.get({}, function(lists) {
$scope.lists = lists.objects;
$scope.delete_list = function(index) {
var isConfirmed = confirm('Are you sure you want to delete it?');
if (isConfirmed) {
var targetlist = $scope.lists[index];
List.delete({ listId: targetlist.id },
function(list) {
$scope.lists.splice(index, 1);
}
)
}
}
});
};
But I wanna enable undo feature as I said.
What is the best way to do that in angular js?
I can't give an exact example without seeing what the List service does, but I think your best bet is to keep track of removed items in another scope variable and if the undo is clicked you just add it back to lists.
Maybe something like:
$scope.deleted_lists = [];
$scope.delete_list = function(index) {
var isConfirmed = confirm('Are you sure you want to delete it?');
if (isConfirmed) {
var targetlist = $scope.lists[index];
List.delete({ listId: targetlist.id }, function(list) {
$scope.deleted_lists.concat($scope.lists.splice(index, 1));
});
}
};
Then you can use an ng-repeat (if you want multiple undos) to display the deleted items and a click on the undo button could simply add the item back to the list via .push().

Categories

Resources