I have a Stimulus JS controller with a target called colorKey
. ColorKey can be one of 5 different colors, set by adding 1 of 5 different CSS classes ('bg-green', 'bg-blue', etc)
From my reading of the css classes section of the stimulusJS docs, it seems I'm expected to update the color by using classList.add
/classList.remove
calls. That works, but it means I'm running:
this.colorKeyTarget.classList.remove(this.orangeClass)
this.colorKeyTarget.classList.remove(this.redClass)
this.colorKeyTarget.classList.remove(this.blueClass)
this.colorKeyTarget.classList.remove(this.blackClass)
this.colorKeyTarget.classlist.add(this.greenClass)
which isn't exactly elegant to put it mildly.
I was hoping/assuming I could do something like:
<div data-bg-color-class="bg-orange"></div>
in the dom, and then simply call:
this.bgColorClass = "bg-green"
instead. I assume there's some way to do something similar to the latter, but I can't figure out what it is. Anyone have any suggestions on best practices for assigning one of 5 classes to a DOM via stimulus in a way that doesn't involve removing every possible state before adding the one state you want?
For what you are trying to achieve, the Stimulus CSS Classes approach may not be the most suitable. It seems like you are setting up something more like a background preview picker.
You could use classes here but as noted, it becomes a bit complex to add more and more options. Classes are better used for things like 'loading/active' classes, a small set of discrete states that can be reflected also by class name changes.
Instead, you may want a Stimulus value approach here and something that is easier to map through to switch all things off except something that is selected.
I recommend a Stimulus Object
value, this is a JSON string that can be stored on the controller and can easily map key/value pairs.
"
for attributes, you will need to escape this correctly. Your rendering library should do this for you but if you are hand-writing this value, double check the output.colorKey
to something simpler as you likely have the word color
already in the HTML in many places.<div data-controller="bg-color" data-bg-color-classes-value='{"red": "something-red","blue":"something-borrowed","orange":"something-new","black":"bg-000000","green":"i-choose-green"}'>
<div data-bg-color-target="container">
CONTENT!
</div>
<button data-action="bg-color#show" data-bg-color-key-param="red">Go Red!</button>
<button data-action="bg-color#show" data-bg-color-key-param="green">Go Green!</button>
</div>
key
from the show
method either at the CustomEvent detail
or the Action params. This could be done a few different ways, depending on how you plan to trigger things.show
method, we do a few checks for things before we attempt to mutate the DOM, returning early if something is missing.Object.entries
to either add the class or remove the class. This means that the only code needing to be changed if you wanted to add/remove colours is the HTML data attribute for the value.class BgColor extends Controller {
static targets = ['container'];
static values = { classes: Object };
show(event) {
const { key } = event?.params || event?.detail || {}; // destructure key from either Stimulus action params for dispatched event detail.
const element = this.containerTarget;
const classes = this.hasClassesValue ? this.classesValue : {};
const currentColorClass = classes[colorKey];
if (!currentColorClass || !element) return; // ignore unmatched class silently (could throw an error also)
Object.entries(classes).forEach(([colorKey, value]) => {
if (key === colorKey) {
element.classList.add(value);
} else {
element.classList.remove(value);
}
});
}
}
classList.add/remove
will not work if you give a space separated string, it only will look at the first part of that string. If you expect that your classes will be something like "color color--blue" you will need to change your usage of classList.add/remove
. Something like element.classList.add(...value.split(" "));
should work no matter what format the string comes in.