Ember’s ability to automatically re-render a view when its model changes is awesome. Like “holy shit, that was basically magic” awesome. But there’s a common task that’s so similar but perplexingly more difficult. It’s using data from the model’s records in the view’s jQuery code (or really any clientside DOM library, but Ember bundles jQuery, so let’s be simple and assume jQuery).
For instance, if this is my router:
App.IndexRoute = Ember.Route.extend({
model: function(params) {
return App.Gallery.find();
},
setupController: function(controller, model) {
controller.set('galleries', model);
}
});
And if this is my view:
App.Gallery = DS.Model.extend({
title: DS.attr('string'),
photos: DS.hasMany('App.Photo')
});
App.Gallery.FIXTURES = [{
id: 'canadian_rockies',
title: 'Canadian Rockies',
photos: ['photo_1']
}, {
id: 'cathedrals',
title: 'Cathedrals',
photos: ['photo_2']
}, {
id: 'bryce',
title: 'Bryce',
photos: ['photo_3']
}];
Then it follows that I should be able to access the model in my view with this.get('controller.galleries')
and indeed, this works well. So far so good!
So at this point in my template I can iterate over and access properties in the galleries
array even though both the model and view do their work ASAP without waiting for the other. This all works perfectly well for templates because the view re-renders it automatically when the model changes. Again, holy smokes, magic. But when data from the model is needed in DOM-based view code, things get trickier.
For example, say you want to have a view fade through an array of strings, one by one. And lets say these strings are the names of galleries. To get these strings, it seems like you’d roughly do this:
- Register a listener for model change events
- When the model says it’s done changing, initialize the StringRotate-a-tron 5000
Registering the listener is fairly easy, the view’s didInsertElement
method seems like a reasonable place to do this. Here’s the best way I’ve found to do that:
this.get('controller.galleries').addArrayObserver({
arrayWillChange: function() { },
arrayDidChange: function() {
// initialize the jQuery module
}
});
But here’s the rub: the observer fires every time the array changes. This is a problem, because you don’t want to initialize your jQuery code repeatedly. And indeed, you don’t want to initialize until the final callback. But there’s no way to tell which callback is the final one. You can’t know if there will be more. Only the model itself knows when it’s done iterating over the data returned from the server/fixtures/flying spaghetti monster. But it seems there’s no callback for this event.
So the question is, how can you delay a view’s DOM-based code until the view’s model has fully loaded?
This will depend on the version of ember/ember-data you use, but in later versions records and record arrays are promises and entering a route can be delayed until a promise succeeds.
In other words, a view wouldn't be rendered until an array is ready for it. I think this is broken in pre4 though emberjs/ember.js#1642