MozillaZine


Javascript Question: how do you update css on change?

Discuss how to use and promote Web standards with the Mozilla Gecko engine.
Bethrezen
 
Posts: 436
Joined: September 13th, 2003, 11:56 am

Post Posted November 4th, 2022, 6:27 pm

Hi all

Ok so this one has me stumped on page load I append some CSS to the style area in the head of the page however I’m running in to difficulty trying to figure out how to update this css when the on change event is triggered.

Here is a striped down version of the code I have at the moment

Code: Select all
<script>

var icons =
{
  icon1: {x: -1322400, y: -1456200, z:   493200},
  icon2: {x:  3468000, y: -1933200, z:   430800},
  icon3: {x: -2322600, y: -1958400, z: -1088400},
};

function checked(event)
{
  for (var key in icons)
  {
    var x = (((icons[key]['x'] / 222) / 36300) + 1) / 2 * 512 - 7;
    var y = (((-icons[key]['y'] / 222) / 36400) + 1) / 2 * 513 + 15;
    var z = (((-icons[key]['z'] / 222) / 36300) + 1) / 2 * 514 + 15;

    if(document.querySelector('#top').checked)
    {
      var top_position = Math.round(y) + 'px';
    }

    if(document.querySelector('#side').checked)
    {
      var top_position = Math.round(z) + 'px';
    }

    var left_position = Math.round(x) + 'px';

    var css = `
    img#${key}
    {
    top:${top_position};
    left:${left_position};
    }
    `;

    document.querySelector('style').innerHTML += css;
  }
}

var inputs = document.querySelectorAll("input");

for (i = 0; i < inputs.length; i++)
{
  window.addEventListener("load", checked);
  inputs[i].addEventListener("change", checked);
}

</script>


Now for the most part this works and when I trigger the change event the display changes accordingly but when I looked at the source code I discovered the css was being appended multiple times clearly this is not desirable and shouldn’t be happening

At first I though it would be a simple case of detecting the type of event and then just altering the line of code that is responsible for appending the css to make it overwrite instead of append so I tried doing

Code: Select all
if(event.type == "load")
{
  document.querySelector('style').innerHTML += css;
}

else
{
  document.querySelector('style').innerHTML = css;
}


However this doesn’t seem to work and it only adds the css for the last item, so my next though was to clear the appended css and then append the updated css however doing

Code: Select all
if(event.type == "load")
{
  document.querySelector('style').innerHTML += css;
}

else
{
  document.querySelector('style').innerHTML = css;
  document.querySelector('style').innerHTML += css;
}


doesn’t seem to work either, so how do I get it to update the appended css on change rather than appending the css multiple times ?

I'm sure there is probably a stupidly simple way to do this but if there is I have thus far been unable to find it.

Bethrezen
 
Posts: 436
Joined: September 13th, 2003, 11:56 am

Post Posted November 11th, 2022, 8:13 am

ok so after some more searching I came across .setAttribute now while the example was for modifying in-line styles pretty much like everything else I've come across I'm wondering if this could work for modifying internal styles as well ?

I tried doing

Code: Select all
document.querySelector("style").setAttribute(key, "top:" + top_position);


but this just placed the css on the style tag in the head of the document figuring I was maybe just targeting this incorrectly I tried doing

Code: Select all
document.querySelector("style").querySelector("img#icon1")


however this just returns null, I also tried doing

Code: Select all
document.querySelector("style > img#icon1")
document.querySelector("style ~ img#icon1")


but again this just returned null, the question is why ? because to the best of my knowledge all 3 of those should of worked.

Certainly the second and third selectors are valid and should select a style rule who's ID is equal to img#icon1 at which time I should then be able to modify the rules in img#icon1 via .setAttribute which would then presumable overwrite the excising values with the new ones.

So my next idea was to try doing

Code: Select all
document.querySelector("style").getElementById(key)


but this just gives me the error

document.querySelector(...).getElementById is not a function


So once again I’m not able to select style rules in the style area in the head of the document appended or otherwise which means I can’t modify them.

So now I’m really stuck I can’t clear the appended css without wiping out everything in the style area in the head of the document, even if I do clear everything if I then try to re-append it doesn’t work and only inserts the css for the last item.

