Programmatically Rendering Ember Components
I'm currently working on a project on which the main page is an interactive dashboard with a number of different 'widgets'. The project is a server monitoring dashboard. I got the inspiration from an open source project called linux-dash. The project hasn't been modified much in some time, so I decided to spin up my own.
I am using Ember on this project, so each of my widgets is an Ember component. My goal is to make it really simple to add new types of widgets for a user to add / view on their dashboard. I am starting out with a number of default widgets, and am hoping that as time goes on, other people will contribute additional widgets to the project.
I ran into a problem fairly quickly. Each widget is its own Ember component, but I don't know which component to render until runtime, when I get the user's widgets from an API call. Each widget has a name, and my Ember components names are based on the widget names. For example, I have a widget called "general". This widget displays some basic information about the server (OS, uptime, host name, etc). I also have an Ember component called widget-general
. This is how my Ember app is structured:
+-- adapters
+-- controllers
+-- helpers
+-- models
+-- routes
+-- serializers
+-- templates
| +-- components
| +-- widget-general.js
+-- views
+-- app.js
+-- router.js
When I get the user's widgets from my API call, there is a property on the widget model called name
, whose value in this case would be general
. Basically, I needed a way to to render an Ember component based on a model property value.
Unfortunately, there is no way to do this by default in Ember.
Fortunately, there is a way to programmatically render an Ember component.
I created a helper called render-widget
. It looks like this:
helpers/render-widget.js
Ember.Handlebars.registerHelper('render-widget', function(widgetName, options) {
var widgetName = Ember.Handlebars.get(this, widgetName, options),
helper = Ember.Handlebars.resolveHelper(options.data.view.container, 'widget-' + widgetName),
template;
try {
template = helper.call(this, options);
} catch(ex) {
helper = Ember.Handlebars.resolveHelper(options.data.view.container, 'widget-error');
template = helper.call(this, options);
}
return template;
});
This helper does the following:
- Get the value of
widgetName
, which is the name of the model property (in our case, this would bename
). - Resolve our component with the value of
widget-name
, prepended withwidget-
. - Try to call our now resolved component (
widget-general
) - If this fails (the template does not exist), render the
widget-error
component.
Calling this helper in our Handlebars template is as easy as
{{render-widget widget.name}}
where widget
is our widget model and name
is the property on the widget model that tells us what widget to render (in our case, name
contains the string general
).
Now, all you need to do to add a new type of widget is specify it on the backend (in the database), and create a new component that is named correctly. Pretty simple!
As always, if you have any questions / comments, don't hesitate to drop me a line on twitter (@awgreenarrow), shoot me an e-mail at andrew@greenarrow.me, or write your own blog post and let me know about it via one of the first two methods!