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

Named yields for components #72

Closed

Conversation

jonathanKingston
Copy link
Contributor

@rlivsey rlivsey mentioned this pull request Jul 13, 2015
@pzuraq
Copy link
Contributor

pzuraq commented Sep 17, 2015

To add onto this, I think that #64 was accepted to try to solve the same issues that named yields do. Named yields go one step further than contextual components, they provide a set template and a set constraint on the positioning and usage of contextual components.

Consider the following example: I have a parent component A, which has child components B and C. One B must appear in A no matter what in order for the component to function properly, but any number of Cs could appear. With contextual components, this may look like:

<component-a as |a|>
    <a.b />

    <a.c />
    <a.c />
</component-a>

This is pretty terse, but it forces the user to always include a B (instead of potentially having a default template for B), and it requires some error handling to make sure that the B always exist. In addition, the positioning of the B may be important - it might need to be the first child component, or the last - which incurs further error handling costs.

With named yields, we can enforce these conditions by design. Child components that are required will always be included, and they will always be in the correct place. They can also have sane defaults for yields, which will reduce boilerplate by allowing users to opt-out of standard conventions rather than forcing them to opt-in.

Unresolved Questions

  • How will slots work inside of each-based components? Consider:
<div>
  <div class="heading">{{yield name="heading"}}</div>
  {{#each things as |thing|}}
    <div>{{yield thing}}</div>
  {{/each}}
</div>

What would the HTMLbars usage of this component look like? Will each slot be aware of the items being yielded to it? Should slots then have some sort of as |...| convention as well?

@knownasilya
Copy link
Contributor

Could we do something similar as an addon using ember-wormhole? Here's my idea: yapplabs/ember-wormhole#39

@jonathanKingston
Copy link
Contributor Author

Not really the whole idea was to match the proposal of slots with minimal friction so it would somewhat have to avoid Ember complexities to avoid conflicting with how the proposal will play out in browsers.

From the spec:

<ul class="stories">
    <li><a href="//example.com/stories/1">A story</a></li>
    <li><a href="//example.com/stories/2">Another story</a></li>
    <li class="breaking" slot="breaking"><a href="//example.com/stories/3">Also a story</a></li>
    <li><a href="//example.com/stories/4">Yet another story</a></li>
    <li><a href="//example.com/stories/5">Awesome story</a></li>
    <li class="breaking" slot="breaking"><a href="//example.com/stories/6">Horrible story</a></li>
</ul>
<div class="breaking">
    <ul>
        <slot name="breaking"></slot> <!-- slot for breaking news -->
    </ul>
</div>
<div class="other">
    <ul>
        <slot></slot> <!-- slot for the rest of the news -->
    </ul>
</div>

@knownasilya
Copy link
Contributor

Here's an example of something close using ember-portal https://github.com/knownasilya/ember-yielded-portals

@Asherlc
Copy link

Asherlc commented Jan 25, 2016

This would be very useful -- Rails has an equivalent feature that I've used on many occasions and would love to have in Ember.

@pzuraq
Copy link
Contributor

pzuraq commented Jan 26, 2016

Agreed, definitely still think this is useful. I've looked at @knownasilya's adaptation and it's solid for now as a testing ground for the concept, but I think that at some point something like this should be added to Handlebars itself.

That said, slots is still a proposal and may not quite fit the use case of named yields exactly. I'd like to see what the core team's thoughts are on this proposal and named yields in general, this is a topic that has come up more than once (see this RFC).

@runspired
Copy link
Contributor

@knownasilya
Copy link
Contributor

I've written a simple wrapper addon over ember-portal to make doing this now a little nicer: https://github.com/knownasilya/ember-named-yields

@NuckChorris
Copy link

Love it, I just ranted in #emberjs IRC about how amazing this would be. Anyways, I feel the proposed syntax is kind of un-ergonomic. Basing this on a thin abstraction around the slots API seems wrong. Ember isn't Polymer, it's not a thin abstraction over future web APIs.

Rails already has a rather venerable way of doing this, of which an Ember port may look something like this:

<!-- panel-component.hbs -->
<div class="panel">
  {{#if (has-content-for 'header')}}
    <div class="panel-header">{{yield name='header'}}</div>
  {{/if}}
  <div class="panel-body">{{yield}}</div>
</div>
<!-- template.hbs -->
<panel-component>
  {{#content-for 'header'}}
    <h1 class="pull-left">Panel Header</h1>
    <button class="pull-right" {{action hidePanel}}>Hide</button>
  {{/content-for}}
  <p>This is some body text<p>
</panel-component>

Any text not in a content-for appears in the main yield (the helper just passes it up and renders emptiness). Unlike in Rails, this would simply provide the content to the nearest parent component, instead of passing it to the entire tree, making it not substantially crazier than passing the information via a component parameter. In fact, it could be interesting if content-for passed the block content in as a parameter with that name (though it could be an XSS problem, depending on how Handlebars is built, I don't know)

In the case of a loop component, probably the most useful (and reasonable) behavior would be to have the block saved per-iteration, so you could abstract over the format of each list element.

It might be possible to experiment with this using emberjs/ember.js#12285 (AKA #64), though I haven't tried implementing it yet. ember-wormhole could definitely do it, but it seems hamfisted to me, since it's aimed at completely circumventing the DOM tree, which just isn't necessary here. We're still in the same place in the DOM, just splitting the child nodes into logical groupings which can then be rendered by the parent as with a normal group of child nodes.

@pzuraq
Copy link
Contributor

pzuraq commented Apr 5, 2016

@NuckChorris I like this syntax, and if we are stepping away from HTML5 specs and the W3C then I think this would be a great choice. There was an RFC not too long ago that was rejected after some time in favor of this slots concept though, you can find it here.

In any case I think that there is definitely still a need for this among addon developers, it's just seems to be lower on the priority list for the core team. Would love to get some input from @rwjblue as to why the content-for approach was initially dropped, and what the core team is thinking with regards to multiple yields.

Ember isn't Polymer, it's not a thin abstraction over future web APIs.

On this, Ember may not be Polymer but it does aim to work with future web APIs rather than against them. This may be why the core team wants to go with slots, as it would prevent us from diverging from HTML components in the future.

Edit: Had a discussion with @runspired and he has some ideas for where this might be heading. Apparently he will be submitting a new RFC sometime soon, hopefully we'll here more about this then.

@foxnewsnetwork
Copy link

Actually, are we over-thinking named yields quite a bit? What's wrong with achieving named yields like so:
app/cyield-demo/template.hbs

<div class="cyield-demo__topnav">
  {{yield stuff "topnav"}}
</div>

<div class="cyield-demo__middle">
  {{yield stuff}}
</div>

<div class="cyield-demo__botnav">
  {{yield stuff "botnav"}}
</div>

Then, in some template where I'm using my component:

  {{#cyield-demo as |section stuff|}}
    section: {{section}} -
    {{#if (eq section "topnav")}}
      dogs - {{stuff}}
    {{else if (eq section "botnav")}}
      cat - {{stuff}}
    {{else}}
      All animals
    {{/if}}
  {{/cyield-demo}}

This then properly renders what I want:

<div id="ember440" class="ember-view">
<div class="cyield-demo__topnav">
      section: topnav -
      dogs - stuff

</div>

<div class="cyield-demo__middle">
      section: <!----> -
      All animals

</div>

<div class="cyield-demo__botnav">
      section: botnav -
      cat - stuff

</div>
</div>

@runspired
Copy link
Contributor

@foxnewsnetwork what's nice about what you have is that it's a natural middle step between what my proposal will be and where we are now :D

@machty
Copy link
Contributor

machty commented Jan 14, 2017

Sorry to do this, but here's yet another yet another RFC that solves a similar problem: #199

@knownasilya
Copy link
Contributor

Lol, no one can come to a consensus... lets improve on one instead of making a bunch 👍
That one might be yours @machty. Off to read it now..

@machty
Copy link
Contributor

machty commented Jul 30, 2017

Superseded by the now-merged Named Blocks RFC.

@machty machty closed this Jul 30, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants