hp2345
hp2345

Reputation: 149

Store user inputs in specific array in local storage

I have this program that can make collapsibles dynamically, the user can then input content into the collapsible they select (click). When the user adds content to a specific collapsible I'm trying to store the users inputs in an array in local storage. I was trying to do this like so:

When the user makes a new collapsible a new array is created with it:

function addElement() {
  
          /*add closable and it's title*/

//make new array for closables content:

    contentArray = new Array();
    contentArray.id = "Content array:" + contentArrayNum;
    contentArrayNum++ 

  localStorage.setItem("contentArray",JSON.stringify(contentArray)); 

}

The user can add to the selected collapsibles content with an input box that's appended to a closable when it is selected(clicked). When they input content to a selected collapsible, I'm trying to add the users inputs into the array that was created with the collapsible. However I'm confused how to reference the array that was created with the collapsible, I tried this (but it (Obviously) didn't work):

 function selectedColl() {
      currentClosable = event.target;
      currentContent = currentClosable.nextElementSibling;
    
      currentArray = currentClosable.array; //reference array(that was made with closable) for the selected closable
      console.log(currentArray)             
    
      document.getElementById("inputTaskDiv").style.display = "block";//shows input box for selected closabales content
      var inputTaskDiv = document.getElementById("inputTaskDiv");//appends the input box to the selcted closables
      $(currentContent).append(inputTaskDiv);
    }

Here is an image for what I'm attempting to do: enter image description here

I'm not sure if this is even possible or there might be an easier way to attempt what I'm trying to do. Simply what I want to do is: store the inputted content for a selected closable in local storage either in an array or anything else that can group the inputs for a selected collapsible in local storage. Please ask for clarification if you need it.

Here is my full code (commented local storage):

var currentClosable;
var currentContent;
var currentArray;

var contentArrayNum = 0;
var contentArray;

