Bootstrap Modal isn't closing properly on angular route change - javascript

I am using Angular (the $routeProvider, not ui-route), and I have a bug when I have a modal open and try to change the route.
On the route change, since the modal is still open, two bugs happen:
1) I can't scroll.
2) The opacity is at 0.5.
What seems to be happening is the routed event is firing before my jQuery.hide.
I have a workaround, but I feel like this way sucks.
The modal includes two relevant things.
One is a static link. I did this:
// This catches the click on signup from the modal, closes the modal, then
// continues the route change.
$('#modal-signup-link').click(function(event) {
event.preventDefault();
$('#login-modal').modal('hide');
$location.path('/signup');
});
Basically, I created a jQuery on click handler. Works well.
The second portion of the modal allows a user to log in, and I use a service to call the API request. This one required me to use the $timeout to delay the route change (and it seems to work). I feel like this is a BAD solution.
$scope.submit=function() {
console.log('Submit');
Login.login($scope.username, $scope.password).then(function(data) {
if (data.id) {
$('#login-modal').modal('hide');
$timeout(function() {
$location.path('/games');
}, 500)
} else {
$scope.loginData = data.errors.password;
$scope.loginError = true;
$timeout(function() {
$scope.loginError = false;
}, 6000)
}
});
};
Ignore the bottom portion, that's just for handling errors.
When I use the $timeout, the modal closes before the route change, and we're gravy.
Is there a better way to approach this problem?

You can use,$routeChangeStart which gets called on every navigation, or a route change, there you can close your modal.
$rootScope.$on('$routeChangeStart', function() { })
Example:
var app = angular.module('myApp', [])
app.run(["$rootScope","$http","$location",
function($rootScope,$http,$location){
$rootScope.$on('$routeChangeStart', function() {
$('.modal').modal('hide'); // hides all modals
$('#login-modal').modal('hide'); // hides specific modal
angular.element('.modal').hide(); // same as first, but a bit angular way
})
}])
HEre is the documentation of the same.

Related

how to delay a click event until a $http call is finished and page content is ready?

I created a modal which is fetching its content using a $http call. The amount of data coming from this $http call varies account by account. So, sometimes the modal is not ready yet and if user clicks the button to open the modal it will show nothing for a while.
I want to delay the click event until the content of the modal is ready. I mean, I want the modal to be opened whenever it is completely rendered. I tried $timeout service to do that but it is not good since some accounts need more time and some of them need less:
$('#modalLink').click(function(e) {
e.preventDefault();
$timeout(function() {
$("#myModal").modal({
persist: true,
height: 'auto',
maxWidth: 900,
onClose: function (dialog) {
$.modal.close();
}
});
}, 500);
});
So how can I set the click event to delay until my scope object ($scope.myArray) is defined?
The easy answer is to show a spinner in your modal, when data is loading. After it is loaded, you hide the spinner and show the data. I would prefer to do it that way.
However, if you do not want to show spinner, you can start loading your data with $http and put opening of your modal in .then() statement of Promise that $http.get, $http.post, etc provides.
Like
$('#modalLink').click(function(e) {
e.preventDefault();
$http.get(blablabla).then(fucntion(){
//open your modal here
};
});
Please read more about promises here https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
P.s. I would also recommend you using Services approach to get data from server.
Update
Also, please do not forget to catch errors with .catch() method.
Let's say the button is
<button ng-disabled="isDisabled">Button</button>
And in controller:
$scope.isDisabled = true;
$http.get('someUrl')
.then(function(result){
$scope.isDisabled = false;
})
This will ensure the button is disabled on page load but enabled after function call.
What you need to do is, set some kind of flag when your $http has resolved:
$http.get('/url')
.then(function(res) {
$scope.flag = true;
});
Now, set an ng-show on the modal or the modal trigger button, to be true for $scope.flag
Or even better, set a loading GIF on the Modal, that has shows itself as long as flag is false. Once your promise resolves, hide this loading icon, and show your modal content.
<div class="modal">
<div class="overlay" ng-show="!flag"></div>
<div class="modal-content" ng-show="flag"></div>
</div>
You can also disable the button in a similar function
try ui-bootstrap and get data in modal's resolve
the modal resolve works same like router resolve
Example:
$scope.openModal = $uibModal.open({
templateUrl: 'stackedModal.html', // you can use template option instead templateUrl
size: 'sm',
resolve: {
items: function () {
return //do something that you want.
}
}
controller: function($scope, items) {
//your data is in items injector.
}
});

In Ember 1.x, how do I use a modal in willTransition()

I'm trying to open a modal dialog when someone tries to navigate away from a page with a form that isn't complete. I have the modal template built, but I can't figure out how to implement it. Here's what I have:
actions: {
willTransition: function( transition ){
var model = this.currentModel;
if( model.get( 'isDirty' ) ){
this.render( 'my-modal', {
into: 'application',
outlet: 'modal'
} );
if(!this.get(abortConfirmed) {
transition.abort();
} else {
model.rollback();
}
}
}
}
NOTE: The dirty checking works and I can generate a prompt, but this modal thing is not working
So here's the workflow I use.
1). in the willTransition(transition) hook, do the check to see if you should show the modal.
2). If you should show the modal (in your case, when the model isDirty), call transition.abort(). You must do this to prevent the transition from happening. You also though need a second property on your controller that determines whether or not the transition has been authorized. So really, you check model.get('isDirty) && this.controller.get('transitionAuthorized')
3). You need a way to pass state to your modal or for your modal to be able to communicate back with the page that has created the modal. I personally pass a continueFn and a cancelFn to my modals that close over the current context. Something like
var continueFn = this.createUnsavedDialogContinueFn(this, transition);
where that function is:
createUnsavedDialogContinueFn: function(context, transition){
return function(){
context.controller.set('transitionAuthorized', true);
transition.retry();
}
}
I pass this continueFn to the modal, whose I don't care if I have Pending changes button calls via an action. You can, though, delegate this work back to the controller/route if that feels easier for you. What's important is that you set the transitionAuthorized to true and call transition.retry()
4). calling transition.retry will pass back thru the willTransition but this time you have set transitionAuthorized to true and everything passes through.
You need to stop the transition from occurring. Add transition.abort() at the bottom of your 'isDirty' check.