I can’t update the appended css either because no matter what I try I just can’t seem to figure out how to select style rules in the in the style area in the head of the document.

I also tried doing

Code: Select all
document.querySelector("style").innerHTML = css.replace("top:" + top_position, "top:" + top_position);
document.querySelector("style").innerHTML += css.replace("top:" + top_position, "top:" + top_position);


Unfortunately this just resulted in the same problem that I ran in to earlier in that it overwrites everything in the style area in the head of the document and only inserts the css for the last item or it just appends the css multiple times.

Upon looking at the source I also noticed that when I did

Code: Select all
document.querySelector("style").setAttribute(key, "top:" + top_position);


the selector was incomplete and when I tried fixing it by doing

Code: Select all
document.querySelector("style").setAttribute("img#" + key, "top:" + top_position);


I just got an illegal character error and it wouldn't work, evidently it didn't like the hash symbol so I changed it to

Code: Select all
document.querySelector("style").innerHTML = "img#" + key + "{top:" + top_position + "}"


which fixed the incomplete selector problem but unfortunately this just overwrites everything in the style area in the head of the document leaving only the css for the last item again

GRRRR !!!! talk about frustrating ](*,)

Surly there has to be a simple way to get styles in the style area in the head of the document appended or otherwise so that I can then modify there values.

Honestly I would of though that this would be a simple and common task which would take me about 10 minutes to figure out what am I missing here ? because I can not find an explanation of how to do this anywhere.

Bethrezen
 
Posts: 436
Joined: September 13th, 2003, 11:56 am

Post Posted November 16th, 2022, 8:32 am

Hi all

Ok so I think I’ve finally figured out to access an internal style sheet find a rule or group of rules within that style sheet and then modify said rule or rules when the change event is triggered.

Note: This should work the same for accessing an external style sheet and then modifying a rule or group of rules when the change event is triggered.

So for the benefit if anyone who might want to do the same here is how.

First get the style sheet you want to access like so

Code: Select all
document.styleSheets[1]


The number in the square bracket represents the index value of the style sheet you want to access the index starts at 0 so index [0] represents the first style sheet index [1] represents the second style sheet and so on

Next you need to get a list of all the rules contained in the style sheet and store that list in a variable so that you can search through it to find the rule or rules that you want to modify so change the above to

Code: Select all
var rules = document.styleSheets[1].cssRules


Next construct a for loop so that you can iterate through the list of rules to find the rule or rules you want to modify you can probably use other types of loops for this but for the sake of simplicity I’m just going to use a for loop

Code: Select all
for (i=0; i < rules.length; i++)
{
  // do stuff...
}


Inside the loop construct an if statement to search for the rule or rules you want to modify, if you had a rule like this in your style sheet

Code: Select all
p.warning
{
font-family: Arial;
font-size:1.2em;
color:red;
}


Then the if statement would look like this

Code: Select all
if(rules[i].selectorText == "p.warning")
{
  //do stuff...
}


if you want to search for a group of rules then the if statement might look something like this

Code: Select all
if(rules[i].selectorText == "img#" + key)
{
  //do stuff...
}


In this case key is a variable that contains the selector text which I am extracting from my icons object but there are other ways you could do this for instance construct an array variable that contains a list of all the style rules you want to target for example

Code: Select all
var selector_text = ["Saab", "Volvo", "BMW"];


in which case the if statement might look like this

Code: Select all
var selector_text = ["Saab", "Volvo", "BMW"];

if(rules[i].selectorText == "#" + selector_text)
{
  //do stuff...
}


and what this would be looking for in the style sheet is

Code: Select all
#Saab {property name: value;}
#Volvo {property name: value;}
#BMW {property name: value;}


Finally inside the if statement you need to define what happens when the rule or rules that you are looking for are found, so for example if you had a rule like this in your style sheet

Code: Select all
p.warning
{
font-family: Arial;
font-size:1.2em;
color:red;
}


and you wanted to change the colour of the text you would do

Code: Select all
rules[i].style.color = "green";


but what about if you want to modify the same property for multiple rules ? well again this straightforward enough you would simple replace the bit at the end "green" with a variable like so

