Reputation: 149
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:
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
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>
Upvotes: 1
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