Component Unification (angle brackets) #60

Closed
wants to merge 1 commit into
from

Conversation

Projects
None yet
@wycats
Member

wycats commented May 25, 2015

@rwjblue

This comment has been minimized.

Show comment
Hide comment
@rwjblue

rwjblue May 25, 2015

Member

I love the direction with this, but had a few questions while reading:

  • Whitelist API for using <some-thing-as-div></some-thing-as-div>: see emberjs/ember.js#11269 for an example.
  • What happens to the existing JS-land API's for these things (attributeBindings, classNameBindings, etc)? Are they additive to the marked {{root}} element? Are they ignored in angle bracket invocation?
  • Does this mean that every component must have a layout? If not, would the existing JS API's take effect?
  • Interop with current curly brace invocation semantics:
    • How can component authors handle both cases since it is up to the caller to use the "right" way?
    • Will helper methods be available to know if a given component was invoked as angle brackets vs curlies?
    • What will happen if {{root} or {{splat-attrs}} is seen when invoked via curlies?
Member

rwjblue commented May 25, 2015

I love the direction with this, but had a few questions while reading:

  • Whitelist API for using <some-thing-as-div></some-thing-as-div>: see emberjs/ember.js#11269 for an example.
  • What happens to the existing JS-land API's for these things (attributeBindings, classNameBindings, etc)? Are they additive to the marked {{root}} element? Are they ignored in angle bracket invocation?
  • Does this mean that every component must have a layout? If not, would the existing JS API's take effect?
  • Interop with current curly brace invocation semantics:
    • How can component authors handle both cases since it is up to the caller to use the "right" way?
    • Will helper methods be available to know if a given component was invoked as angle brackets vs curlies?
    • What will happen if {{root} or {{splat-attrs}} is seen when invoked via curlies?
@wycats

This comment has been minimized.

Show comment
Hide comment
@wycats

wycats May 25, 2015

Member

What happens to the existing JS-land API's for these things (attributeBindings, classNameBindings, etc)? Are they additive to the marked {{root}} element? Are they ignored in angle bracket invocation?

I think they would be an error when used with angle-bracket components.

Does this mean that every component must have a layout? If not, would the existing JS API's take effect?

If you want to describe anything about the component (tags, classes, attributes), a layout is much more expressive than the JS APIs, so "must have a layout" -> "doesn't need the JS APIs" 😉

Interop with current curly brace invocation semantics:

This is the stickiest question you raised and I don't have great answers yet. To flesh it out:

If I'm a component author and I want to be compatible with 1.12/1.13 curly bracket components, 2.1 curly brace invocation, and 2.1 angle bracket invocation, how do I do that?

I don't have a good solution in mind that supports doing all of the above in the same codebase; I kind of feel like you may need to maintain a separate branch for pre-2.0 components 😦

I'm open to anything though, and would love some brainstorming here.

Member

wycats commented May 25, 2015

What happens to the existing JS-land API's for these things (attributeBindings, classNameBindings, etc)? Are they additive to the marked {{root}} element? Are they ignored in angle bracket invocation?

I think they would be an error when used with angle-bracket components.

Does this mean that every component must have a layout? If not, would the existing JS API's take effect?

If you want to describe anything about the component (tags, classes, attributes), a layout is much more expressive than the JS APIs, so "must have a layout" -> "doesn't need the JS APIs" 😉

Interop with current curly brace invocation semantics:

This is the stickiest question you raised and I don't have great answers yet. To flesh it out:

If I'm a component author and I want to be compatible with 1.12/1.13 curly bracket components, 2.1 curly brace invocation, and 2.1 angle bracket invocation, how do I do that?

I don't have a good solution in mind that supports doing all of the above in the same codebase; I kind of feel like you may need to maintain a separate branch for pre-2.0 components 😦

I'm open to anything though, and would love some brainstorming here.

@rwjblue

This comment has been minimized.

Show comment
Hide comment
@rwjblue

rwjblue May 25, 2015

Member

I kind of feel like you may need to maintain a separate branch for pre-2.0 components

The main issue isn't with maintaining branches for supporting different Ember versions (that may just be a fact of life in some cases), but for maintaining branches for supporting the two different invocation styles within the same Ember version.

Member

rwjblue commented May 25, 2015

I kind of feel like you may need to maintain a separate branch for pre-2.0 components

The main issue isn't with maintaining branches for supporting different Ember versions (that may just be a fact of life in some cases), but for maintaining branches for supporting the two different invocation styles within the same Ember version.

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner May 25, 2015

Member

Another point we likely need to investigate is what happens with double curly components today, that contain a slash. {{users/my-form}} is valid today, but does not translate nicely to DOM. We could transition / to : which does translate. But that assumes we don't want to use : for something else.

Also what's the order of precedence between native WebComponents and ember component lookup. I suspect ember components should take a higher precedence. But what is the strategy when a collision occurs? What tagName is used? I suspect these collisions will be frustrating to debug if we do not message and provide escape hatches.

A finally point is the proposed scoped helper/component yielding + dot syntax. As the dot form is valid, it may be an WebComponents (unless they are more limited then document.createElement)

<my-form as |f|>
  <f.input>
</my-from>

How are theoretical collisions with <f.input dealt with?

I suspect the dot versions of tags will not be used, as css rules against them get pretty gnarly

Member

stefanpenner commented May 25, 2015

Another point we likely need to investigate is what happens with double curly components today, that contain a slash. {{users/my-form}} is valid today, but does not translate nicely to DOM. We could transition / to : which does translate. But that assumes we don't want to use : for something else.

Also what's the order of precedence between native WebComponents and ember component lookup. I suspect ember components should take a higher precedence. But what is the strategy when a collision occurs? What tagName is used? I suspect these collisions will be frustrating to debug if we do not message and provide escape hatches.

A finally point is the proposed scoped helper/component yielding + dot syntax. As the dot form is valid, it may be an WebComponents (unless they are more limited then document.createElement)

<my-form as |f|>
  <f.input>
</my-from>

How are theoretical collisions with <f.input dealt with?

I suspect the dot versions of tags will not be used, as css rules against them get pretty gnarly

@igorT

This comment has been minimized.

Show comment
Hide comment
@igorT

igorT May 25, 2015

Member

Does the class get overridden or do they concat when shadowed? It seems like concatting the classes would be very useful for customizing styling of the component, without having to copy all the original styles.

Member

igorT commented May 25, 2015

Does the class get overridden or do they concat when shadowed? It seems like concatting the classes would be very useful for customizing styling of the component, without having to copy all the original styles.

@jonathanKingston

This comment has been minimized.

Show comment
Hide comment
@jonathanKingston

jonathanKingston May 25, 2015

I'm struggling to see the motivation to unifying 'element' components with 'fragment' components.

For me this adds further risks that large changes will come to the component stack when keeping them conceptually separate (perhaps unifying the code base under the hood) will alleviate the risk for change later. Separating a unified concept seems harder than unifying in my opinion.

I like the idea of:

  • Components using custom tags by default
  • Their templates not requiring the top level tag, which is specified in the template
  • The tag is injected into the DOM with the properties the template specified it with

This would remove the need for all the bindings as:

{{!-- application.hbs --}}
<my-component class="thing" other-attr="thing" />
{{!-- my-component.hbs --}}
<div>
  {{other-attr}}
</div>

Renders:

<my-component class="thing" other-attr="thing">
  <div>
    thing
  </div>
</my-component>

Partials or fragments could mop up the rest as they currently do.

This roughly relates to my attrTypes RFC too: #35 which is designed to help filter attributes passed into the component template (So template authors have to conform to a specified API). This mapping could also be responsible for attributes that don't really make much sense being visible to the DOM like actions (unless a textual alternative could be used instead like a uuid which might help with debugging).

I'm struggling to see the motivation to unifying 'element' components with 'fragment' components.

For me this adds further risks that large changes will come to the component stack when keeping them conceptually separate (perhaps unifying the code base under the hood) will alleviate the risk for change later. Separating a unified concept seems harder than unifying in my opinion.

I like the idea of:

  • Components using custom tags by default
  • Their templates not requiring the top level tag, which is specified in the template
  • The tag is injected into the DOM with the properties the template specified it with

This would remove the need for all the bindings as:

{{!-- application.hbs --}}
<my-component class="thing" other-attr="thing" />
{{!-- my-component.hbs --}}
<div>
  {{other-attr}}
</div>

Renders:

<my-component class="thing" other-attr="thing">
  <div>
    thing
  </div>
</my-component>

Partials or fragments could mop up the rest as they currently do.

This roughly relates to my attrTypes RFC too: #35 which is designed to help filter attributes passed into the component template (So template authors have to conform to a specified API). This mapping could also be responsible for attributes that don't really make much sense being visible to the DOM like actions (unless a textual alternative could be used instead like a uuid which might help with debugging).

@jonathanKingston

This comment has been minimized.

Show comment
Hide comment
@jonathanKingston

jonathanKingston May 25, 2015

Also is it worth waiting for the slots syntax to calm down a little as I think this would likely solve the other issues here too.

For an easier migration could there be a property I could set on the components JavaScript to disable the replacement? All my components extend from a base level component so it would make a simple transition to disable this (However I would prefer the separation of fragment/partial style components to normal ones).

Also is it worth waiting for the slots syntax to calm down a little as I think this would likely solve the other issues here too.

For an easier migration could there be a property I could set on the components JavaScript to disable the replacement? All my components extend from a base level component so it would make a simple transition to disable this (However I would prefer the separation of fragment/partial style components to normal ones).

@wycats

This comment has been minimized.

Show comment
Hide comment
@wycats

wycats May 26, 2015

Member

the slots syntax

which syntax do you have in mind?

Member

wycats commented May 26, 2015

the slots syntax

which syntax do you have in mind?

@wycats

This comment has been minimized.

Show comment
Hide comment
@wycats

wycats May 26, 2015

Member

Does the class get overridden or do they concat when shadowed?

They concat. It would make sense to concat styles too.

Member

wycats commented May 26, 2015

Does the class get overridden or do they concat when shadowed?

They concat. It would make sense to concat styles too.

@jgwhite

This comment has been minimized.

Show comment
Hide comment
@jgwhite

jgwhite May 26, 2015

This shadowing only applies to attributes provided as strings in original invocation. This means that an invoker of a component can avoid shadowing any attribute by writing name={{"Yehuda"}} rather than name="Yehuda".

  • The resulting rule is very simple, and gives total control over shadowing, by default, to the invoker:

Any attribute specified as a string will be shadowed onto the root element, if one exists.

I may have missed something, but with this rule how would one bind a dynamic property to an attribute?

i.e.

<x-foo aria-label={{someComputedLabel}} />

jgwhite commented May 26, 2015

This shadowing only applies to attributes provided as strings in original invocation. This means that an invoker of a component can avoid shadowing any attribute by writing name={{"Yehuda"}} rather than name="Yehuda".

  • The resulting rule is very simple, and gives total control over shadowing, by default, to the invoker:

Any attribute specified as a string will be shadowed onto the root element, if one exists.

I may have missed something, but with this rule how would one bind a dynamic property to an attribute?

i.e.

<x-foo aria-label={{someComputedLabel}} />
@wycats

This comment has been minimized.

Show comment
Hide comment
@wycats

wycats May 26, 2015

Member

@jgwhite simple:

<x-foo aria-label="{{someComputedLabel}}" />
Member

wycats commented May 26, 2015

@jgwhite simple:

<x-foo aria-label="{{someComputedLabel}}" />
@jgwhite

This comment has been minimized.

Show comment
Hide comment
@jgwhite

jgwhite May 26, 2015

@wycats ah, let me check I’ve got this straight:

foo={{bar}} ---> this.attrs.foo = bar
foo="{{bar}}" ---> this.element.foo = bar

jgwhite commented May 26, 2015

@wycats ah, let me check I’ve got this straight:

foo={{bar}} ---> this.attrs.foo = bar
foo="{{bar}}" ---> this.element.foo = bar
@wycats

This comment has been minimized.

Show comment
Hide comment
@wycats

wycats May 26, 2015

Member

@jgwhite both cases would assign to attrs, but string attrs would also setAttribute.

Member

wycats commented May 26, 2015

@jgwhite both cases would assign to attrs, but string attrs would also setAttribute.

@jgwhite

This comment has been minimized.

Show comment
Hide comment
@jgwhite

jgwhite May 26, 2015

Okay cool, that makes sense. Another thing I’m not 100% clear on from the RFC:

If I want the in-DOM result of invoking:

<x-foo>Bar baz</x-foo>

to be:

<x-foo><span>Bar baz</span></x-foo>

What should x-foo.hbs look like?

jgwhite commented May 26, 2015

Okay cool, that makes sense. Another thing I’m not 100% clear on from the RFC:

If I want the in-DOM result of invoking:

<x-foo>Bar baz</x-foo>

to be:

<x-foo><span>Bar baz</span></x-foo>

What should x-foo.hbs look like?

@wycats

This comment has been minimized.

Show comment
Hide comment
@wycats

wycats May 26, 2015

Member

@jgwhite

<x-foo><span>Bar baz</span></x-foo>
Member

wycats commented May 26, 2015

@jgwhite

<x-foo><span>Bar baz</span></x-foo>
@jgwhite

This comment has been minimized.

Show comment
Hide comment
@jgwhite

jgwhite May 26, 2015

@wycats and if I wanted to recursively invoke <x-foo /> ?

jgwhite commented May 26, 2015

@wycats and if I wanted to recursively invoke <x-foo /> ?

@wycats

This comment has been minimized.

Show comment
Hide comment
@wycats

wycats May 26, 2015

Member

@jgwhite recursive invocations always require control flow so they wont be at the top of the template.

Member

wycats commented May 26, 2015

@jgwhite recursive invocations always require control flow so they wont be at the top of the template.

@jgwhite

This comment has been minimized.

Show comment
Hide comment
@jgwhite

jgwhite May 26, 2015

@wycats this is making more and more sense as I reason it through.

My initial concern was that in templates like this:

{{! x-foo.hbs}}

<x-foo>
  {{yield}}
  {{#if somePredicate}}
    <x-foo>Some consequent</x-foo>
  {{/if}}
</x-foo>

The difference in behaviour between the root <x-foo> and inner <x-foo> would cause confusion. Looking at it now however, the abstraction seems solid.

jgwhite commented May 26, 2015

@wycats this is making more and more sense as I reason it through.

My initial concern was that in templates like this:

{{! x-foo.hbs}}

<x-foo>
  {{yield}}
  {{#if somePredicate}}
    <x-foo>Some consequent</x-foo>
  {{/if}}
</x-foo>

The difference in behaviour between the root <x-foo> and inner <x-foo> would cause confusion. Looking at it now however, the abstraction seems solid.

@wycats

This comment has been minimized.

Show comment
Hide comment
@wycats

wycats May 26, 2015

Member

@jgwhite yeah I worked through it a bunch and came to the conclusion that any plausible recursion case requires some kind of control flow in order to avoid stack overflow.

You can think of the outermost tag as the component's signature, and any inner tags as recursive calls. @tomdale thinks we should make this the default conventional approach for templates that don't explicitly require a special tag for some reason.

Member

wycats commented May 26, 2015

@jgwhite yeah I worked through it a bunch and came to the conclusion that any plausible recursion case requires some kind of control flow in order to avoid stack overflow.

You can think of the outermost tag as the component's signature, and any inner tags as recursive calls. @tomdale thinks we should make this the default conventional approach for templates that don't explicitly require a special tag for some reason.

@jgwhite

This comment has been minimized.

Show comment
Hide comment
@jgwhite

jgwhite May 26, 2015

You can think of the outermost tag as the component's signature, and any inner tags as recursive calls.

Yeah, that’s very clear and very declarative.

@tomdale thinks we should make this the default conventional approach for templates that don't explicitly require a special tag for some reason.

Could you elaborate on this point? Do you mean that a conventional component template wouldn’t have an explicit signature element?

jgwhite commented May 26, 2015

You can think of the outermost tag as the component's signature, and any inner tags as recursive calls.

Yeah, that’s very clear and very declarative.

@tomdale thinks we should make this the default conventional approach for templates that don't explicitly require a special tag for some reason.

Could you elaborate on this point? Do you mean that a conventional component template wouldn’t have an explicit signature element?

@jonathanKingston

This comment has been minimized.

Show comment
Hide comment
@jonathanKingston

jonathanKingston May 26, 2015

@wycats the slots syntax as is being pushed for Shadow DOM:
https://lists.w3.org/Archives/Public/public-webapps/2015AprJun/0187.html

This appears to be what will land in browsers and I think will solve a lot of the same use cases this is solving.


I understand the merit of the duplicate tag within the template of the DOM, however personally that is a whole lot of components that need changing for something that improve only the 'partial' kind. This improvement isn't (as far as I am aware) ever going to be native to HTML. Much like services are essentially just Ember.Objects wouldn't it be safer to leave the concepts distinct but perhaps share the same code bases?

@wycats the slots syntax as is being pushed for Shadow DOM:
https://lists.w3.org/Archives/Public/public-webapps/2015AprJun/0187.html

This appears to be what will land in browsers and I think will solve a lot of the same use cases this is solving.


I understand the merit of the duplicate tag within the template of the DOM, however personally that is a whole lot of components that need changing for something that improve only the 'partial' kind. This improvement isn't (as far as I am aware) ever going to be native to HTML. Much like services are essentially just Ember.Objects wouldn't it be safer to leave the concepts distinct but perhaps share the same code bases?

@wycats

This comment has been minimized.

Show comment
Hide comment
@wycats

wycats May 27, 2015

Member

@jonathanKingston I'll reply to this more later, but fwiw, I don't think trying to map Ember's semantics very closely to hypothetical web components semantics is a winning strategy.

Web Components are still at a very early stage, and I personally think React's experience (and Ember 1.x's, for that matter), bears more heavily on the problem space than a future WC spec. If Ember and React both land on something inexpressible in WC, and it works out smashingly, that would be a good opportunity to attempt to persuade the standards process to make it expressible.

Member

wycats commented May 27, 2015

@jonathanKingston I'll reply to this more later, but fwiw, I don't think trying to map Ember's semantics very closely to hypothetical web components semantics is a winning strategy.

Web Components are still at a very early stage, and I personally think React's experience (and Ember 1.x's, for that matter), bears more heavily on the problem space than a future WC spec. If Ember and React both land on something inexpressible in WC, and it works out smashingly, that would be a good opportunity to attempt to persuade the standards process to make it expressible.

@jonathanKingston

This comment has been minimized.

Show comment
Hide comment
@jonathanKingston

jonathanKingston May 27, 2015

@wycats My issue isn't in driving the standards development at all with extend the web forward principles. However collapsing both concepts into one before they are complete seems a little premature.

Also as far as I was aware there was consensus from major browsers on this syntax.

@wycats My issue isn't in driving the standards development at all with extend the web forward principles. However collapsing both concepts into one before they are complete seems a little premature.

Also as far as I was aware there was consensus from major browsers on this syntax.

tomdale added a commit to emberjs/ember.js that referenced this pull request Jun 6, 2015

Implements RFC #60 (Component Unification)
emberjs/rfcs#60

This commit implements the proposed semantics for angle-bracket
components. The TLDR is that a component’s template represents its
“outerHTML” rather than its “innerHTML”, which makes it easier to
configure the element itself using templating constructs.

Some salient points:

1. If there is a single root element, the attributes from the invocation
   are copied onto the root element.
2. The invocation’s attributes “win out” over the attributes from the
   root element in the component’s layout.
3. Classes are merged. If the invocation says `class=“a”` and the root
   element says `class=“b”`, the result is `class=“a b”`. The rationale
   is that when you say `class=“a”`, you are really saying “add the `a`
   class to this element”.

A few sticky issues:

1. If the root element for the `my-foo` component is `<my-foo>`, we
   insert an element with tag name of `my-foo`. While this is intuitive,
   note that in all other cases, `<my-foo>` represents an invocation of
   the component. In the root position, that makes no sense, since it
   would inevitably produce a stack overflow.
   a. This “identity element” case makes it idiomatic to reflect the
      name of the component onto the DOM.
   b. Unfortunately, when we compile a template, we do not yet know
      what component that template is used for, and, indeed, whether it
      is even a template for a component at all.
   c. To capture the semantic differences, we do a bit of analysis at
      compile time (to determine *candidates* for top-level elements),
      and use runtime information (component invocation style and
      the name of the component looked up in the container) to
      disambiguate between a component’s element and an invocation of
      another component.
2. If the root element for the `my-foo` component is a regular HTML
   element, we use the `attachAttributes` functionality of HTMLBars to
   attach the attributes that the component was invoked with onto the
   root element.
3. In general, it is important that changes to attributes do not result
   in a change to the identity of the element that they are contained
   in. To achieve this, we reused much of the infrastructure built in
   `buildComponentTemplate`.

The consequence of (1) and (2) above is that the semantics are always
“a component’s layout represents its outerHTML”, even though the
implementation is quite different depending on whether the root element
is the same-named component or not.
@jayphelps

This comment has been minimized.

Show comment
Hide comment
@jayphelps

jayphelps Jun 8, 2015

I'm personally against allowing multiple top level elements w/ marking via {{root}}. I feel like it's not inline with the future of W3C web components and something we'd just end up eventually deprecating. For consumers of your components, it also makes it more confusing for CSS selectors since the sibling elements extraneously appear...rather unintuitive IMO.

I'm personally against allowing multiple top level elements w/ marking via {{root}}. I feel like it's not inline with the future of W3C web components and something we'd just end up eventually deprecating. For consumers of your components, it also makes it more confusing for CSS selectors since the sibling elements extraneously appear...rather unintuitive IMO.

@jayphelps

This comment has been minimized.

Show comment
Hide comment
@jayphelps

jayphelps Jun 8, 2015

However, the fact that this makes web components unsuitable for abstracting content in special contexts (tables, select boxes, and SVG) and poorly suited for abstracting multiple elements in general is an argument in favor of the "replacement" strategy.

I did read this btw. I just haven't concluded this is indeed the best approach. Still digesting..will likely have more targeting queries and comments.

However, the fact that this makes web components unsuitable for abstracting content in special contexts (tables, select boxes, and SVG) and poorly suited for abstracting multiple elements in general is an argument in favor of the "replacement" strategy.

I did read this btw. I just haven't concluded this is indeed the best approach. Still digesting..will likely have more targeting queries and comments.

+Is better than this:
+
+```handlebars
+<content class="box">{{yield}}</content>

This comment has been minimized.

@jayphelps

jayphelps Jun 8, 2015

the <content> tag name here is unintentionally confusing to people familiar with the unrelated Shadow DOM

I completely agree that using the tag vs. <element tagName="x-foo"> is superior. After all, it's what will output, why abstract it?

@jayphelps

jayphelps Jun 8, 2015

the <content> tag name here is unintentionally confusing to people familiar with the unrelated Shadow DOM

I completely agree that using the tag vs. <element tagName="x-foo"> is superior. After all, it's what will output, why abstract it?

@jamesarosen

This comment has been minimized.

Show comment
Hide comment
@jamesarosen

jamesarosen Jun 9, 2015

If a single running app can only have curly components or angle-bracket components, but not both, then upgrading a project will require doing all of the following simultaneously:

  • turn on angle bracket components, either by upgrading ember or setting a flag
  • upgrade all addons to their angle-bracket versions
  • upgrade all components in the project

Given what I know of addon maintenance and app schedules, that will never happen for any but the smallest projects.

Is there no way of letting individual components opt-in to the new behavior? It would be very nice if Ember could raise an exception when an app used an angle-bracket component via curlies.

If a single running app can only have curly components or angle-bracket components, but not both, then upgrading a project will require doing all of the following simultaneously:

  • turn on angle bracket components, either by upgrading ember or setting a flag
  • upgrade all addons to their angle-bracket versions
  • upgrade all components in the project

Given what I know of addon maintenance and app schedules, that will never happen for any but the smallest projects.

Is there no way of letting individual components opt-in to the new behavior? It would be very nice if Ember could raise an exception when an app used an angle-bracket component via curlies.

@igorT

This comment has been minimized.

Show comment
Hide comment
@igorT

igorT Jun 9, 2015

Member

My understanding was that an app would be able to mix and match, but that a single component is either an angle bracket or curly component

Member

igorT commented Jun 9, 2015

My understanding was that an app would be able to mix and match, but that a single component is either an angle bracket or curly component

@workmanw

This comment has been minimized.

Show comment
Hide comment
@workmanw

workmanw Jun 9, 2015

Contributor

that a single component is either an angle bracket or curly component.

Do you mean a single instance of a component or a single component class?

If you're referring to the later, that would be a pretty rough upgrade. Instead of migrating templates one by one, you'd have to cross the other vertice and upgrade every usage of a single component one by one. This would be much more tedious and make testing (actual QAing) much harder.

Contributor

workmanw commented Jun 9, 2015

that a single component is either an angle bracket or curly component.

Do you mean a single instance of a component or a single component class?

If you're referring to the later, that would be a pretty rough upgrade. Instead of migrating templates one by one, you'd have to cross the other vertice and upgrade every usage of a single component one by one. This would be much more tedious and make testing (actual QAing) much harder.

@jamesarosen

This comment has been minimized.

Show comment
Hide comment
@jamesarosen

jamesarosen Jun 9, 2015

@workmanw The component's template either includes the outer HTML or the inner HTML. It might be possible to do per-instance selection with some sort of additional flag like

<my-component exclude-wrapping-tag=true>

or

{{my-component include-wrapping-tag=true}}

but that seems... difficult at least.

@workmanw The component's template either includes the outer HTML or the inner HTML. It might be possible to do per-instance selection with some sort of additional flag like

<my-component exclude-wrapping-tag=true>

or

{{my-component include-wrapping-tag=true}}

but that seems... difficult at least.

@slindberg

This comment has been minimized.

Show comment
Hide comment
@slindberg

slindberg Jun 9, 2015

For those interested, here is the issue in React discussing whether components should be able to return multiple top-level elements (they currently do not). The TL;DR is that it's useful for avoiding wrapper elements for certain CSS and event handling situations, but comes at a cost (from that issue):

A big issue is that you lose the technical, practical and intuitive assumption of one component = one element/node

IMO, the decision boils down to whether or not it's important to maintain a direct conceptual mapping between DOM elements and components.

One of the goals of this proposal is to make the basic mental model simple, and "the component invocation is replaced with its template" is simple and easy to understand.

Multiple root elements run counter to this goal by making rendered DOM difficult to reason about. I think React's one component = one element assumption has immeasurable value that outweighs the headaches that people will experience with the edge cases.

However, it's worth considering that since the argument for a single root node is architectural and the argument against is born out of real developer frustration, there will always be people pushing for it.

For those interested, here is the issue in React discussing whether components should be able to return multiple top-level elements (they currently do not). The TL;DR is that it's useful for avoiding wrapper elements for certain CSS and event handling situations, but comes at a cost (from that issue):

A big issue is that you lose the technical, practical and intuitive assumption of one component = one element/node

IMO, the decision boils down to whether or not it's important to maintain a direct conceptual mapping between DOM elements and components.

One of the goals of this proposal is to make the basic mental model simple, and "the component invocation is replaced with its template" is simple and easy to understand.

Multiple root elements run counter to this goal by making rendered DOM difficult to reason about. I think React's one component = one element assumption has immeasurable value that outweighs the headaches that people will experience with the edge cases.

However, it's worth considering that since the argument for a single root node is architectural and the argument against is born out of real developer frustration, there will always be people pushing for it.

@tim-evans

This comment has been minimized.

Show comment
Hide comment
@tim-evans

tim-evans Jun 10, 2015

For me, this feels like the correct solution. However, I'm a bit reticent to this change because of the scope. If this lands, this should be before angle brackets become a thing in Ember, otherwise there is an overhead to those using angle brackets having to convert from an "old" syntax to a "new" synatax (where the old syntax is barely months old).

Could there be a discussion with this RFC on the migration process? I assume at some point ember will have an end of life for curly brackets, and we'll have to jump on the angle bracket train. If we're to do so, it would be a boon to have tools that aid in the conversion of these.

As for the conversation about {{root}} and whatnot, I am of the opinion that components that have multiple root elements should have to manage that in the component. I think having this.$() return multiple children is reasonable and less surprising. this.element on the other hand, should probably be handled by the author of the component. I'm still waffling on how I feel specifically on these points, but I feel like it might be best to go with a single root, then have another RFC for multiple roots. That way we can move forward the implementation of the unification and still discuss and work out the details of multiple roots.

For me, this feels like the correct solution. However, I'm a bit reticent to this change because of the scope. If this lands, this should be before angle brackets become a thing in Ember, otherwise there is an overhead to those using angle brackets having to convert from an "old" syntax to a "new" synatax (where the old syntax is barely months old).

Could there be a discussion with this RFC on the migration process? I assume at some point ember will have an end of life for curly brackets, and we'll have to jump on the angle bracket train. If we're to do so, it would be a boon to have tools that aid in the conversion of these.

As for the conversation about {{root}} and whatnot, I am of the opinion that components that have multiple root elements should have to manage that in the component. I think having this.$() return multiple children is reasonable and less surprising. this.element on the other hand, should probably be handled by the author of the component. I'm still waffling on how I feel specifically on these points, but I feel like it might be best to go with a single root, then have another RFC for multiple roots. That way we can move forward the implementation of the unification and still discuss and work out the details of multiple roots.

@tomdale

This comment has been minimized.

Show comment
Hide comment
@tomdale

tomdale Jun 11, 2015

Member

This deeply concerns me.

@tim-evans You shouldn't let it concern you. It's easy to fall into a trap where you can add incremental complexity that is easy for advanced users like yourself to absorb, but becomes a huge barrier for new entrants. Obviously new users are not my only concern; this feature is just as much for me as it is for them, and I hope you'll agree I'm a somewhat advanced user.

...isn't that exactly what this RFC is proposing...?

@jayphelps No, the transition between the two styles is very seamless and the mental model you can describe in documentation is very simple: "Whatever you put into the component template is what you will see in DOM." I know this model feels very different to what you're used to, but having been using it for the past few weeks, I promise it is more intuitive than the existing system. I hope you can give it a try in your own apps to see what I mean.

Member

tomdale commented Jun 11, 2015

This deeply concerns me.

@tim-evans You shouldn't let it concern you. It's easy to fall into a trap where you can add incremental complexity that is easy for advanced users like yourself to absorb, but becomes a huge barrier for new entrants. Obviously new users are not my only concern; this feature is just as much for me as it is for them, and I hope you'll agree I'm a somewhat advanced user.

...isn't that exactly what this RFC is proposing...?

@jayphelps No, the transition between the two styles is very seamless and the mental model you can describe in documentation is very simple: "Whatever you put into the component template is what you will see in DOM." I know this model feels very different to what you're used to, but having been using it for the past few weeks, I promise it is more intuitive than the existing system. I hope you can give it a try in your own apps to see what I mean.

@jamesarosen

This comment has been minimized.

Show comment
Hide comment
@jamesarosen

jamesarosen Jun 11, 2015

Whatever you put into the component template is what you will see in DOM.

This rule is great for app authors. It's less good for addon authors (especially those writing mixins for components) if it allows multiple root elements. If components require multiple root elements -- by validating the template at some point -- then it's a great rule for both.

In the past, Ember.Component served two purposes: single root element (tagName="anything") and fragment (tagName=""). There are many benefits to restricting Ember.Component to the former, as @stefanpenner has described.

There are also benefits to having something to support the fragment case. That case would have looser restrictions on the template, but less support for other things like CSS and event handlers. For this, I like the {{fragment}} helper and Ember.Fragment class suggested above.

Whatever you put into the component template is what you will see in DOM.

This rule is great for app authors. It's less good for addon authors (especially those writing mixins for components) if it allows multiple root elements. If components require multiple root elements -- by validating the template at some point -- then it's a great rule for both.

In the past, Ember.Component served two purposes: single root element (tagName="anything") and fragment (tagName=""). There are many benefits to restricting Ember.Component to the former, as @stefanpenner has described.

There are also benefits to having something to support the fragment case. That case would have looser restrictions on the template, but less support for other things like CSS and event handlers. For this, I like the {{fragment}} helper and Ember.Fragment class suggested above.

@tomdale

This comment has been minimized.

Show comment
Hide comment
@tomdale

tomdale Jun 11, 2015

Member

@jayphelps Pinged you in community slack.

Member

tomdale commented Jun 11, 2015

@jayphelps Pinged you in community slack.

@tomdale

This comment has been minimized.

Show comment
Hide comment
@tomdale

tomdale Jun 11, 2015

Member

@jamesarosen Why are two concepts better than one concept with clear error messages?

Member

tomdale commented Jun 11, 2015

@jamesarosen Why are two concepts better than one concept with clear error messages?

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Jun 11, 2015

Member

In terms of 3, tagName= will no longer work and the other attributes you list will be reflected on to the root element.

My concerns are with the fragment case, in which no root or single root for attribute reflection would exist.

@jamesarosen Why are two concepts better than one concept with clear error messages?

Let me illustrate what I believe to be a common scenario producers of HTML today would assume to be reasonable, but in the world of fragments would not. The differentiation simply makes use of a developers ability to pattern match.

<div class="{{mode}}">
  <maybe-fragment-maybe-not class="hide-in-presentation-mode" />
  <video></video>
</div>
.presenting .hide-in-presentation-mode {
  display: none;
}

or via the hidden attribute:

<div class="{{mode}}">
  <maybe-fragment-maybe-not hidden=isPresenting />
  <video></video>
</div>

Now, in-order to get the expected functionality. The developer may be required wrap the maybe-fragment-maybe-not in an additional tag, which then would carry the class/attribute. This requires either memorizing which components are well mannered and which are imposters, or by doing a dive, and hoping the component doesn't shift out from under them.

If instead some visual differentiator existed, the consumer would not be caught unexpected.

Note: I lack the creativity to come up with anything new, so I will like use {{ to represent something not guaranteed to behave as a well mannered tag.

<div class="{{mode}}">
  {{i-am-clearly-a-fragment}}
  <i-am-clearly-a-well-mannered-component />
</div>
Member

stefanpenner commented Jun 11, 2015

In terms of 3, tagName= will no longer work and the other attributes you list will be reflected on to the root element.

My concerns are with the fragment case, in which no root or single root for attribute reflection would exist.

@jamesarosen Why are two concepts better than one concept with clear error messages?

Let me illustrate what I believe to be a common scenario producers of HTML today would assume to be reasonable, but in the world of fragments would not. The differentiation simply makes use of a developers ability to pattern match.

<div class="{{mode}}">
  <maybe-fragment-maybe-not class="hide-in-presentation-mode" />
  <video></video>
</div>
.presenting .hide-in-presentation-mode {
  display: none;
}

or via the hidden attribute:

<div class="{{mode}}">
  <maybe-fragment-maybe-not hidden=isPresenting />
  <video></video>
</div>

Now, in-order to get the expected functionality. The developer may be required wrap the maybe-fragment-maybe-not in an additional tag, which then would carry the class/attribute. This requires either memorizing which components are well mannered and which are imposters, or by doing a dive, and hoping the component doesn't shift out from under them.

If instead some visual differentiator existed, the consumer would not be caught unexpected.

Note: I lack the creativity to come up with anything new, so I will like use {{ to represent something not guaranteed to behave as a well mannered tag.

<div class="{{mode}}">
  {{i-am-clearly-a-fragment}}
  <i-am-clearly-a-well-mannered-component />
</div>
@tomdale

This comment has been minimized.

Show comment
Hide comment
@tomdale

tomdale Jun 11, 2015

Member

@stefanpenner Thanks for the motivating use case. This is actually the original design we came up with, but the thing that made us deviate from that plan was that semantics for curly components are already defined, and we need some syntax to opt people into the new DDAU-style components.

I am open to alternative syntax suggestions, and of course, we could implement the described behavior in Ember 3.0 if we can deprecate curly component invocation in the 2.x series.

Member

tomdale commented Jun 11, 2015

@stefanpenner Thanks for the motivating use case. This is actually the original design we came up with, but the thing that made us deviate from that plan was that semantics for curly components are already defined, and we need some syntax to opt people into the new DDAU-style components.

I am open to alternative syntax suggestions, and of course, we could implement the described behavior in Ember 3.0 if we can deprecate curly component invocation in the 2.x series.

@jamesarosen

This comment has been minimized.

Show comment
Hide comment
@jamesarosen

jamesarosen Jun 11, 2015

Why are two concepts better than one concept with clear error messages?

It depends on whether they really are two concepts. Helper and Component are two concepts because Helper is limited to emitting just strings. Is Fragment different enough? From an app-developer's standpoint, probably not. But from a Mixin's standpoint, I really think so.

If the modes are distinct enough, I always prefer two classes or methods to one class or method with two modes. Flag-avoidance is the same reason Rails changed from .find(:all, query) and .find(:first, query) to .where(query) and .where(query).first().

Why are two concepts better than one concept with clear error messages?

It depends on whether they really are two concepts. Helper and Component are two concepts because Helper is limited to emitting just strings. Is Fragment different enough? From an app-developer's standpoint, probably not. But from a Mixin's standpoint, I really think so.

If the modes are distinct enough, I always prefer two classes or methods to one class or method with two modes. Flag-avoidance is the same reason Rails changed from .find(:all, query) and .find(:first, query) to .where(query) and .where(query).first().

@DingoEatingFuzz

This comment has been minimized.

Show comment
Hide comment
@DingoEatingFuzz

DingoEatingFuzz Jun 11, 2015

Up until this point, I thought specifying {{root}} was a requirement for control-flow/multiple-element components. Reading back through the RFC, it's clear that it is not required, so put me in the concerned camp :)

Having less API surface-area by unifying components and fragments is definitely appealing, but I think in the long run the WTFs related to property assignments falling through and mixins not behaving is greater than the WTFs related to a fragment being kinda like a component, but technically not a component.

At least in this way you can avoid fragments if you don't like the extra confusion/cognition/consequences whereas in the case of a multi-modal component you cannot.

Some examples of unavoidable confusion/cognition/consequences:

  1. How will/should this mixin work with a fragment that has a root node that may vary in tag name?
  2. How will/should this mixin work with a fragment that has no root node
  3. Where all these extra elements in my DOM coming from?
  4. Why isn't a class being set on this component?
  5. Why is the class sometimes being set on the first element in the component, but other times on a random other element?
  6. [During code review] I forget, is <onboarding-messaging> always a single component or is it variable? Are there circumstances where these attributes don't do what the author thinks it will do?

Up until this point, I thought specifying {{root}} was a requirement for control-flow/multiple-element components. Reading back through the RFC, it's clear that it is not required, so put me in the concerned camp :)

Having less API surface-area by unifying components and fragments is definitely appealing, but I think in the long run the WTFs related to property assignments falling through and mixins not behaving is greater than the WTFs related to a fragment being kinda like a component, but technically not a component.

At least in this way you can avoid fragments if you don't like the extra confusion/cognition/consequences whereas in the case of a multi-modal component you cannot.

Some examples of unavoidable confusion/cognition/consequences:

  1. How will/should this mixin work with a fragment that has a root node that may vary in tag name?
  2. How will/should this mixin work with a fragment that has no root node
  3. Where all these extra elements in my DOM coming from?
  4. Why isn't a class being set on this component?
  5. Why is the class sometimes being set on the first element in the component, but other times on a random other element?
  6. [During code review] I forget, is <onboarding-messaging> always a single component or is it variable? Are there circumstances where these attributes don't do what the author thinks it will do?
@jayphelps

This comment has been minimized.

Show comment
Hide comment
@jayphelps

jayphelps Jun 11, 2015

Just an FYI @tomdale and I talked things over on slack, I think we both understand each other's points but are still in the same spot. :trollface:

My suggested course of action: finalize angle brackets with 1:1 component, some form of separate {{frag "x-foo"}}/Ember.Fragment primitive, see how the dust settles in the real world then reevaluate unification. It's easier to merge the two later than it is to break them out later, which would be a massive FU.

Just an FYI @tomdale and I talked things over on slack, I think we both understand each other's points but are still in the same spot. :trollface:

My suggested course of action: finalize angle brackets with 1:1 component, some form of separate {{frag "x-foo"}}/Ember.Fragment primitive, see how the dust settles in the real world then reevaluate unification. It's easier to merge the two later than it is to break them out later, which would be a massive FU.

@jonathanKingston

This comment has been minimized.

Show comment
Hide comment
@jonathanKingston

jonathanKingston Jun 11, 2015

@jayphelps We are working the Web Components folks, as well as other framework/library authors, to better align the standard with the needs of real web development.

@tomdale is this a public conversation, If so where?

@jayphelps We are working the Web Components folks, as well as other framework/library authors, to better align the standard with the needs of real web development.

@tomdale is this a public conversation, If so where?

@ef4

This comment has been minimized.

Show comment
Hide comment
@ef4

ef4 Jun 12, 2015

Contributor

@stefanpenner: most of your list of objections I actually see as features.

event listeners are dubious

Event listeners on the Component class are fundamentally dubious now that we have the component's own element in the template. You can avoid having two APIs for the same thing and just use {{action}} (or the newer alternatives, like on-click=) on the component's top-level element. This has the benefit of being 100% unambiguous, no matter how many top-level elements are in the template.

consumers of the component must realize the component may not be compatible with: tagName, id, class...

I think moving away from those APIs is positive. When somebody wants a particular tag, it is better to let them just write the tag. So instead of:

<some-component tagName="tr" class="tall" />

You can lift the top element out into the caller's context, and implement the component as a fragment:

<tr class="tall">
  <some-component />
</tr>

The reality of the DOM as it works today is that it is not arbitrarily flexible. There are plenty of contexts with strict rules, and plenty of ways to break existing layouts just because you wanted to add a new layer of abstraction.

Components are a unit of abstraction that does need to be arbitrarily flexible. They are the function calls in your template. Splitting a big function into smaller functions should not force you to change the output of the function, but that is exactly what happens if components and DOM are forced to be one-to-one.

I am also unsympathetic to the web components argument. Ember Components are a superset of web components. We already do much that they don't. But we are still completely interoperable, and you can choose to implement a component in Polymer, and it will work fine with Ember.

Contributor

ef4 commented Jun 12, 2015

@stefanpenner: most of your list of objections I actually see as features.

event listeners are dubious

Event listeners on the Component class are fundamentally dubious now that we have the component's own element in the template. You can avoid having two APIs for the same thing and just use {{action}} (or the newer alternatives, like on-click=) on the component's top-level element. This has the benefit of being 100% unambiguous, no matter how many top-level elements are in the template.

consumers of the component must realize the component may not be compatible with: tagName, id, class...

I think moving away from those APIs is positive. When somebody wants a particular tag, it is better to let them just write the tag. So instead of:

<some-component tagName="tr" class="tall" />

You can lift the top element out into the caller's context, and implement the component as a fragment:

<tr class="tall">
  <some-component />
</tr>

The reality of the DOM as it works today is that it is not arbitrarily flexible. There are plenty of contexts with strict rules, and plenty of ways to break existing layouts just because you wanted to add a new layer of abstraction.

Components are a unit of abstraction that does need to be arbitrarily flexible. They are the function calls in your template. Splitting a big function into smaller functions should not force you to change the output of the function, but that is exactly what happens if components and DOM are forced to be one-to-one.

I am also unsympathetic to the web components argument. Ember Components are a superset of web components. We already do much that they don't. But we are still completely interoperable, and you can choose to implement a component in Polymer, and it will work fine with Ember.

@jamesarosen

This comment has been minimized.

Show comment
Hide comment
@jamesarosen

jamesarosen Jun 12, 2015

most of your list of objections I actually see as features...

@ef4 this post of yours really turned me around on this RFC

most of your list of objections I actually see as features...

@ef4 this post of yours really turned me around on this RFC

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Jun 12, 2015

Member

i like the way this discussion is going! My concerns keep getting widled down, but the remaining are becoming well defined. Lets see where this leads.

event listeners are dubious
Event listeners on the Component class are fundamentally dubious now that we have the component's own element in the template. You can avoid having two APIs for the same thing and just use {{action}} (or the newer alternatives, like on-click=) on the component's top-level element. This has the benefit of being 100% unambiguous, no matter how many top-level elements are in the template.

I totally agree, also as event-listeners are already contextual, a video tags canplaythrough is not expected to work on a div, so I see no problem.

consumers of the component must realize the component may not be compatible with: tagName, id, class...
I think moving away from those APIs is positive. When somebody wants a particular tag, it is better to let them just write the tag. So instead of:

You can lift the top element out into the caller's context, and implement the component as a fragment:

I have un-intentionally lumped together tagName and class (legitimate DOM attrs). I wish for us to separate these two, as I also believe tagName usage to be an anti-pattern and your lifting example to be best practices.

So lets continue to refine these thoughts, but lets focus on the example I provided.

Given:

<div class="{{mode}}">
  <maybe-fragment-maybe-not hidden=isPresenting />
  <video></video>
</div>

The lifting strategy, results in defensive structural wrapping.

<div class="{{mode}}">
  <span hidden=isPresenting >
    <maybe-fragment-maybe-not />
  </span>
  <video></video>
</div>

note: hidden is just one example, another would be the accessibility improving aria- labels.

It feels wrong, that applying accessibility hints results in potentially breaking defensive structural wrapping

Now, after investigation of the components implementation (or via trial & error) a developer may infer that the defensive wrapping is unneeded. Unfortunately Overtime this inference may not continue to stand true as the single-root may transition to multi-root.

The transition from single-root to multi root (or back) should be considered poor practice, but the potential does have a non negligible cost.

Finally, and what I believe to be the root of my concerns. We have two concepts, range and element, although related they both expose very different and incompatible capabilities. All while occupying the same syntax.

<div class="{{mode}}">
  {{fragment-thing}}
  <video></video>
</div>
<div class="{{mode}}">
  <not-a-fragment />
  <video></video>
</div>

aside: I wonder if today < is the new hotness, now everything looks better as an <.

Member

stefanpenner commented Jun 12, 2015

i like the way this discussion is going! My concerns keep getting widled down, but the remaining are becoming well defined. Lets see where this leads.

event listeners are dubious
Event listeners on the Component class are fundamentally dubious now that we have the component's own element in the template. You can avoid having two APIs for the same thing and just use {{action}} (or the newer alternatives, like on-click=) on the component's top-level element. This has the benefit of being 100% unambiguous, no matter how many top-level elements are in the template.

I totally agree, also as event-listeners are already contextual, a video tags canplaythrough is not expected to work on a div, so I see no problem.

consumers of the component must realize the component may not be compatible with: tagName, id, class...
I think moving away from those APIs is positive. When somebody wants a particular tag, it is better to let them just write the tag. So instead of:

You can lift the top element out into the caller's context, and implement the component as a fragment:

I have un-intentionally lumped together tagName and class (legitimate DOM attrs). I wish for us to separate these two, as I also believe tagName usage to be an anti-pattern and your lifting example to be best practices.

So lets continue to refine these thoughts, but lets focus on the example I provided.

Given:

<div class="{{mode}}">
  <maybe-fragment-maybe-not hidden=isPresenting />
  <video></video>
</div>

The lifting strategy, results in defensive structural wrapping.

<div class="{{mode}}">
  <span hidden=isPresenting >
    <maybe-fragment-maybe-not />
  </span>
  <video></video>
</div>

note: hidden is just one example, another would be the accessibility improving aria- labels.

It feels wrong, that applying accessibility hints results in potentially breaking defensive structural wrapping

Now, after investigation of the components implementation (or via trial & error) a developer may infer that the defensive wrapping is unneeded. Unfortunately Overtime this inference may not continue to stand true as the single-root may transition to multi-root.

The transition from single-root to multi root (or back) should be considered poor practice, but the potential does have a non negligible cost.

Finally, and what I believe to be the root of my concerns. We have two concepts, range and element, although related they both expose very different and incompatible capabilities. All while occupying the same syntax.

<div class="{{mode}}">
  {{fragment-thing}}
  <video></video>
</div>
<div class="{{mode}}">
  <not-a-fragment />
  <video></video>
</div>

aside: I wonder if today < is the new hotness, now everything looks better as an <.

@ef4

This comment has been minimized.

Show comment
Hide comment
@ef4

ef4 Jun 13, 2015

Contributor

I like keeping curlies for fragments and angles for components so that the invocation is unambiguous, but it has practical upgrade challenges. We would need a way for the curlies to opt in to one-way binding and own-element-in-template.

Contributor

ef4 commented Jun 13, 2015

I like keeping curlies for fragments and angles for components so that the invocation is unambiguous, but it has practical upgrade challenges. We would need a way for the curlies to opt in to one-way binding and own-element-in-template.

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Jun 13, 2015

Member

but it has practical upgrade challenges. We would need a way for the curlies to opt in to one-way binding and own-element-in-template.

I absolutely agree, @tomdale also voiced this concern.

One idea, was to introduce (until we can fully re-claim {{) {{fragment .... This is obviously an unfortunate interim step, but it feels like a worthwhile short term cost.

Member

stefanpenner commented Jun 13, 2015

but it has practical upgrade challenges. We would need a way for the curlies to opt in to one-way binding and own-element-in-template.

I absolutely agree, @tomdale also voiced this concern.

One idea, was to introduce (until we can fully re-claim {{) {{fragment .... This is obviously an unfortunate interim step, but it feels like a worthwhile short term cost.

@jmurphyau

This comment has been minimized.

Show comment
Hide comment
@jmurphyau

jmurphyau Jun 21, 2015

Is there any way to opt in to one way bindings and no attr proxying in curly {{components}}?

I'm finding myself writing (readonly prop) and would love being able to disable attr proxying to the component object.

I know angle brackets will opt in to this but they aren't available yet.. Aside from getting the functionality sooner, would there be any other benefit in having these options available in curlys?

If this isn't currently possible and seems like something reasonable I may make an attempt at a PR..? Turning off attr proxying doesn't seem too difficult but will need to look into readonly by default rather than mut ...

Is there any way to opt in to one way bindings and no attr proxying in curly {{components}}?

I'm finding myself writing (readonly prop) and would love being able to disable attr proxying to the component object.

I know angle brackets will opt in to this but they aren't available yet.. Aside from getting the functionality sooner, would there be any other benefit in having these options available in curlys?

If this isn't currently possible and seems like something reasonable I may make an attempt at a PR..? Turning off attr proxying doesn't seem too difficult but will need to look into readonly by default rather than mut ...

@rwjblue rwjblue referenced this pull request in emberjs/ember.js Jul 1, 2015

Closed

"setProperties" does not pass "attrs" as expected #11537

tomdale added a commit to emberjs/ember.js that referenced this pull request Jul 6, 2015

Implements RFC #60 (Component Unification)
emberjs/rfcs#60

This commit implements the proposed semantics for angle-bracket
components. The TLDR is that a component’s template represents its
“outerHTML” rather than its “innerHTML”, which makes it easier to
configure the element itself using templating constructs.

Some salient points:

1. If there is a single root element, the attributes from the invocation
   are copied onto the root element.
2. The invocation’s attributes “win out” over the attributes from the
   root element in the component’s layout.
3. Classes are merged. If the invocation says `class=“a”` and the root
   element says `class=“b”`, the result is `class=“a b”`. The rationale
   is that when you say `class=“a”`, you are really saying “add the `a`
   class to this element”.

A few sticky issues:

1. If the root element for the `my-foo` component is `<my-foo>`, we
   insert an element with tag name of `my-foo`. While this is intuitive,
   note that in all other cases, `<my-foo>` represents an invocation of
   the component. In the root position, that makes no sense, since it
   would inevitably produce a stack overflow.
   a. This “identity element” case makes it idiomatic to reflect the
      name of the component onto the DOM.
   b. Unfortunately, when we compile a template, we do not yet know
      what component that template is used for, and, indeed, whether it
      is even a template for a component at all.
   c. To capture the semantic differences, we do a bit of analysis at
      compile time (to determine *candidates* for top-level elements),
      and use runtime information (component invocation style and
      the name of the component looked up in the container) to
      disambiguate between a component’s element and an invocation of
      another component.
2. If the root element for the `my-foo` component is a regular HTML
   element, we use the `attachAttributes` functionality of HTMLBars to
   attach the attributes that the component was invoked with onto the
   root element.
3. In general, it is important that changes to attributes do not result
   in a change to the identity of the element that they are contained
   in. To achieve this, we reused much of the infrastructure built in
   `buildComponentTemplate`.

The consequence of (1) and (2) above is that the semantics are always
“a component’s layout represents its outerHTML”, even though the
implementation is quite different depending on whether the root element
is the same-named component or not.

tomdale added a commit to emberjs/ember.js that referenced this pull request Jul 8, 2015

Implements RFC #60 (Component Unification)
emberjs/rfcs#60

This commit implements the proposed semantics for angle-bracket
components. The TLDR is that a component’s template represents its
“outerHTML” rather than its “innerHTML”, which makes it easier to
configure the element itself using templating constructs.

Some salient points:

1. If there is a single root element, the attributes from the invocation
   are copied onto the root element.
2. The invocation’s attributes “win out” over the attributes from the
   root element in the component’s layout.
3. Classes are merged. If the invocation says `class=“a”` and the root
   element says `class=“b”`, the result is `class=“a b”`. The rationale
   is that when you say `class=“a”`, you are really saying “add the `a`
   class to this element”.

A few sticky issues:

1. If the root element for the `my-foo` component is `<my-foo>`, we
   insert an element with tag name of `my-foo`. While this is intuitive,
   note that in all other cases, `<my-foo>` represents an invocation of
   the component. In the root position, that makes no sense, since it
   would inevitably produce a stack overflow.
   a. This “identity element” case makes it idiomatic to reflect the
      name of the component onto the DOM.
   b. Unfortunately, when we compile a template, we do not yet know
      what component that template is used for, and, indeed, whether it
      is even a template for a component at all.
   c. To capture the semantic differences, we do a bit of analysis at
      compile time (to determine *candidates* for top-level elements),
      and use runtime information (component invocation style and
      the name of the component looked up in the container) to
      disambiguate between a component’s element and an invocation of
      another component.
2. If the root element for the `my-foo` component is a regular HTML
   element, we use the `attachAttributes` functionality of HTMLBars to
   attach the attributes that the component was invoked with onto the
   root element.
3. In general, it is important that changes to attributes do not result
   in a change to the identity of the element that they are contained
   in. To achieve this, we reused much of the infrastructure built in
   `buildComponentTemplate`.

The consequence of (1) and (2) above is that the semantics are always
“a component’s layout represents its outerHTML”, even though the
implementation is quite different depending on whether the root element
is the same-named component or not.

machty added a commit to machty/ember.js that referenced this pull request Jul 8, 2015

Implements RFC #60 (Component Unification)
emberjs/rfcs#60

This commit implements the proposed semantics for angle-bracket
components. The TLDR is that a component’s template represents its
“outerHTML” rather than its “innerHTML”, which makes it easier to
configure the element itself using templating constructs.

Some salient points:

1. If there is a single root element, the attributes from the invocation
   are copied onto the root element.
2. The invocation’s attributes “win out” over the attributes from the
   root element in the component’s layout.
3. Classes are merged. If the invocation says `class=“a”` and the root
   element says `class=“b”`, the result is `class=“a b”`. The rationale
   is that when you say `class=“a”`, you are really saying “add the `a`
   class to this element”.

A few sticky issues:

1. If the root element for the `my-foo` component is `<my-foo>`, we
   insert an element with tag name of `my-foo`. While this is intuitive,
   note that in all other cases, `<my-foo>` represents an invocation of
   the component. In the root position, that makes no sense, since it
   would inevitably produce a stack overflow.
   a. This “identity element” case makes it idiomatic to reflect the
      name of the component onto the DOM.
   b. Unfortunately, when we compile a template, we do not yet know
      what component that template is used for, and, indeed, whether it
      is even a template for a component at all.
   c. To capture the semantic differences, we do a bit of analysis at
      compile time (to determine *candidates* for top-level elements),
      and use runtime information (component invocation style and
      the name of the component looked up in the container) to
      disambiguate between a component’s element and an invocation of
      another component.
2. If the root element for the `my-foo` component is a regular HTML
   element, we use the `attachAttributes` functionality of HTMLBars to
   attach the attributes that the component was invoked with onto the
   root element.
3. In general, it is important that changes to attributes do not result
   in a change to the identity of the element that they are contained
   in. To achieve this, we reused much of the infrastructure built in
   `buildComponentTemplate`.

The consequence of (1) and (2) above is that the semantics are always
“a component’s layout represents its outerHTML”, even though the
implementation is quite different depending on whether the root element
is the same-named component or not.
@ohnnyj

This comment has been minimized.

Show comment
Hide comment
@ohnnyj

ohnnyj Jul 14, 2015

Tried adding a comment to emberjs/ember.js@9e5f072 but no response so thought I would repost here, apologies if this is not the right forum.

Trying to use this new implementation but running into an issue (using emberjs/ember.js@04dd84c).

I have a component template:

<my-component>
  <div class="container"></div>
</my-component>

and use it in a route/top level template:

<my-component model={{model}} />

From the description mentioned in the above commit I would expect that I would end up with a tag in the html but instead I get the error mentioned in emberjs/ember.js@9e5f072.

If I remove the <my-component> from my component template no error but not the markup I would like. Maybe I am misunderstanding the correct way to do this.

And, BTW, this is probably an invalid usage pattern but changing the <my-component> to {{my-component}} in the component template results in a stack overflow.

ohnnyj commented Jul 14, 2015

Tried adding a comment to emberjs/ember.js@9e5f072 but no response so thought I would repost here, apologies if this is not the right forum.

Trying to use this new implementation but running into an issue (using emberjs/ember.js@04dd84c).

I have a component template:

<my-component>
  <div class="container"></div>
</my-component>

and use it in a route/top level template:

<my-component model={{model}} />

From the description mentioned in the above commit I would expect that I would end up with a tag in the html but instead I get the error mentioned in emberjs/ember.js@9e5f072.

If I remove the <my-component> from my component template no error but not the markup I would like. Maybe I am misunderstanding the correct way to do this.

And, BTW, this is probably an invalid usage pattern but changing the <my-component> to {{my-component}} in the component template results in a stack overflow.

@chadhietala

This comment has been minimized.

Show comment
Hide comment
@chadhietala

chadhietala Jul 17, 2015

Member

I believe there should be a build time warning or failure if you are using mutative APIs on attrs in angle bracket components. In React props can be mutated as well but their is tooling around this suggests that you should not mutate props.

The example below shows mutating both arrays and objects. Arrays are clearly worst that mutating objects as object properties are not propagated. The program becomes hard to reason about if the attrs can be mutated as they are passed along, we should do our utmost to shorten the conceptual gap between the static program and the dynamic process 😉.

http://emberjs.jsbin.com/xahidifube

Member

chadhietala commented Jul 17, 2015

I believe there should be a build time warning or failure if you are using mutative APIs on attrs in angle bracket components. In React props can be mutated as well but their is tooling around this suggests that you should not mutate props.

The example below shows mutating both arrays and objects. Arrays are clearly worst that mutating objects as object properties are not propagated. The program becomes hard to reason about if the attrs can be mutated as they are passed along, we should do our utmost to shorten the conceptual gap between the static program and the dynamic process 😉.

http://emberjs.jsbin.com/xahidifube

@tomdale

This comment has been minimized.

Show comment
Hide comment
@tomdale

tomdale Jul 18, 2015

Member

@chadhietala Setting up property descriptors on attrs would be slow but is probably worth it in development mode.

Member

tomdale commented Jul 18, 2015

@chadhietala Setting up property descriptors on attrs would be slow but is probably worth it in development mode.

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Jul 19, 2015

Member

@chadhietala Setting up property descriptors on attrs would be slow but is probably worth it in development mode.

in theory we can make attrs the instance of a class with those descriptors on the proto. So that would only be expensive the first time the class is derived. I suspect in practice this will be few, it would not longer be per snapshot or per component instance.

Member

stefanpenner commented Jul 19, 2015

@chadhietala Setting up property descriptors on attrs would be slow but is probably worth it in development mode.

in theory we can make attrs the instance of a class with those descriptors on the proto. So that would only be expensive the first time the class is derived. I suspect in practice this will be few, it would not longer be per snapshot or per component instance.

@tomdale

This comment has been minimized.

Show comment
Hide comment
@tomdale

tomdale Jul 19, 2015

Member

@stefanpenner The problem is that attrs aren't known on a per-component basis; they're known per-invocation. So e.g. I may have a component {{my-component}} that sometimes I invoke as {{my-component foo="bar"}} and sometimes as {{my-component bar="baz"}}. The {{component}} helper introduces even more complexity here.

Your proposal would require us to cache the class with descriptors on a per-invocation basis, which is not impossible but we don't have any infrastructure for it currently, as best I can tell. Maybe our friends on the Chrome team will hurry up and implement Proxy and then these concerns would be moot, anyway. ;)

Member

tomdale commented Jul 19, 2015

@stefanpenner The problem is that attrs aren't known on a per-component basis; they're known per-invocation. So e.g. I may have a component {{my-component}} that sometimes I invoke as {{my-component foo="bar"}} and sometimes as {{my-component bar="baz"}}. The {{component}} helper introduces even more complexity here.

Your proposal would require us to cache the class with descriptors on a per-invocation basis, which is not impossible but we don't have any infrastructure for it currently, as best I can tell. Maybe our friends on the Chrome team will hurry up and implement Proxy and then these concerns would be moot, anyway. ;)

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Jul 19, 2015

Member

@stefanpenner The problem is that attrs aren't known on a per-component basis; they're known per-invocation.

yup, but this is both statically known at compile time and there are also less invocation sites then component instances. I suspect we can incur the penalty once per unique signature call-site even at runtime. (although build time deferral is also likely option).

(not saying this is the solution we must take, just saying i believe it can be done without a noticeable performance problem)

Member

stefanpenner commented Jul 19, 2015

@stefanpenner The problem is that attrs aren't known on a per-component basis; they're known per-invocation.

yup, but this is both statically known at compile time and there are also less invocation sites then component instances. I suspect we can incur the penalty once per unique signature call-site even at runtime. (although build time deferral is also likely option).

(not saying this is the solution we must take, just saying i believe it can be done without a noticeable performance problem)

@chadhietala

This comment has been minimized.

Show comment
Hide comment
@chadhietala

chadhietala Jul 19, 2015

Member

Like I mentioned, I believe tooling can be the happy path. In general I don't really believe in the notion of "in dev it's slower". I would rather have consistent performance characteristics regardless of environment.

Member

chadhietala commented Jul 19, 2015

Like I mentioned, I believe tooling can be the happy path. In general I don't really believe in the notion of "in dev it's slower". I would rather have consistent performance characteristics regardless of environment.

@mitchlloyd

This comment has been minimized.

Show comment
Hide comment
@mitchlloyd

mitchlloyd Aug 22, 2015

Very minor cocern here but with ye olde components, passing in an attr was an effective way to write tests for components that use services.

this.render("{{my-component session=sessionStub}}");

Any thoughts on whether angle bracket components mean the end of this pattern? This may be another reason that we need a well-paved path for stubbing services.

Very minor cocern here but with ye olde components, passing in an attr was an effective way to write tests for components that use services.

this.render("{{my-component session=sessionStub}}");

Any thoughts on whether angle bracket components mean the end of this pattern? This may be another reason that we need a well-paved path for stubbing services.

@rwjblue

This comment has been minimized.

Show comment
Hide comment
@rwjblue

rwjblue Aug 22, 2015

Member

@mitchlloyd - I think emberjs/ember-test-helpers#96 is a much better pattern for this (and works with ember-qunit@0.4.10+).

Member

rwjblue commented Aug 22, 2015

@mitchlloyd - I think emberjs/ember-test-helpers#96 is a much better pattern for this (and works with ember-qunit@0.4.10+).

@workmanw

This comment has been minimized.

Show comment
Hide comment
@workmanw

workmanw Aug 23, 2015

Contributor

While working to upgrade our app to 2.0 and plan for some future updates, one question came up relating to this.attrs, how will default values be configured? Right now it's very easy to provide sensible defaults for components by declaring them on the component class, but with this.attrs will that be possible?

As a use case, we often provide default values labels for many components, but allow them to be overridden.

I'm not even sure if this is the right place to ask this since this.attrs isn't exactly component unification. I just wanted to be sure to raise the concern of default values. If that concern belongs on another RFC or as an issue I'd be happy to raise it their.

Contributor

workmanw commented Aug 23, 2015

While working to upgrade our app to 2.0 and plan for some future updates, one question came up relating to this.attrs, how will default values be configured? Right now it's very easy to provide sensible defaults for components by declaring them on the component class, but with this.attrs will that be possible?

As a use case, we often provide default values labels for many components, but allow them to be overridden.

I'm not even sure if this is the right place to ask this since this.attrs isn't exactly component unification. I just wanted to be sure to raise the concern of default values. If that concern belongs on another RFC or as an issue I'd be happy to raise it their.

@mitchlloyd

This comment has been minimized.

Show comment
Hide comment
@mitchlloyd

mitchlloyd Aug 23, 2015

@rwjblue Thanks for the heads up!

@rwjblue Thanks for the heads up!

@mmun

This comment has been minimized.

Show comment
Hide comment
@mmun

mmun Aug 23, 2015

Member

@workmanw You can just do

this.foo = 'foo' in this.attrs ? this.attrs.foo : yourDefault;

in whatever hook makes sense to you.

Member

mmun commented Aug 23, 2015

@workmanw You can just do

this.foo = 'foo' in this.attrs ? this.attrs.foo : yourDefault;

in whatever hook makes sense to you.

@workmanw

This comment has been minimized.

Show comment
Hide comment
@workmanw

workmanw Aug 23, 2015

Contributor

@mmun Okay yea, I was also thinking I could build a computed macro for this use case too. Something like:

function attrsDefault(key, defaultValue) {
   return Ember.computed('attrs.'+key, function() {
     return (key in this.attrs) ? this.attrs[key] : defaultValue;
   });
}

Ember.Component.extend({
 foo: attrsDefault('foo', 'bar')
});

But I wasn't sure if this would be unadvised or potentially result in a naming conflict for a period of time.

Contributor

workmanw commented Aug 23, 2015

@mmun Okay yea, I was also thinking I could build a computed macro for this use case too. Something like:

function attrsDefault(key, defaultValue) {
   return Ember.computed('attrs.'+key, function() {
     return (key in this.attrs) ? this.attrs[key] : defaultValue;
   });
}

Ember.Component.extend({
 foo: attrsDefault('foo', 'bar')
});

But I wasn't sure if this would be unadvised or potentially result in a naming conflict for a period of time.

@mmun

This comment has been minimized.

Show comment
Hide comment
@mmun

mmun Aug 23, 2015

Member

@workmanw That's definitely something I'm hoping someone experiments with in an addon so we can gather consensus on how useful it is.

Member

mmun commented Aug 23, 2015

@workmanw That's definitely something I'm hoping someone experiments with in an addon so we can gather consensus on how useful it is.

@tim-evans

This comment has been minimized.

Show comment
Hide comment
@tim-evans

tim-evans Nov 3, 2015

How would angle bracket components work with inverses?

For example, I'd write a {{toggle-button}} with an {{else}} to separate the enabled and disabled state. How would I write this with Ember.GlimmerComponent?

How would angle bracket components work with inverses?

For example, I'd write a {{toggle-button}} with an {{else}} to separate the enabled and disabled state. How would I write this with Ember.GlimmerComponent?

@rwjblue

This comment has been minimized.

Show comment
Hide comment
@rwjblue

rwjblue Apr 14, 2016

Member

The conversation in this RFC has been great! I think at this point this RFC doesn't reflect current lines of thinking, and either a rewrite or a new RFC will be needed when we are ready to start progessing forward here.

I propose that we close this PR...

Member

rwjblue commented Apr 14, 2016

The conversation in this RFC has been great! I think at this point this RFC doesn't reflect current lines of thinking, and either a rewrite or a new RFC will be needed when we are ready to start progessing forward here.

I propose that we close this PR...

@rwjblue

This comment has been minimized.

Show comment
Hide comment
@rwjblue

rwjblue Apr 15, 2016

Member

Thanks for everyone's hard work here. We have made a ton of progress, and are in the middle of integrating the updated glimmer engine that is required to start working on angle bracket components again.

I'm going to close this for now while we continue pushing forward on the known path. We should reopen (or entertain a new RFC) when ready to work on angle bracket components again.

Member

rwjblue commented Apr 15, 2016

Thanks for everyone's hard work here. We have made a ton of progress, and are in the middle of integrating the updated glimmer engine that is required to start working on angle bracket components again.

I'm going to close this for now while we continue pushing forward on the known path. We should reopen (or entertain a new RFC) when ready to work on angle bracket components again.

@rwjblue rwjblue closed this Apr 15, 2016

@rwjblue rwjblue deleted the angle-bracket-components branch Apr 15, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment