HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Wrapping the classList API to add/remove array of classes

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
thearrayremovewrappingclassesapiaddclasslist

Problem

I've written a function that wraps the classList API, allowing you to pass an array of classes to add/remove, or the variable arguments already supported by classList.

The methods are the following:

var flatten = function(array) {
    return Array.prototype.concat.apply([], array);
};

var classMethod = function(method) {
    return function(element /*, classes... */) {
        return flatten(Array.prototype.slice.call(arguments, 1)).forEach(function(klass) {
            element.classList[method](klass);
        });
    }
};

var addClass = classMethod('add');
var removeClass = classMethod('remove');


Usage:

var elem = document.querySelector('.box');

addClass(elem,     'class1');
addClass(elem,     'class2', 'class3');
addClass(elem,    ['class2', 'class3']);

removeClass(elem,  'class1');
removeClass(elem,  'class2', 'class3');
removeClass(elem, ['class2', 'class3']);


I'm wondering if there's some better way to implement this function, say without using the forEach call and instead using Function.prototype.apply, but I get a TypeError: Illegal invocation when trying to refactor this into, say:

var classMethod = function(method) {
    return function(element /*, classes... */) {
        element.classList[method].apply(element, flatten(Array.prototype.slice.call(arguments, 1)));
    }
};


Is it possible to write this more succinctly, whilst keeping all of the use cases?

Solution

Interesting question,

I've been looking at this for a while, I dont think there is a more succinct way. I've been playing with reconstructing className from the add or remove function calls but it became a less succinct and terrible hack.

The core reason for that is default JS does not have enough convenience methods for arrays. If you were open to use something like LoDash, you could do this:

var addClass = function addClass(element /*, classes... */){
  element.className = _.uniq( element.className.split(' ').concat( _.flatten( _.rest( arguments ) ) ) ).join(' ');  
};

var removeClass = function removeClass(element /*, classes... */){
  element.className = _.difference( element.className.split(' '),  _.flatten( _.rest( arguments ) )  ).join(' ');
};


Other than that I really like your code; proper naming, indenting, size of functions, etc.

Update on illegal invocation, you are passing element as this to a function that is attached to classList, if you pass classList, then there will be no exception.

var classMethod = function(method) {
    return function(element /*, classes... */) {
        var classList = element.classList;
        element.classList[method].apply(classList,flatten(Array.prototype.slice.call(arguments, 1)));
    };
};


And then that works.. My mind is still boggling at that, but there you go.

Code Snippets

var addClass = function addClass(element /*, classes... */){
  element.className = _.uniq( element.className.split(' ').concat( _.flatten( _.rest( arguments ) ) ) ).join(' ');  
};

var removeClass = function removeClass(element /*, classes... */){
  element.className = _.difference( element.className.split(' '),  _.flatten( _.rest( arguments ) )  ).join(' ');
};
var classMethod = function(method) {
    return function(element /*, classes... */) {
        var classList = element.classList;
        element.classList[method].apply(classList,flatten(Array.prototype.slice.call(arguments, 1)));
    };
};

Context

StackExchange Code Review Q#70303, answer score: 3

Revisions (0)

No revisions yet.