Inline partials provide a syntax that allows partials and helpers to have named programs that they can execute at will. This allows for use cases such as local partials, layout templates, and helpers that have more conditional rendering options than just positive and negative cases.
Inline partials are defined using the <
flag.
{{< bar}}
{{/bar}}
Will declare the bar
inline partial which will be made available when the parent runs.
Each block has it's own set of named inlined partials and are able to access any inline partials defined in parent scopes.
Inline partials follow the same standalone rules as blocks.
Multiple inline partials with the same name in a given block result in a compile error.
Inline partials have access to all block parameters defined by their parents and may also define their own:
{{< bar as |foo| }}
{{foo}}
{{/bar}}
Inline partials contained within helper calls will be exposed to the call's options.partials
object as a named value.
{{#foo}}
{{*bar}}
Blah
{{/bar}}
{{/foo}}
function foo(options) {
return options.partials.bar(context);
}
The existence of a given inline partial is not guaranteed so helpers should implement checks when resiliency is a concern.
Within templates, inline partials may be called using the normal partial syntax:
{{< foo}}bar{{/bar}}
{{< baz}}{{> foo}}{{/baz}}
{{> baz}}
Custom contexts or parameters may be passed to a inline partial in the same manner as other partials:
{{> bar foo baz=bat}}
Would execute the inline partial bar
with context foo
, augmented with the value baz
set to bat
.
Inline partials have any block parameters from their defining scope made available to them at execution time.
When run using {{> }}
, the inlined partials take priority over higher scoped partials that may be defined.
Partial calls may now have a child block, which define inline partials that may be passed to the executed partial as well as a default behavior to be executed if a given partial does not exist.
{{#> foo}}
{{*bar}}
Blah
{{/bar}}
{{/foo}}
Would call the partial foo
, defining the bar
inline partial which will be passed to the partial, which can execute it as follows:
{{> bar}}
Any content in the partial block other than inline partials is not executed if a given partial exists. If the partial would like to render this content, to augment default behavior for example, it may do so using the @default
partial name.
{{> bar}}
foo
{{/bar}}
{{< bar}}
{{> @default}}
other foo
{{/bar}}
Would render:
other foo
If a partial block is provided and no partial with a given name exists, then the partial block will be rendered rather than throwing an error, which occurs if a partial block is not provided.
@wycats @mmun RFC on the above. This is trying to provide a fix or the language primitives for #208, #404, and #893.
Example covering the use cases described in #893
<html>
<head>
<title>{{title}}</title>
<body>
<nav>
{{> sidebar}}
<a href="/">Home</a>
<a href="/blog">Blog</a>
{{/sidebar}}
</nav>
<article>
{{> content}}
</article>
</body>
</html>
{{> layout title="Edit User"}}
{{*inline sidebar}}
{{> @default}}
<a href="/users">Users</a>
{{/inline}}
{{*inline content}}
<form method="post">
<input type="text" />
<button type="submit">Edit User</button>
</form>
{{/inline}}
{{/layout}}
Looks interesting. I have several thoughts that will take some time to write down. A couple of things for now:
options.blocks.bar(context, ["hello"]);
{{#foo}}
{{*bar}}{{/bar}}
{{else}}
{{/foo}}
block params: I need to think about that some more, I didn't fully bake that bit.
inverse:
Named blocks are forbidden in chained inverse sections but all named blocks are made available to all chained invocations.
attempted to address that. Basically these are fine:
{{#foo}}
{{*bar}}bar{{/bar}}
{{else}}
{{bar}}
{{/foo}}
{{^foo}}
{{*bar}}bar{{/bar}}
{{bar}}
{{/foo}}
This is not:
{{#foo}}
{{else}}
{{*bar}}bar{{/bar}}
{{bar}}
{{/foo}}
This was an attempt to avoid confusion, but I can see how others could arise. Perhaps you're right that disallowing inverse entirely when named blocks exist is a better option.
@mmun for block parameters, the calling from a helper case is simple, we can use the same syntax. I'm not sure the best way to call a named block that defines block parameters from a template though.
This comes to mind, but I'm not sure if that's confusing at all.
{{*foo as |bar|}}
{{bar}}
{{/foo}}
{{foo |baz|}}
In ember we use {{yield foo bar}}
to yield block params to the main block. I imagine with named blocks we'd add a to=
argument, like
{{yield currentUser to=header}}
I'd consider prefixing block references to avoid shadowing the scope and generally making the "magic" more explicit, like with @data
, e.g.
{{yield currentUser to=*header}}
Updated the description after an offline discussion with @mmun. Moving this more to inline partials as the feature and resolved some potential ambiguities in the implementation and behavior.
We should do a powwow sometime soon; I'm a little concerned about some frog-boiling complexity happening at the syntax level here:
{{#> foo}}
{{*bar}}
Blah
{{/bar}}
{{/foo}}
One of the nice things about the original grammar was that there were relatively few "mode switches" and most things were done with keywords (else
) or helpers. I'm not in love with the sigil-heavy direction here.
@wycats what alternatives are you thinking?
One concern that I do have is compatibility concerns that will be introduced by using named operators. @mmun's example above (we discussed offline) directly conflicts with https://github.com/walmartlabs/thorax/blob/master/src/helpers/template.js#L23 for example and there's no real way we can avoid that as there are no unused reserved keywords. One idea would be some sort of non-id keyword space:
{{*inline 'bar'}}
Blah
{{/*inline}}
Or similar and then we would own the whole *
namespace (s/*/whatever/). That would certainly be clearer than trying to come up with more and more logical operators using the limited available non-ID token space and allow for more future language-level operations.
@kpdecker I have to noodle on this a little more, but I'd like to point out that the closing /*
wouldn't be necessary (just as you don't need a closing /#
for blocks).
I wonder if there's some grammar tricks we could play, taking advantage of some character that's disallowed in identifiers to form compound keywords.
FWIW: I'm pretty sad that &
isn't available (for largely silly reasons), because "and foo" reads pretty well in many of the relevant use-cases:
{{#if foo}}
<p>{{foo}}</p>
{{&else}}
<p>Nothing</p>
{{/if}}
I'm still digesting the ideas presented here but there's a considerable overlap of concerns with my question here - #1023
When considering these issues I've been trying to break them up as small as possible and then find solutions that fit as widely as possible. Applying this to the given topic I've come to the following -
{{>customHelper otherHashParam=1}}
{{hash key="id" value="unique_val"}}
{{#hash key="moreContent"}}
multiline content
{{/hash}}
{{/customHelper}}
thats it for now although i'll try to chew this over a bit more.
We've been using something like this for a while now, roughly (not exactly) we use:
Handlebars.registerHelper('registerPartial', function(name, options) {
Handlebars.registerPartial(name, options.fn);
});
We use it to pass blocks as arguments to helpers, e.g:
{{#registerPartial "foo"}}Foo?{{/registerPartial}}
{{#registerPartial "bar"}}Bar!{{/registerPartial}}
{{> (switch x A="foo" B="bar")}}
And to other partials, e.g:
{{#registerPartial "foo"}}Foo?{{/registerPartial}}
{{#registerPartial "bar"}}Bar!{{/registerPartial}}
{{> show partial1="foo" partial2="foo"}}
Basically, this, combined with partial context arguments, enables you write your templates more like you would write your Javascript code with the inline partials serving as callback or inline functions allowing for advanced composition and facilitating reduced repetition.
It could also be achieved by creating a bunch of separate partial template files etc, but that quickly becomes unmanageable.
I would be very happy if this would be properly supported by Handlebars itself as this current approach is quite inefficient; recreating the block function and re-registering the partial each time the containing template or partial is rendered.
This discussion has sort of stalled, so I went ahead and implemented a potential solution under #1082. I think this handles most of the cases described here and is a bit clearer than the sigil-based solution. Dynamic definition is not currently implemented but it could potentially be via conditional decorators but I'm not sure about that use case.
Closing this issue out to focus discussion on #1082.
@kpdecker Can "layouts" be nested? I was thinking/hoping this feature could replace shannonmoeller/handlebars-layouts for the basic use case of nesting.
doctype.hbs
<!doctype html>
<html lang="en-us">
<head>
<title>Untitled</title>
</head>
<body>
{{#> content}}
{{/content}}
</body>
</html>
layout.hbs
{{#> doctype}}
{{#*inline "content"}}
layout
{{#> subcontent}}
{{/subcontent}}
{{/inline}}
{{/doctype}}
index.hbs
{{#> layout}}
{{#*inline "subcontent"}}
subcontent
{{/inline}}
{{/layout}}
Gives me this error:
TypeError: Cannot read property 'inline' of undefined
at Function.eval (eval at compile (.../node_modules/handlebars/dist/cjs/handlebars/compiler/javascript-compiler.js:132:36), <anonymous>:5:18)
at executeDecorators (.../node_modules/handlebars/dist/cjs/handlebars/runtime.js:286:15)
at wrapProgram (.../node_modules/handlebars/dist/cjs/handlebars/runtime.js:222:10)
at Object.program (.../node_modules/handlebars/dist/cjs/handlebars/runtime.js:126:26)
at Object.eval (eval at createFunctionContext (.../node_modules/handlebars/dist/cjs/handlebars/compiler/javascript-compiler.js:254:23), <anonymous>:5:102)
at main (.../node_modules/handlebars/dist/cjs/handlebars/runtime.js:173:32)
at Object.ret (.../node_modules/handlebars/dist/cjs/handlebars/runtime.js:176:12)
at Object.ret [as layout] (.../node_modules/handlebars/dist/cjs/handlebars/compiler/compiler.js:525:21)
at Object.invokePartialWrapper [as invokePartial] (.../node_modules/handlebars/dist/cjs/handlebars/runtime.js:72:46)
at Object.eval (eval at createFunctionContext (.../node_modules/handlebars/dist/cjs/handlebars/compiler/javascript-compiler.js:254:23), <anonymous>:5:31)
I am expecting this output:
<!doctype html>
<html lang="en-us">
<head>
<title>Untitled</title>
</head>
<body>
layout
subcontent
</body>
</html>
@michaellopez at the surface it looks like that should work. Do you mind posting this as a separate bug and I'll take a look into what might be going wrong?
@michaellopez I was able to reproduce this and get a fix. 05b82a203e729b5caa206aabfef5456d84e10746 Published under 4.0.1. Thanks for the bug. Please feel free to file new issues if you run into any other problems!
@kpdecker Thank you for your fast response. The fix fixes the use case here. I don't know if there is any technical difference between basic partials and block partials in this case but failover content does not work in the above use case. I created a follow up in #1089. Please have a look at that, thank you.
Most helpful comment
Example covering the use cases described in #893