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

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.

Related

How can I warn user on back button click?

www.example.com/templates/create-template
I want to warn users if they leave create-template page. I mean whether they go to another page or to templates.
I use this code to warn users on a page reload and route changes should the form be dirty.
function preventPageReload() {
var warningMessage = 'Changes you made may not be saved';
if (ctrl.templateForm.$dirty && !confirm(warningMessage)) {
return false
}
}
$transitions.onStart({}, preventPageReload);
window.onbeforeunload = preventPageReload
It works as expected on a page reload and route changes if it is done by clicking on the menu or if you manually change it. However, when I click the back button, it does not fire the warning. only it does if I click the back button for the second time, reload the page, or change route manually.
I am using ui-router. When you click back button, you go from app.templates.create-template state to app.templates state.
How to warn if they press Back button?
First of all, you are using it wrong:
from https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload:
Note: To combat unwanted pop-ups, some browsers don't display prompts
created in beforeunload event handlers unless the page has been interacted
with; some don't display them at all. For a list of specific browsers, see the
Browser_compatibility section.
and
window.onbeforeunload = funcRef
funcRef is a reference to a function or a function expression.
The function should assign a string value to the returnValue property of the Event object and return the same string.
You cannot open any dialogs in onbeforeunload.
Because you don't need a confirm dialog with onbeforeunload. The browser will do that for you if the function returns a value other than null or undefined when you try to leave the page.
Now, as long as you are on the same page, onbeforeunload will not fire because technically you are still on the same page. In that case, you will need some function that fires before the state change where you can put your confirm dialog.
How you do that depends on the router that you are using. I am using ui-router in my current project and I have that check in the uiCanExit function.
Edit:
You can keep your preventPageReload for state changes in angular. But you need a different function for when the user enters a new address or tries to leave the page via link etc.
Example:
window.onbeforeunload = function(e) {
if (ctrl.templateForm.$dirty) {
// note that most broswer will not display this message, but a builtin one instead
var message = 'You have unsaved changes. Do you really want to leave the site?';
e.returnValue = message;
return message;
}
}
However, you can use this as below:(using $transitions)
$transitions.onBefore({}, function(transition) {
return confirm("Are you sure you want to leave this page?");
});
Use $transitions.onBefore insteadof $transitions.onStart.
Hope this may help you. I haven't tested the solutions. This one also can help you.

How to indicate that a function is executing which re-renders the DOM?