function selectedColl() {
  currentClosable = event.target;
  currentContent = currentClosable.nextElementSibling;

  //currentArray = currentClosable.array;
  //console.log(currentArray)

  document.getElementById("inputTaskDiv").style.display = "block";//shows input box for selected closabales content
  var inputTaskDiv = document.getElementById("inputTaskDiv");//appends the input box to the selcted closables
  $(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'><span class='text'>" + text + "</span></div>");
  newTask.id = 'temp' + taskCounter;
  taskCounter++;



  // and give it some content
  var newContent = document.createTextNode(text);

  $(currentContent).append(newTask);

  // store the content in localStorage
    ////here
 // end of local storage

  $(".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 =  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);

  // store the closable(s) in localStorage
    var arr = [];

   arr = $("#divColl").find("button .text")
  .map(function() { return this.textContent })
  .get();

//array for content of closables
    contentArray = new Array();
    contentArray.id = "Content array:" + contentArrayNum;
    contentArrayNum++ 

  //localStorage.setItem("arr",JSON.stringify(arr)); 
  //localStorage.setItem("contentArray",JSON.stringify(contentArray)); 


  //console.log($("#divColl").find("button .text").map
  //(function() { return this.textContent }).get())
 // end of local storage

  newDiv.click(function() {
    selectedColl();
    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 collapsible</button>
  </div>

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


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

</body>

</html>

Upvotes: 1

Views: 237

Answers (2)

Professor Abronsius
Professor Abronsius

Reputation: 33813

Rather than doing any Christmas shopping this afternoon I had a little play around putting together the following app. As a "non-jQuery" user it is done in vanilla js ( I got in a muddle with some of the jQuery code when I tried initially so I binned it ) ~ you ought to be able to copy the entire thing "as-is" and run it ~ I think it does what you were trying to do and there are many comments throughout. Hope it helps - I'm sure you can "cherry-pick" bits from it to tie in with your original code. Happy Xmas...may 2021 be better!

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width">
        <style>
            html *{ box-sizing:border-box; font-family:calibri,verdana,arial; }
            .active,.collapsible:hover { background-color:#555; }
            .active:after { content:"\2212"; }
            .collapsible { background-color:#777; border:none; color:white; cursor:pointer; font-size:15px; outline:none; padding:18px; text-align:left; width:100%; }
            .collapsible:after { color:white; content:'\002B'; float:right; font-weight:bold; margin-left:5px; }
            .content { background-color:#f1f1f1; border-bottom:1px solid grey; border-left:1px solid grey; border-radius:0 0 1rem 1rem; border-right:1px solid grey; margin:0 0 2rem 0; padding:0 18px; padding:1rem; transition:max-height 0.2s ease-out; }
            .currentTask a { color:red; cursor:pointer; display:inline-block; padding:0 1rem; margin:0 0 0 1rem }

            #addSteps{ margin:0 0 2rem 0; }
            #deltask{ display:none; }
            button > span + button{ margin:0 0 0 5rem; }
        </style>
        
        
        <script>
            /* Simple utility to generate semi-random string IDs */
            const uniqid=function(l=6){
                let t=[];
                for(i=0;i<l;i++)t.push(seed());
                return t.join('-');
            };
            const seed=function(){
                return Math.floor((1+Math.random())*0x10000).toString(16).substring(1);
            };
            /*
                I wrote this a couple of years ago just to simplify
                the process of working with localStorage...
            */
            const StoreFactory=function( name, type ){
                'use strict';
                const engine = !type || type.toLowerCase() === 'local' ? localStorage : sessionStorage;
                
                const set=function( data ){
                    engine.setItem( name, JSON.stringify( data ) );
                };
                const get=function(){
                    return exists( name ) ? JSON.parse( engine.getItem( name ) ) : false;
                };
                const remove=function(){
                    engine.removeItem( name );
                };
                const exists=function(){
                    return engine.getItem( name )==null ? false : true;
                };
                const create=function(){
                    if( !exists() ) set( arguments[0] || {} );
                };
                const save=function(){
                    set( get() );
                };
                return Object.freeze({
                    set,
                    get,
                    save,
                    exists,
                    create,
                    remove
                });
            };
            const create=function(t,a,p){
                try{
                    /*
                        t:type ~ the DOM Node type. null for default 'div'
                        a:attributes ~ object literal of attributes to asign to the node
                        p:parent ~ the parent to which the node will be appended. null to negate.
                    */
                    let el = ( typeof( t )=='undefined' || t==null ) ? document.createElement('div') : document.createElement(t);
                    let _arr=['innerHTML','innerText','html','text'];
                    for( let x in a ) if( a.hasOwnProperty( x ) && !~_arr.indexOf( x ) ) el.setAttribute( x, a[ x ] );
                    if( a.hasOwnProperty('innerHTML') || a.hasOwnProperty('html') ) el.innerHTML=a.innerHTML || a.html;
                    if( a.hasOwnProperty('innerText') || a.hasOwnProperty('text') ) el.innerText=a.innerText || a.text;
                    if( p!=null ) typeof( p )=='object' ? p.appendChild( el ) : document.getElementById( p ).appendChild( el );
                    return el;
                }catch( err ){
                    console.error( err.message );
                }
            };
            

            
            
            
            
            
            
            
            
            
            
            
            
            /***********************************************************
                I know there are many jQuery methods that can be used
                for to accomplish the things done here but I don't use
                it so I don't know ~ hence using vanilla javascript
            */
            document.addEventListener('DOMContentLoaded',function(){
                /*************************************************
                    Create the storage object that will be used
                    to keep track of "collapsibles" and tasks.
                    
                    The individual "collapsible" items that the
                    user creates will be stored within this store
                    using a unique key defined in the `addelement`
                    method.
                    
                    That unique ID is used as a dataset attribute
                    for certain DOM elements which allows the store
                    to be used later to reconstruct the "collapsibles"
                    when the page is reloaded.
                    
                    The structure for the generated store will be of
                    the following form:
                    
                    store={
                        key1:{title:'The name of the "collapsible',tasks:['an array','of all','the tasks']},
                        key2:{title:'Another "collapsible"',tasks:['fly','me','to','the','moon']}
                    }
                    etc
                */
                let oStore=new StoreFactory( 'collapsible' );
                    oStore.create();
                    
                let payload=oStore.get();

                
                let parent=document.getElementById('divColl');
                let oText=document.getElementById('input');
                let oTask=document.getElementById('inputTaskDiv');
                let oBttn=document.querySelector('input#input + button');

                
                


                const buildElement=function( parent, id, text ){
                    /*
                        generate the HTML structure as originally used
                        and return the "Content" node for chaining to
                        other calls later.
                    */
                    let bttn=create('button',{'class':'collapsible','data-id':id},parent);
                    create('span',{'class':'text','text':text},bttn);
                    create('button',{'class':'btnDelete','text':'Delete'},bttn);
                    return create('div',{'class':'content','data-id':id},parent);
                };
                
                const buildTask=function( parent, id, text ){
                    /*
                        Construct the HTML content for individual tasks
                        and return a refernce to this new content so that
                        it may be used in chaining if required.
                        
                    */
                    let div=create('div',{'class':'currentTask'},parent);
                    create('input',{'type':'checkbox','name':'task[]','value':text},div);
                    create('span',{'class':'text','text':text},div);
                    return div;
                };
                
                function addelement( event ) {
                    let id=uniqid( 8 );
                    let text=oText.value.trim();
                    
                    if( text!='' ){
                        /* generate new content and append a clone of the input field + button */
                        appendclone( buildElement( parent,id, text ) );
                        
                        /* prepare the data to be stored in localStorage */
                        var payload=oStore.exists() ? oStore.get() : {};
                            payload[ id ]={ 'title':text, 'tasks':[] };
                            
                        /* save the data */
                        oStore.set( payload );
                    }
                    oText.value='';
                    return false;
                };
                
                function addtask( event ) {
                    /* The text data comes from the cloned input element that was inserted */
                    let input=event.target.previousElementSibling;
                    let text =input.value.trim();
                    if( text !='' ){
                        
                        let parent = event.target.parentNode.parentNode;
                        let id = parent.dataset.id;
                        
                        let div=buildTask.call( this, parent, id, text );
                        /*************************************
                            Save the task to the appropriate
                            place within the store. We use the
                            parentNode to work up the DOM Tree to
                            find the dataset ID which forms the 
                            key in the store.
                        */
                        let data=oStore.get();
                        var payload=data.hasOwnProperty( id ) ? data[ id ] : { title:parent.previousElementSibling.querySelector('span').textContent, tasks:[] }
                            payload.tasks.push( text );

                        // rebuild data for updating store
                        data[ id ]=payload;
                        
                        // save the updated store
                        oStore.set( data );
                        /************************************/
                        
                        input.value='';
                        return div;
                    }
                    return false;
                };
                
                
                
                
                
                /*********************************************
                    rebuild the display using stored values
                    ...events handled by delegated listeners 
                */
                Object.keys( payload ).forEach( id => {
                    let text=payload[ id ].title;
                    let tasks=payload[ id ].tasks;
                    // add the "collapsible" to the DOM
                    let content=buildElement.call( this, parent, id, text );
                    
                    tasks.forEach( text => {
                        // Add the tasks to the "collapsible"
                        buildTask.call( this, content, id, text );
                    });
                });

                /*************************************************************************************
                    Newly generated content has event listeners assigned above in the original jQuery
                    code. However any content generated on PageLoad as the localStorage is processed
                    will NOT have these listeners assigned - to my mind using `delegated event handlers`
                    on a suitable parent container would be a better approach as the same listeners
                    would work equally well for new content and existing.
                */
                
                function deleteelement(e){
                    /*
                        Delete the entire "collapsible" from both
                        the DOM and from localStorage
                    */
                    let id=e.target.parentNode.dataset.id;
                    parent.querySelectorAll( '[data-id="'+id+'"]' ).forEach( n=>{
                        parent.removeChild(n)
                    });
                    
                    let data=oStore.get();
                    delete data[ id ];
                    oStore.set( data );
                };
                function deletetask(e){
                    /*
                        Delete specific "Task" from a "Collapsible"
                        - from both the DOM and from localStorage
                    */
                    let id=e.target.parentNode.parentNode.dataset.id;
                    let task=e.target.parentNode;
                    
                    // remove the DOM element
                    task.parentNode.removeChild( task );
                    
                    // remove this item from the store
                    let data=oStore.get();
                        data[ id ].tasks.splice( data[ id ].tasks.indexOf( task ),1 );
                    // save it
                    oStore.set( data );
                };
                function appendclone(n){
                    /*
                        Append a cloned version of the
                        input field and button that are
                        used to add a new task.
                    */
                    if( !n.querySelector('#newtask') ){
                        let clone=oTask.cloneNode(true);
                            clone.id='newtask';
                            clone.style.display='block';
                        n.insertBefore(clone,n.firstChild);
                    }
                    return true;
                };
                function appendclonehref(id,n){
                    /*
                        Append a cloned version of the hyperlink
                        that is used to delete and individual task.
                        The clone is placed at the respective node
                        in the DOM via the delegated "mouseover"
                        handler in conjunction with a "mouseout"
                    */
                    if( !n.querySelector( 'a#'+id ) ){
                        let a=document.getElementById('deltask');
                        let clone=a.cloneNode( true );
                            clone.id=id;
                            clone.addEventListener('click',deletetask)
                        n.appendChild( clone );
                    }
                };
                
                function displaytasks(e){
                    /*
                        Ensure that the "Content" is visible/hidden
                        dependant upon clicking the button. The cloned
                        version of the text field + insert button are
                        added in this method
                    */
                    let content=e.target.nextElementSibling
                        content.style.display=content.style.display=='block' ? 'none' : 'block';
                    return appendclone( content );
                };
                
                function mouseoverhandler(e){
                    /*
                        delegated event handler to intercept and process
                        the "mouseover[out]" events and modify the DOM
                        appropriately.
                    */
                    if( e.target.tagName=='DIV' && e.target!=e.currentTarget ){
                        
                        let id='del-x-task';
                        let expr='a#'+id;
                        
                        if( e.target.className=='currentTask' ){
                            if( e.type=='mouseover' && !e.target.querySelector( expr ) ){
                                e.target.parentNode.querySelectorAll( expr ).forEach(a=>{
                                    a.parentNode.removeChild(a);
                                });
                                appendclonehref( id, e.target );
                            }
                        }
                        if( e.type=='mouseout' && e.target.className!='currentTask' ){
                            e.target.parentNode.querySelectorAll( expr ).forEach(a=>{
                                a.parentNode.removeChild(a);
                            });
                        }
                    }
                };
                
                function clickhandler(e){
                    /*
                        delegated event handler to intercept and process
                        "Click" events on buttons.
                    */
                    if( e.target.tagName == 'BUTTON' ){
                        switch( e.target.className ){
                            case 'btnDelete': return deleteelement.call(this,e);
                            case 'collapsible': return displaytasks.call(this,e);
                            case 'addtask': return addtask.call(this,e);
                        }
                    }   
                };
                
                
                /********************************
                    Delegated event listeners
                */
                parent.addEventListener('click',clickhandler);
                parent.addEventListener('mouseover',mouseoverhandler);
                parent.addEventListener('mouseout',mouseoverhandler);
                /***********************************************************
                    Because the "Make Collapsible" button is outwith 
                    the Parent DIV we cannot use the same delegated listener 
                    or a listener bound to the same parent
                */
                oBttn.addEventListener('click',addelement);
            });
        </script>
    </head>
    <body>
        <!--- Add Step --->
        <div id='addSteps'>
            <p>Make a collapsible:</p>
            <input id='input' type='text' placeholder='Title for collapsible' />
            <button class='addcollapsible'>Make collapsible</button>
        </div>
        <!-- Add content to Collapsible (display:none) --->
        <div id='addTasksToSteps' style='display:none'>
            <!-- this will be cloned -->
            <div id='inputTaskDiv' style='display:none'>
                <input id='taskInput' type='text' />
                <button class='addtask'>Add content</button>
            </div>
        </div>
        <!-- this will be cloned -->
        <a href='#' id='deltask'>X</a>
        <!-- target for generated content -->
        <div id='divColl'></div>
    </body>
</html>

Example

Upvotes: 1

Nikhil Patil
Nikhil Patil

Reputation: 2540

If you want to store array against different ids, you can create the structure in localStorage as follows -

Setting localStorage -

var id = 1;
var arr = ['a', 'b', 'c'];
var content = {
      [id]: arr
    };
var contentString = JSON.stringify(content);
console.log(contentString);
//localStorage.setItem("collapsibleContent",contentString);

This will store your arr against id as key value pair -

You can retrieve the arr -

var contentString = localStorage.getItem("collapsibleContent");
var content = JSON.parse(contentString);
var arr = content[id];

Upvotes: 0

Related Questions