diff --git a/text/0000-contextual-component-lookup.md b/text/0000-contextual-component-lookup.md index 2346b70f37..44423ab961 100644 --- a/text/0000-contextual-component-lookup.md +++ b/text/0000-contextual-component-lookup.md @@ -4,126 +4,230 @@ # Summary -Currently, you can only invoke components using a global name. The goal of this -RFC is to allow better composition of components by allowing components -to be referenced through property lookups on the current scope. For example, +The goal of this RFC is to allow for better component composition and the +usage of components for domain specific languages. -```hbs -{{this.localInput value=post.title}} -``` +Ember components can be invoked three ways: + +* `{{a-component` +* `{{component someBoundComponentName` +* `Save +{{/with}} ``` -and +The returned value of the `(action` nested helper (a function) closes over the +action being called (`actions.save` on the context and the `model` property). +The `{{action` helper can accept this resulting value and invoke the action +when the user clicks. -```js -// components/form-for.js +The `(component` helper will close over a component name. The +`{{component` helper will be modified to accept this resulting value and invoke +the component: -import Ember from 'ember'; -import { curry } from 'factory-utils'; +```hbs +{{#with (component "user-profile") as |uiPane|}} + {{component uiPane}} +{{/with}} +``` + +Additionally, a bound value may be passed to the `(component` helper. For +example `(component someComponentName)`. -export default Ember.Component.extend({ - controls: Ember.computed(function() { - var inputFactory = this.container.lookupFactory('component:form-for-input'); - var submitFactory = this.container.lookupFactory('component:form-for-submit'); +Attrs for the final component can also be closed over. Used with yield, this +allows for the creation of components that have attrs from other scopes. For +example: + +```hbs +{{! app/components/user-profile.hbs }} +{{yield (component "user-profile" user=user.name age=user.age)}} +``` - return { - input: curry(inputFactory, { form: this }), - submit: curry(submitFactory, { form: this }) - }; - }) -}); +```hbs +{{#user-profile user=model as |profile|}} + {{component profile}} +{{/user-profile}} ``` -where `curry` is a simple helper which wraps the factory and assigns the extra -properties to the instance. +Of course attrs can also be passed at invocation. They smash any conflicting +attrs that were closed over. For example `{{component profile age=lyingUser.age}}` -In the future we can use the injection API to avoid directly accessing the -container. +Passing the resulting value from `(component` into JavaScript is permitted, +however that object has no public properties or methods. Its only use would +be to set it on state and reference it in template somewhere. -# Detailed design +### Hash helper -Any component invocation with a dot separator `.` in the name of the component -will perform a lookup of this property on the scope. The value must be a factory -and the factory must create component instances, otherwise an error is thrown. +Unlike values, components are likely to have specific names that are semantically +relevent. When yielded to a new scope, allowing the user to change the name +of the component's variable would quickly lead to confusing addon documentation. +For example: + +```hbs +{{#with (component "user-profile") as |dropDatabaseUI|}} + {{component dropDatabaseUI}} +{{/with}} +``` -The affected syntaxes are: +The simplest way to enforce specific names is to make building hashes +of components (or anything) easy. For example: +```hbs +{{#with (hash profile=(component "user-profile")) as |userComponents|}} + {{component userComponents.profile}} +{{/with}} ``` -{{this.component}} -{{this.component name=name}} - - + +The `(hash` helper is a generic builder of objects, given hash arguments. It +would also be useful in the same manner for actions: + +```hbs +{{#with (hash save=(action "save" model)) as |userActions|}} + +{{/with}} ``` -# Drawbacks +### Component helper shorthand -In the HTML component case, it feels a little weird that the tag name does a -property lookup without being surrounded curlies. That said, I like the simplicity. +To complete building a viable DSL, `.` invocation for `{{` components will be +introduced. For example this `{{component` invocation: -An alternative syntax (that I'm not really a fan of) is +```hbs +{{#with (hash profile=(component "user-profile")) as |userComponents|}} + {{component userComponents.profile}} +{{/with}} +``` + +Could be converted to drop the explicit `component` helper call. ```hbs -<{{this.component}} name="{{name}}" /> +{{#with (hash profile=(component "user-profile")) as |userComponents|}} + {{userComponents.profile}} +{{/with}} ``` +A component can be invoked like this only when it was created by the +`(component` nested helper form. For example unlike with the `{{component` +helper, a string is not acceptable. + +To be a valid invocation, one of two criteria must be met: + +* The component can be called as a path. For example `{{form.input}}`. +* The component can be called as a helper. For example `{{form.input value=baz}}` + +And of course a `.` must be present in the path. + +# Drawbacks + +This proposal encourages aggressive use of the `(` nested helper syntax. +Encouraging this has been slightly controversial. + +No solution for angle components is presented here. The syntax for `.` +notation in angle components is coupled to a decision on the syntax for +bound, dynamic angle component invocation (a `{{component` helper for angle +components basically). + +`(component 'some-component'` may be too verbose. It may make sense to simply +allow `(some-component`. -The requirement for the dot separator may also be seen as a drawback. The reason for -enforcing the dot separator is to avoid accidental fallback to global components. +Other proposals have leaned more heavy on extending factories in JavaScript +then passing an object created in that space. Some arguments against this: -This problem seems unavoidable as long as `{{foo}}` is ambiguous between a property -lookup and a helper call. +* Getting the container correct is tricky. Who sets it when? +* Properties on the classes would not be naturally bound, as they are in this proposal. +* As soon as you start setting properties, you likely want a `mut` helper, + `action` helper, etc, in JavaScript space. +* Keeping the component lookup in the template layer allows us to take advantage + of changes to lookup semantics later, such as local lookup in the pods + proposal. # Alternatives -I don't know of any alternative strategies that support contextualized components. +All pain, no gain. Addons really want this. # Unresolved questions -- Should this also be available for helpers? +There has been discussion of if a similar mechanism should be available for +helpers.