I need add an Event Handler to the li element and then console.log() the name of the shirt they selected.
I am getting a typeError: Cannot convert object to primitive value. I am new to this and am struggling to figure this out.
<h3>Shirts</h3>
<ul id='list'>
<li>Biker Jacket</li>
<li>Mens Shirt</li>
</ul>
var lis = document.getElementById("list").getElementByTagName('li');
for(var i = 0; i < lis.length; i ++){
lis[i].onclick = (event) => {
}
console.log(event);
}
You can use children instead of getElementByTagName and loop over it with the use of Array.from, it's es6 syntax. Array.from will not work in es5.
var lis = Array.from(document.getElementById("list").children);
for(var i = 0; i < lis.length; i ++){
lis[i].onclick = (event) => {
}
console.log(event);
}
Adding event listeners to each li is not good as if there are a lot, you will end up having a lot of handlers. You can use event delegation to solve the issue. Every event that happens on an element propagates upward to it's parent to Grand parents and so on. So you can add just one event listener on ul and detect which on which li the click happened and do the processing like wise. Here is an example:
document.getElementById("list").addEventListener("click", function(e) {
// e.target is the clicked element!
// If it was a list item
if(e.target && e.target.nodeName == "LI") {
// List item found! Output the ID!
console.log("List item ", e.target.id.replace("post-", ""), " was clicked!");
}
});
Hope this helps
Loop through the list item then add a click event and innerText to get the shirt.
const lis = document.querySelectorAll('#list > li');
lis.forEach(function(list){
list.onclick = () => {
console.log(list.innerText)
};
})
<h3>Shirts</h3>
<ul id='list'>
<li>Biker Jacket</li>
<li>Mens Shirt</li>
</ul>
Related
I'm trying to create a list where the user can add their own items to it by typing in a text box and pressing the Enter key or clicking on the "Enter" button. I'm trying to get the list items to get crossed out when the user clicks on them but this doesn't seem to work on list items created by the user, it only works on list items added in the HTML file.
Shouldn't crossBTNs() work on user generated list items too? Sorry if this is a basic question, I'm new to JS.
EDIT: Figured it out
I simply did this:
li.addEventListener("click", function () {
crossOutUserItem(li); });
I added this code to createListElement(). crossOutUserItem(li) adds the class that creates the "crossed out" effect to the list item.
Original code:
var button = document.getElementById("enter");
var input = document.getElementById("userinput");
var ul = document.querySelector("ul");
function createListElement() {
var li = document.createElement("li");
li.append(document.createTextNode(input.value));
ul.appendChild(li);
input.value = "";
}
function addListAfterClick() {
if (input.value.length > 0) {
createListElement();
}
}
function addListAfterKeypress(event) {
if (input.value.length > 0 && event.keyCode === 13) {
createListElement();
}
}
function crossBTNs () {
for (let i = 0; i < ul.children.length; i++) {
ul.children[i].addEventListener("click", function() {
ul.children[i].classList.toggle("crossOut");
});
}
}
button.addEventListener("click", addListAfterClick);
input.addEventListener("keypress", addListAfterKeypress);
crossBTNs();
By adding event handlers on the li, as you have done.
function crossBTNs () {
for (let i = 0; i < ul.children.length; i++) {
ul.children[i].addEventListener("click", function() {
ul.children[i].classList.toggle("crossOut");
});
}
}
It doesn't set any event handler for the list items li which are generated dynamically, because as the js file loads the handlers are set initially only.
You will need to use event delegation in which you set the handler on the parent of li, which is ul and look for the particular child element from where the event has actually happened using e.target properties as per your requirement.
You can use this resource MDN Docs Event Delegation to know more about event delegation in javascript.
Just delegate from the closest static container - likely the UL in this case.
ul.addEventListener("click", function(e) {
const tgt = e.target.closest("li");
tgt.classList.toggle("crossOut");
});
Now you can remove all other event handling for the LI and do not need to call something after adding an LI to add event handling
I'm trying to remove an item from local storage. It works except it occasionally removes more than one item.
I have tried array.splice removing local storage then resetting it with the new values and haven't found a way to fix it, I'm sure it's something simple.
let itemsArray = JSON.parse(localStorage.getItem("itemsArray")) || [];
//Initialize Function
window.addEventListener("load", () => showItems(itemsArray));
//Add event listener for the form submit
myForm.addEventListener("submit", onSubmit);
//Add event listener for the click event on the delete button
itemList.addEventListener("click", removeItem);
function showItems(itemsArray) {
itemList.innerHTML = itemsArray.join("");
}
//Place the input into to list of items
function onSubmit(e) {
//Prevent the form submission
e.preventDefault();
//Create li element for the DOM
li = document.createElement("li");
//Place input value into li
li.appendChild(document.createTextNode(`${item.value}`));
//Create the delete button and place it to the right of input value
const btnDelete = document.createElement("button");
btnDelete.classList.add("btnDelete");
btnDelete.appendChild(document.createTextNode("X"));
li.appendChild(btnDelete);
itemList.appendChild(li);
itemsArray.push(li.outerHTML);
localStorage.setItem("itemsArray", JSON.stringify(itemsArray));
//Reset input value to empty
item.value = "";
}
//Delete item
function removeItem(e) {
if (e.target.classList.contains("btnDelete")) {
if (confirm("Are You Sure You Want To Delete This Item?")) {
removeLocalStorage();
let li = e.target.parentElement;
itemList.removeChild(li);
}
}
}
function removeLocalStorage(){
let store = JSON.parse(localStorage.getItem("itemsArray")) || [];
for(let i = 0; i < store.length; i++){
store.splice(i, 1);
localStorage.setItem('itemsArray', JSON.stringify(store));
}
}
All I want is to remove the item that corresponds to the item being deleted from the UI. When I delete, say index 1, it removes every other index.
This is essentially the Brad Traversy project on DOM manipulation. I am trying to work more with local storage for other projects.
You need to pass the index of the item you want deleted to the removeLocalStorage function. See code below:
//Delete item
function removeItem(e) {
if (e.target.classList.contains("btnDelete")) {
if (confirm("Are You Sure You Want To Delete This Item?")) {
let li = e.target.parentElement;
let index = Array.prototype.indexOf.call(itemList.children, li);
removeLocalStorage(index);
itemList.removeChild(li);
}
}
}
function removeLocalStorage(index){
let store = JSON.parse(localStorage.getItem("itemsArray")) || [];
store.splice(index, 1);
localStorage.setItem('itemsArray', JSON.stringify(store));
}
Did you try the line?:
window.localStorage.removeItem('itemsArray');
This line will delete only the item with specific key in the localstorage.
It seems to me that with the loop you are removing the entire array. You have to pass an identifier to removeFromLocalStorage(). There you have to know what element you want to remove. The loop only make sense to me if you want to discover the index of an particular element with some property. For example:
...
if (
confirm("Are You Sure You Want To Delete This Item?")
) {
removeLocalStorage(e.target.id);
let li = e.target.parentElement; itemList.removeChild(li);
}
removeFromLocalStorage(identifier){
...
let id
store.forEach((el,index)=> {
id = el.identifier === identifier && index
}).
store.splice(id,1)
localStorage.setItem('itemArray', JSON.stringify(store))
....
}
Newby to pure JS.
I'm creating a menu that has to work with mobile.
I'm trying to create with pure .js, instead of using jQuery so, that's an experiment and it has been challenging.
Here's my code:
JS:
(function() {
var menu = document.querySelector('.mobile-menu');
var subMenu = {
downToggle: document.getElementsByClassName('sub-menu'),
downToggleTitle: document.getElementsByClassName('sub-menu-title'),
subMenuItems: document.getElementsByClassName('sub-menu-item-mobile'),
searchBar: document.getElementById('mobile-search'),
onclickimg: document.querySelectorAll('.sub-menu-arrow'),
};
function listen() {
for(var i=0; i<subMenu.downToggleTitle.length; i++) {
subMenu.downToggleTitle.item(i).addEventListener('click', function(e) {
// if there is a menu that's already open and it's not the element that's been clicked, close it before opening the selected menu
for(var i=0; i<subMenu.downToggleTitle.length; i++) {
if (subMenu.downToggleTitle.item(i).classList.contains('expanded') && subMenu.downToggleTitle.item(i) !== e.target) {
subMenu.downToggleTitle.item(i).classList.toggle('expanded');
}
}
// inside each sub-menu is a third-level-sub-menu. So inside each sub-menu we
// check if it's already open, then close it
for(var i=0; i<subMenu.subMenuItems.length; i++) {
// console.log("test test")
if(subMenu.subMenuItems.item(i).classList.contains('expanded') && subMenu.subMenuItems.item(i) !== e.target) {
subMenu.subMenuItems.item(i).classList.toggle('expanded');
}
}
this.classList.toggle('expanded');
});
}
for(var i=0; i<subMenu.subMenuItems.length; i++) {
subMenu.subMenuItems.item(i).addEventListener('click', function(e) {
for(var i=0; i<subMenu.subMenuItems.length; i++) {
if(subMenu.subMenuItems[i].classList.contains('expanded') && subMenu.subMenuItems[i] !== e.target) {
subMenu.subMenuItems[i].classList.toggle('expanded');
console.log("hello Aug 20");
}
}
this.classList.toggle('expanded');
});
}
} listen();
}());
The behavior that I want to change is the following:
In the first version, if the client press the .sub-menu-title class (the variable downToggleTitle), which is a li item, the very element will toggle the class expanded. Now I want something a little bit different.
I added the class sub-menu-arrow, which is the variable onclickimg to an img at the very end of my list element, so if the client will click on the arrow, all the class element sub-menu-title ( var = downToggleTitle ) will toggle the class expanded.
This is not happening because for some reason if I change the code in this way:
subMenu.onclickimg.item(i).addEventListener('click', function(e) {
for(var i=0; i<subMenu.downToggleTitle.length; i++) {
if (subMenu.downToggleTitle.item(i).classList.contains('expanded') && subMenu.downToggleTitle.item(i) !== e.target) {
subMenu.downToggleTitle.item(i).classList.toggle('expanded');
}
}
the class expanded will be toggled to the sub-menu-arrow elements (like I said, some images with animations).
Any suggestion on how to target the parent element in this case?
Also, is it possible to exclude the anchor element with the class mobile-toplevel-link from the click event?
The <a> element is the other children of the sub-menu-title class
This is really just a comment. You can greatly simplify your code using the iterator functionality of modern NodeLists. I don't see the point of the subMenu object, it just makes references longer.
Also, I've replaced getElementsByClassName as it produces a live NodeList, whereas querySelectorAll returns a static list. Not much difference here, but can be significant in other cases.
The following is a simple refactoring, it should work exactly as your current code is supposed to. Note that for arrow functions, this is adopted from the enclosing execution context.
(function() {
let menu = document.querySelector('.mobile-menu');
let downToggle = document.querySelectorAll('.sub-menu'),
downToggleTitle = document.querySelectorAll('.sub-menu-title'),
subMenuItems = document.querySelectorAll('.sub-menu-item-mobile'),
searchBar = document.getElementById('mobile-search'),
onclickimg = document.querySelectorAll('.sub-menu-arrow');
function listen() {
downToggleTitle.forEach(dtTitle => {
dtTitle.addEventListener('click', function(e) {
// If there is a menu that's already open
// and it's not the element that's been clicked,
// close it before opening the selected menu
downToggleTitle.forEach(node => {
if (node.classList.contains('expanded') && node !== this) {
node.classList.toggle('expanded');
}
});
// inside each sub-menu is a third-level-sub-menu. So inside each sub-menu
// If it's already open, close it
subMenuItems.forEach(item => {
// console.log("test test")
if (item.classList.contains('expanded') && item !== this) {
item.classList.toggle('expanded');
}
});
this.classList.toggle('expanded');
});
});
subMenuItems.forEach(item => {
item.addEventListener('click', function(e) {
subMenuItems.forEach(item => {
if (items.classList.contains('expanded') && item !== this) {
item.classList.toggle('expanded');
console.log("hello Aug 20");
}
});
this.classList.toggle('expanded');
});
});
}
listen();
}());
If you want to get the parent element of the clicked target, then you can exploit your current eventListener and use the e.target.parentNode to get it. This will return you an element, which you can add/remove CSS classes from and do pretty much everything you like. Keep in mind, you can use the .parentNode multiple times and for example, if you wanted to get the "grandparent" of an element (2 levels up) you could write e.target.parentNode.parentNode and so on.
I have a simple To Do list that when a new ToDo is generated it creates and appends a new li element into a preexisting ul. I have a toggleAll()function that adds a class which strikes through the li elements when double clicked. What I want to do now is to create a method that makes it so when I double click an li element, it toggles the line-through class just for the element clicked, so I can mark individual ToDos as completed. I tried using regular JS and jQuery but couldn't find the way to do it with either.
My ToDos are composed of a text property and a boolean determining whether the task is completed or not. Toggling sets the boolean to true, and if the boolean is true, then the .marked class is added to the content of the li element.
addTodo: function(todo){
this.list.push({
todoText: todo,
completed: false,
});
console.log(this.list);
},
var view = {
display: function(){
var printed = document.getElementById('printedList');
printed.innerHTML = '';
for(var i = 0; i < todos.list.length; i++){
var newLi = document.createElement('li');
newLi.innerHTML = todos.list[i].todoText;
printed.appendChild(newLi);
}
}
}
.marked{
text-decoration: line-through;
}
<div id='listica'>
<ul id='printedList'></ul>
</div>
Edit: toggleAll function:
toggleAll: function(){
var printedElements = document.getElementById('printedList');
for(var i = 0; i < todos.list.length; i++){
if(todos.list[i].completed == false){
todos.list[i].completed = true;
printedElements.style.textDecoration = 'line-through';
} else if(todos.list[i].completed == true){
todos.list[i].completed = false;
printedElements.style.textDecoration = '';
}
};
You can use .addEventListener() or jQuery's .on().
With .addEventListener you need to loop through all of the lis:
const lis = document.querySelectorAll('li');
lis.foreach(function(li) {
li.addEventListener('dblclick', function(e) {
e.target.classList.add('line-through');
});
});
For .on() you don't have to loop through the elements.
$('li').on('dblclick', function(e) {
$(e.target).addClass('line-through');
});
.on()
.addEventListener()
event.target
NodeList.forEach
Element.classList
So I have a simple script that adds "li" elements to the "ul" and assigns them a class. Now I want to change the class of "li" item on click event.
Here is the HTML:
<form class="form">
<input id="newInput" type="text" placeholder="Dodaj pozycję">
<button id="createNew" type="button">Dodaj</button>
</form>
<h2>Moja lista:</h2>
<div class="listBg">
<ul id="list">
</ul>
</div>
<button id="deleteAll" type="button">Wyczyść</button>
And JS:
function addItem() {
var myList = document.getElementById("list"); // get the main list ("ul")
var newListItem = document.createElement("li"); //create a new "li" element
var itemText = document.getElementById("newInput").value; //read the input value from #newInput
var listText = document.createTextNode(itemText); //create text node with calue from input
newListItem.appendChild(listText); //add text node to new "li" element
if (itemText === "") { // if input calue is empty
alert("Pole nie może być puste"); // show this alert
} else { // if it's not empty
var x = document.createElement("span"); // create a new "span" element
x.innerText = "X"; // add inner text to "span" element
x.className = "closer"; // add class to "span" element
myList.appendChild(newListItem); // add created "li" element to "ul"
newListItem.className = "item"; // add class to new "li" element
newListItem.appendChild(x); // add a "span" to new "li" element
var itemText = document.getElementById("newInput"); // read current input value
itemText.value = ""; // set current input calue to null
}
};
I was thinking something like this should do the trick, but it's not working:
function itemDone() {
var listItems = document.querySelectorAll("li");
var i;
for (i = 0; i < listItems.length; i++) {
listItem[i].className = "itemDone";
};
};
var item = document.getElementsByClassName("item");
item.addEventListener("click", itemDone);
I'm fairly new to javascript so I would appreciate some explanation with the answer.
Use event delegation for the dynamically created elements. With this, you only need one event listener on the ul#list and it will work for all elements you dynamically attach to it:
document.getElementById("list").addEventListener("click",function(e) {
if (e.target && e.target.matches("li.item")) {
e.target.className = "foo"; // new class name here
}
});
Here's a simplified example so you can see what happens with the code:
function addItem(i) {
var li = document.createElement('li');
li.appendChild(document.createTextNode(i));
li.className = 'item';
document.getElementById('list').appendChild(li);
}
var counter = 2;
document.getElementById('btn').addEventListener('click', function() {
addItem(counter++);
});
document.getElementById("list").addEventListener("click", function(e) {
if (e.target && e.target.matches("li.item")) {
e.target.className = "foo"; // new class name here
alert("clicked " + e.target.innerText);
}
});
<ul id="list">
<li class="item">1</li>
</ul>
<button id="btn">
add item
</button>
You'll have to set the eventListener on each single item, as document.getElementsByClassName() returns a collection of items and you can't simply add an event listener to all of them with one call of addEventListener().
So, just like the loop you used in itemDone(), you'll have to iterate over all items and add the listener to them:
var items = document.getElementsByClassName("item");
for (var i = 0; i < items.length; i++) {
items[i].addEventListener("click", itemDone);
}
As pointed out in the comments, you can also do so directly when creating the elements, so in your addItem() function, add:
newListItem.addEventListener("click", itemDone);
try this :
var item = document.getElementsByClassName("item");
for (var i = 0; i < item.length; i++) {
item[i].addEventListener("click", itemDone);
}
function addItem(i) {
var li = document.createElement('li');
li.appendChild(document.createTextNode(i));
li.className = 'item';
document.getElementById('list').appendChild(li);
}
var counter = 2;
document.getElementById('btn').addEventListener('click', function() {
addItem(counter++);
});
document.getElementById("list").addEventListener("click", function(e) {
if (e.target && e.target.matches("li.item")) {
e.target.className = "foo"; // new class name here
alert("clicked " + e.target.innerText);
}
});
<ul id="list">
<li class="item">1</li>
<li class="item">1</li>
<li class="item">1</li>
<li class="item">1</li>
<li class="item">1</li>
</ul>
<button id="btn">
add item
</button>
First, try using getElementByTagName instead of querySelectorAll, because querySelectorAll is slower. And second, item receives an array, so item.addEventListener will give you an error. You have to do the addEventListener over item[counter], in a loop.
document.getElementById("list").addEventListener("click", function (e) {
const parent = e.target.parentElement;
const siblings = Array.from(parent.getElementsByTagName("LI"));
siblings.forEach(n => (n.className = (e.target === n) ? "foo" : ""));
});