Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inline Partials #1018

Closed
kpdecker opened this issue May 5, 2015 · 18 comments
Closed

Inline Partials #1018

kpdecker opened this issue May 5, 2015 · 18 comments
Labels
Milestone

Comments

@kpdecker
Copy link
Collaborator

kpdecker commented May 5, 2015

Inline Partials

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.

Defining

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}}

Calling from helpers

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.

Calling from templates

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 Blocks

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:

  foo
  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.

@kpdecker kpdecker added this to the Next milestone May 5, 2015
@kpdecker
Copy link
Collaborator Author

kpdecker commented May 5, 2015

@wycats @mmun RFC on the above. This is trying to provide a fix or the language primitives for #208, #404, and #893.

@kpdecker
Copy link
Collaborator Author

kpdecker commented May 5, 2015

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}}

@mmun
Copy link
Contributor

mmun commented May 5, 2015

Looks interesting. I have several thoughts that will take some time to write down. A couple of things for now:

  • blocks should support block params, e.g. options.blocks.bar(context, ["hello"]);
  • The behaviour of named blocks + inverses seems confusing. I'd be happy if named blocks opted you out of inverses completely. e.g. This should give a syntax error...
{{#foo}}
  {{*bar}}{{/bar}}
{{else}}
{{/foo}}

@kpdecker
Copy link
Collaborator Author

kpdecker commented May 5, 2015

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.

@kpdecker
Copy link
Collaborator Author

kpdecker commented May 5, 2015

@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|}}

@mmun
Copy link
Contributor

mmun commented May 5, 2015

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}}

@kpdecker kpdecker changed the title Named Blocks Inline Partials May 6, 2015
@kpdecker
Copy link
Collaborator Author

kpdecker commented May 6, 2015

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.

@wycats
Copy link
Collaborator

wycats commented May 7, 2015

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.

@kpdecker
Copy link
Collaborator Author

kpdecker commented May 7, 2015

@wycats what alternatives are you thinking?

@kpdecker
Copy link
Collaborator Author

kpdecker commented May 7, 2015

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.

@wycats
Copy link
Collaborator

wycats commented May 8, 2015

@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}}

@mattkime
Copy link

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 -

  • defining inline partials is an interesting idea that combine several strategies that may be useful in other contexts. to break it down - in document definition of partials, scoping of partials, passing of partial content to other partials and helpers (similar to hash params), wrapping of partial content in conditionals (it should work! - this doesn't seem fully resolved), combining partial content with default content.
  • i'm not entirely clear what specific problem inline partials aim to solve. It would be nice to have a before an after example.
  • inline partials occupy a remarkably similar position to hash parameters. I'm curious if the two concepts could be combined. i think there's an opportunity for the inline partial style of defining content to be an alternative way of expressing hash parameters. A stronger format of this question - do we benefit from passing hash params and partials as separate things?
  • i really like the solution presented for [Proposal] Section blocks. #893
  • i completely agree with wycat's comment about sigil-heavy. i wonder if we could go with a named helper instead of a symbol, dovetailing with hash parameters (i guess the problems with this have been discussed but i'll still leave this here) -
{{>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.

kpdecker added a commit that referenced this issue Aug 14, 2015
This allows for failover for missing partials as well as limited templating ability through the `{{> @partial-block }}` partial special case.

Partial fix for #1018
kpdecker added a commit that referenced this issue Aug 15, 2015
This allows for failover for missing partials as well as limited templating ability through the `{{> @partial-block }}` partial special case.

Partial fix for #1018
@difosfor
Copy link

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.

kpdecker added a commit that referenced this issue Aug 19, 2015
This allows for failover for missing partials as well as limited templating ability through the `{{> @partial-block }}` partial special case.

Partial fix for #1018
kpdecker added a commit that referenced this issue Aug 22, 2015
This allows for failover for missing partials as well as limited templating ability through the `{{> @partial-block }}` partial special case.

Partial fix for #1018
kpdecker added a commit that referenced this issue Aug 22, 2015
Allows for partials to be defined within the current template to allow for localized code reuse as well as for conditional behavior within nested partials.

Fixes #1018
kpdecker added a commit that referenced this issue Aug 22, 2015
Allows for partials to be defined within the current template to allow for localized code reuse as well as for conditional behavior within nested partials.

Fixes #1018
@kpdecker
Copy link
Collaborator Author

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.

flenter pushed a commit to flenter/handlebars.js that referenced this issue Aug 27, 2015
This allows for failover for missing partials as well as limited templating ability through the `{{> @partial-block }}` partial special case.

Partial fix for handlebars-lang#1018
@michaellopez
Copy link

@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>

@kpdecker
Copy link
Collaborator Author

kpdecker commented Sep 2, 2015

@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?

@kpdecker
Copy link
Collaborator Author

kpdecker commented Sep 3, 2015

@michaellopez I was able to reproduce this and get a fix. 05b82a2 Published under 4.0.1. Thanks for the bug. Please feel free to file new issues if you run into any other problems!

@michaellopez
Copy link

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants