Internal view lifecycle hooks for animation purposes


#1

Let’s move the discussion about internal lifecycle hooks from Animation support in Ember 1.1 here.

The basic idea is to provide us with view hooks such as presentCurrentView and dismissCurrentView to enable us to have granular control over when a view is actually removed from the DOM so we can animate it first.

@tchak, can you explain what went wrong with your PR https://github.com/emberjs/ember.js/pull/1198 which definitely looks like the right direction to go?


Animation support in Ember 1.1
#2

If I could hazard a guess I’d say something related to this:


#3

@seilund Can you describe at what point the transition starts and finishes wrt the router lifecycle. Will it happen after data has loaded, ie;- promises from model hooks are resolved?

What I would like is to allow for “Performing actions optimistically”.

If an action would cause the next page to be shown. It should start loading the data and also start the transition immediately. Making the assumption that by the time the transition has finished, data will have been fetched as well.

The article I linked to, by @lukew, goes into great detail how this improves UX.


#4

Great article, and I agree. I believe we can use the same semantics from the new async transitions. Transitions (both animated or not) starts when all route model hooks (beforeModel, model and afterModel) has been resolved. If you want your transition to start before data for the target route has been loaded you should not return promises in your model hooks. This is the same behavior as when not using animation.

So you can achieve both. Here are two simple examples:

App.TransitionImmediatelyRoute = Ember.Route.extend({
  model: function() {
    var items = Em.A();
    items.set('isLoaded', false);
    $.getJSON('/api/items').then(function(payload) {
      items.pushObjects(payload);
      items.set('isLoaded', true);
    });
    return items; //Returns right away, so transition will occur right away
  }
});
App.TransitionAfterDataLoadedRoute = Ember.Route.extend({
  model: function() {
    //Returns a thenable, which will make the transition wait for the AJAX request to complete
    return $.getJSON('/api/items');
  }
});

(Could be prettier if using a data framework, but for simplicity I omitted that)

Edit: The isLoaded property is there so your template can display a “Loading…” message fx.


#5

@seilund The problem I see with this is that nearly every Model implementation, from Ember-Model to Epf returns promises.

The exception to this seems to be Ember-data which returns psuedo promises, which trigger an immediate render and a second render on data load. See this issue.

Two related questions then are,

  1. At what point would the animation kick in?
  2. And how would you support both approaches? There are 2 different animation start triggers.

Having said that from the UX perspective, we should try to display the animation immediately independent of whether the hook return a promise or not. This is key to giving the illusion of speed, as discussed in the article by @lukew.

I have a feeling this isn’t going to be easy. The pausing behaviour of the async router should be limited to calling the lifecycle hooks. Maybe willTransition could be used to start the animation?

Else we’ll need hooks like beforeTransition and afterTransition!


#6

I agree that a transition only in rare cases should not resolve immediately. The user needs to get instant feedback.

In my oppinion a model object should not be a promise. It’s not a promise, it’s the value. See this discussion about Splitting of Promises from DS.Model (or Ember.Model). I think what gained most support in that thread was to have a find method which returns the model, and a fetch method which returns a promise of the model.

As to your questions:

As soon as all model hooks have resolved. It’s your responsibility (and I believe that all the data frameworks should make this trivial) to make sure that your model hooks don’t return promises. The reason is this: Before all model hooks have been resolved there is nothing to transition to. Something might transition to another route instead of resolving. This is the whole idea of the async router.

See my two examples above.

Does this help answering your questions regarding transition promises and transition animation?


#7

@seilund I see, so the change I would need to make to my model hooks is to use fetch instead of find. I think I can work with that.

With regards to the other libraries like Ember-Model, or Epf. Has there been a discussion about supporting this?

Your example code does clarify things with $.getJSON. How does it work with the persistance libraries though?

My perspective is from the api user’s point of view. We really should try to get the opinions of the authors of these persistance frameworks. /cc @ebryn @ghempton


#8

I’m not sure about that. @ebryn?


#9

I’m a bit late to the discussion; but to be honest I think the most straight forward method for handling an issue like this is applying it directly to the views where it belongs, and following a UIKit/Cocoa model of having specific hooks for handling view state in terms of post-render pre-insertion and post-render-post-insertion style methods. Same in the reverse.

Because transitions in the CSS sense are handled interdependently via an independent, css defined delay and duration, ember would need to find a way to potentially read those values from the style properties of the element once rendered and prior to execution of the hook.

angular uses something called ng-cloak for handling this sort of thing; but in angular’s case is used to prevent FOUC.

This would effectively append your view to the dom and keep it hidden while it attempts to compute the duration of the transition via some kind of magical duration sniffing thing that doesn’t seem to exist currently. Sniffing for transition duration properties (if defined) would be one thing; but in my research is unreliable at best.

I’m kind of all over the place right now (it’s late)

Specifically, I think the key thing would be to have a (blocking, sync) way of preventing further router state transition until all hooks have somehow resolved on a route-level hash of attached views within a routes outlet, or potentially as an additional function property of this.render within a route’s renderTemplate() hook.

e.g;

renderTemplate: function () { 
  this.render('someTemplate', {
    transitionIn: function () {
      call_some_actions_on_templates_controller()
    },
    transitionOut: function () {
      same_as_before_but_in_reverse() 
    },
    outlet: 'someoutlet',
    into: 'parentTemplate',
    controller: 'foo'
  });

This obviously isn’t perfect and would need to then()able, but would get you a very long way toward the goal of handling programmatic transitional animations pretty easily.


#10

View lifecycle hooks implementation:

This works really well.