Skip to content

Instantly share code, notes, and snippets.

@machty
Last active January 2, 2016 08:49
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save machty/30dd8ea75096c79e0104 to your computer and use it in GitHub Desktop.
Save machty/30dd8ea75096c79e0104 to your computer and use it in GitHub Desktop.
template block params

Template block params

Ember templates are missing block params.

{{#form-for model |form|}} 
  {{form.input 'first_name'}}
  {{form.input 'last_name'}}
{{/form-for}}

The goal here is to avoid complicated rules about scope that are hard to reason about and lead to brittle code that's hard to refactor. One of the trickiest parts of Ember (and any framework that tries to solve similar problems) is that it's often difficult to figure out and keep in mind what {{this}} is in a template, and what object/context/controller values like {{foo}} are looked up from.

Block helpers complicate this; {{#each items}} will change the context of the provided block template to the item being rendered. {{#with}}'s sole purpose is to change scope.

Present-day components do not change the scope of the provided template block:

{{this}} and {{foo}}
{{#x-component}}
  are the same as {{this}} and {{foo}}.
{{/x-component}}

This is desirable, but there's a major piece missing from the component (and helper) toolkit: the ability for a component or helper to provide data to the template block without changing the scope that the template block runs in.

Try writing a component that iterates over a list and provides the iterated element to the multiply-rendered template block. Can't really do it, not without digging into internals, adding keywords, or changing the scope of the template block through sorcery, which neuters the template block's ability to render stuff from the outer scope (which is kinda what all components are fundamentally supposed to support).

Enter rubyish block semantics

What we really need is block params.

# `self` doesn't change between here...
fibonacci.each do |n|
  # ...and here
  puts "The next number is #{n}"
end

The passed in block runs in the same scope as outside, with the only addition to the scope being n, a parameter name that the block is free to change at any point; in other words, fibonacci doesn't care what you name a parameter (this should be fairly obvious coming from a Ruby mindset, but this concept is easy to get wrong in template-land).

We need to support this in Ember/Handlebars-land. The closest thing we have is {{#each person in people}}, which achieves ruby block semantics, but it doesn't carry over well into component-land (since ordered params aren't supported and can't really support afaik if we're targeting Web Components), it doesn't readily support more than a single block param, and the code to achieve this is pretty ugly and not conducive to fostering a component ecosystem given the boilerplate involved.

So we need a Handlebars equivalent:

{{#fibo-nacci |n|}}
  The next number is {{n}}
{{/fibo-nacci}}

This

  1. Preserves scope inside and outside the scope.
  2. Allows for fibo-nacci consumers to change the name of the block param n to something else in case, say, it started to shadow some other n on the outer scope. Other solutions, such as Angular's approach to having fibo-nacci augment the inner template's scope with values that the inner template can refer to, impose the component's internals and assumptions (and future changes) into the namespace of identifiers that the template block would like to use for its own purposes. Also, debugging and testing when many layers of scope are involved is very hard compared to block params.
  3. Would be supported at a Handlebars level.

Context specific helpers

I took a stab at "context-specific helpers" (helpers that only exist and can only be invoked within certain helpers or components) using child containers, but I think using block params to achieve this is way more safe and straightforward (if not somewhat familiar from the Rails world).

{{#form-for model=person |form|}} 
  {{f.input 'first_name'}}
  {{f.input 'last_name'}}

  {{#f.fields-for 'friends' |f|}}
    {{f.input 'first_name'}}
    {{f.input 'last_name'}}
  {{/f.fields-for}}
{{/form-for}}

Misc.

Note that the above is actually shorthand for the uglier:

{{#form-for model=person}} {{|f|}}
  {{f.input 'first_name'}}
  {{f.input 'last_name'}}
  ...
{{/form-for}}

This uglier form is necessary for template blocks that might live in their own file and still need a way of choosing the name of the block param.

Necessary Handlebars Changes

  1. Support for block param syntax, both the within-mustache shorthand {{#each foos |foo|}} and long form {{#each foos}} {{|foo|}}.
  2. Support for invoking {{f.input}} as a full on helper; right now, if f.input is a path on the current context that refers to a function, the function will be called, but it's not treated the same way as a helper, doesn't get passed any args, etc. See: http://jsbin.com/uguhuqoK/3/edit

De-sugaring to HTML

Ember.Components are still targeting Web Components, so everything we do with block params needs to, at the very least, boil down to and HTML representation. See the section below for what I think an HTML-y approach would probably look like.

My prediction

I bet in a year this will exist:

<ng-fibonacci ng-params="n">
  The next number is {{n}}
</ng-fibonacci>

with the longer form

<ng-fibonacci>
  <ng-param>n</ng-param>
  The next number is {{n}}
</ng-fibonacci>

to support the inner template coming from somewhere else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment