I'm working on a project that will take user input and store it into a list in localStorage. I want to be able to have three of these to do lists on the page.
I have put the code into three separate columns, however when I try to submit information in them, it seems to be adding the values in all of the columns to one list. I don't every use JS or Jquery, so I have been trying to learn as I go, and I'm stumped. This is the code that I am looking to run in three separate columns.
How do I make sure that the lists are stored in three separate places and also stored separately in localStorage?
var list = JSON.parse(localStorage.getItem("todolist"));
// Checks for to dos in LS, if none starts with empty array
if (!Array.isArray(list)) {
list = [];
}
function putOnPage() {
$("#todo-list").empty(); // empties out the html
var insideList = JSON.parse(localStorage.getItem("todolist"));
if (!Array.isArray(insideList)) {
insideList = [];
}
// todos to page
for (var i = 0; i < insideList.length; i++) {
var p = $("<p>").text(insideList[i]);
var b = $("<button class='delete'>").text("x").attr("data-index", i);
p.prepend(b);
$("#todo-list").prepend(p);
}
}
// rtodos on page load
putOnPage();
$(document).on("click", "button.delete", function() {
var todolist = JSON.parse(localStorage.getItem("todolist"));
var currentIndex = $(this).attr("data-index");
// deletes items
todolist.splice(currentIndex, 1);
list = todolist;
localStorage.setItem("todolist", JSON.stringify(todolist));
putOnPage();
});
$("input[type='submit']").on("click", function(event) {
event.preventDefault();
// changes input to variable and clears input field
var val = $("input[type='text']").val();
$("input[type='text']").val("");
// adds to do to list and local storage
list.push(val);
localStorage.setItem("todolist", JSON.stringify(list));
putOnPage();
});
You can probably go with making the list an array with three object each one containing a separate column from the page, than update or set using something list[0] for list one, list[1] for list 2 and so on.
Or
You can approach it with using three stores in the localstorage one for each column.
JS Fiddle Example -> https://jsfiddle.net/jassMarok/3gcnyamp/18/
// find elements
var banner = $("#banner-message")
var button = $("button")
var testObject = [{
name: "todo-list1",
items: ["Sell", "Buy", "Donate"]
},
{
name: "todo-list2",
items: ["Go", "Run", "Sleep"]
},
{
name: "todo-list3",
items: ["Work", "Program", "Code"]
}
];
// Put the object into storage
localStorage.setItem('testObject', JSON.stringify(testObject));
// handle click and add class
button.on("click", function() {
var lists = $('[id|="list"]');
console.log(lists);
var retrievedObject = JSON.parse(localStorage.getItem('testObject'));
console.log(retrievedObject)
for (i = 0; i < lists.length; i++) {
var $list = lists[i];
var items = retrievedObject[i].items;
for (j = 0; j < items.length; j++) {
console.log(items);
$($list).append('<li>' + items[j] + '</li>');
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#banner-message {
background: #fff;
border-radius: 4px;
padding: 20px;
font-size: 25px;
text-align: center;
transition: all 0.2s;
margin: 0 auto;
width: 300px;
}
button {
background: #0084ff;
border: none;
border-radius: 5px;
padding: 8px 14px;
font-size: 15px;
color: #fff;
}
#banner-message.alt {
background: #0084ff;
color: #fff;
margin-top: 40px;
width: 200px;
}
#banner-message.alt button {
background: #fff;
color: #000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="banner-message">
<p>Hello World</p>
<button>Load Data</button>
<hr/>
<h3>
List 1
</h3>
<ul id="list-1"></ul>
<hr/>
<h3>
List 2
</h3>
<ul id="list-2"></ul>
<hr/>
<h3>
List 3
</h3>
<ul id="list-3"></ul>
</div>
Related
I am trying to clone elements onclick one time only but multiple items are being cloned when I click continuously multiple times then multiple items created.
I don't understand why multiple items are created continuously. I only want items from the data-id should be appended one time even if I click it should be removed the cloned item. Also when we click appended items they should be removed.
$('.item-save').click(function() {
$(this).toggleClass('productad')
window.localStorage.setItem('test' + this.dataset.id, $(this).hasClass('productad'));
});
$('.item-save').each(function() {
var id = 'test' + this.dataset.id;
if (localStorage.getItem(id) && localStorage.getItem(id) == "true") {
$(this).addClass('productad');
}
});
$('.item-save').click(function() {
var id = this.dataset.id;
$(".item-save").attr("data-id", function() {
var $button = $(this).clone();
$button.appendTo('.item-append');
});
});
.item-save {
position: relative;
display: block;
font-size: 14px;
margin: 5px;
padding: 5px;
background: #a5a5a5;
text-align: center;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<div class='item-all'>
<div class='item-save' data-id='123'>Save</div>
<div class='item-save' data-id='124'>Save</div>
<div class='item-save' data-id='125'>Save</div>
<div class='item-save' data-id='126'></div>
</div>
<div class='item-append'></div>
Here is my code JsFiddle Demo
I work with localStorage function to store some value so that is important without removing localStorage is it possible to fix this issue Any help or advice is highly appropriated
Get rid of the attr() function in your second click function.
Change this
$('.item-save').click(function() {
var id = this.dataset.id;
$(".item-save").attr("data-id", function() {
var $button = $(this).clone();
$button.appendTo('.item-append');
});
});
To this
$('.item-save').click(function() {
var $button = $(this).clone();
$button.appendTo('.item-append');
});
Please look at the fiddle
jsfiddle
I have multiple cards in a container. And I have a unique value "objectid".
I have "selactAll" button, If I click that button, every card should get selected and save button is enable. That time I need to pass all cards in a parameter.
After "selectAll", if i click on any of the card, that particular card should get unselect. That time if I click save button, the remaining selecteds cards should go in a array format.
Third one is I can select single card on click inside on button. That time I need to pass the single card in a save parameter. How can I pass these datas in a Array format. Only selected Datas.
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', function($scope) {
$scope.unselectBtn = true;
$scope.selectBtn = false;
$scope.saveBtn = true;
$scope.divs = [1, 2, 3]; // for eample
$scope.containerSelected = $scope.divs.map(val => false);
$scope.buttonVisible = $scope.divs.map(val => true);
$scope.toggleButton = function(e, i) {
console.log('toggleButton')
$scope.containerSelected[i] = true;
if($scope.containerSelected[i] = true){
$scope.saveBtn = false;
}
$scope.buttonVisible[i] = false;
e.stopPropagation();
};
$scope.toggleContainer = function(i) {
if ($scope.containerSelected[i]) {
$scope.containerSelected[i] = false;
$scope.buttonVisible[i] = true;
}
};
$scope.toggleAllButton = function() {
$scope.unselectBtn = false;
$scope.selectBtn = true;
$scope.saveBtn = false;
$scope.containerSelected = $scope.containerSelected.map(val => true);
$scope.buttonVisible = $scope.buttonVisible.map(val => false);
console.log($scope.containerSelected)
}
$scope.toggleUnselectAllBtn = function(){
$scope.unselectBtn = true;
$scope.selectBtn = false;
$scope.saveBtn = true;
$scope.containerSelected = $scope.containerSelected.map(val => true);
$scope.buttonVisible = $scope.buttonVisible.map(val => false);
console.log($scope.containerSelected)
}
$scope.saveData = function(data){
console.log(data);
}
});
#myApp {
width: 100%;
display: -webkit-inline-box;
border: 1px solid black;
margin-top: 55px;
}
.container {
width: 10%;
height: 100px;
border: 1px solid black;
margin: 10%;
}
button.btn.btn-primary {
margin-top: 25%;
margin-left: 40%;
}
.selectedBorder {
border: 3px solid blue;
}
#selectALL {
margin-top: 20px;
}
<div title="Angular Scope" ng-app ng-controller="MainCtrl">
<button id="selectALL" ng-hide="selectBtn" ng-click="toggleAllButton()">Select All</button>
<button id="unselectALL" ng-hide="unselectBtn" ng-click="toggleUnselectAllBtn()">UnSelect All</button>
<button id="save" ng-hide="saveBtn" ng-click="saveData(div)">Save</button>
<div id="myApp">
<div class="container" ng-repeat="div in divs track by $index" ng-class="{ 'selectedBorder': containerSelected[$index] }" ng-click="toggleContainer($index)">
<button id="okBtn" class="btn btn-primary" ng-click="toggleButton($event, $index)" ng-show="buttonVisible[$index]">ok</button>
</div>
</div>
</div>
It looks like the position of the value in the $scope.containerSelected array corresponds to the ID of the div.
For example, if $scope.containerSelected is [true, false, false], then the first div is selected, but the remaining two are not.
With this assumption, you could use the reduce method to create an array that gives the position of all true elements, which would correspond to the div. If the div IDs start at 1, just add 1 to the values in the array.
For example:
let dataToSend = $scope.containerSelected.reduce(function(a, e, i) {
if (e === true)
a.push(i);
// or, if they start at 1, use a.push(i+1)
return a;
}, []);
Updated fiddle: https://jsfiddle.net/jmg157/pfzg4t1n/
In this program, I'm able to add inputs with a button but I need to show the length of each input as it changes. I'm able to get the length using an EventListener, but I'm not sure how to change the text value for any newly created buttons.
On line 12, you can see that I'm able to change the value successfully on the first input but I'm using an html variable. If you look at my addCell() function, you'll see that I have an element as a child of each node to keep track of the length of each input. I need to access that element in my change() function so I can set the event.target.value.length to the corresponding nodes child element.
I've tried using this, setting var x = this and I've tried using the event.target properties to find the corresponding node and even innerHTML.
var i = 0;
var count = 1;
var length = 2;
var chars = 0;
document.addEventListener('input', function (evt) {
change(evt);
});
function change(elem) {
var check = document.getElementById("first");
if (event.target == check) {
document.getElementById("len").innerHTML = event.target.value.length;
return;
}
// Here's where I'm stuck
}
function removeCell() {
if (count <= 1) {
alert("Illegal operation, the police have been notified.")
return;
}
var elem = document.getElementById('main');
elem.removeChild(elem.lastChild);
count = count - 1;
length = length - 1;
}
function addCell() {
var node = document.createElement('div');
node.innerHTML += length;
var inp = document.createElement('INPUT');
var size = document.createElement('size');
inp.setAttribute("type", "text");
node.appendChild(inp);
node.appendChild(size);
document.getElementById('main').appendChild(node);
count += 1;
length += 1;
i += 1;
}
#area {
width: 585px;
background-color: lightgrey;
color: black;
border-style: solid;
margin-left: auto;
margin-right: auto;
min-height: 100px;
height: auto
}
#texts {
width: 220px;
height: 50px;
border-style: solid;
}
body {
background-color: grey;
}
<div id="area">
<form id="main">
<pre><b> input </b> length</pre>
<span id="list">
1<input type="text" id="first"> <var id="len"></var>
</span>
</form>
<br />
<button onclick="addCell()">Add Cell</button>
<button onclick="removeCell()">Remove Cell</button>
<button onclick="sort()">Sort</button>
</div>
Since I'm able to use alert() to show me the correct length of each newly created input each time it changes, I know there's a way to access the "size" element I created to update it using event.target.value.length
Your problem is that you use a "global" input event listener and your change() function is not programmed to handle multiple input fields because in it you are querying known element ids first and len.
If you want to go with a global listener you have to tell your change() function how to access the new input and corresponding target fields.
An easier way is that you modify your addCell() function and attach an event listener to the input field that you are creating instead of using a global one. Thereby each input field holds its own event listener. Since both the input field and your size element, which displays the length of the input value, are created in the same scope you can use easily write the length to the corresponding size element.
inp.addEventListener('input', function(){
size.innerText = inp.value.length;
});
If you want this to work with your provided HTML you need to remove your first input field and call addCell() manually so that your initial input gets rendered.
Your code should then look like this (note: I set var count = 0; and var length = 1;):
var i = 0;
var count = 0;
var length = 1;
var chars = 0;
function removeCell() {
if (count <= 1) {
alert("Illegal operation, the police have been notified.")
return;
}
var elem = document.getElementById('main');
elem.removeChild(elem.lastChild);
count = count - 1;
length = length - 1;
}
function addCell() {
var node = document.createElement('div');
node.innerHTML += length;
var inp = document.createElement('INPUT');
var size = document.createElement('size');
inp.setAttribute("type", "text");
inp.addEventListener('input', function(){
size.innerText = inp.value.length;
});
node.appendChild(inp);
node.appendChild(size);
document.getElementById('main').appendChild(node);
count += 1;
length += 1;
i += 1;
}
addCell();
#area {
width: 585px;
background-color: lightgrey;
color: black;
border-style: solid;
margin-left: auto;
margin-right: auto;
min-height: 100px;
height: auto
}
#texts {
width: 220px;
height: 50px;
border-style: solid;
}
body {
background-color: grey;
}
<div id="area">
<form id="main">
<pre><b> input </b> length</pre>
<span id="list"></span>
</form>
<br />
<button onclick="addCell()">Add Cell</button>
<button onclick="removeCell()">Remove Cell</button>
<button onclick="sort()">Sort</button>
</div>
If HTML layout is planned out and is consistent you can use [name] attribute for form controls and .class or even just the tagName. Use of #id when dealing with multiple tags is difficult and unnecessary. Just in case if you weren't aware of this critical rule: #ids must be unique there cannot be any duplicate #ids on the same page. Having duplicate #ids will break JavaScript/jQuery 90% of the time.
To accessing tags by .class, #id, [name], tagName, etc. use document.querySelector() and document.querySelectorAll() for multiple tags.
To access forms and form controls (input, output, select, etc) by [name] or #id use the HTMLFormElement and HTMLFormControlsCollection APIs.
.innerHTML is destructive as it overwrites everything within a tag. .insertAdjacentHTML() is non-destructive and can place an htmlString in 4 different positions in or around a tag.
Event handlers and event listeners work only on tags that were initially on the page as it was loaded. Any tags dynamically added afterwards cannot be registered to listen/handle events. You must delegate events by registering an ancestor tag that's been on the page since it was loaded. This was done with delRow() since the buttons are dynamically created on each row (changed it because one delete button that removes the last row isn't that useful. ex. 7 rows and you need to delete 4 rows just to get to the third row).
Here's a breakdown of: [...ui.len] ui references all form controls .len is all tags with the [name=len]. The brackets and spread operator converts the collection of len tags to an array.
There's no such thing as <size></size>. So document.createElement('size') is very wrong.
const main = document.forms.main;
main.oninput = count;
main.elements.add.onclick = addRow;
document.querySelector('tbody').onclick = delRow;
function count(e) {
const active = e.target;
const ui = e.currentTarget.elements;
const row = active.closest('tr');
const idx = [...row.parentElement.children].indexOf(row);
const length = [...ui.len][idx];
length.value = active.value.length;
return false;
}
function addRow(e) {
const tbody = document.querySelector('tbody');
let last = tbody.childElementCount+1;
tbody.insertAdjacentHTML('beforeend', `<tr><td data-idx='${last}'><input name='txt' type="text"></td><td><output name='len'>0</output></td><td><button class='del' type='button'>Delete</button></td>`);
return false;
}
function delRow(e) {
if (e.target.matches('.del')) {
const row = e.target.closest('tr');
let rows = [...row.parentElement.children];
let qty = rows.length;
let idx = rows.indexOf(row);
for (let i = idx; i < qty; i++) {
rows[i].querySelector('td').dataset.idx = i;
}
row.remove();
}
return false;
}
body {
background-color: grey;
}
#main {
width: 585px;
background-color: lightgrey;
color: black;
border-style: solid;
margin-left: auto;
margin-right: auto;
min-height: 100px;
height: auto
}
tbody tr td:first-of-type::before {
content: attr(data-idx)' ';
}
<form id="main">
<table>
<thead>
<tr>
<th class='txt'>input</th>
<th class='len'>length</th>
<th><button id='add' type='button'>Add</button></th>
</tr>
</thead>
<tbody>
<tr>
<td data-idx='1'><input name='txt' type="text"></td>
<td><output name='len'>0</output></td>
<td><button class='del' type='button'>Delete</button></td>
</tr>
</tbody>
</table>
<!--These are dummy nodes because of the
HTMLFormControlsCollection API ability to use id or name, there
must be at least 2 tags with the same name in order for it to
be considered iterable-->
<input name='txt' type='hidden'>
<input name='len' type='hidden'>
</form>
For a project, I am building an app that gets user input, stores it in an array, and displays the input in the DOM. I did the first two parts but I am having trouble displaying it. More specifically, I can't get the CSS to show up.
I have tried .createElement() which creates a new list-item but it does not include CSS. I am starting to think I am completely going about this incorrectly. If you need more information or code let me know.
\\HTML
<div id="boxhold">
<ul>
<li>
<div class="twogrid">
<h1>Fruit Juice</h1>
<p>50</p>
</div>
</li>
</ul>
</div>
\\CSS
#boxhold {
margin: 0 auto;
ul {
list-style-type: none;
padding: 0;
li {
margin: 0 auto;
width: 408px;
height: 75px;
border: 3px solid $prime-color;
h1 {
font-family: $header-font;
font-weight: $header-weight;
font-size: 1em;
}
p {
font-family: $header-font;
font-weight: $header-weight;
}
}
}
}
\\JS
//Get Data
//Empty array for storing
var added = [];
//Get Data
var userInput = function() {
return {
name: document.getElementById('name').value,
amount: document.getElementById('amount').value
}
};
// Store Data
var newSugar = function(){
return added.push(userInput());
}
// New HTML
function newBox() {
var newLi = document.createElement('li');
var newName = document.getElementById('name').value;
var n = document.createTextNode(newName);
newLi.appendChild(n);
var newAmount = document.getElementById('amount').value;
var a = document.createTextNode(newAmount);
newLi.appendChild(a);
var boxhold = document.getElementById('boxhold').getElementsByTagName('ul')[0];
document.body.appendChild(newLi);
};
//Adding stuff
var displayData = (function() {
var addInput = function() {
var data = userInput();
var item = newSugar();
var box = newBox();
//var box = newItem();
};
var addFood = document.getElementById('addFood');
addFood.addEventListener('click', addInput);
document.addEventListener('keypress', function(event) {
if (event.keyCode === 13 || event.which === 13) {
addInput();
}
});
})(userInput, newSugar, newBox);
Welcome to Stack Overflow #nzart 👋
It looks like you're appending the newly created list item to the document's body, which means it will be added as the last element of the page. Your CSS indicates that the styles only apply to list items inside of an unordered list, so this would explain the lack of styles.
The simple fix should be to replace document.body.appendChild(newLi); with boxhold.appendChild(newLi);. I hope this helps!
I want to create a star rating in JavaScript in which default is 5 i will go down to 1 but i didn't understand how to fill up from 1 to 5.
Here is my code :-
$(".star").click(function(){
var starselect = $(this).attr("id");
for( var j = 5 ; j>starselect ; j--)
{
$("#"+j).removeClass("starchecked");
}
if( j < starselect+1 ){
$("#"+j).addClass("starchecked");
}
$(".review-star").attr("data-rating",starselect);
});
Per my comment, I would do this using css, but if you need to use js, then I would use a mixture of nextAll, prevAll and andSelf - see comments in code
var $stars = $(".star")
$stars.click(function() {
var $star = $(this);
if ($star.hasClass('checked')) {
// if current clicked star is checked
if ($star.next().hasClass('checked')) {
// check if next star is also checked
$star.nextAll().removeClass('checked'); // if next is then disable all following
} else {
$star.nextAll().andSelf().removeClass('checked'); // if not then disable self and all after (shouldn't need all after, but just in case)
}
$star.prevAll().addClass('checked'); // check all before
// if you just want to remove all stars on click of an already checked box, remove the above 2 lines and just ue the below:
// $stars.removeClass('checked');
} else {
$star.nextAll().removeClass('checked'); // remove checked from all following the clicked
$star.prevAll().andSelf().addClass('checked'); // add checked to this and all previous
}
var starselect = $stars.index($star) + 1; // get current star rating
$(".review-star").attr("data-rating", starselect);
console.log(starselect);
});
.star {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.checked {
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span class="star">1</span>
<span class="star">2</span>
<span class="star">3</span>
<span class="star">4</span>
<span class="star">5</span>