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 friendAs @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:
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.
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>
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>
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With