In Angular I have loaded a page. After I press a button "Change" a function is called
ng-click = "changeFunction()"
in which the content of the page is re-evaluated and changes (the change is mostly in {{textVariables}} and images of the page).
If the content of the page being changed is less, this switch is fast enough to not be an issue. However if a lot of the content is being changed, the button appears pressed for an extended period of time, which gives the appearance that something in the page has broken (even though it has not, it is just taking time to re-evaluate the content).
How do I then modify my changeFunction() in the angular controller to mark a flag true when it starts changing and false when it finishes changing, so that I can use the flag to perhaps show the user something is happening?
To be clear, I am just asking for the javascript part of the code, the UI element I can design.
This should be a straightforward addition:
changeFunction() {
if (!this.saving) {
this.saving = true;
}
// Your code here;
// This is the last bit of the method:
this.saving = false;
}
Then elsewhere in the controller (or the template) you can check for this.saving or (if you're using 'vm' in the template) vm.saving.

Prevent user to navigate away with unsaved changes

I'm currently using Backbone.Marionette to create a SPA and in one of the views it is possible for the user to navigate away with unsaved changes. I can control some of these events, like buttons and menu options within the view that would take the user away, but some others would require to manipulate either Backbone.Router or work with the DOM events directly.
I already tried listening to beforeunload (doesn't work as the application is still loaded) and hashchange (doesn't work as you cannot stop the browser from navigating away). These solutions (1, 2, 3) don't work in this case, the Javascript is never unloaded.
Changing the Backbone.Router seems to be the best option, but because of how it is initialized I don't think it is possible to introduce this feature or at least I cannot find a way of doing it. This solution, for example, doesn't work because hashchange is not cancelable (you cannot call stopPropagation on it), and this other solution doesn't work because navigate is not defined on the Backbone.Router object.
Any suggestions?
I've managed to find a solution to this, although some more work is required. For this solution, I am assuming that you keep track when a view is dirty.
There are 4 main ways of moving out of a view;
Click on a link on the view
Click on link outside the view
Click on refresh or external link
Click on back/forward on the browser
1. Application link
This is the easiest case. When you click on your own link, you have to check if your view is dirty. For example, I have an in-app back button that is handled by a historyBack function. On the view:
historyBack: function() {
if (this.isDirty) {
answer = confirm("There are unsaved changes.\n\nDo you wish to continue?")
if (answer) {
this.isDirty = false
window.history.back()
}
}
else {
window.history.back()
}
}
2. Links outside your view
This type of interaction can be handled by extending the Router prototype's execute method, not the navigate method as proposed in other places.
There should be a variable somewhere accessible by the Router that stores the state of the view. In my case, I'm using the Router itself and I update this variable every time I change the dirty flag on the view.
The code should look something like this:
_.extend(Backbone.Router.prototype, {
execute: function (callback, args, name) {
if (Backbone.Router.isDirty) {
answer = confirm "There are unsaved changes.\n\nDo you wish to continue?";
if (!answer) {
return false;
}
}
Backbone.Router.isDirty = false
if (callback) callback.apply(this, args)
}
}
3. Refresh or external link
Refresh and external links actually unload your Javascript so here the solutions based on beforeunload (see question) actually work. Wherever you manage your view, I use a controller but let's assume it's on the same view, you add a listener on show and remove it on destroy:
onShow: function() {
$(window).bind("beforeunload", function (e) {
if (this.isDirty) {
return "There are unsaved changes.";
}
}
}
onDestroy: function() {
$(window).unbind("beforeunload");
}
4. Back/Forward on the browser
This is the trickiest case and the one I haven't figured out completely yet. When hitting back/forward, the user can navigate out of the app or within the app, both cases are covered by the code on 1 and 3, but there is an issue I can't figure out and I will create another question for it.
When hitting back/forward, the browser changes the address bar before calling the router so you end up with an inconsistent state: The address bar shows a different route to the application state. This is a big issue, if the user clicks again on the back button, after saving or discarding the changes, she will be taken to another route, not the previous one.
Everything else works fine, it shows a pop up asking the user if she wants to leave or continue and doesn't reload the view if the user chooses to stay.

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

Update conditional attributes on Meteor user login/logout

I'm using Meteor with Iron-router (if that's relevant). I've made some changes to accommodate the 0.8 update, and I'm still struggling with some things.
Instead of using conditional attributes inside a div in html, I'm using .rendered to set those attributes. But now, when a user logs in, I must either refresh the page or navigate away and back (I don't think the refresh is necessary) so elements behave correctly.
Does anyone know how I can make these attributes behave correctly based on whether a user is logged in or logged out, IMMEDIATELY WHEN THE USER LOGS IN OR OUT?
Template.pt_entry.rendered = function() {
$('#signin').popover(),
$('#reviewModal').on('shown.bs.modal', function () {
$('#review-text').focus();
});
if(Meteor.userId()){
$('#signin').attr( 'data-toggle', 'modal' ),
$('#signin').attr( 'data-target', '#reviewModal' )
} else {
$('#signin').attr( 'data-toggle', 'popover' ),
$('#signin').attr( 'data-container', 'body' ),
$('#signin').attr( 'data-content', 'you must sign in to review' ),
$('#signin').attr( 'data-trigger', 'click' )
//add some logic for displaying error template.
}
}
Actually you have different options:
If you are using iron-router you can generally show different templates in the same route. You can check if the user is logged in and based on this check you can switch between templates. You can maybe check it with a beforeHook:
beforeHooks = {
isLoggedIn: function() {
if(!(Meteor.loggingIn() || Meteor.user())) {
Router.go('landingPage');
this.stop();
}
}
}
Router.onBeforeAction(beforeHooks.isLoggedIn);
Another option is to use the default {{currentUser}} helper
{{#if currentUser}}
<p>User is logged in</p>
{{else}}
<p>User is not logged in</p>
{{/if}}
Maybe the reason why your code doesnt work could be the new blaze engine. The rendered-callback is just fired once Blaze. Normally Meteor.userId() is a reactive datasource so your template gets rerendered but if the rendered callback is just fired once the changes dont get applied. Maybe these minds help you!

Categories

Resources