Async Mixins with Rimmel.js
Dario Mannu
Posted on February 17, 2024
Rimmel.js is all about convenience.
If you want to extend components with any extra functionality (drag'n'drop, tooltips, other special effects), you can create a mixin to isolate it out in its own library or file:
export const mixin = () => ({
'onclick': e => console.log('Clicked button: ', e.button),
'onmouseover': someObservableStream,
'style': {color: 'red'},
'class': { 'a-new-class-name': false },
'dataset': {
'key1': 'data-value-1',
'key2': 'data-value-2',
}
})
The above RDOM (short for Reactive DOM) object can be "merged" into any HTML Element, so that onclick
and onmouseover
will be set as event handlers and any key-value pair inside style
will be just set as CSS.
Finally, an RDOM object like this can be merged into any HTML tag in a RML template like this:
const template = rml`
<button ...${mixin()}>very magic button</button>
`;
Async mixins
Mixins can work asynchrounously, as well.
If you want to activate one at a later time, you can have a promise or an observable emit your mixin:
const asyncMixin = new Promise(resolve => {
setTimeout(resolve(mixin(), 1000));
})
const observableMixin = new Observable(observer => {
setTimeout(observer.next(mixin(), 1000));
// ...
setTimeout(observer.next(anotherMixin(), 2000));
})
const template = rml`
<div ...${asyncMixin()}> blah </div>
<div ...${observableMixin()}> more blah </div>
`;
Essentially a mixin is either a static RDOM object or a function/future (Promise or an Observable) that can return, resolve or emit RDOM objects after the component is mounted.
An RDOM object is a JavaScript object where each key-value pair can be matched to some corresponding DOM attributes, event handlers, styles, class names, etc.
Each value in the object can be a future itself, so you have a remarkable level of flexibility to play with.
When to use async mixins
A good use case for async mixins is a user's logged-in status, or admin status. Suppose you define isLoggedIn
and isAdmin
as promises or observables, you can map them to the extra functionality you want to add, without having to re-render anything on the page.
import { isAdmin } from '/singletons/is-admin';
import { map } from 'rxjs/operators';
const disabled = isAdmin.pipe(
map(x=>!x)
);
const adminOnly = {
disabled // this will set/reset the "disabled" attribute
}
const template = rml`
<button ...${adminOnly}> admin button </button>
`;
Another example
Here is an example of a mixin that starts counting mouseovers on the host element inside a data-click-count
attribute after it's been clicked:
const clickCounter = () => {
const hover = new Subject();
const click = new Subject();
const counter = click.pipe(
take(1),
switchMap(() => hover),
scan(x=>x+1, 0),
);
return {
onmouseover: hover,
onclick: click,
dataset: {
clickCount: counter
}
};
};
document.body.innerHTML = rml`
<button ...${clickCounter()}>click me</button>
`;
Absurd, unrealistic example? Sure, but it helps illustrate the complexity of a relatively complex async, multi-event task resolved with some ridiculously simple, short and easy-to-test solution.
Posted on February 17, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.