Why doesn't DOM style object set CSS variables directly? - javascript

My understanding is there are two equivalent ways to set CSS rules via JavaScript:
#1: element.style.setProperty(propertyName, value)
#2: element.style.propertyName = value
I have always favored the second, shorter method.
When it comes to CSS variables, I find I have to use an explicit setProperty call:
element.style.setProperty('--varName', value)
This approach has no effect on variables:
element.style['--varName'] = value
Why is this?

It's because DOM's Style function only understands HTML properties and not CSS properties. Defining CSS properties are listed in style's setProperty Function.

The .style[propertyName] expects a property name inside but does not support a custom property like .setProperty() does. If you pass --varName, you are passing the value assigned to the brackets.
For example, if --varName: 'blue', by saying .style['--varName'] = value, you are saying change the blue property to value. Since blue is not a property, it will not work.

You need to retrieve it from getComputedStyle
see : https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties#Values_in_JavaScript
To use the values of custom properties in JavaScript, it is just like standard properties.
// get variable from inline style
element.style.getPropertyValue("--my-var");
// get variable from wherever
getComputedStyle(element).getPropertyValue("--my-var");
// set variable on inline style
element.style.setProperty("--my-var", jsVar + 4);
document.body.style.setProperty('--varName', 'see i got stored even i`m a useless value');
let root = window.getComputedStyle(document.body );
let varNameValue= root.getPropertyValue('--varName');
console.log(varNameValue);

Related

Defining a setter for individual style property

According to the current CSS specification CSSStyleDeclaration.setProperty() has a shorthand of writing directly to the style property. Like in the code below, both lines have the same functionality:
element.style.color = "#fff";
element.style.setProperty("color", "#fff");
Though there is an interesting and unclear situation to me here:
I couldn't find the way to hook a custom setter for individual style properties, since these don't have an explicit setter. Changing the setProperty method only works for the direct call of the method element.style.setProperty() and not the shorthand (i.e. element.style.color).
Both seem to refer to CSSStyleDeclaration interface, but I can't find a way to define a setter for a specific style property (i.e. element.style.color).
MDN says the following:
While this property is considered read-only, it is possible to set an inline style by assigning a string directly to the style property. In this case the string is forwarded to CSSStyleDeclaration.cssText. Using style in this manner will completely overwrite all inline styles on the element.
According to this, changing a setter for cssText could possibly intercept the setter for specific style properties, but it only does for inline style definition itself (i.e. element.style = 'color:#fff;')
Is there a way to define a custom setter for individual style properties, such as element.style.color, or at least have a generic setter for any of them?
As you have explained, there are two ways to set a style property:
e.style.property=
e.style.setProperty(property,..)
Depending on implementation, these two ways can be handled either independently, or using each other. It is very unprobably, that setProperty calls the way 1. The way 1. can have a setter, which may or may not call the setProperty.
The property may exist (have a property descriptor), or the way 1. may be handled another way (e.g. by catch the exception non-existent property.)
This does mean, that you can define/redefine the property as an accessor, which's getter calls getPropertyValue, and which's setter calls setProperty. Then, you can inject any call of (even instance-defined) function.
Object.defineProperty(CSSStyleDeclaration.prototype, "color", {configurable:true, enumerable:true, get:function() {return this.getPropertyValue("color");}, set:function(c) {let tweakedC=tweakColor(c); this.setProperty("color", tweakedC); handleNewPropertyValue(c, tweakedC);}});
function handleNewPropertyValue(c, tweakedC) {...};
I have used this way in some version of Chrome, but I believe, that it will do in most implementations, except of those, where setProperty depends on existing the property per se (unprobably).
Tweaking the prototype to create a new instance property on first access is very easy, but it is another question.

Javascript getComputedStyle - copy object without reference

I want to change the color of an input field border. But yet before changing, I need to save the initial CSS via getComputedStyle so that I can set all the initial CSS of the input field back.
The problem is that the object of getComputedStyle is not fixed and changes dynamically.
I tried to copy the object into a new one without reference, but then I cannot use the property getPropertyValue because it is a different type of object.
Is there any way how I could retrieve back the initial CSS of the input field?
My code is the following:
const input_element = document.getElementById('text-id');
let computedStyle_fixed;
function change_color() {
// get computed style
const computedStyle = window.getComputedStyle(input_element);
// copy computed style into new object without reference
computedStyle_fixed = JSON.parse(JSON.stringify(computedStyle));
// change the border color of the input field
input_element.style.borderColor = 'red';
}
function retrieve_color() {
Array.from(computedStyle_fixed).forEach(
key => element.style.setProperty(key, computedStyle_fixed.getPropertyValue(key), 'important')
)
}
<input type='text' id='text-id'>
<button onclick='change_color()'>Change color</button>
<button onclick='retrieve_color()'>Retrieve color</button>
from what I understood is that you need all the css properties of a element and store in an object.
you can use spread operator to do so.
const computedStyle = {...window.getComputedStyle(input_element)};
you can now use this like a normal object.
ex : console.log(computedStyle.backgroundColor);
There are a number of issues in your code. Firstly in this line:
const computedStyle = window.getComputedStyle(input_element);
window.getComputedStyle returns a CSSStyleDeclaration object. When copied using:
computedStyle_fixed = JSON.parse(JSON.stringify(computedStyle));
the object assigned to computedStyle_fixed is a plain object, not a CSSStyleDeclaration object. This has consequences in the retrieve_color function explained further down.
Then in:
Array.from(computedStyle_fixed)
Array.from expects the argument to be an array–like object with a length property or an iterable object. computedStyle_fixed is neither so the result is an empty array and the following forEach does nothing (because length is 0).
Within the forEach callback, there is:
computedStyle_fixed.getPropertyValue(key)
the computedStyle_fixed object doesn't have a getPropertyValue method (see above), so if that line is executed the result will be a type error.
To iterate the properties of the computedStyle_fixed object, use Object.keys and access properties using square bracket notation, not the (missing) getPropertyValue method, e.g.
Object.keys(computedStyle_fixed).forEach(key =>
element.style.setProperty(key, computedStyle_fixed[key], 'important')
);
Other notes
Creating implicit globals like computedStyle_fixed is not recommended, declare them or set them as object properties.
Using JSON.stringify to create a shallow copy of an object is also not a good idea, it's much better to use Object.assign:
let computedStyle_fixed = Object.assign({}, computedStyle);
This also creates a plain object, not a CSSStyleDeclaration object.
I have solved it, I have just change a little the retrieve_color() function.
But I thought that it would set the style of the input border exactly as it used to be initialy.
If you look at the code at jsfiddle and click on change_color and then click on retrieve_color, you will see that the input does not look as it was (the border are different).
Do you have any idea how I could get it back exactly as it was?
// the code
https://jsfiddle.net/gfxjn4se/

How to pass javascript property (name) without using hard coded String?

Example use case:
I have an object with an attribute "myProperty", having getter and setter ("Property Getters and Setters" are supported since EcmaScript 5: https://www.w3schools.com/js/js_es5.asp):
var obj = {
myProperty:'myPropertyValue',
get myProperty(){
return this.property;
},
set myProperty(value){
this.property=value;
}
};
I would like to bind that attribute to a view, which is the task of a custom function that is called bindProperty.
In order to pass the property myProperty to the function, I could do something like
bindProperty(obj, 'myProperty');
However, I would like to avoid to pass the property name as a hard coded String. Strings have the disadvantage, that they are not updated when the attribute name changes during refactoring.
If I would use
bindProperty(obj, obj.myProperty);
the function would only know the property value 'myPropertyValue' and not where the value comes from.
=>How can I pass/identify the property itself, without using a String?
A. Using reflection?
I could imagine something like
bindProperty(obj, ()=>obj.myProperty);
The function bindProperty would then have to do some reflection magic to find out the name of the attribute in the body of the lambda expression (pseudo code):
let attributeName = getNameofArgumentFromBodyOfLambdaExpression(lambda);
obj[attributeName] = 'newValue';
=>Is it possible in JavaScript to evaluate the body of the lambda expression using reflection to get the name of the property?
(I know that this can be done in .NET languages, e.g.
Private Sub BindProperty(Of T)(propertyExpression As Expression(Of Func(Of T)))
Dim propertyName As String = GetPropertyName(propertyExpression)
'...
)
B. Using complex attributes
An alternative whould be that I use wrapping property objects, having their own getters and setters. Howerver, then I would have to use the property like
obj.myProperty.set('newValue')
or
obj.myProperty('newValue') //similar to knockout observables
I still want to be able to use the great Getter/Setter feature. With other words: I want to use my properties like plain attributes:
obj.myProperty = 'newValue'
Therefore, this is not an option for me and I would prefer to use Strings instead of B.
C. Any other alternatives?
An object in javascript is more or less just a mapping of strings or symbols to values. There is no real reflection that you can call upon in the runtime environment that would enable you to move backward from the value to the property name.
If all you need is refactoring, the one way to do this would be to just configure your IDE to recognize string accessors by providing some sort of type information either via Flow or Typescript or something of that sort (the type information is likely what allows reflection to work in languages like .NET). Or you could just settle for a unique prefix like "viewable_propName" and just do simple find and replace if you need to rename.
If you are really focused on getting this to work without type information and in current ES6 syntax, you could do the following:
function getNameofPropFromVal(obj, val){
for(let prop in obj){
if(obj[prop] === val) return prop;
}
}
obj[getNameofPropFromVal(obj, obj.myProp)] = 'newVal';
Though this has shortcomings:
(1) There is no guarantee that two properties won't share the same value.
(2) It introduces unnecessary runtime overhead.
Finally, if you're willing to be cutting edge and use a transformer like babel you could use decorators for your bindProperty method. That way you can just do the binding in the object definition itself. Here is an article explaining the gist and here is the more formal ECMAScript proposal.
I just found following simple work around that might fullfill my needs:
function bindProperty(obj, lambdaExpression){
let expression = lambdaExpression.toString(); // "()=> obj.myProperty"
let subStrings = expression.split(".");
let propertyName = subStrings[1];
console.info(propertyName );
//...
}

Can't seem to understand this javascript code! if(!this.class)

New to javascript here.
So I've been trying to learn to use Raphael.js and came across this http://jsfiddle.net/terryyounghk/AeU2r/ snippet of code.
Now if you look at line 167, there is this "if" statement I just don't understand.
Raphael.el.style = function (state, style, aniOptions)
{
if (!this.class)
{
this.class = style ? style : 'default';
this.aniOptions = aniOptions ? aniOptions : null;
// start assigning some basic behaviors
this.mouseover(function () { this.style('hover'); });
....
What class? What is it returning? Who is returning it?
What is it even checking? That it's a class?
From the Raphael documentation, Raphael.el is a way of adding one's own methods to the Raphael.element class. In general, the purpose of this class is to make it easier to manipulate SVG elements.
this.class in the code example has nothing to do with the the word class in the programming sense, as used in the preceding sentences. Nor (as far as I can see) is it part of the standard Raphael framework. Nor does it refer to the class attribute that HTML and SVG elements can have, and which is usually accessed in javascript using element.className or element.setAttribute('class', '...').
this refers to the element wrapper object (an instance of Raphael.element), and it seems that the person who wrote this method has simply used the name class to store some additional information in the element wrapper. (As pointed out in comments, this might be a bad idea because class is a reserved keyword in javascript; but anyway, it works.)
Specifically, in the example, this.class is initially undefined because it has not been assigned a value anywhere else in Raphael or in the code. In the if clause, !undefined evaluates to true, and in the following line, no value has been passed to the function for style, so that style ? style : 'default' evaluates to 'default'. So this.class is assigned the value 'default'. Afterwards, if you right-click on a shape and choose Use custom style, the class for that shape becomes 'custom'.
Note that javascript very easily lets you refer to, and assign values to, properties of an object that have not been initialised anywhere. It does not throw an error but simply returns undefined if no value has been assigned.
You can see all this by inserting a line that logs what's going on to the browser console:
console.log('style', state, style, aniOptions, this, 'class:', this.class);
and then using your browser's developer tools to see the output (JSFiddle).
It checks if a property class is defined and if not, it will assign it to style ? style : 'default'.
What you're seeing is simply a conditional statement, look at it as an abbreviated if-else-clause which checks if style evaluates to true or false, if it's true this.Class will have the value of style, if it's not it will get the value of 'default'.
I don't know how raphael.js works, but it looks to me like it is simply a html element class.

Saving a reference to .innerHTML

This works:
var button = $A("#aba_but_del")[0];
button.innerHTML = Su.Ani.flipPane.p1;
But this does not work
var button_text = $A("#aba_but_del")[0].innerHTML;
button_text = Su.Ani.flipPane.p1;
Why?
According to https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Operator_Precedence, the . has highest precedence. Does this mean that .innerHTML is being applied to [0]?
All primitives in javascript are immutable. This means that the following code just changes a different instance of a string:
button_text = Su.Ani.flipPane.p1;
In the first case button is an object referencing a DOM element. So when changing the innerHTML property value you are changing the DOM element itself.
button_text = ... replaces the variable with the value.
button.innerHTML = ... replaces the property of the object, which in turn affects the displayed HTML because of how the setter function works for that property.
You can't have a reference to a property.
When you do like this:
var button = $A("#aba_but_del")[0];
you get a reference to the element, so later on you can use that reference to access members of the element.
When you do like this:
var button = $A("#aba_but_del")[0].innerHTML;
you get the value of the property, not a reference to the property. The value is a string taht you assign to the button variable, and assigning a different string to the variable doesn't change the first string, and not the object where that string was copied from.
"The . has highest precedence. Does this mean that .innerHTML is being
applied to [0]?"
No. The [] operator has the same precedence as the . operator, as they are actually the same operator, so they are evaluated from left to right. Using obj['name'] is the same as obj.name, but the . operator can't be used with a property name that doesn't follow the rules of an identifier, like a numeric index.
1)
button_text is a string, no longer bound to the button DOM object.
So changing it will not impact the button element.
2)
Does this mean that .innerHTML is being applied to [0]? Yes. "." and "[]" have the same priority going from left to right.
JavaScript has no pointers or such stuff. If you assign something to a variable, you overwrite the previous value. You want to assign to the innerHTML property of the DOM object, changing the page rendering.

Categories

Resources