emberjs / ember.js

Ember.js - A JavaScript framework for creating ambitious web applications
https://emberjs.com
MIT License
22.47k stars 4.21k forks source link

Template inheritance #11167

Closed RobbieTheWagner closed 9 years ago

RobbieTheWagner commented 9 years ago

I think something like this should be supported, especially with the push towards components.

I want to have a parent component with a template like:

<div>Foo</div>
<div>Bar</div>
{{yield}}

And then I would expect for child components, that extend that parent component to just be able to define a template of:

<div>Baz</div>

And have it automatically put that in the yield, so whenever I used the child component, it would show up as:

<div>Foo</div>
<div>Bar</div>
<div>Baz</div>
selvagsz commented 9 years ago

+1 for this. I ran into a similar use case. I'm not sure whether it is semantically correct to inherit the template. I worked around it by specifying the layout name for the child component and rendering it as a block.

mixonic commented 9 years ago

@rwwagner90 This seems pretty unlikely to be adopted- Overriding how and where a component {{yield}}s is very much something a child component (via inheritance) would want to do. IMO you want a way to call {{super}} from a template to accomplish what you want here.

But templates play absolutely no role in the inheritance flow right now, and making them aware of it is a rough sell. Hm.

RobbieTheWagner commented 9 years ago

@mixonic it just seems counter intuitive to me to not be able to do some sort of inheritance. If I have a parent component that has a lot of common functionality and DOM elements, I obviously want to have my child component extend it for the JS functionality, but then there is no provision for pulling in the common DOM from the parent template, unless you use partials, which blows away the point of the component inheritance or actually use component blocks, which forces you to duplicate code.

jmurphyau commented 9 years ago

I've managed to get the behaviour you're describing working.. I'm not sure I would recommend it and I'm likely going to move away from it in favour of tagless components.

I achieved this by setting the components layout property to the parent component template, and the template property to the child (current) component.

If you're using Ember CLI with pod structure there is a particular way to get this to work. Basically when a component is resolved Ember will try find the template.hbs file - once that's found it uses that template as the layout property - completely ignoring if you've explicitly set a layout property on your component.js - you need to trick Ember by calling the child component template something other than template.hbs

Anyway I'm not sure I've explained it well enough - but if I haven't then I have an example that might help. I've previously logged this as an issue with Ember CLI (https://github.com/ember-cli/ember-cli/issues/3496) - in this issue there is a link to a repo (https://github.com/jmurphyau/component-template-issue) which shows how I got the inheritance to work.

mixonic commented 9 years ago

The layout represents a components shadow dom. The template is the "light" dom, of the contents of yield that gets rendered in the outer scope. I definitely don't think that is a viable long-term solution, and may have some weird edge cases.

RobbieTheWagner commented 9 years ago

So let's assume for a minute that I have an inheritance chain like parent -> child -> child -> child. What would be the recommended way to use all the common elements from each level in the final child component? It would make sense to me to have some way to have the templates work together.

mixonic commented 9 years ago

@rwwagner90 a compiled template is just an object stored on a property. Like any other property on an instance, there is no way to arbitrarily yank parts of an object you've replaced from a superclass at the same property.

I think I suggested a fairly viable idea with {{super but the implementation and ramifications across apps are unclear (like in init, do you always call super just in case you have a parent? Seems onerous).

I hope you agree there isn't a trivial solution and the problem will require a lot of thought and api design work.

RobbieTheWagner commented 9 years ago

Yeah, I definitely don't think there is a trivial solution. There are some "easier" solutions, but they could have bad implications. I'm not sure how to get this right, but I think it would be very useful to have. Changing everything over to components, but then still requiring you to use partials because this isn't supported just feels incomplete to me. I realize this may not be implemented anytime soon, but do you agree there is a need there @mixonic?

jmurphyau commented 9 years ago

Could you do something like this:

child-component/template.hbs

{{#parent-component}}
  This is the child component
{{/parent-component}}

parent-component/template.hbs

{{#grandparent-component}}
  This is the parent component
  {{yield}}
{{/grandparent-component}}

grandparent-component/template.hbs

This is the grandparent component
{{yield}}
jmurphyau commented 9 years ago

That outputs the HTML below when calling {{child-component}}

<div id="ember434" class="ember-view grandparent parent child">
  <div id="ember441" class="ember-view grandparent parent">
    <div id="ember446" class="ember-view grandparent">
      This is the grandparent component
      This is the parent component
      This is the child component
    </div>
  </div>
</div>

This was/is a problem for me and this is where tagless components are have an advantage - if the {{grandparent-component}} and {{parent-component}} both have tagName: '' you only end up with one div:

<div id="ember434" class="ember-view grandparent parent child">
  This is the grandparent component
  This is the parent component
  This is the child component
</div>
RobbieTheWagner commented 9 years ago

@jmurphyau we did consider an option along those lines, but won't wrapping each of those like that cause us to have to pass variables back up the chain to the grandparent component?

jmurphyau commented 9 years ago

I would expect so - yeah.

RobbieTheWagner commented 9 years ago

I'd like to avoid having to do that. There may not be a way around it though. Just would really like to see this implemented. Would be very slick.

stefanpenner commented 9 years ago

They may be good to explore in an RFC or RFC issue, as this appears as a feature request not a bug report.

RobbieTheWagner commented 9 years ago

@stefanpenner what should I do to start the discussion in a more appropriate place?

mmun commented 9 years ago

@rwwagner90 Make an issue (not a PR) on the RFCs repo emberjs/rfcs and gather feedback. I'm interested in seeing actual use cases rather than foo and bar. We should have this discussion ASAP so the feedback can be taken into account for the work on angle bracket components.

RobbieTheWagner commented 9 years ago

@mmun I made an issue in rfcs. https://github.com/emberjs/rfcs/issues/77

It's been awhile since I had an actual use case for this, but I'll try to come up with a better example than foo and bar.

hoIIer commented 9 years ago

hello, I am running into an issue where I have a parent route and I want the template to have some default content, but render over that with a child route.

Example:

import Ember from 'ember'
import config from './config/environment'

Router = Ember.Router.extend
  location: config.locationType

Router.map ->

  # Channels
  @route 'channels', path: '/', ->
    @route 'channel', path: ':slug', ->

      # Content
      @route 'content', path: ':slug'

export default Router

# templates/channels/index.hbs

<main class="main channels container-fluid">
  <div class="row">
    <h1>The brown fox jumped over the...</h1>
    {{#outlet}}
      <p>Here I am, the content for my cool component that displays within the parent index view.</p>
    {{/outlet}}
  </div>
</main>

# templates/channels/channel.hbs

<p>Here I am, the content for the child route/template</p>

Is this possible? it seems to touch in template inheritance which is how I found this issue... Is there any way to achieve this currently?