[Proposal] JS property `styles` (now a method)


#1

Current, if you want to set multiple styles on an element using JS, you have to set them one at a time. What I’m proposing is a styles property that is set with a JSON object. This can be achieved pretty easily in vanilla JS:


Object.defineProperty(Object.prototype, 'styles', {
	enumerable: true,
	configurable: false,
	get: function() { return this.style; },
	set: function(param) {
		if(this instanceof HTMLCollection) {
			for(i=0; i<this.length; i++) {
				for(key in param) {
					key = key.replace(/-[a-z]/g, "$&".toUpperCase() );
					this[i].style[key] = param[key];
				}
			}
		}
		else if(this instanceof HTMLElement) {
			for(key in param) {
				key = key.replace(/-[a-z]/g, "$&".toUpperCase() );
				this.style[key] = param[key];
			}
		}
	}
});

Obviously it would be implemented in ES6, rather than including that piece of code, but it shows how it could be done with current JS capabilities.

Proposed usage:

document.getElementById("foo").styles = {
    "background-color" : "red",
    "border"           : "3px solid purple",
    "color"            : "grey"
};

With the code block I included above, it would also work on iterable objects (returned by document.getElementsByClassName). It also uses regex to replace hyphens with the camelCase version, although that’s just because of the fact that it’s done using vanilla JS. Obviously that wouldn’t be necessary in a full implementation.

Thoughts?


#2

Assigning may be confusing as for whether styles should be added to existing or replace those. A method like setStyles() or setStyle() would probably be better in this regard.

Fwiw, all modern browsers including Firefox 35+ support setting hyphen-separated properties directly, with no need to convert their names to camelCase:

element.style['background-color'] = 'green';

#3

I don’t think so. It works the exact same way style does, just setting multiple at once. Apart from a JSON object, I can’t think of any other way to do this.

Fwiw, all modern browsers including Firefox 35+ support setting hyphen-separated properties directly, with no need to convert their names to camelCase:

Did now know this! Regardless, that script is more a demonstration of what I picture it to be, rather than what would actually be implemented.


#4

A method like setStyle() could take an object of the same type as you’ve proposed to assign to the styles attribute. The potentially confusing thing is not the object, but the assignment syntax. It’s typical that assignment replaces what has been previously stored in the variable or property the value is assigned to.


#5

Note that the pattern below is already possible:

Object.assign(foo.style, {
    "background-color" : "red",
    "border"           : "3px solid purple",
    "color"            : "grey"
});

Object.assign seems like a nice, terse way to do this.


#6

Nice, @simevidas.

I like the idea of setting multiple properties at one time.

This method would also need to consider that it can be confusing on which key would win when declaring multiple styles that are associated and can potentially overlap like:

{
    "background" : "red",
    "background-color" : "blue",
    "border-left"  : "3px solid purple",
    "border" : "3px dotted red",
}

When settings styles today, this is clear because the winning style would be the last declared. With this way, it’s not so clear since key value pairs don’t necessarily guaranteed an order. I could be wrong here, though. It’s been a while since I’ve looked at this spec.


#8

@simevidas, I could be wrong in my guess as to how that Object.assign() works, but isn’t that very expensive in terms of DOM lookups? Also, does this resolve the issue @mkay581 put forth?

I actually didn’t even consider the order. It could certainly be an edge case that outputs a warning in the console.

@MT Would a method make sense over a property here? The current style is a property, as is most everything else that affects the DOM directly.


#9

@mkay581 I think, ECMAScript guarantees order now.

@jhpratt It adds a bunch of properties to foo.style in sequence. I don’t see how that’s different from setting inline styles in any other way.


#10

I think what @MT is saying is that normally when you set an object on a property, like this:

element.styles = {
  border: '1px solid blue';
};

You are essentially wiping out every property on the styles object font-size, color, or anything other styles property that may have a value at the time.

So to have your styles setter just add properties, instead of replacing the element.styles object entirely, is not clear to the implementor. But adding it as a method name could help with eliminating any possible confusion with the behavior.


#11

You are essentially wiping out every property on the styles object font-size, color, or anything other styles property that may have a value at the time. So to have your styles setter just add properties, instead of replacing the element.styles object entirely, is not clear to the implementor.

Gotcha. Now I understand the issue. Yes, I agree a method would be the way to go in this circumstance.

Given that a method is appropriate and JSON objects are in order (ES6 spec here), are there any other issues anyone can think of?


#12

I, too, would like to define styles with an object, especially after experiencing the joys of doing so with .animate. @simevidas’ method is how I currently handle these things, and I would be in favor of the setStyles method. It avoids the confusion of styles = {} wiping out previous styles, and the plurality of setStyles prevents confusion with the already familiar setAttribute.


#13

Bikeshedding here, but setStyles would mean (to me) that it also replaces existing inline styles. updateStyles might have made it clear to me that it only adds or updates but does not remove, though I am not even sure about that. Think about that - how would you remove multiple properties using a single property or method? Perhaps a delta-type object, like {update: {'background-image': '...', 'cursor': 'default'}, remove: ['background', 'float']} (just thinking out loud, not suggesting as I can see some unnecessary overhead here).

And on the other hand, perhaps inline styles are discouraged in general, which is why there is no real need to add multiple properties at once?

Also, consider element.style.cssText += 'background-image: ...; cursor: default;' which should kind of already do exactly what you want, albeit using actual strings instead of an object (though template strings can take care of the beauty of it). (And the memory implications of having a large cssText, despite the fact that it simply includes many duplicate properties)


#14

By the way, the category should be “APIs” (or maybe “HTML”), not “JS” as “JS” is for JavaScript only things, like language, syntax, built ins (that are relevant to any JavaScript environment, like Node).