How to toggle list element through DOM Events in Javascript - javascript

I have a list containing three elements inside a . I'm trying to toggle on and off a CSS class which strikes the text when the list element is clicked. I've created a function and used a for loop to check for the array index of the list element that was clicked.
I've defined the following:
List Items:
<ul>
<li class="items line-through">Watch</li>
<li class="items line-through">Shoes</li>
<li class="items line-through">Cake</li>
</ul>
var list = document.getElementsByClassName("line-through");
Created a function to toggle the array element from the list based on the index when "click" event happens
function checkItem(index) {
console.log("event = " + index)
list[index].classList.toggle("line-through");
console.log("list = " + index);}
for (var i = 0; i <= list.length; i++) {
console.log("i= " + i);
list[i].addEventListener("click", checkItem(i));}
So after my logic I've declared the for and i index with a value of zero, which is the value of the first list in the array. Then I've added the prints to check if it runs and then checked if list 0, 1, or 2 was clicked. If yes, run function and toggle list[i]. If not, i++ and verify the next one.
The problem is that now it immediately adds the toggle to the first and last items from the list. I've also tried to use querySelectorAll("li") but then it says that list[i] is undefined, which I do not understand why, cause when verifying list[0] in the console it present the first element.

You could refactor your code and use the this keyword to target the clicked element, like this:
document.addEventListener('DOMContentLoaded', () => {
// Execute the code after the HTML content is loaded
const myList = document.querySelector('.my-list');
const listElements = Array.from(myList.children);
listElements.forEach( li => {
li.addEventListener('click', checkItem);
});
function checkItem() {
// The `this` keyword is the clicked element
this.classList.toggle('line-through');
}
});
<ul class="my-list">
<li class="items line-through">Watch</li>
<li class="items line-through">Shoes</li>
<li class="items line-through">Cake</li>
</ul>
This will add the event listener to all the children elements of the list and toggle the line-through class on the clicked one.
Keep in mind that when manipulating the DOM, you should wrap your JS code inside a DOMContentLoaded event listener to prevent selecting elements that are not yet present on the page.

Related

How to add event handlers to button elements rendered in an OL using Javascript and get the index position?

I'm trying to build a simple to do list application. I've linked my input field to an array which gets rendered out as an ordered list, and there are two buttons added to each item, one to state "completed" and one for "remove".
I've having difficulty with two parts: 1) adding event handlers to the buttons rendered out in the OL (I've managed to in the code below but I'm not sure if it's the right way. Part 2) identifying the index of the item so that I can modify it with the completed and remove buttons.
I've tried to use event delegation to tackle part 1 but I'm not sure if this is correct, and I don't know how to relate it to part 2.
document.getElementById("parent-list").addEventListener("click", function(e) {
if(e.target && e.target.nodeName == "BUTTON") {
console.log(e.target);
}
});
One way would be to add the index as an attribute of the LI element within your ordered list. Then you could access it like so:
document.getElementById("parent-list").addEventListener("click", function(e) {
if (e.target && e.target.nodeName == "BUTTON") {
const list = e.target.closest('li[data-idx]');
const idx = list.dataset.idx;
console.log(e.target, list, idx);
}
});
<ol id='parent-list'>
<li data-idx='1'><button class='action'>click me</button></li>
<li data-idx='2'><button class='action'>click me</button></li>
</ol>
Of course this is just a proof of concept, you'd need to add error handling and such.
First off, you should keep track of the items
const items = document.querySelectorAll('.my-classname');
This will create an items array where you can do all sorts of stuff, like .forEach(). This is great, because you need the index of the item as well.
That would solve part 2.
Now, for part 1..
You should add classnames to each buttons, like
<button class="completed-btn"></button>
<button class="remove-btn"></button>
The reason you need this is to be able to differentiate them and their handlers.
Next step is same as with items. You want to keep track of each button.
const completedButtons = document.querySelectorAll('.completed-btn');
const removeButtons = document.querySelectorAll('.remove-btn');
You can now add your handlers
completedButtons.forEach((button, i) => {
button.addEventListener("click", function (e) {
const item = items[i];
const target = e.target;
// do stuff
});
});
removeButtons.forEach((button, i) => {
button.addEventListener("click", function (e) {
const item = items[i];
const target = e.target;
// do stuff
});
});
The variable i will match the index of the clicked item's button, therefore will match the index of the item.

Closing one element when opening another within the same loop using Javascript

first time on here so i'll try my best to explain what I'm asking.
So I have 3 list items with the same class name. I've put them in a looping function so that when you click on one it will display a sub set of list items for that specific list item. I also have them inside an if statement that adds a new class name to the specific list item that was clicked. It allows opening and closing of the sub list items when you click the corresponding parent element.
My question is; how can I use this same principle of checking for the additional class name, when the user clicks any of the list items. In other words, I am trying to code it in a way that will allow me to close any of the open sub list items when the user clicks a new list item.
This is what I came up with but it doesn't know what button[i] is when I include it within the "click" function. What I was trying to do with this code is to take whatever list item was clicked, and then check the previous and next iterations of the class name "button" to see if any of the contain also contain the class name "clicked.
HTML
<div class="main">
<ul>
<li>One
<ul>
<li>One-1</li>
<li>One-2</li>
</ul>
</li>
<li>Two
<ul>
<li>Two-1</li>
<li>Two-2</li>
</ul>
</li>
<li>Three
<ul>
<li>Three-1</li>
<li>Three-2</li>
</ul>
</li>
</ul>
</div>
CSS
.main ul ul {
display: none;
}
.main ul ul li {
display: block;
}
Javascript
var button = document.getElementsByClassName("button");
for (i = 0; i < button.length; i++) {
button[i].addEventListener("click", function() {
var prevItem = button[i - 1];
var nextItem = button[i + 1];
if (prevItem.className !== "button") {
prevItem.className = "button";
prevItem.nextElementSibling.style.display = "none";
}
if (nextItem.className !== "button") {
nextItem.className = "button";
nextItem.nextElementSibling.style.display = "none";
}
if (this.className === "button") {
this.className += " clicked";
this.nextElementSibling.style.display = "block";
}
});
}
I am wanting to make this code usable no matter how many list items you add. So checking exactly button[0] button[1] and button[2] wasn't really an option, but I can see how button[i + 1] might not check every list item after it but rather just the next one. I tried adding another loop but ran into similar issues. anyway that's why I'm here. Thanks for any help in advance.
Since I am not sure whether I understood your question correctly, I quickly rephrase it in my own words.
Question: "I have an arbitrary number of list elements, of which each contains a button and a nested list. The button is always visible, the nested list is hidden by default. When the user clicks on a button, the corresponding nested list should be shown. At the same time, all other shown nested lists should be hidden again. How can I achieve this?"
The original HTML looks fine:
<div class="main">
<ul>
<li>One
<ul>
<li>One-1</li>
<li>One-2</li>
</ul>
</li>
<li>Two
<ul>
<li>Two-1</li>
<li>Two-2</li>
</ul>
</li>
<li>Three
<ul>
<li>Three-1</li>
<li>Three-2</li>
</ul>
</li>
</ul>
</div>
The CSS I did not fully understand, but I suggest the following:
.main ul ul {
display: none;
}
.main li.is-active ul {
display: block;
}
.main ul ul li {
display: block;
}
By adding the "is-active" class to an LI element, it is shown. This way, the CSS controls the visibility.
For the JavaScript part, I suggest this:
const buttonElements = Array.from(document.querySelectorAll('.button'));
buttonElements.forEach(buttonElement => {
buttonElement.addEventListener('click', () => {
const activeElements = Array.from(document.querySelectorAll('.is-active'));
activeElements.forEach(activeElement => {
activeElement.classList.remove('is-active');
});
buttonElement.parentElement.classList.add('is-active');
});
});
This solution assumes you can use newer versions of JavaScript/ECMAScript. Overall, it makes use of const and arrow functions.
First, we get all elements with the class "button" by using document.querySelectorAll(). Since the result is a NodeList and no array, we convert it using Array.from(). Afterwards, we loop through the array by using Array.prototpye.forEach(). We add an event listener for the "click" event. When a button is clicked, we search for all elements with the "is-active" class and for each one remove it. Finally, we add the "is-active" class to the parent element of the clicked button using Node.prototype.parentElement().
Here is another solution that works in older browsers:
var buttonElements = document.getElementsByClassName('button');
for (var i = 0; i < buttonElements.length; i++) {
buttonElements[i].addEventListener('click', function(event) {
var activeListElements = document.getElementsByClassName('is-active');
for (var i = 0; i < activeListElements.length; i++) {
activeListElements[i].setAttribute('class', '');
}
event.target.parentNode.setAttribute('class', 'is-active');
});
}
This is pretty much the same as the other approach but works with older versions of JavaScript.
Generally, the idea is to focus on an arbitrary sum of elements instead of an array with a specific length. In natural language something like: "Give me all buttons. For every button, add an event listener. When a button is clicked, give me all active list elements and remove their active status. Then, mark the list item above the button as active".
Hope this helps

