@@ -17,9 +17,10 @@ an element is setting up or tearing down. Today, this logic conventionally lives
17
17
in the ` didInsertElement ` , ` didRender ` , ` didUpdate ` , and ` willDestroyElement `
18
18
hooks in components, but there are cases where these hooks are not ideal.
19
19
20
- This RFC proposes adding two new generic element modifiers, ` {{did-render}} ` and
21
- ` {{will-destroy}} ` , which users can use to run code during the most common
22
- phases of any element's lifecycle.
20
+ This RFC proposes creating an official Ember addon which provides three new
21
+ generic element modifiers: ` {{did-insert}} ` , ` {{did-update}} ` , and
22
+ ` {{will-destroy}} ` . Users will be able to use these to run code during the most
23
+ common phases of any element's lifecycle.
23
24
24
25
## Motivation
25
26
@@ -87,7 +88,7 @@ without worrying about the overall lifecycle:
87
88
``` hbs
88
89
{{#if this.isOpen}}
89
90
<div
90
- {{did-render (action this.setupPopper)}}
91
+ {{did-insert (action this.setupPopper)}}
91
92
{{will-destroy (action this.teardownPopper)}}
92
93
93
94
class="popover"
@@ -119,7 +120,7 @@ loop, and the render modifiers can be used to solve them as well:
119
120
<ul>
120
121
{{#each items as |item|}}
121
122
<li
122
- {{did-render (action this.registerElement)}}
123
+ {{did-insert (action this.registerElement)}}
123
124
{{will-destroy (action this.unregisterElement)}}
124
125
>
125
126
...
@@ -206,71 +207,106 @@ Developers will be able to define element modifiers in the future with modifier
206
207
managers provided by addons. However, the proposed modifier APIs are fairly
207
208
verbose (with good reason) and not stabilized.
208
209
209
- However, ` {{did- render}} ` and ` {{will-destroy}} ` can receive _ any_ function as
210
- their first parameter, allowing users to share and reuse common element setup
211
- code with helpers. For instance, a simple ` scrollTo ` helper could be created to
212
- set the scroll position of an element:
210
+ However, the render modifiers can receive _ any_ function as their first
211
+ parameter, allowing users to share and reuse common element setup code with
212
+ helpers. For instance, a simple ` scrollTo ` helper could be created to set the
213
+ scroll position of an element:
213
214
214
215
``` js
215
216
// helpers/scroll-to.js
216
217
export default function scrollTo () {
217
- (element , scrollPosition ) => element .scrollTop = scrollPosition;
218
+ return (element , scrollPosition ) => element .scrollTop = scrollPosition;
218
219
}
219
220
```
220
221
``` hbs
221
- <div {{did-render (scroll-to) @scrollPosition}} class="scroll-container">
222
+ <div
223
+ {{did-insert (scroll-to) @scrollPosition}}
224
+ {{did-update (scroll-to) @scrollPosition}}
225
+ class="scroll-container"
226
+ >
222
227
...
223
228
</div>
224
229
```
225
230
231
+ ### Official Addon
232
+
233
+ While these modifiers will be generally useful, modifiers are meant to be a more
234
+ generic API that can be used to create libraries for solving specific problems.
235
+ Unfortunately, the community hasn't had much time to experiment with modifiers,
236
+ since the public API for them hasn't been finalized.
237
+
238
+ The modifiers in this RFC will provide an basic stepping stone for users who
239
+ want to emulate lifecycle hooks and incrementally convert their applications to
240
+ modifiers while modifiers in general are being experimented with in the
241
+ community. In time, users should be able to pick and choose the modifiers that
242
+ suit their needs more directly and effectively, and they shouldn't have to
243
+ include these modifiers in the payload. These modifiers should also not be seen
244
+ as the "Ember way" - they are just another addon, a basic one supported by
245
+ the Ember core team, but one which may or may not be appropriate for a given
246
+ application.
247
+
226
248
## Detailed design
227
249
228
- This RFC proposes adding two element modifiers, ` {{did-render}} ` and
229
- ` {{will-destroy}} ` . Note that element modifiers do _ not_ run in SSR mode - this
230
- code is only run on clients.
250
+ This RFC proposes adding three element modifiers:
251
+
252
+ * ` {{did-insert}} `
253
+ * ` {{did-update}} `
254
+ * ` {{will-destroy}} `
255
+
256
+ Note that element modifiers do _ not_ run in SSR mode - this code is only run on
257
+ clients. Each of these modifiers receives a callback as it's first positional
258
+ parameter:
259
+
260
+ ``` ts
261
+ type RenderModifierCallback = (element : Element , positionalArgs : [any ], namedArgs : object ): void ;
262
+ ```
263
+
264
+ The ` element ` argument is the element that the modifier is applied to,
265
+ ` positionalArgs ` contains any remaining positional arguments passed to the
266
+ modifier besides the callback, and ` namedArgs ` contains any named arguments
267
+ passed to the modifier. If the first positional argument is not a callable
268
+ function, the modifier will throw an error.
231
269
232
270
> Note: The timing semantics in the following section were mostly defined in the
233
271
> [ element modifier manager RFC] ( https://github.com/emberjs/rfcs/blob/master/text/0373-Element-Modifier-Managers.md )
234
272
> and are repeated here for clarity and convenience.
235
273
236
- ### ` {{did-render }} `
274
+ ### ` {{did-insert }} `
237
275
238
- This modifier is activated:
239
-
240
- 1 . When The element is inserted in the DOM
241
- 2 . Whenever any of the arguments passed to it update, including the function
242
- passed as the first argument.
276
+ This modifier is activated only when The element is inserted in the DOM.
243
277
244
278
It has the following timing semantics when activated:
245
279
246
280
* ** Always**
247
281
* called after DOM insertion
248
- * called _ after_ any child element's ` {{did-render }} ` modifiers
282
+ * called _ after_ any child element's ` {{did-insert }} ` modifiers
249
283
* called _ after_ the enclosing component's ` willRender ` hook
250
284
* called _ before_ the enclosing component's ` didRender ` hook
251
285
* called in definition order in the template
252
- * called after the arguments to the modifier have changed
253
286
* ** May or May Not**
254
287
* be called in the same tick as DOM insertion
255
288
* have the sibling nodes fully initialized in DOM
256
- * ** Never**
257
- * called if the arguments to the modifier are constants
258
289
259
290
Note that these statements do not refer to when the modifier is _ activated_ ,
260
291
only to when it will be run relative to other hooks and modifiers _ should it be
261
292
activated_ . The modifier is only activated on insertion and arg changes.
262
293
263
- ` {{did-render}} ` receives a function with the following signature as the first
264
- positional parameter:
294
+ ### ` {{did-update}} `
265
295
266
- ``` ts
267
- type DidRenderHandler = (element : Element , ... args ): void ;
268
- ```
296
+ This modifier is activated only on _ updates_ to it's arguments (both positional
297
+ and named). It does _ not_ run during or after initial render, or before
298
+ element destruction.
299
+
300
+ It has the following timing semantics when activated:
269
301
270
- The ` element ` argument is the element that the modifier is applied to, and the
271
- rest of the arguments are any remaining positional parameters passed to
272
- ` {{did-render}} ` . If the first positional parameter is not a callable function,
273
- ` {{did-render}} ` will throw an error.
302
+ * ** Always**
303
+ * called after the arguments to the modifier have changed
304
+ * called _ after_ any child element's ` {{did-update}} ` modifiers
305
+ * called _ after_ the enclosing component's ` willUpdate ` hook
306
+ * called _ before_ the enclosing component's ` didUpdate ` hook
307
+ * called in definition order in the template
308
+ * ** Never**
309
+ * called if the arguments to the modifier are constants
274
310
275
311
### ` {{will-destroy}} `
276
312
@@ -285,52 +321,33 @@ It has the following timing semantics when activated:
285
321
* called _ before_ the enclosing component's ` willDestroy ` hook
286
322
* called in definition order in the template
287
323
* ** May or May Not**
288
- * be called in the same tick as DOM insertion
289
-
290
- ` {{will-destroy}} ` receives a function with the following signature as the first
291
- positional parameter:
292
-
293
- ``` ts
294
- type WillDestroyHandler = function (element : Element , ... args ): void ;
295
- ```
296
-
297
- The ` element ` argument is the element that the modifier is applied to, and the
298
- rest of the arguments are any remaining positional parameters passed to
299
- ` {{will-destroy}} ` . If the first positional parameter is not a callable function,
300
- ` {{will-destroy}} ` will throw an error.
324
+ * be called in the same tick as DOM removal
301
325
302
326
### Function Binding
303
327
304
328
Functions which are passed to these element modifiers will _ not_ be bound to any
305
329
context by default. Users can bind them using the ` (action) ` helper:
306
330
307
331
``` hbs
308
- <div {{did-render (action this.teardownElement )}}></div>
332
+ <div {{did-insert (action this.setupElement )}}></div>
309
333
```
310
334
311
- Currently, neither modifiers nor helpers in Glimmer are given the context of the
312
- template at any point. Both the ` {{action}} ` helper and modifier are given the
313
- context as an implicit first argument, via an AST transform. The above becomes
314
- the following in the final template, before it is compiled into the Glimmer byte
315
- code:
335
+ Or by using the ` @action ` decorator provided by the
336
+ [ Decorators RFC] ( https://github.com/emberjs/rfcs/pull/408 ) to bind the function
337
+ in the class itself:
316
338
339
+ ``` js
340
+ export default class ExampleComponent extends Component {
341
+ @action
342
+ setupElement () {
343
+ // ...
344
+ }
345
+ }
346
+ ```
317
347
``` hbs
318
- <div {{did-render (action this this.teardownElement) }}></div>
348
+ <div {{did-insert this.setupElement }}></div>
319
349
```
320
350
321
- This gives ` {{action}} ` the correct context to bind the function it is passed
322
- and was done purely for backwards compatibility, since ` {{action}} ` existed
323
- before modifiers and helpers were fully rationalized as features.
324
-
325
- Adding this implicit context to other helpers and modifiers would require
326
- changes to the Glimmer VM and is a much larger language design problem. As such,
327
- we believe it is out of scope for this RFC. Default binding behavior could be
328
- added in the future, if a context API is decided on.
329
-
330
- > Note: It's worth calling out that action's binding behavior can be confusing
331
- > in cases as well, check out [ ember-bind-helper] ( https://github.com/Serabe/ember-bind-helper )
332
- > for an example and alternatives.
333
-
334
351
## How we teach this
335
352
336
353
Element modifiers will be new to everyone, so we're starting with a mostly blank
@@ -343,14 +360,18 @@ should be seen as the place for any logic which needs to act directly on an
343
360
element, or when an element is added to or removed from the DOM. Modifiers can
344
361
be fully independent (for instance, a ` scroll-to ` modifier that transparently
345
362
manages the scroll position of the element) or they can interact with the
346
- component (like the ` did-render ` and ` will-destroy ` modifiers). In all cases
363
+ component (like the ` did-insert ` and ` will-destroy ` modifiers). In all cases
347
364
though, they are _ tied to the render lifecycle of the element_ , and they
348
365
generally contain _ side-effects_ (though these may be transparent and
349
366
declarative, as in the case of ` {{action}} ` or the theoretical ` {{scroll-to}} ` ).
350
367
351
368
Second, we should teach the render modifiers specifically. We can do this by
352
369
illustrating common use cases which can currently be solved with render hooks,
353
- and comparing them to using modifiers for the same solution.
370
+ and comparing them to using modifiers for the same solution. We should also
371
+ emphasize that these are an addon, not part of the core framework, and are
372
+ useful as solutions for _ specific_ problems. As more modifiers become available,
373
+ we should create additional guides that focus on using the _ best_ modifier for
374
+ the job, rather than these generic ones.
354
375
355
376
One thing we should definitely avoid teaching except in advanced cases is the
356
377
_ ordering_ of element modifiers. Ideally, element modifiers should be
@@ -548,9 +569,6 @@ Usage:
548
569
549
570
## Drawbacks
550
571
551
- * Element modifiers are a new concept that haven't been fully stabilized as of
552
- yet. It may be premature to add default modifiers to the framework.
553
-
554
572
* Adding these modifiers means that there are more ways to accomplish similar
555
573
goals, which may be confusing to developers. It may be less clear which is the
556
574
conventional solution in a given situation.
@@ -564,24 +582,4 @@ Usage:
564
582
* Stick with only lifecycle hooks for these situations, and don't add generic
565
583
modifiers for them.
566
584
567
- * Add an implicit context to modifiers and helpers, instead of relying on users
568
- to bind functions manually. Doing this should take into account a few
569
- constraints and considerations:
570
-
571
- * Adding an implicit context may make it more difficult to optimize modifiers
572
- and helpers in the future. If possible, this should be something they opt
573
- _into_, so only helpers which _need_ a context will deoptimize.
574
-
575
- * Binding can be counterintuitive in some cases. For instance:
576
-
577
- ` ` ` hbs
578
- < button {{action this .service .reloadData }}> Reload< / button>
579
- ` ` `
580
-
581
- This example will likely error, because the ` reloadData` function will be
582
- bound to the _component_, not the service. Likewise, binding helpers doesn't
583
- really make sense, since they should be pure functions. Solutions like the
584
- [` {{bind}}` helper](https://github.com/Serabe/ember-bind-helper) attempt to
585
- address this, but may not be something that can be fully rationalized (what
586
- happens if there are multiple contexts?)
587
585
0 commit comments