dotJEM / angular-routing

Enhanced state based routing for Angular applications!
https://dotjem.github.io/angular-routing/
MIT License
75 stars 9 forks source link

$viewBeforeUnload event or equivalent transition? #80

Closed groner closed 10 years ago

groner commented 10 years ago

I'm looking for a way to intercept and prevent state changes that would unload a view containing an unsubmitted form.

The current transitions and events don't show what views would be present after the pending transition.

jeme commented 10 years ago

Think you need to explain the scenario where you require to know what views will be present after?...

Mostly to evaluate if there may be a better way than beginning to inspect "views"... which shouldn't be to difficult to provide (simply allow getting the pending transaction view state on a get rather than the state as it was when you entered.)...

Not that it couldn't make sense anyways so ill have a look at how exactly it should be exposed.

groner commented 10 years ago

My need is to know if a view is being unloaded, inspecting what the views will be after a transition is just a way to derive that information. If I don't need to do the comparisons myself, then the details of those views are not important to me (for this purpose).

Once I know that a view is being unloaded I want to check if a form is dirty and present a confirmation dialog to the user. I think I can hack this part up externally, but it would be nice if the transition could be suspended until the user has made their decision. Something like:

    if (formIsDirty) {
        $transition.suspendUntil($modal(...));
    }
jeme commented 10 years ago

Ok I guess my question wasn't precise enough >.<... The reason why I ask was to figure out why it wasn't enough knowing what state you where exiting... E.g.:

angular.module('demo.home', ['dotjem.routing'])
  .config(['$stateProvider', function(sp){
    sp.state('item', {
      route: '/',
      views: { 
          main: {  template: 'home.html'
        } 
      }
    });

    sp.state('item.edit', {
      route: '/',
      views: { 
          form: {  template: 'form.html'
        } 
      },
      onExit: { before: ['$validationState', function(vs){
        //This is an edit state, we always know we have something to validate before leaving..
        if(!vs.validate()){
          //...
        }    
      }] }
    });
  }]);

But ill look at making the view transaction state available anyways as it could probably help some other cases along... So it would be to allow you to use: $view.getXXX() which would give you a list of view configurations and how they are meant to be applied... (Which are cleared, Which are updated, Which are notified etc).

As for your suspendUntil idea, that could probably be valuable feature. And I will look into how that would be implemented, if it's a call as you shown or if it could be enough to just let you return a promise which then had to be resolved before the transition continues.

I think the later would be more in line with how most things work, but if it makes it harder in some cases it's obviously a bad idea... In any case it's not something that will be straight forward to implement correctly.

I have created a separate issue for that.

groner commented 10 years ago

Using the state transition hooks will work, and it's what I will do for now.

One of the things I want to do with the view unload event is to have a form safety directive that wouldn't need to be configured for a specific state or view.

module.directive('safety', function() {
  return {
    require: '^form',
    link: function($scope, $element, $attr, formCtrl) {
      var expr = $attr.safety;
      $scope.$on('$viewUnload', function(event) {
        if (formCtrl.$dirty)
          // FIXME: event.transition doesn't exist
          event.transition.suspendUntil($scope.$eval(expr));
      });
    },
  };
});
<form ng-submit="submitForm()" safety="confirmUnload()">
  ...
</form>

This needs the ability to hook into transitions dynamically, as events allow but with access to local injectables like $transition, $to and $from.

Should I open another issue for this?

jeme commented 10 years ago

@groner I have done a first iteration on getting you access to "what will happen", however it's not done yet... (#83 us a continuation of #81)...

a Short demonstration http://plnkr.co/edit/CW7dDC8TbKAtJSpVLawY?p=preview

Right now all it gives is what action will be taken and if a view with a given name is already loaded... that gives the permutations:

action: 'create', exists: true -> view will remain. action: 'create', exists: false -> view will load. action: 'update', exists: true -> view will (re)load. action: 'update', exists: false -> view will (re)load. action: 'clear', exists: true -> view will unload. action: 'clear', exists: false -> nothing. action: 'refresh', exists: true -> refresh for view. action: 'refresh', exists: false -> nothing.

Right now the most important thing that lack is the sticky flag, as it actually turns an update into a refresh... And that is not reflected above... In the end I wan't to redo it all so it will give a more precise information (aligned to the outcome on the right hand side)

So rather than getting action and exists and maybe more... you would just get a string as something like: "update", "load", "refresh", "unload", "keep", "invalid"...

As I recall your on the 1.2 branch right?... Since it seems like Angular is getting close to a final release or at least a RC4, ill pause for now with merging it into that branch to see what happens from Angulars side...

If they bump to 1.2 stable, then I will work towards the 1.2 branch going into the main branch. If they bump to 1.2 rc4, then ill do another rebase/merge to the 1.2 branch.

groner commented 10 years ago

Hi @jeme, this is really helpful.

One thing I think might be missing is some way to determine what is being loaded/updated/unloaded/kept/refreshed. A handle or descriptor to do that comparison with doesn't exist right now (AFAIK), so I don't know what to suggest here. The event/directive scenario sidesteps this issue, if the event is broadcast from the jem-view scope.

I am using the 1.2 branch. Please let me know if I can do something to help.

jeme commented 10 years ago

The above should now be implemented. But the $view service would need a bigger refactoring before I would say it was even possible to provide an idea of what template, controller etc. that is loaded/created.... This is only really lacking for "Create" and "Update"... As for Unload, Keep, Refresh you can just use $view.get which returns the current state for a view or all views if invoked without a view name.

But adding it to the current implementation will create a redundant mess... My thoughts here is leaning towards deprecating allot of the update and create overloads, and only sticky with one taking a view name and a args object.

Taken this list:

Short that down to:

That would make it far easier to deal with and it will allow pending to just return that object of args... as well as the state service can just pass on the actual view object as defined on a state rather than running a deconstruction on it. (Locals being added as a $locals instead though to keep it private/reserved). This would even allow people to decorate it will their own parameters...

But for now the above is how far this will get on #83 for now, as ill focus on #81 as the next thing instead...

As for broadcasting an event on jem-view... I think that would be to late as views don't begin to actually update before the transition is completed. There is a $viewPrep event though which may be of some use... But it is really not meant to hold anything back, rather it's for giving the ability to display a loader on a view if it's expected to be updated.

jeme commented 10 years ago

@groner FYI the main branch is now on 1.2

jeme commented 10 years ago

All issues related to this is now closed.