Code: Select all
rules[i].style.top = top_position;


in this case top_position contains a list of values for the top property and as it loops through my list of rules it updates the value of the top property for each one

so putting it all together the final code should look something like this

Code: Select all
if(event.type == "change")
{
  var rules = document.styleSheets[1].cssRules

  for (i=0; i < rules.length; i++)
  {
    if(rules[i].selectorText == "img#" + key)
    {
      rules[i].style.top = top_position;
    }
  }
}


What this says is: When the change event is triggered get the second style sheet and get a list of all the rules contained in that style sheet then loop through that list of rules to look for all the rules that match the specified selector text and then for each one update the value of the top property.

It should be noted here that if you know the index value of a given rule that you want to change and you are only looking to change a single rule then this can be done more simple for example

Code: Select all
if(event.type == "change")
{
  document.styleSheets[0].cssRules[0].style.color = "orange";
}


What this says is when the change event is triggered get the first style sheet, get the first rule in that style sheet and then change the value of the color property to orange

the problem with doing things this way though is that it's targeting the rule you want to change by its position within the style sheet which is less then ideal because if you change the style sheet then the index value of the rule you are targeting changes.

so for example if I was to add a rule before the one above then the rule that I’m targeting would no longer be at index[0] it would at index[1] so the above would stop working until I updated the index value so in this instance the above would need to be changed to

Code: Select all
if(event.type == "change")
{
  document.styleSheets[0].cssRules[1].style.color = "orange";
}


because a rule was added before it and therefore changed its position within the style sheet, unfortunately it seems that that the api for interactive with style sheets is incredibly primitive and trying to target style rules by there selector text which is a much easier way to go about things isn’t really possible and therefore you need to employ the sort of round about method described above.

now admittedly my knowledge of JavaScript is limited and I'm still learning but after countless hours of searching trying to find a simple and easy to understand explanation of how to access internal/external style sheets and then get and modify rules within them this is the best that I could come up with perhaps someone more knowledgeable could improve this, or provide a better method.

Bethrezen
 
Posts: 436
Joined: September 13th, 2003, 11:56 am

Post Posted November 16th, 2022, 9:08 am

ok so now I have this working, I'm curious why is it that when I try going into inspector the values of the appended css haven't changed even though the positions of the icons and therefore the values of the top property are definitely being updated when the change event is triggered ?

is there some way to see the updated values because I'm not seeing anything in the source or in the inspector is that supposed to happen ??

Anyway here is a striped down version of the code I have at the moment

Code: Select all
<script>

var icons =
{
  icon1: {x: -1322400, y: -1456200, z:   493200},
  icon2: {x:  3468000, y: -1933200, z:   430800},
  icon3: {x: -2322600, y: -1958400, z: -1088400},
};

function checked(event)
{
  for (var key in icons)
  {
    var x = (((icons[key]['x'] / 222) / 36300) + 1) / 2 * 512 - 7;
    var y = (((-icons[key]['y'] / 222) / 36400) + 1) / 2 * 513 + 15;
    var z = (((-icons[key]['z'] / 222) / 36300) + 1) / 2 * 514 + 15;

    if(document.querySelector('#top').checked)
    {
      var top_position = Math.round(y) + 'px';
    }

    if(document.querySelector('#side').checked)
    {
      var top_position = Math.round(z) + 'px';
    }

    var left_position = Math.round(x) + 'px';

    var css = `
    img#${key}
    {
    top:${top_position};
    left:${left_position};
    }
    `;

    if(event.type == "load")
    {
      document.querySelector("style").innerHTML += css;
    }

    if(event.type == "change")
    {
      var rules = document.styleSheets[1].cssRules

      for (i=0; i < rules.length; i++)
      {
        if(rules[i].selectorText == "img#" + key)
        {
          rules[i].style.top = top_position;
        }
      }
    }
  }
}

var inputs = document.querySelectorAll("input");

for (i = 0; i < inputs.length; i++)
{
  window.addEventListener("load", checked);
  inputs[i].addEventListener("change", checked);
}

</script>

Return to Web Development / Standards Evangelism


Who is online

Users browsing this forum: No registered users and 1 guest