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>
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,
};
};
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>
`;
}
Play with a fully working example here: