JavaScript - weird bug doesn't remove element - javascript

I have a weird problem with my simple to-do app. Its main features are adding new elements to "ul" and clicking on "li" to change its class. After clicking element it also adds an "i" with font-awesome icon and after clicking it again it should remove it. It works fine when I click added elements from top to bottom, but when I click from bottom to top, "i" is removed only from the first element from top. I can't figure out why is that, so I would appreciate any help.
Here is live demo where you can reproduce this bug:
https://michalgrochowski.github.io/to-do/
And here is the code responsible for that action:
document.getElementById("list").addEventListener("click", function(e) {
if (e.target && e.target.matches("li.item")) {
e.target.className = "itemDone";
var check = document.createElement("i");
check.className = "fa fa-check done";
e.target.appendChild(check);
} else if (e.target && e.target.matches("li.itemDone")) {
e.target.className = "item";
var done = document.getElementsByClassName("done");
var i;
for (i = 0; i < done.length; i++) {
e.target.removeChild(done[i]);
};
};
});
I've tested it in firefox and chrome, so it's not browser-related, it must be something in the code. I was thinking maybe the problem is with the loop inside the function, but I don't know how to change it.

var done = document.getElementsByClassName("done"); gets a list of all the .done elements in your document (including those who aren't child elements of e.target). So when executing e.target.removeChild(done[i]) if the current done[i] is a child of e.target, it will be removed, otherwise it will throw an error saying that the element done[i] is not a child of e.target. Try this:
var done = e.target.getElementsByClassName("done");
Note: if there could be sub-elements (children of children ...) of e.target that have the class .done. This will throw an error too. Because then the sub-children won't be direct children of e.target. removeChild work only on direct children. You can avoid trouble by using this:
var done = e.target.children; // to get the direct children only
var i;
for (i = 0; i < done.length; i++) {
if(done[i].matches('.done') // if it matches the criteria
e.target.removeChild(done[i]); // remove it
}

Related

Touchstart event fires only once

I'm trying to write a code to change the innerHTML of a single element according to the elements touched by the user. The text has to be taken from the first child of the element touched. After the function is fired once, it doesn't fire anymore.
I wrote this:
let title = document.getElementById("title"); //element to be changed
let elements = document.getElementsByClassName("elements"); //touchable elements
for (i = 0; i < elements.length; i++) {
let ele = elements[i];
ele.addEventListener("touchstart", function() {
title.innerHTML = ele.firstChild.innerHTML;
})
}
I wrote an analogous code to work on desktop and, with click events, it works.
Where am I doing wrong?

Remove class based on elements but not others

I have these sections on this side scrolling site. And want to add a class which will change styling depending if you're on a certain section.
I'm working on this function. The top is what determines the section of the side scroller you are viewing.
The let variables and below is where it stops working. I'm trying to have it so if a nonHome ID section is clicked, for example "slide-1", then add the class 'nav-visibilty'. If they are a match "slide-2" and "slide-2" then remove said class. Am I close?
https://codepen.io/mikayp-the-styleful/pen/NWPxoXR?editors=1111
setTimeout(function(){
for (i=0; i < nonHome.length; i++ ){
if (nonHome[i].id != nonHomeID){
nonHome[i].classList.add("nav-visibility");
console.log('add')
} else{
nonHomeID.classList.remove("nav-visibility");
console.log('rem')
}
}
I am still not totally clear on the behavior that you want, but there are two errors in the code that can be fixed:
It seems like you are always using 'slide-2' instead of the slideId in your event handler.
As mentioned in a comment, nonHomeID is being used incorrectly in your comparison (it is either a string or an element, but you are using it as if it was a string in the if condition, and as the element in the else branch.) Here I have kept it as an element and renamed it for clarity.
Fixing these errors results in code that applies the nav-visibility class to all slides except the one selected by the button. Is that the desired behavior?
let nonHome = document.querySelectorAll(".slide-container section");
let nonHomeSelected = document.getElementById(slideId);
var i;
setTimeout(function() {
for (i = 0; i < nonHome.length; i++) {
if (nonHome[i] != nonHomeSelected) {
nonHome[i].classList.add("nav-visibility");
console.log("add");
} else {
nonHome[i].classList.remove("nav-visibility");
console.log("rem");
}
}
}, 1000);
Edit to add: If the goal is to add nav-visibility to all only the specific slideId, you should not be adding in a loop, i.e. you need to pull your check for whether the slide is Home outside the loop. There are conceptually two steps here: remove the class from all elements that are no longer to have it, then add the class to the element that needs it.
let slideToAddVisibilityTo = document.getElementById(slideId)
let homeSlide = document.getElementById('slide-2')
let allSlides = document.querySelectorAll(".slide-container section")
for (let i = 0; i < allSlides.length; ++i)
allSlides[i].classList.remove('nav-visiblity')
if (slideToAddVisibilityTo != homeSlide)
slideToAddVisibilityTo.classList.add('nav-visibility')
Just hide them all, then show the clicked one:
function showSection(id) {
var sections = document.getElementsByTagName("section");
for(var i=0; i<sections.length; i++) sections[i].classList.remove("nav-visibility");
var current = document.getElementById(id);
current.classList.add("nav-visibility");
}
Example: showSection("foo") will remove nav-visibility from all sections, then add it to the section with id foo.

Attaching eventListener to dynamic elements in Javascript

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.

select one div and unselected another one

Working on the div's. I am doing changes that if one div is selected, it should deselect the another div.
The div's defined are in ul li
Like in every li, there is a div with same classname called as inonset. Now the div which is already selected is having a class of inonset isactive.
I am adding a onclick function on every <div class="inonset" onclick="selectme(divid)"> to select it and unselect other, but how the other will be unelected, I am lost
Here is the fiddle
Not have not added the javascript code yet, but from the code, it will clear what I am trying to do.
You will see initially one selected and others are there, i just trying to selected any other one and deselect the previous one, Hope my questions makes sense
Worst thing: I cannot use Jquery, Only Dojo or Plain Javascript
Update #1
<div class="optionsBox" align="left" id="Invoicing" onclick="chooseDiv(this);">
function chooseDiv(oObj){
var d = document.getElementsByClassName("ul li div");
alert(d.className);
It is giving me undefined and not looping over the classes
the div is having classes like
iv class="headerSelected isactive">
where isactive needs to be removed from the previous selected div and add to the newly selected Div
First u need to change on click event for this:
onclick="selectme(this)"
And then in function:
function selectme(oObj){
var d = document.getElementById("ul li div");
d.removeAttribute("class");
oObj.className = oObj.className + " otherclass";
}
It should work fine
I am not sure whether the answer is still required or not. However, posting my approach of doing it.
function removeClass(className) {
// convert the result to an Array object
var els = Array.prototype.slice.call(
document.getElementsByClassName(className)
);
for (var i = 0, l = els.length; i < l; i++) {
var el = els[i];
el.className = el.className.replace(
new RegExp('(^|\\s+)' + className + '(\\s+|$)', 'g'),
'$1'
);
}
}
var elements = document.getElementsByClassName("inoneset");
for (var i = 0; i < elements.length; i++) {
(function(i) {
elements[i].onclick = function() {
removeClass("isactive");
//this.setAttribute("class", this.getAttribute("class") + " isactive");
var headerElem = this.getElementsByClassName("headerSelected")[0];
headerElem.setAttribute("class", headerElem.getAttribute("class") + " isactive");
var addressElem = this.getElementsByClassName("selDelAddress")[0];
addressElem.setAttribute("class", addressElem.getAttribute("class") + " isactive");
var footerElem = this.getElementsByClassName("footerSelected")[0];
footerElem.setAttribute("class", footerElem.getAttribute("class") + " isactive");
};
})(i);
}
Fiddle - http://jsfiddle.net/38nv5rft/18/
Reasoning
As you can see, there is a remove class function, that removes the class from the elements in the document. One can update it as per the requirement.
Then the main logic, which gets all the elements, iterate on them and bind the click function.
In click function, we are removing the inActive class from every element and then for current block, adding inActive class. Please note, as per the fiddle, I did not find the class on inoneset elements being updated, hence, commented out the code.
Important Point
Click event bubbles, hence, click on elements with showDelete and showDialog click functions will bubble the event to click event of inoneset i.e. click handler of inoneset will also be triggered. In order to stop the propagation of event to it, use event.stopPropogation() in showDelete and showDialog functions.

How to append an element, all its children, and all classes of the parent and children with jQuery

I have a function that is successful in removing an element and appending it elsewhere on the page as successful. The problem is that as soon as the document is ready jQuery adds classes and attributes to the children that upon moving are lost. I need these classes and attributes to remain after removing and appending. I have thought about calling the original function that adds the classes, but the problem is they are key based and rely on their position prior to the move, calling it after changes the key and thus will add brand new and different classes.
The classes adding jQuery is pretty standard:
$(function(){
$("div").each(function(key){
if ($(this).hasClass("container")){
$(this).find("ul").addClass("parent" + key);
$(this).find(".container-item").attr("parentClass", ".parent" + key);
};
});
});
The remove/append function:
function copy_item(draggable, target){
var account = clone_items(draggable);
//$('#'+name.uid).remove();
$('#'+name.uid).hide();
target.append(make_div(name, true, true));
//$(draggable).children().attr("class", ($(draggable).children().attr("class")));
}
function make_div(name, drag, drop){
var newdiv = document.createElement('div');
newdiv.setAttribute('id', name.uid);
newdiv.appendChild(make_h3(name.username));
ul = document.createElement('ul');
ul.setAttribute("class", "domain_list");
newdiv.appendChild(ul);
for (j = 0; j < name.domains.length; ++j) {
ul.appendChild(make_li(name.domains[j], drag));
}
return newdiv;
}
The end result in the HTMl is basically:
<div class="container">
<ul class="parent0">
<li parentClass="parent0">
<li parentClass="parent0">
When recreating this structure, I need to have the class "parent0" and the parentClass attribute intact. Above you can see I've tried hiding the element, ensuring that it still stays a valid element with the correct classes/attributes, but in the end that still didn't work out. Ideally, I could remove the element entirely and recreate it with the correct classes.
If I am correct in my understanding of what you are trying to do, you do not need to .remove() and recreate the element in order to move it. You can just do this:
function copy_item(draggable, target) {
// not sure what this variable is for
// as you don't seem to be using it?
var account = clone_items(draggable);
// ...however, appending an existing
// element to another will 'move' it
// and preserve all of it's properties
target.append($('#' + name.uid));
}

Categories

Resources