how to select a specific list item from a ul javascript

I have a function that adds list items that all have the same id to a ul. I want to be able to click a specific list item and change the id of only the one I clicked.
so I'll call the function a few times and have it spit out:
thing1
thing2
thing3
then when I click thing2 the color changes from black to red.
edit: in response to request for the code I tried.
function addlist(){
var ask = prompt("what is the list item", "title");
document.getElementById("thing").innerHTML += "<li id='vid'><a href='#'>" + ask+ "</a></li>";
}
When you dynamically create the list items, as others have pointed out, you should give them different id="". I am assuming you could implement a solution where you ensure a unique ID and add an onmousedown attribute onto each <li>.
HTML
This assumes you add the onmousedown attribute to each <li> and have given each item a specific ID.
<ul>
<li id="theone" onmousedown="changeColor(this.id)">Click 1</li>
<li id="thetwo" onmousedown="changeColor(this.id)">Click 2</li>
<li id="thethree" onmousedown="changeColor(this.id)">Click 3</li>
</ul>
Javascript
function changeColor(id) {
//Do whatever you want to the specific element
var listitem = document.getElementById(id);
listitem.style.backgroundColor = "red";
}
Demo: http://jsfiddle.net/codyogden/t8xfeL0p/
You shouldn't use the same id. Instead give them all the same class. http://jsfiddle.net/anw66zu7/4/
HTML
<ul>
<li class="black">One</li>
<li class="black">Two</li>
<li class="black">Three</li>
</ul>
Javascript
window.onload = function() {
//when window loads
var blackelements = document.getElementsByClassName("black");
// get all elements with class "black"
for(var i=0; i < blackelements.length;i++) {
//create a for loop to go through each of these elements.
//the variable i increments until it reaches the amount of elements
//stored in the variable blackelements
var blackelement = blackelements[i];
//get the element with the index of the variable i
blackelement.addEventListener("click", function() {
//add an event listener to this element that checks for a mouse click
this.style.backgroundColor = "red";
//if clicked change the background color to red.
});
//close out of the event listener
}
//close out of the for loop
}
//close out of the onload function
Basically what happens is that it looks for all elements with the class "black" - it then loops through them and adds an event listener that looks for the "click" event to each of them. When you click it changes the background color of that specific item "this" to red.
First, your <li>s need to contain buttons to click and onclick call a function:
<input type="button" onclick="functionName(this)">Thing1</input>
In the function, you will call on the object and use the id function to change the id and the backgroundColor to change the color:
function functionName(obj)
{
obj.id = "new id"
obj.backgroundColor = "red"
}

Element is duplicated in a loop

