DEV Community

Dario Mannu
Dario Mannu

Posted on

Drag'n'Drop without CSS classes using ObservableTypes

Many UI libraries and CSS frameworks used to enable special functionality by resorting to CSS classes. This was especially trendy at the time of jQuery plugins.

Despite being a very popular choice, it's definitely a programming anti-pattern.

Today we have several alternative ways. One approach from the functional-reactive land makes it possible to just "merge" functionality into an existing element. No CSS classes, no id attribute abuse.

Suppose we want to enable drag'n'drop in an HTML list by means of a separate reusable module we can add or remove at will.

  <ul ...${Sortable({onOrderChange: todoList.move})}>
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
    <li>item 4</li>
    <li>item 5</li>
  </ul>
Enter fullscreen mode Exit fullscreen mode

Sortable is going to be implemented in what's known as a Mixin. What it does, is export attributes, styles, classes, event handlers in a so called "DOM Object": whatever it contains, will be merged in the target element.

// sortable.ts
import { Subject, map, withLatestFrom } from 'rxjs';

export const Sortable = ({ onOrderChange }) => {
  const dragStart = new Subject<HTMLLIElement>();
  const drop = new Subject<HTMLLIElement>();

  drop.pipe(
    withLatestFrom(dragStart),
    map(([dropEvt, dragEvt]) => {
      const list = [...dragEvt.target.closest('ol,ul').children];
      return [ list.indexOf(dragEvt.target), list.indexOf(dropEvt.target.closest('li')) ]
    }),
  ).subscribe(([src, dst])=>onOrderChange(src, dst));

  // Export a DOM Object for a framework or UI library
  // to take care of and merge into the target element
  return {
    ondragstart: dragStart,
    ondragover: e=>e.preventDefault(),
    ondrop: drop,
  };
};
Enter fullscreen mode Exit fullscreen mode

So, the final application code will look something like this:

import { rml } from 'rimmel';

const List = () => {

  return rml`
      <ul ...${Sortable({onOrderChange: todoList.move})}>
        <li>item 1</li>
        <li>item 2</li>
        <li>item 3</li>
        <li>item 4</li>
        <li>item 5</li>
      </ul>
  `;
}
Enter fullscreen mode Exit fullscreen mode

Play with a fully working example here:

Top comments (0)