Angular modal close function not executing - javascript

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.

Related

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.

Showing multiple modals in a loop

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

KnockoutJS/Bootstrap - Clearing modal form when closing modal using javascript

I have a Bootstrap Modal that contains a form for updating or creating an entity (Company in my example). Right now my issue is that if I view an entity using the modal, it doesn't clear out the fields when I close the modal by any means. Causing the form to still be populated if I then click a "Create" button, which should bring me up a blank modal.
How can I execute one of my ViewModels methods from just regular javascript? Here is some of my code:
function ViewModel() {
var self = this;
function CompanyViewModel(company) {
var self = this;
self.Id = company.CompanyId;
self.Name = company.Name;
}
function BlankCompanyViewModel() {
var self = this;
self.Id = 0;
self.Name = "";
}
self.company = ko.observable();
self.companies = ko.observableArray();
self.clearCurrentCompany = function() {
self.company(new BlankCompanyViewModel());
};
// Initialize the view-model
$.getJSON("/api/company", function(companies) {
$.each(companies, function(index, company) {
self.companies.push(new CompanyViewModel(company));
});
self.clearCurrentCompany();
});
}
Ideally I'd like to run ViewModel.clearCurrentCompany on the "Hidden" event of the modal like so:
$('#myModal').on('hidden', function() {
//Do something here, not sure what
});
I like to use a custom binding around a modal to make it open/close/display based on populating/clearing an observable.
Something like:
ko.bindingHandlers.modal = {
init: function(element, valueAccessor, allBindings, vm, context) {
var modal = valueAccessor();
//init the modal and make sure that we clear the observable no matter how the modal is closed
$(element).modal({ show: false, backdrop: 'static' }).on("hidden.bs.modal", function() {
if (ko.isWriteableObservable(modal)) {
modal(null);
}
});
//apply the template binding to this element
ko.applyBindingsToNode(element, { with: modal }, context);
return { controlsDescendantBindings: true };
},
update: function(element, valueAccessor) {
var data = ko.utils.unwrapObservable(valueAccessor());
//show or hide the modal depending on whether the associated data is populated
$(element).modal(data ? "show" : "hide");
}
};
You then use this against an observable. It acts like a with binding against that observable and shows/hides the modal based on whether the observable is populated.
Here is a sample that shows this in use and sets up a subscription where you could run custom code when the modal is closed. http://jsfiddle.net/rniemeyer/uf3DF/
function ViewModel() {
var self = this;
// your previous code
$('#myModal').on('hide', function() {
self.clearCurrentCompany();
});
}
Just like that. Note that you want hide, not hidden, because hidden fires only after the modal completely disappears. If a user opens a create before the previous view closes, it will still be populated.

Backbone.Marionette: Defer view close until beforeClose animation is complete

I'm trying to set animations on rendering and closing an ItemView with Backbone.Marionette. For rendering a view, this is fairly simple:
MyItemView = Backbone.Marionette.View.extend({
...
onRender: function() {
this.$el.hide().fadeIn();
}
...
});
This will have my view fade in when I render it. But let's say I want to fade out my view upon close.
beforeClose: function() {
this.$el.fadeOut(); // doesn't do anything....
}
This won't work, because the item closes immediately after calling this.beforeClose(), so the animation doesn't have time to complete.
Is there any way, using Marionette as it stands, to accomplish a closing animation?
Alternatively, this is the workaround I've been using:
_.extend(Backbone.Marionette.ItemView.prototype, {
close: function(callback) {
if (this.beforeClose) {
// if beforeClose returns false, wait for beforeClose to resolve before closing
// Before close calls `run` parameter to continue with closing element
var dfd = $.Deferred(), run = dfd.resolve, self = this;
if(this.beforeClose(run) === false) {
dfd.done(function() {
self._closeView(); // call _closeView, making sure our context is still `this`
});
return true;
}
}
// Run close immediately if beforeClose does not return false
this._closeView();
},
// The standard ItemView.close method.
_closeView: function() {
this.remove();
if (this.onClose) { this.onClose(); }
this.trigger('close');
this.unbindAll();
this.unbind();
}
});
Now I can do this:
beforeClose: function(run) {
this.$el.fadeOut(run); // continue closing view after fadeOut is complete
return false;
},
I'm new to using Marionette, so I'm not sure if this is the best solution. If this is the best way, I'll submit a pull request, though I'll want to put a bit more thought into how this could work with other types of views.
This could potentially be used for other purposes, such as asking for confirmation on close (see this issue), or running any kind of asynchronous request.
Thoughts?
Overriding the close method is the one way to do this, but you can write it bit shorter, as you can call the Marionettes close method instead of duplicating it:
_.extend(Backbone.Marionette.ItemView.prototype, {
close: function(callback) {
var close = Backbone.Marionette.Region.prototype.close;
if (this.beforeClose) {
// if beforeClose returns false, wait for beforeClose to resolve before closing
// Before close calls `run` parameter to continue with closing element
var dfd = $.Deferred(), run = dfd.resolve, self = this;
if(this.beforeClose(run) === false) {
dfd.done(function() {
close.call(self);
});
return true;
}
}
// Run close immediately if beforeClose does not return false
close.call(this);
},
});
Another idea is to overide the remove method of your view. So you fade out the element of the view and then remove it from the DOM
remove: function(){
this.$el.fadeOut(function(){
$(this).remove();
});
}

Categories

Resources