I have to implement a customized close dialog function for close button of ngDialog.
As per requirement in some cases (where there is a form) I have to show another ngDialog confirm popup asking if user really want to close the dialog or not so there are 2 options 'YES' and 'NO' which has this behavior.
I have tried it with preCloseCallback() method but somehow it did not worked for me as it does not wait for user confirmation. It is just like the function called on click of close and dialog closed or stays open depending on what I return from function immediately. If I don't return anything it assumes it to be true and closes the dialog.
Can anybody please let me know the way to solve this issue?
Here comes the nice solutions! It's bit hacky but worked perfectly for my case.
Step 1
Set showClose option false while opening dialog.
// In controller
PopUpFactory.openModal('SOME_NAME','SOME_URL.html', 'ngdialog-theme-default SOME_EXTRA_CSS', $scope, function(value){
console.log("Done:", value);
},'SOME_CONTROLLER',false); // false passes to set **showClose** option false
// In common Factory
function openModal(name, templateUrl, classes, scope, callback, ctrl, showClose){
if(showClose === undefined){
showClose = true;
}
ngDialog.openConfirm({
name: name,
controller: ctrl,
template: templateUrl,
className: classes,
closeByDocument: false, // to prevent popup close by clicking outside
closeByEscape: false, // to prevent popup close by ESC key
closeByNavigation : true, // to close popup on state navigation
scope: scope,
disableAnimation: true,
showClose: showClose,
preCloseCallback: function(value) {
return true;
}
}).then(function (value) {
callback(value);
});
}
Step 2
Write common close button handling function
// In Common Factory
/**
* Customize close for any open modal form
* #param isDirty - flag saying if form is dirty
* #param $scope - scope object of current open form
* #param $event - $event object passed from close button of open form
*/
var closeConfirmOpen = false;
function closeForm(isDirty,$scope,$event){
// following lines are important to prevent default behavior of ngDialog close event
if($event){
$event.preventDefault();
$event.stopPropagation();
}
if(isDirty){
var msg = $filter('translate')('navigateAwayWithoutSavingConfirmMsg');
closeConfirmOpen = true;
confirmPopUp('Warning', msg, null, 'leavePage', 'red', 'stayOnPage', function(isOK){
if(isOK == 1){
$scope.closeThisDialog();
}
closeConfirmOpen = false;
});
}else{
$scope.closeThisDialog();
}
}
Step 3
Write a close function in controller to call factory function
/**
* Close sample location modal
*/
$scope.closeForm = function($event){
PopUpFactory.closeForm($scope.formName.$dirty,$scope,$event);
}
Step 4
Add following line after defining header/title for HTML of ngDialog
<div id="SOME_ID" class="ngdialog-close" ng-click="closeForm($event)"></div>
Yooo... done the job...!!!
The best part of this solutions is a common code for closing any form, so once you done with factory function, you only need to add close button wherever required in HTML and add simple close function in controller
Related
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
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;
});
}
Is there anyway to call a function when for example pressing a close button on a modal window that will take different action depending on the function that opened the modal window?
So say for example we had a landing page with items to click on that showed a image of that item in a modal window and a certain function was called when the image was opened from this context and we had a search side nav-bar that displayed items and when these were clicked the function that opened the modal windows was different from the first. Now when closing the modal window, and depending on the function that was called to open the modal, I would like to write a condition that would allow me to either go back to landing page or return to side nav-bar.
I don't have any code to show, but I was wondering if such a thing is possible; writing a condition based on the function that was previously called? What would be the command for that condition?
So
function 1 () {
doSomething;
}
function 2 () {
doAnotherThing;
}
$("closeButton").on('click', function () {
if (function 1 was called) {
// do something else
} else if (function2 was called) {
// do another thing
}
}
Could something like that be possible?
var fnClicked = null
function fn1() {
fnClicked = fn1;
doSomething();
}
function fn2() {
fnClicked = fn2;
doAnotherThing();
}
$('closeButton').on('click', function(){
if (fnClicked === fn1) {
//do something else
} else if (fnClicked === fn2) {
//do another thing
}
});
Alternatively you could hav fn1 and fn2 unbind the closebutton click event and rebind it to the appropriate followup.
In an MVC framework, you can bind a property to the related view. If not, you can always keep bind state to the window object.
If you also don't want to do that, you can keep the state in the DOM (the close button) as an attribute. For example, a data-attribute.
$("closeButton").on('click', function (e) {
var state = $(e.currentTarget).data("state");
}
You can use data attributes on the modal element to store info that indicates what area the modal was opened from. Then when closing the modal, look in that attribute and decide what to do based on the value stored there when the modal was opened.
Variables can store references to functions in Javascript. So I would have function1 set some internal variable that would be checked when you close the modal:
var calledBy;
function1 () {
calledBy = function1;
//open modal
}
function2 () {
calledBy = function2;
//open modal
}
$("closeButton").on("click", function () {
if(calledBy === function1) {
//...
} else if(calledBy === function2) {
//...
}
});
But as hyperstack pointed out, it's better organization to have one function for opening the modal and pass in an argument. I would have an object for the modal:
var modal = {
//...
calledBy: null,
open: functio (calledBy) {
this.calledBy = calledBy;
}
};
You can use the 'this' special keyword to refer to the object on which a method is being invoked.
EG.
<div class="cval">
test
</div>
<script>
$(".cval").click(function (e) {
e.preventDefault();
alert($(this).attr('class'));
if($(this).attr('class') == 'cval')
//dosomething
else
//dosomething
});
</script>
Interrogating any of the elements attribute(s) for value and then using a conditional to control flow.
I have a login-dialog using a angular-strap modal, which gets invoked by:
scope.authModal = $modal({
template: '/components/login/login.html',
show: false,
scope: scope,
backdrop: 'static'
});
(that code is inside the link function of a login-directive.)
Now, my protractor code looks like this:
it('should perform login properly', function () {
browser.manage().deleteAllCookies();
element(by.model('login.username')).sendKeys('xy123');
element(by.model('login.password')).sendKeys('abz89');
element(by.binding("guiText.loginButton")).click();
browser.waitForAngular();
expect(element(by.id('login.username')).isPresent()).to.eventually.equal(false);
});
In another test above the element(by.id('login.username')).isPresent() has been proved to equal true when the login-dialog is visible.
The problem is, I'm getting Error: timeout of 10000ms exceeded with that test. In the browser I can see, that the credentials are typed in correctly and the button is being clicked. The login modal disappeas and then nothing happens and the browser is eventually running in to that timeout exception after waiting 10 seconds.
I had the same problem and I did below to solve this.
Write this function in your helper file and call this to click on login button in your code. Try to access the button by Id and then pass the id in this function, if not id then update the function as per your need
var clickAndWait= function (btnId) {
var returnVal = false;
browser.wait(function () {
if (!returnVal) {
element(by.id(btnId)).click().then(function () {
returnVal = true;
});
}
return returnVal;
}, 30000);
};
Initially, I had a problem that a click event was firing multiple times, but I have managed to overcome that with a probably over use of unbind() and one() as you'll see in my code below!
What I have here is some code which opens up a universally usable Modal window which I use for various things, including, in some cases a password form.
I don't think you need the HTML so I won't post that.
When a button, or an action causes the window to be required, I call the function like this:
showModalAlert(type, theWidth, theHeight, title, html, confirmThis, denyThis)
The first three variables determine how the window will look, title and html determine the content and confirmThis and denyThis are functions set immediately prior to calling this function and determine what the action should be if this is a confirm window and the confirm or deny buttons are press.
In the case of a security window, the confirm button is replace by a "sign it" button which submits a simple password form and returns a User Id from database. If a User Id is successfully returned, the script programatically presses the confirm button and in turn runs it's function as per the call to the inital opening of the modal window.
My problem is that if an incorrect password is entered, or a user cancels the window and then later without refreshing the browser window, re-enters the password correctly, the confirmThis() function is performed twice (or as many times as the incorrect password/cancel action was performed).
So, clearly, what it is doing is "remembering" the confirmThis function each time.
As I said, initially, the password success function was clicking confirmIt twice, copious use of one() has fixed this, it is now definitely only clicking confirmIt once, but it is still performing the function multiple time.
How can I clear this function and ensure it is only performed once?
The function from which I am calling the modal window looks like this:
$('#saveDelivery').click(function () {
function confirmIt() {
formData = (JSON.stringify($('#delDetail').serializeObject()));
saveData(formData);
$('#saveDelivery').removeClass('centreLoader');
};
showModalAlert('security', '300px', '185px', 'Security!', 'You need to "Sign" this action.', confirmIt, '');
});
It's simply a click on the saveDelivery element, the confirmThis function is declared at this point and submits an AJAX form
the actual showModalAlert function is below:
function showModalAlert(type, theWidth, theHeight, title, html, confirmThis, denyThis) {
// stuff that opens the alert window \\
if (confirmThis == '') {
$('#confirmIt').one('click', function () { $('#closeAlert').one('click').click(); });
} else {
$('#confirmIt').one('click', function () { confirmThis(); $('#closeAlert').one('click').click(); });
};
if (denyThis == '') {
$('#denyIt').one('click', function () { $('#closeAlert').one('click').click(); $('#signIt').unbind(); });
} else {
$('#denyIt').one('click', function () { denyThis(); $('#closeAlert').one('click').click(); $('#signIt').unbind(); });
};
if (type == "confirm") {
$('.closeAlert, .signItForm').hide();
};
if (type == "alert") {
$('.alertConfirm, .signItForm').hide();
};
if (type == "fixedAlert") {
$('.closeAlert, .alertConfirm, .signItForm').hide();
};
if (type == "security") {
$('.signItForm').show();
$('.closeAlert').hide();
$('#confirmIt').hide();
$('#signIt').unbind().fadeTo('fast',1);
};
};
$('#signIt').live('click', function () {
var formData = (JSON.stringify($('.secureSign').serializeObject()));
var signitPwd = $('#signItpwd').val();
var jsonURL = "/jsonout/getdata.aspx?sql=SELECT id, password FROM users WHERE password ='" + signitPwd + "' LIMIT 1&output=json&usedb=new&labelName=any&fileName=";
$.getJSON(jsonURL, function (data) {
if (data.length > 0) {
$('.savingUserID').val(data[0].id);
$('#confirmIt').one('click').click();
$('#signIt').fadeTo('fast', 0);
$('#confirmIt').show();
} else {
$('#signIt').fadeTo('fast', 0);
$('#confirmIt').one('click').show();
$('.closeAlert').show();
$('.alertConfirm, .signItForm').hide();
$('#alertTitle').html("Error!");
$('#alertContent').css({ 'text-align': 'center' }).html("Password Denied");
};
});
});
From my understanding of $.one, it merely runs the event ONCE. If you bind it twice to the event, it will run twice instantaneously, but no more.
Example: http://jsfiddle.net/qCwMH/ (click the button, and it will run the event 4 times).
Each time you click saveDelivery, you are infact, binding another $.one event to #confirmIt.
What you could do is unbind your events from confirmIt and denyIt at the start of the modal function (i.e. $('#confirmIt, #denyIt').unbind('click');, and then you will assign them fresh each time that function is called, rather than building on top of them. Not ideal, as binding/unbinding uses more resources than other options, but just give that a try to start with perhaps?