hp2345
hp2345

Reputation: 149

How to save dynamically created elements in local storage

I have this program that can make collapsibles with content dynamically. I'm trying to save the collapsibles created in local storage. I was able to successfully save a users input in local storage, but ran into a road block when I tried to save elements with the same classname in local storage. I tried something like this:

   var collapsibles = document.getElementsByClassName('collapsible');

    function populateStorage() {
      localStorage.setItem('collapsibles', document.getElementsByClassName('collapsible'));
      setStyles();
    }

    function setStyles() {
      var collapsibles = localStorage.getItem('collapsibles');
      document.getElementById("myCollapsibles").innerHTML = collapsibles;
    }

    collapsibles.onchange = populateStorage;

But it didn't work, Is it possible to save dynamically created elements in local storage?

Code Snippet of Program (excluding local storage snippet):

var currentClosable;
var currentContent;
function selectedColl() {
    document.getElementById("inputTaskDiv").style.display = "block";
    currentClosable = event.target;
    currentContent = currentClosable.nextElementSibling;
    var inputTaskDiv = document.getElementById("inputTaskDiv");
    $(currentContent).append(inputTaskDiv);
}

var taskCounter = 0;
function addTask() {
    var text = document.getElementById("taskInput").value;
    // create a new div element and give it a unique id
    var newTask = $("<div class='currentTask'><input class='checkbox' type='checkbox'><label>" + text + "</label></div>");
    newTask.id = 'temp' + taskCounter;
    taskCounter++
    // and give it some content
    var newContent = document.createTextNode(text);

    $(currentContent).append(newTask); 
    console.log("appended");


  $(".currentTask").hover(
    function() {
      var taskCur = event.target;
      $( this ).find( "a" ).last().remove();
    $(taskCur).append( $( "<a class='taskX'> x</a>" ) );
    
    function dump() {
      $(taskCur).remove();
      
    }
    $( "a" ).on( "click", dump );

    }, function() {
    $( this ).find( "a" ).last().remove();
   });
  document.getElementById("taskInput").value = " ";
}

var elementCounter = 0;
var elementCounterContent = 0;
var text;
function addElement() {
    text = document.getElementById("input").value;
    // create a new div element and give it a unique id

    var newDiv = $("<button class='collapsible' onclick='selectedColl()'></button>").text(text);
    $(newDiv).append("<button class='btnDelete'>Delete</button>");
    var newContentOfDiv = $("<div class='content'></div>");

    newDiv.id = 'temp' + elementCounter;
    newContentOfDiv.id = 'content' + elementCounterContent;

    newDiv.classList = "div";
    elementCounter++
    elementCounterContent++
    // and give it some content
    var newContent = document.createTextNode(text);

    // add the newly created element and its content into the DOM

    document.getElementById("input").value = " ";
    $("#divColl").append(newDiv, newContentOfDiv);

    newDiv.click(function () {
        this.classList.toggle("active");
        content = this.nextElementSibling;
        if (content.style.display === 'block') {
            content.style.display = 'none';
        } else {
            content.style.display = 'block';
        }
    });
}


$("#divColl").on('click', '.btnDelete', function () {
      $(this).closest('.collapsible').remove();
      content.style.display = 'none';
    });
.collapsible {
    background-color: #777;
    color: white;
    cursor: pointer;
    padding: 18px;
    width: 100%;
    border: none;
    text-align: left;
    outline: none;
    font-size: 15px;
}

.active,
.collapsible:hover {
    background-color: #555;
}

.collapsible:after {
    content: '\002B';
    color: white;
    font-weight: bold;
    float: right;
    margin-left: 5px;
}

.active:after {
    content: "\2212";
}

.content {
    padding: 0 18px;
    transition: max-height 0.2s ease-out;
    background-color: #f1f1f1;
}

