Search code examples
javascriptobjectdomiframeassign

Assigning a subset of properties with the Object.assign() method


I am developing a chrome extension and I am creating a side panel using an iframe element. I have an object that stores styles for the corresponding iframe:

const sidePanelStyle = {
    background: 'white',
    // some other vars 
};

I create the iframe and assign my settings:

let sidePanel = document.createElement('iframe');
Object.assign(sidePanel.style, sidePanelStyle);

Everything works, but when before I did

sidePanel.style = Object.assign(sidePanel.style, sidePanelStyle);

it did not merge anything into the sidePanel.style (I expected that .assign() returns a merged object, as per MDN).

I am new to JS, so the questions are:

  1. What exactly I am missing with Object.assign()?
  2. What is the best practice to assign multiple settings properties to an object from the existing framework and what is the best practice to keep them in my source code (a separate module? one or more objects? etc.).

While returning a merged object is redundant (the .assign() method merges everything into the first argument), I am still curios why it does not work, when the object is returned.

const sidePanelStyle = {
	background: 'gray',
	height: '100%',
	padding: '20px',
	width: '400px',
	position: 'fixed',
	top: '0px',
	right: '0px',
	zIndex: '9000000000000000000',
};

let sidePanel = document.createElement('iframe');
// this works fine
// Object.assign(sidePanel.style, sidePanelStyle);

// this doesn't
sidePanel.style = Object.assign(sidePanel.style, sidePanelStyle);


document.body.appendChild(sidePanel);


Solution

  • This is a quirk of the style property on DOM elements, which is an unfortunate throwback to the early days of web browsers where things were added a bit...willy nilly with some very, very strange semantics.

    When you read the style property of an element, you get an object that has properties for the inline styles. But when you write to it, what you write is treated as though it were a string or null. (Although officially, it's supposed to be read-only. It isn't treated that way in today's browsers, though.)

    Moral of the story: Don't write to it (unless you're writing null to completely clear it).

    So when you do:

    sidePanel.style = Object.assign(sidePanel.style, sidePanelStyle);
    

    ...what happens is:

    1. The styles are successfully added to sidePanel.style, because Object.assign writes to the object given in its first argument, and then

    2. The object it returns (also sidePanel.style) is converted to string and interpreted as style properties. (Although, again, it's supposed to be read-only.)

    But when you convert it to string, the resulting string is "[object CSSStyleDeclaration]", which can't be converted to styles, so you wipe out the styles on the element.

    Here's a simpler demonstration of what's going on:

    const example = document.getElementById("example");
    example.style.color = "blue";
    setTimeout(function() {
        console.log("example.style.color before: " + example.style.color);
        // Assigning it to itself, which is effectively what
        // your code with `Object.assign` was doing
        example.style = example.style;
        console.log("example.style.color after:  " + example.style.color);
        console.log("String(example.style): " + String(example.style));
    }, 800);
    <div id="example">This is the example div</div>

    As you've seen, there's no reason to write back to it anyway, as the properties are added to it because it's the first argument to Object.assign.