I'm making a basic list using vanilla javascript. I am able to add items, and change their class when they are clicked. Now, I want the items that have been selected (so their class has been changed) to be removed when they are clicked. At the bottom of the code, I am trying to loop through the list, then if the element in the list has the selected class, an event listener will remove the element when clicked, but this isn't working for me. Any ideas on what I'm doing wrong? (live demo: http://codepen.io/nicolaswilmot/pen/oXLgyq)
Here is the code:
var list = document.getElementById("theList"); // Get the list
// Add new item to top of list
function addItem(e) {
var userTxt = document.getElementById("userInput"); // Get user text
var newItem = document.createElement("li"); // Create new list item
var itemTxt = document.createTextNode(userTxt.value); // Get the text for item
newItem.appendChild(itemTxt); // Add text to list item
list.insertBefore(newItem, list.firstChild); // Put new item at top of list
newItem.className = 'defaultItem'; // Set default class for li
document.getElementById("userInput").value = ''; // Clear the input box
e.preventDefault(); // Prevent page from reloading when page is submitted
// Changes list item class
function changeClass () {
newItem.className = 'selectedItem';
}
// Initialize array for list items
listArray = [];
// Loop through list, add items to array, update class and counter
// when items are clicked
for (var i=0; i<list.children.length; i++) {
listArray.push(newItem);
listArray[i].addEventListener("click",changeClass);
listArray[i].addEventListener("click",countStuff);
}
}
var docForm = document.getElementById("theForm"); // Get the form element
docForm.addEventListener('submit',addItem,false); // Call addItem function when form is submitted
docForm.addEventListener('submit',countStuff,false); //Call counter when form submitted
// Function for the list item counter
function countStuff() {
// Get div container for counter
var itemCount = document.getElementById("counter");
// Get all list items that have not been selected (default class)
var unselectedItems = document.querySelectorAll('li.defaultItem');
//If more than one item, display plural "items"
if (unselectedItems.length > 1) {
itemCount.innerHTML = 'You still need '+unselectedItems.length+' items!';
} else if (unselectedItems.length == 0) {
itemCount.innerHTML = 'You have all items!';
} else {
itemCount.innerHTML = 'You still need '+unselectedItems.length+' item!';
}
}
// Loop through the list
for (var i=0; i<list.children.length; i++) {
// Remove items that are in selected state
if (list.childNodes[i].className='selectedItem') {
list.childNodes[i].addEventListener('click',function () {
list.removeChild([i])},false);
}
}
The placement of your code where you are trying to remove the element once it has the selectedItem class does not make sense, because that code will only run once on page load when the page has no items in the list. Instead, in the same function where you add the selectedItem class, you can bind an event listener to that DOM element that removes it from the list on the next click. http://codepen.io/anon/pen/vOKEzz
function changeClass () {
newItem.className = 'selectedItem';
//Remove it on click!
newItem.addEventListener('click',function () {
list.removeChild(newItem)
}, false);
}
Related
(New to Javascript)
I have created a input box which turns the input value into a list item, and adds a star
I am trying to create a new list whereby when the star is clicked on, it will copy into the 'favourites' section
I have tried this, but it is not working
let newStarSpan = document.querySelector("span")
newStarSpan.addEventListener('click', function(e){
//on click, get the parent node of the item clicked on
//i.e., want to return the individual <li> list item for the starSpan that was cliked on
let favNode = this.parentNode
//create a copy of this list item (the parent node)
let nodeCopy = favNode.cloneNode(true)
//add it to list B - favourites list
listB.appendChild(nodeCopy)
https://codepen.io/jemimacholzer/pen/dyYMKxj
You have few issues in your code, the first issue is related to the selector
const listB = document.querySelector('listB')
update it with
Fix:
const listB = document.getElementById('listB')
the other issue is related to newSpan event declaration & its scope, the newSpan can't be accessed outside of btn.onclick function.
You also assigned click listener to newSpan which seems to declare & bind on page when no newSpan is present in Dom. So instead of defining the listener you need create a function which triggers on newSpan click and bind that function during newSpan creation.
const listA = document.getElementById("listA")
const input = document.querySelector('input')
const btn = document.querySelector('button')
const listB = document.getElementById('listB')
btn.onclick = function() {
let Shopitem = input.value //store current value of input element in variable
input.value = ' ' //empty input element
const listItem = document.createElement('li') //creating a list item for the shopping item
listItem.setAttribute('id', Shopitem)
const span = document.createElement('span') //list text
const newBtn = document.createElement('button') //creating button to delete item
var starSpan = document.createElement("span")
var text = document.createTextNode("\u2B50")
starSpan.appendChild(text)
//making span and button children of the list item (containing within each list item)
listItem.appendChild(starSpan)
listItem.appendChild(span) //add list text to list item
listItem.appendChild(newBtn) //add list remove button to list item
newBtn.textContent = 'Delete'
span.textContent = Shopitem
listA.appendChild(listItem) //add li to the ul list
newBtn.onclick = function(e) {
list.removeChild(listItem)
}
starSpan.onclick = function(e) {
cloneListItem(listItem)
}
input.focus(); //focus the input method ready for entering the next shopping list item
}
//need to create new reference to star span as old reference is inside eother function
function cloneListItem(favNode){
//create a copy of this list item (the parent node)
let nodeCopy = favNode.cloneNode(true)
//add it to list B - favourites list
listB.appendChild(nodeCopy)
}
https://codepen.io/jemimacholzer/pen/dyYMKxj
I deleted the button part in my script but not even the first part of my function is working where I type in input box and suppose to be added to the ...I don't understand why. When I run the code without the buttons code which is titled " //BUTTON creation " I get no error but no item is being added to the list. So I have two problems Items aren't being added to my list and aren't displaying and also if I include the button part its saying an error "list.appendChild is not a function"
<input type="text" placeholder="Enter an Activity" id="textItem">
<img src="images/add-button.png" id="addButton">
<div id="container">
<ul class="ToDo">
<!--
<li>
This is an item
<div id="buttons">
<button ></button>
<img src="images/remove-icon.png"id="remove">
<button id="complete"></button>
<img src="images/complete-icon.jpg" id="complete">
</div>
</li>
!-->
</ul>
</div>
<script type="text/javascript">
//Remove and complete icons
var remove = document.createElement('img').src =
"images/remove-icon.png";
var complete = document.createElement('img').src = "images/complete-icon.jpg";
//user clicks add button
//if there is text in the item field we grab the item into var text
document.getElementById("addButton").onclick = function()
{
//value item is the text entered by user
var value = document.getElementById("textItem").value;
//checks if there is a value typed
if(value)
{
addItem(value);
}
//adds a new item to the ToDo list
function addItem(text)
{
var list = document.getElementsByClassName("ToDo");
//created a varibale called item that will create a list item everytime this function is called
var item = document.createElement("li");
//this will add to the innerText of the <li> text
item.innerText = text;
//BUTTON creation
var buttons = document.createElement('div');
buttons.classList.add('buttons');
var remove = document.createElement('buttons');
buttons.classList.add('remove');
remove.innerHTML = remove;
var complete = document.createElement('buttons');
buttons.classList.add('complete');
complete.innerHTML = complete;
buttons.appendChild(remove);
buttons.appendChild(complete);
list.appendChild(buttons);
list.appendChild(item);
}
}
</script>
The problem is in the line:
var list = document.getElementsByClassName("ToDo");
list.appendChild(item);
The line var list = document.getElementsByClassName("ToDo"); will provide a collection, notice the plural name in the api.
You need to access it using :
list[0].appendChild(item);
There are other problems too in the code but hopefully this gets you going!
There are a couple of issues in your code that need to be addressed to get it to work properly.
1) You are creating your image elements and then setting the variables to the src name of that image and not the image object itself. When you use that reference later on, you are only getting the image url and not the element itself. Change var remove = document.createElement('img').src = "images/remove-icon.png" to this:
var removeImg = document.createElement('img')
removeImg.src = "images/remove-icon.png";
2) As #Pankaj Shukla noted, inside the onclick function, getElementsByClassName returns an array, you will need to address the first item of this array to add your elements. Change var list = document.getElementsByClassName("ToDo") to this:
var list = document.getElementsByClassName("ToDo")[0];
3) For your buttons, you are trying to creating them using: var remove = document.createElement('buttons'). This is invalid, buttons is an not the correct element name, its button. Additionally, you are re-declaring the variables remove and complete as button objects, so within the onclick function it reference these buttons, not the images you defined earlier. So when you assign the innerHTML to remove and complete, you are assigning the buttons innerHTML to itself. The solution is to change the image variables to something different.
4) Finally, also relating to the buttons, you are assigning the innnerHTML to an image object, that's incorrect. You can either insert the html text of the img directly, or append the image object as a child of the button, similar to how the button is a child of the div.
The updated code with all these changes looks like this:
//Remove and complete icons
var removeImg = document.createElement('img');
removeImg.src = "images/remove-icon.png";
var completeImg = document.createElement('img');
completeImg.src = "images/complete-icon.jpg";
//user clicks add button
//if there is text in the item field we grab the item into var text
document.getElementById("addButton").onclick = function() {
//value item is the text entered by user
var value = document.getElementById("textItem").value;
//checks if there is a value typed
if (value) {
addItem(value);
}
//adds a new item to the ToDo list
function addItem(text) {
var list = document.getElementsByClassName("ToDo")[0];
//created a varibale called item that will create a list item everytime this function is called
var item = document.createElement("li");
//this will add to the innerText of the <li> text
item.innerText = text;
//BUTTON creation
var buttons = document.createElement('div');
buttons.classList.add('buttons');
var remove = document.createElement('button');
remove.classList.add('remove');
remove.appendChild(removeImg);
var complete = document.createElement('button');
complete.classList.add('complete');
complete.appendChild(completeImg);
buttons.appendChild(remove);
buttons.appendChild(complete);
list.appendChild(buttons);
list.appendChild(item);
}
}
I have a users card list, my task is:
Clicking "undo" button: will restore the last card that was deleted (use array)
Question 1: How to make array from cards list I have displayed ?
Question 2: How to restore the last card that was deleted?
(IF 2 cards are removed, "undo" button will to restore card one by one)
Link to codepen here https://codepen.io/MarinaShumeiko/pen/Nbeqew?editors=1010
var root = 'https://jsonplaceholder.typicode.com';
var notificationMessage = "Oops, there are no more user cards to display";
var userIndex = 0;
var undoBtn = $("#button")
var $clearBtn = $("#clear");
var $contentArea = $("#content");
var cardTemplate = '<div class="card" data-id="{id}"><div class="title"><div class="image"></div><div class="name">{name}</div><button onclick="removeUser({postid})" class="close"></button></div><div class="description">{body}{email}</div></div>';
// - Load all the card at once, when screen load
$(function() {
$contentArea.append(renderUser);
});
// Make array from usercards
var $cardDiv = $(".card");
var usersCardArray = $cardDiv.toArray(); // return usersCardArray.length = 0 :(
//remove all card at once
$clearBtn.click(clearUsers);
function clearUsers () {
$contentArea.empty();
userIndex = 0;
}
//remove one card
$('.card .close').on('click', removeUser);
function removeUser(postId) {
$('[data-id="' + postId + '"]').remove();
}
// get user data
function getUser () {
return $.ajax({ url: root + '/posts/1/comments', method: 'GET' });
}
function renderUser() {
getUser().then(function (user) {
for (var i = 0; i = user.length; i++) {
var card = cardTemplate
.replace('{id}', user[userIndex].id)
.replace('{postid}', user[userIndex].id)
.replace('{name}', user[userIndex].name)
.replace('{body}', user[userIndex].body)
.replace('{email}', user[userIndex].email);
$contentArea.append(card);
userIndex++;
}
})
}
Since you don't manipulate any actual data, only the view itself - you can add class .hidden on delete card, and remove this class on undo.
To keep track on deleted users I added array var deletedUsers =[]. Each time you delete a user, add its id to array and hide it from view by adding class hidden.
On undo - pop the user id from deletedUsers, and remove class hidden from this user's card
https://codepen.io/anon/pen/PbXxrB
I have the following javascript http://jsfiddle.net/847Kf/4/ that contains a counter at line 14 and 36. The code works but when i delete an item that i added and add a new item the counter continues from where it left.
For example: i insert 4 items 1 , 2 , 3 , 4 now i delete the second item and add a new item so i will have 1 , 3 , 4 , 5. How can i make that counter when i delete an item the counter should reset and redisplay the values according the example:
1, 3, 4, 5 - deleted an item and now the counter should change the values for the remaining item to: 1, 2, 3, 4.
HTML:
<ul class="tags" data-prototype="<div><label class=" required">__name__</label><div id="task_tags___name__"><div><label for="task_tags___name___name" class=" required">Name</label><input type="text" id="task_tags___name___name" name="task[tags][__name__][name]" required="required" maxlength="255" /></div></div></div>">
</ul>
Javascript:
// setup an "add a tag" link
var $addTagLink = $('Add a tag');
var $newLinkLi = $('<li></li>').append($addTagLink);
jQuery(document).ready(function() {
// Get the ul that holds the collection of tags
var $collectionHolder = $('ul.tags');
// add the "add a tag" anchor and li to the tags ul
$collectionHolder.append($newLinkLi);
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
$addTagLink.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new tag form (see code block below)
addTagForm($collectionHolder, $newLinkLi);
});
});
function addTagForm($collectionHolder, $newLinkLi) {
// Get the data-prototype explained earlier
var prototype = $collectionHolder.data('prototype');
// get the new index
var index = $collectionHolder.data('index');
// Replace '$$name$$' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, index);
// increase the index with one for the next item
$collectionHolder.data('index', index + 1);
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormLi = $('<li></li>').append(newForm);
// also add a remove button, just for this example
$newFormLi.append('x');
$newLinkLi.before($newFormLi);
// handle the removal, just for this example
$('.remove-tag').click(function(e) {
e.preventDefault();
$(this).parent().remove();
return false;
});
}
The main problem is on how you count the elements.
When creating a new tag, you update the quantity on the data, but it doesn't get noticed when deleting.
You have two options:
Update on the removal the value $collectionHolder.data('index', index - 1)
take the quantity on each creation, not setting it on document ready
I've noticed another issue with deleting. When you you create every element, you are attaching a delete event to every .remove-tag in the DOM, on the 3rd creation, the first element would have 3 events attached, and the 2nd two.
Two avoid that I'd do something like:
$('ul.tags').on('click','.remove-tag',function(e){
e.preventDefault();
$(this).parent().remove();
})
What that means is something like "Look on dom for ul.tag, and if a click event is triggered on a .remove-tag do this" so it keeps alive and active for the new tags created.
Because you have so many id's / name's / class's etc which incorporate the index. I would recommend regenerating the list on delete.
ie:-
function regenerateTagList(){
var vals = $('.tags li.tag input').toArray().map(function(v, i) { return v.value; });
$('.tags li.tag').remove();
$('ul.tags').data('index', 0);
for(var i = 0; i < vals.length; i++){
addTagForm($('ul.tags'), $newLinkLi);
$('.tags li.tag input:last').val(vals[i]);
}
}
And add a class to the li for the tag so you can select them from the add link:-
var $newFormLi = $('<li></li>', { class:"tag" }).append(newForm);
then call after you remove:-
$('.remove-tag').click(function(e) {
e.preventDefault();
$(this).parent().remove();
regenerateTagList();
return false;
});
Working Fiddle
This modification will do the change without regenerating the whole thing:
$('.remove-tag').click(function(e) {
e.preventDefault();
$(this).parent().remove();
var count = 0;
$('.tags li').each(function() {
$(this).find('div label').first().text(count);
count++;
});
return false;
});
It finds all the li's, then finds the first label in each li and changes it to the count variable. It could be simplified if the first label had a class called 'count'.
I'm making a todo list and I have li and button tags added dynamically when adding a new list item. The button is an x which is supposed to remove the list item. I have tried several things but can't figure out how to make an eventListener for each individual x button and remove the corresponding list item when it is clicked.
The renderTodos function is where all of the dynamically added content is created. I have a data-index set to each button in which I was trying to use to access each button to attach an eventListener on each dynamic button, but I wasn't sure how to implement that. From what I have read there should be a way to do this using the currentTarget or target of the event but I don't understand how that works.
var input = document.querySelector('input[name=todoItem]'),
btnAdd = document.querySelector('button[name=add]'),
btnClear = document.querySelector('button[name=clear]'),
list = document.querySelector('.todo'),
storeList = [];
function renderTodos(){
var el = document.createElement('li'),
x = document.createElement('button');
listLength = storeList.length;
//Set text for remove button
x.innerHTML = 'x';
for(var i = 0; i < listLength; i++){
el.innerHTML = storeList[i];
list.appendChild(el);
x.setAttribute('data-index', i);
el.appendChild(x);
}
// check for correct data-index property on x button
}
function addTodos(){
storeList.push(input.value);
// Check that input is getting pushed to list array
console.log(storeList);
renderTodos();
}
function clearList(){
// make list empty
list.innerHTML = '';
storeList.splice(0, storeList.length);
//render empty list
renderTodos();
//Check that list array is empty
console.log(storeList);
}
btnAdd.addEventListener('click', addTodos);
btnClear.addEventListener('click', clearList);
Everything else on the list works so far I just can't figure out how to implement this eventListener.
One simple example can be
//a click hadler is added to #mylist which is already present in the dom
document.querySelector('#mylist').addEventListener('click', function(e) {
//assuming that the the `x` is in a span and it is the only span in the `li` we check for that, we can improve this check more to make sure we have actually clicked on the delete button
if (e.target.tagName == 'SPAN') {
//if so then since we know the structure we can delete the parent node of the target which is the span element
e.target.parentNode.parentNode.removeChild(e.target.parentNode);
}
}, false);
//kindly forgive the use of jQuery here
for (var i = 0; i < 10; i++) {
$('<li />', {
text: i
}).append('<span class="x">X</span>').appendTo('#mylist');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul id="mylist"></ul>
This is a very basic implementation of event delegation, where the actual event is bound to an ancestor element but then we use the actual event target to determine whether to act on it. We can improve the if condition to test for an class for any other attribute!!!
You can add a listener to each button using something like:
x.innerHTML = '';
x.onclick = function(){
var node = this.parentNode;
node.parentNode.removeChild(node);
};
Or you can keep the renderTodos code as it is and delegate the remove to the parent UL:
// Add the listener
list.addEventListener('click', removeItem);
// The listener function
function removeItem(event) {
var node = event.target;
// Check that the click came from an X button
// better to check against a class name though
if (node.tagName &&
node.tagName.toLowerCase() == 'button' &&
node.innerHTML == 'x') {
node = node.parentNode;
node.parentNode.removeChild(node);
}
}
basically what you want to do is add an event on the parent container and wait for the event to bubble up and identify if the event originating is from your x mark and if it is then trigger the callback function.. This is the concept I think most of the libraries use..
Or use a library like jQuery, why solve a problem that has already been solved by others.