.taskX{
  color:red;
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  </head>
  <body>
    

    <!---Add Step --->
    <div id="addSteps">
     <p>Make a collapsible:</p>
     <input id="input" type="text" placeholder="title for collapsible"><button onclick="addElement()">Make</button>
    </div>

    <!-- Add tasks to steps --->
    <div id="addTasksToSteps" style="display:none">
    <div id="inputTaskDiv" style="display:none">
     <input id="taskInput" type="text"><button onclick="addTask()">Add Task</button>
    </div>
    </div>

    <!-- Final --->
    <div id="scheduleDiv" style="display:none">
    </div> 

       <div id="divColl"></div>   

       <header></header>

      </body>
    </html>

Upvotes: 1

Views: 2230

Answers (2)

HSLM
HSLM

Reputation: 2022

Just for a better end result, try to separate presentation logic from the data logic. So instead of storing the whole HTML into the local storage, why don't you create a data structure that holds the tasks and you make changes to this data structure and store it into the local storage? And when you load the page you get the data from the storage and just render it to your page. That separation will make your application easy to maintain and change.

I can suggest this data structure that I would use if I were you:

[
    {
        title: "Task group title",
        tasks: [{
           status: "done",
           title: "Task title"
        }]
    }
]

This will give you full control over your items and it will be easier to extend later. Because storing the presentation layer will cost you a lot later when you change something in your design. of course in this case it would be much easier for you to use some framework like Vuejs or React...


But for your current problem:

document.getElementsByClassName will return an array of HTML objects, and it cannot be stored inside the local storage as a string directly you have to store the HTML string instead. local storage can store strings only

For more information about localStorage please visit: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage

So you need to iterate over them and get their HTML. Or you can use their container and just get its content by ID and store its innerHTML and retrieve it when needed

if you use the following code in a loop with i it would be:

localStorage.setItem('collapsibles', document.getElementsByClassName('collapsible')[i].outerHTML);

so basically you can use:

localStorage.setItem('collapsibles', document.getElementById('divColl').innerHTML);

Then you set the innerHTML of the divColl when needed. like

document.getElementById('divColl').innerHTML = localStorage.getItem('collapsibles')

In this way you are storing the whole HTML into your local storage. not just an object which will be stored like: "[object HTMLDivElement]"

Upvotes: 1

mplungjan
mplungjan

Reputation: 178422

localStorage expects strings

You can add a span

var newDiv = $("<button class='collapsible'><span class='text'>"+text+"</span></button>");

using delegation too

$("#divColl").on("click","collapsible", selectedColl)

and

const arr = $("#divColl").find("button .text")
  .map(function() { return this.textContent })
  .get();
localStorage.setItem("arr",JSON.stringify(arr)); // stringify is mandatory for non-strings

Why mix DOM access into this when you have jQuery? Or why use jQuery if you want to use DOM access

var currentClosable;
var currentContent;

function selectedColl() {
  document.getElementById("inputTaskDiv").style.display = "block";
  currentClosable = event.target;
  currentContent = currentClosable.nextElementSibling;
  var inputTaskDiv = document.getElementById("inputTaskDiv");
  $(currentContent).append(inputTaskDiv);
}

var taskCounter = 0;

function addTask() {
  var text = document.getElementById("taskInput").value;
  // create a new div element and give it a unique id
  var newTask = $("<div class='currentTask'><input class='checkbox' type='checkbox'><label>" + text + "</label></div>");
  newTask.id = 'temp' + taskCounter;
  taskCounter++
  // and give it some content
  var newContent = document.createTextNode(text);

  $(currentContent).append(newTask);
  console.log("appended");


  $(".currentTask").hover(
    function() {
      var taskCur = event.target;
      $(this).find("a").last().remove();
      $(taskCur).append($("<a class='taskX'> x</a>"));

      function dump() {
        $(taskCur).remove();

      }
      $("a").on("click", dump);

    },
    function() {
      $(this).find("a").last().remove();
    });
  document.getElementById("taskInput").value = " ";
}

var elementCounter = 0;
var elementCounterContent = 0;
var text;

function addElement() {
  text = document.getElementById("input").value;
  // create a new div element and give it a unique id

  var newDiv = $("<button class='collapsible'><span class='text'>"+text+"</span></button>");
  $(newDiv).append("<button class='btnDelete'>Delete</button>");
  var newContentOfDiv = $("<div class='content'></div>");

  newDiv.id = 'temp' + elementCounter;
  newContentOfDiv.id = 'content' + elementCounterContent;

  newDiv.classList = "div";
  elementCounter++
  elementCounterContent++
  // and give it some content
  var newContent = document.createTextNode(text);

  // add the newly created element and its content into the DOM

  document.getElementById("input").value = " ";
  $("#divColl").append(newDiv, newContentOfDiv);
  // here store the content in localStorage
  console.log($("#divColl").find("button .text").map(function() { return this.textContent }).get())
  newDiv.click(function() {
    this.classList.toggle("active");
    content = this.nextElementSibling;
    if (content.style.display === 'block') {
      content.style.display = 'none';
    } else {
      content.style.display = 'block';
    }
  });
}


$("#divColl").on('click', '.btnDelete', function() {
  $(this).closest('.collapsible').remove();
  content.style.display = 'none';
});

$("#divColl").on("click","collapsible", selectedColl)
.collapsible {
  background-color: #777;
  color: white;
  cursor: pointer;
  padding: 18px;
  width: 100%;
  border: none;
  text-align: left;
  outline: none;
  font-size: 15px;
}

.active,
.collapsible:hover {
  background-color: #555;
}

.collapsible:after {
  content: '\002B';
  color: white;
  font-weight: bold;
  float: right;
  margin-left: 5px;
}

.active:after {
  content: "\2212";
}

.content {
  padding: 0 18px;
  transition: max-height 0.2s ease-out;
  background-color: #f1f1f1;
}

.taskX {
  color: red;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>Project HOB</title>
  <link href="style.css" rel="stylesheet" type="text/css" />
  <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
  <link rel="stylesheet" href="/resources/demos/style.css">
  <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>


  <!---Add Step --->
  <div id="addSteps">
    <p>Make a collapsible:</p>
    <input id="input" type="text" placeholder="title for collapsible"><button onclick="addElement()">Make</button>
  </div>

  <!-- Add tasks to steps --->
  <div id="addTasksToSteps" style="display:none">
    <div id="inputTaskDiv" style="display:none">
      <input id="taskInput" type="text"><button onclick="addTask()">Add Task</button>
    </div>
  </div>

  <!-- Final Schedule --->
  <div id="scheduleDiv" style="display:none">
  </div>

  <div id="divColl">
    <h1 id="assignmentTitle"></h1>
  </div>

  <div class="container"></div>

  <header></header>

  <script src="script.js"></script>


</body>

</html>

Upvotes: 1

Related Questions