From b2dfc54f1d93a722c0f3c94eb73812fa038dac65 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Fri, 18 Dec 2020 15:10:27 -0800 Subject: [PATCH 1/2] [FEATURE named-blocks] Enable Named Blocks Enables named blocks. As a language feature, this does not include any extra documentation. --- packages/@ember/canary-features/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@ember/canary-features/index.ts b/packages/@ember/canary-features/index.ts index 8ba141bd5d0..13fb1360125 100644 --- a/packages/@ember/canary-features/index.ts +++ b/packages/@ember/canary-features/index.ts @@ -15,7 +15,7 @@ import { assign } from '@ember/polyfills'; export const DEFAULT_FEATURES = { EMBER_LIBRARIES_ISREGISTERED: null, EMBER_IMPROVED_INSTRUMENTATION: null, - EMBER_NAMED_BLOCKS: null, + EMBER_NAMED_BLOCKS: true, EMBER_GLIMMER_HELPER_MANAGER: true, EMBER_GLIMMER_INVOKE_HELPER: true, EMBER_MODERNIZED_BUILT_IN_COMPONENTS: null, From c9840c0d96e3701a6191856ecf379e13a6c18388 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Sat, 19 Dec 2020 08:34:28 -0800 Subject: [PATCH 2/2] add docs --- .../-internals/glimmer/lib/component.ts | 105 ++----- .../glimmer/lib/glimmer-component-docs.ts | 269 +++++++++++++++++- 2 files changed, 278 insertions(+), 96 deletions(-) diff --git a/packages/@ember/-internals/glimmer/lib/component.ts b/packages/@ember/-internals/glimmer/lib/component.ts index f7f65e7c123..83a3130c338 100644 --- a/packages/@ember/-internals/glimmer/lib/component.ts +++ b/packages/@ember/-internals/glimmer/lib/component.ts @@ -30,95 +30,30 @@ import { */ /** - A component is an isolated piece of UI, represented by a template and an - optional class. When a component has a class, its template's `this` value - is an instance of the component class. + A component is a reusable UI element that consists of a `.hbs` template and an + optional JavaScript class that defines its behavior. For example, someone + might make a `button` in the template and handle the click behavior in the + JavaScript file that shares the same name as the template. - ## Template-only Components + Components are broken down into two categories: - The simplest way to create a component is to create a template file in - `app/templates/components`. For example, if you name a template - `app/templates/components/person-profile.hbs`: + - Components _without_ JavaScript, that are based only on a template. These + are called Template-only or TO components. + - Components _with_ JavaScript, which consist of a template and a backing + class. - ```app/templates/components/person-profile.hbs -

{{@person.name}}

- -

{{@person.signature}}

- ``` - - You will be able to use `` to invoke this component elsewhere - in your application: - - ```app/templates/application.hbs - - ``` - - Note that component names are capitalized here in order to distinguish them - from regular HTML elements, but they are dasherized in the file system. - - While the angle bracket invocation form is generally preferred, it is also - possible to invoke the same component with the `{{person-profile}}` syntax: - - ```app/templates/application.hbs - {{person-profile person=this.currentUser}} - ``` - - Note that with this syntax, you use dashes in the component name and - arguments are passed without the `@` sign. - - In both cases, Ember will render the content of the component template we - created above. The end result will be something like this: - - ```html -

Tomster

- -

Out of office this week

- ``` - - ## File System Nesting - - Components can be nested inside sub-folders for logical groupping. For - example, if we placed our template in - `app/templates/components/person/short-profile.hbs`, we can invoke it as - ``: - - ```app/templates/application.hbs - - ``` + Ember ships with two types of JavaScript classes for components: - Or equivalently, `{{person/short-profile}}`: + 1. Glimmer components, imported from `@glimmer/component`, which are the + default component's for Ember Octane (3.15) and more recent editions. + 2. Classic components, imported from `@ember/component`, which were the + default for older editions of Ember (pre 3.15). - ```app/templates/application.hbs - {{person/short-profile person=this.currentUser}} - ``` - - ## Yielding Contents - - You can use `yield` inside a template to include the **contents** of any block - attached to the component. The block will be executed in its original context: - - ```handlebars - -

Admin mode

- {{! Executed in the current context. }} -
- ``` - - or - - ```handlebars - {{#person-profile person=this.currentUser}} -

Admin mode

- {{! Executed in the current context. }} - {{/person-profile}} - ``` - - ```app/templates/components/person-profile.hbs -

{{@person.name}}

- {{yield}} - ``` + Below is the documentation for Classic components. If you are looking for the + API documentation for Template-only or Glimmer components, it is + [available here](/ember/release/modules/@glimmer%2Fcomponent). - ## Customizing Components With JavaScript + ## Defining a Classic Component If you want to customize the component in order to handle events, transform arguments or maintain internal state, you implement a subclass of `Component`. @@ -130,7 +65,7 @@ import { export default Component.extend({ displayName: computed('person.title', 'person.firstName', 'person.lastName', function() { - let { title, firstName, lastName } = this; + let { title, firstName, lastName } = this.person; if (title) { return `${title} ${lastName}`; @@ -148,7 +83,7 @@ import { {{yield}} ``` - ## Customizing a Component's HTML Element in JavaScript + ## Customizing a Classic Component's HTML Element in JavaScript ### HTML Tag diff --git a/packages/@ember/-internals/glimmer/lib/glimmer-component-docs.ts b/packages/@ember/-internals/glimmer/lib/glimmer-component-docs.ts index 2b09c1fec54..f2bc9aa09ea 100644 --- a/packages/@ember/-internals/glimmer/lib/glimmer-component-docs.ts +++ b/packages/@ember/-internals/glimmer/lib/glimmer-component-docs.ts @@ -4,25 +4,272 @@ might make a `button` in the template and handle the click behavior in the JavaScript file that shares the same name as the template. - The APIs available in a component vary depending on whether they import from - `@glimmer/component` or the older "classic" type, `@ember/component`. The - documentation below covers 100% of the available methods, hooks, and - properties of `@glimmer/component`. The source code can be found in - [`@glimmer/component`](https://github.com/glimmerjs/glimmer.js/tree/master/packages/%40glimmer/component). + Components are broken down into two categories: - ## Defining a component + - Components _without_ JavaScript, that are based only on a template. These + are called Template-only or TO components. + - Components _with_ JavaScript, which consist of a template and a backing + class. - To define a component, subclass `Component` and add your own properties, - methods and lifecycle hooks: + Ember ships with two types of JavaScript classes for components: - ```javascript + 1. Glimmer components, imported from `@glimmer/component`, which are the + default component's for Ember Octane (3.15) and more recent editions. + 2. Classic components, imported from `@ember/component`, which were the + default for older editions of Ember (pre 3.15). + + Below is the documentation for Template-only and Glimmer components. If you + are looking for the API documentation for Classic components, it is + [available here](/ember/release/classes/Component). The source code for + Glimmer components can be found in [`@glimmer/component`](https://github.com/glimmerjs/glimmer.js/tree/master/packages/%40glimmer/component). + + ## Defining a Template-only Component + + The simplest way to create a component is to create a template file in + `app/templates/components`. For example, if you name a template + `app/templates/components/person-profile.hbs`: + + ```app/templates/components/person-profile.hbs +

{{@person.name}}

+ +

{{@person.signature}}

+ ``` + + You will be able to use `` to invoke this component elsewhere + in your application: + + ```app/templates/application.hbs + + ``` + + Note that component names are capitalized here in order to distinguish them + from regular HTML elements, but they are dasherized in the file system. + + While the angle bracket invocation form is generally preferred, it is also + possible to invoke the same component with the `{{person-profile}}` syntax: + + ```app/templates/application.hbs + {{person-profile person=this.currentUser}} + ``` + + Note that with this syntax, you use dashes in the component name and + arguments are passed without the `@` sign. + + In both cases, Ember will render the content of the component template we + created above. The end result will be something like this: + + ```html +

Tomster

+ +

Out of office this week

+ ``` + + ## File System Nesting + + Components can be nested inside sub-folders for logical groupping. For + example, if we placed our template in + `app/templates/components/person/short-profile.hbs`, we can invoke it as + ``: + + ```app/templates/application.hbs + + ``` + + Or equivalently, `{{person/short-profile}}`: + + ```app/templates/application.hbs + {{person/short-profile person=this.currentUser}} + ``` + + ## Using Blocks + + You can use `yield` inside a template to include the **contents** of any block + attached to the component. For instance, if we added a `{{yield}}` to our + component like so: + + ```app/templates/components/person-profile.hbs +

{{@person.name}}

+ {{yield}} + ``` + + We could then invoke it like this: + + ```handlebars + +

Admin mode

+
+ ``` + + or with curly syntax like this: + + ```handlebars + {{#person-profile person=this.currentUser}} +

Admin mode

+ {{/person-profile}} + ``` + + And the content passed in between the brackets of the component would be + rendered in the same place as the `{{yield}}` within it, replacing it. + + Blocks are executed in their original context, meaning they have access to the + scope and any in-scope variables where they were defined. + + ### Passing parameters to blocks + + You can also pass positional parameters to `{{yield}}`, which are then made + available in the block: + + ```app/templates/components/person-profile.hbs +

{{@person.name}}

+ {{yield @person.signature}} + ``` + + We can then use this value in the block like so: + + ```handlebars + + {{signature}} + + ``` + + ### Passing multiple blocks + + You can pass multiple blocks to a component by giving them names, and + specifying which block you are yielding to with `{{yield}}`. For instance, if + we wanted to add a way for users to customize the title of our + `` component, we could add a named block inside of the header: + + ```app/templates/components/person-profile.hbs +

{{yield to="title"}}

+ {{yield}} + ``` + + This component could then be invoked like so: + + ```handlebars + + <:title>{{this.currentUser.name}} + <:default>{{this.currentUser.siganture}} + + ``` + + When passing named blocks, you must name every block, including the `default` + block, which is the block that is defined if you do not pass a `to` parameter + to `{{yield}}`. Whenever you invoke a component without passing explicitly + named blocks, the passed block is considered the `default` block. + + ### Passing parameters to named bxlocks + + You can also pass parameters to named blocks: + + ```app/templates/components/person-profile.hbs +

{{yield to="title" @person.name}}

+ {{yield @person.signature}} + ``` + + These parameters can then be used like so: + + ```handlebars + + <:title as |name|>{{name}} + <:default as |signature|>{{siganture}} + + ``` + + ### Checking to see if a block exists + + You can also check to see if a block exists using the `(has-block)` keyword, + and conditionally use it, or provide a default template instead. + + ```app/templates/components/person-profile.hbs +

+ {{#if (has-block "title")}} + {{yield to="title" @person.name}} + {{else}} + {{@person.name}} + {{/if}} +

+ + {{#if (has-block)}} + {{yield @person.signature}} + {{else}} + {{@person.signature}} + {{/if}} + ``` + + With this template, we can then optionally pass in one block, both blocks, or + none at all: + + ```handlebars + {{! passing both blocks }} + + <:title as |name|>{{name}} + <:default as |signature|>{{siganture}} + + + {{! passing just the title block }} + + <:title as |name|>{{name}} + + + {{! passing just the default block }} + + {{signature}} + + + {{! not passing any blocks }} + + ``` + + ### Checking to see if a block has parameters + + We can also check if a block receives parameters using the `(has-block-params)` + keyword, and conditionally yield different values if so. + + ```app/templates/components/person-profile.hbs + {{#if (has-block-params)}} + {{yield @person.signature}} + {{else}} + {{yield}} + {{/if}} + ``` + + ## Customizing Components With JavaScript + + To add JavaScript to a component, create a JavaScript file in the same + location as the template file, with the same name, and export a subclass + of `Component` as the default value. For example, to add Javascript to the + `PersonProfile` component which we defined above, we would create + `app/comopnents/person-profile.js` and export our class as the default, like + so: + + ```app/components/person-profile.js import Component from '@glimmer/component'; - export default class SomeComponent extends Component { - // your code here + export default class PersonProfileComponent extends Component { + get displayName() { + let { title, firstName, lastName } = this.args.person; + + if (title) { + return `${title} ${lastName}`; + } else { + return `${firstName} ${lastName}`; + } + }) } ``` + You can add your own properties, methods, and lifecycle hooks to this + subclass to customize its behavior, and you can reference the instance of the + class in your template using `{{this}}`. For instance, we could access the + `displayName` property of our `PersonProfile` component instance in the + template like this: + + ```app/templates/components/person-profile.hbs +

{{this.displayName}}

+ {{yield}} + ``` + ## `constructor` params: `owner` object and `args` object