Async Mixins with Rimmel.js
Tags: functionalreactiveprogramming, javascript, rimmel, webdev
Published: 2024-02-17 10:30:00

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:

```js
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:

```js
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:

```js
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.

```js
  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:

```js
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.