From 82018984e7679813992ce4bc912aab2ec58ff969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mu=C3=B1oz?= Date: Fri, 12 Jun 2015 14:47:32 -0400 Subject: [PATCH] Contextual component lookup RFC --- text/0064-contextual-component-lookup.md | 233 +++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 text/0064-contextual-component-lookup.md diff --git a/text/0064-contextual-component-lookup.md b/text/0064-contextual-component-lookup.md new file mode 100644 index 0000000000..da1cae2f14 --- /dev/null +++ b/text/0064-contextual-component-lookup.md @@ -0,0 +1,233 @@ +- Start Date: 2015-06-12 +- RFC PR: https://github.com/emberjs/rfcs/pull/64 +- Ember Issue: (leave this empty) + +# Summary + +The goal of this RFC is to allow for better component composition and the +usage of components for domain specific languages. + +Ember components can be invoked three ways: + +* `{{a-component` +* `{{component someBoundComponentName` +* `Save +{{/with}} +``` + +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. + +The `(component` helper will close over a component name. The +`{{component` helper will be modified to accept this resulting value and invoke +the component: + +```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)`. + +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)}} +``` + +```hbs +{{#user-profile user=model as |profile|}} + {{component profile}} +{{/user-profile}} +``` + +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}}` + +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. + +### Hash helper + +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 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}} +``` + +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}} +``` + +### Component helper shorthand + +To complete building a viable DSL, `.` invocation for `{{` components will be +introduced. For example this `{{component` invocation: + +```hbs +{{#with (hash profile=(component "user-profile")) as |userComponents|}} + {{component userComponents.profile}} +{{/with}} +``` + +Could be converted to drop the explicit `component` helper call. + +```hbs +{{#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}}` or `{{this.input}}` +* The component can be called as a helper. For example `{{form.input value=baz}}` or `{{this.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`. + +Other proposals have leaned more heavy on extending factories in JavaScript +then passing an object created in that space. Some arguments against this: + +* 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 + +All pain, no gain. Addons really want this. + +# Unresolved questions + +There has been discussion of if a similar mechanism should be available for +helpers.