I’m struggling to figure out an efficient way to let deeply nested components know what actions to take. As an example, let’s say we have a configurable ToolboxMenuComponent. Inside, it contains a number of “tools”, essentially icons that may be clicked on to get it to call some desired action. To be able to reuse this pair of components, we need to be able to pass in some information. However, listing each action is less than ideal,especially if we also need to pass in class information,a title, etc. For example:
(using non-CLI syntax for clarity)
App.UserListingComponent = Ember.Component.extend({
// config is in one tidy place
userToolsConfig: [
{ classList: "icon-star", action: "star", title: "star me!", defaultState: false },
{ classList: "icon-meh", action: "meh", title: "meh ... i guess", defaultState: false }
],
actions: {
star: function(user, state) {
user.set("starred", state);
},
meh: function(user, state) {
user.set("meh", state);
}
}
});
We apply the ToolboxMenu in our template, passing in the config array:
// inserted inside {{#each users as |user|} loop
<script type="text/x-handlebars" data-template-name="components/user-listing">
<li>
{{toolbox-menu
target=user
toolboxConfig=userToolsConfig}}
<span class="user-name">{{user.name}}</span>
</li>
</script>
The ToolboxMenu template loops over the array, adding in generic ToolboxItems, each with their own config.
<script type="text/x-handlebars" data-template-name="components/toolbox-menu">
<div class="toolbox-controls" type="toolbar">
{{#each toolboxConfig as |config|}}
{{toolbox-menu-item action="proxy" config=config}}
{{/each}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="components/toolbox-menu-item">
<a class="toolbox-menu-item {{classList}}" title="{{config.title}}" {{action "click"}}></a>
</script>
When an icon is clicked its underlying component triggers a call to the parent’s proxy()
App.ToolboxMenuItemComponent = Ember.Component.extend({
mode: null,
classList: null,
didInitAttrs: function didInitAttrs() {
this.set("mode", this.get("config").defaultState || false);
this.set("classList", this.get("config").classList || "");
},
actions: {
click: function click() {
var mode = this.get("mode");
this.set("mode", !mode);
// calls parent proxy method, passing desired action to ba called
this.sendAction("action", this.get("config").action, this.get("mode"));
}
}
});
App.ToolboxMenuComponent = Ember.Component.extend({
actions: {
proxy: function proxy(action, state) {
// calls desired outer scope action, passing target (the user)
this.sendAction(action, this.get("target"), state);
}
}
});
But this won’t work because the component’s actions must be declared in the template, like this:
{{toolbox-menu
target=user
toolboxConfig=userToolsConfig
star="star"
meh="meh"}}
That one change will allow the foregoing to work but it’s not very DRY. Imagine that there were a dozen tools in a given menu.
Is there a compelling reason that the actions must be declared in the template? Could they not simply be allowed to this.sendAction(“whatever”) out into the enclosing scope,and if nothing handles it, so be it? Or, well, i guess that’s kind of what’s happening now, except that nothing in the outer scope hears the action being sent.
I’ve tried to sort it out in the source but i confess that i became quite lost.