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.