2

I'm going through an array to apply different classes based on a state that is set externally. This is how I do it now, but I feel like i'm repeating myself a lot. Is there a DRYer way to do it? The classnames could be something else if that helps.

var children2 = Array.from(wrapper.children);
var s = state.state;
children2.forEach((child, i) => {
    var classes = [];
    child.classList.remove('active', 'before', 'previous', 'next', 'after');
    if(i < s) { classes.push('before'); };
    if(i > s) { classes.push('after');  };
    if(i === s) { classes.push('active') }
    if(i === s - 1) { classes.push('previous') }
    if (i === s + 1) { classes.push('next') }
    child.classList.add(...classes)
})
GYaN
  • 2,327
  • 4
  • 19
  • 39
Himmators
  • 14,278
  • 36
  • 132
  • 223

3 Answers3

6

The easiest solution is to use toggle:

toggle( String [, force] )

When only one argument is present: Toggle class value; i.e., if class exists then remove it and return false, if not, then add it and return true.

When a second argument is present: If the second argument evaluates to true, add specified class value, and if it evaluates to false, remove it.

For example like this:

let classes = child.classList;
classes.toggle('before', i < s);
classes.toggle('after', i > s);
classes.toggle('active', i === s);
classes.toggle('previous', i === s-1);
classes.toggle('next', i === s+1);

You could also create an object with the keys and conditions, then loop over it to toggle them individually:

const classes = {
  before: i < s,
  after: i > s,
  active: i === s,
  previous: i === s - 1,
  next: i === s + 1,
};
Object.entries(classes).forEach(([className, condition]) => child.classList.toggle(className, condition));

(Note that Object.entries is an ECMAScript 2017 feature.)

str
  • 42,689
  • 17
  • 109
  • 127
0

here's one way:

var children2 = Array.from(wrapper.children);
var s = state.state;

//Represent your states as StateName-Predicate map
const States = {
  'active': (i,s) => i===s,
  'before': (i,s) => i<s},
  'previous': (i,s) => i===s-1,
  'next': (i,s) => i===s+1,
  'after': (i,s) => i>s
};


children2.forEach((child, i) => {    
    const classList = child.classList;
    
    //Iterate over all available states
    Object.keys(States).forEach(stateName => {
      //Extract predicate function for a state
      const predicate = States[stateName];
      //If predicate evaluates to true - set class, otherwise - remove it
      classList.toggle(stateName, predicate(i,s) );  
    });
    
})
travnik
  • 690
  • 7
  • 18
0

Currently there is one pattern that is extremely repetitive in your code: "if (some condition), add this class". You could probably instead do something like this:

var classes = [
  i < s && 'before',
  i > s && 'after',
  i === s && 'active',
  i === s - 1 && 'previous',
  i === s + 1 && 'next'
].filter(x => x)

The first part of that code, var classes = [...], creates an array that looks something like [false, false, 'active', false, false], by conditionally adding items. For example, suppose i is 3 and s is also 3. When JavaScript evaluates i === s && 'active', it sees that i (3) === s (3), so it moves on to the next value, which is 'active', and sticks that inside the array. When it evaluates i === s + 1 && 'next', it sees that i (3) is not the same as s + 1 (4), so it immediately takes false and adds it to the list.

Then, we use filter to get rid of those false items, so we get an array more like ['active']. Basically, filter takes a function and passes each in the array item one by one to it; when the function returns a truthy value, it keeps the item, and otherwise, it removes the item. The function we passed is an arrow function; it just returns the value that is passed to it. You could replace it with function(x) { return x } and it would be functionally the same.

(I sometimes use .filter(Boolean) instead of .filter(x => x). Boolean can be used as a function to convert a value into a boolean (according to the "truthy" link above) - if you've seen !!x syntax before, it does the same thing. I find .filter(Boolean) more syntactic: "keep items that are truthy (like a boolean)". But it's entirely unnecessary because filter itself automatically checks if your function's returned value is truthy. So if you use it is just a matter of taste!)

Another option is to just get rid of the unnecessary braces and semicolons, like this:

if (i < s) classes.push('before')
if (i > s) classes.push('after')
// ..and so on.

It's certainly a bit more verbose, but you might prefer it anyways!

Nebula
  • 6,614
  • 4
  • 20
  • 40