Building Bridges to the DOM with Modifiers
kkkk
"Those who do not remember the past, are condemned to repeat it.
- George Santayana
What?
- Similar to Handlebars helpers
- Functions or classes
- Can be used in templates directly using {{double-curlies}}
{{action}}
{{bind-attr}}
Don't lifecycle hooks solve the same problem?
Better solution for DOM manipulation
- Allow targeting specific elements more easily
-
Allow related code to live in the same place.
- Make sharing code between components much easier
- Work with template only components
- Work with tagless components and Glimmer components
#1
Allow Targeting specific elements more easily.
Lifecycle hooks only allow you to work with the component's root element.
didInsertElement() {
this.element
.querySelector('button')
.addEventListener('click', this.handleClick);
}
#2
Allow related code to live in the same place.
class HelloWorld extends Component {
addTooltipListener() {
// save the element so we can remove the listener later
this._tooltip = this.element.querySelector('.tooltip');
if (this._tooltip) {
this._tooltip.addEventListener(
'mouseover',
this.toggleTooltip
);
}
}
removeTooltipListener() {
if (this._tooltip) {
this._tooltip.removeEventListener(
'mouseover',
this.toggleTooltip
);
}
}
didInsertElement() {
this.element
.querySelector('button')
.addEventListener('click', this.handleClick);
this.addTooltipListener();
}
didUpdate() {
this.removeTooltipListener();
this.addTooltipListener();
}
willDestroyElement() {
this.element
.querySelector('button')
.removeEventListener('click', this.handleClick);
this.removeTooltipListener();
}
// ...
}
Modifiers have their own setup and teardown logic, completely
self-contained.
Run on the insertion & destruction of the element they are modifying, not the component,
#3
Make sharing code between components much easier
Modifications are applied where they happen
#4
Work with template-only components
You MUST create a component class to do even simple DOM manipulation
#5
Work with tag-less components & Glimmer components
tagName: ''
Tag-less Components
tagName: ''
Life-cycle Hooks
Tag-less Components
tagName: ''
Life-cycle Hooks
this.element
Tag-less Components
this.element
Glimmer Components
this.element
Glimmer Components
didInsertElement
didRender
Component Class Definition
DOM Manipulation
Work without component API
How other framework do it?
React => useLayoutEffect
Modifier syntax
<div {{action this.handleClick}}></div>
<div onclick={{action this.handleClick}}></div>
<!-- MODIFIER -->
<div {{action this.handleClick}}></div>
<!-- HELPER -->
<div onclick={{action this.handleClick}}></div>
Same syntax as helper
Helper => Attribute
Modifier => Element
Modifiers run :
-
whenever the element is inserted or destroyed
-
whenever any of arguments to them change.
User defined modifiers
Modifier Manager
Low level API
Class Based Modifiers
- Fully featured
- Instance & State
- Ability to control each lifecycle event
export default class DarkMode extends Modifier {
@service userSettings;
didInsert(element, [darkModeClass]) {
if (this.userSettings.darkModeEnabled) {
this._previousDarkModeClass = darkModeClass;
element.classList.add(darkModeClass);
}
}
willDestroy(element) {
element.classList.remove(this._previousDarkModeClass);
}
didUpdate() {
this.willDestroy(...arguments);
this.didInsert(...arguments);
}
}
<!-- usage -->
<div {{dark-mode 'ui-dark'}}></div>
Functional Modifiers
- Functional API
- Same as `useLayoutEffect`
- Single function returns a cleanup function
function darkMode(userSettings, element, [darkModeClass]) {
if (userSettings.darkModeEnabled) {
element.classList.add(darkModeClass);
return () => {
element.classList.remove(darkModeClass);
};
}
}
export default modifier(
{ services: ['userSettings'] },
darkMode
);
With Decorators
@modifier
function darkMode(
@service userSettings,
element,
[darkModeClass]
) {
if (userSettings.darkModeEnabled) {
element.classList.add(darkModeClass);
return () => {
element.classList.remove(darkModeClass);
}
}
}
Ember Element Modifiers
By Rajasegar Chandiran
Ember Element Modifiers
- 927