Towards better reuseability of components (?)


#1

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.


#2

Maybe Closure Ations can help? You can get a return value back this way in a deeply nested component. Fast “Actions up Data down”.


Unique key on components
#3

@broerse I forgot to mention closure actions. That’s a help for sure, generally, but not for configuring the icons. I suppose it would have been better to write “configurable”, rather than “reuseable”, in the subject.