From 873c18181051ee4c0b3bf5702738fc164ef2e563 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Sun, 12 Apr 2015 15:00:47 -0400 Subject: [PATCH 1/4] Initial draft version of Named Template Blocks RFC --- active/0003-named-template-blocks.md | 123 +++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 active/0003-named-template-blocks.md diff --git a/active/0003-named-template-blocks.md b/active/0003-named-template-blocks.md new file mode 100644 index 0000000000..d051547ec4 --- /dev/null +++ b/active/0003-named-template-blocks.md @@ -0,0 +1,123 @@ +- Start Date: 2015/04/12 +- RFC PR: +- ember-cli Issue: + +# Summary + +Introduce a new Handlebars syntax that would denote beginning and end of a template block. + +# Motivation + +In Ember 2.0, we're moving towards components becoming primary mechanisms for encapsulating functionality with templates being the mechanism that wires these components together. It would be helpful to allow developers the ability to customize certain portion of component's template from it's block template without having to extend a component or overwrite it's layout. + +Currently, we have the ability to do this but it's limited to `{{else}}` helper. + +For example, we can customize what `{{each}}` helper presents when the array passed to it is empty. + +``` +{{#each model as |item|}} + {{item.name}} +{{else}} + No items are available. +{{/each}} +``` + +`{{if}}` helper has the same behaviour. + +We are currently lacking the ability to specify a custom template block that we can consume inside of the component or a helper. This RFC proposes that we introduce the ability to specify custom named template blocks that can be used to customize the default behaviour of a component or a helper. + +A table component is a good example of a component that could benefit from this API. Let's consider Addepar's [Ember Table Component](https://github.com/Addepar/ember-table). *Ember Table* has 3 distinct areas that a user might want to cusomize while using the component, namely header, body & footer. + +Currently, *Ember Table*'s component layout looks like this + +``` +{{#if controller.hasHeader}} + {{view Ember.Table.HeaderTableContainer}} +{{/if}} +{{view Ember.Table.BodyTableContainer}} +{{#if controller.hasFooter}} + {{view Ember.Table.FooterTableContainer}} +{{/if}} +{{view Ember.Table.ScrollContainer}} +{{view Ember.Table.ColumnSortableIndicator}} +``` + +To customize any of these areas, the developer has to extend the component, change specify a new layout and make changes inside of this new layout. If we had the ability to specify custom named template blocks then we could allow the developer to customize one of these areas from the template where the component is being used. + +Here is what that might look like, + +``` +{{#ember-table}} + {{^header as |name|}} + {{name}} + {{^}} + {{^cell as |value|}} + {{value}} + {{^}} +{{/ember-table}} +``` +Specifying a named block inside of the component's block template would override default implementation of that block inside of the component's layout. + +# Detailed design + +## New Handlebars syntax + +To enable this functionality we would need to introduce a new syntax that would deferentiate a named template block from a regular helper with a block. This would be done with the introduction of `^` syntax that indicates that this is a named template block. + +Here is an example of what a template with this syntax might look like. + +``` +{{#table-component}} + {{^header}} + Header Content goes here + {{^}} + {{^footer}} + Footer goes here + {{^}} +{{/table-component}} +``` + +## Refactor `{{else}}` helper + +One possible implementation would be to refactor `{{else}}` implementation into a more generic implementation that allows the name of the helper to be modified. + +Handlebars has several built in blocks that are availble on helper's `options` argument, namely `options.fn` & `options.inverse`. These would be changed to `options.blocks.default` & `options.blocks.inverse` respectively. Every other named template block would be available on `options.blocks` hash. The above example would have `options.blocks.header` & `options.blocks.footer` in addition to it's default blocks. + +## Blocks must be available in the template + +The component must be able to determine programmatically if it should consume it's default block or use the passed in named block. If we consider the above example, then `table-component`'s layout might look something like this. + +``` +{{#if template.blocks.header}} + {{template.blocks.header.yield headerContent}} +{{else}} + + {{#each headerContent as |name|}} + {{name}} + {{/each}} + +{{/if}} +``` + +Similar pattern can be followed to specify the ability to customize body and footer. + +## Named blocks can yield + +Named template blocks need to be able to yield values into the scope in the same way as regular template blocks can. This would allow the component to expose template friendly values to be used in the template block. + +## Named blocks must be portable + +Under certain circomstances it might be necessary to be able to pass a named block into a nested component. This would make it easier to compose nested components. + +# Drawbacks + +Why should we *not* do this? + +# Alternatives + +What other designs have been considered? What is the impact of not doing this? +`` + +# Unresolved questions + +What parts of the design are still TBD? From f7722ce8d68f591331ecf5dc8e23d76832d5c29b Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Sun, 12 Apr 2015 19:15:09 -0400 Subject: [PATCH 2/4] Updated syntax based on Kris' recommendation --- active/0003-named-template-blocks.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/active/0003-named-template-blocks.md b/active/0003-named-template-blocks.md index d051547ec4..97992c7b4c 100644 --- a/active/0003-named-template-blocks.md +++ b/active/0003-named-template-blocks.md @@ -50,36 +50,38 @@ Here is what that might look like, {{#ember-table}} {{^header as |name|}} {{name}} - {{^}} {{^cell as |value|}} {{value}} {{^}} + Nothing to show. {{/ember-table}} ``` + Specifying a named block inside of the component's block template would override default implementation of that block inside of the component's layout. # Detailed design -## New Handlebars syntax +## Expand ^ syntax -To enable this functionality we would need to introduce a new syntax that would deferentiate a named template block from a regular helper with a block. This would be done with the introduction of `^` syntax that indicates that this is a named template block. +Currently, Handlebars implements `^` which means `else`. Infact, `else` is aliased to `^`. We would expand this syntax to allow named portions of template. Here is an example of what a template with this syntax might look like. ``` -{{#table-component}} +{{#table-component items as |item|}} + // default block {{^header}} - Header Content goes here - {{^}} + // header content {{^footer}} - Footer goes here + // footer content {{^}} + // empty view {{/table-component}} ``` ## Refactor `{{else}}` helper -One possible implementation would be to refactor `{{else}}` implementation into a more generic implementation that allows the name of the helper to be modified. +One possible implementation would be to refactor `{{^}}` implementation into a more generic implementation that allows the name of the helper to be modified. Handlebars has several built in blocks that are availble on helper's `options` argument, namely `options.fn` & `options.inverse`. These would be changed to `options.blocks.default` & `options.blocks.inverse` respectively. Every other named template block would be available on `options.blocks` hash. The above example would have `options.blocks.header` & `options.blocks.footer` in addition to it's default blocks. @@ -101,9 +103,9 @@ The component must be able to determine programmatically if it should consume it Similar pattern can be followed to specify the ability to customize body and footer. -## Named blocks can yield +## Named blocks have block params -Named template blocks need to be able to yield values into the scope in the same way as regular template blocks can. This would allow the component to expose template friendly values to be used in the template block. +Named template blocks need to be able to receive block params and yield values into the scope in the same way as regular template blocks can. This would allow the component to expose template friendly values to be used in the template block. ## Named blocks must be portable @@ -116,7 +118,6 @@ Why should we *not* do this? # Alternatives What other designs have been considered? What is the impact of not doing this? -`` # Unresolved questions From f6e0f4046e64fee11e2a24630cd589eb212d8640 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Sun, 12 Apr 2015 21:01:36 -0400 Subject: [PATCH 3/4] Further cleaned up with Kris' feedback --- active/0003-named-template-blocks.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/active/0003-named-template-blocks.md b/active/0003-named-template-blocks.md index 97992c7b4c..7ce46557df 100644 --- a/active/0003-named-template-blocks.md +++ b/active/0003-named-template-blocks.md @@ -79,19 +79,23 @@ Here is an example of what a template with this syntax might look like. {{/table-component}} ``` +`{{^name}}` serve as dividers of the block template. + ## Refactor `{{else}}` helper -One possible implementation would be to refactor `{{^}}` implementation into a more generic implementation that allows the name of the helper to be modified. +One possible implementation would be to make `{{^}}` implementation and allow the name of the helper to be specified. Handlebars has several built in blocks that are availble on helper's `options` argument, namely `options.fn` & `options.inverse`. These would be changed to `options.blocks.default` & `options.blocks.inverse` respectively. Every other named template block would be available on `options.blocks` hash. The above example would have `options.blocks.header` & `options.blocks.footer` in addition to it's default blocks. -## Blocks must be available in the template +The component hook will append named blocks onto the template as it does currently with default block. + +## Block are available in the layout The component must be able to determine programmatically if it should consume it's default block or use the passed in named block. If we consider the above example, then `table-component`'s layout might look something like this. ``` -{{#if template.blocks.header}} - {{template.blocks.header.yield headerContent}} +{{#if blocks.header}} + {{yield-to 'header' headerContent}} {{else}} {{#each headerContent as |name|}} @@ -101,11 +105,13 @@ The component must be able to determine programmatically if it should consume it {{/if}} ``` -Similar pattern can be followed to specify the ability to customize body and footer. +`blocks` keyword becomes a reserved keyword and will throw a warning when the component defines a *blocks* property. We already have a reserved `hasBlock` keyword in the template, so this will not be a far stretch. ## Named blocks have block params -Named template blocks need to be able to receive block params and yield values into the scope in the same way as regular template blocks can. This would allow the component to expose template friendly values to be used in the template block. +Named blocks will have block params and will receive values with a new helper. `{{yield-to}}` will take name of a block as a first parameter and yield properties as `{{yield}}` does. This will allow the component to expose template friendly values to be used in the named blocks. + +```{{yield-to 'header' headerContent}}``` will yield `headerContent` to `header` block. ## Named blocks must be portable From 5bde6840e4f61a683f7654ef2d71074ec7fd1208 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Sun, 12 Apr 2015 21:41:39 -0400 Subject: [PATCH 4/4] Removed reference to be able to pass blocks around --- active/0003-named-template-blocks.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/active/0003-named-template-blocks.md b/active/0003-named-template-blocks.md index 7ce46557df..5cb1812b99 100644 --- a/active/0003-named-template-blocks.md +++ b/active/0003-named-template-blocks.md @@ -109,14 +109,10 @@ The component must be able to determine programmatically if it should consume it ## Named blocks have block params -Named blocks will have block params and will receive values with a new helper. `{{yield-to}}` will take name of a block as a first parameter and yield properties as `{{yield}}` does. This will allow the component to expose template friendly values to be used in the named blocks. +Named blocks will have block params and will receive values with a new `{{yield-to}}` helper. `{{yield-to}}` will take name of a block as a first parameter and yield properties as `{{yield}}` does. This will allow the component to expose template friendly values to be used in the named blocks. ```{{yield-to 'header' headerContent}}``` will yield `headerContent` to `header` block. -## Named blocks must be portable - -Under certain circomstances it might be necessary to be able to pass a named block into a nested component. This would make it easier to compose nested components. - # Drawbacks Why should we *not* do this? @@ -127,4 +123,4 @@ What other designs have been considered? What is the impact of not doing this? # Unresolved questions -What parts of the design are still TBD? +* Should this be added to Handlebars or should we fork Handlebars?