This code move elements inside other elements to create a tree hierarchy.
<ul>
<li id="task_111" class="task"><a>task1</a></li>
<li id="task_222" data-in-task-group-id="333" class="task"><a>task3</a></li>
<li id="task_333" class="task task_group">
<a>task2</a>
<ul data-task-group-id="333" class="task_group_list"></ul>
</li>
<li id="task_444" data-in-task-group-id="333" class="task task_group">
<a>task4</a>
<ul data-task-group-id="444" class="task_group_list"></ul>
</li>
<li id="task_555" data-in-task-group-id="333" class="task"><a>task5</a></li>
</ul>
Loop that moves the elements:
$('li').each(function() {
task_group_id = $(this).attr('data-in-task-group-id');
if (task_group_id) {
$("li#task_" + task_group_id + " .task_group_list").append($(this));
}
})
All looks pretty simple but one element (task5) is copied wrong:
As you see task5 is placed inside two parent elements: the correct parent is task2, task4 should be empty.
Why is task5 is copied wrong inside task4?
JSfiddle to play around.
What happens is that task4 - including its <ul> element with a class of task_group_list - is moved into task3. Then, when it's the turn of task5 to be moved, there are multiple elements that match this selector:
li#task_333 .task_group_list
As stated in the doc for append:
The .append() method inserts the specified content as the last child of each element in the jQuery collection
Since you have multiple elements you get task5 appended to each of them, cloning the element as necessary.
You'll want to change your selector so that it only matches the <ul> that's an immediate child, rather than a descendent, of that <li> element:
li#task_333 > .task_group_list
The code for the loop would become:
$('li').each(function() {
task_group_id = $(this).attr('data-in-task-group-id');
if (task_group_id) {
$("li#task_" + task_group_id + " > .task_group_list").append($(this));
}
})
Updated jsFiddle

Need a way to filter divs based on user input and at the same time keep event binding on them

At the moment I'm filtering an array with array.filter, passing in a regex.
The array I am filtering is based on the contents of a div.
Each child div of that div has a text which I extract with $.text() and put into its own array so I can filter it. Then I output the contents of the filtered array.
The problem is that the filtering needs an array of strings, and thus I make separate array which I filter and then print to the html. But, then when a user clicks one of the items in the list, nothing happens, because the user clicks on div which isn't bound to an event. I do the binding on document ready, and I use the bound event to get the value of the data-item-pageid attribute.
I need a way to this which gives me access to the data-item-pageid of the clicked element and still is fast. I would prefer not to bind the events all over again each time the user types, so I think the logic needs to be changed. I need to hide the divs that do not match the regex.
this is the html:
<div class="content">
<div class="categories">
<div class="item" data-item-pageid="1">text1</div>
<div class="item" data-item-pageid="2">text2</div>
<div class="item" data-item-pageid="3">text3</div>
</div>
<div class="categories">
<div class="item" data-item-pageid="4">text4</div>
<div class="item"data-item-pageid="5">text5</div>
<div class="item"data-item-pageid="6">text6</div>
</div>
</div>
<div class="filter_result"></div>
This is the JavaScript code:
// Binds the click event for each category item
$('.category_item').click(function() {
console.log($(this));
});
...
// Array for filter
filterArray = [];
for (var i = 0; i < $categoryItems.length; ++i) {
filterArray[i] = $($categoryItems[i]).text();
}
...
function filterList(key) {
var result = filterArray.filter(/./.test.bind(new RegExp(key, 'i')));
if (key != '' || key) {
var markup = []; //ugh
for (var i = 0; i < result.length; ++i) {
markup += '<div class="category_item">' + result[i] + '</div>';
}
if ($categoryItems.is(':visible'))
$categoriesDiv.toggle();
$filteredResult.html(markup);
} else {
$filteredResult.html('');
if (!$categoryItems.is(':visible'))
$categoriesDiv.toggle();
}
}
You could do delegated event handling
$(CONTAINER_SELECTOR).on('click', CHILD_SELECTOR, function(){
/* Your click handler*/
});
Where CONTAINER_SELECTOR is an element that is not added/removed so it retains its listeners
CHILD_SELECTOR is the selector for the children which are added/removed, of whose clicks we wish to listen to
In your case $(CONTAINER_SELECTOR) could be $filteredResult and CHILD_SELECTOR .category_item.
For better understanding of delegated events check out the official docs or have a look at one of my other answers on delegated events.
UPDATE
You could easily retrieve the filtered list using:
$elements = $('.item');
$filteredList = $elements.filter(
function(index){
return conditionIsMetFor($(this).text()); // this refers to the element under iteration
}
);
Then you cant take $filteredList and:
$.clone() it
iterate over it and create new elements based on the $.data() they contain
etc.

Categories

Resources