CSS transitions through Ember.js Views: The "Ember" way to do it? - ember.js

I'm using the {{bind-attr}} helper to bind a class name to an element for the purposes of transitions (in css), and setting the values in the controller on 'didInsertElement' seems to short circuit the transition, even with delays in my CSS to prevent it. Though, the end-state of the transition is rendered.
I've tried Ember.run.scheduleOnce('afterRender') which seems to do the trick, but isn't quite what I want from a 'clarity' standpoint.
Is there a hook (or a way to create one, outside of an uggo setTimeout call) that fires AFTER an element has been inserted into the dom and is effectively finished rendering?

You have to use Ember.run.scheduleOnce('afterRender'). But you can do it more elegantly by doing the following. Now all of your Views have the hook afterRenderEvent. This should suffice from a clarity standpoint, right?
Ember.View.reopen({
didInsertElement : function(){
this._super();
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
},
afterRenderEvent : function(){
// implement this hook in your own subclasses and run your jQuery logic there
}
});
PS: This snippet is taken from a post on my blog, where i explain the approach more in detail.

Bit late to the party, but we needed css animations in ember-paper, so I created an ember addon that makes it possible to transition components (just like ng-animate and React Animation) with only css.
https://github.com/peec/ember-css-transitions

Related

Ember: Re-render a route's template when the model changes

