-
-
Notifications
You must be signed in to change notification settings - Fork 408
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
Yieldable named blocks #460
Conversation
153ae6a
to
2f4c4e2
Compare
@wycats it is very exciting to see this feature moving forward, thank you for working on it! I noticed that this RFC specifically does not make any changes to Specifically, as a component author I want to allow a caller to pass any number of things (plain string, closure component, or a named block). In order to do that (unless I've misunderstood things in this RFC) is via something akin to the following snippet in the component's template: <article>
<header>
{{#if (has-block 'title')}}
{{yield to="title"}}
{{else}}
{{@title}}
{{/if}}
</header>
<section>
{{if (has-block 'body')}}
{{yield to='body'}}
{{else}}
{{yield}}
{{/if}}
</section>
</article> This allows the callsite to use any of the following invocations: <Article @title={{@article.title}}>
{{! block contents here }}
</Article>
<Article @title={{component 'special-article-title'}}>
{{! block contents here }}
</Article>
<Article>
<:title><b>{{@article.title}}</b></:title>
<:body>
{{! block contents here }}
</:body>
</Article> While thats totally fine its a bit gnarly to type out each time, and since blocks themselves can't be passed (mentioned in the RFC as capturing) it can't be abstracted away into a shared control flow component. What is the plan for this? Just keep the verbosity here, and come up with some clever AST "macros"? I'm also curious how the longer term plan to be able to pass these blocks around would work, have you thought much about that? Since it seems that this design intentionally does not limit named blocks to exist in the same conceptual space as named arguments, naming conflicts are likely (see API I demo'ed just above :P ), that seems vaguely concerning unless we have a relatively good idea how we plan to reference a passed in block (and that that isn't as a named argument). |
@rwjblue The concept of "unified renderables" as envisioned in #226 was described (and carefully specified) in more detail in #432, so I left it out of this RFC. From the perspective of this RFC, if you have a component that takes a block for <Article>
<:title>{{@article.title}}</:title>
<:body>
{{! block contents here }}
</:body>
</Article> It's slightly more verbose than It also makes upgrading from a simple block to a block that takes named arguments easier to teach, implement (and use), which was a thorny aspect of the original proposal. That said, when we do decide on a strategy for reifying blocks in a future RFC, we will have to decide what happens when you put one inside of |
My thinking is that we'd use <Article :title=:headline /> In this example, the |
Sure, I see what you mean here but until the broader ecosystem can assume newer Ember versions (which support this new named block syntax) addon components would have to continue to use the snippet I demoed above (which kinda sucks). It would be nice to have something nicer for them to use, but again maybe we could do it via an AST transform ala a macro. |
Really like this proposal overall! I think this is well-scoped, and minimal, without blocking us from iterating on it in the future. Most of my concerns are for future functionality actually, I just want to note them down here so they're available when we get around to it:
|
@wycats really exciting to see this feature moving forward and coming to fruition. While after reading for a bit I was able to parse the example in the "block parameters" section it was a bit unclear what was the actual result. On first glance it looked like the positional params were fallbacks if the block was not invoked. It took some head scratching to put together that it worked like normal positional params to the I'm not sure how to best improve this (maybe different variable/param names since |
Playing around with the HBS a little I came up with this possible example (slightly modified from the existing example: <Article @article={{this.article}}>
<:header as |title author|>
<h1>{{title}} by {{author.firstName}}</h1>
</:header>
<:body as |post|>
<div>{{post.body}}</div>
</:body>
</Article>
<article>
<header>{{yield @article.title @article.author to='header'}}</header>
<section>{{yield @article.post to='body'}}</section>
</article> This not only decouples some of the more repetitive naming (especially |
Does this RFC propose that block names MUST be unique? i.e. would the following cause a compile-time error? <Article>
<:byline>Alice</:byline>
<:byline>Bob</:byline>
</Article> |
I think we should allow on duplications. It may be useful to display the same content in different places with different format. Eg you may want to display article's created At in header and footer. So you will be able to configure one block to see the result in both places. |
@Exelord interesting, could you provide a code snippet to go with that example? |
Oh sorry, you are actually right! But I think in that case it should just overwrite the prevoius one. <Article>
<:byline>Alice</:byline>
{{#if isBob}}
<:byline>Bob</:byline>
{{/if}}
</Article> The other important question (I think) that need to be clarified is the block's name format, as it is not clear from the example. eg, it is allowed to name blocks like From angle bracket convention I assume the correct syntax, in that case, is |
And I think we should also consider restricting <MyArticle>
<:else>
No article :(
</else>
</MyArticle> will equal {{#my-article}}
{{else}}
No article :(
{{/my-article}} Otherwise, to keep the backward compatibility we would have to call |
@Exelord that makes sense, though I think we’re talking about different (but related) things. My comment refers to the presence of multiple blocks with the same name, whereas yours refers to yielding to a single block multiple times. Both worth clarifying, for sure. EDIT: In response to your edit in #460 (comment) — yes, that is an interesting case. |
We discussed this in todays Ember, and we are moving this into final comment period! |
We discussed this in today's core team meeting, and are moving it into final comment period. |
OK, y'all it's that time, let's do this! |
Does this have a tracking issue yet? |
Yep, over in emberjs/rfc-tracking#44 |
It's been some time since this has landed in ember, and I'd say an absolute critical missing feature is the ability to forward blocks without needing to explore if/else has-block conditionals. For example, say you are wrapping a component that has named blocks. {{!-- wrapper.hbs --}}
<Wrapped>
<:block-a>
{{yield to="block-a"}}
</:block-a>
<:block-b as |any number of args|>
{{yield any number of args to="block-b"}}
</:block-b>
</Wrapped> But now let's pretend the act of passing a block at all adds additional markup (which is handy in many cases!), but for your wrapper component you need to ensure that you don't force the rendering of that additional markup when the consumer of your wrapper component does not pass those named blocks -- the wrapper.hbs now has to look like this: {{!-- wrapper.hbs --}}
{{#if (has-block "block-a")}}
<Wrapped>
<:block-a>
{{yield to="block-a"}}
<:block-a>
</Wrapped>
{{else if (has-block "block-b")}}
<Wrapped>
<:block-b as |any number of args|>
{{yield any number of args to="block-b"}}
</:block-b>
</Wrapped>
{{else if (and (has-block 'block-a') (has-block 'block-b'))}}
{{!-- NOTE: at the time of writing `and` is not a built in helper -- implementation is in progress tho --}}
<Wrapped>
<:block-a>
{{yield to="block-a"}}
</:block-a>
<:block-b as |any number of args|>
{{yield any number of args to="block-b"}}
</:block-b>
</Wrapped>
{{else}}
<Wrapped />
{{/if This is especially deadly to productivity as the number of named blocks increases (which is very common in layout-style components). The number of conditionals you need is the square of the total number of blocks. So, if you have a max of 9 named blocks, you need 81 if/else conditionals to properly wrap that component with only 9 named blocks. I think in one of the named-blocks RFCs, someone suggested passing the named blocks kinda like arguments, but with the prefixing the above example would be: <Wrapped :block-a=:block-a :block-b=:block-b /> or something. |
maybe a |
Here is an additional (maybe more concrete) demonstration and use case that came up with the need for this feature: (was working on this with @RuslanZavacky ) <T @key="test-key" @label="my label" as |wrapper|>
<wrapper>
<:text>
some text that is dynamic {{wooo}}
</:text>
<:link>
<a href="#">link</a>
</:link>
</wrapper>
</T> But it'd be ideal if we didn't need it at all: <T @key="test-key" @label="my label">
<:text>
some text that is dynamic {{wooo}}
</:text>
<:link>
<a href="#">link</a>
</:link>
</T> but we can't do that unless we can forward all blocks passed to |
Rendered