SultanaSmock
SultanaSmock

Reputation: 31

JavaScript local storage mixing up toggle keys?

I am a Javascript beginner trying to use local storage to store a set of button toggle states (on/off). My goal is to be able to toggle a set of svg fills, on and off, and have the states stored, returning to the selected state, after a browser refresh.

//LOOP 1 - GET STORED VALUES AND SET SVGs ACCORDINGLY
        function grabStoredStates(){
              for (let i=0; i < localStorage.length; i++) {
                  let key = localStorage.key(i);
                  let value = localStorage.getItem(key);
                  if (value == "on") {
                      //"cbox" below is from LOOP 2  - maybe it's causing the problem
                  cbox[i].style.fill = "coral";
              }
          }
        }

//LOOP 2 - TOGGLE AND STORE TOGGLE STATES
         let cbox = document.querySelectorAll(".toggleMe");
         for (let i = 0; i < cbox.length; i++) {
                cbox[i].addEventListener("click", function() {
                   if (cbox[i].style.fill == "coral") {
                       cbox[i].style.fill = "white";
                       localStorage.setItem(cbox[i].id, "off");
                   }else{
                      cbox[i].style.fill = "coral";
                     localStorage.setItem(cbox[i].id, "on");

                   }
                });
            }
<body onload="grabStoredStates()">
    <svg height="300" width="700>
    <g  pointer-events="all">    
      <rect class="toggleMe" cursor:pointer; id="rect1" width="100" height="100" x="50" y="80" fill="aliceblue" stroke="black" stroke-width="2" />
      <rect class="toggleMe" cursor:pointer; id="rect2" width="100" height="100" x="175" y="80" fill="aliceblue" stroke="black" stroke-width="2" />
      <rect class="toggleMe" cursor:pointer; id="rect3" width="100" height="100" x="300" y="80" fill="aliceblue" stroke="black" stroke-width="2" />
      <rect class="toggleMe" cursor:pointer; id="rect4" width="100" height="100" x="425" y="80" fill="aliceblue" stroke="black" stroke-width="2" />
      </g>
    </svg>
</body>

My problem is that if you load the page, and then open local storage in Chrome Dev Tools, (Application > Storage > Local Storage), and then click the rectangles in sequence from left to right you should see this, as they toggle correctly from light blue to orange:

Key Value

rect1 on

rect2 on

rect3 on

rect4 on

Toggle state 1

In local storage, the keys and values are, to that point, matched properly, showing all squares to be orange, as intended. However, if you then click rect3, (the third rectangle from the left), for instance, toggling it to light blue, with an expected corresponding "off" state -- on refreshing the page, the stored values appear to get jumbled, with the loop apparently renumbering the rectangles as if it was starting from [0] -- ignoring the intended key/value pairing. You then see this:

Key Value [PROBLEM]

rect1 on

rect2 on [ --> Displays as Off]

rect3 off [ --> Displays as on]

rect4 on

Jumbled state with bad annotation...

Is there any way that I can make the keys and values stay together, regardless of loop called. The "grabStoredStates()" loop appears to work, as tested by manually changing values and refreshing... I suspect, then, that it has to do with the transition between two separate for loops? Maybe the "cbox" loop should also be in a function? I looked at the prior stack overflow "can't use booleans in local storage" answer, but as I'm using "on" and "off" that doesn't seem to apply. I also saw a jQuery answer, but I'm not familiar with that library yet. (I wouldn't, however, turn down a jQuery answer if offered.)

Again, my original goal was to be able to toggle svg fills on and off and have the states stored after browser refresh. I'd love a more elegant way if there is one...

Thanks everyone, in advance, for your help.

Upvotes: 1

Views: 107

Answers (1)

chrwahl
chrwahl

Reputation: 13100

Here I tried to make a simpler version. The presentation of the <rect>s relies on an attribute called data-state. When the document is loaded the attributes will be updated (I commented out the code for localStorage and replaced it with the array testArr). An event listener on <g> will handle click events and test if a <rect> was clicked, if so, the attribute is updated/toggled. After that the values are all saved to localStorage/testArr.

var cbox;
var testArr = ['on', 'off', 'off', 'on']; // for testing

document.addEventListener('DOMContentLoaded', e => {
  cbox = document.querySelectorAll(".toggleMe");
  /* // Code for localStorage:
  Array.from(Array(localStorage.length).keys()).forEach(i => {
    let key = localStorage.key(i);
    let value = localStorage.getItem(key);
    cbox[i].attributes['data-state'] = value;
  });*/
  testArr.forEach((state, i) => cbox[i].attributes['data-state'].value = state); // for testing
  
  document.querySelector('g').addEventListener('click', e => {
    if(e.target.classList.contains('toggleMe')){
      let currentValue = e.target.attributes['data-state'].value;
      e.target.attributes['data-state'].value = (currentValue == 'on') ? 'off' : 'on';
      /* // Code for localStorage:
      [...cbox].forEach((elm, i) => {
        let value = elm.attributes['data-state'].value;
        localStorage.setItem(`Rect${i}`, value);
      });*/
      
      testArr = [...cbox].map(elm => elm.attributes['data-state'].value); // for testing
      console.log(testArr.join(',')); // for testing
    }
  });
});
rect.toggleMe {
  cursor: pointer;
  width: 50px;
  height: 50px;
  stroke: black;
  stroke-width: 2px;
}

rect[data-state="on"] {
  fill: aliceblue;
}

rect[data-state="off"] {
  fill: coral;
}
<body>
  <svg height="300" width="300">
    <g>    
      <rect class="toggleMe" data-state="off" x="50" y="80" />
      <rect class="toggleMe" data-state="off" x="110" y="80" />
      <rect class="toggleMe" data-state="off" x="170" y="80" />
      <rect class="toggleMe" data-state="off" x="230" y="80" />
    </g>
  </svg>
</body>

Upvotes: 0

Related Questions