Angularjs handling leaving a template in various ways?

I am trying to implement a function in my angular module that will fire an event that saves the changes on the page. The different ways a user can leave the page is:
Clicking a link that loads a new template.
Clicking the back button
Closing the tab/window
My implementation handles #1 using this code:
.directive('confirmOnExit', function() {
return {
link: function($scope, elem, attrs) {
window.onbeforeunload = function(){
return "If you leave this page, you will exit the practice session. You will be able to view your score report for the completed session but will have to start a new one if you want to continue practicing.";
}
$scope.$on('$locationChangeStart', function(event, next, current) {
if($scope.$dirty & confirm("Are you sure you want to leave this practice session?")) {
console.log("Will end session!");
end_session();
}
});
}
};
});
However, when #2 or #3 occur, the pop up dialog shows up but it seems like it is not triggering the end_session() function.
Any insights into how to deal with the back button and closing the tab/window would be appreciated?
Sounds to me like a race condition. end_session() might not be firing before the browser successfully unloads the page, but without more code I can't really help, since I'm not sure what end_session() does.
What you could do is capture the locationChangeStart and defer it until after all of your unload tasks are complete.
A quick note, directly referencing window is an Angular "anti-pattern" of sorts:
https://docs.angularjs.org/api/ng/service/$window

angularjs $anchorScroll sometimes refresh all page

I have an app with angularjs routing, but on some view i want to scroll to some specific div and i use anchorScroll but sometimes (not all times) it refresh all page even i stop event propagation.
Did anyone had this issue?
$scope.redirectTodiv = function(divname,event) {
event.stopPropagation();
event.preventDefault();
$location.hash(divname);
$anchorScroll();
};
Try like this
$scope.redirectTodiv = function(divname,event) {
var id = $location.hash();
$location.hash(divname);
$anchorScroll();
$location.hash(id);
};
The way to ensure navigation with one click is to combine $location.hash() $anchorScroll and setting routeProvider reloadOnSearch property to false i.e.
In your controller code:
$location.hash("editor");
$anchorScroll();
In your route provider:
$routeProvider.when("/masters/voucher", {
templateUrl: "views/card/voucher.html",
reloadOnSearch: false
})
I've two use cases on the same page :
When clicking save, if the response is an error, scroll to the errorDiv that displays the error to the user.
David Kabii's answer did work for me.
However, on the load of the page, in the controller, I want to scroll to a specific area of the page where the user's input is expected. This wouldn't work. I had to use window.setTimeout to workaround this. (there's probably a better way)
window.setTimeout(function(){
$location.hash("anchorForm");
$anchorScroll();
}, 300);

AngularJS: Change route before a redirect countdown runs out but the redirect starts

I have got a problem with a simple login/logout page. When a user logs in/out, a success message with a countdown is shown. When the timout expires, the route changes.
However, the user can navigate before the countdown ends (using the menu or a button). The problem is that even when using the menu, the login/-out timeout still fires.
Here the code of the counter:
$scope.onTimeout = function(){
$scope.counter--;
$scope.timeoutCountdown = $timeout($scope.onTimeout,1000);
if($scope.counter === -1){
$scope.safeApply(function() { $location.path("/home"); }); //if the user isn't logged...redirect to main
}
};
And this is the code that change the location when press the button:
$scope.redirectToHome = function() {
$scope.safeApply(function() { $location.path("/portale"); });
};
Does anybody know why the timeout fires after changing the controller?
Thanks in advance
The issue you're having is due to the fact the timeout is not cleared when the $scope is destroyed.
You need to manually clear the timeout by listening to the $destroy event. This event originates from Angular and is thrown whenever a $scope is destroyed. Try adding the following to your controller (I did not test this, so consider this pseudo code)
$scope.$watch('$destroy', function(){
$timeout.cancel($scope.timeoutCountdown);
});
Edit:
You could also do this right before changing the route:
$scope.redirectToHome = function() {
$timeout.cancel($scope.timeoutCountdown);
$scope.safeApply(function() { $location.path("/portale"); });
};
Just keep in mind that if you have other redirect possibilities (i.e. other buttons the user could use to navigate from the login/-out page), you'd need to do this for each of those (or preferably wrap it in a method).

Categories

Resources