The idea is to substitute class manipulation functions, such as 'add', 'remove'... with wrappers, that send class change messages before or after class list changed. It's very simple to use:
choose element(s) or query that selects elements, and pass it to the function.
add 'class-change' and/or 'class-add', 'class-remove'... handlers to either elements or their container ('document', for example).
after that, any class list change by either add, remove, replace or toggle methods will fire corresponding events.
Event sequence is:
A) 'class-change' request event is fired, that can be rejected by handler by preventDefault() if needed. If rejected, then class change will be cancelled.
B) class change function will be executed
B) 'class-add' or 'class-remove'... information event is fired.
function addClassChangeEventDispatcher( el )
{
// select or use multiple elements
if(typeof el === 'string') el = [...document.querySelectorAll( el )];
// process multiple elements
if(Array.isArray( el )) return el.map( addClassChangeEventDispatcher );
// process single element
// methods that are called by user to manipulate
let clMethods = ['add','remove','toggle','replace'];
// substitute each method of target element with wrapper that fires event after class change
clMethods.forEach( method =>
{
let f = el.classList[method];
el.classList[method] = function()
{
// prepare message info
let detail = method == 'toggle' ? { method, className: arguments[0] } :
method == 'replace' ? { method, className: arguments[0], newClassName: arguments[1] } :
{ method, className: arguments[0], classNames: [...arguments] };
// fire class change request, and if rejected, cancel class operation
if(!el.dispatchEvent( new CustomEvent( 'class-change', {bubbles: true, cancelable: true, detail} ))) return;
// call original method and then fire changed event
f.call( this, ...arguments );
el.dispatchEvent( new CustomEvent( 'class-' + method, {bubbles: true, detail} ));
};
});
return el;
}