@Spencer_Price wrote:
Helpers are becoming more-and-more used in Ember templates and as far as I can tell, there is not a great way to compose helpers together. As Edward Faulkner put it,
I think we need a story for composing helpers directly out of other helpers.
I agree. Because, let's be honest, stuff like this is not easy to read (but super powerful!):
{{hash name="parent" children=(array (hash name="child1" ) (hash name="child2" ) ) }}
And, with addons like ember-composable-helpers, the need for a better composability story only increases. The only real alternative is to use computed properties inside of your component, but invoking a component has quite a bit of overhead if the goal is only to call a pure function.
So, before opening an RFC, I'd like to see if there's any feedback that this discussion forum can provide to my proposal.
I think there's a few things we could call this: "Composable Helpers", "Helper Macros", "Partial Helpers", "Block Helpers", "Anonymous Helpers" or "Contextual Helpers". For now, let's go with "Contextual Helpers" because I think that how I would want to use these aligns fairly closely with how contextual components.
I recently opened up an issue against a promising new visualization library, Maximum Plaid, which shows an example of how we might use something like this. Say we have a set of helpers (fyi: this exists) that we can use to put together the d3 scales needed to construct a simple bar chart. That template might look like this:
<svg width={{width}} height={{height}}> {{#with (hash xScale=(band-scale people (append 0 width)) yScale=(linear-scale (append 0 (max people 'age')) (append 0 height) accessor='age' ) ) as |scales|}} {{#each people as |person|}} <rect x={{compute xScale person}} y={{subtract height (compute yScale person)}} width={{compute xScale.bandwidth}} height={{compute yScale person}} /> {{/each}} {{/with}} </svg>
Because d3 scales are just functions, to actually invoke them later on, we can use the
compute
helper fromember-composable-helpers
. Like so:x={{compute xScale person}}
But, ideally, we should be able to express xScale as a helper itself. The above would become:
x={{xScale person}}
...but this is not currently possible with Ember (as far as I can tell!)
So there's two parts to this: I would like to be able to return a helper from another helper. Also, the complexity of the logic inside the
{{#with}}
is quite cumbersome and not reusable, even though this pattern might exist everywhere we want to create a bar chart. Instead, I would love to be able to see the template above expressed like this:<svg width={{width}} height={{height}}> {{#bar-scales height width people y-accessor='age' as |barDataMaker|}} {{#each people as |person|}} {{#with (barDataMaker person) as |barData|}} <rect x={{barData.x}} y={{barData.y}} width={{barData.width}} height={{barData.height}} /> {{/with}} {{/each}} {{/bar-scales}} </svg>
In this template:
bar-scales
is my Contextual Helper which, like{{with}}
is expressed as a block.bar-scales
yieldsbarDataMaker
, which is a new anonymous helper that is returned by thebar-scales
helper.If we were to peek inside that
bar-scales
helper, it might look something like this:import Ember from 'ember'; import { ordinalScale } from 'ember-d3-scale'; // another helper export function barScales([height, width, data], opts) { let xScale = ordinalScale([data, [0, width]], opts); // etc... return Ember.Helper.helper(([itemContext]) => { return { x: xScale(itemContext), // ...etc }; }); } export default Ember.Helper.helper(barScales);
An RFC for this would require some Drawbacks and Alternatives that I have not really though through just yet. I'd love to get some feedback on this before going that route.
So, please, lend me your ears (Robin Hood: Men in Tights style) and your thoughts. I'd be curious too if y'all have any other ideas for how composed helpers could be used.
Posts: 13
Participants: 4