ember-class-based-modifier
This is the next iteration of ember-oo-modifiers with some breaking changes to the API. If you are looking for the previous documentation, select the version you are using in the tags dropdown at the top of the page. For a list of API differences, see here
This addon provides a class-based API for authoring element modifiers in Ember, similar to the class-based helper API.
Compatibility
This is currently compatible with:
- Ember.js v2.18 or above
- Ember CLI v2.13 or above
Installation
ember install ember-class-based-modifier
Usage
This addon does not provide any modifiers out of the box; instead, this library allows you to write your own.
Much of this addon was based on ember-oo-modifiers, and, in turn, ember-functional-modifiers.
Example without Cleanup
For example, let's say you want to implement your own {{scroll-position}}
modifier (similar to this).
This modifier can be attached to any element and accepts a single positional argument. When the element is inserted, and whenever the argument is updated, it will set the element's scrollTop
property to the value of its argument.
// app/modifiers/scroll-position.js ; { // get the first positional argument passed to the modifier // // {{scoll-position @someNumber relative=@someBoolean}} // ~~~~~~~~~~~ // return thisargspositional0; } { // get the named argument "relative" passed to the modifier // // {{scoll-position @someNumber relative=@someBoolean}} // ~~~~~~~~~~~~ // return thisargsnamedrelative } { ifthisisRelative thiselementscrollTop += thisscrollPosition; else thiselementscrollTop = thisscrollPosition; }
Usage:
{{!-- app/components/scroll-container.hbs --}} <div class="scroll-container" style="width: 300px; heigh: 300px; overflow-y: scroll" {{scroll-position this.scrollPosition relative=false}}> {{yield this.scrollToTop}}</div>
// app/components/scroll-container.js ;;; @tracked scrollPosition = 0; @action { thisscrollPosition = 0; }
{{!-- app/templates/application.hbs --}} <ScrollContainer as |scroll|> A lot of content... <button {{on "click" scroll}}>Back To Top</button></ScrollContainer>
Example with Cleanup
If the functionality you add in the modifier needs to be torn down when the modifier is removed, you can use the willRemove
hook.
For example, if you want to have your elements dance randomly on the page using setInterval
, but you wanted to make sure that was canceled when the modifier was removed, you could do this:
// app/modifiers/move-randomly.js ;; const random round = Math;const DEFAULT_DELAY = 1000; setIntervalId = null; { // get the named argument "delay" passed to the modifier // // {{move-randomly delay=@someNumber}} // ~~~~~~~~~~~ // return thisargsnameddelay || DEFAULT_DELAY; } @action { let top = ; let left = ; thiselementstyletransform = `translate(px, px)`; } { if thissetIntervalId !== null ; thissetIntervalId = ; } { ; thissetIntervalId = null; }
Usage:
<div {{move-randomly}}> Catch me if you can!</div>
Example with Service Injection
You can also use services into your modifier, just like any other class in Ember.
For example, suppose you wanted to track click events with ember-metrics
:
// app/modifiers/track-click.js ;;; @service metrics; { // get the first positional argument passed to the modifier // // {{track-click "like-button-click" page="some page" title="some title"}} // ~~~~~~~~~~~~~~~~~~~ // return thisargspositional0; } { // get the named arguments passed to the modifier // // {{track-click "like-button-click" page="some page" title="some title"}} // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // return thisargsnamed; } @action { thismetrics; } { thiselement; } { thiselement; }
Usage:
<button {{track-click "like-button-click" page="some page" title="some title"}}> Click Me!</button>
Classic API
If you would like to use Ember.Object
based APIs, such as this.get
, this.set
, this.setProperties
, etc, you can import the "classic" base class instead, located at the import path ember-class-based-modifier/classic
.
The examples above can be rewritten using the classic API:
// app/modifiers/scroll-position.js ;; ;
// app/modifiers/move-randomly.js ;; const random round = Math;const DEFAULT_DELAY = 1000; ;
// app/modifiers/track-click.js ;;; ;
Whenever possible, it is recommended that you use the default "modern" API instead of the classic API.
TypeScript
Using the "modern" native class API, you can use .ts
instead of .js
and it'll just work, as long as you do runtime checks to narrow the types of your args when you access them.
// app/modifiers/scroll-position.ts;
But to avoid writing runtime checks, you can extend Modifier
with your own args, similar to the way you would define your args for a Glimmer Component.
// app/modifiers/scroll-position.ts;
See this pull request comment for a full discussion about using TypeScript with your Modifiers.
API
element
- The DOM element the modifier is attached to.
args
:{ positional: Array, named: Object }
- The arguments passed to the modifier.
args.positional
is an array of positional arguments, andargs.named
is an object containing the named arguments. isDestroying
true
if the modifier is in the process of being destroyed, or has already been destroyed.isDestroyed
true
if the modifier has already been destroyed.constructor(owner, args)
(orinit()
in classic API)- Constructor for the modifier. You must call
super(...arguments)
(orthis._super(...arguments)
in classic API) before performing other initialization. Theelement
is not yet available at this point (i.e. its value isnull
during construction). didReceiveArguments()
- Called when the modifier is installed and anytime the arguments are updated.
didUpdateArguments()
- Called anytime the arguments are updated but not on the initial install. Called before
didReceiveArguments
. didInstall()
- Called when the modifier is installed on the DOM element. Called after
didReceiveArguments
. willRemove()
- Called when the DOM element is about to be destroyed; use for removing event listeners on the element and other similar clean-up tasks.
willDestroy()
- Called when the modifier itself is about to be destroyed; use for teardown code. Called after
willRemove
. Theelement
is no longer available at this point (i.e. its value isnull
during teardown).
Lifecycle Summary
Install | Update | Remove | this.element |
this.args |
|
---|---|---|---|---|---|
constructor() |
(1) | ❌ | ❌ | ❌ | after super() |
didUpdateArguments() |
❌ | (1) | ❌ | ✔️ | ✔️ |
didReceiveArguments() |
(2) | (2) | ❌ | ✔️ | ✔️ |
didInstall() |
(3) | ❌ | ❌ | ✔️ | ✔️ |
willRemove() |
❌ | ❌ | (1) | ✔️ | ✔️ |
willDestroy() |
❌ | ❌ | (2) | ❌ | ✔️ |
- (#) Indicates the order of invocation for the lifecycle event.
- ❌ Indicates that the method is not invoked for a given lifecycle / property is not available.
- ✔️ Indicates that the property is available during the invocation of the given method.
ember-oo-modifiers
API differences from- Renamed package to
ember-class-based-modifier
. - No
Modifier.modifier()
function. - Classic API is located at
ember-class-based-modifier/classic
. - Arguments, both positional and named, are available on
this.args
. - Named arguments do not become properties on the modifier instance.
- Arguments are not passed to life-cycle hooks.
- Renamed
didInsertElement
todidInstall
andwillDestroyElement
towillRemove
. This is to emphasize that when the modifier is installed or removed, the underlying element may not be freshly inserted or about to go away. Therefore, it is important to perform clean-up work in thewillRemove
to reverse any modifications you made to the element. - Changed life-cycle hook order:
didReceiveArguments
fires beforedidInstall
, anddidUpdateArguments
fires beforedidReceiveArguments
, mirroring the classic component life-cycle hooks ordering. - Added
willDestroy
,isDestroying
andisDestroyed
with the same semantics as Ember objects and Glimmer components.
Contributing
See the Contributing guide for details.
License
This project is licensed under the MIT License.