I've noticed that a route doesn't re-render when the model changes (i.e. transitionTo the same route with a different model). I have some jQuery plugins set up on a particular page and I need them to re-render when the model changes, so it appears as a fresh page.
Is there a way to do this? Perhaps by observing the model's ID and firing a re-render of the route somehow?
Thanks in advance
I have an ember twiddle that, I believe, does what you're looking for, but first I would like to argue there are no straightforward ways to do what you're asking because it is the opposite of what an SPA is designed to do.
Data binding (without refreshing the view) is typically a boon of an SPA, and the SPA works hard to avoid brute force reloading/refreshing/rerendering the view at all costs. It took me a while to find a solution to your question as it is stated because it seems to go against Ember design principles. Even hooks like route refresh() are meant to update the model and bound data, not reload the template.
Although other people have asked the same question before, it seems that most answers guide users towards not refreshing the whole view. More often than not, the ideal of refreshing the view is an incorrect assumption.
Following previous examples, I would suggest that your goal shouldn't be to refresh the template completely, but rather, figure out how you can make your jQuery plugin better fit in to a Single Page App/client-side JS friendly design and have it reload as a natural part of the route lifecycle.
For instance, maybe the plugin can be reloaded/reset/re-run in afterModel() or somewhere similar.
That said, I was able to accomplish what you asked for using (in my opinion, a hack) Ember.run.later() so that I could invalidate an if block and force the content to rerender. Note, this is typically not what users want since (aside from design principle reasons) it causes UI flicker.
I have a component like so.
/* will-rerender.hbs */
{{#if show}}
{{yield}}
{{/if}}
And it has a JS file like so.
/* will-rerender.js */
import Ember from 'ember';
export default Ember.Component.extend({
show: false,
didReceiveAttrs() {
this._super(...arguments);
/*
Ugly hack, but we need to reset `show` in
a separate run loop in order to force the view
to rerender.
*/
this.set('show', false);
Ember.run.later(() => {
this.set('show', true);
});
}
});
You can invoke it like this...
/* your template */
{{#will-rerender cacheKey=model.id}}
... your content to rerender ...
{{/will-rerender}}
Whenever the model.id changes the component will invoke didReceiveAttrs() causing show to invalidate and the view to refresh.
As an aside, I think the behavior of switching between models would be much more natural with {{link-to}} rather than calling transitionTo yourself.

Recommended way to rerender page in Ember after language switch

I need to rerender whole page application after language has switched. I don't like to use observers for it because of performance issues.
Language switch is done by a Ember.View. I tried to rerender parentView after changing Ember.I18n.translations but running into a known bug. It works one time but Ember Inspector shows parentView has dutzend of children views of it's own afterwards. After another switch parentView got destroyed. Just like demonstrated in this JSFiddle.
Here is simplified code of my view:
export default Ember.View.extend({
templateName: 'language-switch',
languageChanged: function() {
// change language
var language = this.get('controller.language.selected');
Ember.I18n.translations = translations[language];
// rerender page
this.get('parentView').rerender();
}.observes('controller.language.selected')
});
There is also a discussion about that problem on discuss.emberjs.com. I tried the suggestions there but they don't work. Last post suggest a work-a-round to map over all views, rerender them and afterwards use a transition. I don't like that one since I am afraid getting side problems by that hack.
I am pretty sure there must be a way to do a language switch in Ember with ember-i18n but what's the right way to do that?
Update:
I tried to implement the hack from discuss.emberjs.com. It's working but it's very limited. I implement it like this in the view:
var self = this;
var currentRoute = self.get('controller.currentRouteName');
this.get('controller.target').transitionTo('loading').then(function(){
self.get('controller.target').transitionTo(currentRoute);
});
Problem is that all data stored in model is lost. I did not find a way to get the current model and use it in transition. Also query parameters are lost. That limitation makes this work-a-round unacceptable for my application.
Update App.reset():
As suggested in comment I tried to use App.reset(). It works better than transitionTo work-a-round but doesn't match all needs. If calling App.reset() queryParams aren't lost but model is reseted. I'm looking for a way to rerender application while keeping current model and queryParams.
It seems that mostly these problem is handled by a full page reload on locale change. E.q. one of the main developers of ember-i18n said so in this discussion.

Rerender view after change route's dynamic segment

I have a Route with dynamic segment :id. When I change only the dynamic segement part manually in browser url input field, then goes a transition, and all model hooks are called as expected.
The problem is: none of the views are rerendered. I guess it is because only model changed - not the UI. But I have some UI logic in views' didInsertElement handler - reinitialize UI plugins and so on.
How to force ember to rerender view after dynamic segment change?
I agree with the josh's comment. if you are rerendering views with the change of dynamic segment means your code is not written properly. But still if you want to go like that i am gonna give is a part of code.
In your route:
model: function(params){
model.set('id', params.id);
}
In your view which needs to be rerendered:
_modelIdChange: function(){
this.rerender();
}.observes('controller.model.id')
But i don't suggest this. I would rather prefer proper bindings. But i did use rerendering in some cases which mostly when you end up using jquery plugins.

Architecture for reusable object in ember

I am building an admin dashboard using ember. I want to create a reusable chart object of which I can have multiple instances throughout the application. The chart object should have a template consisting of some markup and a canvas element of which I need the id after insertion in the DOM in order to attach the actual chart (chart.js). I have tried several approaches, but I can not seem to figure out the right architecture to do this.
What would be the right architecture in ember to achieve the above?
Thanks!
Ember.Component is your friend
As #raulbrito already mentioned, the best way to go if you want reusable components in ember is indeed to use the new Ember.Component which is heavily based on the new w3 draft for web components and thus beeing future proof.
I've tried to make a simple example on how this could be implemented.
Given a simple route where the model hook returns some static data:
Index Route
App.IndexRoute = Ember.Route.extend({
model: function(){
return Ember.Object.create({
modelOne: data,
modelTwo: data2
});
}
});
data and data2 are simply static objects globally defined for simplicity (as you will see in the demo), but this could be also data coming from a backend or from fixtures etc.
Index template
In the template then we insert our chart component with the line {{line-chart data=model.modelOne}} and as you can see, we also set the data attribute to the index model model.modelOne or model.modelTwo:
<script type="text/x-handlebars" id="index">
<h2>Chart one</h2>
{{line-chart data=model.modelOne}}
<h2>Chart two</h2>
{{line-chart data=model.modelTwo}}
</script>
Component Template
Our component template looks fairly simple because it will render a simple canvas element, but it could be as complex as needed, on how to use Ember.Component please refer also to the docs:
<script type="text/x-handlebars" id="components/line-chart">
</script>
Component Subclass
App.LineChartComponent = Ember.Component.extend({
tagName: 'canvas',
attributeBindings: ['width', 'height'],
width: '480',
height: '360',
data: null,
didInsertElement: function() {
var ctx = this.get('element').getContext("2d");
var myNewChart = new Chart(ctx).Line(this.get('data'));
}
});
Note the naming is important here, Ember knows which subclass powers a component based on its name. For example, if you have a component called line-chart, you would create a subclass called App.LineChartComponent. If your component was called bar-chart-simple, the class name would be App.BarChartSimpleComponent and so on. Ember will look for a class with the camelized name of the component, followed by Component.
So, and since Ember.Component extends from Ember.View we can define all sorts of properties Ember.View supports like tagName. In our case we use canvas because this is what chart.js needs to work. As you can see we have also defined some attributeBindings to control the width and height of the canvas from inside ember. The component has also a data attribute (which could be called whatever you find appropriate) defined on which we later set our model data in the template returned from the IndexRoute model hook. And finally in your didInsertElement hook of our component we initialize the chart passing with this.get('data') the data object to new created Chart.js class.
var ctx = this.get('element').getContext("2d");
var myNewChart = new Chart(ctx).Line(this.get('data'));
And last but not least, please see here for a working example of the above explained.
Hope it helps.
Update in response to your last comment
I've tried to simulate a delay in the resolution of the model hook to mimic a response from a backend, as you can see the template rendering is waiting for the model promise to resolve first. Basically what I've done is to use Ember.run.later with a delay of 2000ms that resolves the promise once timed out:
App.IndexRoute = Ember.Route.extend({
model: function(){
return new Ember.RSVP.Promise(function(resolve) {
Ember.run.later(function() {
var m = Ember.Object.create({
modelOne: data,
modelTwo: data2
});
resolve(m);
}, 2000);
});
}
});
And just for fun I've also added a LoadingRoute to show a spinner while the promise resolution is waiting for data, the LoadingRoute is a less documented feature of ember, you can read more about it here: https://gist.github.com/machty/5647589 under How do I put up a (global) Loading Spinner during a transition w/ Promises?
Plase see here for a updated example: http://jsbin.com/odosoy/145/edit
Update in response to #SamSelikoff's comment
As for the above mentioned LoadingRoute #SamSelikoff pointed out that it's officially documented now: http://emberjs.com/guides/routing/defining-your-routes/#toc_initial-routes
I have some thoughts on this, so just throwing it out there, in case it helps you.
First of all, I would advise you to go and watch Sam Selikoff's presentation on using Ember with D3. All the info here: http://www.samselikoff.com/blog/2013/08/09/ember-d3-simple-dashboards/ . Also, don't miss the comments section on the blog post.
It is a great example on using Ember Views to wrap D3 objects, and can be a good reusable solution. The caveat here is that Ember Views require a backing controller that provides the data. Depending on where in the application you would want to reuse your charts, this might be inconvenience.
The alternative would be to use Ember Components. In that case, you just need to define the Component and associated handlebars template. The good thing about it is that it won't need any backing controller, therefore freeing you from a dependency, which might make it easier for you to add such a component in different places of your application. Without a concrete example, I think it's hard to reach a great conclusion, but maybe this will help you clarify things.

In an Ember view, what's the best way to run code after the rerendering of an internal child view?

I found a lot of questions about how to initialize jQuery plugins with dynamic data etc and usually the answer is that you should use the didInsertElement hook. My problem is that when using ember-data, initially the view is inserted empty and the template vars are filled afterwards through the bindings. Thus in didInsertElement the elements I need are not there.
I found a solution that works for me but I think there must be a better solution.
In addition to the didInsertElement method I also observe the attributes in the controller and call my code from there, too, wrapped in a Ember.run.next so the child view is rendered before.
Is there a better solution to react to updated child views?
It depends a bit on the size of your application, and the number of elements your view is rendering.
Edit: You might successfully observer the isLoaded property of the RecordArray. Some details here: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/record_arrays/adapter_populated_record_array.js#L21
One option that I have used for views that have a single object as their content is something like:
MyApp.CustomView = Ember.View.extend({
content: null,
contentObserver: function() {
this.rerender();
}.observes('content')
}
If your view contents is a list, it makes little sense to re render the view for each item in the list, though.
However, I think this approach is fairly similar to what you are already doing, though.
Another approach is to implement your own adapter with Ember Data. This way, you can tell the rest of your application that it's finished loading your data. Something like:
MyApp.Adapter = DS.Adapter.create({
//Finding all object of a certain type. Fetching from the server
findAll: function(store, type, ids) {
var url = type.url;
jQuery.getJSON(url, function(data) {
store.loadMany(type, data);
});
//Perform some custom logic here...
}
}
This should work, but its not as generic as it should/could be though.
Also, you might want to take a look at this commit, which allows for registering a one-time event.
https://github.com/emberjs/ember.js/commit/1809e65012b93c0a530bfcb95eec22d972069745#L0R19
I had this problem and came up with a solution: make a handlebars helper that tells you when the sub-section has rendered. Hope that helps some other folks!