DEV Community

Cover image for Async Mixins with Rimmel.js
Dario Mannu
Dario Mannu

Posted on

Async Mixins with Rimmel.js

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',
  }
})
Enter fullscreen mode Exit fullscreen mode

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>
`;
Enter fullscreen mode Exit fullscreen mode

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>
`;
Enter fullscreen mode Exit fullscreen mode

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>
  `;
Enter fullscreen mode Exit fullscreen mode

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>
`;
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)